Add sssf skill, installable via the skills CLI
Port the sssf skill from ~/.agents/skills/sssf into this repo so it can be distributed and installed with the skills CLI (skills add INDigitalStudio/skills --skill sssf). - Copy the skill (SKILL.md, cookbooks, references, scripts, templates, and the visualizer app source) into sssf/. - Gitignore build/runtime artifacts: the visualizer's node_modules/ and dist/, Python bytecode, and the machine-specific repos.json. - Make the skill location-independent: install.py now stamps the skill's real path into the stamped justfile's skill_dir (replacing the hardcoded ~/.agents/skills/sssf), so 'just obs' finds the visualizer wherever the CLI installed the skill. - Update cookbooks to use <skill>/scripts/... instead of the hardcoded path, and document the skills CLI install command. - Update the repo README with install instructions.
This commit is contained in:
parent
a42608f602
commit
2cc766aabe
98 changed files with 12508 additions and 0 deletions
131
sssf/apps/visualizer/src/lib/events.ts
Normal file
131
sssf/apps/visualizer/src/lib/events.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import type { AgentStartPayload, EventRow, ToolCallPayload } from './types'
|
||||
|
||||
// ── Event dot colors ─────────────────────────────────────────────────────────
|
||||
// One color per event type, shared by the session-card timelines and the phase
|
||||
// detail list. gate_fail reads as an error signal on purpose.
|
||||
|
||||
export const EVENT_DOT_COLORS: Record<string, string> = {
|
||||
agent_start: '#c89bff',
|
||||
tool_call: '#5ad2dd',
|
||||
handoff: '#94a3ff',
|
||||
agent_end: '#4ade80',
|
||||
error: '#ff6f67',
|
||||
gate_fail: '#ff6f67',
|
||||
}
|
||||
|
||||
export function dotColor(type: string | null): string | null {
|
||||
if (!type) return null
|
||||
return EVENT_DOT_COLORS[type] ?? null
|
||||
}
|
||||
|
||||
// ── Agent lane colors ────────────────────────────────────────────────────────
|
||||
// Config color wins (agents[].color from the API, or the agent_start payload
|
||||
// for in-flight agents); the palette below covers dbs written before the
|
||||
// color column existed.
|
||||
|
||||
export const AGENT_FALLBACK_COLORS = ['#c89bff', '#5ad2dd', '#94a3ff', '#e8b64a', '#f2a2c4']
|
||||
|
||||
export function agentColor(
|
||||
configColor: string | null | undefined,
|
||||
payloadColor: string | null | undefined,
|
||||
index: number,
|
||||
): string {
|
||||
return (
|
||||
configColor ??
|
||||
payloadColor ??
|
||||
AGENT_FALLBACK_COLORS[index % AGENT_FALLBACK_COLORS.length] ??
|
||||
'#c89bff'
|
||||
)
|
||||
}
|
||||
|
||||
/** "#c89bff" + alpha → rgba() usable in inline styles. Invalid input → transparent. */
|
||||
export function hexAlpha(hex: string, alpha: number): string {
|
||||
const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim())
|
||||
if (!m || !m[1]) return 'transparent'
|
||||
const n = Number.parseInt(m[1], 16)
|
||||
return `rgba(${(n >> 16) & 0xff}, ${(n >> 8) & 0xff}, ${n & 0xff}, ${alpha})`
|
||||
}
|
||||
|
||||
// ── Payload parsing ──────────────────────────────────────────────────────────
|
||||
|
||||
export function parsePayload(raw: string | null | undefined): Record<string, unknown> | null {
|
||||
if (!raw) return null
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(raw)
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, unknown>
|
||||
}
|
||||
} catch {
|
||||
/* legacy or truncated payloads render raw */
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function parseToolCall(e: EventRow): ToolCallPayload | null {
|
||||
const payload = parsePayload(e.payload_json)
|
||||
if (!payload || typeof payload.tool !== 'string') return null
|
||||
return payload as ToolCallPayload
|
||||
}
|
||||
|
||||
export function parseAgentStart(e: EventRow): AgentStartPayload | null {
|
||||
return parsePayload(e.payload_json) as AgentStartPayload | null
|
||||
}
|
||||
|
||||
// Args keys most likely to BE the call, in priority order — a bash command, a
|
||||
// file path, a search pattern. Used to build the one-line label.
|
||||
const ARG_LABEL_KEYS = [
|
||||
'command',
|
||||
'cmd',
|
||||
'file_path',
|
||||
'path',
|
||||
'pattern',
|
||||
'query',
|
||||
'url',
|
||||
'prompt',
|
||||
'description',
|
||||
]
|
||||
|
||||
const LABEL_MAX = 160
|
||||
|
||||
function oneLine(value: string): string {
|
||||
const flat = value.replaceAll(/\s+/g, ' ').trim()
|
||||
return flat.length > LABEL_MAX ? `${flat.slice(0, LABEL_MAX)}…` : flat
|
||||
}
|
||||
|
||||
/** One-line summary of a tool's args: the call itself, not a JSON dump. */
|
||||
export function argsSummary(args: Record<string, unknown> | undefined): string {
|
||||
if (!args) return ''
|
||||
for (const key of ARG_LABEL_KEYS) {
|
||||
const v = args[key]
|
||||
if (typeof v === 'string' && v.trim() !== '') return oneLine(v)
|
||||
}
|
||||
const parts: string[] = []
|
||||
for (const [key, v] of Object.entries(args)) {
|
||||
if (v == null) continue
|
||||
parts.push(`${key}=${typeof v === 'string' ? v : JSON.stringify(v)}`)
|
||||
}
|
||||
return oneLine(parts.join(' '))
|
||||
}
|
||||
|
||||
/**
|
||||
* Exact-call row label for an event.
|
||||
* Rich tool_call → "bash: bun test src". Legacy payloads fall back to
|
||||
* the event name plus whatever hint the payload carries.
|
||||
*/
|
||||
export function eventLabel(e: EventRow): string {
|
||||
if (e.type === 'tool_call') {
|
||||
const call = parseToolCall(e)
|
||||
if (call?.tool) {
|
||||
// New-tracer rows already carry the human label in events.name
|
||||
// ("bash: ls -la src") — prefer it over re-deriving.
|
||||
if (e.name?.startsWith(call.tool)) return oneLine(e.name)
|
||||
const summary = argsSummary(call.args)
|
||||
return summary ? `${call.tool}: ${summary}` : call.tool
|
||||
}
|
||||
const legacy = parsePayload(e.payload_json)
|
||||
if (legacy && typeof legacy.pi_event === 'string') {
|
||||
return `${e.name ?? 'tool'} ${legacy.pi_event}`
|
||||
}
|
||||
}
|
||||
return e.name ?? e.type ?? ''
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue