osborn 0.9.246 → 0.9.248

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.
@@ -2022,7 +2022,7 @@ class ClaudeLLMStream extends llm.LLMStream {
2022
2022
  // the permission dialog; adversarial agents (reviewer/tester) do
2023
2023
  // NOT emit here — parity with the prior hardcoded branches.
2024
2024
  if (agentType === 'writer') {
2025
- this.#eventEmitter.emit('tool_use', { name: toolName, input: toolInput, agentRole: agentType || 'main' });
2025
+ this.#eventEmitter.emit('tool_use', { name: toolName, input: toolInput, agentRole: agentType || 'main', agentId: input?.agent_id });
2026
2026
  }
2027
2027
  return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'ask' } };
2028
2028
  }
@@ -2038,7 +2038,7 @@ class ClaudeLLMStream extends llm.LLMStream {
2038
2038
  if (st && d)
2039
2039
  this.#pendingTaskByType.set(st, d);
2040
2040
  }
2041
- this.#eventEmitter.emit('tool_use', { name: toolName, input: toolInput, agentRole: agentType || 'main' });
2041
+ this.#eventEmitter.emit('tool_use', { name: toolName, input: toolInput, agentRole: agentType || 'main', agentId: input?.agent_id });
2042
2042
  return {};
2043
2043
  }]
2044
2044
  }],
@@ -2054,7 +2054,7 @@ class ClaudeLLMStream extends llm.LLMStream {
2054
2054
  const agentId = input?.tool_input?.agent_id ?? input?.tool_input?.id ?? undefined;
2055
2055
  console.log('[LIFECYCLE-PROBE] PostToolUse on', toolName, 'agent_id=', agentId, 'ts=', Date.now());
2056
2056
  }
2057
- this.#eventEmitter.emit('tool_result', { name: toolName, input: toolInput, response: toolResponse, agentRole: agentTypePost || 'main' });
2057
+ this.#eventEmitter.emit('tool_result', { name: toolName, input: toolInput, response: toolResponse, agentRole: agentTypePost || 'main', agentId: input?.agent_id });
2058
2058
  // Pattern #1 verifier chaining has MOVED to the SubagentStop hook below. Reason:
2059
2059
  // sub-agents run with run_in_background:true, so PostToolUse-on-Task fires at
2060
2060
  // DISPATCH (placeholder tool_response), not completion — injecting the "now verify"
package/dist/index.js CHANGED
@@ -60,8 +60,10 @@ import { llm } from '@livekit/agents';
60
60
  // any field that can't be derived is simply omitted and the card degrades to
61
61
  // the tool name. `input` is the raw tool arguments already carried on the
62
62
  // tool_use/tool_result events emitted by claude-llm.
63
- function buildToolLogEvent(name, input, status, agentRole) {
64
- const ev = { type: 'tool_use', tool: name, status, agentRole };
63
+ function buildToolLogEvent(name, input, status, agentRole, agentId) {
64
+ // agentId is the sub-agent's id (present only for sub-agent tool calls) — lets the
65
+ // frontend tie this activity to its Running row, not just the Log feed.
66
+ const ev = { type: 'tool_use', tool: name, status, agentRole, agentId };
65
67
  const inp = input || {};
66
68
  const basename = (p) => String(p).split('/').pop() || String(p);
67
69
  const fp = inp.file_path || inp.path || inp.notebook_path;
@@ -3392,11 +3394,11 @@ async function main() {
3392
3394
  // Wire up events from the Claude SDK wrapper to frontend
3393
3395
  directLLM.events.on('tool_use', (data) => {
3394
3396
  console.log(`🔧 Claude: ${data.name}`);
3395
- sendToFrontend(buildToolLogEvent(data.name, data.input, 'running', data.agentRole || 'main'));
3397
+ sendToFrontend(buildToolLogEvent(data.name, data.input, 'running', data.agentRole || 'main', data.agentId));
3396
3398
  });
3397
3399
  directLLM.events.on('tool_result', (data) => {
3398
3400
  console.log(`✅ Done: ${data.name}`);
3399
- sendToFrontend(buildToolLogEvent(data.name, data.input, 'completed', data.agentRole || 'main'));
3401
+ sendToFrontend(buildToolLogEvent(data.name, data.input, 'completed', data.agentRole || 'main', data.agentId));
3400
3402
  // Detect research artifact writes (session workspace or legacy research dir)
3401
3403
  if ((data.name === 'Write' || data.name === 'Edit') && data.input?.file_path) {
3402
3404
  const fp = data.input.file_path;
@@ -3754,6 +3756,7 @@ async function main() {
3754
3756
  type: 'agent_started',
3755
3757
  agent_type: d.agent_type,
3756
3758
  agent_id: d.agent_id,
3759
+ task: d.task, // forward the dispatch instruction so the Running row can label itself
3757
3760
  }));
3758
3761
  // Create the Agent with instructions, STT, LLM, TTS
3759
3762
  // VAD (Silero ONNX) removed — caused 2-5s inference lag on CPU, making interruption detection worse
@@ -12,6 +12,7 @@ import { llm, DEFAULT_API_CONNECT_OPTIONS } from '@livekit/agents';
12
12
  import { ClaudeLLM } from './claude-llm.js';
13
13
  import { askPipelineFastBrain } from './pipeline-fastbrain.js';
14
14
  import { storeExists, openStore, recentRows } from './session-store.js';
15
+ import { statusManager } from './status-manager.js';
15
16
  // ============================================================
16
17
  // SESSION TAIL — compact recent-conversation block prepended to every user turn
17
18
  // so the orchestrator never loses the thread across context compaction.
@@ -185,6 +186,22 @@ export class PipelineDirectLLM extends llm.LLM {
185
186
  // Actual mid-TTS interruption — keep the full [INTERRUPTED] template
186
187
  console.log(`🔇 [pipeline] Enriching: interrupt (spoken=${interruptCtx.spokenText.length} chars, suppressed=${interruptCtx.suppressedText.length} chars)`);
187
188
  this.#claudeLLM.interruptQuery().catch(() => { });
189
+ // An interrupt/cutoff SEVERS in-flight background Task sub-agents — the SDK's
190
+ // interrupt() kills run_in_background Task children and they never fire SubagentStop,
191
+ // so the agent gets NO signal they died and they can't be resumed. statusManager still
192
+ // lists them (start-tracked, never stopped) = the authoritative orphan set. Name them
193
+ // so the agent re-dispatches the necessary ones; mark them terminal so the tracker +
194
+ // Running panel don't leave them stuck "running" forever.
195
+ const orphanedSubagents = statusManager.getActiveDispatches();
196
+ const subagentReminder = orphanedSubagents.length
197
+ ? `SUB-AGENTS SEVERED BY THE CUTOFF: your in-flight background sub-agents were killed by the interrupt and will NOT report back (no completion signal, cannot be resumed): ${orphanedSubagents.map(o => o.subagentType || 'agent').join(', ')}. Check which of these you still need and RE-DISPATCH them fresh — do not wait on them.`
198
+ : `SUB-AGENTS: if you had any background Task sub-agents in flight, an interrupt severs them silently (no completion signal, no resume) — check and re-dispatch any still needed rather than waiting on them.`;
199
+ for (const o of orphanedSubagents) {
200
+ try {
201
+ statusManager.upsertDispatch(o.id, { dispatchState: 'failed' });
202
+ }
203
+ catch { }
204
+ }
188
205
  enrichedMessage = [
189
206
  `[INTERRUPTED] The user interrupted your response mid-speech.`,
190
207
  ``,
@@ -206,6 +223,8 @@ export class PipelineDirectLLM extends llm.LLM {
206
223
  `CONTEXT PRESERVATION (READ THIS):`,
207
224
  `The user has NO memory of unheard content. If any of it bears on their current message — answers a question they just asked, sets up a follow-up they're now asking about, or shows a knowledge gap and fills in a detail they're reacting to — you MUST surface it. Briefly is fine if their message is off-topic or explores a direction they haven't asked about yet. But never assume they remember what they never heard.`,
208
225
  ``,
226
+ subagentReminder,
227
+ ``,
209
228
  `RESPOND with speech first, then act:`,
210
229
  `- ALWAYS reply with at least one spoken sentence before doing any tool calls`,
211
230
  `- If it's a quick side question, answer it then continue where you left off`,
@@ -102,6 +102,16 @@ export declare class StatusManager {
102
102
  * stale row from the dead process is reconciled away on the next poll.
103
103
  */
104
104
  getActiveDispatchIds(): string[];
105
+ /**
106
+ * Non-terminal dispatches with their type — the set considered "in flight". Used on
107
+ * a voice interrupt to name the sub-agents that were just severed: an interrupt kills
108
+ * in-flight Task sub-agents (SDK regression) and they never fire SubagentStop, so they
109
+ * linger here as the authoritative orphan list until we mark them terminal.
110
+ */
111
+ getActiveDispatches(): Array<{
112
+ id: string;
113
+ subagentType?: string;
114
+ }>;
105
115
  /**
106
116
  * Dispatcher v1 — create or update a dispatch entry keyed by tool_use_id.
107
117
  * Creates a minimal TaskStatus shell if the id doesn't yet exist so callers
@@ -201,6 +201,20 @@ export class StatusManager {
201
201
  t.dispatchState !== 'rejected')
202
202
  .map(t => t.id);
203
203
  }
204
+ /**
205
+ * Non-terminal dispatches with their type — the set considered "in flight". Used on
206
+ * a voice interrupt to name the sub-agents that were just severed: an interrupt kills
207
+ * in-flight Task sub-agents (SDK regression) and they never fire SubagentStop, so they
208
+ * linger here as the authoritative orphan list until we mark them terminal.
209
+ */
210
+ getActiveDispatches() {
211
+ return Array.from(this.tasks.values())
212
+ .filter(t => !!t.subagentType &&
213
+ t.dispatchState !== 'completed' &&
214
+ t.dispatchState !== 'failed' &&
215
+ t.dispatchState !== 'rejected')
216
+ .map(t => ({ id: t.id, subagentType: t.subagentType }));
217
+ }
204
218
  /**
205
219
  * Dispatcher v1 — create or update a dispatch entry keyed by tool_use_id.
206
220
  * Creates a minimal TaskStatus shell if the id doesn't yet exist so callers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.246",
3
+ "version": "0.9.248",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {