@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,485 @@
|
|
|
1
|
+
import { sanitizeSubagentRunId } from '../../shared/subagent-run-id.mjs';
|
|
2
|
+
import { capThought } from '../../shared/thought-cap.mjs';
|
|
3
|
+
import { shouldEmitStreamUpdate } from '../../shared/stream-emit-gating.mjs';
|
|
4
|
+
|
|
5
|
+
const MAX_TOOL_DETAIL_LENGTH = 140;
|
|
6
|
+
const SUBAGENT_TOOL_NAMES = new Set(['task', 'agent']);
|
|
7
|
+
|
|
8
|
+
function truncate(text, maxLength = MAX_TOOL_DETAIL_LENGTH) {
|
|
9
|
+
const value = String(text || '').trim();
|
|
10
|
+
if (value.length <= maxLength) return value;
|
|
11
|
+
return `${value.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isSubagentToolName(name) {
|
|
15
|
+
return SUBAGENT_TOOL_NAMES.has(String(name || '').trim().toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function summarizeToolInput(toolName, input) {
|
|
19
|
+
if (!input || typeof input !== 'object') return '';
|
|
20
|
+
const candidates = [
|
|
21
|
+
input.command,
|
|
22
|
+
input.file_path,
|
|
23
|
+
input.path,
|
|
24
|
+
input.pattern,
|
|
25
|
+
input.description,
|
|
26
|
+
input.url,
|
|
27
|
+
input.query,
|
|
28
|
+
input.prompt,
|
|
29
|
+
];
|
|
30
|
+
for (const candidate of candidates) {
|
|
31
|
+
const value = String(candidate ?? '').trim();
|
|
32
|
+
if (value) return value;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const serialized = JSON.stringify(input);
|
|
36
|
+
return serialized === '{}' ? '' : serialized;
|
|
37
|
+
} catch {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatToolActivityText(toolName, input) {
|
|
43
|
+
const summary = summarizeToolInput(toolName, input);
|
|
44
|
+
return truncate(summary ? `Tool (${toolName}): ${summary}` : `Tool (${toolName})`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Re-exported (rather than defined here) so existing importers of this
|
|
48
|
+
// normalizer keep working; the rule itself lives in shared/.
|
|
49
|
+
export { shouldEmitStreamUpdate };
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Stateful normalizer mapping the Cursor adapter's merged event stream onto
|
|
53
|
+
* relay channel actions. One instance per turn.
|
|
54
|
+
*
|
|
55
|
+
* `normalize(event)` consumes `{ source: 'delta', update }` (InteractionUpdate
|
|
56
|
+
* from send's onDelta) and `{ source: 'stream', message }` (SDKMessage from
|
|
57
|
+
* run.stream()) and returns `{ channel, payload }` actions on the same six
|
|
58
|
+
* channels as the Claude worker.
|
|
59
|
+
*
|
|
60
|
+
* Both surfaces can describe the same text/thinking. Ownership latches decide
|
|
61
|
+
* once per turn: the first `text-delta` (resp. `thinking-delta`) makes the
|
|
62
|
+
* delta surface authoritative, and stream-surface `assistant` text blocks
|
|
63
|
+
* (resp. `thinking` messages) are then only kept as a final-text fallback.
|
|
64
|
+
* Without deltas (degraded mode) the stream messages are the emission path.
|
|
65
|
+
* Tool lifecycle is owned by stream `tool_call` messages; the delta surface's
|
|
66
|
+
* tool-call updates are ignored.
|
|
67
|
+
*
|
|
68
|
+
* Subagent attribution rides `tool-call-delta` updates: each carries the task
|
|
69
|
+
* tool_call's `callId` (live-verified identical to the stream `call_id`,
|
|
70
|
+
* 2026-08-16 probe) plus one nested frame (`taskUpdate`) from inside the
|
|
71
|
+
* subagent — text, thinking, and nested tool starts publish under that
|
|
72
|
+
* `subagentRunId`. One nesting level only; deeper levels are dropped by the
|
|
73
|
+
* SDK at convert time.
|
|
74
|
+
*/
|
|
75
|
+
export function createSdkMessageNormalizer() {
|
|
76
|
+
let initModel = '';
|
|
77
|
+
let streamText = ''; // text-delta accumulator
|
|
78
|
+
let fallbackText = ''; // assistant-message text accumulator
|
|
79
|
+
let lastEmittedStreamText = '';
|
|
80
|
+
let deltaOwnsText = false;
|
|
81
|
+
let deltaOwnsThinking = false;
|
|
82
|
+
let openThought = null; // { reasoningId, text }
|
|
83
|
+
let stepIndex = 0;
|
|
84
|
+
let thoughtIndex = 0; // monotonic across the turn, so ids never collide
|
|
85
|
+
let lastUsage = null;
|
|
86
|
+
let assistantMessageCount = 0; // degraded-mode stand-in for the step count
|
|
87
|
+
const knownSubagentRuns = new Map(); // call_id -> displayName
|
|
88
|
+
const openSubagentRuns = new Set(); // call_ids currently 'running'
|
|
89
|
+
const seenToolFrames = new Set(); // `${call_id}\u0000${status}` de-dupe
|
|
90
|
+
// Per-subagent nested-delta accumulators (tool-call-delta frames).
|
|
91
|
+
const subagentDeltas = new Map(); // runId -> { streamText, lastEmittedStreamText, openThought, thoughtIndex }
|
|
92
|
+
|
|
93
|
+
function subagentDeltaState(runId) {
|
|
94
|
+
if (!subagentDeltas.has(runId)) {
|
|
95
|
+
subagentDeltas.set(runId, {
|
|
96
|
+
streamText: '',
|
|
97
|
+
lastEmittedStreamText: '',
|
|
98
|
+
openThought: null,
|
|
99
|
+
thoughtIndex: 0,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return subagentDeltas.get(runId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function allocateThoughtId() {
|
|
106
|
+
const id = `cursor-thought-main-${stepIndex}-${thoughtIndex}`;
|
|
107
|
+
thoughtIndex += 1;
|
|
108
|
+
return id;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function closeOpenThought(actions) {
|
|
112
|
+
if (!openThought) return;
|
|
113
|
+
if (openThought.text.trim()) {
|
|
114
|
+
actions.push({
|
|
115
|
+
channel: 'thought',
|
|
116
|
+
payload: {
|
|
117
|
+
reasoningId: openThought.reasoningId,
|
|
118
|
+
text: openThought.text,
|
|
119
|
+
done: true,
|
|
120
|
+
subagentRunId: null,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
openThought = null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function normalizeDelta(update) {
|
|
128
|
+
const actions = [];
|
|
129
|
+
const type = String(update?.type || '');
|
|
130
|
+
|
|
131
|
+
if (type === 'text-delta') {
|
|
132
|
+
deltaOwnsText = true;
|
|
133
|
+
streamText += String(update?.text || '');
|
|
134
|
+
if (shouldEmitStreamUpdate(streamText, lastEmittedStreamText)) {
|
|
135
|
+
lastEmittedStreamText = streamText;
|
|
136
|
+
actions.push({
|
|
137
|
+
channel: 'stream',
|
|
138
|
+
payload: { text: streamText, done: false, subagentRunId: null },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return actions;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (type === 'thinking-delta') {
|
|
145
|
+
deltaOwnsThinking = true;
|
|
146
|
+
if (!openThought) openThought = { reasoningId: allocateThoughtId(), text: '' };
|
|
147
|
+
openThought.text = capThought(openThought.text + String(update?.text || ''));
|
|
148
|
+
actions.push({
|
|
149
|
+
channel: 'thought',
|
|
150
|
+
payload: {
|
|
151
|
+
reasoningId: openThought.reasoningId,
|
|
152
|
+
text: openThought.text,
|
|
153
|
+
done: false,
|
|
154
|
+
subagentRunId: null,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
return actions;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (type === 'thinking-completed') {
|
|
161
|
+
closeOpenThought(actions);
|
|
162
|
+
return actions;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (type === 'step-started') {
|
|
166
|
+
stepIndex += 1;
|
|
167
|
+
return actions;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (type === 'step-completed') {
|
|
171
|
+
// Steps can end without an explicit thinking-completed; never leave a
|
|
172
|
+
// thought dangling in "streaming" state across a step boundary.
|
|
173
|
+
closeOpenThought(actions);
|
|
174
|
+
return actions;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (type === 'turn-ended') {
|
|
178
|
+
if (update?.usage && typeof update.usage === 'object') lastUsage = update.usage;
|
|
179
|
+
return actions;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (type === 'tool-call-delta') {
|
|
183
|
+
// Nested interaction updates streamed from a task/subagent tool call.
|
|
184
|
+
// `callId` is the task tool_call's own id — live-verified byte-equal on
|
|
185
|
+
// 2026-08-16 (probe: tool_f68aee7f… on both surfaces) — which is
|
|
186
|
+
// exactly the subagentRunId the lifecycle chips already use, so these
|
|
187
|
+
// frames fill the bubbles that used to render header-and-nothing.
|
|
188
|
+
const runId = sanitizeSubagentRunId(update?.callId);
|
|
189
|
+
const nested = update?.taskUpdate && typeof update.taskUpdate === 'object' ? update.taskUpdate : null;
|
|
190
|
+
if (!runId || !nested) return actions;
|
|
191
|
+
const nestedType = String(nested.type || '');
|
|
192
|
+
const state = subagentDeltaState(runId);
|
|
193
|
+
if (nestedType === 'text-delta') {
|
|
194
|
+
state.streamText += String(nested.text || '');
|
|
195
|
+
if (shouldEmitStreamUpdate(state.streamText, state.lastEmittedStreamText)) {
|
|
196
|
+
state.lastEmittedStreamText = state.streamText;
|
|
197
|
+
actions.push({
|
|
198
|
+
channel: 'stream',
|
|
199
|
+
payload: { text: state.streamText, done: false, subagentRunId: runId },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return actions;
|
|
203
|
+
}
|
|
204
|
+
if (nestedType === 'thinking-delta') {
|
|
205
|
+
if (!state.openThought) {
|
|
206
|
+
state.openThought = { reasoningId: `cursor-thought-${runId}-${state.thoughtIndex++}`, text: '' };
|
|
207
|
+
}
|
|
208
|
+
state.openThought.text = capThought(state.openThought.text + String(nested.text || ''));
|
|
209
|
+
actions.push({
|
|
210
|
+
channel: 'thought',
|
|
211
|
+
payload: {
|
|
212
|
+
reasoningId: state.openThought.reasoningId,
|
|
213
|
+
text: state.openThought.text,
|
|
214
|
+
done: false,
|
|
215
|
+
subagentRunId: runId,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
return actions;
|
|
219
|
+
}
|
|
220
|
+
if (nestedType === 'thinking-completed' || nestedType === 'step-completed') {
|
|
221
|
+
if (state.openThought && state.openThought.text.trim()) {
|
|
222
|
+
actions.push({
|
|
223
|
+
channel: 'thought',
|
|
224
|
+
payload: {
|
|
225
|
+
reasoningId: state.openThought.reasoningId,
|
|
226
|
+
text: state.openThought.text,
|
|
227
|
+
done: true,
|
|
228
|
+
subagentRunId: runId,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (state.openThought) state.openThought = null;
|
|
233
|
+
// A finished subagent step flushes its streamed text as final so the
|
|
234
|
+
// bubble keeps the prose even though the run's own turn never emits a
|
|
235
|
+
// top-level stream terminator for it.
|
|
236
|
+
if (nestedType === 'step-completed' && state.streamText.trim() && state.streamText !== state.lastEmittedStreamText) {
|
|
237
|
+
state.lastEmittedStreamText = state.streamText;
|
|
238
|
+
actions.push({
|
|
239
|
+
channel: 'stream',
|
|
240
|
+
payload: { text: state.streamText, done: false, subagentRunId: runId },
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return actions;
|
|
244
|
+
}
|
|
245
|
+
if (nestedType === 'tool-call-started' || nestedType === 'tool-call-completed') {
|
|
246
|
+
const nestedName = String(nested.name || nested.toolName || nested.title || 'tool').trim() || 'tool';
|
|
247
|
+
if (nestedType === 'tool-call-started') {
|
|
248
|
+
actions.push({
|
|
249
|
+
channel: 'activity',
|
|
250
|
+
payload: { text: formatToolActivityText(nestedName, nested.args || nested.input || null), subagentRunId: runId },
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return actions;
|
|
254
|
+
}
|
|
255
|
+
return actions;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// tool-call-started/-completed, partial-tool-call, token-delta,
|
|
259
|
+
// summary*, user-message-appended, shell-output-delta: intentionally ignored.
|
|
260
|
+
return actions;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function actionsForToolCall(message) {
|
|
264
|
+
const actions = [];
|
|
265
|
+
// call_id becomes a relay row id; malformed ones (embedded newline, two
|
|
266
|
+
// concatenated ids) have been seen live from this SDK.
|
|
267
|
+
const callId = sanitizeSubagentRunId(message?.call_id) || '';
|
|
268
|
+
const status = String(message?.status || '').trim();
|
|
269
|
+
if (!callId || !status) return actions;
|
|
270
|
+
const frameKey = `${callId}\u0000${status}`;
|
|
271
|
+
if (seenToolFrames.has(frameKey)) return actions;
|
|
272
|
+
seenToolFrames.add(frameKey);
|
|
273
|
+
|
|
274
|
+
const toolName = String(message?.name || '').trim() || 'unknown';
|
|
275
|
+
const args = message?.args && typeof message.args === 'object' ? message.args : {};
|
|
276
|
+
|
|
277
|
+
if (status === 'running') {
|
|
278
|
+
if (isSubagentToolName(toolName)) {
|
|
279
|
+
const displayName = String(
|
|
280
|
+
args.description
|
|
281
|
+
|| args.name
|
|
282
|
+
|| args.subagent_type
|
|
283
|
+
|| 'Subagent',
|
|
284
|
+
).trim() || 'Subagent';
|
|
285
|
+
knownSubagentRuns.set(callId, displayName);
|
|
286
|
+
openSubagentRuns.add(callId);
|
|
287
|
+
actions.push({
|
|
288
|
+
channel: 'subagent',
|
|
289
|
+
payload: {
|
|
290
|
+
subagentRunId: callId,
|
|
291
|
+
parentSubagentId: null,
|
|
292
|
+
displayName,
|
|
293
|
+
status: 'running',
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
actions.push({
|
|
298
|
+
channel: 'activity',
|
|
299
|
+
payload: { text: formatToolActivityText(toolName, args), subagentRunId: null },
|
|
300
|
+
});
|
|
301
|
+
return actions;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (status === 'completed') {
|
|
305
|
+
if (knownSubagentRuns.has(callId)) {
|
|
306
|
+
openSubagentRuns.delete(callId);
|
|
307
|
+
actions.push({
|
|
308
|
+
channel: 'subagent',
|
|
309
|
+
payload: {
|
|
310
|
+
subagentRunId: callId,
|
|
311
|
+
parentSubagentId: null,
|
|
312
|
+
displayName: knownSubagentRuns.get(callId),
|
|
313
|
+
status: 'completed',
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return actions;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (status === 'error') {
|
|
321
|
+
if (knownSubagentRuns.has(callId)) {
|
|
322
|
+
openSubagentRuns.delete(callId);
|
|
323
|
+
actions.push({
|
|
324
|
+
channel: 'subagent',
|
|
325
|
+
payload: {
|
|
326
|
+
subagentRunId: callId,
|
|
327
|
+
parentSubagentId: null,
|
|
328
|
+
displayName: knownSubagentRuns.get(callId),
|
|
329
|
+
status: 'failed',
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
const resultText = typeof message?.result === 'string'
|
|
334
|
+
? message.result
|
|
335
|
+
: String(message?.result?.text ?? '');
|
|
336
|
+
actions.push({
|
|
337
|
+
channel: 'activity',
|
|
338
|
+
payload: {
|
|
339
|
+
text: truncate(`Tool failed: ${resultText.trim() || 'unknown error'}`),
|
|
340
|
+
subagentRunId: null,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
return actions;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return actions;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function normalizeMessage(message) {
|
|
350
|
+
const actions = [];
|
|
351
|
+
const type = String(message?.type || '');
|
|
352
|
+
|
|
353
|
+
if (type === 'system' && message?.subtype === 'init') {
|
|
354
|
+
// `model` is a ModelSelection object ({id, params?}) in current SDK
|
|
355
|
+
// builds; older shapes and test fixtures carry a plain string.
|
|
356
|
+
initModel = String(message.model?.id ?? message.model ?? '').trim();
|
|
357
|
+
actions.push({
|
|
358
|
+
channel: 'init',
|
|
359
|
+
payload: {
|
|
360
|
+
sessionId: String(message.session_id ?? message.sessionId ?? '').trim(),
|
|
361
|
+
model: initModel,
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
return actions;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (type === 'assistant') {
|
|
368
|
+
assistantMessageCount += 1;
|
|
369
|
+
const content = Array.isArray(message?.message?.content) ? message.message.content : [];
|
|
370
|
+
for (const block of content) {
|
|
371
|
+
if (block?.type !== 'text') continue; // tool_use lifecycle is owned by tool_call messages
|
|
372
|
+
const text = String(block?.text || '');
|
|
373
|
+
if (!text) continue;
|
|
374
|
+
fallbackText += text;
|
|
375
|
+
if (!deltaOwnsText && shouldEmitStreamUpdate(fallbackText, lastEmittedStreamText)) {
|
|
376
|
+
lastEmittedStreamText = fallbackText;
|
|
377
|
+
actions.push({
|
|
378
|
+
channel: 'stream',
|
|
379
|
+
payload: { text: fallbackText, done: false, subagentRunId: null },
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return actions;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (type === 'thinking') {
|
|
387
|
+
if (deltaOwnsThinking) return actions;
|
|
388
|
+
const text = capThought(String(message?.text || ''));
|
|
389
|
+
if (!text.trim()) return actions;
|
|
390
|
+
actions.push({
|
|
391
|
+
channel: 'thought',
|
|
392
|
+
payload: {
|
|
393
|
+
reasoningId: allocateThoughtId(),
|
|
394
|
+
text,
|
|
395
|
+
done: true,
|
|
396
|
+
subagentRunId: null,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
return actions;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (type === 'tool_call') {
|
|
403
|
+
return actionsForToolCall(message);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (type === 'task') {
|
|
407
|
+
const status = String(message?.status || '').trim() || 'unknown';
|
|
408
|
+
return [{
|
|
409
|
+
channel: 'activity',
|
|
410
|
+
payload: {
|
|
411
|
+
text: truncate(`Background task ${status}: ${String(message?.text || '').trim()}`),
|
|
412
|
+
subagentRunId: null,
|
|
413
|
+
},
|
|
414
|
+
}];
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (type === 'usage') {
|
|
418
|
+
if (message?.usage && typeof message.usage === 'object') lastUsage = message.usage;
|
|
419
|
+
return actions;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (type === 'status') {
|
|
423
|
+
const status = String(message?.status || '').trim().toUpperCase();
|
|
424
|
+
const terminal = {
|
|
425
|
+
FINISHED: { isError: false, subtype: 'finished' },
|
|
426
|
+
ERROR: { isError: true, subtype: 'error' },
|
|
427
|
+
EXPIRED: { isError: true, subtype: 'expired' },
|
|
428
|
+
CANCELLED: { isError: false, subtype: 'cancelled' },
|
|
429
|
+
}[status];
|
|
430
|
+
if (!terminal) return actions; // CREATING / RUNNING
|
|
431
|
+
// Errored turns may end before any text streamed; the status message is
|
|
432
|
+
// then the only human-readable explanation, so surface it as the result.
|
|
433
|
+
const errorMessage = terminal.isError ? String(message?.message || '').trim() : '';
|
|
434
|
+
const text = finalStreamText() || errorMessage;
|
|
435
|
+
actions.push({
|
|
436
|
+
channel: 'result',
|
|
437
|
+
payload: {
|
|
438
|
+
text,
|
|
439
|
+
isError: terminal.isError,
|
|
440
|
+
subtype: terminal.subtype,
|
|
441
|
+
// Kept separate from text (which streamed prose can shadow) so the
|
|
442
|
+
// runner can classify the failure from the status message itself.
|
|
443
|
+
errorMessage: errorMessage || null,
|
|
444
|
+
usage: lastUsage,
|
|
445
|
+
totalCostUsd: null,
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
return actions;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// user, request: intentionally ignored.
|
|
452
|
+
return actions;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function normalize(event) {
|
|
456
|
+
if (!event || typeof event !== 'object') return [];
|
|
457
|
+
if (event.source === 'delta') return normalizeDelta(event.update);
|
|
458
|
+
if (event.source === 'stream') return normalizeMessage(event.message);
|
|
459
|
+
return [];
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function finalStreamText() {
|
|
463
|
+
return streamText || fallbackText;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
normalize,
|
|
468
|
+
finalStreamText,
|
|
469
|
+
// Subagent runs opened but not yet terminated — the auth-retry path
|
|
470
|
+
// closes them out before discarding this normalizer, otherwise their
|
|
471
|
+
// relay rows stay 'running' forever.
|
|
472
|
+
activeSubagentRuns: () => [...openSubagentRuns].map((callId) => ({
|
|
473
|
+
subagentRunId: callId,
|
|
474
|
+
displayName: knownSubagentRuns.get(callId) || 'Subagent',
|
|
475
|
+
})),
|
|
476
|
+
get model() { return initModel; },
|
|
477
|
+
// The turn's usage as last seen on either surface, so the runner can still
|
|
478
|
+
// publish context data when the run ends without a terminal status message.
|
|
479
|
+
get lastUsage() { return lastUsage; },
|
|
480
|
+
// Model calls made this turn: step boundaries when the delta surface is
|
|
481
|
+
// live, assistant messages in degraded mode. Divides the turn's aggregate
|
|
482
|
+
// usage into a per-call context estimate (see cursor-context-usage.mjs).
|
|
483
|
+
get modelCallCount() { return Math.max(stepIndex, assistantMessageCount, 1); },
|
|
484
|
+
};
|
|
485
|
+
}
|