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
|
@@ -6,12 +6,12 @@ const MIN_PROVIDER_TIMEOUT_MS = 30_000;
|
|
|
6
6
|
export const STALL_TICK_MS = 15_000;
|
|
7
7
|
export const DEFAULT_STALL_WARN_S = 300;
|
|
8
8
|
export const DEFAULT_STALL_ABORT_S = 600;
|
|
9
|
-
// First-byte (no-stream-delta) abort for the
|
|
9
|
+
// First-byte (no-stream-delta) abort for the agent stall watchdog. A wedged
|
|
10
10
|
// socket can sit at stage=requesting with zero server events. The 30s deadline
|
|
11
11
|
// trialed here false-aborted slow high-reasoning first bytes (e.g. gpt-5.5
|
|
12
12
|
// XHIGH, which can legitimately think >30s before the first delta) and, paired
|
|
13
13
|
// with dispatch auto-retry, produced premature aborts + duplicate re-dispatches.
|
|
14
|
-
// Auto-retry is now removed
|
|
14
|
+
// Auto-retry is now removed, so a single
|
|
15
15
|
// attempt must get a generous first-byte window: 300s (5 min). Env-overridable.
|
|
16
16
|
export const DEFAULT_STALL_FIRST_BYTE_ABORT_S = (() => {
|
|
17
17
|
const raw = process.env.MIXDOG_STALL_FIRST_BYTE_ABORT_S;
|
|
@@ -58,6 +58,14 @@ export function resolveTimeoutMs(envNames, fallbackMs, { minMs = 1_000, maxMs =
|
|
|
58
58
|
return Math.min(Math.max(fallbackMs, minMs), maxMs);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// While a tool call is in flight, bump lastProgressAt on this cadence so long
|
|
62
|
+
// executions stay "active" without emitting model-visible content.
|
|
63
|
+
export const DEFAULT_ACTIVITY_HEARTBEAT_MS = resolveTimeoutMs(
|
|
64
|
+
'MIXDOG_AGENT_ACTIVITY_HEARTBEAT_MS',
|
|
65
|
+
30_000,
|
|
66
|
+
{ minMs: 5_000, maxMs: 300_000 },
|
|
67
|
+
);
|
|
68
|
+
|
|
61
69
|
export const PROVIDER_FIRST_BYTE_TIMEOUT_MS = resolveTimeoutMs(
|
|
62
70
|
'MIXDOG_PROVIDER_FIRST_BYTE_TIMEOUT_MS',
|
|
63
71
|
Math.min(120_000, PROVIDER_MAX_BEFORE_WARN_MS),
|
|
@@ -94,19 +102,26 @@ export const PROVIDER_HTTP_RESPONSE_TIMEOUT_MS = resolveTimeoutMs(
|
|
|
94
102
|
{ minMs: 10_000, maxMs: PROVIDER_MAX_BEFORE_WARN_MS },
|
|
95
103
|
);
|
|
96
104
|
|
|
97
|
-
// Stream idle watchdog is
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
105
|
+
// Stream idle watchdog is ON by default. Earlier it was OFF (reference-agent
|
|
106
|
+
// parity) on the theory that a short inter-chunk timer would false-abort slow
|
|
107
|
+
// high-reasoning streams — but every provider's SSE loop resets the idle timer
|
|
108
|
+
// on EVERY chunk, including content-free keepalives (Anthropic `:ping`,
|
|
109
|
+
// OpenAI/compat reasoning_content deltas, Gemini chunks). So a live
|
|
110
|
+
// extended-thinking stream keeps the timer fresh; only a genuinely silent
|
|
111
|
+
// socket (no bytes at all for the whole window) trips it. With the watchdog
|
|
112
|
+
// OFF, such a wedged socket sat until the 600s agent stall backstop, which the
|
|
113
|
+
// trace logs showed as the dominant mid-stream hang (sse_parse_ms p99 ~183s,
|
|
114
|
+
// max ~411s). Defaulting ON with a generous 120s window cuts that tail ~5x
|
|
115
|
+
// while leaving genuine reasoning pauses (kept alive by keepalive frames)
|
|
116
|
+
// untouched. Force OFF with MIXDOG_ENABLE_STREAM_WATCHDOG=0.
|
|
103
117
|
const _sseWatchdogRaw = process.env.MIXDOG_ENABLE_STREAM_WATCHDOG;
|
|
104
|
-
export const PROVIDER_SSE_IDLE_WATCHDOG_ENABLED =
|
|
105
|
-
|
|
118
|
+
export const PROVIDER_SSE_IDLE_WATCHDOG_ENABLED = _sseWatchdogRaw === undefined || _sseWatchdogRaw === ''
|
|
119
|
+
? true
|
|
120
|
+
: (_sseWatchdogRaw === '1' || _sseWatchdogRaw === 'true' || _sseWatchdogRaw === 'yes');
|
|
106
121
|
|
|
107
122
|
export const PROVIDER_SSE_IDLE_TIMEOUT_MS = resolveTimeoutMs(
|
|
108
123
|
'MIXDOG_PROVIDER_SSE_IDLE_TIMEOUT_MS',
|
|
109
|
-
|
|
124
|
+
120_000,
|
|
110
125
|
{ minMs: 10_000, maxMs: STALL_WARN_MS },
|
|
111
126
|
);
|
|
112
127
|
|
|
@@ -143,15 +158,15 @@ export const PROVIDER_RETRY_JITTER_RATIO = (() => {
|
|
|
143
158
|
return 0.2;
|
|
144
159
|
})();
|
|
145
160
|
|
|
146
|
-
export function
|
|
161
|
+
export function resolveAgentStallThresholds(role, env = process.env) {
|
|
147
162
|
const cfg = role ? getHiddenRole(role) : null;
|
|
148
163
|
const cfgAbort = cfg?.stallCap?.idleSeconds > 0 ? cfg.stallCap.idleSeconds : STALL_ABORT_S;
|
|
149
164
|
const envOverride = envThresholdSeconds(env);
|
|
150
165
|
const abort = envOverride != null ? envOverride : cfgAbort;
|
|
151
|
-
// Mid-stream "slow" warning disabled —
|
|
166
|
+
// Mid-stream "slow" warning disabled — an agent stall now notifies ONLY at
|
|
152
167
|
// the abort deadline (10 min default). warn === abort means the watchdog's
|
|
153
168
|
// warn branch (verdict 'ok' && stale >= warn) can never fire before 'stall',
|
|
154
|
-
// so the only notification a stalled
|
|
169
|
+
// so the only notification a stalled agent worker emits is at the deadline.
|
|
155
170
|
const warn = abort;
|
|
156
171
|
// First-byte deadline: a request still in 'requesting' that never produced a
|
|
157
172
|
// single SSE delta is hung, not slow-reasoning. Abort on this shorter deadline
|
|
@@ -160,7 +175,7 @@ export function resolveBridgeStallThresholds(role, env = process.env) {
|
|
|
160
175
|
return { warn, abort, firstByteAbort };
|
|
161
176
|
}
|
|
162
177
|
|
|
163
|
-
export function
|
|
178
|
+
export function resolveAgentToolThresholdSeconds(role, thresholdSeconds) {
|
|
164
179
|
const cfg = role ? getHiddenRole(role) : null;
|
|
165
180
|
if (cfg?.stallCap?.toolRunningSeconds > 0) return cfg.stallCap.toolRunningSeconds;
|
|
166
181
|
return thresholdSeconds;
|
|
@@ -206,7 +221,7 @@ export function createTimeoutSignal(parentSignal, timeoutMs, label) {
|
|
|
206
221
|
// replaced-by-newer-request). Used for the streaming generation phase, where a fixed
|
|
207
222
|
// total-lifetime cap would false-abort a stream that is still emitting SSE deltas; the
|
|
208
223
|
// streaming phase is bounded instead by the per-attempt first-byte timeout, the parent
|
|
209
|
-
// signal, and the
|
|
224
|
+
// signal, and the agent stall watchdog (STALL_ABORT_S, progress-based). cleanup()
|
|
210
225
|
// detaches the parent listener so no listener leaks on the (long-lived) parent signal.
|
|
211
226
|
export function createPassthroughSignal(parentSignal) {
|
|
212
227
|
if (!parentSignal) return { signal: null, cleanup: () => {} };
|
|
@@ -1,229 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Soft-warn marker stripper.
|
|
3
3
|
*
|
|
4
4
|
* The tool-loop guard subsystem (repeated/fanned-out tool-use detection and its
|
|
5
|
-
* soft-warn sidecars) was removed once every warn path had been disabled.
|
|
6
|
-
*
|
|
7
|
-
* • classifyBashFileLookupCommand — detects a `bash` call whose first token is
|
|
8
|
-
* a file-lookup that has a dedicated in-process tool (read/grep/glob/list).
|
|
9
|
-
* Used by the bridge-worker permission gate to block the shell route.
|
|
10
|
-
* • classifyBridgeWorkerGitMutationCommand — detects bridge-worker shell
|
|
11
|
-
* calls that try to run git operations reserved for Lead.
|
|
5
|
+
* soft-warn sidecars) was removed once every warn path had been disabled. This
|
|
6
|
+
* module now retains a single utility:
|
|
12
7
|
* • stripSoftWarns — strips legacy soft-warn marker blocks from outbound
|
|
13
8
|
* bodies so older transcripts that still carry them stay clean.
|
|
14
9
|
*/
|
|
15
10
|
|
|
16
|
-
// File-lookup-via-shell detector. Matches a `bash` command whose first token
|
|
17
|
-
// is a unix find/grep/cat/ls/head/tail OR a powershell file-discovery cmdlet
|
|
18
|
-
// (Get-ChildItem / Select-String / Get-Content). These have dedicated tools
|
|
19
|
-
// (glob / grep / read / list) per rules/shared/01-tool.md; using bash routes
|
|
20
|
-
// around the in-process tool cache and the path-permission check, and the
|
|
21
|
-
// outputs were observed in PG telemetry running 100x slower than the dedicated
|
|
22
|
-
// path. Get-CimInstance / Win32_Process etc. are NOT matched — process
|
|
23
|
-
// enumeration genuinely needs the shell. One advisory per session per matched
|
|
24
|
-
// command class; subsequent matches stay silent so the model isn't drowned.
|
|
25
|
-
const SHELL_FILE_LOOKUP_COMMANDS = [
|
|
26
|
-
'find', 'ls', 'grep', 'rg', 'fd', 'cat', 'head', 'tail',
|
|
27
|
-
'sed', 'awk', 'less', 'more', 'wc', 'tree',
|
|
28
|
-
// File-metadata mirror paths called out in rules/shared/01-tool.md
|
|
29
|
-
// Tool Routing section. Added after probe HS-A3 surfaced that the
|
|
30
|
-
// rule explicitly lists `stat`, `du`, `file`, and `ls -l` as
|
|
31
|
-
// forbidden mirror paths but the policy under-enforced them.
|
|
32
|
-
'stat', 'du', 'file',
|
|
33
|
-
'dir', 'type', 'findstr',
|
|
34
|
-
'Get-ChildItem', 'gci', 'Select-String', 'sls', 'Get-Content', 'gc',
|
|
35
|
-
].join('|');
|
|
36
|
-
const BASH_FILE_LOOKUP_RE = new RegExp(
|
|
37
|
-
`^\\s*(?:git\\s+(?:grep|ls-files)\\b|(?:${SHELL_FILE_LOOKUP_COMMANDS})\\b)`,
|
|
38
|
-
'i',
|
|
39
|
-
);
|
|
40
|
-
// Wrapper / eval forms that route the same file-lookup intent through a host
|
|
41
|
-
// shell (`bash -c "grep ..."`, `pwsh -Command Get-Content ...`) or an
|
|
42
|
-
// inline interpreter eval (`node -e fs.readFileSync(...)`,
|
|
43
|
-
// `python -c "open(...)"`). Without these the advisory was bypassable just
|
|
44
|
-
// by quoting the call inside another command.
|
|
45
|
-
const BASH_WRAPPER_FILE_LOOKUP_RE = new RegExp(
|
|
46
|
-
`^\\s*(?:bash|sh|zsh|fish|dash)\\b[^\\n]*\\s-\\w*c\\w*\\s+["']?\\s*(?:${SHELL_FILE_LOOKUP_COMMANDS})\\b`,
|
|
47
|
-
'i',
|
|
48
|
-
);
|
|
49
|
-
const CMD_WRAPPER_FILE_LOOKUP_RE = /^\s*cmd(?:\.exe)?\s+\/[cs]\s+["']?\s*(?:dir|type|findstr)\b/i;
|
|
50
|
-
const PWSH_WRAPPER_FILE_LOOKUP_RE = /^\s*(?:pwsh|powershell)(?:\.exe)?\b[\s\S]*?(?:-|\/)(?:Command|c)\s+["']?\s*(?:Get-ChildItem|gci|dir|ls|Select-String|sls|Get-Content|gc|cat|type)\b/i;
|
|
51
|
-
const NODE_FS_EVAL_RE = /^\s*node(?:\.exe)?\b[\s\S]*?\s-e\s+["'](?![\s\S]*\bJSON\.parse\s*\()[\s\S]*?\b(?:fs\.|readFileSync|readFile|readdirSync|readdir|statSync|stat)\b/i;
|
|
52
|
-
const PYTHON_OPEN_EVAL_RE = /^\s*python3?(?:\.exe)?\b[\s\S]*?\s-c\s+["'][\s\S]*?\b(?:open\s*\(|Path\s*\(|read_text\s*\(|read_bytes\s*\(|iterdir\s*\()/i;
|
|
53
|
-
function _classifyBashFileLookup(command) {
|
|
54
|
-
if (typeof command !== 'string' || !command) return null;
|
|
55
|
-
const direct = command.match(BASH_FILE_LOOKUP_RE);
|
|
56
|
-
if (direct) return direct[0].trim().toLowerCase();
|
|
57
|
-
if (BASH_WRAPPER_FILE_LOOKUP_RE.test(command)) return 'bash-wrapped';
|
|
58
|
-
if (CMD_WRAPPER_FILE_LOOKUP_RE.test(command)) return 'cmd-wrapped';
|
|
59
|
-
if (PWSH_WRAPPER_FILE_LOOKUP_RE.test(command)) return 'pwsh-wrapped';
|
|
60
|
-
if (NODE_FS_EVAL_RE.test(command)) return 'node-fs-eval';
|
|
61
|
-
if (PYTHON_OPEN_EVAL_RE.test(command)) return 'python-open-eval';
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function classifyBashFileLookupCommand(command) {
|
|
66
|
-
return _classifyBashFileLookup(command);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const SHELL_WRAPPER_COMMANDS = new Set([
|
|
70
|
-
'bash', 'sh', 'zsh', 'fish', 'dash',
|
|
71
|
-
'cmd', 'cmd.exe',
|
|
72
|
-
'pwsh', 'pwsh.exe', 'powershell', 'powershell.exe',
|
|
73
|
-
]);
|
|
74
|
-
const POSIX_SHELL_WRAPPER_COMMANDS = new Set(['bash', 'sh', 'zsh', 'fish', 'dash']);
|
|
75
|
-
const CMD_SHELL_WRAPPER_COMMANDS = new Set(['cmd', 'cmd.exe']);
|
|
76
|
-
const POWERSHELL_WRAPPER_COMMANDS = new Set(['pwsh', 'pwsh.exe', 'powershell', 'powershell.exe']);
|
|
77
|
-
const SHELL_SEPARATORS = new Set([';', '\n', '&&', '||', '|', '&']);
|
|
78
|
-
const READONLY_GIT_SUBCOMMANDS = new Set([
|
|
79
|
-
'status', 'diff', 'show', 'log', 'rev-parse', 'ls-files', 'grep', 'blame', 'describe', 'merge-base',
|
|
80
|
-
]);
|
|
81
|
-
|
|
82
|
-
function shellTokenizeLoose(command) {
|
|
83
|
-
const tokens = [];
|
|
84
|
-
let current = '';
|
|
85
|
-
let quote = null;
|
|
86
|
-
let escape = false;
|
|
87
|
-
const push = () => {
|
|
88
|
-
if (current !== '') tokens.push(current);
|
|
89
|
-
current = '';
|
|
90
|
-
};
|
|
91
|
-
const pushSep = (sep) => {
|
|
92
|
-
push();
|
|
93
|
-
tokens.push(sep);
|
|
94
|
-
};
|
|
95
|
-
for (let i = 0; i < command.length; i += 1) {
|
|
96
|
-
const ch = command[i];
|
|
97
|
-
if (escape) {
|
|
98
|
-
current += ch;
|
|
99
|
-
escape = false;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (ch === '\\') {
|
|
103
|
-
escape = true;
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
if (quote) {
|
|
107
|
-
if (ch === quote) quote = null;
|
|
108
|
-
else current += ch;
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
if (ch === '\'' || ch === '"') {
|
|
112
|
-
quote = ch;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (ch === '\n') {
|
|
116
|
-
pushSep('\n');
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (ch === ';') {
|
|
120
|
-
pushSep(';');
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if ((ch === '&' || ch === '|') && command[i + 1] === ch) {
|
|
124
|
-
pushSep(`${ch}${ch}`);
|
|
125
|
-
i += 1;
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (ch === '|' || ch === '&') {
|
|
129
|
-
pushSep(ch);
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
if (/\s/.test(ch)) {
|
|
133
|
-
push();
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
current += ch;
|
|
137
|
-
}
|
|
138
|
-
push();
|
|
139
|
-
return tokens;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function isCommandPosition(tokens, index) {
|
|
143
|
-
for (let i = index - 1; i >= 0; i -= 1) {
|
|
144
|
-
if (!tokens[i]) continue;
|
|
145
|
-
return SHELL_SEPARATORS.has(tokens[i]);
|
|
146
|
-
}
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function skipGitGlobalOptions(tokens, index) {
|
|
151
|
-
let i = index;
|
|
152
|
-
while (i < tokens.length) {
|
|
153
|
-
const token = String(tokens[i] || '');
|
|
154
|
-
const lower = token.toLowerCase();
|
|
155
|
-
if (!token || SHELL_SEPARATORS.has(token)) return i;
|
|
156
|
-
if (lower === '-c' || lower === '-C' || lower === '--git-dir' || lower === '--work-tree' || lower === '--namespace') {
|
|
157
|
-
i += 2;
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
if (/^(?:--git-dir|--work-tree|--namespace)=/i.test(token)) {
|
|
161
|
-
i += 1;
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
if (lower === '--no-pager' || lower === '-p' || lower === '--literal-pathspecs' || lower === '--glob-pathspecs' || lower === '--noglob-pathspecs' || lower === '--icase-pathspecs') {
|
|
165
|
-
i += 1;
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
if (/^-/.test(token)) {
|
|
169
|
-
i += 1;
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
return i;
|
|
173
|
-
}
|
|
174
|
-
return i;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function readonlyGitInvocation(tokens, subIndex) {
|
|
178
|
-
const sub = String(tokens[subIndex] || '').toLowerCase();
|
|
179
|
-
if (!READONLY_GIT_SUBCOMMANDS.has(sub)) return false;
|
|
180
|
-
if (sub === 'remote') {
|
|
181
|
-
const next = String(tokens[subIndex + 1] || '').toLowerCase();
|
|
182
|
-
return !next || next === '-v' || next === '--verbose' || next === 'show';
|
|
183
|
-
}
|
|
184
|
-
if (sub === 'branch') {
|
|
185
|
-
const rest = tokens.slice(subIndex + 1).filter((token) => token && !SHELL_SEPARATORS.has(token));
|
|
186
|
-
return rest.length === 0 || rest.every((token) => ['--show-current', '-a', '-r', '-v', '-vv', '--all', '--remotes', '--verbose', '--contains', '--merged', '--no-merged'].includes(String(token).toLowerCase()));
|
|
187
|
-
}
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function isShellCommandArgFlag(wrapper, flag) {
|
|
192
|
-
const lower = String(flag || '').toLowerCase();
|
|
193
|
-
if (POSIX_SHELL_WRAPPER_COMMANDS.has(wrapper)) return /^-\w*c\w*$/.test(lower);
|
|
194
|
-
if (CMD_SHELL_WRAPPER_COMMANDS.has(wrapper)) return lower === '/c';
|
|
195
|
-
if (POWERSHELL_WRAPPER_COMMANDS.has(wrapper)) return lower === '-command' || lower === '/command' || lower === '-c';
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export function classifyBridgeWorkerGitMutationCommand(command) {
|
|
200
|
-
if (typeof command !== 'string' || !command.trim()) return null;
|
|
201
|
-
const tokens = shellTokenizeLoose(command);
|
|
202
|
-
for (let i = 0; i < tokens.length; i += 1) {
|
|
203
|
-
const token = String(tokens[i] || '').toLowerCase();
|
|
204
|
-
if (!token || SHELL_SEPARATORS.has(token) || !isCommandPosition(tokens, i)) continue;
|
|
205
|
-
if (SHELL_WRAPPER_COMMANDS.has(token)) {
|
|
206
|
-
for (let j = i + 1; j < tokens.length; j += 1) {
|
|
207
|
-
const flag = String(tokens[j] || '').toLowerCase();
|
|
208
|
-
if (SHELL_SEPARATORS.has(flag)) break;
|
|
209
|
-
if (!isShellCommandArgFlag(token, flag)) continue;
|
|
210
|
-
const nested = tokens.slice(j + 1).join(' ');
|
|
211
|
-
const nestedHit = classifyBridgeWorkerGitMutationCommand(nested || '');
|
|
212
|
-
if (nestedHit) return nestedHit;
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
if (token !== 'git' && token !== 'git.exe') continue;
|
|
218
|
-
const subIndex = skipGitGlobalOptions(tokens, i + 1);
|
|
219
|
-
const sub = String(tokens[subIndex] || '').toLowerCase();
|
|
220
|
-
if (!sub || SHELL_SEPARATORS.has(sub)) return 'git';
|
|
221
|
-
if (readonlyGitInvocation(tokens, subIndex)) continue;
|
|
222
|
-
return `git ${sub}`;
|
|
223
|
-
}
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
11
|
// Strip soft-warn marker blocks (header ⚠ <label> through next blank line / EOF)
|
|
228
12
|
// from outbound bodies. Never call on tool-result bodies fed back to the model.
|
|
229
13
|
// New compact format: each warn is a single line starting with `⚠ <Label>(` and ending at newline.
|
|
@@ -41,7 +41,7 @@ import * as nodeUtil from 'node:util';
|
|
|
41
41
|
import { existsSync } from 'node:fs';
|
|
42
42
|
import { randomUUID } from 'node:crypto';
|
|
43
43
|
import { invalidateBuiltinResultCache, analyzeShellCommandEffects, preflightShellLargeFileProbe } from './builtin.mjs';
|
|
44
|
-
import { markCodeGraphDirtyPaths, drainCodeGraphCache } from './code-graph.mjs';
|
|
44
|
+
import { markCodeGraphDirtyPaths, drainCodeGraphCache } from './code-graph-state.mjs';
|
|
45
45
|
import { maybeRewriteWmicProcessCommand } from './shell-policy.mjs';
|
|
46
46
|
import { _maybeEncodePowerShellCommand } from './shell-command.mjs';
|
|
47
47
|
import { _captureTrackedMtimes, _trackedDriftNoteAfter, _injectionBlockTargets, getDedupedDestructiveWarnings } from './builtin/bash-tool.mjs';
|
|
@@ -49,6 +49,8 @@ import { scrubLoaderVars, scrubProviderSecrets } from './env-scrub.mjs';
|
|
|
49
49
|
import { checkExecPolicyMessage } from './bash-policy-scan.mjs';
|
|
50
50
|
import { startChildGuardian } from '../../../shared/child-guardian.mjs';
|
|
51
51
|
|
|
52
|
+
globalThis.__mixdogBashSessionRuntimeLoaded = true;
|
|
53
|
+
|
|
52
54
|
// Default 600 s (10 min), max 1800 s. Aligned with the one-shot bash tool's
|
|
53
55
|
// 600 s default (builtin/bash-tool.mjs); the persistent shell carries
|
|
54
56
|
// longer-running pipelines (build/test/install) than one-shot calls, so a
|
|
@@ -171,15 +173,18 @@ function _startReaper() {
|
|
|
171
173
|
_killSession(id, 'idle-timeout');
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
|
-
|
|
175
|
-
clearInterval(_reaperTimer);
|
|
176
|
-
_reaperTimer = null;
|
|
177
|
-
}
|
|
176
|
+
_clearReaperIfIdle();
|
|
178
177
|
}, 30_000);
|
|
179
178
|
// Don't keep the event loop alive just for the reaper.
|
|
180
179
|
if (typeof _reaperTimer.unref === 'function') _reaperTimer.unref();
|
|
181
180
|
}
|
|
182
181
|
|
|
182
|
+
function _clearReaperIfIdle() {
|
|
183
|
+
if (_sessions.size !== 0 || !_reaperTimer) return;
|
|
184
|
+
clearInterval(_reaperTimer);
|
|
185
|
+
_reaperTimer = null;
|
|
186
|
+
}
|
|
187
|
+
|
|
183
188
|
// Kill a spawned shell along with any child processes it forked. Posix path
|
|
184
189
|
// signals the process group (pgid == pid because we spawn with detached:true),
|
|
185
190
|
// so `sleep 1000 &` or a node server started inside the session is reaped
|
|
@@ -239,6 +244,17 @@ async function _killSession(id, _reason) {
|
|
|
239
244
|
try { s.proc.stdin?.end(); } catch { /* ignore */ }
|
|
240
245
|
_killProcessTree(s.proc);
|
|
241
246
|
await Promise.race([exited, new Promise((r) => setTimeout(r, 3000))]);
|
|
247
|
+
_clearReaperIfIdle();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function _killSessionNow(id, _reason) {
|
|
251
|
+
const s = _sessions.get(id);
|
|
252
|
+
if (!s) return false;
|
|
253
|
+
_sessions.delete(id);
|
|
254
|
+
try { s.proc.stdin?.end(); } catch { /* ignore */ }
|
|
255
|
+
_killProcessTree(s.proc);
|
|
256
|
+
_clearReaperIfIdle();
|
|
257
|
+
return true;
|
|
242
258
|
}
|
|
243
259
|
|
|
244
260
|
function shellQuoteSingle(s) {
|
|
@@ -713,10 +729,21 @@ export function closeBashSession(sessionId, reason = 'external-close') {
|
|
|
713
729
|
return true;
|
|
714
730
|
}
|
|
715
731
|
|
|
732
|
+
export async function shutdownBashSessions(reason = 'runtime-close') {
|
|
733
|
+
const ids = [..._sessions.keys()];
|
|
734
|
+
await Promise.allSettled(ids.map((id) => _killSession(id, reason)));
|
|
735
|
+
_clearReaperIfIdle();
|
|
736
|
+
return { closed: ids.length };
|
|
737
|
+
}
|
|
738
|
+
|
|
716
739
|
// Best-effort cleanup on process exit so orphan bash children don't linger
|
|
717
740
|
// when the plugin host shuts down. Self-registered exit drain; bare 'exit' hook stays as idempotent backup.
|
|
718
|
-
export function drainBashSessions() {
|
|
719
|
-
|
|
741
|
+
export function drainBashSessions(reason = 'process-exit') {
|
|
742
|
+
let closed = 0;
|
|
743
|
+
for (const id of [..._sessions.keys()]) {
|
|
744
|
+
if (_killSessionNow(id, reason)) closed += 1;
|
|
745
|
+
}
|
|
746
|
+
return { closed };
|
|
720
747
|
}
|
|
721
748
|
if (typeof process?.on === 'function') {
|
|
722
749
|
process.on('exit', drainBashSessions);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Cross-process advisory lock for write
|
|
1
|
+
// Cross-process advisory lock for patch/write operations. Uses a sibling
|
|
2
2
|
// lockfile written exclusively (`wx`) and a process.kill(pid, 0) liveness
|
|
3
3
|
// check to clean up after crashed holders. Pair with the in-process
|
|
4
4
|
// withPathLock for the same target: in-process serialises async callers
|
|
@@ -311,6 +311,24 @@ function guardList(a) {
|
|
|
311
311
|
return null;
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
function guardFind(a) {
|
|
315
|
+
if (!hasOwn(a, 'query') || typeof a.query !== 'string' || a.query.trim().length === 0) {
|
|
316
|
+
return `Error: find requires non-empty string "query" (got ${describeType(a.query)})`;
|
|
317
|
+
}
|
|
318
|
+
if (hasOwn(a, 'path') && !isString(a.path)) {
|
|
319
|
+
return `Error: find arg "path" must be a string (got ${describeType(a.path)})`;
|
|
320
|
+
}
|
|
321
|
+
if (hasOwn(a, 'head_limit') && a.head_limit !== undefined && a.head_limit !== null) {
|
|
322
|
+
if (!isFiniteInt(a.head_limit)) {
|
|
323
|
+
return `Error: find arg "head_limit" must be a finite integer (got ${describeType(a.head_limit)})`;
|
|
324
|
+
}
|
|
325
|
+
if (a.head_limit < 0) {
|
|
326
|
+
return `Error: find arg "head_limit" must be >= 0 (0 means no cap); got ${a.head_limit}`;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
314
332
|
function guardGlob(a) {
|
|
315
333
|
// path alias root; pattern aliases glob/name/file_pattern
|
|
316
334
|
for (const k of ['path', 'root']) {
|
|
@@ -365,6 +383,7 @@ const GUARDS = {
|
|
|
365
383
|
shell: guardShell,
|
|
366
384
|
task: guardTask,
|
|
367
385
|
list: guardList,
|
|
386
|
+
find: guardFind,
|
|
368
387
|
glob: guardGlob,
|
|
369
388
|
code_graph: guardCodeGraph,
|
|
370
389
|
};
|
|
@@ -4,7 +4,7 @@ import { wrapCommandWithSnapshot } from '../shell-snapshot.mjs';
|
|
|
4
4
|
import { getDestructiveCommandWarning } from '../destructive-warning.mjs';
|
|
5
5
|
import { maybeRewriteWmicProcessCommand } from '../shell-policy.mjs';
|
|
6
6
|
import { buildBashPolicyScanTargets, checkExecPolicyMessage, injectionBlockTargets } from '../bash-policy-scan.mjs';
|
|
7
|
-
import { markCodeGraphDirtyPaths, drainCodeGraphCache } from '../code-graph.mjs';
|
|
7
|
+
import { markCodeGraphDirtyPaths, drainCodeGraphCache } from '../code-graph-state.mjs';
|
|
8
8
|
import {
|
|
9
9
|
buildJobNotFoundMessage,
|
|
10
10
|
startBackgroundShellJob,
|
|
@@ -60,7 +60,7 @@ export function _trackedDriftNoteAfter(_scope, _pre) {
|
|
|
60
60
|
// AbortSignal (e.g. the MCP/request signal threaded through options.abortSignal).
|
|
61
61
|
// Returns null when neither is present so existing session-only behavior is
|
|
62
62
|
// preserved unchanged. Uses AbortSignal.any when available; falls back to a
|
|
63
|
-
// manual controller + listener
|
|
63
|
+
// manual controller + listener path otherwise. The returned signal aborts as
|
|
64
64
|
// soon as either input signal aborts, which propagates to execShellCommand /
|
|
65
65
|
// executeBashSessionTool and triggers the same child-kill path the session
|
|
66
66
|
// signal already drives.
|
|
@@ -343,7 +343,7 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
343
343
|
// the background task finished (no polling tool is auto-driven). The
|
|
344
344
|
// notify ctx is threaded down from the MCP dispatch frame
|
|
345
345
|
// (server-main agentContext / _dispatchByModule) the same way the
|
|
346
|
-
//
|
|
346
|
+
// agent/explore-style tools receive notifyFn/routingSessionId/clientHostPid.
|
|
347
347
|
// Missing notifyFn (e.g. a non-MCP caller) degrades to a stderr
|
|
348
348
|
// diagnostic inside watchBackgroundShellJob — never fails the spawn.
|
|
349
349
|
try {
|
|
@@ -548,12 +548,12 @@ export async function executeTaskTool(args, options = {}) {
|
|
|
548
548
|
|
|
549
549
|
const taskId = typeof args.task_id === 'string' ? args.task_id.trim() : '';
|
|
550
550
|
if (!taskId) return 'Error: task_id is required';
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
if (/^
|
|
556
|
-
return `Error: "${taskId}" is
|
|
551
|
+
// sess_* values are agent/orchestrator session ids, not background shell
|
|
552
|
+
// tasks. task only resolves `shell mode=async` tasks, so surface a
|
|
553
|
+
// self-correcting hint instead of the bare "task not found" that otherwise
|
|
554
|
+
// invites a wrong-tool retry loop.
|
|
555
|
+
if (/^sess_/.test(taskId)) {
|
|
556
|
+
return `Error: "${taskId}" is an agent/session id, not a background task_id. Agent tasks deliver completion notifications; use agent list/read only for manual recovery.`;
|
|
557
557
|
}
|
|
558
558
|
|
|
559
559
|
const task = getBackgroundTask(taskId, { context: options });
|