mixdog 0.8.0 → 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/README.md +47 -23
- package/package.json +33 -27
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +344 -0
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1223 -95
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -7
- package/src/lib/rules-builder.cjs +240 -96
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2325 -450
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +118 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
- package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +152 -24
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -2
- package/src/runtime/channels/lib/tool-format.mjs +0 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/memory/index.mjs +635 -107
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
- package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
- package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
- package/src/runtime/memory/lib/session-ingest.mjs +194 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +102 -466
- package/src/runtime/search/lib/web-tools.mjs +45 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +145 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -98
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +2 -69
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4798 -2153
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +87 -29
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +367 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +617 -368
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +365 -0
- package/src/tui/components/tool-output-format.test.mjs +220 -0
- package/src/tui/dist/index.mjs +8915 -2418
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +511 -68
- package/src/tui/markdown/format-token.test.mjs +216 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +746 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +3 -16
- package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export const TOOL_SYNC_EXECUTION_CONTRACT =
|
|
2
|
-
'Runs synchronously
|
|
2
|
+
'Runs synchronously in this tool call.';
|
|
3
3
|
|
|
4
4
|
export const TOOL_ASYNC_EXECUTION_CONTRACT =
|
|
5
|
-
'Async
|
|
5
|
+
'Async returns task_id; completion notification follows. status/read/wait are manual recovery or blocking only.';
|
|
6
6
|
|
|
7
7
|
export const TOOL_MANUAL_CONTROL_CONTRACT =
|
|
8
|
-
'
|
|
8
|
+
'Manual wait/read/status/cancel only for explicit blocking or recovery.';
|
|
9
9
|
|
|
10
10
|
function clean(value) {
|
|
11
11
|
return String(value ?? '').trim();
|
|
@@ -16,13 +16,111 @@ function positivePid(value) {
|
|
|
16
16
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const NON_PERSISTENT_TOOL_STATUSES = new Set(['running', 'pending', 'queued']);
|
|
20
|
+
const TERMINAL_TOOL_STATUSES = new Set([
|
|
21
|
+
'completed',
|
|
22
|
+
'failed',
|
|
23
|
+
'cancelled',
|
|
24
|
+
'canceled',
|
|
25
|
+
'error',
|
|
26
|
+
'timeout',
|
|
27
|
+
'done',
|
|
28
|
+
'success',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
function notificationResultBody(text) {
|
|
32
|
+
const match = /\n\s*\n([\s\S]*)$/.exec(String(text || ''));
|
|
33
|
+
return match ? String(match[1] || '').trim() : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function backgroundTaskHeaderStatus(text) {
|
|
37
|
+
const match = /^status:\s*(\S+)/mi.exec(String(text || ''));
|
|
38
|
+
return clean(match?.[1]).toLowerCase();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function notificationHead(text) {
|
|
42
|
+
const value = String(text || '').trim();
|
|
43
|
+
const match = /\n\s*\n/.exec(value);
|
|
44
|
+
if (!match) return value;
|
|
45
|
+
return value.slice(0, match.index).trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isInternalTaskNotificationEnvelope(text) {
|
|
49
|
+
const value = String(text || '').trim();
|
|
50
|
+
if (!value) return false;
|
|
51
|
+
if (/^background task\b/i.test(value)) return false;
|
|
52
|
+
if (/^<task-notification\b/i.test(value)) return true;
|
|
53
|
+
const head = notificationHead(value);
|
|
54
|
+
return /^<task-notification\b/i.test(head);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function shouldPersistModelVisibleToolCompletion(text, meta = {}) {
|
|
58
|
+
const message = String(text || '').trim();
|
|
59
|
+
if (!message) return false;
|
|
60
|
+
if (isInternalTaskNotificationEnvelope(message)) return false;
|
|
61
|
+
|
|
62
|
+
const metaStatus = clean(meta?.status).toLowerCase();
|
|
63
|
+
if (NON_PERSISTENT_TOOL_STATUSES.has(metaStatus)) return false;
|
|
64
|
+
|
|
65
|
+
if (/^background task\b/i.test(message)) {
|
|
66
|
+
const headerStatus = backgroundTaskHeaderStatus(message) || metaStatus;
|
|
67
|
+
if (NON_PERSISTENT_TOOL_STATUSES.has(headerStatus)) return false;
|
|
68
|
+
if (!TERMINAL_TOOL_STATUSES.has(headerStatus) && !TERMINAL_TOOL_STATUSES.has(metaStatus)) return false;
|
|
69
|
+
return Boolean(notificationResultBody(message));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (meta?.execution_id || meta?.execution_surface) {
|
|
73
|
+
if (NON_PERSISTENT_TOOL_STATUSES.has(metaStatus)) return false;
|
|
74
|
+
if (!TERMINAL_TOOL_STATUSES.has(metaStatus)) return false;
|
|
75
|
+
return Boolean(notificationResultBody(message));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (/^(?:agent task:|task_id:)/mi.test(message)) {
|
|
79
|
+
if (NON_PERSISTENT_TOOL_STATUSES.has(metaStatus)) return false;
|
|
80
|
+
if (!TERMINAL_TOOL_STATUSES.has(metaStatus)) return false;
|
|
81
|
+
return Boolean(notificationResultBody(message));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const BRACKETED_SHELL_STATUS_RE = /^\[status:\s*(?:running|pending|queued|completed|failed|cancelled|canceled|error|timeout|done|success)\]/im;
|
|
88
|
+
|
|
89
|
+
function isBracketedShellNotificationEnvelope(text) {
|
|
90
|
+
const value = String(text ?? '').trim();
|
|
91
|
+
if (!value) return false;
|
|
92
|
+
if (!/^\[task_id:\s*\S+\]/im.test(value)) return false;
|
|
93
|
+
return BRACKETED_SHELL_STATUS_RE.test(value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function isInternalRuntimeNotificationText(text) {
|
|
97
|
+
const value = String(text ?? '').trim();
|
|
98
|
+
if (!value) return false;
|
|
99
|
+
if (isInternalTaskNotificationEnvelope(value)) return true;
|
|
100
|
+
if (isBracketedShellNotificationEnvelope(value)) return true;
|
|
101
|
+
if (/^background task\b/i.test(value)
|
|
102
|
+
&& /^task_id:\s*\S+/mi.test(value)
|
|
103
|
+
&& /^status:\s*(?:running|pending|queued|completed|failed|cancelled|canceled)\b/mi.test(value)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (/^task_id:\s*\S+/mi.test(value)
|
|
107
|
+
&& /^status:\s*(?:running|pending|queued|completed|failed|cancelled|canceled)\b/mi.test(value)
|
|
108
|
+
&& /^(?:surface|operation|type|target|role|agent|preset|model|effort|fast|notification):\s*/mi.test(value)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
19
114
|
export function normalizeToolNotifyContext(context = {}) {
|
|
20
|
-
const
|
|
115
|
+
const explicitCallerSessionId = clean(context.callerSessionId || context.sessionId);
|
|
116
|
+
const explicitRoutingSessionId = clean(context.routingSessionId);
|
|
117
|
+
const callerSessionId = explicitCallerSessionId || explicitRoutingSessionId;
|
|
118
|
+
const routingSessionId = explicitRoutingSessionId || callerSessionId;
|
|
21
119
|
const clientHostPid = positivePid(context.clientHostPid);
|
|
22
120
|
return {
|
|
23
121
|
notifyFn: typeof context.notifyFn === 'function' ? context.notifyFn : null,
|
|
24
122
|
callerSessionId: callerSessionId || null,
|
|
25
|
-
routingSessionId:
|
|
123
|
+
routingSessionId: routingSessionId || null,
|
|
26
124
|
clientHostPid,
|
|
27
125
|
};
|
|
28
126
|
}
|
|
@@ -30,8 +128,8 @@ export function normalizeToolNotifyContext(context = {}) {
|
|
|
30
128
|
export function toolCompletionInstruction({ surface = 'tool', id, status, detail } = {}) {
|
|
31
129
|
const label = surface === 'shell'
|
|
32
130
|
? 'shell task'
|
|
33
|
-
: surface === '
|
|
34
|
-
? '
|
|
131
|
+
: surface === 'agent'
|
|
132
|
+
? 'agent task'
|
|
35
133
|
: `${surface} execution`;
|
|
36
134
|
const statusText = status ? ` (${status}${detail ? `, ${detail}` : ''})` : '';
|
|
37
135
|
return `The async ${label} ${id || ''} has finished${statusText} - review this result in your next step.`;
|
|
@@ -53,10 +151,73 @@ export function toolCompletionMeta({
|
|
|
53
151
|
status: status || null,
|
|
54
152
|
instruction: instruction || toolCompletionInstruction({ surface, id, status }),
|
|
55
153
|
...(ctx.callerSessionId ? { caller_session_id: ctx.callerSessionId } : {}),
|
|
154
|
+
...(ctx.routingSessionId && ctx.routingSessionId !== ctx.callerSessionId ? { routing_session_id: ctx.routingSessionId } : {}),
|
|
56
155
|
...(ctx.clientHostPid ? { client_host_pid: String(ctx.clientHostPid) } : {}),
|
|
57
156
|
};
|
|
58
157
|
}
|
|
59
158
|
|
|
159
|
+
const MODEL_VISIBLE_COMPLETION_INSTRUCTION_RE = /\b(async (?:agent task|shell task|\w+ execution)|Async \S+)/i;
|
|
160
|
+
const MODEL_VISIBLE_COMPLETION_REVIEW_RE = /has finished\b[\s\S]*review this result in your next step/i;
|
|
161
|
+
const MODEL_VISIBLE_COMPLETION_ASYNC_HEADER_RE = /^Async .+ finished\./i;
|
|
162
|
+
|
|
163
|
+
export function isModelVisibleToolCompletionWrapper(text) {
|
|
164
|
+
const value = String(text ?? '').trim();
|
|
165
|
+
if (!value) return false;
|
|
166
|
+
const resultSplit = /\n\nResult:\n/.exec(value);
|
|
167
|
+
if (!resultSplit) return false;
|
|
168
|
+
const preamble = value.slice(0, resultSplit.index).trim();
|
|
169
|
+
if (!preamble) return false;
|
|
170
|
+
const instructionLike = MODEL_VISIBLE_COMPLETION_INSTRUCTION_RE.test(preamble)
|
|
171
|
+
|| MODEL_VISIBLE_COMPLETION_REVIEW_RE.test(preamble)
|
|
172
|
+
|| MODEL_VISIBLE_COMPLETION_ASYNC_HEADER_RE.test(preamble);
|
|
173
|
+
if (!instructionLike) return false;
|
|
174
|
+
const quotedSection = value.slice(resultSplit.index + resultSplit[0].length);
|
|
175
|
+
const quotedLines = quotedSection.split(/\r?\n/).filter((line) => line.length > 0);
|
|
176
|
+
if (quotedLines.length === 0) return false;
|
|
177
|
+
if (!quotedLines.every((line) => /^> /.test(line))) return false;
|
|
178
|
+
const unquoted = quotedLines.map((line) => line.slice(2)).join('\n');
|
|
179
|
+
return isInternalRuntimeNotificationText(unquoted);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function modelVisibleToolCompletionMessage(text, meta = {}) {
|
|
183
|
+
const message = String(text || '').trim();
|
|
184
|
+
if (!message) return '';
|
|
185
|
+
if (!shouldPersistModelVisibleToolCompletion(message, meta)) return '';
|
|
186
|
+
const instruction = clean(meta?.instruction);
|
|
187
|
+
const type = clean(meta?.type || meta?.execution_surface || 'tool_completion');
|
|
188
|
+
const id = clean(meta?.execution_id);
|
|
189
|
+
const status = clean(meta?.status);
|
|
190
|
+
const header = `Async ${type}${id ? ` ${id}` : ''}${status ? ` ${status}` : ''} finished.`;
|
|
191
|
+
const MODEL_VISIBLE_RESULT_BODY_MAX = 12_000;
|
|
192
|
+
const bounded = message.length > MODEL_VISIBLE_RESULT_BODY_MAX
|
|
193
|
+
? `${message.slice(0, MODEL_VISIBLE_RESULT_BODY_MAX)}\n\n[result truncated for model context]`
|
|
194
|
+
: message;
|
|
195
|
+
const quoted = bounded.split(/\r?\n/).map((line) => `> ${line}`).join('\n');
|
|
196
|
+
return [
|
|
197
|
+
instruction || header,
|
|
198
|
+
'',
|
|
199
|
+
'Result:',
|
|
200
|
+
quoted,
|
|
201
|
+
].join('\n');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Shared enqueue-fallback helper used by both the synchronous fallback path and
|
|
205
|
+
// the asynchronous notifyFn reject/false-resolve rescue path. Only enqueues when
|
|
206
|
+
// a caller session and fallback fn are present. Returns true only on a non-false,
|
|
207
|
+
// non-zero fallback result; logs and returns false on throw.
|
|
208
|
+
function tryEnqueueFallback(ctx, message, meta, enqueueFallback, logPrefix, id) {
|
|
209
|
+
if (!ctx.callerSessionId || typeof enqueueFallback !== 'function') return false;
|
|
210
|
+
try {
|
|
211
|
+
const enq = enqueueFallback(ctx.callerSessionId, message, meta);
|
|
212
|
+
return enq !== false && enq !== 0;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
try {
|
|
215
|
+
process.stderr.write(`[${logPrefix}] async completion fallback enqueue failed: id=${id || 'unknown'} err=${err?.message || err}\n`);
|
|
216
|
+
} catch {}
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
60
221
|
export function notifyToolCompletion({
|
|
61
222
|
surface = 'tool',
|
|
62
223
|
id,
|
|
@@ -80,25 +241,43 @@ export function notifyToolCompletion({
|
|
|
80
241
|
context: ctx,
|
|
81
242
|
});
|
|
82
243
|
|
|
244
|
+
// Try the upstream owner notifyFn first. A `false` return means the owner
|
|
245
|
+
// *declined* delivery and a throw means it failed outright — in both cases we
|
|
246
|
+
// do NOT return early but fall through to the enqueueFallback path so the
|
|
247
|
+
// completion can still reach the caller session. Only a successful (non-false)
|
|
248
|
+
// notifyFn short-circuits as delivered.
|
|
83
249
|
if (typeof ctx.notifyFn === 'function') {
|
|
84
|
-
Promise.resolve(ctx.notifyFn(message, meta)).catch((err) => {
|
|
85
|
-
try {
|
|
86
|
-
process.stderr.write(`[${logPrefix}] async completion notify failed: id=${id || 'unknown'} err=${err?.message || err}\n`);
|
|
87
|
-
} catch {}
|
|
88
|
-
});
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (ctx.callerSessionId && typeof enqueueFallback === 'function') {
|
|
93
250
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
251
|
+
const notifyResult = ctx.notifyFn(message, meta);
|
|
252
|
+
if (notifyResult !== false) {
|
|
253
|
+
// Optimistically report delivered (keeps this fn synchronous). But a
|
|
254
|
+
// notifyFn that returns a Promise can still reject — or resolve to an
|
|
255
|
+
// explicit false/0 (declined/failed) — *after* we returned. In that
|
|
256
|
+
// case rescue the completion via enqueueFallback so the owner isn't
|
|
257
|
+
// left without a notification. The normal success path (truthy
|
|
258
|
+
// resolve) never enqueues, preserving exact-once delivery.
|
|
259
|
+
Promise.resolve(notifyResult).then((settled) => {
|
|
260
|
+
if (settled === false || settled === 0) {
|
|
261
|
+
tryEnqueueFallback(ctx, message, meta, enqueueFallback, logPrefix, id);
|
|
262
|
+
}
|
|
263
|
+
}).catch((err) => {
|
|
264
|
+
try {
|
|
265
|
+
process.stderr.write(`[${logPrefix}] async completion notify failed: id=${id || 'unknown'} err=${err?.message || err}\n`);
|
|
266
|
+
} catch {}
|
|
267
|
+
tryEnqueueFallback(ctx, message, meta, enqueueFallback, logPrefix, id);
|
|
268
|
+
});
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
96
271
|
} catch (err) {
|
|
97
272
|
try {
|
|
98
|
-
process.stderr.write(`[${logPrefix}] async completion
|
|
273
|
+
process.stderr.write(`[${logPrefix}] async completion notify failed: id=${id || 'unknown'} err=${err?.message || err}\n`);
|
|
99
274
|
} catch {}
|
|
100
275
|
}
|
|
101
276
|
}
|
|
102
277
|
|
|
103
|
-
|
|
278
|
+
// Fallback enqueue (used when notifyFn is absent, declined, or threw). Respect
|
|
279
|
+
// the fallback's own success signal: an explicit false/0 return means the
|
|
280
|
+
// enqueue failed, so report failure and leave room for a retry rather than
|
|
281
|
+
// marking the task notified.
|
|
282
|
+
return tryEnqueueFallback(ctx, message, meta, enqueueFallback, logPrefix, id);
|
|
104
283
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import {
|
|
4
|
+
isInternalRuntimeNotificationText,
|
|
5
|
+
isModelVisibleToolCompletionWrapper,
|
|
6
|
+
modelVisibleToolCompletionMessage,
|
|
7
|
+
shouldPersistModelVisibleToolCompletion,
|
|
8
|
+
toolCompletionMeta,
|
|
9
|
+
toolCompletionInstruction,
|
|
10
|
+
} from './tool-execution-contract.mjs';
|
|
11
|
+
import { _isInternalRuntimeNotificationText } from '../agent/orchestrator/session/manager.mjs';
|
|
12
|
+
|
|
13
|
+
const completedBackgroundTask = [
|
|
14
|
+
'background task',
|
|
15
|
+
'task_id: task_agent_abc123',
|
|
16
|
+
'surface: agent',
|
|
17
|
+
'operation: run',
|
|
18
|
+
'status: completed',
|
|
19
|
+
'started: 2026-03-28T00:00:00.000Z',
|
|
20
|
+
'finished: 2026-03-28T00:00:05.000Z',
|
|
21
|
+
'',
|
|
22
|
+
'worker finished successfully',
|
|
23
|
+
].join('\n');
|
|
24
|
+
|
|
25
|
+
const runningBackgroundTask = [
|
|
26
|
+
'background task',
|
|
27
|
+
'task_id: task_agent_abc123',
|
|
28
|
+
'surface: agent',
|
|
29
|
+
'operation: run',
|
|
30
|
+
'status: running',
|
|
31
|
+
'started: 2026-03-28T00:00:00.000Z',
|
|
32
|
+
'notification: completion will be delivered to the owner session; use status/read only for manual recovery.',
|
|
33
|
+
].join('\n');
|
|
34
|
+
|
|
35
|
+
test('terminal background task with body should persist as model-visible wrapper', () => {
|
|
36
|
+
const meta = toolCompletionMeta({
|
|
37
|
+
surface: 'agent',
|
|
38
|
+
id: 'task_agent_abc123',
|
|
39
|
+
status: 'completed',
|
|
40
|
+
context: { callerSessionId: 'sess_owner' },
|
|
41
|
+
});
|
|
42
|
+
assert.equal(shouldPersistModelVisibleToolCompletion(completedBackgroundTask, meta), true);
|
|
43
|
+
const visible = modelVisibleToolCompletionMessage(completedBackgroundTask, meta);
|
|
44
|
+
assert.ok(visible);
|
|
45
|
+
assert.equal(isInternalRuntimeNotificationText(visible), false);
|
|
46
|
+
assert.equal(_isInternalRuntimeNotificationText(visible), false);
|
|
47
|
+
assert.equal(isModelVisibleToolCompletionWrapper(visible), true);
|
|
48
|
+
assert.match(visible, /worker finished successfully/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('model-visible completion wrapper is not a human transcript item', () => {
|
|
52
|
+
const meta = toolCompletionMeta({
|
|
53
|
+
surface: 'agent',
|
|
54
|
+
id: 'task_agent_abc123',
|
|
55
|
+
status: 'completed',
|
|
56
|
+
instruction: toolCompletionInstruction({
|
|
57
|
+
surface: 'agent',
|
|
58
|
+
id: 'task_agent_abc123',
|
|
59
|
+
status: 'completed',
|
|
60
|
+
}),
|
|
61
|
+
context: { callerSessionId: 'sess_owner' },
|
|
62
|
+
});
|
|
63
|
+
const visible = modelVisibleToolCompletionMessage(completedBackgroundTask, meta);
|
|
64
|
+
assert.ok(visible);
|
|
65
|
+
assert.match(visible, /^The async agent task/);
|
|
66
|
+
assert.match(visible, /\n\nResult:\n/);
|
|
67
|
+
assert.equal(isModelVisibleToolCompletionWrapper(visible), true);
|
|
68
|
+
assert.equal(isModelVisibleToolCompletionWrapper('please fix the leak in engine.mjs'), false);
|
|
69
|
+
assert.equal(isModelVisibleToolCompletionWrapper(completedBackgroundTask), false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('running background task stays hidden from model-visible pending drain', () => {
|
|
73
|
+
const meta = toolCompletionMeta({
|
|
74
|
+
surface: 'agent',
|
|
75
|
+
id: 'task_agent_abc123',
|
|
76
|
+
status: 'running',
|
|
77
|
+
context: { callerSessionId: 'sess_owner' },
|
|
78
|
+
});
|
|
79
|
+
assert.equal(shouldPersistModelVisibleToolCompletion(runningBackgroundTask, meta), false);
|
|
80
|
+
assert.equal(modelVisibleToolCompletionMessage(runningBackgroundTask, meta), '');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('terminal background task persists when result body mentions task-notification', () => {
|
|
84
|
+
const taskWithTagInBody = [
|
|
85
|
+
...completedBackgroundTask.split('\n').slice(0, -1),
|
|
86
|
+
'log line: saw <task-notification status="completed"> in worker output',
|
|
87
|
+
].join('\n');
|
|
88
|
+
const meta = toolCompletionMeta({
|
|
89
|
+
surface: 'agent',
|
|
90
|
+
id: 'task_agent_abc123',
|
|
91
|
+
status: 'completed',
|
|
92
|
+
context: { callerSessionId: 'sess_owner' },
|
|
93
|
+
});
|
|
94
|
+
assert.equal(shouldPersistModelVisibleToolCompletion(taskWithTagInBody, meta), true);
|
|
95
|
+
const visible = modelVisibleToolCompletionMessage(taskWithTagInBody, meta);
|
|
96
|
+
assert.ok(visible);
|
|
97
|
+
assert.match(visible, /<task-notification/);
|
|
98
|
+
assert.equal(isInternalRuntimeNotificationText(visible), false);
|
|
99
|
+
assert.equal(_isInternalRuntimeNotificationText(visible), false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('internal task-notification envelope does not persist', () => {
|
|
103
|
+
const internalEnvelope = [
|
|
104
|
+
'<task-notification>',
|
|
105
|
+
'<status>completed</status>',
|
|
106
|
+
'<summary>Agent completed</summary>',
|
|
107
|
+
'<task-id>task_agent_internal</task-id>',
|
|
108
|
+
'</task-notification>',
|
|
109
|
+
].join('\n');
|
|
110
|
+
const meta = toolCompletionMeta({
|
|
111
|
+
surface: 'agent',
|
|
112
|
+
id: 'task_agent_internal',
|
|
113
|
+
status: 'completed',
|
|
114
|
+
context: { callerSessionId: 'sess_owner' },
|
|
115
|
+
});
|
|
116
|
+
assert.equal(shouldPersistModelVisibleToolCompletion(internalEnvelope, meta), false);
|
|
117
|
+
assert.equal(modelVisibleToolCompletionMessage(internalEnvelope, meta), '');
|
|
118
|
+
assert.equal(isInternalRuntimeNotificationText(internalEnvelope), true);
|
|
119
|
+
assert.equal(_isInternalRuntimeNotificationText(internalEnvelope), true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('raw running background task remains internal for manager pending drain', () => {
|
|
123
|
+
assert.equal(isInternalRuntimeNotificationText(runningBackgroundTask), true);
|
|
124
|
+
assert.equal(_isInternalRuntimeNotificationText(runningBackgroundTask), true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
const bracketedShellCompleted = [
|
|
130
|
+
"[task_id: shell_job_1]",
|
|
131
|
+
"[status: completed]",
|
|
132
|
+
"[exit: 0]",
|
|
133
|
+
"",
|
|
134
|
+
"done output",
|
|
135
|
+
].join("\n");
|
|
136
|
+
|
|
137
|
+
const bracketedShellRunning = [
|
|
138
|
+
"[task_id: shell_job_1]",
|
|
139
|
+
"[status: running]",
|
|
140
|
+
"[command: npm test]",
|
|
141
|
+
].join("\n");
|
|
142
|
+
|
|
143
|
+
test("bracketed shell completed raw envelope is internal", () => {
|
|
144
|
+
assert.equal(isInternalRuntimeNotificationText(bracketedShellCompleted), true);
|
|
145
|
+
assert.equal(_isInternalRuntimeNotificationText(bracketedShellCompleted), true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("bracketed shell running raw envelope is internal", () => {
|
|
149
|
+
assert.equal(isInternalRuntimeNotificationText(bracketedShellRunning), true);
|
|
150
|
+
assert.equal(_isInternalRuntimeNotificationText(bracketedShellRunning), true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("wrapped shell completion stays model-visible and not internal", () => {
|
|
154
|
+
const meta = toolCompletionMeta({
|
|
155
|
+
surface: "shell",
|
|
156
|
+
id: "shell_job_1",
|
|
157
|
+
status: "completed",
|
|
158
|
+
context: { callerSessionId: "sess_owner" },
|
|
159
|
+
});
|
|
160
|
+
const visible = modelVisibleToolCompletionMessage(bracketedShellCompleted, meta);
|
|
161
|
+
assert.ok(visible);
|
|
162
|
+
assert.match(visible, /done output/);
|
|
163
|
+
assert.equal(isInternalRuntimeNotificationText(visible), false);
|
|
164
|
+
assert.equal(_isInternalRuntimeNotificationText(visible), false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("model-visible wrapper bounds large completion bodies", () => {
|
|
168
|
+
const hugeBody = "x".repeat(20_000);
|
|
169
|
+
const hugeTask = [
|
|
170
|
+
...completedBackgroundTask.split("\n").slice(0, -1),
|
|
171
|
+
hugeBody,
|
|
172
|
+
].join("\n");
|
|
173
|
+
const meta = toolCompletionMeta({
|
|
174
|
+
surface: "agent",
|
|
175
|
+
id: "task_agent_abc123",
|
|
176
|
+
status: "completed",
|
|
177
|
+
context: { callerSessionId: "sess_owner" },
|
|
178
|
+
});
|
|
179
|
+
const visible = modelVisibleToolCompletionMessage(hugeTask, meta);
|
|
180
|
+
assert.ok(visible);
|
|
181
|
+
assert.match(visible, /\[result truncated for model context\]/);
|
|
182
|
+
assert.ok(visible.length < hugeBody.length);
|
|
183
|
+
});
|