osborn 0.9.240 → 0.9.241

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.
@@ -2321,6 +2321,16 @@ class ClaudeLLMStream extends llm.LLMStream {
2321
2321
  hooks: [async (input) => {
2322
2322
  console.log('[LIFECYCLE-PROBE] SubagentStart', JSON.stringify(input));
2323
2323
  this.#eventEmitter.emit('agent_started', { agent_type: input?.agent_type, agent_id: input?.agent_id });
2324
+ // Record the dispatch as LIVE so the frontend can reconcile orphaned
2325
+ // "running" rows against statusManager.getActiveDispatchIds(). Without a
2326
+ // start-time entry the manager only knew about dispatches at completion,
2327
+ // so a reconcile had no authoritative live set to diff against.
2328
+ if (input?.agent_id) {
2329
+ try {
2330
+ statusManager.upsertDispatch(input.agent_id, { subagentType: input?.agent_type, dispatchState: 'running' });
2331
+ }
2332
+ catch { }
2333
+ }
2324
2334
  return {};
2325
2335
  }]
2326
2336
  }],
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ import { createSTT, createTTS } from './voice-io.js';
39
39
  import { createClaudeLLM, NAMED_AGENTS, applyTurbo } from './claude-llm.js';
40
40
  import { clearPipelineFastBrainSession, prewarmBM25Index } from './pipeline-fastbrain.js';
41
41
  import { ensureClaudeAuth, applyAuthFallback, replayInFlightAuth } from './claude-auth.js';
42
+ import { statusManager } from './status-manager.js';
42
43
  import { createSmitheryProxy, destroySmitheryProxy, parseSmitheryUrl, isSmitheryUrl, SmitheryAuthorizationError } from './smithery-proxy.js';
43
44
  import { DIRECT_MODE_PROMPT } from './prompts.js';
44
45
  import { MCP_CATALOG } from './config.js';
@@ -5952,6 +5953,12 @@ async function main() {
5952
5953
  processes: topProcesses,
5953
5954
  totalCount,
5954
5955
  memory: { usedMb, totalMb, freeMb },
5956
+ // Authoritative set of still-live sub-agent dispatches. The frontend
5957
+ // reconciles its "running" background-flow rows against this and clears
5958
+ // any orphan whose completion/stop event was lost (or belonged to a
5959
+ // now-dead agent process — this list is per-process, so it's empty
5960
+ // after a restart and every stale row gets swept on the next poll).
5961
+ activeDispatchIds: statusManager.getActiveDispatchIds(),
5955
5962
  });
5956
5963
  }
5957
5964
  catch (err) {
@@ -90,6 +90,18 @@ export declare class StatusManager {
90
90
  * Get context summary for the brain
91
91
  */
92
92
  getContextSummary(): string;
93
+ /**
94
+ * Dispatcher reconciliation — the agent_ids of dispatches that are still LIVE
95
+ * (a real sub-agent dispatch, i.e. has a subagentType, whose dispatchState has
96
+ * not reached a terminal 'completed'/'failed'/'rejected'). The frontend
97
+ * cross-checks its "running" background-flow rows against this authoritative
98
+ * set on each process_list poll and clears any row NOT present — self-healing
99
+ * the orphaned "running" cards left when a task_completed / agent_stopped event
100
+ * was lost to a disconnect. Because this manager is a per-process singleton, an
101
+ * agent restart (the common session-drop case) resets it to empty, so every
102
+ * stale row from the dead process is reconciled away on the next poll.
103
+ */
104
+ getActiveDispatchIds(): string[];
93
105
  /**
94
106
  * Dispatcher v1 — create or update a dispatch entry keyed by tool_use_id.
95
107
  * Creates a minimal TaskStatus shell if the id doesn't yet exist so callers
@@ -182,6 +182,25 @@ export class StatusManager {
182
182
  getContextSummary() {
183
183
  return this.conversationContext.slice(-5).join(' | ');
184
184
  }
185
+ /**
186
+ * Dispatcher reconciliation — the agent_ids of dispatches that are still LIVE
187
+ * (a real sub-agent dispatch, i.e. has a subagentType, whose dispatchState has
188
+ * not reached a terminal 'completed'/'failed'/'rejected'). The frontend
189
+ * cross-checks its "running" background-flow rows against this authoritative
190
+ * set on each process_list poll and clears any row NOT present — self-healing
191
+ * the orphaned "running" cards left when a task_completed / agent_stopped event
192
+ * was lost to a disconnect. Because this manager is a per-process singleton, an
193
+ * agent restart (the common session-drop case) resets it to empty, so every
194
+ * stale row from the dead process is reconciled away on the next poll.
195
+ */
196
+ getActiveDispatchIds() {
197
+ return Array.from(this.tasks.values())
198
+ .filter(t => !!t.subagentType &&
199
+ t.dispatchState !== 'completed' &&
200
+ t.dispatchState !== 'failed' &&
201
+ t.dispatchState !== 'rejected')
202
+ .map(t => t.id);
203
+ }
185
204
  /**
186
205
  * Dispatcher v1 — create or update a dispatch entry keyed by tool_use_id.
187
206
  * 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.240",
3
+ "version": "0.9.241",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {