@oar-sh/oar 0.9.0
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/.github/extensions/web-relay/README.md +190 -0
- package/.github/extensions/web-relay/extension.mjs +972 -0
- package/.github/extensions/web-relay/model-api/model-switching.mjs +338 -0
- package/.github/extensions/web-relay/polling/heartbeat.mjs +55 -0
- package/.github/extensions/web-relay/polling/polling-loop.mjs +1105 -0
- package/.github/extensions/web-relay/runtime/api-client.mjs +34 -0
- package/.github/extensions/web-relay/runtime/banner-state.mjs +126 -0
- package/.github/extensions/web-relay/runtime/config-loader.mjs +104 -0
- package/.github/extensions/web-relay/runtime/debug-log.mjs +27 -0
- package/.github/extensions/web-relay/runtime/send-and-wait-errors.mjs +170 -0
- package/.github/extensions/web-relay/runtime/session-binding.mjs +78 -0
- package/.github/extensions/web-relay/runtime/session-io.mjs +349 -0
- package/.github/extensions/web-relay/runtime/session-join-retry.mjs +44 -0
- package/.github/extensions/web-relay/runtime/session-registry.mjs +29 -0
- package/.github/extensions/web-relay/runtime/session-runtime-manager.mjs +156 -0
- package/.github/extensions/web-relay/runtime/session-sync-bridge.mjs +74 -0
- package/.github/extensions/web-relay/runtime/worker-websocket-link.mjs +325 -0
- package/.github/extensions/web-relay/runtime/workspace-root.mjs +55 -0
- package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +386 -0
- package/.github/extensions/web-relay/skills/prompt-context.mjs +160 -0
- package/.github/extensions/web-relay/skills/question-bridge.mjs +141 -0
- package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +463 -0
- package/.github/extensions/web-relay/skills/reasoning-stream.mjs +285 -0
- package/.github/extensions/web-relay/skills/subagent-lifecycle.mjs +345 -0
- package/.github/extensions/web-relay/skills/tool-activity.mjs +243 -0
- package/.github/extensions/web-relay/utils/process-utils.mjs +25 -0
- package/.github/extensions/web-relay/utils/token-generation.mjs +58 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +717 -0
- package/bin/bat/oar.bat +46 -0
- package/bin/oar.js +665 -0
- package/package.json +68 -0
- package/server/README.md +1379 -0
- package/server/claude-worker/claude-attachments.mjs +134 -0
- package/server/claude-worker/claude-preview-tool.mjs +57 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +420 -0
- package/server/claude-worker/claude-session-process.mjs +2391 -0
- package/server/claude-worker/claude-session-test-harness.mjs +236 -0
- package/server/claude-worker/claude-session-worker.mjs +186 -0
- package/server/claude-worker/claude-transcript-relocator.mjs +189 -0
- package/server/claude-worker/claude-turn-publisher.mjs +291 -0
- package/server/claude-worker/fixtures/workflow-run-record.json +1 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +629 -0
- package/server/claude-worker/workflow-progress-digest.mjs +269 -0
- package/server/copilot-sdk-runtime.mjs +126 -0
- package/server/copilot-worker/copilot-attachments.mjs +97 -0
- package/server/copilot-worker/copilot-byok-provider.mjs +121 -0
- package/server/copilot-worker/copilot-continuation-signals.mjs +349 -0
- package/server/copilot-worker/copilot-plan-board.mjs +110 -0
- package/server/copilot-worker/copilot-prompt-context.mjs +86 -0
- package/server/copilot-worker/copilot-question-bridge.mjs +216 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +494 -0
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +800 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +1729 -0
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +167 -0
- package/server/copilot-worker/copilot-sdk-test-harness.mjs +329 -0
- package/server/copilot-worker/fixtures/abort-turn.json +588 -0
- package/server/copilot-worker/fixtures/ask-user-turn.json +1154 -0
- package/server/copilot-worker/fixtures/background-timer-continuation.json +445 -0
- package/server/copilot-worker/fixtures/background-timer-turn.json +497 -0
- package/server/copilot-worker/fixtures/happy-turn.json +566 -0
- package/server/copilot-worker/fixtures/quota-turn.json +647 -0
- package/server/copilot-worker/fixtures/reasoning-turn.json +625 -0
- package/server/copilot-worker/fixtures/resume-turn.json +700 -0
- package/server/copilot-worker/fixtures/subagent-turn.json +1378 -0
- package/server/copilot-worker/fixtures/tool-permission-turn.json +1293 -0
- package/server/cursor-worker/cursor-ask-user-tool.mjs +83 -0
- package/server/cursor-worker/cursor-attachments.mjs +88 -0
- package/server/cursor-worker/cursor-context-usage.mjs +85 -0
- package/server/cursor-worker/cursor-preview-tool.mjs +44 -0
- package/server/cursor-worker/cursor-sdk-adapter.mjs +554 -0
- package/server/cursor-worker/cursor-session-worker.mjs +145 -0
- package/server/cursor-worker/cursor-subagent-roster.mjs +81 -0
- package/server/cursor-worker/cursor-turn-runner.mjs +717 -0
- package/server/cursor-worker/sdk-message-normalizer.mjs +485 -0
- package/server/db-schema.mjs +932 -0
- package/server/features.mjs +106 -0
- package/server/grok-worker/acp-client.mjs +434 -0
- package/server/grok-worker/acp-host-services.mjs +277 -0
- package/server/grok-worker/grok-context-usage.mjs +77 -0
- package/server/grok-worker/grok-sdk-adapter.mjs +334 -0
- package/server/grok-worker/grok-session-worker.mjs +145 -0
- package/server/grok-worker/grok-turn-runner.mjs +523 -0
- package/server/grok-worker/sdk-message-normalizer.mjs +311 -0
- package/server/migrations/0001-add-structured-answer.mjs +80 -0
- package/server/migrations/0002-recent-workspace-roots-path-key.mjs +121 -0
- package/server/migrations/0003-push-subscriptions.mjs +87 -0
- package/server/public/app/action-confirmations.js +267 -0
- package/server/public/app/activity-replay-state.mjs +149 -0
- package/server/public/app/api-client.js +1082 -0
- package/server/public/app/ask-user-view.js +545 -0
- package/server/public/app/attachments-view.js +2112 -0
- package/server/public/app/auto-compact-window-options.mjs +59 -0
- package/server/public/app/background-tasks-view.mjs +494 -0
- package/server/public/app/bootstrap.js +4347 -0
- package/server/public/app/claude-auth-ui.js +394 -0
- package/server/public/app/claude-thinking-options.mjs +38 -0
- package/server/public/app/cli-install-ui.js +455 -0
- package/server/public/app/code-copy.mjs +85 -0
- package/server/public/app/composer-attachment-cache.mjs +151 -0
- package/server/public/app/composer-control-state.mjs +107 -0
- package/server/public/app/composer-paste.mjs +132 -0
- package/server/public/app/context-tier-options.mjs +68 -0
- package/server/public/app/context-usage-view.mjs +338 -0
- package/server/public/app/conversation-draft-timestamp-utils.mjs +11 -0
- package/server/public/app/conversation-preferences.mjs +91 -0
- package/server/public/app/conversation-provider-indicator.mjs +88 -0
- package/server/public/app/conversation-view.js +2898 -0
- package/server/public/app/cwd-menu-placement.mjs +85 -0
- package/server/public/app/cwd-picker.js +605 -0
- package/server/public/app/emoji-view.js +101 -0
- package/server/public/app/external-link-policy.mjs +80 -0
- package/server/public/app/file-preview-navigation.mjs +51 -0
- package/server/public/app/font-scaling.js +262 -0
- package/server/public/app/git-changes-view.js +339 -0
- package/server/public/app/git-diff-model.mjs +109 -0
- package/server/public/app/grok-auth-ui.js +387 -0
- package/server/public/app/image-downscale.mjs +174 -0
- package/server/public/app/in-flight-snapshot.mjs +40 -0
- package/server/public/app/infinite-loader.js +164 -0
- package/server/public/app/journal-view.js +1141 -0
- package/server/public/app/known-cwd-options.mjs +41 -0
- package/server/public/app/launch-cwd-path.mjs +23 -0
- package/server/public/app/live-message-dedupe.mjs +57 -0
- package/server/public/app/message-search-view.js +242 -0
- package/server/public/app/model-selector-options.mjs +31 -0
- package/server/public/app/model-vision-support.mjs +56 -0
- package/server/public/app/new-conversation-model-choice.mjs +67 -0
- package/server/public/app/plan-usage-view.mjs +307 -0
- package/server/public/app/preview-cards.mjs +273 -0
- package/server/public/app/preview-command.mjs +103 -0
- package/server/public/app/push-settings.js +370 -0
- package/server/public/app/pwa-install.js +456 -0
- package/server/public/app/question-schema-view.mjs +71 -0
- package/server/public/app/reasoning-effort-labels.mjs +42 -0
- package/server/public/app/relay-board-view.js +117 -0
- package/server/public/app/relay-dot-state.mjs +69 -0
- package/server/public/app/relay-error-ctas.mjs +52 -0
- package/server/public/app/relay-prompt-sanitizer.mjs +114 -0
- package/server/public/app/relay-thoughts.mjs +98 -0
- package/server/public/app/relay-watchdog-policy.mjs +29 -0
- package/server/public/app/repo-browser-preferences.mjs +79 -0
- package/server/public/app/repo-browser-tree-state.mjs +97 -0
- package/server/public/app/router.js +657 -0
- package/server/public/app/selection-guard.mjs +93 -0
- package/server/public/app/settings-modal.js +1484 -0
- package/server/public/app/settings-tabs.js +129 -0
- package/server/public/app/slash-autocomplete.mjs +188 -0
- package/server/public/app/slash-commands.mjs +183 -0
- package/server/public/app/socket-handlers.js +703 -0
- package/server/public/app/status-store.mjs +242 -0
- package/server/public/app/status-view.mjs +315 -0
- package/server/public/app/store.js +1454 -0
- package/server/public/app/stream-state.mjs +73 -0
- package/server/public/app/streaming-dom-patch.mjs +23 -0
- package/server/public/app/sync-outbox.mjs +208 -0
- package/server/public/app/tap-actions.js +62 -0
- package/server/public/app/thread-order.mjs +54 -0
- package/server/public/app/tmux-inspector-view.js +422 -0
- package/server/public/app/transcript-separators.mjs +282 -0
- package/server/public/app-icon-192.png +0 -0
- package/server/public/app-icon-512.png +0 -0
- package/server/public/app-icon.svg +4 -0
- package/server/public/favicon.ico +0 -0
- package/server/public/index.html +5743 -0
- package/server/public/manifest.webmanifest +32 -0
- package/server/public/sw.js +307 -0
- package/server/relay-exit-codes.mjs +4 -0
- package/server/relay-self-restart.mjs +170 -0
- package/server/relay-tools.md +27 -0
- package/server/relay.mjs +1026 -0
- package/server/repositories/image-conversation-repository.mjs +317 -0
- package/server/repositories/message-repository.mjs +434 -0
- package/server/repositories/question-repository.mjs +161 -0
- package/server/repositories/session-repository.mjs +320 -0
- package/server/repositories/share-visibility-statements.mjs +24 -0
- package/server/routes/ask-user-routes.mjs +292 -0
- package/server/routes/cache-routes.mjs +44 -0
- package/server/routes/git-routes.mjs +92 -0
- package/server/routes/messages-routes-test-harness.mjs +111 -0
- package/server/routes/messages-routes.mjs +7107 -0
- package/server/routes/preview-routes.mjs +108 -0
- package/server/routes/push-routes.mjs +59 -0
- package/server/routes/relay-board-routes.mjs +268 -0
- package/server/routes/sessions-routes.mjs +6150 -0
- package/server/server-runtime.mjs +7463 -0
- package/server/server.js +24 -0
- package/server/services/ask-user-routing-service.mjs +117 -0
- package/server/services/cache-rebuild-service.mjs +236 -0
- package/server/services/claude-auth-service.mjs +578 -0
- package/server/services/claude-context-usage.mjs +240 -0
- package/server/services/claude-session-root-service.mjs +228 -0
- package/server/services/cli-install-service.mjs +1152 -0
- package/server/services/cli-process-runner.mjs +181 -0
- package/server/services/cloudflared-tunnel-service.mjs +358 -0
- package/server/services/context-snapshot-service.mjs +499 -0
- package/server/services/context-usage-view.mjs +160 -0
- package/server/services/conversation-preferences-service.mjs +208 -0
- package/server/services/cursor-dashboard-usage.mjs +217 -0
- package/server/services/cursor-session-root-service.mjs +62 -0
- package/server/services/delete-archive-service.mjs +154 -0
- package/server/services/drives-path-helpers.mjs +66 -0
- package/server/services/fixtures/claude-auth-stub.sh +62 -0
- package/server/services/fixtures/cli-install-stub.sh +75 -0
- package/server/services/fixtures/grok-stub.sh +86 -0
- package/server/services/generated-image-cleanup-service.mjs +114 -0
- package/server/services/git-changes-service.mjs +195 -0
- package/server/services/github-billing-usage.mjs +133 -0
- package/server/services/grok-auth-service.mjs +593 -0
- package/server/services/grok-billing-usage.mjs +120 -0
- package/server/services/image-context-service.mjs +106 -0
- package/server/services/image-operation-service.mjs +135 -0
- package/server/services/keyed-mutex-service.mjs +82 -0
- package/server/services/mime-sniffer.mjs +118 -0
- package/server/services/oar-cli-helpers.mjs +109 -0
- package/server/services/oar-state-migration-service.mjs +243 -0
- package/server/services/plan-usage-claude.mjs +387 -0
- package/server/services/plan-usage-contract.mjs +271 -0
- package/server/services/plan-usage-copilot.mjs +421 -0
- package/server/services/plan-usage-cursor.mjs +566 -0
- package/server/services/plan-usage-grok.mjs +436 -0
- package/server/services/plan-usage-service.mjs +487 -0
- package/server/services/preview-health-probe.mjs +82 -0
- package/server/services/preview-proxy-server.mjs +498 -0
- package/server/services/preview-registry-service.mjs +457 -0
- package/server/services/preview-static-handler.mjs +237 -0
- package/server/services/provider-model-selection.mjs +91 -0
- package/server/services/provider-reasoning-effort.mjs +94 -0
- package/server/services/push-active-device-service.mjs +57 -0
- package/server/services/push-dispatch-service.mjs +459 -0
- package/server/services/rate-limit-service.mjs +63 -0
- package/server/services/relay-bridge-owner-service.mjs +84 -0
- package/server/services/relay-cli-launcher-service.mjs +209 -0
- package/server/services/relay-prompt-sanitizer.mjs +137 -0
- package/server/services/relay-queue-gate-service.mjs +38 -0
- package/server/services/relay-restart-orchestrator-service.mjs +935 -0
- package/server/services/relay-singleton-guard.mjs +124 -0
- package/server/services/safe-served-content.mjs +57 -0
- package/server/services/sdk-session-import-service.mjs +271 -0
- package/server/services/sdk-session-sync-service.mjs +207 -0
- package/server/services/session-history-refresh-service.mjs +497 -0
- package/server/services/session-transcript-service.mjs +419 -0
- package/server/services/session-worker-launch-service.mjs +786 -0
- package/server/services/session-worker-process-service.mjs +319 -0
- package/server/services/session-worker-registry-service.mjs +194 -0
- package/server/services/session-worker-stop-service.mjs +141 -0
- package/server/services/session-worker-supervisor-service.mjs +898 -0
- package/server/services/session-worker-websocket-service.mjs +483 -0
- package/server/services/ssh-tunnel-manager-service.mjs +423 -0
- package/server/services/status-event-service.mjs +158 -0
- package/server/services/tmux-inspector-access-policy.mjs +77 -0
- package/server/services/tmux-inspector-socket-service.mjs +191 -0
- package/server/services/tmux-inspector-stream-service.mjs +552 -0
- package/server/services/tunnel-worker-path-guard.mjs +107 -0
- package/server/services/upload-sweep.mjs +60 -0
- package/server/services/usage-snapshot-helpers.mjs +209 -0
- package/server/services/windows-autostart-service.mjs +115 -0
- package/server/services/workspace-root-defaults-service.mjs +34 -0
- package/server/services/workspace-root-path-policy.mjs +196 -0
- package/server/services/workspace-root-relaunch-service.mjs +132 -0
- package/server/services/workspace-symlink-guard.mjs +54 -0
- package/server/tools/capture-proxy.mjs +173 -0
- package/server/tty-console-bootstrap.mjs +75 -0
- package/server/uploads/README.md +36 -0
- package/server/uploads/image_input.md +540 -0
- package/server/windows-terminal-launcher.mjs +29 -0
- package/server/workspace-root.mjs +87 -0
- package/shared/ask-user-bridge.mjs +131 -0
- package/shared/auto-compact-window.mjs +116 -0
- package/shared/background-task-timeout.mjs +52 -0
- package/shared/claude-thinking.mjs +101 -0
- package/shared/context-window-fallbacks.mjs +148 -0
- package/shared/control-poller.mjs +115 -0
- package/shared/empty-turn-completion.mjs +20 -0
- package/shared/model-catalog-freshness.mjs +11 -0
- package/shared/model-descriptors.mjs +118 -0
- package/shared/model-id.mjs +147 -0
- package/shared/model-refresh.mjs +24 -0
- package/shared/openai-reasoning.mjs +13 -0
- package/shared/plan-lines.mjs +13 -0
- package/shared/preview-instructions.mjs +77 -0
- package/shared/preview-tool-core.mjs +191 -0
- package/shared/provider-routing.mjs +29 -0
- package/shared/question-schema.mjs +315 -0
- package/shared/question-timeout.mjs +18 -0
- package/shared/stream-emit-gating.mjs +28 -0
- package/shared/subagent-run-id.mjs +17 -0
- package/shared/thought-cap.mjs +13 -0
- package/shared/tool-activity.mjs +98 -0
- package/shared/turn-ceiling.mjs +53 -0
- package/shared/worker-bootstrap.mjs +44 -0
- package/shared/worker-crash-guard.mjs +60 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reasoning-stream.mjs — capture the Copilot SDK's assistant reasoning ("thoughts")
|
|
3
|
+
* and incremental assistant text via session.on(...) subscriptions, and relay them to
|
|
4
|
+
* the web server.
|
|
5
|
+
*
|
|
6
|
+
* The SDK exposes reasoning as session EVENTS (not hooks):
|
|
7
|
+
* - assistant.reasoning complete reasoning block (fires regardless of streaming flag)
|
|
8
|
+
* - assistant.reasoning_delta token-level reasoning deltas (requires streaming:true at join)
|
|
9
|
+
* - assistant.message_delta token-level assistant text deltas (requires streaming:true)
|
|
10
|
+
*
|
|
11
|
+
* Events are still delivered to handlers registered via on() while sendAndWait is in flight,
|
|
12
|
+
* so we can capture reasoning without abandoning the stable sendAndWait turn path.
|
|
13
|
+
*
|
|
14
|
+
* Guards mirror question-routing-hooks.mjs: only relay while a relay turn is active and an
|
|
15
|
+
* active message exists. Sub-agent thoughts (event.agentId set) are dropped to start.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Coalesce reasoning deltas to avoid request floods: emit when >= this many new chars
|
|
19
|
+
// accumulated or a sentence/line boundary is reached.
|
|
20
|
+
const REASONING_DELTA_MIN_CHARS = 24;
|
|
21
|
+
const MAX_THOUGHT_CHARS = 16 * 1024;
|
|
22
|
+
|
|
23
|
+
function shouldEmitDelta(nextText, previousText) {
|
|
24
|
+
const next = String(nextText || "");
|
|
25
|
+
const prev = String(previousText || "");
|
|
26
|
+
if (!next) return false;
|
|
27
|
+
if (!prev) return true;
|
|
28
|
+
if (next === prev) return false;
|
|
29
|
+
const delta = next.length - prev.length;
|
|
30
|
+
if (delta >= REASONING_DELTA_MIN_CHARS) return true;
|
|
31
|
+
if (delta > 0 && /[\n.!?:)]$/.test(next)) return true;
|
|
32
|
+
if (delta <= 0) return true;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function capThought(text) {
|
|
37
|
+
const value = String(text || "");
|
|
38
|
+
if (value.length <= MAX_THOUGHT_CHARS) return value;
|
|
39
|
+
return value.slice(0, MAX_THOUGHT_CHARS);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function fnv1a32(value) {
|
|
43
|
+
const text = String(value || "");
|
|
44
|
+
let hash = 0x811c9dc5;
|
|
45
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
46
|
+
hash ^= text.charCodeAt(index);
|
|
47
|
+
hash = Math.imul(hash, 0x01000193);
|
|
48
|
+
}
|
|
49
|
+
return (hash >>> 0).toString(16);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function shouldSkipCompleteReasoning(nextText, previousText) {
|
|
53
|
+
const next = String(nextText || "");
|
|
54
|
+
const previous = String(previousText || "");
|
|
55
|
+
if (!next) return true;
|
|
56
|
+
if (!previous) return false;
|
|
57
|
+
if (next === previous) return true;
|
|
58
|
+
if (!next.startsWith(previous)) return false;
|
|
59
|
+
const growth = next.length - previous.length;
|
|
60
|
+
return growth >= 0 && growth < REASONING_DELTA_MIN_CHARS;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function toolThoughtReasoningId(messageId, phase, toolRequests, messageContent) {
|
|
64
|
+
const tools = (Array.isArray(toolRequests) ? toolRequests : []).map((request) => ({
|
|
65
|
+
id: String(request?.id || request?.toolCallId || "").trim() || null,
|
|
66
|
+
name: String(request?.name || request?.toolName || request?.tool || "").trim() || null,
|
|
67
|
+
}));
|
|
68
|
+
const fingerprintInput = JSON.stringify({
|
|
69
|
+
phase: String(phase || "").trim().toLowerCase(),
|
|
70
|
+
tools,
|
|
71
|
+
content: String(messageContent || "").trim(),
|
|
72
|
+
});
|
|
73
|
+
return `message:${messageId}:tool:${fnv1a32(fingerprintInput)}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function createReasoningStreamHandlers({
|
|
77
|
+
api,
|
|
78
|
+
dbg = () => {},
|
|
79
|
+
getRelayTurnActive,
|
|
80
|
+
getActiveMessage,
|
|
81
|
+
notifySubagentAgentId,
|
|
82
|
+
} = {}) {
|
|
83
|
+
// reasoningId -> accumulated reasoning text (for delta coalescing)
|
|
84
|
+
const reasoningAccum = new Map();
|
|
85
|
+
// reasoningId -> last emitted reasoning text (so we only emit meaningful changes)
|
|
86
|
+
const reasoningLastSent = new Map();
|
|
87
|
+
// messageId -> last assistant.message.reasoningText we emitted (dedupe repeated final envelopes)
|
|
88
|
+
const messageReasoningLastSent = new Map();
|
|
89
|
+
// cumulative assistant text seen via message deltas + last value pushed to /api/stream
|
|
90
|
+
let messageTextAccum = "";
|
|
91
|
+
let messageTextLastSent = "";
|
|
92
|
+
let maxReadableReasoningChars = 0;
|
|
93
|
+
|
|
94
|
+
function relayContext() {
|
|
95
|
+
if (!getRelayTurnActive?.()) return null;
|
|
96
|
+
const activeMsg = getActiveMessage?.();
|
|
97
|
+
if (!activeMsg?.id) return null;
|
|
98
|
+
return activeMsg;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function extractAgentId(event) {
|
|
102
|
+
const agentId = event?.agentId || event?.data?.agentId || null;
|
|
103
|
+
return agentId ? String(agentId).trim() : null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isRootAgentEvent(event) {
|
|
107
|
+
return !extractAgentId(event);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function reasoningIdFor(event) {
|
|
111
|
+
return String(event?.data?.reasoningId || event?.reasoningId || "reasoning").trim() || "reasoning";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function noteReadableReasoning(text) {
|
|
115
|
+
const length = String(text || "").length;
|
|
116
|
+
if (length > maxReadableReasoningChars) {
|
|
117
|
+
maxReadableReasoningChars = length;
|
|
118
|
+
}
|
|
119
|
+
return length;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isClaudeModel(event) {
|
|
123
|
+
const model = String(event?.data?.model || event?.model || "").trim().toLowerCase();
|
|
124
|
+
return model.includes("claude");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function postThought(activeMsg, { reasoningId, text, done, subagentRunId = null }) {
|
|
128
|
+
try {
|
|
129
|
+
await api("POST", "/api/thought", {
|
|
130
|
+
messageId: activeMsg.id,
|
|
131
|
+
conversationId: activeMsg.conversationId,
|
|
132
|
+
mode: activeMsg.relayMode || "agent",
|
|
133
|
+
reasoningId,
|
|
134
|
+
text: capThought(text),
|
|
135
|
+
done: !!done,
|
|
136
|
+
subagentRunId: subagentRunId || undefined,
|
|
137
|
+
});
|
|
138
|
+
} catch (error) {
|
|
139
|
+
dbg("relay thought publish failed", `msgId=${activeMsg.id}`, `reasoningId=${reasoningId}`, error?.message || String(error));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function onReasoning(event) {
|
|
144
|
+
const activeMsg = relayContext();
|
|
145
|
+
if (!activeMsg) return;
|
|
146
|
+
const subagentRunId = extractAgentId(event);
|
|
147
|
+
if (subagentRunId) await notifySubagentAgentId?.(subagentRunId);
|
|
148
|
+
const reasoningId = reasoningIdFor(event);
|
|
149
|
+
const text = String(event?.data?.content ?? event?.data?.text ?? "");
|
|
150
|
+
if (!text) return;
|
|
151
|
+
const lastSent = reasoningLastSent.get(reasoningId) || "";
|
|
152
|
+
if (shouldSkipCompleteReasoning(text, lastSent)) {
|
|
153
|
+
reasoningAccum.delete(reasoningId);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// The complete block is authoritative — clear delta bookkeeping for this id.
|
|
157
|
+
reasoningAccum.delete(reasoningId);
|
|
158
|
+
reasoningLastSent.set(reasoningId, text);
|
|
159
|
+
noteReadableReasoning(text);
|
|
160
|
+
await postThought(activeMsg, { reasoningId, text, done: true, subagentRunId });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function onReasoningDelta(event) {
|
|
164
|
+
const activeMsg = relayContext();
|
|
165
|
+
if (!activeMsg) return;
|
|
166
|
+
const subagentRunId = extractAgentId(event);
|
|
167
|
+
if (subagentRunId) await notifySubagentAgentId?.(subagentRunId);
|
|
168
|
+
const reasoningId = reasoningIdFor(event);
|
|
169
|
+
const deltaContent = String(event?.data?.deltaContent ?? event?.data?.delta ?? "");
|
|
170
|
+
if (!deltaContent) return;
|
|
171
|
+
const accumulated = (reasoningAccum.get(reasoningId) || "") + deltaContent;
|
|
172
|
+
reasoningAccum.set(reasoningId, accumulated);
|
|
173
|
+
const lastSent = reasoningLastSent.get(reasoningId) || "";
|
|
174
|
+
if (!shouldEmitDelta(accumulated, lastSent)) return;
|
|
175
|
+
reasoningLastSent.set(reasoningId, accumulated);
|
|
176
|
+
noteReadableReasoning(accumulated);
|
|
177
|
+
await postThought(activeMsg, { reasoningId, text: accumulated, done: false, subagentRunId });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function onMessage(event) {
|
|
181
|
+
const activeMsg = relayContext();
|
|
182
|
+
if (!activeMsg) return;
|
|
183
|
+
const subagentRunId = extractAgentId(event);
|
|
184
|
+
if (subagentRunId) await notifySubagentAgentId?.(subagentRunId);
|
|
185
|
+
const messageId = String(event?.data?.messageId || event?.messageId || "").trim();
|
|
186
|
+
const phase = String(event?.data?.phase || event?.phase || "").trim().toLowerCase();
|
|
187
|
+
const toolRequests = Array.isArray(event?.data?.toolRequests) ? event.data.toolRequests : [];
|
|
188
|
+
const messageContent = String(event?.data?.content ?? event?.data?.text ?? "");
|
|
189
|
+
const reasoningText = String(event?.data?.reasoningText ?? "");
|
|
190
|
+
if (!messageId) return;
|
|
191
|
+
|
|
192
|
+
const lastSent = messageReasoningLastSent.get(messageId) || "";
|
|
193
|
+
const hasToolThought = toolRequests.length > 0 && !phase.includes("final") && !!messageContent;
|
|
194
|
+
if (hasToolThought) {
|
|
195
|
+
if (lastSent === messageContent) return;
|
|
196
|
+
messageReasoningLastSent.set(messageId, messageContent);
|
|
197
|
+
noteReadableReasoning(messageContent);
|
|
198
|
+
await postThought(activeMsg, {
|
|
199
|
+
reasoningId: toolThoughtReasoningId(messageId, phase, toolRequests, messageContent),
|
|
200
|
+
text: messageContent,
|
|
201
|
+
done: true,
|
|
202
|
+
subagentRunId,
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!reasoningText || lastSent === reasoningText) return;
|
|
208
|
+
|
|
209
|
+
const previousMax = maxReadableReasoningChars;
|
|
210
|
+
const reasoningChars = reasoningText.length;
|
|
211
|
+
const readableReasoningAlreadySeen = previousMax > 0;
|
|
212
|
+
const isMeaningfulUpgrade = reasoningChars > (previousMax + REASONING_DELTA_MIN_CHARS);
|
|
213
|
+
const shouldPublish = !readableReasoningAlreadySeen || (!isClaudeModel(event) && isMeaningfulUpgrade);
|
|
214
|
+
if (!shouldPublish) return;
|
|
215
|
+
|
|
216
|
+
messageReasoningLastSent.set(messageId, reasoningText);
|
|
217
|
+
noteReadableReasoning(reasoningText);
|
|
218
|
+
await postThought(activeMsg, {
|
|
219
|
+
reasoningId: `message:${messageId}`,
|
|
220
|
+
text: reasoningText,
|
|
221
|
+
done: true,
|
|
222
|
+
subagentRunId,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function onMessageDelta(event) {
|
|
227
|
+
const activeMsg = relayContext();
|
|
228
|
+
if (!activeMsg) return;
|
|
229
|
+
const subagentRunId = extractAgentId(event);
|
|
230
|
+
if (subagentRunId) await notifySubagentAgentId?.(subagentRunId);
|
|
231
|
+
const deltaContent = String(event?.data?.deltaContent ?? event?.data?.delta ?? "");
|
|
232
|
+
if (!deltaContent) return;
|
|
233
|
+
messageTextAccum += deltaContent;
|
|
234
|
+
if (!shouldEmitDelta(messageTextAccum, messageTextLastSent)) return;
|
|
235
|
+
messageTextLastSent = messageTextAccum;
|
|
236
|
+
try {
|
|
237
|
+
await api("POST", "/api/stream", {
|
|
238
|
+
messageId: activeMsg.id,
|
|
239
|
+
conversationId: activeMsg.conversationId,
|
|
240
|
+
mode: activeMsg.relayMode || "agent",
|
|
241
|
+
text: messageTextAccum,
|
|
242
|
+
done: false,
|
|
243
|
+
subagentRunId: subagentRunId || undefined,
|
|
244
|
+
});
|
|
245
|
+
} catch (error) {
|
|
246
|
+
dbg("relay stream (message_delta) publish failed", `msgId=${activeMsg.id}`, error?.message || String(error));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Clear per-turn accumulators. Call when a relay turn ends.
|
|
251
|
+
function reset() {
|
|
252
|
+
reasoningAccum.clear();
|
|
253
|
+
reasoningLastSent.clear();
|
|
254
|
+
messageReasoningLastSent.clear();
|
|
255
|
+
messageTextAccum = "";
|
|
256
|
+
messageTextLastSent = "";
|
|
257
|
+
maxReadableReasoningChars = 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Subscribe to the session events. Returns an unsubscribe function.
|
|
261
|
+
function attach(session) {
|
|
262
|
+
if (!session || typeof session.on !== "function") {
|
|
263
|
+
dbg("reasoning-stream attach skipped: session.on unavailable");
|
|
264
|
+
return () => {};
|
|
265
|
+
}
|
|
266
|
+
const subscriptions = [
|
|
267
|
+
session.on("assistant.reasoning", onReasoning),
|
|
268
|
+
session.on("assistant.reasoning_delta", onReasoningDelta),
|
|
269
|
+
session.on("assistant.message", onMessage),
|
|
270
|
+
session.on("assistant.message_delta", onMessageDelta),
|
|
271
|
+
];
|
|
272
|
+
dbg("reasoning-stream attached", `sessionId=${session?.sessionId || "(none)"}`);
|
|
273
|
+
return () => {
|
|
274
|
+
for (const unsubscribe of subscriptions) {
|
|
275
|
+
try {
|
|
276
|
+
if (typeof unsubscribe === "function") unsubscribe();
|
|
277
|
+
} catch (error) {
|
|
278
|
+
dbg("reasoning-stream unsubscribe failed", error?.message || String(error));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return { onReasoning, onReasoningDelta, onMessage, onMessageDelta, reset, attach };
|
|
285
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { extractToolName, toolArgsSnapshot, parseMaybeJson } from "./tool-activity.mjs";
|
|
2
|
+
|
|
3
|
+
function extractAgentIdFromRequest(request) {
|
|
4
|
+
const agentId = request?.agentId || request?.data?.agentId || null;
|
|
5
|
+
return agentId ? String(agentId).trim() : null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function extractToolCallId(request) {
|
|
9
|
+
const candidates = [
|
|
10
|
+
request?.toolCallId,
|
|
11
|
+
request?.id,
|
|
12
|
+
request?.toolCall?.id,
|
|
13
|
+
request?.toolCall?.toolCallId,
|
|
14
|
+
];
|
|
15
|
+
for (const value of candidates) {
|
|
16
|
+
const id = String(value || "").trim();
|
|
17
|
+
if (id) return id;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isSubagentSpawnTool(request) {
|
|
23
|
+
const name = extractToolName(request).toLowerCase();
|
|
24
|
+
if (!name) return false;
|
|
25
|
+
return name.includes("execution_subagent")
|
|
26
|
+
|| name === "task"
|
|
27
|
+
|| name.includes("subagent")
|
|
28
|
+
|| name.includes("launch_subagent");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function extractSubagentDisplayName(request) {
|
|
32
|
+
const args = toolArgsSnapshot(request);
|
|
33
|
+
const candidates = [
|
|
34
|
+
args?.description,
|
|
35
|
+
args?.subagent_type,
|
|
36
|
+
args?.subagentType,
|
|
37
|
+
args?.name,
|
|
38
|
+
args?.title,
|
|
39
|
+
args?.agent_type,
|
|
40
|
+
args?.agentType,
|
|
41
|
+
];
|
|
42
|
+
for (const value of candidates) {
|
|
43
|
+
const text = String(value || "").trim();
|
|
44
|
+
if (text) return text.slice(0, 120);
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extractSubagentRunIdFromArgs(request) {
|
|
50
|
+
const args = toolArgsSnapshot(request);
|
|
51
|
+
const candidates = [
|
|
52
|
+
args?.subagentRunId,
|
|
53
|
+
args?.runId,
|
|
54
|
+
args?.agentId,
|
|
55
|
+
args?.resume,
|
|
56
|
+
args?.id,
|
|
57
|
+
];
|
|
58
|
+
for (const value of candidates) {
|
|
59
|
+
const id = String(value || "").trim();
|
|
60
|
+
if (id) return id;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function extractSubagentRunIdFromResult(result) {
|
|
66
|
+
const parsed = parseMaybeJson(result);
|
|
67
|
+
const sources = [result, parsed, parsed?.data, parsed?.result, parsed?.output].filter(Boolean);
|
|
68
|
+
for (const source of sources) {
|
|
69
|
+
if (typeof source === "string") {
|
|
70
|
+
const id = source.trim();
|
|
71
|
+
if (/^[0-9a-f-]{8,}$/i.test(id)) return id;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!source || typeof source !== "object") continue;
|
|
75
|
+
const candidates = [
|
|
76
|
+
source.subagentRunId,
|
|
77
|
+
source.agentId,
|
|
78
|
+
source.runId,
|
|
79
|
+
source.id,
|
|
80
|
+
source.agent?.id,
|
|
81
|
+
source.agent?.agentId,
|
|
82
|
+
];
|
|
83
|
+
for (const value of candidates) {
|
|
84
|
+
const id = String(value || "").trim();
|
|
85
|
+
if (id) return id;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createSubagentLifecycleHandlers({
|
|
92
|
+
api,
|
|
93
|
+
dbg = () => {},
|
|
94
|
+
getRelayTurnActive,
|
|
95
|
+
getActiveMessage,
|
|
96
|
+
} = {}) {
|
|
97
|
+
/** @type {Map<string, { status: string, parentSubagentId: string|null, displayName: string|null, messageId: string }>} */
|
|
98
|
+
const knownRuns = new Map();
|
|
99
|
+
/** @type {Map<string, { parentSubagentId: string|null, displayName: string|null }>} */
|
|
100
|
+
const pendingByToolCallId = new Map();
|
|
101
|
+
/** @type {Array<{ parentSubagentId: string|null, displayName: string|null, toolCallId: string|null }>} */
|
|
102
|
+
const pendingSpawns = [];
|
|
103
|
+
|
|
104
|
+
async function postSubagentRun(activeMsg, payload) {
|
|
105
|
+
if (!activeMsg?.id || !activeMsg?.conversationId) return;
|
|
106
|
+
const subagentRunId = String(payload?.subagentRunId || "").trim();
|
|
107
|
+
if (!subagentRunId) return;
|
|
108
|
+
try {
|
|
109
|
+
await api("POST", "/api/subagent-run", {
|
|
110
|
+
messageId: activeMsg.id,
|
|
111
|
+
conversationId: activeMsg.conversationId,
|
|
112
|
+
subagentRunId,
|
|
113
|
+
parentSubagentId: payload?.parentSubagentId || undefined,
|
|
114
|
+
displayName: payload?.displayName || undefined,
|
|
115
|
+
status: payload?.status || "running",
|
|
116
|
+
});
|
|
117
|
+
} catch (error) {
|
|
118
|
+
dbg(
|
|
119
|
+
"subagent-run publish failed",
|
|
120
|
+
`msgId=${activeMsg.id}`,
|
|
121
|
+
`runId=${subagentRunId.slice(0, 8)}`,
|
|
122
|
+
error?.message || String(error),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function registerSubagentRun(activeMsg, subagentRunId, {
|
|
128
|
+
parentSubagentId = null,
|
|
129
|
+
displayName = null,
|
|
130
|
+
status = "running",
|
|
131
|
+
} = {}) {
|
|
132
|
+
const id = String(subagentRunId || "").trim();
|
|
133
|
+
if (!id || !activeMsg?.id) return;
|
|
134
|
+
|
|
135
|
+
const normalizedParent = parentSubagentId ? String(parentSubagentId).trim() : null;
|
|
136
|
+
const normalizedName = displayName ? String(displayName).trim().slice(0, 120) : null;
|
|
137
|
+
const existing = knownRuns.get(id);
|
|
138
|
+
const nextStatus = String(status || "running").trim().toLowerCase();
|
|
139
|
+
|
|
140
|
+
if (existing
|
|
141
|
+
&& existing.status === nextStatus
|
|
142
|
+
&& existing.parentSubagentId === normalizedParent
|
|
143
|
+
&& existing.displayName === normalizedName
|
|
144
|
+
&& existing.messageId === activeMsg.id) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
knownRuns.set(id, {
|
|
149
|
+
status: nextStatus,
|
|
150
|
+
parentSubagentId: normalizedParent,
|
|
151
|
+
displayName: normalizedName,
|
|
152
|
+
messageId: activeMsg.id,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await postSubagentRun(activeMsg, {
|
|
156
|
+
subagentRunId: id,
|
|
157
|
+
parentSubagentId: normalizedParent,
|
|
158
|
+
displayName: normalizedName,
|
|
159
|
+
status: nextStatus,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function consumePendingSpawn({ parentSubagentId = null, toolCallId = null } = {}) {
|
|
164
|
+
if (toolCallId && pendingByToolCallId.has(toolCallId)) {
|
|
165
|
+
const pending = pendingByToolCallId.get(toolCallId);
|
|
166
|
+
pendingByToolCallId.delete(toolCallId);
|
|
167
|
+
return pending;
|
|
168
|
+
}
|
|
169
|
+
if (pendingSpawns.length === 1) {
|
|
170
|
+
return pendingSpawns.shift();
|
|
171
|
+
}
|
|
172
|
+
if (pendingSpawns.length > 1) {
|
|
173
|
+
const idx = pendingSpawns.findIndex((entry) => entry.parentSubagentId === parentSubagentId);
|
|
174
|
+
if (idx >= 0) return pendingSpawns.splice(idx, 1)[0];
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function ensureFromAgentId(subagentRunId, {
|
|
180
|
+
parentSubagentId = null,
|
|
181
|
+
displayName = null,
|
|
182
|
+
toolCallId = null,
|
|
183
|
+
} = {}) {
|
|
184
|
+
if (!getRelayTurnActive?.()) return;
|
|
185
|
+
const activeMsg = getActiveMessage?.();
|
|
186
|
+
if (!activeMsg?.id) return;
|
|
187
|
+
|
|
188
|
+
const id = String(subagentRunId || "").trim();
|
|
189
|
+
if (!id) return;
|
|
190
|
+
|
|
191
|
+
let resolvedParent = parentSubagentId ? String(parentSubagentId).trim() : null;
|
|
192
|
+
let resolvedName = displayName ? String(displayName).trim().slice(0, 120) : null;
|
|
193
|
+
|
|
194
|
+
if (!knownRuns.has(id)) {
|
|
195
|
+
const pending = consumePendingSpawn({ parentSubagentId: resolvedParent, toolCallId });
|
|
196
|
+
if (pending) {
|
|
197
|
+
resolvedParent = resolvedParent || pending.parentSubagentId || null;
|
|
198
|
+
resolvedName = resolvedName || pending.displayName || null;
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
// Re-discovery of a known run (e.g. its next tool call) must not demote
|
|
202
|
+
// it to a root or rename it — keep the established parent/name.
|
|
203
|
+
const existing = knownRuns.get(id);
|
|
204
|
+
resolvedParent = resolvedParent || existing.parentSubagentId || null;
|
|
205
|
+
resolvedName = resolvedName || existing.displayName || null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await registerSubagentRun(activeMsg, id, {
|
|
209
|
+
parentSubagentId: resolvedParent,
|
|
210
|
+
displayName: resolvedName || `Subagent ${id.slice(0, 8)}`,
|
|
211
|
+
status: "running",
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function onPreToolUse(request) {
|
|
216
|
+
if (!getRelayTurnActive?.()) return;
|
|
217
|
+
const activeMsg = getActiveMessage?.();
|
|
218
|
+
if (!activeMsg?.id) return;
|
|
219
|
+
|
|
220
|
+
const callerAgentId = extractAgentIdFromRequest(request);
|
|
221
|
+
if (callerAgentId) {
|
|
222
|
+
await ensureFromAgentId(callerAgentId, { parentSubagentId: null });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!isSubagentSpawnTool(request)) return;
|
|
226
|
+
|
|
227
|
+
const toolCallId = extractToolCallId(request);
|
|
228
|
+
const parentSubagentId = callerAgentId || null;
|
|
229
|
+
const displayName = extractSubagentDisplayName(request);
|
|
230
|
+
const runIdFromArgs = extractSubagentRunIdFromArgs(request);
|
|
231
|
+
|
|
232
|
+
const pending = { parentSubagentId, displayName, toolCallId };
|
|
233
|
+
pendingSpawns.push(pending);
|
|
234
|
+
if (toolCallId) pendingByToolCallId.set(toolCallId, pending);
|
|
235
|
+
|
|
236
|
+
if (runIdFromArgs) {
|
|
237
|
+
await registerSubagentRun(activeMsg, runIdFromArgs, {
|
|
238
|
+
parentSubagentId,
|
|
239
|
+
displayName: displayName || `Subagent ${runIdFromArgs.slice(0, 8)}`,
|
|
240
|
+
status: "running",
|
|
241
|
+
});
|
|
242
|
+
if (toolCallId) pendingByToolCallId.delete(toolCallId);
|
|
243
|
+
const idx = pendingSpawns.indexOf(pending);
|
|
244
|
+
if (idx >= 0) pendingSpawns.splice(idx, 1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function onPostToolUse(request, result) {
|
|
249
|
+
if (!getRelayTurnActive?.()) return;
|
|
250
|
+
const activeMsg = getActiveMessage?.();
|
|
251
|
+
if (!activeMsg?.id) return;
|
|
252
|
+
|
|
253
|
+
if (!isSubagentSpawnTool(request)) return;
|
|
254
|
+
|
|
255
|
+
const toolCallId = extractToolCallId(request);
|
|
256
|
+
const parentSubagentId = extractAgentIdFromRequest(request) || null;
|
|
257
|
+
const displayName = extractSubagentDisplayName(request);
|
|
258
|
+
const runId = extractSubagentRunIdFromResult(result) || extractSubagentRunIdFromArgs(request);
|
|
259
|
+
if (!runId) return;
|
|
260
|
+
|
|
261
|
+
if (toolCallId) pendingByToolCallId.delete(toolCallId);
|
|
262
|
+
const idx = pendingSpawns.findIndex((entry) => entry.toolCallId === toolCallId);
|
|
263
|
+
if (idx >= 0) pendingSpawns.splice(idx, 1);
|
|
264
|
+
|
|
265
|
+
await registerSubagentRun(activeMsg, runId, {
|
|
266
|
+
parentSubagentId,
|
|
267
|
+
displayName: displayName || `Subagent ${runId.slice(0, 8)}`,
|
|
268
|
+
status: "running",
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function finalizeTurn(activeMsg) {
|
|
273
|
+
if (!activeMsg?.id) return;
|
|
274
|
+
const messageId = activeMsg.id;
|
|
275
|
+
for (const [runId, entry] of knownRuns.entries()) {
|
|
276
|
+
if (entry.messageId !== messageId) continue;
|
|
277
|
+
if (entry.status === "cancelled" || entry.status === "failed") continue;
|
|
278
|
+
if (entry.status === "completed") continue;
|
|
279
|
+
await postSubagentRun(activeMsg, {
|
|
280
|
+
subagentRunId: runId,
|
|
281
|
+
parentSubagentId: entry.parentSubagentId,
|
|
282
|
+
displayName: entry.displayName,
|
|
283
|
+
status: "completed",
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function reset() {
|
|
289
|
+
knownRuns.clear();
|
|
290
|
+
pendingByToolCallId.clear();
|
|
291
|
+
pendingSpawns.length = 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function attach(session) {
|
|
295
|
+
if (!session || typeof session.on !== "function") return () => {};
|
|
296
|
+
const eventNames = [
|
|
297
|
+
"agent.start",
|
|
298
|
+
"agent.end",
|
|
299
|
+
"agent.spawn",
|
|
300
|
+
"execution.subagent.start",
|
|
301
|
+
"execution.subagent.end",
|
|
302
|
+
];
|
|
303
|
+
const subscriptions = [];
|
|
304
|
+
for (const eventName of eventNames) {
|
|
305
|
+
subscriptions.push(session.on(eventName, async (event) => {
|
|
306
|
+
const activeMsg = getActiveMessage?.();
|
|
307
|
+
if (!getRelayTurnActive?.() || !activeMsg?.id) return;
|
|
308
|
+
const subagentRunId = extractAgentIdFromRequest(event)
|
|
309
|
+
|| String(event?.data?.subagentRunId || event?.data?.runId || "").trim()
|
|
310
|
+
|| null;
|
|
311
|
+
if (!subagentRunId) return;
|
|
312
|
+
const parentSubagentId = String(event?.data?.parentAgentId || event?.data?.parentSubagentId || "").trim() || null;
|
|
313
|
+
const displayName = String(event?.data?.displayName || event?.data?.name || "").trim() || null;
|
|
314
|
+
const isEnd = eventName.includes(".end");
|
|
315
|
+
if (isEnd) {
|
|
316
|
+
await registerSubagentRun(activeMsg, subagentRunId, {
|
|
317
|
+
parentSubagentId,
|
|
318
|
+
displayName,
|
|
319
|
+
status: "completed",
|
|
320
|
+
});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
await ensureFromAgentId(subagentRunId, { parentSubagentId, displayName });
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
return () => {
|
|
327
|
+
for (const unsubscribe of subscriptions) {
|
|
328
|
+
try {
|
|
329
|
+
if (typeof unsubscribe === "function") unsubscribe();
|
|
330
|
+
} catch {
|
|
331
|
+
// ignore
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
ensureFromAgentId,
|
|
339
|
+
onPreToolUse,
|
|
340
|
+
onPostToolUse,
|
|
341
|
+
finalizeTurn,
|
|
342
|
+
reset,
|
|
343
|
+
attach,
|
|
344
|
+
};
|
|
345
|
+
}
|