@kolisachint/hoocode-agent 0.4.55 → 0.4.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.57] - 2026-06-14
4
+
5
+ ## [0.4.56] - 2026-06-13
6
+
3
7
  ## [0.4.55] - 2026-06-12
4
8
 
5
9
  ### Added
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/core/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,eAAO,MAAM,sBAAsB,EAAE,aAAwB,CAAC","sourcesContent":["import type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\n\nexport const DEFAULT_THINKING_LEVEL: ThinkingLevel = \"medium\";\n"]}
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/core/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,eAAO,MAAM,sBAAsB,EAAE,aAAqB,CAAC","sourcesContent":["import type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\n\nexport const DEFAULT_THINKING_LEVEL: ThinkingLevel = \"off\";\n"]}
@@ -1,2 +1,2 @@
1
- export const DEFAULT_THINKING_LEVEL = "medium";
1
+ export const DEFAULT_THINKING_LEVEL = "off";
2
2
  //# sourceMappingURL=defaults.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/core/defaults.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,sBAAsB,GAAkB,QAAQ,CAAC","sourcesContent":["import type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\n\nexport const DEFAULT_THINKING_LEVEL: ThinkingLevel = \"medium\";\n"]}
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/core/defaults.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,sBAAsB,GAAkB,KAAK,CAAC","sourcesContent":["import type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\n\nexport const DEFAULT_THINKING_LEVEL: ThinkingLevel = \"off\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"team-view.d.ts","sourceRoot":"","sources":["../../src/core/team-view.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * hooteams team client (`--team <url>`).\n *\n * Connects to a running hooteams server, registers every role as a\n * kind=\"role\" agent in the task store, and maps the server's TeamEvent SSE\n * stream onto task-store patches so the task panel's existing \"teams\" view\n * shows live role state. On top of that mirror the connection exposes\n * steering (POST /steer) and an event subscription used by the attach\n * side-panel — both share the single /events stream; no second SSE\n * connection is ever opened.\n *\n * The connection is best-effort by design — a connect failure or a later\n * drop logs a warning and never blocks (or crashes) the main agent. At most\n * one SSE connection (to /events) is open at any time.\n */\n\nimport { type TaskAgentState, type TaskStatus, taskStore } from \"./task-store.js\";\n\n/** Shape of GET /status: coarse per-role status keyed by role name. */\nexport type TeamStatusSnapshot = Record<string, { status?: string; lastEventType?: string }>;\n\n/** One frame of GET /events: a hoocode AgentEvent tagged with its producer. */\nexport interface TeamViewEvent {\n\ttype: string;\n\trole: string;\n\tagentId?: string;\n\tts?: number;\n\ttoolName?: string;\n\targs?: unknown;\n\tisError?: boolean;\n\t/** Streaming assistant-message delta carried by message_update events. */\n\tassistantMessageEvent?: { type?: string; delta?: string };\n\tmessage?: {\n\t\trole?: string;\n\t\terrorMessage?: string;\n\t\tusage?: { input?: number; output?: number; cost?: { total?: number } };\n\t};\n\t/** Task lifecycle fields carried by task_* events from the orchestrator. */\n\ttaskId?: string;\n\t/** Approval gate carried by task_paused. */\n\tquestion?: string;\n\toptions?: string[];\n\t/** Answer carried by task_resumed. */\n\tchosenOption?: string;\n\t/** \"done\" | \"error\" on task_finished. */\n\tstatus?: string;\n\t/** Run id carried by dag_complete / dag_failed. */\n\trunId?: string;\n}\n\n/** One unanswered approval gate from GET /tasks/pending. */\nexport interface TeamPendingApproval {\n\ttaskId: string;\n\tquestion: string;\n\toptions: string[];\n}\n\n/** hooteams AgentStatus word → task panel agent state. */\nfunction stateFromStatus(status: string | undefined): TaskAgentState {\n\tswitch (status) {\n\t\tcase \"idle\":\n\t\t\treturn \"idle\";\n\t\tcase \"thinking\":\n\t\tcase \"streaming\":\n\t\t\treturn \"active\";\n\t\tcase \"tool\":\n\t\t\treturn \"running\";\n\t\tcase \"done\":\n\t\t\treturn \"done\";\n\t\tcase \"error\":\n\t\t\treturn \"failed\";\n\t\tcase \"paused\":\n\t\t\treturn \"waiting\";\n\t\tdefault:\n\t\t\treturn \"idle\";\n\t}\n}\n\nfunction taskStatusFromState(state: TaskAgentState): TaskStatus {\n\tswitch (state) {\n\t\tcase \"done\":\n\t\t\treturn \"done\";\n\t\tcase \"failed\":\n\t\t\treturn \"failed\";\n\t\tcase \"idle\":\n\t\t\t// Idle is settled, not queued: a \"pending\" task here would survive every\n\t\t\t// taskStore.reset() and pin the pane at \"working\" for the whole session.\n\t\t\treturn \"done\";\n\t\tdefault:\n\t\t\treturn \"in_progress\";\n\t}\n}\n\n/** Only these states represent activity worth a task row of its own. */\nfunction stateWarrantsTask(state: TaskAgentState): boolean {\n\treturn state === \"active\" || state === \"running\" || state === \"failed\" || state === \"waiting\";\n}\n\n/**\n * Maps team status snapshots and TeamEvents onto task-store patches.\n *\n * Each role owns one roster entry (id `team:<role>`) and at most one task whose\n * title tracks the role's latest activity. Tasks exist only while a role is\n * actually doing something (active/running, or failed so the error is visible);\n * idle roles keep their roster entry but no task — the panel's teams lens\n * renders them as placeholder groups, so a quiet team reads as an idle roster\n * instead of pinning the pane at \"working\". Entries are re-created on demand\n * because taskStore.reset() wipes finished tasks between user turns.\n */\nexport class TeamViewMapper {\n\tprivate readonly store: typeof taskStore;\n\tprivate readonly taskIds = new Map<string, number>();\n\n\tconstructor(store: typeof taskStore = taskStore) {\n\t\tthis.store = store;\n\t}\n\n\t/** Register roles from a GET /status snapshot. */\n\tapplyStatus(snapshot: TeamStatusSnapshot): void {\n\t\tfor (const [role, info] of Object.entries(snapshot)) {\n\t\t\tconst state = stateFromStatus(info?.status);\n\t\t\tthis.ensureRole(role, state, info?.lastEventType ?? \"connected\");\n\t\t\tthis.patchRole(role, state);\n\t\t}\n\t}\n\n\t/** Map one TeamEvent from GET /events onto the store. */\n\tapplyEvent(event: TeamViewEvent): void {\n\t\tif (!event || typeof event.role !== \"string\" || event.role.length === 0) return;\n\t\t// Dag settlement events are tagged role=\"orchestrator\" — they describe\n\t\t// the run, not a member, and must not create a phantom roster entry.\n\t\tif (event.type === \"dag_complete\" || event.type === \"dag_failed\") return;\n\t\tconst role = event.role;\n\t\tswitch (event.type) {\n\t\t\tcase \"agent_start\":\n\t\t\tcase \"turn_start\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"thinking\");\n\t\t\t\tthis.patchRole(role, \"active\", \"thinking\");\n\t\t\t\tbreak;\n\t\t\tcase \"message_start\":\n\t\t\tcase \"message_update\":\n\t\t\tcase \"message_end\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"responding\");\n\t\t\t\tthis.patchRole(role, \"active\");\n\t\t\t\tbreak;\n\t\t\tcase \"tool_execution_start\":\n\t\t\t\tthis.ensureRole(role, \"running\", `tool: ${event.toolName ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"running\", `tool: ${event.toolName ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"tool_execution_end\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"thinking\");\n\t\t\t\tthis.patchRole(role, \"active\", \"thinking\");\n\t\t\t\tbreak;\n\t\t\tcase \"turn_end\":\n\t\t\t\tif (event.message?.role === \"assistant\" && event.message.errorMessage) {\n\t\t\t\t\tthis.ensureRole(role, \"failed\", \"error\");\n\t\t\t\t\tthis.patchRole(role, \"failed\", \"error\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"agent_end\": {\n\t\t\t\t// A failed run stays failed; agent_end only marks clean completions\n\t\t\t\t// (mirrors hooteams' own status tracking).\n\t\t\t\tconst failed = this.store.agents().find((a) => a.id === this.agentId(role))?.state === \"failed\";\n\t\t\t\tif (!failed) {\n\t\t\t\t\tthis.ensureRole(role, \"done\", \"idle\");\n\t\t\t\t\tthis.patchRole(role, \"done\", \"idle\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"task_started\":\n\t\t\t\tthis.ensureRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"task_paused\": {\n\t\t\t\tconst title = `awaiting approval: ${event.question ?? \"?\"}`;\n\t\t\t\tthis.ensureRole(role, \"waiting\", title);\n\t\t\t\tthis.patchRole(role, \"waiting\", title);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"task_resumed\":\n\t\t\t\tthis.ensureRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"task_finished\": {\n\t\t\t\tconst state = event.status === \"error\" ? \"failed\" : \"done\";\n\t\t\t\tthis.ensureRole(role, state, state === \"failed\" ? \"error\" : \"idle\");\n\t\t\t\tthis.patchRole(role, state, state === \"failed\" ? \"error\" : \"idle\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// Unknown event types still prove the role exists.\n\t\t\t\tthis.ensureRole(role, \"idle\", event.type);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate agentId(role: string): string {\n\t\treturn `team:${role}`;\n\t}\n\n\t/**\n\t * Make sure the role's roster entry exists, plus its task when the state\n\t * warrants one (reset() may have dropped both). Idle/done states never\n\t * create a task — only patch one that live activity already opened.\n\t */\n\tprivate ensureRole(role: string, state: TaskAgentState, title: string): void {\n\t\tconst id = this.agentId(role);\n\t\tthis.store.upsertAgent({ id, name: role, kind: \"role\", state });\n\t\tconst taskId = this.taskIds.get(role);\n\t\tconst existing = taskId !== undefined ? this.store.list().find((task) => task.id === taskId) : undefined;\n\t\tif (!existing && stateWarrantsTask(state)) {\n\t\t\tconst task = this.store.create(title, { agent: id });\n\t\t\tthis.store.update(task.id, { status: taskStatusFromState(state) });\n\t\t\tthis.taskIds.set(role, task.id);\n\t\t}\n\t}\n\n\tprivate patchRole(role: string, state: TaskAgentState, title?: string): void {\n\t\tthis.store.patchAgent(this.agentId(role), { state });\n\t\tconst taskId = this.taskIds.get(role);\n\t\tif (taskId === undefined) return;\n\t\tthis.store.update(taskId, { status: taskStatusFromState(state), ...(title !== undefined ? { title } : {}) });\n\t}\n}\n\nexport interface TeamViewOptions {\n\t/** Warning sink; defaults to console.error. */\n\twarn?: (message: string) => void;\n\t/** Store override for tests. */\n\tstore?: typeof taskStore;\n\t/** Delay between reconnect attempts in ms (default 5000). */\n\tretryDelayMs?: number;\n}\n\nexport interface TeamViewConnection {\n\t/** Close the SSE connection and stop reconnecting. */\n\tstop(): void;\n\t/** POST /steer { role, message }. Rejects on network or HTTP error. */\n\tsteer(role: string, message: string): Promise<void>;\n\t/**\n\t * Answer a paused task: POST /tasks/:taskId/resume { option, feedback? }.\n\t * Rejects with \"answered elsewhere\" on 409 (first answer wins across\n\t * surfaces) and with HTTP/network errors otherwise.\n\t */\n\tresume(taskId: string, option: string, feedback?: string): Promise<void>;\n\t/**\n\t * GET /tasks/pending — gates that opened before we attached. Resolves to\n\t * [] when the server has no active run (404) so callers need no special\n\t * casing.\n\t */\n\tpendingApprovals(): Promise<TeamPendingApproval[]>;\n\t/**\n\t * Subscribe to every TeamEvent delivered by the shared /events stream.\n\t * Returns an unsubscribe function. Listeners receive events for all roles;\n\t * per-role filtering is the subscriber's job (the attach panel filters).\n\t */\n\tsubscribe(listener: (event: TeamViewEvent) => void): () => void;\n\t/** Number of live event subscribers. Exposed for leak tests. */\n\tsubscriberCount(): number;\n}\n\nconst STATUS_TIMEOUT_MS = 5000;\nconst STEER_TIMEOUT_MS = 5000;\n\n/**\n * Start the read-only team view against a hooteams server base URL.\n *\n * Returns immediately; all network work happens in the background and any\n * failure is reported through `warn` without ever throwing.\n */\nexport function connectTeamView(url: string, options: TeamViewOptions = {}): TeamViewConnection {\n\tconst base = url.replace(/\\/+$/, \"\");\n\tconst warn = options.warn ?? ((message: string) => console.error(message));\n\tconst retryDelayMs = options.retryDelayMs ?? 5000;\n\tconst mapper = new TeamViewMapper(options.store);\n\tconst controller = new AbortController();\n\tconst listeners = new Set<(event: TeamViewEvent) => void>();\n\tlet stopped = false;\n\n\tconst deliver = (event: TeamViewEvent): void => {\n\t\tmapper.applyEvent(event);\n\t\tfor (const listener of listeners) {\n\t\t\ttry {\n\t\t\t\tlistener(event);\n\t\t\t} catch {\n\t\t\t\t// A broken subscriber must not take down the stream or its peers.\n\t\t\t}\n\t\t}\n\t};\n\n\tconst run = async (): Promise<void> => {\n\t\t// 1. Status snapshot: register the current roles.\n\t\ttry {\n\t\t\tconst response = await fetch(`${base}/status`, {\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STATUS_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t\tmapper.applyStatus((await response.json()) as TeamStatusSnapshot);\n\t\t} catch (error) {\n\t\t\tif (stopped) return;\n\t\t\twarn(`team view: failed to fetch ${base}/status (${String(error)}); continuing without the team view`);\n\t\t}\n\n\t\t// 2. Single SSE subscription, reconnecting on drops.\n\t\tlet announcedDrop = false;\n\t\twhile (!stopped) {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${base}/events`, { signal: controller.signal });\n\t\t\t\tif (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`);\n\t\t\t\tconst reader = response.body.getReader();\n\t\t\t\tconst decoder = new TextDecoder();\n\t\t\t\tlet buffer = \"\";\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { value, done } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\t// Only a stream that actually delivers data counts as recovered. A 200\n\t\t\t\t\t// that closes immediately (e.g. a server that answers /events without\n\t\t\t\t\t// streaming) used to re-arm the warning and repeat it every retry.\n\t\t\t\t\tannouncedDrop = false;\n\t\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t\t\tlet index = buffer.indexOf(\"\\n\\n\");\n\t\t\t\t\twhile (index !== -1) {\n\t\t\t\t\t\tconst frame = buffer.slice(0, index);\n\t\t\t\t\t\tbuffer = buffer.slice(index + 2);\n\t\t\t\t\t\tfor (const line of frame.split(\"\\n\")) {\n\t\t\t\t\t\t\tif (!line.startsWith(\"data:\")) continue;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tdeliver(JSON.parse(line.slice(5).trim()) as TeamViewEvent);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Malformed frames are dropped; the stream stays up.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tindex = buffer.indexOf(\"\\n\\n\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (stopped) return;\n\t\t\t\tthrow new Error(\"stream ended\");\n\t\t\t} catch (error) {\n\t\t\t\tif (stopped || controller.signal.aborted) return;\n\t\t\t\tif (!announcedDrop) {\n\t\t\t\t\tannouncedDrop = true;\n\t\t\t\t\twarn(`team view: lost connection to ${base}/events (${String(error)}); retrying in background`);\n\t\t\t\t}\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, retryDelayMs));\n\t\t\t}\n\t\t}\n\t};\n\n\tvoid run().catch((error) => {\n\t\tif (!stopped) warn(`team view: unexpected error (${String(error)})`);\n\t});\n\n\treturn {\n\t\tstop() {\n\t\t\tstopped = true;\n\t\t\tlisteners.clear();\n\t\t\tcontroller.abort();\n\t\t},\n\t\tasync steer(role: string, message: string): Promise<void> {\n\t\t\tconst response = await fetch(`${base}/steer`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ role, message }),\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STEER_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t},\n\t\tasync resume(taskId: string, option: string, feedback?: string): Promise<void> {\n\t\t\tconst response = await fetch(`${base}/tasks/${encodeURIComponent(taskId)}/resume`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ option, ...(feedback !== undefined ? { feedback } : {}) }),\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STEER_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (response.status === 409) throw new Error(`task \"${taskId}\" was answered elsewhere`);\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t},\n\t\tasync pendingApprovals(): Promise<TeamPendingApproval[]> {\n\t\t\tconst response = await fetch(`${base}/tasks/pending`, {\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STATUS_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (response.status === 404) return [];\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t\tconst body = (await response.json()) as { pending?: TeamPendingApproval[] };\n\t\t\treturn Array.isArray(body.pending) ? body.pending : [];\n\t\t},\n\t\tsubscribe(listener: (event: TeamViewEvent) => void): () => void {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tsubscriberCount(): number {\n\t\t\treturn listeners.size;\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"team-view.d.ts","sourceRoot":"","sources":["../../src/core/team-view.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * hooteams team client (`--team <url>`).\n *\n * Connects to a running hooteams server, registers every role as a\n * kind=\"role\" agent in the task store, and maps the server's TeamEvent SSE\n * stream onto task-store patches so the task panel's existing \"teams\" view\n * shows live role state. On top of that mirror the connection exposes\n * steering (POST /steer) and an event subscription used by the attach\n * side-panel — both share the single /events stream; no second SSE\n * connection is ever opened.\n *\n * The connection is best-effort by design — a connect failure or a later\n * drop logs a warning and never blocks (or crashes) the main agent. At most\n * one SSE connection (to /events) is open at any time.\n */\n\nimport { type TaskAgentState, type TaskStatus, taskStore } from \"./task-store.js\";\n\n/** Shape of GET /status: coarse per-role status keyed by role name. */\nexport type TeamStatusSnapshot = Record<string, { status?: string; lastEventType?: string }>;\n\n/** One frame of GET /events: a hoocode AgentEvent tagged with its producer. */\nexport interface TeamViewEvent {\n\ttype: string;\n\trole: string;\n\tagentId?: string;\n\tts?: number;\n\ttoolName?: string;\n\targs?: unknown;\n\tisError?: boolean;\n\t/** Streaming assistant-message delta carried by message_update events. */\n\tassistantMessageEvent?: { type?: string; delta?: string };\n\tmessage?: {\n\t\trole?: string;\n\t\terrorMessage?: string;\n\t\tusage?: { input?: number; output?: number; cost?: { total?: number } };\n\t};\n\t/** Task lifecycle fields carried by task_* events from the orchestrator. */\n\ttaskId?: string;\n\t/** Approval gate carried by task_paused. */\n\tquestion?: string;\n\toptions?: string[];\n\t/** Answer carried by task_resumed. */\n\tchosenOption?: string;\n\t/** \"done\" | \"error\" on task_finished. */\n\tstatus?: string;\n\t/** Run id carried by dag_complete / dag_failed. */\n\trunId?: string;\n}\n\n/** One unanswered approval gate from GET /tasks/pending. */\nexport interface TeamPendingApproval {\n\ttaskId: string;\n\tquestion: string;\n\toptions: string[];\n}\n\n/** hooteams AgentStatus word → task panel agent state. */\nfunction stateFromStatus(status: string | undefined): TaskAgentState {\n\tswitch (status) {\n\t\tcase \"idle\":\n\t\t\treturn \"idle\";\n\t\tcase \"thinking\":\n\t\tcase \"streaming\":\n\t\t\treturn \"active\";\n\t\tcase \"tool\":\n\t\t\treturn \"running\";\n\t\tcase \"done\":\n\t\t\treturn \"done\";\n\t\tcase \"error\":\n\t\t\treturn \"failed\";\n\t\tcase \"paused\":\n\t\t\treturn \"waiting\";\n\t\tdefault:\n\t\t\treturn \"idle\";\n\t}\n}\n\nfunction taskStatusFromState(state: TaskAgentState): TaskStatus {\n\tswitch (state) {\n\t\tcase \"done\":\n\t\t\treturn \"done\";\n\t\tcase \"failed\":\n\t\t\treturn \"failed\";\n\t\tcase \"idle\":\n\t\t\t// Idle is settled, not queued: a \"pending\" task here would survive every\n\t\t\t// taskStore.reset() and pin the pane at \"working\" for the whole session.\n\t\t\treturn \"done\";\n\t\tdefault:\n\t\t\treturn \"in_progress\";\n\t}\n}\n\n/** Only these states represent activity worth a task row of its own. */\nfunction stateWarrantsTask(state: TaskAgentState): boolean {\n\treturn state === \"active\" || state === \"running\" || state === \"failed\" || state === \"waiting\";\n}\n\n/**\n * Maps team status snapshots and TeamEvents onto task-store patches.\n *\n * Each role owns one roster entry (id `team:<role>`) and at most one task whose\n * title tracks the role's latest activity. Tasks exist only while a role is\n * actually doing something (active/running, or failed so the error is visible);\n * idle roles keep their roster entry but no task — the panel's teams lens\n * renders them as placeholder groups, so a quiet team reads as an idle roster\n * instead of pinning the pane at \"working\". Entries are re-created on demand\n * because taskStore.reset() wipes finished tasks between user turns.\n */\nexport class TeamViewMapper {\n\tprivate readonly store: typeof taskStore;\n\tprivate readonly taskIds = new Map<string, number>();\n\n\tconstructor(store: typeof taskStore = taskStore) {\n\t\tthis.store = store;\n\t}\n\n\t/** Register roles from a GET /status snapshot. */\n\tapplyStatus(snapshot: TeamStatusSnapshot): void {\n\t\tfor (const [role, info] of Object.entries(snapshot)) {\n\t\t\tconst state = stateFromStatus(info?.status);\n\t\t\tthis.ensureRole(role, state, info?.lastEventType ?? \"connected\");\n\t\t\tthis.patchRole(role, state);\n\t\t}\n\t}\n\n\t/** Map one TeamEvent from GET /events onto the store. */\n\tapplyEvent(event: TeamViewEvent): void {\n\t\tif (!event || typeof event.role !== \"string\" || event.role.length === 0) return;\n\t\t// Dag settlement events are tagged role=\"orchestrator\" — they describe\n\t\t// the run, not a member, and must not create a phantom roster entry.\n\t\tif (event.type === \"dag_complete\" || event.type === \"dag_failed\") return;\n\t\tconst role = event.role;\n\t\tswitch (event.type) {\n\t\t\tcase \"agent_start\":\n\t\t\tcase \"turn_start\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"thinking\");\n\t\t\t\tthis.patchRole(role, \"active\", \"thinking\");\n\t\t\t\tbreak;\n\t\t\tcase \"message_start\":\n\t\t\tcase \"message_update\":\n\t\t\tcase \"message_end\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"responding\");\n\t\t\t\tthis.patchRole(role, \"active\");\n\t\t\t\tbreak;\n\t\t\tcase \"tool_execution_start\":\n\t\t\t\tthis.ensureRole(role, \"running\", `tool: ${event.toolName ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"running\", `tool: ${event.toolName ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"tool_execution_end\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"thinking\");\n\t\t\t\tthis.patchRole(role, \"active\", \"thinking\");\n\t\t\t\tbreak;\n\t\t\tcase \"turn_end\":\n\t\t\t\tif (event.message?.role === \"assistant\" && event.message.errorMessage) {\n\t\t\t\t\tthis.ensureRole(role, \"failed\", \"error\");\n\t\t\t\t\tthis.patchRole(role, \"failed\", \"error\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"agent_end\": {\n\t\t\t\t// A failed run stays failed; a paused run (waiting on an approval gate)\n\t\t\t\t// stays waiting — hooteams mirrors agent_end right after task_paused, so\n\t\t\t\t// without this guard the gate's \"waiting\" state would flip to idle and the\n\t\t\t\t// roster would read as done while the AskOptions pane is still open.\n\t\t\t\t// agent_end only marks clean completions (mirrors hooteams' own tracking).\n\t\t\t\tconst current = this.store.agents().find((a) => a.id === this.agentId(role))?.state;\n\t\t\t\tif (current !== \"failed\" && current !== \"waiting\") {\n\t\t\t\t\tthis.ensureRole(role, \"done\", \"idle\");\n\t\t\t\t\tthis.patchRole(role, \"done\", \"idle\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"task_started\":\n\t\t\t\tthis.ensureRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"task_paused\": {\n\t\t\t\tconst title = `awaiting approval: ${event.question ?? \"?\"}`;\n\t\t\t\tthis.ensureRole(role, \"waiting\", title);\n\t\t\t\tthis.patchRole(role, \"waiting\", title);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"task_resumed\":\n\t\t\t\tthis.ensureRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"task_finished\": {\n\t\t\t\tconst state = event.status === \"error\" ? \"failed\" : \"done\";\n\t\t\t\tthis.ensureRole(role, state, state === \"failed\" ? \"error\" : \"idle\");\n\t\t\t\tthis.patchRole(role, state, state === \"failed\" ? \"error\" : \"idle\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// Unknown event types still prove the role exists.\n\t\t\t\tthis.ensureRole(role, \"idle\", event.type);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate agentId(role: string): string {\n\t\treturn `team:${role}`;\n\t}\n\n\t/**\n\t * Make sure the role's roster entry exists, plus its task when the state\n\t * warrants one (reset() may have dropped both). Idle/done states never\n\t * create a task — only patch one that live activity already opened.\n\t */\n\tprivate ensureRole(role: string, state: TaskAgentState, title: string): void {\n\t\tconst id = this.agentId(role);\n\t\tthis.store.upsertAgent({ id, name: role, kind: \"role\", state });\n\t\tconst taskId = this.taskIds.get(role);\n\t\tconst existing = taskId !== undefined ? this.store.list().find((task) => task.id === taskId) : undefined;\n\t\tif (!existing && stateWarrantsTask(state)) {\n\t\t\tconst task = this.store.create(title, { agent: id });\n\t\t\tthis.store.update(task.id, { status: taskStatusFromState(state) });\n\t\t\tthis.taskIds.set(role, task.id);\n\t\t}\n\t}\n\n\tprivate patchRole(role: string, state: TaskAgentState, title?: string): void {\n\t\tthis.store.patchAgent(this.agentId(role), { state });\n\t\tconst taskId = this.taskIds.get(role);\n\t\tif (taskId === undefined) return;\n\t\tthis.store.update(taskId, { status: taskStatusFromState(state), ...(title !== undefined ? { title } : {}) });\n\t}\n}\n\nexport interface TeamViewOptions {\n\t/** Warning sink; defaults to console.error. */\n\twarn?: (message: string) => void;\n\t/** Store override for tests. */\n\tstore?: typeof taskStore;\n\t/** Delay between reconnect attempts in ms (default 5000). */\n\tretryDelayMs?: number;\n}\n\nexport interface TeamViewConnection {\n\t/** Close the SSE connection and stop reconnecting. */\n\tstop(): void;\n\t/** POST /steer { role, message }. Rejects on network or HTTP error. */\n\tsteer(role: string, message: string): Promise<void>;\n\t/**\n\t * Answer a paused task: POST /tasks/:taskId/resume { option, feedback? }.\n\t * Rejects with \"answered elsewhere\" on 409 (first answer wins across\n\t * surfaces) and with HTTP/network errors otherwise.\n\t */\n\tresume(taskId: string, option: string, feedback?: string): Promise<void>;\n\t/**\n\t * GET /tasks/pending — gates that opened before we attached. Resolves to\n\t * [] when the server has no active run (404) so callers need no special\n\t * casing.\n\t */\n\tpendingApprovals(): Promise<TeamPendingApproval[]>;\n\t/**\n\t * Subscribe to every TeamEvent delivered by the shared /events stream.\n\t * Returns an unsubscribe function. Listeners receive events for all roles;\n\t * per-role filtering is the subscriber's job (the attach panel filters).\n\t */\n\tsubscribe(listener: (event: TeamViewEvent) => void): () => void;\n\t/** Number of live event subscribers. Exposed for leak tests. */\n\tsubscriberCount(): number;\n}\n\nconst STATUS_TIMEOUT_MS = 5000;\nconst STEER_TIMEOUT_MS = 5000;\n\n/**\n * Start the read-only team view against a hooteams server base URL.\n *\n * Returns immediately; all network work happens in the background and any\n * failure is reported through `warn` without ever throwing.\n */\nexport function connectTeamView(url: string, options: TeamViewOptions = {}): TeamViewConnection {\n\tconst base = url.replace(/\\/+$/, \"\");\n\tconst warn = options.warn ?? ((message: string) => console.error(message));\n\tconst retryDelayMs = options.retryDelayMs ?? 5000;\n\tconst mapper = new TeamViewMapper(options.store);\n\tconst controller = new AbortController();\n\tconst listeners = new Set<(event: TeamViewEvent) => void>();\n\tlet stopped = false;\n\n\tconst deliver = (event: TeamViewEvent): void => {\n\t\tmapper.applyEvent(event);\n\t\tfor (const listener of listeners) {\n\t\t\ttry {\n\t\t\t\tlistener(event);\n\t\t\t} catch {\n\t\t\t\t// A broken subscriber must not take down the stream or its peers.\n\t\t\t}\n\t\t}\n\t};\n\n\tconst run = async (): Promise<void> => {\n\t\t// 1. Status snapshot: register the current roles.\n\t\ttry {\n\t\t\tconst response = await fetch(`${base}/status`, {\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STATUS_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t\tmapper.applyStatus((await response.json()) as TeamStatusSnapshot);\n\t\t} catch (error) {\n\t\t\tif (stopped) return;\n\t\t\twarn(`team view: failed to fetch ${base}/status (${String(error)}); continuing without the team view`);\n\t\t}\n\n\t\t// 2. Single SSE subscription, reconnecting on drops.\n\t\tlet announcedDrop = false;\n\t\twhile (!stopped) {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${base}/events`, { signal: controller.signal });\n\t\t\t\tif (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`);\n\t\t\t\tconst reader = response.body.getReader();\n\t\t\t\tconst decoder = new TextDecoder();\n\t\t\t\tlet buffer = \"\";\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { value, done } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\t// Only a stream that actually delivers data counts as recovered. A 200\n\t\t\t\t\t// that closes immediately (e.g. a server that answers /events without\n\t\t\t\t\t// streaming) used to re-arm the warning and repeat it every retry.\n\t\t\t\t\tannouncedDrop = false;\n\t\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t\t\tlet index = buffer.indexOf(\"\\n\\n\");\n\t\t\t\t\twhile (index !== -1) {\n\t\t\t\t\t\tconst frame = buffer.slice(0, index);\n\t\t\t\t\t\tbuffer = buffer.slice(index + 2);\n\t\t\t\t\t\tfor (const line of frame.split(\"\\n\")) {\n\t\t\t\t\t\t\tif (!line.startsWith(\"data:\")) continue;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tdeliver(JSON.parse(line.slice(5).trim()) as TeamViewEvent);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Malformed frames are dropped; the stream stays up.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tindex = buffer.indexOf(\"\\n\\n\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (stopped) return;\n\t\t\t\tthrow new Error(\"stream ended\");\n\t\t\t} catch (error) {\n\t\t\t\tif (stopped || controller.signal.aborted) return;\n\t\t\t\tif (!announcedDrop) {\n\t\t\t\t\tannouncedDrop = true;\n\t\t\t\t\twarn(`team view: lost connection to ${base}/events (${String(error)}); retrying in background`);\n\t\t\t\t}\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, retryDelayMs));\n\t\t\t}\n\t\t}\n\t};\n\n\tvoid run().catch((error) => {\n\t\tif (!stopped) warn(`team view: unexpected error (${String(error)})`);\n\t});\n\n\treturn {\n\t\tstop() {\n\t\t\tstopped = true;\n\t\t\tlisteners.clear();\n\t\t\tcontroller.abort();\n\t\t},\n\t\tasync steer(role: string, message: string): Promise<void> {\n\t\t\tconst response = await fetch(`${base}/steer`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ role, message }),\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STEER_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t},\n\t\tasync resume(taskId: string, option: string, feedback?: string): Promise<void> {\n\t\t\tconst response = await fetch(`${base}/tasks/${encodeURIComponent(taskId)}/resume`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ option, ...(feedback !== undefined ? { feedback } : {}) }),\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STEER_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (response.status === 409) throw new Error(`task \"${taskId}\" was answered elsewhere`);\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t},\n\t\tasync pendingApprovals(): Promise<TeamPendingApproval[]> {\n\t\t\tconst response = await fetch(`${base}/tasks/pending`, {\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STATUS_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (response.status === 404) return [];\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t\tconst body = (await response.json()) as { pending?: TeamPendingApproval[] };\n\t\t\treturn Array.isArray(body.pending) ? body.pending : [];\n\t\t},\n\t\tsubscribe(listener: (event: TeamViewEvent) => void): () => void {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tsubscriberCount(): number {\n\t\t\treturn listeners.size;\n\t\t},\n\t};\n}\n"]}
@@ -113,10 +113,13 @@ export class TeamViewMapper {
113
113
  }
114
114
  break;
115
115
  case "agent_end": {
116
- // A failed run stays failed; agent_end only marks clean completions
117
- // (mirrors hooteams' own status tracking).
118
- const failed = this.store.agents().find((a) => a.id === this.agentId(role))?.state === "failed";
119
- if (!failed) {
116
+ // A failed run stays failed; a paused run (waiting on an approval gate)
117
+ // stays waiting — hooteams mirrors agent_end right after task_paused, so
118
+ // without this guard the gate's "waiting" state would flip to idle and the
119
+ // roster would read as done while the AskOptions pane is still open.
120
+ // agent_end only marks clean completions (mirrors hooteams' own tracking).
121
+ const current = this.store.agents().find((a) => a.id === this.agentId(role))?.state;
122
+ if (current !== "failed" && current !== "waiting") {
120
123
  this.ensureRole(role, "done", "idle");
121
124
  this.patchRole(role, "done", "idle");
122
125
  }
@@ -1 +1 @@
1
- {"version":3,"file":"team-view.js","sourceRoot":"","sources":["../../src/core/team-view.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * hooteams team client (`--team <url>`).\n *\n * Connects to a running hooteams server, registers every role as a\n * kind=\"role\" agent in the task store, and maps the server's TeamEvent SSE\n * stream onto task-store patches so the task panel's existing \"teams\" view\n * shows live role state. On top of that mirror the connection exposes\n * steering (POST /steer) and an event subscription used by the attach\n * side-panel — both share the single /events stream; no second SSE\n * connection is ever opened.\n *\n * The connection is best-effort by design — a connect failure or a later\n * drop logs a warning and never blocks (or crashes) the main agent. At most\n * one SSE connection (to /events) is open at any time.\n */\n\nimport { type TaskAgentState, type TaskStatus, taskStore } from \"./task-store.js\";\n\n/** Shape of GET /status: coarse per-role status keyed by role name. */\nexport type TeamStatusSnapshot = Record<string, { status?: string; lastEventType?: string }>;\n\n/** One frame of GET /events: a hoocode AgentEvent tagged with its producer. */\nexport interface TeamViewEvent {\n\ttype: string;\n\trole: string;\n\tagentId?: string;\n\tts?: number;\n\ttoolName?: string;\n\targs?: unknown;\n\tisError?: boolean;\n\t/** Streaming assistant-message delta carried by message_update events. */\n\tassistantMessageEvent?: { type?: string; delta?: string };\n\tmessage?: {\n\t\trole?: string;\n\t\terrorMessage?: string;\n\t\tusage?: { input?: number; output?: number; cost?: { total?: number } };\n\t};\n\t/** Task lifecycle fields carried by task_* events from the orchestrator. */\n\ttaskId?: string;\n\t/** Approval gate carried by task_paused. */\n\tquestion?: string;\n\toptions?: string[];\n\t/** Answer carried by task_resumed. */\n\tchosenOption?: string;\n\t/** \"done\" | \"error\" on task_finished. */\n\tstatus?: string;\n\t/** Run id carried by dag_complete / dag_failed. */\n\trunId?: string;\n}\n\n/** One unanswered approval gate from GET /tasks/pending. */\nexport interface TeamPendingApproval {\n\ttaskId: string;\n\tquestion: string;\n\toptions: string[];\n}\n\n/** hooteams AgentStatus word → task panel agent state. */\nfunction stateFromStatus(status: string | undefined): TaskAgentState {\n\tswitch (status) {\n\t\tcase \"idle\":\n\t\t\treturn \"idle\";\n\t\tcase \"thinking\":\n\t\tcase \"streaming\":\n\t\t\treturn \"active\";\n\t\tcase \"tool\":\n\t\t\treturn \"running\";\n\t\tcase \"done\":\n\t\t\treturn \"done\";\n\t\tcase \"error\":\n\t\t\treturn \"failed\";\n\t\tcase \"paused\":\n\t\t\treturn \"waiting\";\n\t\tdefault:\n\t\t\treturn \"idle\";\n\t}\n}\n\nfunction taskStatusFromState(state: TaskAgentState): TaskStatus {\n\tswitch (state) {\n\t\tcase \"done\":\n\t\t\treturn \"done\";\n\t\tcase \"failed\":\n\t\t\treturn \"failed\";\n\t\tcase \"idle\":\n\t\t\t// Idle is settled, not queued: a \"pending\" task here would survive every\n\t\t\t// taskStore.reset() and pin the pane at \"working\" for the whole session.\n\t\t\treturn \"done\";\n\t\tdefault:\n\t\t\treturn \"in_progress\";\n\t}\n}\n\n/** Only these states represent activity worth a task row of its own. */\nfunction stateWarrantsTask(state: TaskAgentState): boolean {\n\treturn state === \"active\" || state === \"running\" || state === \"failed\" || state === \"waiting\";\n}\n\n/**\n * Maps team status snapshots and TeamEvents onto task-store patches.\n *\n * Each role owns one roster entry (id `team:<role>`) and at most one task whose\n * title tracks the role's latest activity. Tasks exist only while a role is\n * actually doing something (active/running, or failed so the error is visible);\n * idle roles keep their roster entry but no task — the panel's teams lens\n * renders them as placeholder groups, so a quiet team reads as an idle roster\n * instead of pinning the pane at \"working\". Entries are re-created on demand\n * because taskStore.reset() wipes finished tasks between user turns.\n */\nexport class TeamViewMapper {\n\tprivate readonly store: typeof taskStore;\n\tprivate readonly taskIds = new Map<string, number>();\n\n\tconstructor(store: typeof taskStore = taskStore) {\n\t\tthis.store = store;\n\t}\n\n\t/** Register roles from a GET /status snapshot. */\n\tapplyStatus(snapshot: TeamStatusSnapshot): void {\n\t\tfor (const [role, info] of Object.entries(snapshot)) {\n\t\t\tconst state = stateFromStatus(info?.status);\n\t\t\tthis.ensureRole(role, state, info?.lastEventType ?? \"connected\");\n\t\t\tthis.patchRole(role, state);\n\t\t}\n\t}\n\n\t/** Map one TeamEvent from GET /events onto the store. */\n\tapplyEvent(event: TeamViewEvent): void {\n\t\tif (!event || typeof event.role !== \"string\" || event.role.length === 0) return;\n\t\t// Dag settlement events are tagged role=\"orchestrator\" — they describe\n\t\t// the run, not a member, and must not create a phantom roster entry.\n\t\tif (event.type === \"dag_complete\" || event.type === \"dag_failed\") return;\n\t\tconst role = event.role;\n\t\tswitch (event.type) {\n\t\t\tcase \"agent_start\":\n\t\t\tcase \"turn_start\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"thinking\");\n\t\t\t\tthis.patchRole(role, \"active\", \"thinking\");\n\t\t\t\tbreak;\n\t\t\tcase \"message_start\":\n\t\t\tcase \"message_update\":\n\t\t\tcase \"message_end\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"responding\");\n\t\t\t\tthis.patchRole(role, \"active\");\n\t\t\t\tbreak;\n\t\t\tcase \"tool_execution_start\":\n\t\t\t\tthis.ensureRole(role, \"running\", `tool: ${event.toolName ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"running\", `tool: ${event.toolName ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"tool_execution_end\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"thinking\");\n\t\t\t\tthis.patchRole(role, \"active\", \"thinking\");\n\t\t\t\tbreak;\n\t\t\tcase \"turn_end\":\n\t\t\t\tif (event.message?.role === \"assistant\" && event.message.errorMessage) {\n\t\t\t\t\tthis.ensureRole(role, \"failed\", \"error\");\n\t\t\t\t\tthis.patchRole(role, \"failed\", \"error\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"agent_end\": {\n\t\t\t\t// A failed run stays failed; agent_end only marks clean completions\n\t\t\t\t// (mirrors hooteams' own status tracking).\n\t\t\t\tconst failed = this.store.agents().find((a) => a.id === this.agentId(role))?.state === \"failed\";\n\t\t\t\tif (!failed) {\n\t\t\t\t\tthis.ensureRole(role, \"done\", \"idle\");\n\t\t\t\t\tthis.patchRole(role, \"done\", \"idle\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"task_started\":\n\t\t\t\tthis.ensureRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"task_paused\": {\n\t\t\t\tconst title = `awaiting approval: ${event.question ?? \"?\"}`;\n\t\t\t\tthis.ensureRole(role, \"waiting\", title);\n\t\t\t\tthis.patchRole(role, \"waiting\", title);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"task_resumed\":\n\t\t\t\tthis.ensureRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"task_finished\": {\n\t\t\t\tconst state = event.status === \"error\" ? \"failed\" : \"done\";\n\t\t\t\tthis.ensureRole(role, state, state === \"failed\" ? \"error\" : \"idle\");\n\t\t\t\tthis.patchRole(role, state, state === \"failed\" ? \"error\" : \"idle\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// Unknown event types still prove the role exists.\n\t\t\t\tthis.ensureRole(role, \"idle\", event.type);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate agentId(role: string): string {\n\t\treturn `team:${role}`;\n\t}\n\n\t/**\n\t * Make sure the role's roster entry exists, plus its task when the state\n\t * warrants one (reset() may have dropped both). Idle/done states never\n\t * create a task — only patch one that live activity already opened.\n\t */\n\tprivate ensureRole(role: string, state: TaskAgentState, title: string): void {\n\t\tconst id = this.agentId(role);\n\t\tthis.store.upsertAgent({ id, name: role, kind: \"role\", state });\n\t\tconst taskId = this.taskIds.get(role);\n\t\tconst existing = taskId !== undefined ? this.store.list().find((task) => task.id === taskId) : undefined;\n\t\tif (!existing && stateWarrantsTask(state)) {\n\t\t\tconst task = this.store.create(title, { agent: id });\n\t\t\tthis.store.update(task.id, { status: taskStatusFromState(state) });\n\t\t\tthis.taskIds.set(role, task.id);\n\t\t}\n\t}\n\n\tprivate patchRole(role: string, state: TaskAgentState, title?: string): void {\n\t\tthis.store.patchAgent(this.agentId(role), { state });\n\t\tconst taskId = this.taskIds.get(role);\n\t\tif (taskId === undefined) return;\n\t\tthis.store.update(taskId, { status: taskStatusFromState(state), ...(title !== undefined ? { title } : {}) });\n\t}\n}\n\nexport interface TeamViewOptions {\n\t/** Warning sink; defaults to console.error. */\n\twarn?: (message: string) => void;\n\t/** Store override for tests. */\n\tstore?: typeof taskStore;\n\t/** Delay between reconnect attempts in ms (default 5000). */\n\tretryDelayMs?: number;\n}\n\nexport interface TeamViewConnection {\n\t/** Close the SSE connection and stop reconnecting. */\n\tstop(): void;\n\t/** POST /steer { role, message }. Rejects on network or HTTP error. */\n\tsteer(role: string, message: string): Promise<void>;\n\t/**\n\t * Answer a paused task: POST /tasks/:taskId/resume { option, feedback? }.\n\t * Rejects with \"answered elsewhere\" on 409 (first answer wins across\n\t * surfaces) and with HTTP/network errors otherwise.\n\t */\n\tresume(taskId: string, option: string, feedback?: string): Promise<void>;\n\t/**\n\t * GET /tasks/pending — gates that opened before we attached. Resolves to\n\t * [] when the server has no active run (404) so callers need no special\n\t * casing.\n\t */\n\tpendingApprovals(): Promise<TeamPendingApproval[]>;\n\t/**\n\t * Subscribe to every TeamEvent delivered by the shared /events stream.\n\t * Returns an unsubscribe function. Listeners receive events for all roles;\n\t * per-role filtering is the subscriber's job (the attach panel filters).\n\t */\n\tsubscribe(listener: (event: TeamViewEvent) => void): () => void;\n\t/** Number of live event subscribers. Exposed for leak tests. */\n\tsubscriberCount(): number;\n}\n\nconst STATUS_TIMEOUT_MS = 5000;\nconst STEER_TIMEOUT_MS = 5000;\n\n/**\n * Start the read-only team view against a hooteams server base URL.\n *\n * Returns immediately; all network work happens in the background and any\n * failure is reported through `warn` without ever throwing.\n */\nexport function connectTeamView(url: string, options: TeamViewOptions = {}): TeamViewConnection {\n\tconst base = url.replace(/\\/+$/, \"\");\n\tconst warn = options.warn ?? ((message: string) => console.error(message));\n\tconst retryDelayMs = options.retryDelayMs ?? 5000;\n\tconst mapper = new TeamViewMapper(options.store);\n\tconst controller = new AbortController();\n\tconst listeners = new Set<(event: TeamViewEvent) => void>();\n\tlet stopped = false;\n\n\tconst deliver = (event: TeamViewEvent): void => {\n\t\tmapper.applyEvent(event);\n\t\tfor (const listener of listeners) {\n\t\t\ttry {\n\t\t\t\tlistener(event);\n\t\t\t} catch {\n\t\t\t\t// A broken subscriber must not take down the stream or its peers.\n\t\t\t}\n\t\t}\n\t};\n\n\tconst run = async (): Promise<void> => {\n\t\t// 1. Status snapshot: register the current roles.\n\t\ttry {\n\t\t\tconst response = await fetch(`${base}/status`, {\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STATUS_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t\tmapper.applyStatus((await response.json()) as TeamStatusSnapshot);\n\t\t} catch (error) {\n\t\t\tif (stopped) return;\n\t\t\twarn(`team view: failed to fetch ${base}/status (${String(error)}); continuing without the team view`);\n\t\t}\n\n\t\t// 2. Single SSE subscription, reconnecting on drops.\n\t\tlet announcedDrop = false;\n\t\twhile (!stopped) {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${base}/events`, { signal: controller.signal });\n\t\t\t\tif (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`);\n\t\t\t\tconst reader = response.body.getReader();\n\t\t\t\tconst decoder = new TextDecoder();\n\t\t\t\tlet buffer = \"\";\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { value, done } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\t// Only a stream that actually delivers data counts as recovered. A 200\n\t\t\t\t\t// that closes immediately (e.g. a server that answers /events without\n\t\t\t\t\t// streaming) used to re-arm the warning and repeat it every retry.\n\t\t\t\t\tannouncedDrop = false;\n\t\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t\t\tlet index = buffer.indexOf(\"\\n\\n\");\n\t\t\t\t\twhile (index !== -1) {\n\t\t\t\t\t\tconst frame = buffer.slice(0, index);\n\t\t\t\t\t\tbuffer = buffer.slice(index + 2);\n\t\t\t\t\t\tfor (const line of frame.split(\"\\n\")) {\n\t\t\t\t\t\t\tif (!line.startsWith(\"data:\")) continue;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tdeliver(JSON.parse(line.slice(5).trim()) as TeamViewEvent);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Malformed frames are dropped; the stream stays up.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tindex = buffer.indexOf(\"\\n\\n\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (stopped) return;\n\t\t\t\tthrow new Error(\"stream ended\");\n\t\t\t} catch (error) {\n\t\t\t\tif (stopped || controller.signal.aborted) return;\n\t\t\t\tif (!announcedDrop) {\n\t\t\t\t\tannouncedDrop = true;\n\t\t\t\t\twarn(`team view: lost connection to ${base}/events (${String(error)}); retrying in background`);\n\t\t\t\t}\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, retryDelayMs));\n\t\t\t}\n\t\t}\n\t};\n\n\tvoid run().catch((error) => {\n\t\tif (!stopped) warn(`team view: unexpected error (${String(error)})`);\n\t});\n\n\treturn {\n\t\tstop() {\n\t\t\tstopped = true;\n\t\t\tlisteners.clear();\n\t\t\tcontroller.abort();\n\t\t},\n\t\tasync steer(role: string, message: string): Promise<void> {\n\t\t\tconst response = await fetch(`${base}/steer`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ role, message }),\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STEER_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t},\n\t\tasync resume(taskId: string, option: string, feedback?: string): Promise<void> {\n\t\t\tconst response = await fetch(`${base}/tasks/${encodeURIComponent(taskId)}/resume`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ option, ...(feedback !== undefined ? { feedback } : {}) }),\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STEER_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (response.status === 409) throw new Error(`task \"${taskId}\" was answered elsewhere`);\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t},\n\t\tasync pendingApprovals(): Promise<TeamPendingApproval[]> {\n\t\t\tconst response = await fetch(`${base}/tasks/pending`, {\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STATUS_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (response.status === 404) return [];\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t\tconst body = (await response.json()) as { pending?: TeamPendingApproval[] };\n\t\t\treturn Array.isArray(body.pending) ? body.pending : [];\n\t\t},\n\t\tsubscribe(listener: (event: TeamViewEvent) => void): () => void {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tsubscriberCount(): number {\n\t\t\treturn listeners.size;\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"team-view.js","sourceRoot":"","sources":["../../src/core/team-view.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * hooteams team client (`--team <url>`).\n *\n * Connects to a running hooteams server, registers every role as a\n * kind=\"role\" agent in the task store, and maps the server's TeamEvent SSE\n * stream onto task-store patches so the task panel's existing \"teams\" view\n * shows live role state. On top of that mirror the connection exposes\n * steering (POST /steer) and an event subscription used by the attach\n * side-panel — both share the single /events stream; no second SSE\n * connection is ever opened.\n *\n * The connection is best-effort by design — a connect failure or a later\n * drop logs a warning and never blocks (or crashes) the main agent. At most\n * one SSE connection (to /events) is open at any time.\n */\n\nimport { type TaskAgentState, type TaskStatus, taskStore } from \"./task-store.js\";\n\n/** Shape of GET /status: coarse per-role status keyed by role name. */\nexport type TeamStatusSnapshot = Record<string, { status?: string; lastEventType?: string }>;\n\n/** One frame of GET /events: a hoocode AgentEvent tagged with its producer. */\nexport interface TeamViewEvent {\n\ttype: string;\n\trole: string;\n\tagentId?: string;\n\tts?: number;\n\ttoolName?: string;\n\targs?: unknown;\n\tisError?: boolean;\n\t/** Streaming assistant-message delta carried by message_update events. */\n\tassistantMessageEvent?: { type?: string; delta?: string };\n\tmessage?: {\n\t\trole?: string;\n\t\terrorMessage?: string;\n\t\tusage?: { input?: number; output?: number; cost?: { total?: number } };\n\t};\n\t/** Task lifecycle fields carried by task_* events from the orchestrator. */\n\ttaskId?: string;\n\t/** Approval gate carried by task_paused. */\n\tquestion?: string;\n\toptions?: string[];\n\t/** Answer carried by task_resumed. */\n\tchosenOption?: string;\n\t/** \"done\" | \"error\" on task_finished. */\n\tstatus?: string;\n\t/** Run id carried by dag_complete / dag_failed. */\n\trunId?: string;\n}\n\n/** One unanswered approval gate from GET /tasks/pending. */\nexport interface TeamPendingApproval {\n\ttaskId: string;\n\tquestion: string;\n\toptions: string[];\n}\n\n/** hooteams AgentStatus word → task panel agent state. */\nfunction stateFromStatus(status: string | undefined): TaskAgentState {\n\tswitch (status) {\n\t\tcase \"idle\":\n\t\t\treturn \"idle\";\n\t\tcase \"thinking\":\n\t\tcase \"streaming\":\n\t\t\treturn \"active\";\n\t\tcase \"tool\":\n\t\t\treturn \"running\";\n\t\tcase \"done\":\n\t\t\treturn \"done\";\n\t\tcase \"error\":\n\t\t\treturn \"failed\";\n\t\tcase \"paused\":\n\t\t\treturn \"waiting\";\n\t\tdefault:\n\t\t\treturn \"idle\";\n\t}\n}\n\nfunction taskStatusFromState(state: TaskAgentState): TaskStatus {\n\tswitch (state) {\n\t\tcase \"done\":\n\t\t\treturn \"done\";\n\t\tcase \"failed\":\n\t\t\treturn \"failed\";\n\t\tcase \"idle\":\n\t\t\t// Idle is settled, not queued: a \"pending\" task here would survive every\n\t\t\t// taskStore.reset() and pin the pane at \"working\" for the whole session.\n\t\t\treturn \"done\";\n\t\tdefault:\n\t\t\treturn \"in_progress\";\n\t}\n}\n\n/** Only these states represent activity worth a task row of its own. */\nfunction stateWarrantsTask(state: TaskAgentState): boolean {\n\treturn state === \"active\" || state === \"running\" || state === \"failed\" || state === \"waiting\";\n}\n\n/**\n * Maps team status snapshots and TeamEvents onto task-store patches.\n *\n * Each role owns one roster entry (id `team:<role>`) and at most one task whose\n * title tracks the role's latest activity. Tasks exist only while a role is\n * actually doing something (active/running, or failed so the error is visible);\n * idle roles keep their roster entry but no task — the panel's teams lens\n * renders them as placeholder groups, so a quiet team reads as an idle roster\n * instead of pinning the pane at \"working\". Entries are re-created on demand\n * because taskStore.reset() wipes finished tasks between user turns.\n */\nexport class TeamViewMapper {\n\tprivate readonly store: typeof taskStore;\n\tprivate readonly taskIds = new Map<string, number>();\n\n\tconstructor(store: typeof taskStore = taskStore) {\n\t\tthis.store = store;\n\t}\n\n\t/** Register roles from a GET /status snapshot. */\n\tapplyStatus(snapshot: TeamStatusSnapshot): void {\n\t\tfor (const [role, info] of Object.entries(snapshot)) {\n\t\t\tconst state = stateFromStatus(info?.status);\n\t\t\tthis.ensureRole(role, state, info?.lastEventType ?? \"connected\");\n\t\t\tthis.patchRole(role, state);\n\t\t}\n\t}\n\n\t/** Map one TeamEvent from GET /events onto the store. */\n\tapplyEvent(event: TeamViewEvent): void {\n\t\tif (!event || typeof event.role !== \"string\" || event.role.length === 0) return;\n\t\t// Dag settlement events are tagged role=\"orchestrator\" — they describe\n\t\t// the run, not a member, and must not create a phantom roster entry.\n\t\tif (event.type === \"dag_complete\" || event.type === \"dag_failed\") return;\n\t\tconst role = event.role;\n\t\tswitch (event.type) {\n\t\t\tcase \"agent_start\":\n\t\t\tcase \"turn_start\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"thinking\");\n\t\t\t\tthis.patchRole(role, \"active\", \"thinking\");\n\t\t\t\tbreak;\n\t\t\tcase \"message_start\":\n\t\t\tcase \"message_update\":\n\t\t\tcase \"message_end\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"responding\");\n\t\t\t\tthis.patchRole(role, \"active\");\n\t\t\t\tbreak;\n\t\t\tcase \"tool_execution_start\":\n\t\t\t\tthis.ensureRole(role, \"running\", `tool: ${event.toolName ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"running\", `tool: ${event.toolName ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"tool_execution_end\":\n\t\t\t\tthis.ensureRole(role, \"active\", \"thinking\");\n\t\t\t\tthis.patchRole(role, \"active\", \"thinking\");\n\t\t\t\tbreak;\n\t\t\tcase \"turn_end\":\n\t\t\t\tif (event.message?.role === \"assistant\" && event.message.errorMessage) {\n\t\t\t\t\tthis.ensureRole(role, \"failed\", \"error\");\n\t\t\t\t\tthis.patchRole(role, \"failed\", \"error\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"agent_end\": {\n\t\t\t\t// A failed run stays failed; a paused run (waiting on an approval gate)\n\t\t\t\t// stays waiting — hooteams mirrors agent_end right after task_paused, so\n\t\t\t\t// without this guard the gate's \"waiting\" state would flip to idle and the\n\t\t\t\t// roster would read as done while the AskOptions pane is still open.\n\t\t\t\t// agent_end only marks clean completions (mirrors hooteams' own tracking).\n\t\t\t\tconst current = this.store.agents().find((a) => a.id === this.agentId(role))?.state;\n\t\t\t\tif (current !== \"failed\" && current !== \"waiting\") {\n\t\t\t\t\tthis.ensureRole(role, \"done\", \"idle\");\n\t\t\t\t\tthis.patchRole(role, \"done\", \"idle\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"task_started\":\n\t\t\t\tthis.ensureRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"task_paused\": {\n\t\t\t\tconst title = `awaiting approval: ${event.question ?? \"?\"}`;\n\t\t\t\tthis.ensureRole(role, \"waiting\", title);\n\t\t\t\tthis.patchRole(role, \"waiting\", title);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"task_resumed\":\n\t\t\t\tthis.ensureRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tthis.patchRole(role, \"active\", `task: ${event.taskId ?? \"?\"}`);\n\t\t\t\tbreak;\n\t\t\tcase \"task_finished\": {\n\t\t\t\tconst state = event.status === \"error\" ? \"failed\" : \"done\";\n\t\t\t\tthis.ensureRole(role, state, state === \"failed\" ? \"error\" : \"idle\");\n\t\t\t\tthis.patchRole(role, state, state === \"failed\" ? \"error\" : \"idle\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// Unknown event types still prove the role exists.\n\t\t\t\tthis.ensureRole(role, \"idle\", event.type);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate agentId(role: string): string {\n\t\treturn `team:${role}`;\n\t}\n\n\t/**\n\t * Make sure the role's roster entry exists, plus its task when the state\n\t * warrants one (reset() may have dropped both). Idle/done states never\n\t * create a task — only patch one that live activity already opened.\n\t */\n\tprivate ensureRole(role: string, state: TaskAgentState, title: string): void {\n\t\tconst id = this.agentId(role);\n\t\tthis.store.upsertAgent({ id, name: role, kind: \"role\", state });\n\t\tconst taskId = this.taskIds.get(role);\n\t\tconst existing = taskId !== undefined ? this.store.list().find((task) => task.id === taskId) : undefined;\n\t\tif (!existing && stateWarrantsTask(state)) {\n\t\t\tconst task = this.store.create(title, { agent: id });\n\t\t\tthis.store.update(task.id, { status: taskStatusFromState(state) });\n\t\t\tthis.taskIds.set(role, task.id);\n\t\t}\n\t}\n\n\tprivate patchRole(role: string, state: TaskAgentState, title?: string): void {\n\t\tthis.store.patchAgent(this.agentId(role), { state });\n\t\tconst taskId = this.taskIds.get(role);\n\t\tif (taskId === undefined) return;\n\t\tthis.store.update(taskId, { status: taskStatusFromState(state), ...(title !== undefined ? { title } : {}) });\n\t}\n}\n\nexport interface TeamViewOptions {\n\t/** Warning sink; defaults to console.error. */\n\twarn?: (message: string) => void;\n\t/** Store override for tests. */\n\tstore?: typeof taskStore;\n\t/** Delay between reconnect attempts in ms (default 5000). */\n\tretryDelayMs?: number;\n}\n\nexport interface TeamViewConnection {\n\t/** Close the SSE connection and stop reconnecting. */\n\tstop(): void;\n\t/** POST /steer { role, message }. Rejects on network or HTTP error. */\n\tsteer(role: string, message: string): Promise<void>;\n\t/**\n\t * Answer a paused task: POST /tasks/:taskId/resume { option, feedback? }.\n\t * Rejects with \"answered elsewhere\" on 409 (first answer wins across\n\t * surfaces) and with HTTP/network errors otherwise.\n\t */\n\tresume(taskId: string, option: string, feedback?: string): Promise<void>;\n\t/**\n\t * GET /tasks/pending — gates that opened before we attached. Resolves to\n\t * [] when the server has no active run (404) so callers need no special\n\t * casing.\n\t */\n\tpendingApprovals(): Promise<TeamPendingApproval[]>;\n\t/**\n\t * Subscribe to every TeamEvent delivered by the shared /events stream.\n\t * Returns an unsubscribe function. Listeners receive events for all roles;\n\t * per-role filtering is the subscriber's job (the attach panel filters).\n\t */\n\tsubscribe(listener: (event: TeamViewEvent) => void): () => void;\n\t/** Number of live event subscribers. Exposed for leak tests. */\n\tsubscriberCount(): number;\n}\n\nconst STATUS_TIMEOUT_MS = 5000;\nconst STEER_TIMEOUT_MS = 5000;\n\n/**\n * Start the read-only team view against a hooteams server base URL.\n *\n * Returns immediately; all network work happens in the background and any\n * failure is reported through `warn` without ever throwing.\n */\nexport function connectTeamView(url: string, options: TeamViewOptions = {}): TeamViewConnection {\n\tconst base = url.replace(/\\/+$/, \"\");\n\tconst warn = options.warn ?? ((message: string) => console.error(message));\n\tconst retryDelayMs = options.retryDelayMs ?? 5000;\n\tconst mapper = new TeamViewMapper(options.store);\n\tconst controller = new AbortController();\n\tconst listeners = new Set<(event: TeamViewEvent) => void>();\n\tlet stopped = false;\n\n\tconst deliver = (event: TeamViewEvent): void => {\n\t\tmapper.applyEvent(event);\n\t\tfor (const listener of listeners) {\n\t\t\ttry {\n\t\t\t\tlistener(event);\n\t\t\t} catch {\n\t\t\t\t// A broken subscriber must not take down the stream or its peers.\n\t\t\t}\n\t\t}\n\t};\n\n\tconst run = async (): Promise<void> => {\n\t\t// 1. Status snapshot: register the current roles.\n\t\ttry {\n\t\t\tconst response = await fetch(`${base}/status`, {\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STATUS_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t\tmapper.applyStatus((await response.json()) as TeamStatusSnapshot);\n\t\t} catch (error) {\n\t\t\tif (stopped) return;\n\t\t\twarn(`team view: failed to fetch ${base}/status (${String(error)}); continuing without the team view`);\n\t\t}\n\n\t\t// 2. Single SSE subscription, reconnecting on drops.\n\t\tlet announcedDrop = false;\n\t\twhile (!stopped) {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${base}/events`, { signal: controller.signal });\n\t\t\t\tif (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`);\n\t\t\t\tconst reader = response.body.getReader();\n\t\t\t\tconst decoder = new TextDecoder();\n\t\t\t\tlet buffer = \"\";\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { value, done } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\t// Only a stream that actually delivers data counts as recovered. A 200\n\t\t\t\t\t// that closes immediately (e.g. a server that answers /events without\n\t\t\t\t\t// streaming) used to re-arm the warning and repeat it every retry.\n\t\t\t\t\tannouncedDrop = false;\n\t\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t\t\tlet index = buffer.indexOf(\"\\n\\n\");\n\t\t\t\t\twhile (index !== -1) {\n\t\t\t\t\t\tconst frame = buffer.slice(0, index);\n\t\t\t\t\t\tbuffer = buffer.slice(index + 2);\n\t\t\t\t\t\tfor (const line of frame.split(\"\\n\")) {\n\t\t\t\t\t\t\tif (!line.startsWith(\"data:\")) continue;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tdeliver(JSON.parse(line.slice(5).trim()) as TeamViewEvent);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Malformed frames are dropped; the stream stays up.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tindex = buffer.indexOf(\"\\n\\n\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (stopped) return;\n\t\t\t\tthrow new Error(\"stream ended\");\n\t\t\t} catch (error) {\n\t\t\t\tif (stopped || controller.signal.aborted) return;\n\t\t\t\tif (!announcedDrop) {\n\t\t\t\t\tannouncedDrop = true;\n\t\t\t\t\twarn(`team view: lost connection to ${base}/events (${String(error)}); retrying in background`);\n\t\t\t\t}\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, retryDelayMs));\n\t\t\t}\n\t\t}\n\t};\n\n\tvoid run().catch((error) => {\n\t\tif (!stopped) warn(`team view: unexpected error (${String(error)})`);\n\t});\n\n\treturn {\n\t\tstop() {\n\t\t\tstopped = true;\n\t\t\tlisteners.clear();\n\t\t\tcontroller.abort();\n\t\t},\n\t\tasync steer(role: string, message: string): Promise<void> {\n\t\t\tconst response = await fetch(`${base}/steer`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ role, message }),\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STEER_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t},\n\t\tasync resume(taskId: string, option: string, feedback?: string): Promise<void> {\n\t\t\tconst response = await fetch(`${base}/tasks/${encodeURIComponent(taskId)}/resume`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ option, ...(feedback !== undefined ? { feedback } : {}) }),\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STEER_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (response.status === 409) throw new Error(`task \"${taskId}\" was answered elsewhere`);\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t},\n\t\tasync pendingApprovals(): Promise<TeamPendingApproval[]> {\n\t\t\tconst response = await fetch(`${base}/tasks/pending`, {\n\t\t\t\tsignal: AbortSignal.any([controller.signal, AbortSignal.timeout(STATUS_TIMEOUT_MS)]),\n\t\t\t});\n\t\t\tif (response.status === 404) return [];\n\t\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\t\tconst body = (await response.json()) as { pending?: TeamPendingApproval[] };\n\t\t\treturn Array.isArray(body.pending) ? body.pending : [];\n\t\t},\n\t\tsubscribe(listener: (event: TeamViewEvent) => void): () => void {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tsubscriberCount(): number {\n\t\t\treturn listeners.size;\n\t\t},\n\t};\n}\n"]}
@@ -14,6 +14,7 @@
14
14
  * 1. ~/.hoocode/hoo-config.json (global defaults)
15
15
  * 2. ./.hoocode/hoo-config.json (project overrides — scalars win; arrays union)
16
16
  */
17
+ import type { ThinkingLevel } from "@kolisachint/hoocode-agent-core";
17
18
  import type { ExtensionAPI } from "../../core/extensions/types.js";
18
19
  interface ModeConfig {
19
20
  /** Tool names that bypass the permission gate in this mode */
@@ -29,6 +30,32 @@ interface ModeConfig {
29
30
  /** Regex patterns for denied bash commands. A command matching any pattern is blocked. */
30
31
  denied_bash_commands?: string[];
31
32
  }
33
+ /**
34
+ * Tool-outcome-driven thinking escalation (on by default).
35
+ *
36
+ * The default (fast) path keeps thinking low so mechanical tool turns — reads,
37
+ * greps, successful edits — don't pay the extended-thinking prefill tax. When a
38
+ * tool *fails*, the next turn(s) escalate to a higher thinking level so the model
39
+ * reasons through the failure, then the level is restored. This buys low latency
40
+ * on the happy path while preserving deep reasoning exactly when something breaks.
41
+ *
42
+ * Enabled by default; set `thinking_escalation.enabled` to `false` to turn it off.
43
+ *
44
+ * Note: escalation uses the same setter as manual thinking control, so the
45
+ * escalated level is briefly written to settings and restored when the window
46
+ * ends. If a run is interrupted mid-window, the escalated level may persist until
47
+ * the next change.
48
+ */
49
+ interface ThinkingEscalationConfig {
50
+ /** Master switch. Default: true (set to false to disable). */
51
+ enabled?: boolean;
52
+ /** Level to escalate to after a tool error. Default: "high". */
53
+ on_error?: ThinkingLevel;
54
+ /** Restrict escalation to errors from these tool names. Default: any tool. */
55
+ tools?: string[];
56
+ /** Number of subsequent turns to stay escalated after an error. Default: 1. */
57
+ cooldown_turns?: number;
58
+ }
32
59
  export interface HooConfig {
33
60
  /** Manually-pinned active mode (overrides default "build") */
34
61
  active_mode?: string;
@@ -36,6 +63,8 @@ export interface HooConfig {
36
63
  modes?: Record<string, ModeConfig>;
37
64
  /** Extra directories to search for `{name}/system.md` mode files (after project + user). */
38
65
  mode_paths?: string[];
66
+ /** Raise thinking after tool failures, restore on success. On by default; set `enabled: false` to disable. */
67
+ thinking_escalation?: ThinkingEscalationConfig;
39
68
  }
40
69
  /**
41
70
  * Deep-merges a project-local config on top of the global config.
@@ -46,6 +75,7 @@ export interface HooConfig {
46
75
  * - modes[x].allowed_write_paths: union of global + project arrays
47
76
  * - modes[x].enabled_tools: project wins if set, else falls back to global
48
77
  * - mode_paths: project list is prepended so project paths are searched first
78
+ * - thinking_escalation: project wins as a whole if set, else inherit global
49
79
  */
50
80
  export declare function mergeConfigs(global: HooConfig, project: HooConfig): HooConfig;
51
81
  /**
@@ -112,6 +142,20 @@ export declare function parsePlanSections(planContent: string): PlanSections;
112
142
  export declare function buildApproveMessage(sections: PlanSections): string;
113
143
  export declare function setupMode(pi: ExtensionAPI): void;
114
144
  export declare function setupAskOptions(pi: ExtensionAPI): void;
145
+ /**
146
+ * Raise the thinking level for the turn(s) following a tool error, then restore
147
+ * it. Opt-in via `thinking_escalation.enabled` in hoo-config.json.
148
+ *
149
+ * Timing model (per agent run):
150
+ * turn N: assistant calls tools → tool_execution_end(s) → turn_end
151
+ * a failing tool here escalates the level used by turn N+1
152
+ * turn N+1: runs escalated; its turn_end decrements the cooldown and, at zero,
153
+ * restores the captured baseline so turn N+2 is fast again
154
+ *
155
+ * `escalatedThisTurn` ensures the error turn's own `turn_end` does not consume a
156
+ * cooldown step — the cooldown counts the turns *after* the failure.
157
+ */
158
+ export declare function setupThinkingEscalation(pi: ExtensionAPI): void;
115
159
  declare function hooCore(pi: ExtensionAPI): void;
116
160
  declare namespace hooCore {
117
161
  var displayName: string;