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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Regression tests pinning the native-provider (OpenAI-compat / claude-code / opencode)
|
|
3
|
+
// tool_call arguments contract: completed-but-malformed arguments JSON is NEVER
|
|
4
|
+
// thrown (which would kill the turn) NOR silently swallowed to {}. With a finish
|
|
5
|
+
// signal observed it is surfaced as an invalid-args MARKER carried on the
|
|
6
|
+
// arguments slot, so the dispatch loop feeds the parse error back to the model
|
|
7
|
+
// as an is_error tool_result and the model self-corrects in the SAME turn.
|
|
8
|
+
// Without a finish signal (mid-stream truncation) it remains a retryable
|
|
9
|
+
// TruncatedStreamError — that transient behavior is deliberately preserved.
|
|
10
|
+
// Unit-test style: synthetic inputs fed
|
|
11
|
+
// directly to the exported parser, asserting the resulting outcome — no
|
|
12
|
+
// network, no model.
|
|
13
|
+
import test from 'node:test';
|
|
14
|
+
import assert from 'node:assert/strict';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { dirname, resolve } from 'node:path';
|
|
18
|
+
import {
|
|
19
|
+
parseCompletedToolCallArgumentsJson,
|
|
20
|
+
toolCallsFromStreamAcc,
|
|
21
|
+
isInvalidToolArgsMarker,
|
|
22
|
+
formatInvalidToolArgsResult,
|
|
23
|
+
} from '../src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs';
|
|
24
|
+
import { classifyError } from '../src/runtime/agent/orchestrator/providers/retry-classifier.mjs';
|
|
25
|
+
|
|
26
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const STREAM_SRC = resolve(__dirname, '../src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs');
|
|
28
|
+
|
|
29
|
+
// Captures the synchronous throw of `fn` and returns the Error, failing if none.
|
|
30
|
+
function captureThrow(fn) {
|
|
31
|
+
try {
|
|
32
|
+
const v = fn();
|
|
33
|
+
assert.fail(`expected throw, got resolved value: ${JSON.stringify(v)}`);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
assert.ok(err instanceof Error, 'thrown value must be an Error');
|
|
36
|
+
return err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test('A: valid arguments JSON parses to the intended object', () => {
|
|
41
|
+
const out = parseCompletedToolCallArgumentsJson(
|
|
42
|
+
'{"pattern":"x","path":"src"}', 'test', { finishReason: 'stop' });
|
|
43
|
+
assert.deepEqual(out, { pattern: 'x', path: 'src' });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('B: empty/missing arguments default to {}', () => {
|
|
47
|
+
assert.deepEqual(parseCompletedToolCallArgumentsJson('', 'test', { finishReason: 'stop' }), {});
|
|
48
|
+
assert.deepEqual(parseCompletedToolCallArgumentsJson(null, 'test', { finishReason: 'stop' }), {});
|
|
49
|
+
assert.deepEqual(parseCompletedToolCallArgumentsJson(undefined, 'test'), {});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('C: bareword JSON with finishReason → invalid-args marker (no throw, no salvage)', () => {
|
|
53
|
+
const bareword = '{"pattern": dispatchAiWrapped, "path": "src/agent"}';
|
|
54
|
+
const out = parseCompletedToolCallArgumentsJson(bareword, 'test', { finishReason: 'stop' });
|
|
55
|
+
assert.equal(isInvalidToolArgsMarker(out), true);
|
|
56
|
+
assert.equal(out.__invalidToolArgs, true);
|
|
57
|
+
assert.equal(out.__rawArguments, bareword);
|
|
58
|
+
assert.equal(typeof out.__parseError, 'string');
|
|
59
|
+
assert.ok(out.__parseError.length > 0, 'parse error message must be carried');
|
|
60
|
+
// Model-facing tool_result text uses the shared invalid-args template.
|
|
61
|
+
const msg = formatInvalidToolArgsResult({ name: 'grep', arguments: out });
|
|
62
|
+
assert.match(msg, /invalid JSON/);
|
|
63
|
+
assert.match(msg, /Re-issue this tool call with valid JSON arguments/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('C: bareword JSON without finishReason → retryable TruncatedStreamError', () => {
|
|
67
|
+
const bareword = '{"pattern": dispatchAiWrapped, "path": "src/agent"}';
|
|
68
|
+
// meta omitted entirely
|
|
69
|
+
const err1 = captureThrow(() => parseCompletedToolCallArgumentsJson(bareword, 'test'));
|
|
70
|
+
assert.equal(err1.name, 'TruncatedStreamError');
|
|
71
|
+
assert.equal(err1.code, 'TRUNCATED_STREAM');
|
|
72
|
+
assert.equal(err1.truncatedStream, true);
|
|
73
|
+
// meta present but finishReason unset
|
|
74
|
+
const err2 = captureThrow(() =>
|
|
75
|
+
parseCompletedToolCallArgumentsJson(bareword, 'test', { id: 'call_1', name: 'grep' }));
|
|
76
|
+
assert.equal(err2.name, 'TruncatedStreamError');
|
|
77
|
+
assert.equal(err2.code, 'TRUNCATED_STREAM');
|
|
78
|
+
assert.equal(err2.truncatedStream, true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('D: fully malformed "{" splits on finishReason just like bareword', () => {
|
|
82
|
+
const marker = parseCompletedToolCallArgumentsJson('{', 'test', { finishReason: 'stop' });
|
|
83
|
+
assert.equal(isInvalidToolArgsMarker(marker), true);
|
|
84
|
+
assert.equal(marker.__rawArguments, '{');
|
|
85
|
+
|
|
86
|
+
const transient = captureThrow(() =>
|
|
87
|
+
parseCompletedToolCallArgumentsJson('{', 'test'));
|
|
88
|
+
assert.equal(transient.name, 'TruncatedStreamError');
|
|
89
|
+
assert.equal(transient.code, 'TRUNCATED_STREAM');
|
|
90
|
+
assert.equal(transient.truncatedStream, true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('E: source no longer contains any salvageBarewordJson reference', () => {
|
|
94
|
+
const src = readFileSync(STREAM_SRC, 'utf8');
|
|
95
|
+
assert.equal(src.includes('salvageBarewordJson'), false,
|
|
96
|
+
'salvage helper must be fully removed');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Stream-accumulation path: synthetic per-index accumulator + a parseToolCalls
|
|
100
|
+
// shim that delegates to the real parser (same wiring as openai-compat.mjs).
|
|
101
|
+
function parseToolCallsShim(choice, label) {
|
|
102
|
+
const calls = choice.message?.tool_calls;
|
|
103
|
+
if (!calls?.length) return undefined;
|
|
104
|
+
const finishReason = choice.finish_reason || null;
|
|
105
|
+
return calls
|
|
106
|
+
.filter((tc) => tc.type === 'function')
|
|
107
|
+
.map((tc) => ({
|
|
108
|
+
id: tc.id,
|
|
109
|
+
name: tc.function.name,
|
|
110
|
+
arguments: parseCompletedToolCallArgumentsJson(
|
|
111
|
+
tc.function.arguments, label,
|
|
112
|
+
{ id: tc.id, name: tc.function.name, finishReason }),
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
test('stream acc: valid accumulated arguments parse through to a tool call', () => {
|
|
117
|
+
const acc = new Map();
|
|
118
|
+
acc.set('n:0', {
|
|
119
|
+
id: 'call_a', type: 'function',
|
|
120
|
+
function: { name: 'grep', arguments: '{"pattern":"x","path":"src"}' },
|
|
121
|
+
_order: 1,
|
|
122
|
+
});
|
|
123
|
+
const out = toolCallsFromStreamAcc(acc, parseToolCallsShim, 'test', 'stop');
|
|
124
|
+
assert.deepEqual(out, [{ id: 'call_a', name: 'grep', arguments: { pattern: 'x', path: 'src' } }]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('stream acc: bareword with finish_reason → invalid-args marker on the call (no throw)', () => {
|
|
128
|
+
const acc = new Map();
|
|
129
|
+
acc.set('n:0', {
|
|
130
|
+
id: 'call_b', type: 'function',
|
|
131
|
+
function: { name: 'grep', arguments: '{"pattern": dispatchAiWrapped, "path": "src/agent"}' },
|
|
132
|
+
_order: 1,
|
|
133
|
+
});
|
|
134
|
+
const out = toolCallsFromStreamAcc(acc, parseToolCallsShim, 'test', 'stop');
|
|
135
|
+
assert.equal(Array.isArray(out), true);
|
|
136
|
+
assert.equal(out.length, 1);
|
|
137
|
+
assert.equal(out[0].id, 'call_b');
|
|
138
|
+
assert.equal(out[0].name, 'grep');
|
|
139
|
+
assert.equal(isInvalidToolArgsMarker(out[0].arguments), true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('retry-classifier: truncated stream (no finish) → transient (preserved behavior)', () => {
|
|
143
|
+
// The invalid-args marker is no longer a thrown Error, so there is nothing
|
|
144
|
+
// for classifyError to route — the dispatch loop handles it as a
|
|
145
|
+
// tool_result. Only the mid-stream truncation path still throws, and it
|
|
146
|
+
// must remain transient/retryable.
|
|
147
|
+
const transient = captureThrow(() =>
|
|
148
|
+
parseCompletedToolCallArgumentsJson('{', 'test'));
|
|
149
|
+
assert.equal(classifyError(transient), 'transient');
|
|
150
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { backgroundTaskFailureStatusLabel, isBackgroundErrorOnlyBody } from '../src/runtime/shared/err-text.mjs';
|
|
3
|
+
import { parseBackgroundTaskEnvelope } from '../src/tui/engine.mjs';
|
|
4
|
+
|
|
5
|
+
function assertEq(label, got, want) {
|
|
6
|
+
if (got !== want) throw new Error(`${label}: expected ${JSON.stringify(want)}, got ${JSON.stringify(got)}`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
assertEq('context overflow', backgroundTaskFailureStatusLabel('failed', 'AGENT_CONTEXT_OVERFLOW', { surface: 'agent' }), 'Failed · Context too large');
|
|
10
|
+
assertEq('first response timeout', backgroundTaskFailureStatusLabel('failed', 'agent first response stale (120000ms)', { surface: 'agent' }), 'Timeout · No first response 120s');
|
|
11
|
+
assertEq('task stale', backgroundTaskFailureStatusLabel('failed', 'agent task stale (1800000ms)', { surface: 'agent' }), 'Stale · No progress 30m');
|
|
12
|
+
assertEq('tool running stale', backgroundTaskFailureStatusLabel('failed', 'agent tool running stale (180000ms)', { surface: 'agent' }), 'Stale · No progress 3m');
|
|
13
|
+
assertEq(
|
|
14
|
+
'normalized first response',
|
|
15
|
+
backgroundTaskFailureStatusLabel('failed', 'No first response from the agent within 120s.', { surface: 'agent' }),
|
|
16
|
+
'Timeout · No first response 120s',
|
|
17
|
+
);
|
|
18
|
+
assertEq(
|
|
19
|
+
'normalized stale',
|
|
20
|
+
backgroundTaskFailureStatusLabel('failed', 'The agent went stale after 30m without new stream/tool progress.', { surface: 'agent' }),
|
|
21
|
+
'Stale · No progress 30m',
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
assertEq('error-only body', isBackgroundErrorOnlyBody('Error: Context too large.', 'Context too large.'), true);
|
|
25
|
+
assertEq('real body', isBackgroundErrorOnlyBody('found 3 files\nsrc/a.mjs', ''), false);
|
|
26
|
+
|
|
27
|
+
function showRawResultForBackgroundMetadata(expanded, isBackgroundMetadataResult, hasDisplayResult, hasRawResult) {
|
|
28
|
+
return expanded && (hasDisplayResult || hasRawResult) && (!isBackgroundMetadataResult || hasRawResult);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function collapsedFailureUsesHeaderNotDetail(headerFailureStatus, showRawResult) {
|
|
32
|
+
return Boolean(headerFailureStatus) && !showRawResult;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
assertEq('metadata expand with raw', showRawResultForBackgroundMetadata(true, true, false, true), true);
|
|
36
|
+
assertEq('metadata blocked without raw', showRawResultForBackgroundMetadata(true, true, false, false), false);
|
|
37
|
+
|
|
38
|
+
const headerFailure = backgroundTaskFailureStatusLabel(
|
|
39
|
+
'failed',
|
|
40
|
+
'No first response from the agent within 120s.',
|
|
41
|
+
{ surface: 'agent' },
|
|
42
|
+
);
|
|
43
|
+
assertEq('header failure label', headerFailure, 'Timeout · No first response 120s');
|
|
44
|
+
assertEq('failure in header not detail', collapsedFailureUsesHeaderNotDetail(headerFailure, false), true);
|
|
45
|
+
assertEq('expanded raw keeps detail path', collapsedFailureUsesHeaderNotDetail(headerFailure, true), false);
|
|
46
|
+
|
|
47
|
+
const failedEnvelope = [
|
|
48
|
+
'background task',
|
|
49
|
+
'task_id: task_fail_smoke',
|
|
50
|
+
'surface: explore',
|
|
51
|
+
'operation: explore',
|
|
52
|
+
'status: failed',
|
|
53
|
+
'error: No first response from the agent within 120s.',
|
|
54
|
+
].join('\n');
|
|
55
|
+
const parsed = parseBackgroundTaskEnvelope(failedEnvelope);
|
|
56
|
+
if (!parsed?.rawResult || String(parsed.result || '').trim()) {
|
|
57
|
+
throw new Error(`failed envelope should keep rawResult and empty display result: ${JSON.stringify(parsed)}`);
|
|
58
|
+
}
|
|
59
|
+
if (!parsed.args?.error) {
|
|
60
|
+
throw new Error('failed envelope should preserve args.error');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const failedEnvelopeWithErrorBody = [
|
|
64
|
+
failedEnvelope,
|
|
65
|
+
'',
|
|
66
|
+
'Error: No first response from the agent within 120s.',
|
|
67
|
+
].join('\n');
|
|
68
|
+
const parsedWithErrorBody = parseBackgroundTaskEnvelope(failedEnvelopeWithErrorBody);
|
|
69
|
+
if (!parsedWithErrorBody?.rawResult || String(parsedWithErrorBody.result || '').trim()) {
|
|
70
|
+
throw new Error(`failed envelope with error-only body should keep rawResult and empty display result: ${JSON.stringify(parsedWithErrorBody)}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log('tui-background-failure-smoke: ok');
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
bumpUsageMetricsEpoch,
|
|
4
|
+
bumpUsageMetricsTurnId,
|
|
5
|
+
resolveUsageMetricsEpoch,
|
|
6
|
+
usageMetricsIdempotencyKey,
|
|
7
|
+
applyAskTerminalUsageTotals,
|
|
8
|
+
} from '../src/runtime/agent/orchestrator/session/manager.mjs';
|
|
9
|
+
|
|
10
|
+
function assert(condition, message) {
|
|
11
|
+
if (!condition) throw new Error(message);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function applyDelta(sessionId, session, delta, seen) {
|
|
15
|
+
const epoch = resolveUsageMetricsEpoch(session, delta);
|
|
16
|
+
session.usageMetricsEpoch = epoch;
|
|
17
|
+
const ikey = usageMetricsIdempotencyKey(sessionId, session, delta);
|
|
18
|
+
const isReplay = seen.has(ikey);
|
|
19
|
+
seen.add(ikey);
|
|
20
|
+
if (!isReplay) {
|
|
21
|
+
session.totalInputTokens = (session.totalInputTokens || 0) + (delta.deltaInput || 0);
|
|
22
|
+
}
|
|
23
|
+
session.lastIterationIndex = delta.iterationIndex;
|
|
24
|
+
return isReplay;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const sessionId = 'smoke-sess';
|
|
28
|
+
const session = { usageMetricsEpoch: 0, lastIterationIndex: null, totalInputTokens: 0 };
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
|
|
31
|
+
assert(
|
|
32
|
+
applyDelta(sessionId, session, { iterationIndex: 1, deltaInput: 100 }, seen) === false,
|
|
33
|
+
'first iteration should count',
|
|
34
|
+
);
|
|
35
|
+
assert(session.totalInputTokens === 100, 'first iteration tokens');
|
|
36
|
+
|
|
37
|
+
bumpUsageMetricsEpoch(session);
|
|
38
|
+
assert(session.usageMetricsEpoch === 1, 'compact reset should bump epoch');
|
|
39
|
+
|
|
40
|
+
const replay = applyDelta(sessionId, session, {
|
|
41
|
+
iterationIndex: 1,
|
|
42
|
+
usageMetricsEpoch: session.usageMetricsEpoch,
|
|
43
|
+
deltaInput: 50,
|
|
44
|
+
}, seen);
|
|
45
|
+
assert(replay === false, 'equal index after compact reset must not be treated as replay');
|
|
46
|
+
assert(session.totalInputTokens === 150, 'post-compact iteration must add tokens');
|
|
47
|
+
|
|
48
|
+
assert(
|
|
49
|
+
applyDelta(sessionId, session, {
|
|
50
|
+
iterationIndex: 1,
|
|
51
|
+
usageMetricsEpoch: 1,
|
|
52
|
+
deltaInput: 999,
|
|
53
|
+
}, seen) === true,
|
|
54
|
+
'retry of same epoch+index should replay',
|
|
55
|
+
);
|
|
56
|
+
assert(session.totalInputTokens === 150, 'replay must not double-count');
|
|
57
|
+
|
|
58
|
+
// Semantic compact + provider send at same iteration/epoch (no_change compact path)
|
|
59
|
+
const collisionSeen = new Set();
|
|
60
|
+
const collisionSession = { usageMetricsEpoch: 0, lastIterationIndex: null, totalInputTokens: 0 };
|
|
61
|
+
assert(
|
|
62
|
+
applyDelta(sessionId, collisionSession, {
|
|
63
|
+
iterationIndex: 1,
|
|
64
|
+
source: 'semantic_compact',
|
|
65
|
+
deltaInput: 20,
|
|
66
|
+
}, collisionSeen) === false,
|
|
67
|
+
'semantic compact usage should count',
|
|
68
|
+
);
|
|
69
|
+
assert(
|
|
70
|
+
applyDelta(sessionId, collisionSession, {
|
|
71
|
+
iterationIndex: 1,
|
|
72
|
+
source: 'provider_send',
|
|
73
|
+
deltaInput: 80,
|
|
74
|
+
}, collisionSeen) === false,
|
|
75
|
+
'provider send at same iteration must not replay semantic compact key',
|
|
76
|
+
);
|
|
77
|
+
assert(collisionSession.totalInputTokens === 100, 'semantic + provider usage both counted');
|
|
78
|
+
|
|
79
|
+
// Integrated incremental + terminal: totals applied once when incremental flag set
|
|
80
|
+
const integrated = { totalInputTokens: 1000, totalOutputTokens: 0, tokensCumulative: 1000 };
|
|
81
|
+
integrated.totalInputTokens += 40;
|
|
82
|
+
integrated.totalOutputTokens += 6;
|
|
83
|
+
integrated.tokensCumulative += 46;
|
|
84
|
+
applyAskTerminalUsageTotals(integrated, {
|
|
85
|
+
usage: { inputTokens: 40, outputTokens: 6 },
|
|
86
|
+
lastTurnUsage: { inputTokens: 40, outputTokens: 6 },
|
|
87
|
+
}, { skipTotalsIfIncremental: true });
|
|
88
|
+
assert(integrated.totalInputTokens === 1040, 'terminal must not double-count incremental turn totals');
|
|
89
|
+
assert(integrated.totalOutputTokens === 6, 'terminal must not double-count output');
|
|
90
|
+
assert(integrated.lastInputTokens === 40, 'terminal still updates last-turn window snapshot');
|
|
91
|
+
|
|
92
|
+
// Cross-ask collision (reviewer07): ask #2 reuses iteration indices 1-2
|
|
93
|
+
const crossSeen = new Set();
|
|
94
|
+
const crossSession = { usageMetricsEpoch: 0, usageMetricsTurnId: 0, lastIterationIndex: null, totalInputTokens: 0 };
|
|
95
|
+
bumpUsageMetricsTurnId(crossSession);
|
|
96
|
+
assert(crossSession.usageMetricsTurnId === 1, 'ask #1 turn id');
|
|
97
|
+
assert(applyDelta(sessionId, crossSession, { iterationIndex: 1, source: 'provider_send', deltaInput: 100 }, crossSeen) === false);
|
|
98
|
+
assert(applyDelta(sessionId, crossSession, { iterationIndex: 2, source: 'provider_send', deltaInput: 200 }, crossSeen) === false);
|
|
99
|
+
assert(crossSession.totalInputTokens === 300, 'ask #1 iterations counted');
|
|
100
|
+
bumpUsageMetricsTurnId(crossSession);
|
|
101
|
+
assert(crossSession.usageMetricsTurnId === 2, 'ask #2 turn id');
|
|
102
|
+
assert(
|
|
103
|
+
applyDelta(sessionId, crossSession, { iterationIndex: 1, source: 'provider_send', deltaInput: 10 }, crossSeen) === false,
|
|
104
|
+
'ask #2 iteration 1 must not replay ask #1 key',
|
|
105
|
+
);
|
|
106
|
+
assert(
|
|
107
|
+
applyDelta(sessionId, crossSession, { iterationIndex: 2, source: 'provider_send', deltaInput: 20 }, crossSeen) === false,
|
|
108
|
+
'ask #2 iteration 2 must not replay ask #1 key',
|
|
109
|
+
);
|
|
110
|
+
assert(applyDelta(sessionId, crossSession, { iterationIndex: 3, source: 'provider_send', deltaInput: 30 }, crossSeen) === false);
|
|
111
|
+
assert(crossSession.totalInputTokens === 360, 'ask #2 tokens must include iter 1-3 without undercount');
|
|
112
|
+
|
|
113
|
+
process.stdout.write('usage-metrics-epoch-smoke: ok\n');
|
|
114
|
+
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
permission: read
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Debugger
|
|
2
6
|
|
|
3
7
|
Bug tracking and analysis agent.
|
|
8
|
+
|
|
9
|
+
Return the most likely cause, supporting evidence, and the smallest useful next
|
|
10
|
+
check or fix path. Avoid broad speculation; distinguish confirmed facts from
|
|
11
|
+
inferences.
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
---
|
|
2
|
+
permission: read-write
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Heavy Worker
|
|
6
|
+
Complex implementation agent.
|
|
7
|
+
|
|
8
|
+
Reviewer/Lead verification follows your delivery; avoid broad build/test loops and focus on minimal checks plus how to verify. Scope confirmation, obvious-error checks, and static review are fine.
|
|
9
|
+
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
permission: read
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Reviewer
|
|
2
6
|
|
|
3
7
|
Cross-checking review agent.
|
|
8
|
+
|
|
9
|
+
Return findings first, ordered by severity, with precise file references when
|
|
10
|
+
available. If no actionable issues are found, say so clearly and note only
|
|
11
|
+
material residual risk or missing verification.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
permission: read-write
|
|
3
|
+
toolSchemaProfile: read-write-search
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Scheduler Task
|
|
7
|
+
|
|
8
|
+
Scheduled task execution agent.
|
|
9
|
+
|
|
10
|
+
Output a concise English handoff for Lead: outcome, action taken or skipped,
|
|
11
|
+
notable evidence, and any remaining risk. If there is nothing actionable, use
|
|
12
|
+
the skip protocol.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
permission: read-write
|
|
3
|
+
toolSchemaProfile: read-write-search
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Webhook Handler
|
|
7
|
+
|
|
8
|
+
Inbound webhook event analysis agent.
|
|
9
|
+
|
|
10
|
+
Output a concise English handoff for Lead: event outcome, action taken or
|
|
11
|
+
skipped, notable evidence, and any remaining risk. If there is nothing
|
|
12
|
+
actionable, use the skip protocol.
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
---
|
|
2
|
+
permission: read-write
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Worker
|
|
6
|
+
Basic implementation agent.
|
|
7
|
+
|
|
8
|
+
Reviewer/Lead verification follows your delivery; keep self-checks minimal (scope confirmation and obvious-error checks). Static review, change summaries, and manual verification notes are fine.
|
|
9
|
+
|
package/src/app.mjs
CHANGED
|
@@ -6,9 +6,33 @@ import { performance } from 'node:perf_hooks';
|
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
const VALUE_OPTIONS = new Set(['--provider', '--model']);
|
|
8
8
|
const FLAG_OPTIONS = new Set(['--readonly', '--help', '-h', '--plain', '--react']);
|
|
9
|
+
const HEADLESS_ROLE_ALIASES = new Map([
|
|
10
|
+
['explorer', 'explore'],
|
|
11
|
+
['explore', 'explore'],
|
|
12
|
+
['maint', 'maintainer'],
|
|
13
|
+
['maintenance', 'maintainer'],
|
|
14
|
+
['maintainer', 'maintainer'],
|
|
15
|
+
['worker', 'worker'],
|
|
16
|
+
['heavy', 'heavy-worker'],
|
|
17
|
+
['heavyworker', 'heavy-worker'],
|
|
18
|
+
['heavy-worker', 'heavy-worker'],
|
|
19
|
+
['review', 'reviewer'],
|
|
20
|
+
['reviewer', 'reviewer'],
|
|
21
|
+
['debug', 'debugger'],
|
|
22
|
+
['debugger', 'debugger'],
|
|
23
|
+
['web', 'web-researcher'],
|
|
24
|
+
['web-researcher', 'web-researcher'],
|
|
25
|
+
]);
|
|
9
26
|
const BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ''));
|
|
10
27
|
const BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance.now());
|
|
11
28
|
|
|
29
|
+
// Many independent singletons self-register a process 'exit' drain (session
|
|
30
|
+
// store, bash sessions, search/memory state, bridge trace, channel worker, …),
|
|
31
|
+
// which legitimately exceeds Node's default 10-listener warning threshold in a
|
|
32
|
+
// fully-loaded runtime. Raise the cap once at the CLI entry so a benign
|
|
33
|
+
// MaxListenersExceededWarning never leaks into the user's terminal.
|
|
34
|
+
try { process.setMaxListeners(Math.max(process.getMaxListeners(), 64)); } catch { /* ignore */ }
|
|
35
|
+
|
|
12
36
|
function bootProfile(event, fields = {}) {
|
|
13
37
|
if (!BOOT_PROFILE_ENABLED) return;
|
|
14
38
|
const elapsedMs = performance.now() - BOOT_PROFILE_START;
|
|
@@ -33,6 +57,41 @@ function unknownOption(argv) {
|
|
|
33
57
|
return null;
|
|
34
58
|
}
|
|
35
59
|
|
|
60
|
+
function positionalArgs(argv) {
|
|
61
|
+
const out = [];
|
|
62
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
63
|
+
const arg = argv[i];
|
|
64
|
+
if (VALUE_OPTIONS.has(arg)) {
|
|
65
|
+
i += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (FLAG_OPTIONS.has(arg)) continue;
|
|
69
|
+
if (String(arg || '').startsWith('-')) continue;
|
|
70
|
+
out.push(arg);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeHeadlessRole(value) {
|
|
76
|
+
const key = String(value || '').trim().toLowerCase().replace(/[\s_]+/g, '-');
|
|
77
|
+
return HEADLESS_ROLE_ALIASES.get(key) || null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function parseHeadlessRoleCommand(argv = []) {
|
|
81
|
+
const args = positionalArgs(argv);
|
|
82
|
+
if (args.length === 0) return null;
|
|
83
|
+
|
|
84
|
+
if (String(args[0] || '').toLowerCase() === 'role') {
|
|
85
|
+
return { error: 'usage: mixdog <role> <message...>' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const role = normalizeHeadlessRole(args[0]);
|
|
89
|
+
if (!role) return null;
|
|
90
|
+
const message = args.slice(1).join(' ').trim();
|
|
91
|
+
if (!message) return { error: `usage: mixdog ${args[0]} <message...>` };
|
|
92
|
+
return { role, message };
|
|
93
|
+
}
|
|
94
|
+
|
|
36
95
|
/**
|
|
37
96
|
* mixdog launcher.
|
|
38
97
|
*
|
|
@@ -57,10 +116,9 @@ export async function run(argv = []) {
|
|
|
57
116
|
toolMode,
|
|
58
117
|
};
|
|
59
118
|
|
|
60
|
-
// `--help` / `-h`:
|
|
61
|
-
// repl.mjs (single source of truth) and is exported as printHelp().
|
|
119
|
+
// `--help` / `-h`: keep this path tiny; do not import the REPL/runtime stack.
|
|
62
120
|
if (argv.includes('--help') || argv.includes('-h')) {
|
|
63
|
-
const { printHelp } = await import('./
|
|
121
|
+
const { printHelp } = await import('./help.mjs');
|
|
64
122
|
printHelp();
|
|
65
123
|
return 0;
|
|
66
124
|
}
|
|
@@ -76,6 +134,22 @@ export async function run(argv = []) {
|
|
|
76
134
|
return 1;
|
|
77
135
|
}
|
|
78
136
|
|
|
137
|
+
const headless = parseHeadlessRoleCommand(argv);
|
|
138
|
+
if (headless?.error) {
|
|
139
|
+
process.stderr.write(`mixdog: ${headless.error}\n`);
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
if (headless) {
|
|
143
|
+
const { runHeadlessRole } = await import('./headless-role.mjs');
|
|
144
|
+
return await runHeadlessRole({
|
|
145
|
+
role: headless.role,
|
|
146
|
+
message: headless.message,
|
|
147
|
+
provider: opts.provider,
|
|
148
|
+
model: opts.model,
|
|
149
|
+
cwd: process.cwd(),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
79
153
|
// Default: the canonical React/Ink TUI over the mixdog session runtime.
|
|
80
154
|
const bundle = join(__dirname, 'tui', 'dist', 'index.mjs');
|
|
81
155
|
if (!existsSync(bundle)) {
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
{
|
|
4
4
|
"name": "explorer",
|
|
5
5
|
"slot": "explore",
|
|
6
|
-
"systemFile": "rules/
|
|
6
|
+
"systemFile": "rules/agent/30-explorer.md",
|
|
7
7
|
"description": "Filesystem navigation agent invoked by the `explore` MCP tool",
|
|
8
8
|
"invokedBy": "explore",
|
|
9
|
-
"toolSchemaProfile": "
|
|
9
|
+
"toolSchemaProfile": "read",
|
|
10
10
|
"kind": "retrieval",
|
|
11
11
|
"permission": "read",
|
|
12
12
|
"stallCap": { "idleSeconds": 240, "toolRunningSeconds": 180 }
|
|
@@ -14,43 +14,47 @@
|
|
|
14
14
|
{
|
|
15
15
|
"name": "cycle1-agent",
|
|
16
16
|
"slot": "cycle1",
|
|
17
|
-
"systemFile": "rules/
|
|
17
|
+
"systemFile": "rules/agent/40-cycle1-agent.md",
|
|
18
18
|
"description": "Chunker/classifier invoked by memory-cycle runCycle1",
|
|
19
19
|
"invokedBy": "cycle1",
|
|
20
20
|
"maintKey": "memory",
|
|
21
|
-
"toolSchemaProfile": "
|
|
21
|
+
"toolSchemaProfile": "none",
|
|
22
|
+
"kind": "maintenance",
|
|
22
23
|
"permission": "read",
|
|
23
24
|
"stallCap": { "idleSeconds": 300, "toolRunningSeconds": 300 }
|
|
24
25
|
},
|
|
25
26
|
{
|
|
26
27
|
"name": "cycle2-agent",
|
|
27
28
|
"slot": "cycle2",
|
|
28
|
-
"systemFile": "rules/
|
|
29
|
+
"systemFile": "rules/agent/41-cycle2-agent.md",
|
|
29
30
|
"description": "Root re-scorer invoked by memory-cycle runCycle2",
|
|
30
31
|
"invokedBy": "cycle2",
|
|
31
32
|
"maintKey": "memory",
|
|
32
|
-
"toolSchemaProfile": "
|
|
33
|
+
"toolSchemaProfile": "none",
|
|
34
|
+
"kind": "maintenance",
|
|
33
35
|
"permission": "read",
|
|
34
36
|
"stallCap": { "idleSeconds": 300, "toolRunningSeconds": 300 }
|
|
35
37
|
},
|
|
36
38
|
{
|
|
37
39
|
"name": "cycle3-agent",
|
|
38
40
|
"slot": "cycle3",
|
|
39
|
-
"systemFile": "rules/
|
|
41
|
+
"systemFile": "rules/agent/42-cycle3-agent.md",
|
|
40
42
|
"description": "Core memory reviewer invoked by memory-cycle runCycle3",
|
|
41
43
|
"invokedBy": "cycle3",
|
|
42
44
|
"maintKey": "memory",
|
|
43
|
-
"toolSchemaProfile": "
|
|
45
|
+
"toolSchemaProfile": "none",
|
|
46
|
+
"kind": "maintenance",
|
|
44
47
|
"permission": "read",
|
|
45
48
|
"stallCap": { "idleSeconds": 300, "toolRunningSeconds": 300 }
|
|
46
49
|
},
|
|
47
50
|
{
|
|
48
51
|
"name": "scheduler-task",
|
|
49
52
|
"slot": "scheduler",
|
|
50
|
-
"systemFile": "agents/scheduler-task.md",
|
|
53
|
+
"systemFile": "agents/scheduler-task/AGENT.md",
|
|
51
54
|
"description": "Scheduled-task executor invoked by scheduler tick",
|
|
52
55
|
"invokedBy": "scheduler",
|
|
53
|
-
"toolSchemaProfile": "
|
|
56
|
+
"toolSchemaProfile": "read-write-search",
|
|
57
|
+
"kind": "maintenance",
|
|
54
58
|
"permission": "read-write",
|
|
55
59
|
"inboundEvent": true,
|
|
56
60
|
"instructionDir": "schedules",
|
|
@@ -59,10 +63,11 @@
|
|
|
59
63
|
{
|
|
60
64
|
"name": "webhook-handler",
|
|
61
65
|
"slot": "webhook",
|
|
62
|
-
"systemFile": "agents/webhook-handler.md",
|
|
66
|
+
"systemFile": "agents/webhook-handler/AGENT.md",
|
|
63
67
|
"description": "Webhook payload handler invoked by inbound webhook events",
|
|
64
68
|
"invokedBy": "webhook",
|
|
65
|
-
"toolSchemaProfile": "
|
|
69
|
+
"toolSchemaProfile": "read-write-search",
|
|
70
|
+
"kind": "maintenance",
|
|
66
71
|
"permission": "read-write",
|
|
67
72
|
"inboundEvent": true,
|
|
68
73
|
"instructionDir": "webhooks",
|