/** * @fileoverview In-memory collector for tool-call traces. * * Each task has its own append-only log of `ToolCallRecord`s. The collector * exposes simple getters used by the HTTP layer; eviction keeps the memory * footprint bounded (oldest tasks are dropped past `maxStoredTasks`). */ import type { TaskSession, TaskShowConfig } from "./types.js"; /** * ToolCallCollector * * Owns the `Map` and exposes: * - recordToolCall(): append a single tool call record * - getSession(): read-only snapshot * - listSessions(): sorted snapshot for the index page * - finalizeOnTaskUpdate(): mark session as completed/failed and stamp the * visualization URL (called from the `task:after.update` hook) * * The collector is intentionally synchronous apart from the explicit async * hook handlers — that keeps the mental model simple. */ export declare class ToolCallCollector { private readonly sessions; private readonly cfg; private readonly logger; /** callback used by the server to know when data has changed (no-op in tests). */ private readonly onChange?; constructor(cfg: TaskShowConfig, options?: { onChange?: () => void; logPrefix?: string; }); /** * Get a defensive snapshot of one task's session (or undefined). */ getSession(taskId: number): TaskSession | undefined; /** * List every session, sorted by `startedAt` descending (newest first). */ listSessions(): TaskSession[]; /** * Convenience accessor used by the server to build per-task URLs. */ size(): number; /** * Append a `tool:after.execute` invocation into the right session. * * - If `explicitTaskId` is supplied (or recovered from the hook metadata), * the record lands under that task. * - Otherwise we open a synthetic session so demo runs without a * TaskComponent still produce a nice flowchart. */ /** * v0.6.6+: returns `null` when no env-context task id is present * (i.e. no upstream task in the roy-agent task system). Interactive * sessions without `task_create` are intentionally dropped — the * synthetic "Interactive session" aggregation was hiding the fact * that those tool calls belong to no real task. */ recordToolCall(opts: { toolName: string; args: Record; success: boolean; outputPreview: string; error?: string; durationMs: number; timestamp: number; iteration?: number; metadata?: Record; ctx?: any; explicitTaskId?: number; }): number | null; /** * Mark a task as completed/failed and freeze its `endedAt`. Used by the * `task:after.update` hook. * * If no session exists for the task yet (e.g. the task completed without * any recorded tool calls — pure response tasks), we create a synthetic * session on the fly so the visualization page can still render a * meaningful "task finished" card. `startedAt` is set to one second * before `endedAt` so the duration widget renders as ~1s rather than 0s. * * Returns the (possibly newly-created) session, or undefined only if * taskId is invalid. */ finalizeOnTaskUpdate(opts: { taskId: number; newStatus: TaskSession["status"]; title?: string; timestamp?: number; }): TaskSession | undefined; /** * Bulk-finalize every session whose status is still `"running"`. * * Why: when the host's interactive session ends (plugin dispose) without * firing `task:after.update`/`task:after.complete` for every session, * those sessions would otherwise remain `running` forever — the browser * shows them with the "running" spinner. We sweep them here so they * transition to `"completed"` with `endedAt = dispose-time`. * * Already-terminal sessions (`completed`/`failed`/`cancelled`) are NOT * touched — that keeps host-driven finalization authoritative. * * Returns the snapshot list of finalized sessions so the caller can * broadcast `task.completed` SSE events for each one. */ finalizeRunningSessions(opts?: { title?: string; timestamp?: number; }): TaskSession[]; /** * Stamp or refresh a session's title (used by `recordTaskTitle` from any * context that happens to know the title, e.g. task:before.create). */ setTaskTitle(taskId: number, title: string): void; /** * v2.5.12 (Task #2951): patch a session's `context` field in place. * Used by the file-tree handler when an async operations-cache * lookup yields a `projectPath` that the live session never * received via the `tool:after.execute` payload. * * Merges into the existing context (other host-supplied keys are * preserved). Returns `true` when the patch landed on an existing * session, `false` when there is no session to patch. */ patchSessionContext(taskId: number, patch: Record): boolean; /** * Insert or replace a session. Used by `task:before.create` / * `task:after.create` to mint a fresh TaskSession before any tool call * is recorded — gives the frontend a stable taskId/title even for tasks * that complete without firing any tool. * * If a session already exists for the taskId, it is overwritten with the * new value (callers should pass a snapshot built from the existing * session if they want to preserve toolCalls). */ upsertSession(session: TaskSession): void; /** * Clear all data. Useful in tests and when the plugin is disposed. */ clear(): void; /** * Drop the oldest non-running sessions once we exceed `maxStoredTasks`. * * "Running" sessions are never evicted because the user may still be * interacting with the page; we simply remove the oldest `completed` * / `failed` / `cancelled` sessions. */ private evictIfNeeded; /** * Defensive deep clone so external code never mutates internal state. * We use JSON because all payloads must already be JSON-serializable * (they come from the tool hook / TaskComponent and include args/output * previews). */ private cloneSession; } //# sourceMappingURL=collector.d.ts.map