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
|
@@ -4,16 +4,14 @@
|
|
|
4
4
|
* Scheduler instance (which would create a module cycle).
|
|
5
5
|
*
|
|
6
6
|
* channels/index.mjs registers a listener at boot that forwards into
|
|
7
|
-
* scheduler.noteActivity().
|
|
8
|
-
*
|
|
9
|
-
* where work is kicked off.
|
|
7
|
+
* scheduler.noteActivity(). Producers signal activity via setListener's
|
|
8
|
+
* forwarded pings.
|
|
10
9
|
*
|
|
11
|
-
* Boot race:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* All failures are swallowed — an activity ping is never load-bearing.
|
|
10
|
+
* Boot race: a ping that fires before channels boot registers the listener
|
|
11
|
+
* is buffered (most-recent wins) and replayed on setListener. Only one
|
|
12
|
+
* timestamp is buffered — "most recent activity was at T" is all
|
|
13
|
+
* scheduler.mjs needs. All failures are swallowed — a ping is never
|
|
14
|
+
* load-bearing.
|
|
17
15
|
*/
|
|
18
16
|
|
|
19
17
|
let _listener = null;
|
|
@@ -27,12 +25,3 @@ export function setListener(fn) {
|
|
|
27
25
|
try { _listener(ts); } catch { /* best-effort */ }
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
|
-
|
|
31
|
-
export function notifyActivity() {
|
|
32
|
-
const ts = Date.now();
|
|
33
|
-
if (_listener) {
|
|
34
|
-
try { _listener(ts); } catch { /* best-effort */ }
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
_pendingPingAt = ts; // buffer one, most recent wins
|
|
38
|
-
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const AGENT_OWNER = 'agent';
|
|
2
|
+
|
|
3
|
+
function ownerValue(value) {
|
|
4
|
+
if (value && typeof value === 'object') return String(value.owner || '').trim().toLowerCase();
|
|
5
|
+
return String(value || '').trim().toLowerCase();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isAgentOwner(value) {
|
|
9
|
+
const owner = ownerValue(value);
|
|
10
|
+
return owner === AGENT_OWNER;
|
|
11
|
+
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Agent Runtime — Internal LLM Helper (session-based).
|
|
3
3
|
*
|
|
4
4
|
* Every one-shot LLM dispatch from internal callers (memory-cycle,
|
|
5
5
|
* scheduler, webhook) now flows through the SAME session pipeline as the
|
|
6
|
-
* MCP `
|
|
7
|
-
* path = one message shape = one usage log = "
|
|
6
|
+
* MCP `agent` tool. No more parallel `provider.send()` helper — one code
|
|
7
|
+
* path = one message shape = one usage log = "agent single path".
|
|
8
8
|
*
|
|
9
9
|
* The returned function uses the existing caller signature, so call sites
|
|
10
10
|
* do not need changes:
|
|
11
11
|
*
|
|
12
|
-
* const llm =
|
|
12
|
+
* const llm = makeAgentDispatch({ role: 'maintenance', preset: 'haiku' });
|
|
13
13
|
* const text = await llm({ prompt });
|
|
14
14
|
*
|
|
15
15
|
* Internally it:
|
|
16
|
-
* 1. Resolves the preset (explicit arg > opts.preset >
|
|
16
|
+
* 1. Resolves the preset (explicit arg > opts.preset > hidden-role config)
|
|
17
17
|
* 2. Creates or reuses a session via the session manager
|
|
18
18
|
* 3. Applies stateless-reset for stateless profiles so the prefix handle
|
|
19
19
|
* stays warm while per-dispatch transcripts never leak
|
|
@@ -21,33 +21,27 @@
|
|
|
21
21
|
* `session/manager.mjs` (mode='active')
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { readFileSync } from 'fs';
|
|
25
|
-
import { join } from 'path';
|
|
26
24
|
import { loadConfig } from '../config.mjs';
|
|
27
25
|
import { resolveRuntimeSpec } from '../config.mjs';
|
|
28
|
-
import { getHiddenRole,
|
|
29
|
-
import {
|
|
30
|
-
import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
|
|
26
|
+
import { getHiddenRole, resolveAgentSessionPermission } from '../internal-roles.mjs';
|
|
27
|
+
import { prepareAgentSession } from './session-builder.mjs';
|
|
31
28
|
import {
|
|
32
29
|
askSession,
|
|
33
30
|
updateSessionStatus,
|
|
34
31
|
closeSession,
|
|
35
32
|
} from '../session/manager.mjs';
|
|
33
|
+
import {
|
|
34
|
+
agentWatchdogPolicyActive,
|
|
35
|
+
evaluateAgentWatchdogAbort,
|
|
36
|
+
resolveAgentWatchdogPolicy,
|
|
37
|
+
} from './agent-progress-watchdog.mjs';
|
|
36
38
|
|
|
37
|
-
// Cap
|
|
39
|
+
// Cap agent role synthesis to ~3000 tokens (~12 KB at the 4 B/tok
|
|
38
40
|
// working average). Pool B explore/recall/search answers occasionally land
|
|
39
41
|
// 8-10k-token walls that then ride in the Lead context for the rest of the
|
|
40
42
|
// turn; the cap keeps those outliers bounded without touching the 95%+ of
|
|
41
43
|
// answers already under the threshold.
|
|
42
44
|
const BRIEF_CAP_BYTES = 12 * 1024;
|
|
43
|
-
function envTimeoutMs(name, fallback) {
|
|
44
|
-
const raw = process.env[name];
|
|
45
|
-
if (raw === undefined || raw === '') return fallback;
|
|
46
|
-
const n = Number(raw);
|
|
47
|
-
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
|
|
48
|
-
}
|
|
49
|
-
const DEFAULT_FIRST_RESPONSE_TIMEOUT_MS = envTimeoutMs('MIXDOG_BRIDGE_FIRST_RESPONSE_TIMEOUT_MS', 120_000);
|
|
50
|
-
const DEFAULT_STALE_TIMEOUT_MS = envTimeoutMs('MIXDOG_BRIDGE_STALE_TIMEOUT_MS', 30 * 60_000);
|
|
51
45
|
function applyBriefCap(text) {
|
|
52
46
|
if (typeof text !== 'string') return text;
|
|
53
47
|
if (text.length <= BRIEF_CAP_BYTES) return text;
|
|
@@ -56,25 +50,35 @@ function applyBriefCap(text) {
|
|
|
56
50
|
return `${head}\n\n... [TRUNCATED — full answer was ~${approxTokens} tokens / ${Math.round(text.length / 1024)} KB. Re-run with brief:false for the complete synthesis]`;
|
|
57
51
|
}
|
|
58
52
|
|
|
59
|
-
// Unified-shard policy — most
|
|
53
|
+
// Unified-shard policy — most agent sessions (Pool B + Pool C) share the
|
|
60
54
|
// same tool schema so BP_1 is bit-identical across roles and one provider-side
|
|
61
55
|
// cache shard serves every caller. Per-role behaviour is steered by:
|
|
62
|
-
// 1.
|
|
63
|
-
//
|
|
64
|
-
// hidden-role catalog so the shard stays bit-identical across roles)
|
|
56
|
+
// 1. role-scoped BP2 instructions from agents/<role>.md and
|
|
57
|
+
// rules/agent/<role>.md
|
|
65
58
|
// 2. call-time guards (loop.mjs write-block + ai-wrapped-dispatch
|
|
66
59
|
// recursion break)
|
|
67
60
|
// Hidden-role exceptions are declarative: defaults/hidden-roles.json may set
|
|
68
61
|
// toolSchemaProfile when first-turn routing quality is worth a separate tool
|
|
69
|
-
// prefix.
|
|
70
|
-
// shard split.
|
|
62
|
+
// prefix. Standard profiles are none/read/full; legacy names stay as aliases.
|
|
71
63
|
// See manager.mjs resolveSessionTools for the single source of truth;
|
|
72
|
-
//
|
|
64
|
+
// agent visibility is declared via annotations.agentHidden on each tool def.
|
|
73
65
|
const HIDDEN_ROLE_TOOL_SCHEMA_PROFILES = Object.freeze({
|
|
66
|
+
full: null,
|
|
67
|
+
none: Object.freeze([]),
|
|
68
|
+
read: Object.freeze([
|
|
69
|
+
'code_graph',
|
|
70
|
+
'find',
|
|
71
|
+
'glob',
|
|
72
|
+
'list',
|
|
73
|
+
'grep',
|
|
74
|
+
'read',
|
|
75
|
+
]),
|
|
76
|
+
// Backward-compatible aliases for older hidden-role definitions.
|
|
74
77
|
unified: null,
|
|
75
78
|
'llm-only': Object.freeze([]),
|
|
76
79
|
'filesystem-read': Object.freeze([
|
|
77
80
|
'code_graph',
|
|
81
|
+
'find',
|
|
78
82
|
'glob',
|
|
79
83
|
'list',
|
|
80
84
|
'grep',
|
|
@@ -87,99 +91,127 @@ export function resolveHiddenRoleSchemaAllowedTools(hidden) {
|
|
|
87
91
|
if (Array.isArray(hidden.schemaAllowedTools)) {
|
|
88
92
|
return hidden.schemaAllowedTools.map((name) => String(name || '').trim()).filter(Boolean);
|
|
89
93
|
}
|
|
90
|
-
const profile = String(hidden.toolSchemaProfile || '
|
|
94
|
+
const profile = String(hidden.toolSchemaProfile || 'full').trim() || 'full';
|
|
91
95
|
if (Object.prototype.hasOwnProperty.call(HIDDEN_ROLE_TOOL_SCHEMA_PROFILES, profile)) {
|
|
92
96
|
return HIDDEN_ROLE_TOOL_SCHEMA_PROFILES[profile];
|
|
93
97
|
}
|
|
94
|
-
process.stderr.write(`[
|
|
98
|
+
process.stderr.write(`[agent-dispatch] unknown hidden-role toolSchemaProfile="${profile}" role="${hidden.name || 'unknown'}"; using full schema\n`);
|
|
95
99
|
return null;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
/**
|
|
99
|
-
* Resolve
|
|
103
|
+
* Resolve the maintenance ROUTE (or legacy preset name) for a dispatch.
|
|
100
104
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
105
|
+
* Returns one of:
|
|
106
|
+
* - a route object `{ provider, model, effort?, fast? }` — the preferred
|
|
107
|
+
* shape: a maintenance slot now stores its model directly (parity with
|
|
108
|
+
* `agents.<role>`), so no preset-array name lookup is needed.
|
|
109
|
+
* - a string — a legacy preset NAME still stored in a maintenance slot, or an
|
|
110
|
+
* explicit `preset`/`opts.preset` override. The caller resolves the name
|
|
111
|
+
* against config.presets for backward compatibility.
|
|
112
|
+
* - null — unresolved.
|
|
113
|
+
*
|
|
114
|
+
* Hidden roles read their slot from `maint[maintKey || slot]`; the cycle1/2/3
|
|
115
|
+
* agents share one knob via the `maintKey: 'memory'` override.
|
|
104
116
|
*/
|
|
105
|
-
export function
|
|
117
|
+
export function resolveMaintenanceRoute({ preset, optsPreset, role, config: cfgIn = null }) {
|
|
106
118
|
if (preset) return preset;
|
|
107
119
|
if (optsPreset) return optsPreset;
|
|
108
120
|
if (!role) return null;
|
|
109
|
-
// Hidden roles resolve their maintenance preset by SLOT. Every slot carries
|
|
110
|
-
// a concrete default in DEFAULT_MAINTENANCE, so `maint[slot]` resolves
|
|
111
|
-
// directly; the Setup panel can still tune each slot independently.
|
|
112
|
-
// (explorer.slot = 'explore', cycle1-agent.slot = 'cycle1', …). A hidden
|
|
113
|
-
// role may override which maintenance key it reads via `maintKey`
|
|
114
|
-
// (e.g. the cycle1/2/3 agents all read `maint.memory` instead of their
|
|
115
|
-
// own slot) so several agents can share one model knob while keeping
|
|
116
|
-
// distinct slots/identity.
|
|
117
121
|
const hidden = getHiddenRole(role);
|
|
118
122
|
if (hidden) {
|
|
119
123
|
try {
|
|
120
|
-
const config = cfgIn || loadConfig();
|
|
124
|
+
const config = cfgIn || loadConfig({ secrets: false });
|
|
121
125
|
const maint = config?.maintenance || {};
|
|
122
|
-
return maint[hidden.maintKey || hidden.slot]
|
|
126
|
+
return maint[hidden.maintKey || hidden.slot] ?? null;
|
|
123
127
|
} catch { return null; }
|
|
124
128
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Back-compat alias: older callers/tests import resolvePresetName. It now
|
|
133
|
+
// returns whatever resolveMaintenanceRoute does (route object OR name string).
|
|
134
|
+
export const resolvePresetName = resolveMaintenanceRoute;
|
|
135
|
+
|
|
136
|
+
// A maintenance slot value is a direct route when it carries provider+model.
|
|
137
|
+
function maintenanceRouteToPreset(routeOrName, role) {
|
|
138
|
+
if (!routeOrName || typeof routeOrName !== 'object') return null;
|
|
139
|
+
const provider = String(routeOrName.provider || '').trim();
|
|
140
|
+
const model = String(routeOrName.model || '').trim();
|
|
141
|
+
if (!provider || !model) return null;
|
|
142
|
+
const out = {
|
|
143
|
+
id: `maint-${role}`,
|
|
144
|
+
name: `MAINT ${String(role || '').toUpperCase()}`,
|
|
145
|
+
type: 'agent',
|
|
146
|
+
provider,
|
|
147
|
+
model,
|
|
148
|
+
tools: 'full',
|
|
149
|
+
};
|
|
150
|
+
const effort = String(routeOrName.effort || '').trim();
|
|
151
|
+
if (effort) out.effort = effort;
|
|
152
|
+
if (routeOrName.fast === true) out.fast = true;
|
|
153
|
+
return out;
|
|
135
154
|
}
|
|
136
155
|
|
|
137
156
|
/**
|
|
138
|
-
* Build
|
|
157
|
+
* Build an agent-backed dispatch callback.
|
|
139
158
|
*
|
|
140
159
|
* @param {object} opts
|
|
141
160
|
* @param {string} opts.role — REQUIRED; canonical role name (worker, cycle1-agent, scheduler-task, ...)
|
|
142
161
|
* @param {string} [opts.taskType] — optional internal classification stamped on the session
|
|
143
162
|
* @param {string} [opts.preset] — explicit preset override (bypasses role → preset lookup)
|
|
144
|
-
* @param {string} [opts.parentSessionId] — parent
|
|
163
|
+
* @param {string} [opts.parentSessionId] — parent agent session for trace aggregation
|
|
145
164
|
* @param {string|null} [opts.ownerSessionId] — owning Mixdog session for statusline isolation
|
|
146
165
|
* @param {AbortSignal} [opts.parentSignal] — optional AbortSignal from the fan-out coordinator;
|
|
147
|
-
* when aborted the
|
|
166
|
+
* when aborted the agent role session's own controller is also aborted so the
|
|
148
167
|
* provider call tears down promptly (parent→child cascade).
|
|
149
168
|
* @returns {(args: { prompt, preset?, sourceName? }) => Promise<string>}
|
|
150
169
|
*/
|
|
151
|
-
export function
|
|
170
|
+
export function makeAgentDispatch(opts = {}) {
|
|
152
171
|
if (!opts.role || typeof opts.role !== 'string') {
|
|
153
|
-
throw new Error('[
|
|
172
|
+
throw new Error('[agent-dispatch] opts.role is required');
|
|
154
173
|
}
|
|
155
174
|
const role = opts.role;
|
|
156
175
|
|
|
157
|
-
return async function
|
|
176
|
+
return async function agentDispatch({ prompt, preset: presetArg, sourceName: sourceNameArg, parentSignal: callParentSignal, idleTimeoutMs: callIdleTimeoutMs }) {
|
|
158
177
|
if (typeof prompt !== 'string' || !prompt) {
|
|
159
|
-
throw new Error(`[
|
|
178
|
+
throw new Error(`[agent-dispatch] prompt required for role "${role}"`);
|
|
160
179
|
}
|
|
161
180
|
|
|
162
|
-
const config = opts.config || loadConfig();
|
|
163
|
-
const
|
|
181
|
+
const config = opts.config || loadConfig({ secrets: false });
|
|
182
|
+
const routeOrName = resolveMaintenanceRoute({
|
|
164
183
|
preset: presetArg,
|
|
165
184
|
optsPreset: opts.preset,
|
|
166
185
|
role,
|
|
167
186
|
config,
|
|
168
187
|
});
|
|
169
|
-
if (!
|
|
188
|
+
if (!routeOrName) {
|
|
170
189
|
throw new Error(
|
|
171
|
-
`[
|
|
190
|
+
`[agent-dispatch] maintenance route unresolved for role "${role}" `
|
|
172
191
|
+ `(preset="${presetArg || opts.preset || ''}")`,
|
|
173
192
|
);
|
|
174
193
|
}
|
|
175
|
-
|
|
176
|
-
|
|
194
|
+
// Preferred path: a maintenance slot that stores its model directly
|
|
195
|
+
// (route object). Legacy path: a slot still holding a preset NAME —
|
|
196
|
+
// resolve it against config.presets for backward compatibility.
|
|
197
|
+
let preset = maintenanceRouteToPreset(routeOrName, role);
|
|
177
198
|
if (!preset) {
|
|
178
|
-
|
|
199
|
+
const legacyName = String(routeOrName || '').trim();
|
|
200
|
+
preset = config.presets?.find((p) => p.id === legacyName || p.name === legacyName) || null;
|
|
201
|
+
if (!preset) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`[agent-dispatch] maintenance route for role "${role}" is neither a `
|
|
204
|
+
+ `{provider,model} route nor a known preset name ("${legacyName}")`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
179
207
|
}
|
|
208
|
+
// Stable label for traces / session metadata, derived from the resolved
|
|
209
|
+
// preset object regardless of whether it came from a direct route or a
|
|
210
|
+
// legacy preset name.
|
|
211
|
+
const presetName = preset.id || preset.name || `maint-${role}`;
|
|
180
212
|
|
|
181
213
|
const runtimeSpec = resolveRuntimeSpec(preset, {
|
|
182
|
-
lane: '
|
|
214
|
+
lane: 'agent',
|
|
183
215
|
agentId: role,
|
|
184
216
|
});
|
|
185
217
|
|
|
@@ -189,38 +221,37 @@ export function makeBridgeLlm(opts = {}) {
|
|
|
189
221
|
// we pass `null` through instead of falling back to `process.cwd()`
|
|
190
222
|
// — the MCP server's launch dir is not deterministic across callers,
|
|
191
223
|
// and the downstream skill-discovery path tolerates null. Combined
|
|
192
|
-
// with the frozen
|
|
224
|
+
// with the frozen agent skill meta-tools (collect.mjs) this keeps
|
|
193
225
|
// every caller on the same provider cache shard.
|
|
194
226
|
const cwd = (typeof opts.cwd === 'string' && opts.cwd) ? opts.cwd : null;
|
|
195
227
|
|
|
196
228
|
// Unified dispatch: Pool B/C share bit-identical tools + system prompt
|
|
197
229
|
// unless a hidden role declares a narrow toolSchemaProfile. Per-role
|
|
198
|
-
// differentiation
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
230
|
+
// differentiation lives in scoped role rules / stable session context;
|
|
231
|
+
// raw role and permission labels are not repeated in the prompt.
|
|
232
|
+
// Runtime permission enforcement was removed (every tool call is
|
|
233
|
+
// trusted); schema profiles remain a routing-efficiency layer that
|
|
234
|
+
// narrows the advertised tool list, not a runtime safety gate.
|
|
202
235
|
const hidden = getHiddenRole(role);
|
|
203
236
|
const isPoolC = Boolean(hidden);
|
|
204
237
|
// Permission: read-declared hidden roles are locked in
|
|
205
|
-
//
|
|
206
|
-
const permission =
|
|
238
|
+
// resolveAgentSessionPermission (prepareAgentSession applies the same).
|
|
239
|
+
const permission = resolveAgentSessionPermission(
|
|
207
240
|
role,
|
|
208
241
|
opts.permission ?? (isPoolC ? (hidden?.permission || 'read') : null),
|
|
209
242
|
);
|
|
210
|
-
// Pool C hidden-role instructions live in BP2
|
|
211
|
-
// by
|
|
212
|
-
// reminder is suppressed so the shard stays bit-identical across
|
|
213
|
-
// every bridge role.
|
|
243
|
+
// Pool C hidden-role instructions live in BP2 role-scoped context
|
|
244
|
+
// (loaded by loadScopedRoleInstructions from rules/agent/*.md).
|
|
214
245
|
//
|
|
215
|
-
// User message = pure query.
|
|
216
|
-
//
|
|
217
|
-
//
|
|
246
|
+
// User message = pure query. Stable role rules ride in BP2; stable
|
|
247
|
+
// memory/meta rides in BP3; only the query varies per call, so
|
|
248
|
+
// provider cache reuses the shared prefix.
|
|
218
249
|
//
|
|
219
250
|
// Stateless ephemeral session — created fresh per call, never
|
|
220
251
|
// pooled or resumed. Cache prefix matching happens at the provider
|
|
221
252
|
// layer (account-level), not the session level.
|
|
222
253
|
const finalPrompt = prompt;
|
|
223
|
-
const { session } =
|
|
254
|
+
const { session } = prepareAgentSession({
|
|
224
255
|
role,
|
|
225
256
|
presetName,
|
|
226
257
|
preset,
|
|
@@ -242,7 +273,7 @@ export function makeBridgeLlm(opts = {}) {
|
|
|
242
273
|
// count-only "tools=N" line.
|
|
243
274
|
try {
|
|
244
275
|
const _toolNames = (session.tools || []).map((t) => t?.name).filter(Boolean);
|
|
245
|
-
process.stderr.write(`[
|
|
276
|
+
process.stderr.write(`[agent-dispatch] role=${role} tool-list (${_toolNames.length}): ${_toolNames.join(',')}\n`);
|
|
246
277
|
} catch { /* best-effort diagnostic */ }
|
|
247
278
|
|
|
248
279
|
await updateSessionStatus(session.id, 'running');
|
|
@@ -265,15 +296,16 @@ export function makeBridgeLlm(opts = {}) {
|
|
|
265
296
|
// Watchdog policy is split:
|
|
266
297
|
// - firstResponseTimeoutMs cuts quickly only when the model produces no
|
|
267
298
|
// first stream/tool activity at all.
|
|
268
|
-
// -
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
299
|
+
// - idle/tool-running caps come from role stallCap (hidden roles) or env defaults.
|
|
300
|
+
const _watchdogPolicy = resolveAgentWatchdogPolicy(role, {
|
|
301
|
+
idleTimeoutMs: Number.isFinite(callIdleTimeoutMs)
|
|
302
|
+
? callIdleTimeoutMs
|
|
303
|
+
: opts.idleTimeoutMs,
|
|
304
|
+
firstResponseTimeoutMs: opts.firstResponseTimeoutMs,
|
|
305
|
+
});
|
|
306
|
+
const _idleController = (agentWatchdogPolicyActive(_watchdogPolicy) && _linkSignal)
|
|
307
|
+
? new AbortController()
|
|
308
|
+
: null;
|
|
277
309
|
if (_idleController) {
|
|
278
310
|
try { _linkSignal(session.id, _idleController.signal); } catch { /* ignore */ }
|
|
279
311
|
}
|
|
@@ -281,35 +313,30 @@ export function makeBridgeLlm(opts = {}) {
|
|
|
281
313
|
? setInterval(() => {
|
|
282
314
|
const now = Date.now();
|
|
283
315
|
const snapshot = typeof _getProgressSnapshot === 'function' ? _getProgressSnapshot(session.id) : null;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
const last = snapshot.lastProgressAt || snapshot.firstActivityAt;
|
|
293
|
-
if (_staleMs > 0 && last && now - last > _staleMs) {
|
|
294
|
-
try { _idleController.abort(new Error(`bridge task stale (${_staleMs}ms without stream/tool progress)`)); } catch { /* ignore */ }
|
|
316
|
+
const abortErr = snapshot
|
|
317
|
+
? evaluateAgentWatchdogAbort(snapshot, now, _watchdogPolicy)
|
|
318
|
+
: null;
|
|
319
|
+
if (!abortErr && !snapshot && typeof _getLastProgressAt === 'function') {
|
|
320
|
+
const last = _getLastProgressAt(session.id);
|
|
321
|
+
if (_watchdogPolicy.idleStaleMs > 0 && last && now - last > _watchdogPolicy.idleStaleMs) {
|
|
322
|
+
try { _idleController.abort(new Error(`agent task stale (${_watchdogPolicy.idleStaleMs}ms without progress)`)); } catch { /* ignore */ }
|
|
295
323
|
}
|
|
296
324
|
return;
|
|
297
325
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
try { _idleController.abort(new Error(`bridge task stale (${_staleMs}ms without progress)`)); } catch { /* ignore */ }
|
|
326
|
+
if (abortErr) {
|
|
327
|
+
try { _idleController.abort(abortErr); } catch { /* ignore */ }
|
|
301
328
|
}
|
|
302
329
|
}, 1000)
|
|
303
330
|
: null;
|
|
304
331
|
if (_idleTimer && typeof _idleTimer.unref === 'function') _idleTimer.unref();
|
|
305
332
|
let terminalStatus = 'idle';
|
|
306
|
-
process.stderr.write(`[
|
|
307
|
-
const
|
|
333
|
+
process.stderr.write(`[agent-dispatch] role=${role} preset=${presetName} model=${preset.model} provider=${preset.provider} session=${session.id}\n`);
|
|
334
|
+
const _agentDispatchT0 = Date.now();
|
|
308
335
|
try {
|
|
309
336
|
const result = await askSession(session.id, finalPrompt, null, null, cwd);
|
|
310
|
-
process.stderr.write(`[
|
|
337
|
+
process.stderr.write(`[agent-dispatch] role=${role} session=${session.id} elapsed=${Date.now() - _agentDispatchT0}ms\n`);
|
|
311
338
|
const raw = result?.content || '';
|
|
312
|
-
// Brief cap.
|
|
339
|
+
// Brief cap. Agent role answers (explore/recall/search)
|
|
313
340
|
// occasionally balloon to 8-10k token walls that then ride in the
|
|
314
341
|
// parent Lead's context for the rest of the turn. A 3000-token
|
|
315
342
|
// (~12 KB) ceiling trims the long tail while leaving the vast
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified agent progress / stale watchdog policy for agent-tool spawns and
|
|
3
|
+
* agent-dispatch internal roles. Activity heartbeats (session manager) refresh
|
|
4
|
+
* lastProgressAt during long tool work; this module decides when to abort.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getHiddenRole } from '../internal-roles.mjs';
|
|
8
|
+
import {
|
|
9
|
+
resolveAgentStallThresholds,
|
|
10
|
+
resolveAgentToolThresholdSeconds,
|
|
11
|
+
} from '../stall-policy.mjs';
|
|
12
|
+
|
|
13
|
+
function envTimeoutMs(name, fallback) {
|
|
14
|
+
const raw = process.env[name];
|
|
15
|
+
if (raw === undefined || raw === '') return fallback;
|
|
16
|
+
const n = Number(raw);
|
|
17
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_FIRST_RESPONSE_TIMEOUT_MS = envTimeoutMs(
|
|
21
|
+
'MIXDOG_AGENT_FIRST_RESPONSE_TIMEOUT_MS',
|
|
22
|
+
120_000,
|
|
23
|
+
);
|
|
24
|
+
export const DEFAULT_STALE_TIMEOUT_MS = envTimeoutMs(
|
|
25
|
+
'MIXDOG_AGENT_STALE_TIMEOUT_MS',
|
|
26
|
+
30 * 60_000,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
function resolveExplicitMs(value, fallback) {
|
|
30
|
+
if (Number.isFinite(value) && value >= 0) return Math.floor(value);
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveAgentWatchdogPolicy(role, overrides = {}) {
|
|
35
|
+
const firstResponseMs = resolveExplicitMs(
|
|
36
|
+
overrides.firstResponseTimeoutMs,
|
|
37
|
+
DEFAULT_FIRST_RESPONSE_TIMEOUT_MS,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
let idleStaleMs;
|
|
41
|
+
if (Number.isFinite(overrides.idleTimeoutMs) && overrides.idleTimeoutMs >= 0) {
|
|
42
|
+
idleStaleMs = Math.floor(overrides.idleTimeoutMs);
|
|
43
|
+
} else if (getHiddenRole(role)) {
|
|
44
|
+
const { abort } = resolveAgentStallThresholds(role);
|
|
45
|
+
idleStaleMs = abort * 1000;
|
|
46
|
+
} else {
|
|
47
|
+
idleStaleMs = DEFAULT_STALE_TIMEOUT_MS;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const idleSec = idleStaleMs / 1000;
|
|
51
|
+
const toolRunningSec = resolveAgentToolThresholdSeconds(role, idleSec);
|
|
52
|
+
const toolRunningMs = Math.max(0, Math.floor(toolRunningSec * 1000));
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
firstResponseMs,
|
|
56
|
+
idleStaleMs,
|
|
57
|
+
toolRunningMs,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function evaluateAgentWatchdogAbort(snapshot, now, policy) {
|
|
62
|
+
if (!snapshot || !policy) return null;
|
|
63
|
+
|
|
64
|
+
if (snapshot.waitingForFirstActivity) {
|
|
65
|
+
const startedAt = snapshot.modelRequestStartedAt || snapshot.askStartedAt;
|
|
66
|
+
if (policy.firstResponseMs > 0 && startedAt && now - startedAt > policy.firstResponseMs) {
|
|
67
|
+
return new Error(`agent first response stale (${policy.firstResponseMs}ms)`);
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const last = snapshot.lastProgressAt || snapshot.firstActivityAt;
|
|
73
|
+
if (policy.idleStaleMs > 0 && last && now - last > policy.idleStaleMs) {
|
|
74
|
+
return new Error(`agent task stale (${policy.idleStaleMs}ms without stream/tool progress)`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
snapshot.stage === 'tool_running'
|
|
79
|
+
&& snapshot.toolStartedAt
|
|
80
|
+
&& policy.toolRunningMs > 0
|
|
81
|
+
&& now - snapshot.toolStartedAt > policy.toolRunningMs
|
|
82
|
+
) {
|
|
83
|
+
return new Error(`agent tool running stale (${policy.toolRunningMs}ms)`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function agentWatchdogPolicyActive(policy) {
|
|
90
|
+
if (!policy) return false;
|
|
91
|
+
return (policy.firstResponseMs > 0)
|
|
92
|
+
|| (policy.idleStaleMs > 0)
|
|
93
|
+
|| (policy.toolRunningMs > 0);
|
|
94
|
+
}
|