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,330 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { createStandaloneHookBus } from '../src/standalone/hook-bus.mjs';
|
|
7
|
+
|
|
8
|
+
function tempRoot() {
|
|
9
|
+
return mkdtempSync(join(tmpdir(), 'mixdog-hook-test-'));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function writeJson(file, value) {
|
|
13
|
+
writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('standard PreToolUse hook can deny and modify tool input', async () => {
|
|
17
|
+
const root = tempRoot();
|
|
18
|
+
const hookScript = join(root, 'pretool.mjs');
|
|
19
|
+
writeFileSync(hookScript, `
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
const input = JSON.parse(readFileSync(0, 'utf8'));
|
|
22
|
+
const command = input.tool_input?.command || '';
|
|
23
|
+
if (command.includes('rm -rf')) {
|
|
24
|
+
console.log(JSON.stringify({ hookSpecificOutput: {
|
|
25
|
+
hookEventName: 'PreToolUse',
|
|
26
|
+
permissionDecision: 'deny',
|
|
27
|
+
permissionDecisionReason: 'blocked destructive command'
|
|
28
|
+
}}));
|
|
29
|
+
} else if (command === 'rewrite-me') {
|
|
30
|
+
console.log(JSON.stringify({ hookSpecificOutput: {
|
|
31
|
+
hookEventName: 'PreToolUse',
|
|
32
|
+
updatedInput: { command: 'rewritten' }
|
|
33
|
+
}}));
|
|
34
|
+
}
|
|
35
|
+
`, 'utf8');
|
|
36
|
+
const hooksFile = join(root, 'hooks.json');
|
|
37
|
+
writeJson(hooksFile, {
|
|
38
|
+
hooks: {
|
|
39
|
+
PreToolUse: [{
|
|
40
|
+
matcher: 'shell',
|
|
41
|
+
hooks: [{ type: 'command', command: process.execPath, args: [hookScript] }],
|
|
42
|
+
}],
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
47
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
48
|
+
try {
|
|
49
|
+
const bus = createStandaloneHookBus({ dataDir: root });
|
|
50
|
+
const denied = await bus.beforeTool({ sessionId: 'sess_test', cwd: root, name: 'shell', args: { command: 'rm -rf build' } });
|
|
51
|
+
assert.equal(denied.action, 'deny');
|
|
52
|
+
assert.match(denied.reason, /blocked destructive/);
|
|
53
|
+
|
|
54
|
+
const modified = await bus.beforeTool({ sessionId: 'sess_test', cwd: root, name: 'shell', args: { command: 'rewrite-me' } });
|
|
55
|
+
assert.equal(modified.action, 'modify');
|
|
56
|
+
assert.deepEqual(modified.args, { command: 'rewritten' });
|
|
57
|
+
} finally {
|
|
58
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
59
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('UserPromptSubmit hook returns additional context', async () => {
|
|
64
|
+
const root = tempRoot();
|
|
65
|
+
const hookScript = join(root, 'prompt.mjs');
|
|
66
|
+
writeFileSync(hookScript, `console.log('plain prompt context');\n`, 'utf8');
|
|
67
|
+
const hooksFile = join(root, 'hooks.json');
|
|
68
|
+
writeJson(hooksFile, {
|
|
69
|
+
hooks: {
|
|
70
|
+
UserPromptSubmit: [{
|
|
71
|
+
hooks: [{ type: 'command', command: process.execPath, args: [hookScript] }],
|
|
72
|
+
}],
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
77
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
78
|
+
try {
|
|
79
|
+
const bus = createStandaloneHookBus({ dataDir: root });
|
|
80
|
+
const result = await bus.dispatch('UserPromptSubmit', { session_id: 'sess_test', cwd: root, prompt: 'hello' });
|
|
81
|
+
assert.deepEqual(result.additionalContext, ['plain prompt context']);
|
|
82
|
+
} finally {
|
|
83
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
84
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('legacy hook rule ask requests approval instead of allowing silently', async () => {
|
|
89
|
+
const root = tempRoot();
|
|
90
|
+
const rulesFile = join(root, 'hooks.json');
|
|
91
|
+
writeJson(rulesFile, {
|
|
92
|
+
toolBefore: [{
|
|
93
|
+
tool: 'shell',
|
|
94
|
+
action: 'ask',
|
|
95
|
+
reason: 'legacy rule approval',
|
|
96
|
+
enabled: true,
|
|
97
|
+
}],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const bus = createStandaloneHookBus({ dataDir: root });
|
|
101
|
+
const asked = await bus.beforeTool({ sessionId: 'sess_test', cwd: root, name: 'shell', args: { command: 'echo ok' } });
|
|
102
|
+
assert.equal(asked.action, 'ask');
|
|
103
|
+
assert.match(asked.reason, /legacy rule approval/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('hook config ignores Claude settings paths and reads project .mixdog hooks', async () => {
|
|
107
|
+
const root = tempRoot();
|
|
108
|
+
const claudeDir = join(root, '.claude');
|
|
109
|
+
const mixdogDir = join(root, '.mixdog');
|
|
110
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
111
|
+
mkdirSync(mixdogDir, { recursive: true });
|
|
112
|
+
|
|
113
|
+
const denyScript = join(root, 'deny.mjs');
|
|
114
|
+
writeFileSync(denyScript, `
|
|
115
|
+
console.log(JSON.stringify({ hookSpecificOutput: {
|
|
116
|
+
hookEventName: 'PreToolUse',
|
|
117
|
+
permissionDecision: 'deny',
|
|
118
|
+
permissionDecisionReason: 'mixdog hook only'
|
|
119
|
+
}}));
|
|
120
|
+
`, 'utf8');
|
|
121
|
+
|
|
122
|
+
writeJson(join(claudeDir, 'settings.json'), {
|
|
123
|
+
hooks: {
|
|
124
|
+
PreToolUse: [{
|
|
125
|
+
matcher: 'shell',
|
|
126
|
+
hooks: [{ type: 'command', command: process.execPath, args: [denyScript] }],
|
|
127
|
+
}],
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const busWithoutMixdog = createStandaloneHookBus({ dataDir: join(root, 'data-empty') });
|
|
132
|
+
const ignored = await busWithoutMixdog.beforeTool({ sessionId: 'sess_test', cwd: root, name: 'shell', args: { command: 'echo ok' } });
|
|
133
|
+
assert.equal(ignored, null);
|
|
134
|
+
assert.equal(busWithoutMixdog.status().configSources.some((p) => p.includes(`${join(root, '.claude')}`)), false);
|
|
135
|
+
|
|
136
|
+
writeJson(join(mixdogDir, 'hooks.json'), {
|
|
137
|
+
hooks: {
|
|
138
|
+
PreToolUse: [{
|
|
139
|
+
matcher: 'shell',
|
|
140
|
+
hooks: [{ type: 'command', command: process.execPath, args: [denyScript] }],
|
|
141
|
+
}],
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const busWithMixdog = createStandaloneHookBus({ dataDir: join(root, 'data-mixdog') });
|
|
146
|
+
const denied = await busWithMixdog.beforeTool({ sessionId: 'sess_test', cwd: root, name: 'shell', args: { command: 'echo ok' } });
|
|
147
|
+
assert.equal(denied.action, 'deny');
|
|
148
|
+
assert.match(denied.reason, /mixdog hook only/);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('registered plugin hooks read from plugin root with plugin env aliases', async () => {
|
|
152
|
+
const root = tempRoot();
|
|
153
|
+
const dataDir = join(root, 'data');
|
|
154
|
+
const pluginRoot = join(root, 'plugin-a');
|
|
155
|
+
const hooksDir = join(pluginRoot, 'hooks');
|
|
156
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
157
|
+
mkdirSync(join(dataDir, 'plugins'), { recursive: true });
|
|
158
|
+
|
|
159
|
+
const envScript = join(pluginRoot, 'env-check.mjs');
|
|
160
|
+
writeFileSync(envScript, `
|
|
161
|
+
import { readFileSync } from 'node:fs';
|
|
162
|
+
JSON.parse(readFileSync(0, 'utf8'));
|
|
163
|
+
if (process.env.CLAUDE_PLUGIN_ROOT === ${JSON.stringify(pluginRoot)}
|
|
164
|
+
&& process.env.MIXDOG_PLUGIN_ROOT === ${JSON.stringify(pluginRoot)}
|
|
165
|
+
&& /plugin-a/.test(process.env.CLAUDE_PLUGIN_DATA || '')
|
|
166
|
+
&& /plugin-a/.test(process.env.MIXDOG_PLUGIN_DATA || '')) {
|
|
167
|
+
console.log(JSON.stringify({ hookSpecificOutput: {
|
|
168
|
+
hookEventName: 'PreToolUse',
|
|
169
|
+
permissionDecision: 'deny',
|
|
170
|
+
permissionDecisionReason: 'plugin root env ok'
|
|
171
|
+
}}));
|
|
172
|
+
}
|
|
173
|
+
`, 'utf8');
|
|
174
|
+
|
|
175
|
+
writeJson(join(dataDir, 'plugins', 'registry.json'), {
|
|
176
|
+
version: 1,
|
|
177
|
+
plugins: [{
|
|
178
|
+
id: 'plugin-a',
|
|
179
|
+
source: pluginRoot,
|
|
180
|
+
sourceType: 'local',
|
|
181
|
+
root: pluginRoot,
|
|
182
|
+
name: 'plugin-a',
|
|
183
|
+
managed: false,
|
|
184
|
+
}],
|
|
185
|
+
});
|
|
186
|
+
writeJson(join(hooksDir, 'hooks.json'), {
|
|
187
|
+
hooks: {
|
|
188
|
+
PreToolUse: [{
|
|
189
|
+
matcher: 'shell',
|
|
190
|
+
hooks: [{ type: 'command', command: process.execPath, args: ['${CLAUDE_PLUGIN_ROOT}/env-check.mjs'] }],
|
|
191
|
+
}],
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const bus = createStandaloneHookBus({ dataDir });
|
|
196
|
+
const denied = await bus.beforeTool({ sessionId: 'sess_test', cwd: root, name: 'shell', args: { command: 'echo ok' } });
|
|
197
|
+
assert.equal(denied.action, 'deny');
|
|
198
|
+
assert.match(denied.reason, /plugin root env ok/);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('standard PreToolUse ask requests approval instead of allowing silently', async () => {
|
|
202
|
+
const root = tempRoot();
|
|
203
|
+
const hookScript = join(root, 'ask.mjs');
|
|
204
|
+
writeFileSync(hookScript, `
|
|
205
|
+
console.log(JSON.stringify({ hookSpecificOutput: {
|
|
206
|
+
hookEventName: 'PreToolUse',
|
|
207
|
+
permissionDecision: 'ask',
|
|
208
|
+
permissionDecisionReason: 'needs human approval'
|
|
209
|
+
}}));
|
|
210
|
+
`, 'utf8');
|
|
211
|
+
const hooksFile = join(root, 'hooks.json');
|
|
212
|
+
writeJson(hooksFile, {
|
|
213
|
+
hooks: {
|
|
214
|
+
PreToolUse: [{
|
|
215
|
+
matcher: 'shell',
|
|
216
|
+
hooks: [{ type: 'command', command: process.execPath, args: [hookScript] }],
|
|
217
|
+
}],
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
222
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
223
|
+
try {
|
|
224
|
+
const bus = createStandaloneHookBus({ dataDir: root });
|
|
225
|
+
const asked = await bus.beforeTool({ sessionId: 'sess_test', cwd: root, name: 'shell', args: { command: 'echo ok' } });
|
|
226
|
+
assert.equal(asked.action, 'ask');
|
|
227
|
+
assert.match(asked.reason, /human approval/);
|
|
228
|
+
} finally {
|
|
229
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
230
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('async command hook reports spawn errors without crashing', async () => {
|
|
235
|
+
const root = tempRoot();
|
|
236
|
+
const missingCommand = join(root, 'missing-async-hook-binary');
|
|
237
|
+
const hooksFile = join(root, 'hooks.json');
|
|
238
|
+
writeJson(hooksFile, {
|
|
239
|
+
hooks: {
|
|
240
|
+
Stop: [{
|
|
241
|
+
hooks: [{
|
|
242
|
+
type: 'command',
|
|
243
|
+
command: missingCommand,
|
|
244
|
+
args: [],
|
|
245
|
+
async: true,
|
|
246
|
+
}],
|
|
247
|
+
}],
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
252
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
253
|
+
try {
|
|
254
|
+
const bus = createStandaloneHookBus({ dataDir: root });
|
|
255
|
+
await bus.dispatch('Stop', { session_id: 'sess_test', cwd: root });
|
|
256
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
257
|
+
const status = bus.status();
|
|
258
|
+
const spawnError = status.recent.find((entry) => entry.name === 'hook:error'
|
|
259
|
+
&& String(entry.payload?.error || '').includes('hook spawn failed'));
|
|
260
|
+
assert.ok(spawnError, 'expected async spawn failure to emit hook:error');
|
|
261
|
+
} finally {
|
|
262
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
263
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('PostToolUse dispatch omits updatedToolOutput when hook does not override', async () => {
|
|
268
|
+
const root = tempRoot();
|
|
269
|
+
const hookScript = join(root, 'posttool-noop.mjs');
|
|
270
|
+
writeFileSync(hookScript, `console.log('{}');\n`, 'utf8');
|
|
271
|
+
const hooksFile = join(root, 'hooks.json');
|
|
272
|
+
writeJson(hooksFile, {
|
|
273
|
+
hooks: {
|
|
274
|
+
PostToolUse: [{
|
|
275
|
+
hooks: [{ type: 'command', command: process.execPath, args: [hookScript] }],
|
|
276
|
+
}],
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
281
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
282
|
+
try {
|
|
283
|
+
const bus = createStandaloneHookBus({ dataDir: root });
|
|
284
|
+
const result = await bus.dispatch('PostToolUse', {
|
|
285
|
+
session_id: 'sess_test',
|
|
286
|
+
cwd: root,
|
|
287
|
+
tool_name: 'list',
|
|
288
|
+
tool_response: 'entries',
|
|
289
|
+
});
|
|
290
|
+
assert.equal('updatedToolOutput' in result, false);
|
|
291
|
+
} finally {
|
|
292
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
293
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('PostToolUse dispatch preserves empty-string updatedToolOutput override', async () => {
|
|
298
|
+
const root = tempRoot();
|
|
299
|
+
const hookScript = join(root, 'posttool-clear.mjs');
|
|
300
|
+
writeFileSync(hookScript, `
|
|
301
|
+
console.log(JSON.stringify({ hookSpecificOutput: {
|
|
302
|
+
hookEventName: 'PostToolUse',
|
|
303
|
+
updatedToolOutput: ''
|
|
304
|
+
}}));
|
|
305
|
+
`, 'utf8');
|
|
306
|
+
const hooksFile = join(root, 'hooks.json');
|
|
307
|
+
writeJson(hooksFile, {
|
|
308
|
+
hooks: {
|
|
309
|
+
PostToolUse: [{
|
|
310
|
+
hooks: [{ type: 'command', command: process.execPath, args: [hookScript] }],
|
|
311
|
+
}],
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
316
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
317
|
+
try {
|
|
318
|
+
const bus = createStandaloneHookBus({ dataDir: root });
|
|
319
|
+
const result = await bus.dispatch('PostToolUse', {
|
|
320
|
+
session_id: 'sess_test',
|
|
321
|
+
cwd: root,
|
|
322
|
+
tool_name: 'list',
|
|
323
|
+
tool_response: 'entries',
|
|
324
|
+
});
|
|
325
|
+
assert.equal(result.updatedToolOutput, '');
|
|
326
|
+
} finally {
|
|
327
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
328
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
@@ -9,12 +9,6 @@ const args = parseArgs(process.argv.slice(2));
|
|
|
9
9
|
const timeoutMs = Number(args.get('timeout-ms') || 480000);
|
|
10
10
|
const keep = args.get('keep') === true;
|
|
11
11
|
const selectedScenario = String(args.get('scenario') || 'explicit-parallel');
|
|
12
|
-
const RETRIEVAL_SCENARIOS = new Set([
|
|
13
|
-
'broad-locator-explore',
|
|
14
|
-
'known-symbol-direct',
|
|
15
|
-
'memory-recall',
|
|
16
|
-
'web-search',
|
|
17
|
-
]);
|
|
18
12
|
|
|
19
13
|
const SCENARIOS = {
|
|
20
14
|
'explicit-parallel': {
|
|
@@ -28,7 +22,7 @@ ${projectDir}
|
|
|
28
22
|
|
|
29
23
|
Goal: validate how the lead plans and batches independent work. Do not solve everything directly in the lead.
|
|
30
24
|
|
|
31
|
-
Please use the
|
|
25
|
+
Please use the agent tool with async workers for the independent sections below. Spawn distinct tags early so the work can overlap, then wait for completion notifications and make a concise final report.
|
|
32
26
|
|
|
33
27
|
Independent sections:
|
|
34
28
|
1. worker: update src/math.mjs to export subtract(a, b).
|
|
@@ -37,20 +31,20 @@ Independent sections:
|
|
|
37
31
|
|
|
38
32
|
Use minimal exploration. Prefer grep/code_graph/apply_patch over broad reading. Keep each worker brief: maxLoopIterations 4 and idleTimeoutMs 120000 are enough.
|
|
39
33
|
After worker results return, run npm run smoke in ${projectDir} if needed, then summarize:
|
|
40
|
-
- whether
|
|
41
|
-
- which
|
|
34
|
+
- whether agent workers were used
|
|
35
|
+
- which agents/tags ran
|
|
42
36
|
- whether the smoke passed
|
|
43
37
|
`.trim(),
|
|
44
38
|
validate: validateMultiFileProject,
|
|
45
39
|
expect: {
|
|
46
40
|
minSpawns: 3,
|
|
47
|
-
|
|
41
|
+
minFirstIterAgentCalls: 2,
|
|
48
42
|
requiredRoles: ['worker', 'debugger', 'reviewer'],
|
|
49
43
|
requireFilesOk: true,
|
|
50
44
|
},
|
|
51
45
|
},
|
|
52
46
|
'natural-multifile': {
|
|
53
|
-
description: 'Natural multi-file task with no hard
|
|
47
|
+
description: 'Natural multi-file task with no hard agent command; should still delegate useful independent work.',
|
|
54
48
|
setup: setupMultiFileProject,
|
|
55
49
|
prompt: (projectDir) => `
|
|
56
50
|
Work in this temporary project only:
|
|
@@ -68,7 +62,7 @@ Keep exploration tight and verify with npm run smoke.
|
|
|
68
62
|
validate: validateMultiFileProject,
|
|
69
63
|
expect: {
|
|
70
64
|
minSpawns: 2,
|
|
71
|
-
|
|
65
|
+
minFirstIterAgentCalls: 1,
|
|
72
66
|
minPreEditImplementationSpawns: 1,
|
|
73
67
|
requiredAnyRoles: ['worker', 'debugger', 'reviewer'],
|
|
74
68
|
requireFilesOk: true,
|
|
@@ -315,7 +309,7 @@ function compact(value, max = 700) {
|
|
|
315
309
|
return raw.length > max ? `${raw.slice(0, max)}...` : raw;
|
|
316
310
|
}
|
|
317
311
|
|
|
318
|
-
function
|
|
312
|
+
function agentSummary(call) {
|
|
319
313
|
const input = call?.input || call?.arguments || call?.args || {};
|
|
320
314
|
return {
|
|
321
315
|
id: call?.id || call?.callId || null,
|
|
@@ -352,24 +346,24 @@ function evaluate(summary, scenario) {
|
|
|
352
346
|
if (Number.isFinite(expect.maxMutations) && summary.tools.mutations > expect.maxMutations) {
|
|
353
347
|
failures.push(`expected mutations <= ${expect.maxMutations}, got ${summary.tools.mutations}`);
|
|
354
348
|
}
|
|
355
|
-
if (Number.isFinite(expect.minSpawns) && summary.
|
|
356
|
-
failures.push(`expected at least ${expect.minSpawns}
|
|
349
|
+
if (Number.isFinite(expect.minSpawns) && summary.agent.spawns < expect.minSpawns) {
|
|
350
|
+
failures.push(`expected at least ${expect.minSpawns} agent spawns, got ${summary.agent.spawns}`);
|
|
357
351
|
}
|
|
358
|
-
if (Number.isFinite(expect.maxSpawns) && summary.
|
|
359
|
-
failures.push(`expected at most ${expect.maxSpawns}
|
|
352
|
+
if (Number.isFinite(expect.maxSpawns) && summary.agent.spawns > expect.maxSpawns) {
|
|
353
|
+
failures.push(`expected at most ${expect.maxSpawns} agent spawns, got ${summary.agent.spawns}`);
|
|
360
354
|
}
|
|
361
|
-
if (Number.isFinite(expect.
|
|
362
|
-
failures.push(`expected at least ${expect.
|
|
355
|
+
if (Number.isFinite(expect.minFirstIterAgentCalls) && summary.agent.firstIterAgentCount < expect.minFirstIterAgentCalls) {
|
|
356
|
+
failures.push(`expected at least ${expect.minFirstIterAgentCalls} agent calls in first agent iteration, got ${summary.agent.firstIterAgentCount}`);
|
|
363
357
|
}
|
|
364
|
-
if (Number.isFinite(expect.minPreEditImplementationSpawns) && summary.
|
|
365
|
-
failures.push(`expected at least ${expect.minPreEditImplementationSpawns} pre-edit implementation/debug
|
|
358
|
+
if (Number.isFinite(expect.minPreEditImplementationSpawns) && summary.agent.preEditImplementationSpawns < expect.minPreEditImplementationSpawns) {
|
|
359
|
+
failures.push(`expected at least ${expect.minPreEditImplementationSpawns} pre-edit implementation/debug agent spawns, got ${summary.agent.preEditImplementationSpawns}`);
|
|
366
360
|
}
|
|
367
361
|
for (const role of expect.requiredRoles || []) {
|
|
368
|
-
if (!summary.
|
|
362
|
+
if (!summary.agent.roles.includes(role)) failures.push(`missing agent role ${role}`);
|
|
369
363
|
}
|
|
370
364
|
if (Array.isArray(expect.requiredAnyRoles) && expect.requiredAnyRoles.length > 0) {
|
|
371
|
-
const hasAny = expect.requiredAnyRoles.some((role) => summary.
|
|
372
|
-
if (!hasAny) failures.push(`missing any
|
|
365
|
+
const hasAny = expect.requiredAnyRoles.some((role) => summary.agent.roles.includes(role));
|
|
366
|
+
if (!hasAny) failures.push(`missing any agent role from ${expect.requiredAnyRoles.join(', ')}`);
|
|
373
367
|
}
|
|
374
368
|
if (expect.requireFilesOk && !Object.values(summary.files || {}).every(Boolean)) {
|
|
375
369
|
failures.push(`file validation failed: ${JSON.stringify(summary.files)}`);
|
|
@@ -454,7 +448,7 @@ async function runScenario(name) {
|
|
|
454
448
|
const { result } = await runtime.ask(scenario.prompt(projectDir), {
|
|
455
449
|
onToolCall(iter, calls) {
|
|
456
450
|
for (const call of calls || []) {
|
|
457
|
-
const summary =
|
|
451
|
+
const summary = agentSummary(call);
|
|
458
452
|
toolCalls.push({ iter, ...summary });
|
|
459
453
|
record('toolCall', { iter, ...summary });
|
|
460
454
|
}
|
|
@@ -484,13 +478,13 @@ async function runScenario(name) {
|
|
|
484
478
|
}
|
|
485
479
|
|
|
486
480
|
const elapsedMs = Math.round(performance.now() - startedAt);
|
|
487
|
-
const
|
|
481
|
+
const agentCalls = toolCalls.filter((call) => call.name === 'agent');
|
|
488
482
|
const toolCounts = {};
|
|
489
483
|
for (const call of toolCalls) {
|
|
490
484
|
toolCounts[call.name] = (toolCounts[call.name] || 0) + 1;
|
|
491
485
|
}
|
|
492
486
|
const mutationCount = toolCalls.filter((call) => call.name === 'apply_patch').length;
|
|
493
|
-
const spawnCalls =
|
|
487
|
+
const spawnCalls = agentCalls.filter((call) => call.type === 'spawn' || call.type === null);
|
|
494
488
|
const mutationIters = toolCalls
|
|
495
489
|
.filter((call) => call.name === 'apply_patch')
|
|
496
490
|
.map((call) => Number(call.iter))
|
|
@@ -505,11 +499,11 @@ async function runScenario(name) {
|
|
|
505
499
|
const preEditImplementationSpawns = firstMutationIter == null
|
|
506
500
|
? implementationSpawnCalls.length
|
|
507
501
|
: implementationSpawnCalls.filter((call) => Number(call.iter) < firstMutationIter).length;
|
|
508
|
-
const distinctRoles = [...new Set(
|
|
509
|
-
const distinctTags = [...new Set(
|
|
510
|
-
const
|
|
511
|
-
const
|
|
512
|
-
?
|
|
502
|
+
const distinctRoles = [...new Set(agentCalls.map((call) => call.role).filter(Boolean))];
|
|
503
|
+
const distinctTags = [...new Set(agentCalls.map((call) => call.tag).filter(Boolean))];
|
|
504
|
+
const firstAgentIter = Math.min(...agentCalls.map((call) => Number(call.iter)).filter(Number.isFinite));
|
|
505
|
+
const firstIterAgentCount = Number.isFinite(firstAgentIter)
|
|
506
|
+
? agentCalls.filter((call) => Number(call.iter) === firstAgentIter).length
|
|
513
507
|
: 0;
|
|
514
508
|
|
|
515
509
|
const summary = {
|
|
@@ -522,11 +516,11 @@ async function runScenario(name) {
|
|
|
522
516
|
projectDir,
|
|
523
517
|
route: events.find((e) => e.kind === 'runtime')?.payload || null,
|
|
524
518
|
prepareResults,
|
|
525
|
-
|
|
526
|
-
calls:
|
|
519
|
+
agent: {
|
|
520
|
+
calls: agentCalls.length,
|
|
527
521
|
spawns: spawnCalls.length,
|
|
528
|
-
|
|
529
|
-
|
|
522
|
+
firstAgentIter: Number.isFinite(firstAgentIter) ? firstAgentIter : null,
|
|
523
|
+
firstIterAgentCount,
|
|
530
524
|
firstMutationIter,
|
|
531
525
|
implementationSpawns: implementationSpawnCalls.length,
|
|
532
526
|
preEditImplementationSpawns,
|
|
@@ -578,9 +572,9 @@ async function main() {
|
|
|
578
572
|
scenario: summary.scenario,
|
|
579
573
|
passed: summary.passed,
|
|
580
574
|
elapsedMs: summary.elapsedMs,
|
|
581
|
-
spawns: summary.
|
|
582
|
-
|
|
583
|
-
roles: summary.
|
|
575
|
+
spawns: summary.agent.spawns,
|
|
576
|
+
firstIterAgentCount: summary.agent.firstIterAgentCount,
|
|
577
|
+
roles: summary.agent.roles,
|
|
584
578
|
failures: summary.failures,
|
|
585
579
|
})),
|
|
586
580
|
}, null, 2));
|