mixdog 0.9.1 → 0.9.3
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/package.json +9 -1
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/build-tui.mjs +13 -1
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +310 -67
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +20 -9
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +17 -9
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +1040 -2036
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +17 -7
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +8 -12
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +13 -2
- package/src/rules/shared/01-tool.md +23 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
- package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +437 -1439
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +280 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +465 -345
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +121 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/recall-format.mjs +183 -0
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +10 -7
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +156 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/session-runtime/config-helpers.mjs +209 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/output-styles.mjs +124 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool.mjs +302 -117
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +10 -292
- package/src/standalone/explore-tool.mjs +12 -5
- package/src/standalone/hook-bus.mjs +165 -8
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/opencode-go-login.mjs +121 -0
- package/src/standalone/provider-admin.mjs +25 -3
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2883 -778
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +355 -22
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +183 -101
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +161 -23
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6731 -2333
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +569 -948
- package/src/tui/index.jsx +117 -7
- package/src/tui/input-editing.mjs +58 -8
- package/src/tui/input-editing.selection.test.mjs +75 -0
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +469 -0
- package/src/tui/markdown/format-token.mjs +128 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +36 -9
- package/src/tui/paste-fix.test.mjs +119 -0
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -657
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +80 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +75 -27
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +46 -12
- package/src/workflows/sequential/WORKFLOW.md +51 -0
- package/src/workflows/solo/WORKFLOW.md +12 -1
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// internal-comms-bench.mjs — live A/B token measurement for internal-comms rules.
|
|
3
|
+
//
|
|
4
|
+
// Variant A (verbose): prior committed blobs via `git show HEAD:src/<rule>`.
|
|
5
|
+
// Variant B (optimized): current on-disk src/ copied into a temp PLUGIN_ROOT.
|
|
6
|
+
// Only the listed rule files differ between variants; everything else matches.
|
|
7
|
+
//
|
|
8
|
+
// node scripts/internal-comms-bench.mjs
|
|
9
|
+
// node scripts/internal-comms-bench.mjs --run [--model grok] [--provider P] [--json]
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
11
|
+
import {
|
|
12
|
+
copyFileSync,
|
|
13
|
+
cpSync,
|
|
14
|
+
existsSync,
|
|
15
|
+
mkdirSync,
|
|
16
|
+
mkdtempSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
readdirSync,
|
|
19
|
+
rmSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
} from 'node:fs';
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
import { dirname, join, resolve } from 'node:path';
|
|
24
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
25
|
+
|
|
26
|
+
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const REPO_ROOT = resolve(__dir, '..');
|
|
28
|
+
const PLUGIN_ROOT = join(REPO_ROOT, 'src');
|
|
29
|
+
const HEADLESS = pathToFileURL(resolve(__dir, '../src/headless-role.mjs')).href;
|
|
30
|
+
|
|
31
|
+
const RULE_FILES = [
|
|
32
|
+
'rules/agent/00-common.md',
|
|
33
|
+
'agents/worker/AGENT.md',
|
|
34
|
+
'agents/heavy-worker/AGENT.md',
|
|
35
|
+
'agents/reviewer/AGENT.md',
|
|
36
|
+
'agents/debugger/AGENT.md',
|
|
37
|
+
'workflows/default/WORKFLOW.md',
|
|
38
|
+
'rules/lead/lead-tool.md',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const DEFAULT_WORKER_PROMPT =
|
|
42
|
+
'In math.js add an exported function add(a, b) that returns a + b. Keep the existing mul export unchanged. Use apply_patch only. Stop when the function exists; hand off with outcome and file:line.';
|
|
43
|
+
|
|
44
|
+
// Lead-mode task: FORCES delegation so the real internal-comms flow runs
|
|
45
|
+
// (Lead writes a brief -> worker returns a handoff -> reviewer verifies).
|
|
46
|
+
// Identical across variants A/B; only the on-disk rule files differ.
|
|
47
|
+
const DEFAULT_LEAD_PROMPT = [
|
|
48
|
+
'You are the Lead in an automation benchmark. A file named math.js already exists in your working directory.',
|
|
49
|
+
'Do NOT edit any file yourself and do NOT call apply_patch yourself. You MUST delegate every implementation step.',
|
|
50
|
+
'Step 1: call the agent tool with agent "worker" and give it a brief to add an exported function add(a, b) that returns a + b to math.js, keeping the existing mul export unchanged, using apply_patch only.',
|
|
51
|
+
'Step 2: after the worker hands off, call the agent tool with agent "reviewer" and give it a brief to verify that math.js exports both add and mul.',
|
|
52
|
+
'Step 3: stop and reply with a one-line outcome plus math.js:line. Do exactly these three steps and nothing else.',
|
|
53
|
+
].join(' ');
|
|
54
|
+
|
|
55
|
+
const MODEL_ALIASES = {
|
|
56
|
+
opus: { provider: 'anthropic-oauth', model: 'claude-opus-4-8' },
|
|
57
|
+
sonnet: { provider: 'anthropic-oauth', model: 'claude-sonnet-5' },
|
|
58
|
+
gpt: { provider: 'openai-oauth', model: 'gpt-5.5' },
|
|
59
|
+
'gpt-5.5': { provider: 'openai-oauth', model: 'gpt-5.5' },
|
|
60
|
+
grok: { provider: 'grok-oauth', model: 'grok-composer-2.5-fast' },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const AUTH_ARTIFACT_BY_PROVIDER = {
|
|
64
|
+
'grok-oauth': ['grok-oauth.json', 'grok-oauth-models.json'],
|
|
65
|
+
'anthropic-oauth': ['anthropic-oauth-credentials.json', 'anthropic-oauth-models.json'],
|
|
66
|
+
'openai-oauth': ['openai-oauth.json', 'openai-oauth-models.json'],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const INITIAL_MATH_JS = `export function mul(a, b) {
|
|
70
|
+
return a * b;
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
function argValue(name, fallback = null) {
|
|
75
|
+
const idx = process.argv.indexOf(name);
|
|
76
|
+
if (idx >= 0 && idx + 1 < process.argv.length) return process.argv[idx + 1];
|
|
77
|
+
const pref = `${name}=`;
|
|
78
|
+
const hit = process.argv.find((a) => a.startsWith(pref));
|
|
79
|
+
return hit ? hit.slice(pref.length) : fallback;
|
|
80
|
+
}
|
|
81
|
+
function hasFlag(name) { return process.argv.includes(name); }
|
|
82
|
+
|
|
83
|
+
function resolveModelOpts(modelArg, providerArg) {
|
|
84
|
+
const key = String(modelArg || '').trim().toLowerCase();
|
|
85
|
+
if (MODEL_ALIASES[key] && !providerArg) return { ...MODEL_ALIASES[key] };
|
|
86
|
+
return { provider: providerArg || null, model: modelArg || null };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function defaultUserDataDir() {
|
|
90
|
+
return process.env.MIXDOG_DATA_DIR || join(process.env.MIXDOG_HOME || join(homedir(), '.mixdog'), 'data');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readUnifiedConfig(dataDir) {
|
|
94
|
+
try {
|
|
95
|
+
const unified = JSON.parse(readFileSync(join(dataDir, 'mixdog-config.json'), 'utf8'));
|
|
96
|
+
return unified && typeof unified === 'object' ? unified : {};
|
|
97
|
+
} catch { return {}; }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function gitPathForRule(relFromSrc) {
|
|
101
|
+
return `src/${String(relFromSrc).replace(/\\/g, '/')}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readPriorRuleBlob(relFromSrc) {
|
|
105
|
+
const gitPath = gitPathForRule(relFromSrc);
|
|
106
|
+
try {
|
|
107
|
+
return execFileSync('git', ['show', `HEAD:${gitPath}`], {
|
|
108
|
+
cwd: REPO_ROOT,
|
|
109
|
+
encoding: 'utf8',
|
|
110
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
111
|
+
});
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readCurrentRule(relFromSrc) {
|
|
118
|
+
return readFileSync(join(PLUGIN_ROOT, relFromSrc), 'utf8');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function ruleVariantBytes() {
|
|
122
|
+
const rows = [];
|
|
123
|
+
for (const rel of RULE_FILES) {
|
|
124
|
+
const prior = readPriorRuleBlob(rel);
|
|
125
|
+
const current = readCurrentRule(rel);
|
|
126
|
+
const aText = prior != null ? prior : current;
|
|
127
|
+
rows.push({
|
|
128
|
+
file: rel,
|
|
129
|
+
a_chars: aText.length,
|
|
130
|
+
b_chars: current.length,
|
|
131
|
+
prior_from_git: prior != null,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return rows;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function authArtifactNamesForSandbox(realDataDir, provider) {
|
|
138
|
+
const names = new Set();
|
|
139
|
+
for (const file of AUTH_ARTIFACT_BY_PROVIDER[provider] || []) names.add(file);
|
|
140
|
+
try {
|
|
141
|
+
for (const entry of readdirSync(realDataDir, { withFileTypes: true })) {
|
|
142
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
|
|
143
|
+
if (/oauth/i.test(entry.name) || /credentials/i.test(entry.name)) names.add(entry.name);
|
|
144
|
+
}
|
|
145
|
+
} catch { /* missing real data dir */ }
|
|
146
|
+
return [...names];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function copyAuthArtifacts(realDataDir, sandboxDataDir, provider) {
|
|
150
|
+
const copied = [];
|
|
151
|
+
const skipped = [];
|
|
152
|
+
for (const name of authArtifactNamesForSandbox(realDataDir, provider)) {
|
|
153
|
+
const src = join(realDataDir, name);
|
|
154
|
+
const dest = join(sandboxDataDir, name);
|
|
155
|
+
if (!existsSync(src)) {
|
|
156
|
+
skipped.push(name);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
copyFileSync(src, dest);
|
|
161
|
+
copied.push(name);
|
|
162
|
+
} catch {
|
|
163
|
+
skipped.push(name);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { copied, skipped };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function materializePluginRoot(variant, sandboxRoot) {
|
|
170
|
+
const pluginRoot = join(sandboxRoot, `plugin-${variant}`);
|
|
171
|
+
cpSync(PLUGIN_ROOT, pluginRoot, { recursive: true });
|
|
172
|
+
if (variant === 'A') {
|
|
173
|
+
for (const rel of RULE_FILES) {
|
|
174
|
+
const prior = readPriorRuleBlob(rel);
|
|
175
|
+
if (prior != null) writeFileSync(join(pluginRoot, rel), prior, 'utf8');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return pluginRoot;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function prepareDataDir(sandboxRoot, variant, realDataDir, userUnified, provider) {
|
|
182
|
+
const dataDir = join(sandboxRoot, `data-${variant}`);
|
|
183
|
+
mkdirSync(join(dataDir, 'history'), { recursive: true });
|
|
184
|
+
writeFileSync(join(dataDir, 'mixdog-config.json'), JSON.stringify(userUnified, null, 2));
|
|
185
|
+
copyAuthArtifacts(realDataDir, dataDir, provider);
|
|
186
|
+
return dataDir;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function resetTaskCwd(taskCwd) {
|
|
190
|
+
writeFileSync(join(taskCwd, 'math.js'), INITIAL_MATH_JS, 'utf8');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function prepareTaskCwd(parentDir) {
|
|
194
|
+
const taskCwd = mkdtempSync(join(parentDir, 'task-'));
|
|
195
|
+
resetTaskCwd(taskCwd);
|
|
196
|
+
return taskCwd;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function extractSessionId(text) {
|
|
200
|
+
const s = String(text || '');
|
|
201
|
+
const m = s.match(/sessionId:\s*(sess_[A-Za-z0-9_]+)/) || s.match(/\b(sess_[A-Za-z0-9_]+)/);
|
|
202
|
+
return m ? m[1] : null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function runHeadlessWorker({ pluginRoot, dataDir, taskCwd, prompt, provider, model, effort, fast }) {
|
|
206
|
+
const driver = [
|
|
207
|
+
`import { runHeadlessRole } from ${JSON.stringify(HEADLESS)};`,
|
|
208
|
+
`const out = [];`,
|
|
209
|
+
`const code = await runHeadlessRole({`,
|
|
210
|
+
` agent: 'worker',`,
|
|
211
|
+
` message: ${JSON.stringify(prompt)},`,
|
|
212
|
+
` provider: ${JSON.stringify(provider || null)},`,
|
|
213
|
+
` model: ${JSON.stringify(model || null)},`,
|
|
214
|
+
` cwd: ${JSON.stringify(taskCwd)},`,
|
|
215
|
+
` write: (t) => out.push(t),`,
|
|
216
|
+
` writeErr: (t) => process.stderr.write(t),`,
|
|
217
|
+
`});`,
|
|
218
|
+
`process.stdout.write(out.join(''));`,
|
|
219
|
+
`process.exit(code);`,
|
|
220
|
+
].join('\n');
|
|
221
|
+
const started = Date.now();
|
|
222
|
+
let raw = '';
|
|
223
|
+
let ok = false;
|
|
224
|
+
try {
|
|
225
|
+
raw = execFileSync('node', ['--input-type=module', '-e', driver], {
|
|
226
|
+
encoding: 'utf8',
|
|
227
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
228
|
+
env: {
|
|
229
|
+
...process.env,
|
|
230
|
+
MIXDOG_ROOT: pluginRoot,
|
|
231
|
+
MIXDOG_DATA_DIR: dataDir,
|
|
232
|
+
...(effort ? { MIXDOG_AGENT_EFFORT: effort } : {}),
|
|
233
|
+
...(fast ? { MIXDOG_AGENT_FAST: '1' } : {}),
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
ok = true;
|
|
237
|
+
} catch (e) {
|
|
238
|
+
raw = String(e.stdout || '') + String(e.stderr || '');
|
|
239
|
+
ok = false;
|
|
240
|
+
}
|
|
241
|
+
return { sessionId: extractSessionId(raw), ok, ms: Date.now() - started, raw };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function readRows(path) {
|
|
245
|
+
if (!existsSync(path)) return [];
|
|
246
|
+
const rows = [];
|
|
247
|
+
for (const line of readFileSync(path, 'utf8').split(/\r?\n/)) {
|
|
248
|
+
if (!line) continue;
|
|
249
|
+
try {
|
|
250
|
+
rows.push(JSON.parse(line));
|
|
251
|
+
} catch { /* tail */ }
|
|
252
|
+
}
|
|
253
|
+
return rows;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function payload(row) {
|
|
257
|
+
return row?.payload && typeof row.payload === 'object' ? row.payload : {};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function field(row, name) {
|
|
261
|
+
if (row && row[name] != null) return row[name];
|
|
262
|
+
const p = payload(row);
|
|
263
|
+
return p[name] != null ? p[name] : null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function num(row, name) {
|
|
267
|
+
const n = Number(field(row, name));
|
|
268
|
+
return Number.isFinite(n) ? n : null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function sessionId(row) {
|
|
272
|
+
return String(row?.session_id || row?.sessionId || field(row, 'session_id') || '');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function sum(values) {
|
|
276
|
+
return values.reduce((s, v) => s + (Number.isFinite(Number(v)) ? Number(v) : 0), 0);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function groupBy(rows, keyFn) {
|
|
280
|
+
const map = new Map();
|
|
281
|
+
for (const row of rows) {
|
|
282
|
+
const key = keyFn(row);
|
|
283
|
+
if (!key) continue;
|
|
284
|
+
if (!map.has(key)) map.set(key, []);
|
|
285
|
+
map.get(key).push(row);
|
|
286
|
+
}
|
|
287
|
+
return map;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function inferSessionMeta(rows) {
|
|
291
|
+
const sorted = [...rows].sort((a, b) => Number(a.ts || 0) - Number(b.ts || 0));
|
|
292
|
+
const preset = sorted.find((r) => r.kind === 'preset_assign');
|
|
293
|
+
const usage = [...sorted].reverse().find((r) => r.kind === 'usage_raw' || r.kind === 'usage');
|
|
294
|
+
const tool = sorted.find((r) => r.kind === 'tool');
|
|
295
|
+
const last = sorted[sorted.length - 1] || {};
|
|
296
|
+
const tsValues = sorted.map((r) => Number(r.ts || 0)).filter((n) => n > 0);
|
|
297
|
+
return {
|
|
298
|
+
session_id: sessionId(last),
|
|
299
|
+
parent_session_id: field(preset, 'parent_session_id') || field(preset, 'parentSessionId') || null,
|
|
300
|
+
agent: field(preset, 'agent') || field(tool, 'agent') || field(usage, 'sourceName') || field(last, 'sourceName') || null,
|
|
301
|
+
min_ts: tsValues.length ? Math.min(...tsValues) : null,
|
|
302
|
+
max_ts: tsValues.length ? Math.max(...tsValues) : null,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function selectSessionFamily(sessionMetas, query) {
|
|
307
|
+
const selected = sessionMetas.find((m) => m.session_id === query)
|
|
308
|
+
|| sessionMetas.find((m) => m.session_id.startsWith(query));
|
|
309
|
+
if (!selected) return [];
|
|
310
|
+
const root = selected.parent_session_id
|
|
311
|
+
? sessionMetas.find((m) => m.session_id === selected.parent_session_id) || selected
|
|
312
|
+
: selected;
|
|
313
|
+
const ids = new Set([root.session_id, selected.session_id]);
|
|
314
|
+
for (const meta of sessionMetas) {
|
|
315
|
+
if (meta.parent_session_id === root.session_id) ids.add(meta.session_id);
|
|
316
|
+
}
|
|
317
|
+
return [...ids].filter(Boolean);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function sumUsageForSession(dataDir, rootSessionId) {
|
|
321
|
+
const tracePath = join(dataDir, 'history', 'agent-trace.jsonl');
|
|
322
|
+
const rows = readRows(tracePath);
|
|
323
|
+
const bySid = groupBy(rows, sessionId);
|
|
324
|
+
const sessionMetas = [...bySid.entries()].map(([, srows]) => inferSessionMeta(srows));
|
|
325
|
+
const family = selectSessionFamily(sessionMetas, rootSessionId);
|
|
326
|
+
const usageRows = rows.filter((r) => r.kind === 'usage_raw' && family.includes(sessionId(r)));
|
|
327
|
+
return {
|
|
328
|
+
tracePath,
|
|
329
|
+
session_ids: family,
|
|
330
|
+
prompt_tokens: sum(usageRows.map((r) => num(r, 'prompt_tokens'))),
|
|
331
|
+
output_tokens: sum(usageRows.map((r) => num(r, 'output_tokens'))),
|
|
332
|
+
cached_tokens: sum(usageRows.map((r) => num(r, 'cached_tokens'))),
|
|
333
|
+
usage_rows: usageRows.length,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ---- lead mode: multi-agent role-split token attribution -------------------
|
|
338
|
+
|
|
339
|
+
const ROLES = ['lead', 'worker', 'reviewer', 'other'];
|
|
340
|
+
|
|
341
|
+
function roleOf(agent) {
|
|
342
|
+
const a = String(agent || '').toLowerCase();
|
|
343
|
+
if (!a) return 'other';
|
|
344
|
+
if (a === 'lead' || a === 'main') return 'lead';
|
|
345
|
+
if (a.includes('review')) return 'reviewer'; // reviewer before worker (heavy-worker matches 'worker')
|
|
346
|
+
if (a.includes('worker')) return 'worker';
|
|
347
|
+
return 'other';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function emptyRoleBucket() {
|
|
351
|
+
return { prompt_tokens: 0, output_tokens: 0, cached_tokens: 0, usage_rows: 0, total_tokens: 0 };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Transitive closure over parent_session_id from the Lead root: collects the
|
|
355
|
+
// whole Lead+children session tree (worker/reviewer sessions link back via
|
|
356
|
+
// parent_session_id recorded on their preset_assign rows).
|
|
357
|
+
function collectSessionTree(sessionMetas, rootSessionId) {
|
|
358
|
+
const byParent = new Map();
|
|
359
|
+
for (const meta of sessionMetas) {
|
|
360
|
+
const p = meta.parent_session_id || null;
|
|
361
|
+
if (!byParent.has(p)) byParent.set(p, []);
|
|
362
|
+
byParent.get(p).push(meta);
|
|
363
|
+
}
|
|
364
|
+
const ids = new Set();
|
|
365
|
+
const queue = [rootSessionId];
|
|
366
|
+
while (queue.length) {
|
|
367
|
+
const id = queue.shift();
|
|
368
|
+
if (!id || ids.has(id)) continue;
|
|
369
|
+
ids.add(id);
|
|
370
|
+
for (const child of byParent.get(id) || []) queue.push(child.session_id);
|
|
371
|
+
}
|
|
372
|
+
return ids;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function emptyRoleSplit(dataDir) {
|
|
376
|
+
return {
|
|
377
|
+
tracePath: join(dataDir, 'history', 'agent-trace.jsonl'),
|
|
378
|
+
session_ids: [],
|
|
379
|
+
byRole: { lead: emptyRoleBucket(), worker: emptyRoleBucket(), reviewer: emptyRoleBucket(), other: emptyRoleBucket() },
|
|
380
|
+
total: { prompt_tokens: 0, output_tokens: 0, total_tokens: 0, usage_rows: 0 },
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function splitTokensByRole(dataDir, rootSessionId) {
|
|
385
|
+
const tracePath = join(dataDir, 'history', 'agent-trace.jsonl');
|
|
386
|
+
const rows = readRows(tracePath);
|
|
387
|
+
const bySid = groupBy(rows, sessionId);
|
|
388
|
+
const sessionMetas = [...bySid.entries()].map(([sid, srows]) => ({ ...inferSessionMeta(srows), session_id: sid }));
|
|
389
|
+
const treeIds = collectSessionTree(sessionMetas, rootSessionId);
|
|
390
|
+
const roleBySession = new Map();
|
|
391
|
+
for (const meta of sessionMetas) {
|
|
392
|
+
if (treeIds.has(meta.session_id)) roleBySession.set(meta.session_id, roleOf(meta.agent));
|
|
393
|
+
}
|
|
394
|
+
// The Lead root session is created via createSession() directly (no
|
|
395
|
+
// session-builder), so it emits no preset_assign row and inferSessionMeta
|
|
396
|
+
// falls back to sourceName ('internal-comms-bench-lead') which roleOf() maps
|
|
397
|
+
// to 'other'. Pin the root to 'lead' explicitly. Worker/reviewer children go
|
|
398
|
+
// through session-builder -> traceAgentPreset, so their agent is recorded.
|
|
399
|
+
if (treeIds.has(rootSessionId)) roleBySession.set(rootSessionId, 'lead');
|
|
400
|
+
const byRole = { lead: emptyRoleBucket(), worker: emptyRoleBucket(), reviewer: emptyRoleBucket(), other: emptyRoleBucket() };
|
|
401
|
+
const usageRows = rows.filter((r) => r.kind === 'usage_raw' && treeIds.has(sessionId(r)));
|
|
402
|
+
for (const r of usageRows) {
|
|
403
|
+
const role = roleBySession.get(sessionId(r)) || 'other';
|
|
404
|
+
const bucket = byRole[role] || byRole.other;
|
|
405
|
+
bucket.prompt_tokens += num(r, 'prompt_tokens') || 0;
|
|
406
|
+
bucket.output_tokens += num(r, 'output_tokens') || 0;
|
|
407
|
+
bucket.cached_tokens += num(r, 'cached_tokens') || 0;
|
|
408
|
+
bucket.usage_rows += 1;
|
|
409
|
+
}
|
|
410
|
+
const total = { prompt_tokens: 0, output_tokens: 0, total_tokens: 0, usage_rows: 0 };
|
|
411
|
+
for (const role of ROLES) {
|
|
412
|
+
byRole[role].total_tokens = byRole[role].prompt_tokens + byRole[role].output_tokens;
|
|
413
|
+
total.prompt_tokens += byRole[role].prompt_tokens;
|
|
414
|
+
total.output_tokens += byRole[role].output_tokens;
|
|
415
|
+
total.total_tokens += byRole[role].total_tokens;
|
|
416
|
+
total.usage_rows += byRole[role].usage_rows;
|
|
417
|
+
}
|
|
418
|
+
return { tracePath, session_ids: [...treeIds], byRole, total };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function median(values) {
|
|
422
|
+
const arr = values.filter((v) => Number.isFinite(v)).sort((a, b) => a - b);
|
|
423
|
+
if (!arr.length) return 0;
|
|
424
|
+
const mid = Math.floor(arr.length / 2);
|
|
425
|
+
return arr.length % 2 ? arr[mid] : (arr[mid - 1] + arr[mid]) / 2;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function mean(values) {
|
|
429
|
+
const arr = values.filter((v) => Number.isFinite(v));
|
|
430
|
+
return arr.length ? sum(arr) / arr.length : 0;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function aggregateVariant(splits) {
|
|
434
|
+
const out = { byRole: {}, total: {} };
|
|
435
|
+
for (const role of ROLES) {
|
|
436
|
+
const totals = splits.map((s) => s.byRole[role].total_tokens);
|
|
437
|
+
out.byRole[role] = { median: median(totals), mean: mean(totals), runs: totals };
|
|
438
|
+
}
|
|
439
|
+
const grand = splits.map((s) => s.total.total_tokens);
|
|
440
|
+
out.total = { median: median(grand), mean: mean(grand), runs: grand };
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Drives a REAL Lead session in the variant sandbox (createSession/askSession/
|
|
445
|
+
// closeSession from the runtime manager — same pattern as output-style-bench).
|
|
446
|
+
// The variant rule files take effect through MIXDOG_ROOT; the runtime modules
|
|
447
|
+
// themselves are imported from the real PLUGIN_ROOT (identical across variants).
|
|
448
|
+
function runLiveLeadDelegation({ pluginRoot, dataDir, taskCwd, prompt, provider, model, effort, fast }) {
|
|
449
|
+
const cfgUrl = pathToFileURL(join(PLUGIN_ROOT, 'runtime/agent/orchestrator/config.mjs')).href;
|
|
450
|
+
const regUrl = pathToFileURL(join(PLUGIN_ROOT, 'runtime/agent/orchestrator/providers/registry.mjs')).href;
|
|
451
|
+
const mgrUrl = pathToFileURL(join(PLUGIN_ROOT, 'runtime/agent/orchestrator/session/manager.mjs')).href;
|
|
452
|
+
const driver = [
|
|
453
|
+
`import * as cfgMod from ${JSON.stringify(cfgUrl)};`,
|
|
454
|
+
`import * as reg from ${JSON.stringify(regUrl)};`,
|
|
455
|
+
`import { createSession, askSession, closeSession } from ${JSON.stringify(mgrUrl)};`,
|
|
456
|
+
`const config = cfgMod.loadConfig({ secrets: true });`,
|
|
457
|
+
`await reg.initProviders(config.providers || {});`,
|
|
458
|
+
`const sessionOpts = { provider: ${JSON.stringify(provider)}, model: ${JSON.stringify(model)},`,
|
|
459
|
+
` owner: 'cli', agent: 'lead', lane: 'cli', sourceType: 'lead', sourceName: 'internal-comms-bench-lead',`,
|
|
460
|
+
` cwd: ${JSON.stringify(taskCwd)}, tools: 'full', fast: ${fast ? 'true' : 'false'} };`,
|
|
461
|
+
effort ? `sessionOpts.effort = ${JSON.stringify(effort)};` : '',
|
|
462
|
+
`const session = createSession(sessionOpts);`,
|
|
463
|
+
`let result;`,
|
|
464
|
+
`try { result = await askSession(session.id, ${JSON.stringify(prompt)}, null, null, ${JSON.stringify(taskCwd)}); }`,
|
|
465
|
+
`finally { try { closeSession(session.id, 'internal-comms-bench-lead'); } catch {} }`,
|
|
466
|
+
`process.stdout.write(JSON.stringify({ text: String(result?.text || result?.content || '').trim(), sessionId: session.id }));`,
|
|
467
|
+
].filter(Boolean).join('\n');
|
|
468
|
+
const started = Date.now();
|
|
469
|
+
let raw = '';
|
|
470
|
+
let ok = false;
|
|
471
|
+
let sid = null;
|
|
472
|
+
try {
|
|
473
|
+
raw = execFileSync('node', ['--input-type=module', '-e', driver], {
|
|
474
|
+
encoding: 'utf8',
|
|
475
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
476
|
+
env: {
|
|
477
|
+
...process.env,
|
|
478
|
+
MIXDOG_ROOT: pluginRoot,
|
|
479
|
+
MIXDOG_DATA_DIR: dataDir,
|
|
480
|
+
...(effort ? { MIXDOG_AGENT_EFFORT: effort } : {}),
|
|
481
|
+
...(fast ? { MIXDOG_AGENT_FAST: '1' } : {}),
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
const j = raw.lastIndexOf('{');
|
|
485
|
+
if (j >= 0) { try { sid = JSON.parse(raw.slice(j)).sessionId || null; } catch { /* tail */ } }
|
|
486
|
+
ok = !!sid;
|
|
487
|
+
} catch (e) {
|
|
488
|
+
raw = String(e.stdout || '') + String(e.stderr || '');
|
|
489
|
+
const j = raw.lastIndexOf('{');
|
|
490
|
+
if (j >= 0) { try { sid = JSON.parse(raw.slice(j)).sessionId || null; } catch { /* tail */ } }
|
|
491
|
+
ok = false;
|
|
492
|
+
}
|
|
493
|
+
return { sessionId: sid || extractSessionId(raw), ms: Date.now() - started, ok, raw };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function fmtDeltaEntry(entry) {
|
|
497
|
+
const sign = entry.delta >= 0 ? '+' : '';
|
|
498
|
+
const pct = entry.pct == null ? '' : ` (${entry.pct >= 0 ? '+' : ''}${entry.pct.toFixed(1)}%)`;
|
|
499
|
+
return `${sign}${Math.round(entry.delta)}${pct}`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function runLeadMode({ route, effort, fast, repeat, jsonMode, leadPrompt }) {
|
|
503
|
+
const realDataDir = defaultUserDataDir();
|
|
504
|
+
const userUnified = readUnifiedConfig(realDataDir);
|
|
505
|
+
const sandboxRoot = mkdtempSync(join(REPO_ROOT, '.tmp-internal-comms-bench-lead-'));
|
|
506
|
+
const perVariant = { A: [], B: [] };
|
|
507
|
+
const runsMeta = { A: [], B: [] };
|
|
508
|
+
try {
|
|
509
|
+
const pluginRoots = {
|
|
510
|
+
A: materializePluginRoot('A', sandboxRoot),
|
|
511
|
+
B: materializePluginRoot('B', sandboxRoot),
|
|
512
|
+
};
|
|
513
|
+
for (let i = 0; i < repeat; i += 1) {
|
|
514
|
+
for (const variant of ['A', 'B']) {
|
|
515
|
+
const taskCwd = prepareTaskCwd(sandboxRoot);
|
|
516
|
+
const dataDir = prepareDataDir(sandboxRoot, `${variant}-r${i}`, realDataDir, userUnified, route.provider);
|
|
517
|
+
process.stderr.write(`[internal-comms-bench] lead run ${i + 1}/${repeat} variant=${variant} ${route.provider}/${route.model}\n`);
|
|
518
|
+
const run = runLiveLeadDelegation({
|
|
519
|
+
pluginRoot: pluginRoots[variant],
|
|
520
|
+
dataDir,
|
|
521
|
+
taskCwd,
|
|
522
|
+
prompt: leadPrompt,
|
|
523
|
+
provider: route.provider,
|
|
524
|
+
model: route.model,
|
|
525
|
+
effort,
|
|
526
|
+
fast,
|
|
527
|
+
});
|
|
528
|
+
const split = run.sessionId ? splitTokensByRole(dataDir, run.sessionId) : emptyRoleSplit(dataDir);
|
|
529
|
+
perVariant[variant].push(split);
|
|
530
|
+
runsMeta[variant].push({ ok: run.ok, ms: run.ms, sessionId: run.sessionId, total: split.total.total_tokens });
|
|
531
|
+
process.stderr.write(`[internal-comms-bench] -> ${run.ok ? 'ok' : 'FAIL'} ${Math.round(run.ms / 1000)}s session=${run.sessionId || '(none)'} total=${split.total.total_tokens} lead=${split.byRole.lead.total_tokens} worker=${split.byRole.worker.total_tokens} reviewer=${split.byRole.reviewer.total_tokens} other=${split.byRole.other.total_tokens}\n`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
} finally {
|
|
535
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const aggA = aggregateVariant(perVariant.A);
|
|
539
|
+
const aggB = aggregateVariant(perVariant.B);
|
|
540
|
+
const deltaMedian = {};
|
|
541
|
+
const deltaMean = {};
|
|
542
|
+
for (const role of [...ROLES, 'total']) {
|
|
543
|
+
const aM = role === 'total' ? aggA.total.median : aggA.byRole[role].median;
|
|
544
|
+
const bM = role === 'total' ? aggB.total.median : aggB.byRole[role].median;
|
|
545
|
+
const aAvg = role === 'total' ? aggA.total.mean : aggA.byRole[role].mean;
|
|
546
|
+
const bAvg = role === 'total' ? aggB.total.mean : aggB.byRole[role].mean;
|
|
547
|
+
deltaMedian[role] = { delta: bM - aM, pct: pctChange(bM, aM) };
|
|
548
|
+
deltaMean[role] = { delta: bAvg - aAvg, pct: pctChange(bAvg, aAvg) };
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (jsonMode) {
|
|
552
|
+
console.log(JSON.stringify({
|
|
553
|
+
mode: 'lead',
|
|
554
|
+
route,
|
|
555
|
+
repeat,
|
|
556
|
+
prompt: leadPrompt,
|
|
557
|
+
runs: runsMeta,
|
|
558
|
+
variants: { A: { label: 'prior_verbose@HEAD', ...aggA }, B: { label: 'optimized_on_disk', ...aggB } },
|
|
559
|
+
delta_B_vs_A: { median: deltaMedian, mean: deltaMean },
|
|
560
|
+
note: 'single runs are noise-dominated; median+mean over --repeat N reduce run-to-run noise.',
|
|
561
|
+
}, null, 2));
|
|
562
|
+
} else {
|
|
563
|
+
console.log(`lead-mode multi-agent ${route.provider}/${route.model} repeat=${repeat}`);
|
|
564
|
+
console.log('NOTE: single runs are noise-dominated; use --repeat N to stabilize (median+mean shown).');
|
|
565
|
+
for (const [id, agg] of [['A', aggA], ['B', aggB]]) {
|
|
566
|
+
console.log(`variant ${id} (${id === 'A' ? 'prior_verbose@HEAD' : 'optimized_on_disk'}) per-role tokens median/mean over ${repeat} run(s):`);
|
|
567
|
+
for (const role of ROLES) {
|
|
568
|
+
const r = agg.byRole[role];
|
|
569
|
+
console.log(` ${role.padEnd(9)} med=${Math.round(r.median)} mean=${Math.round(r.mean)}`);
|
|
570
|
+
}
|
|
571
|
+
console.log(` ${'total'.padEnd(9)} med=${Math.round(agg.total.median)} mean=${Math.round(agg.total.mean)}`);
|
|
572
|
+
}
|
|
573
|
+
console.log('delta B-vs-A (median): ' + [...ROLES, 'total'].map((r) => `${r}=${fmtDeltaEntry(deltaMedian[r])}`).join(' '));
|
|
574
|
+
console.log('delta B-vs-A (mean): ' + [...ROLES, 'total'].map((r) => `${r}=${fmtDeltaEntry(deltaMean[r])}`).join(' '));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const allOk = runsMeta.A.every((r) => r.ok) && runsMeta.B.every((r) => r.ok);
|
|
578
|
+
const anyUsage = perVariant.A.some((s) => s.total.usage_rows > 0) || perVariant.B.some((s) => s.total.usage_rows > 0);
|
|
579
|
+
process.exit(allOk && anyUsage ? 0 : 1);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function pctChange(b, a) {
|
|
583
|
+
if (!Number.isFinite(a) || a === 0) return null;
|
|
584
|
+
return ((b - a) / a) * 100;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function printUsage() {
|
|
588
|
+
process.stdout.write(`internal-comms-bench — live A/B token effect (internal-comms rules).
|
|
589
|
+
|
|
590
|
+
Variant A = prior verbose rules from git: git show HEAD:src/<rule> (fallback: current file).
|
|
591
|
+
Variant B = optimized rules: current on-disk src/ copied to temp PLUGIN_ROOT.
|
|
592
|
+
|
|
593
|
+
Modes:
|
|
594
|
+
--mode worker (default) — single worker task via runHeadlessRole (bench-run child pattern).
|
|
595
|
+
--mode lead — REAL internal-comms flow: a live Lead session is forced to delegate
|
|
596
|
+
one small task to a worker and then a reviewer (via the agent tool).
|
|
597
|
+
Tokens are split BY ROLE (lead/worker/reviewer/other) across the whole
|
|
598
|
+
Lead+children session tree, parsed from the sandbox agent-trace.jsonl.
|
|
599
|
+
|
|
600
|
+
Usage:
|
|
601
|
+
node scripts/internal-comms-bench.mjs [--json]
|
|
602
|
+
node scripts/internal-comms-bench.mjs --run [--model grok] [--provider P] [--effort E] [--fast] [--prompt "..."] [--json]
|
|
603
|
+
node scripts/internal-comms-bench.mjs --run --mode lead [--repeat N] [--model grok] [--provider P] [--effort E] [--fast] [--prompt "..."] [--json]
|
|
604
|
+
|
|
605
|
+
--run required for live model calls. Sandboxes temp PLUGIN_ROOT + MIXDOG_DATA_DIR; copies OAuth JSON read-only from your real data dir.
|
|
606
|
+
--mode lead measures the optimization the worker mode cannot (Lead brief + agent handoff tokens).
|
|
607
|
+
--repeat N (default 1) runs the A/B N times and reports per-role median AND mean; single runs are noise-dominated.
|
|
608
|
+
`);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function main() {
|
|
612
|
+
const jsonMode = hasFlag('--json');
|
|
613
|
+
const doRun = hasFlag('--run');
|
|
614
|
+
const mode = String(argValue('--mode', 'worker') || 'worker').trim().toLowerCase();
|
|
615
|
+
const repeat = Math.max(1, Number.parseInt(argValue('--repeat', '1'), 10) || 1);
|
|
616
|
+
const prompt = argValue('--prompt', mode === 'lead' ? DEFAULT_LEAD_PROMPT : DEFAULT_WORKER_PROMPT);
|
|
617
|
+
const cli = resolveModelOpts(argValue('--model', doRun ? 'grok' : null), argValue('--provider', null));
|
|
618
|
+
const effort = argValue('--effort', null);
|
|
619
|
+
const fast = hasFlag('--fast');
|
|
620
|
+
|
|
621
|
+
if (!doRun) {
|
|
622
|
+
printUsage();
|
|
623
|
+
const ruleStats = ruleVariantBytes();
|
|
624
|
+
const aSum = sum(ruleStats.map((r) => r.a_chars));
|
|
625
|
+
const bSum = sum(ruleStats.map((r) => r.b_chars));
|
|
626
|
+
if (jsonMode) {
|
|
627
|
+
console.log(JSON.stringify({
|
|
628
|
+
mode: 'usage',
|
|
629
|
+
run_mode: mode,
|
|
630
|
+
repeat,
|
|
631
|
+
rule_files: RULE_FILES,
|
|
632
|
+
rule_stats: ruleStats,
|
|
633
|
+
rule_chars: { A: aSum, B: bSum },
|
|
634
|
+
liveCommand: 'node scripts/internal-comms-bench.mjs --run --model grok',
|
|
635
|
+
liveLeadCommand: 'node scripts/internal-comms-bench.mjs --run --mode lead --repeat 3 --model grok',
|
|
636
|
+
}, null, 2));
|
|
637
|
+
} else {
|
|
638
|
+
process.stdout.write(`[internal-comms-bench] rule chars A(prior@HEAD)=${aSum} B(on-disk)=${bSum} (${RULE_FILES.length} files); mode=${mode} repeat=${repeat}\n`);
|
|
639
|
+
}
|
|
640
|
+
process.exit(0);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const realDataDir = defaultUserDataDir();
|
|
644
|
+
const userUnified = readUnifiedConfig(realDataDir);
|
|
645
|
+
const route = {
|
|
646
|
+
provider: cli.provider || MODEL_ALIASES.grok.provider,
|
|
647
|
+
model: cli.model || MODEL_ALIASES.grok.model,
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
if (mode === 'lead') {
|
|
651
|
+
runLeadMode({ route, effort, fast, repeat, jsonMode, leadPrompt: prompt });
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const sandboxRoot = mkdtempSync(join(REPO_ROOT, '.tmp-internal-comms-bench-'));
|
|
656
|
+
const results = {};
|
|
657
|
+
try {
|
|
658
|
+
const taskCwd = prepareTaskCwd(sandboxRoot);
|
|
659
|
+
for (const variant of ['A', 'B']) {
|
|
660
|
+
resetTaskCwd(taskCwd);
|
|
661
|
+
const pluginRoot = materializePluginRoot(variant, sandboxRoot);
|
|
662
|
+
const dataDir = prepareDataDir(sandboxRoot, variant, realDataDir, userUnified, route.provider);
|
|
663
|
+
process.stderr.write(`[internal-comms-bench] variant=${variant} ${route.provider}/${route.model}\n`);
|
|
664
|
+
const run = runHeadlessWorker({
|
|
665
|
+
pluginRoot,
|
|
666
|
+
dataDir,
|
|
667
|
+
taskCwd,
|
|
668
|
+
prompt,
|
|
669
|
+
provider: route.provider,
|
|
670
|
+
model: route.model,
|
|
671
|
+
effort,
|
|
672
|
+
fast,
|
|
673
|
+
});
|
|
674
|
+
const usage = run.sessionId
|
|
675
|
+
? sumUsageForSession(dataDir, run.sessionId)
|
|
676
|
+
: {
|
|
677
|
+
session_ids: [],
|
|
678
|
+
prompt_tokens: 0,
|
|
679
|
+
output_tokens: 0,
|
|
680
|
+
cached_tokens: 0,
|
|
681
|
+
usage_rows: 0,
|
|
682
|
+
tracePath: join(dataDir, 'history', 'agent-trace.jsonl'),
|
|
683
|
+
};
|
|
684
|
+
usage.total_tokens = usage.prompt_tokens + usage.output_tokens;
|
|
685
|
+
results[variant] = { ...run, usage };
|
|
686
|
+
process.stderr.write(`[internal-comms-bench] -> ${run.ok ? 'ok' : 'FAIL'} ${Math.round(run.ms / 1000)}s session=${run.sessionId || '(none)'} tokens=${usage.total_tokens}\n`);
|
|
687
|
+
}
|
|
688
|
+
} finally {
|
|
689
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const a = results.A?.usage || {};
|
|
693
|
+
const b = results.B?.usage || {};
|
|
694
|
+
const delta = {
|
|
695
|
+
prompt_tokens: (b.prompt_tokens || 0) - (a.prompt_tokens || 0),
|
|
696
|
+
output_tokens: (b.output_tokens || 0) - (a.output_tokens || 0),
|
|
697
|
+
total_tokens: (b.total_tokens || 0) - (a.total_tokens || 0),
|
|
698
|
+
pct_total_B_vs_A: pctChange(b.total_tokens || 0, a.total_tokens || 0),
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
if (jsonMode) {
|
|
702
|
+
console.log(JSON.stringify({
|
|
703
|
+
mode: 'live',
|
|
704
|
+
route,
|
|
705
|
+
prompt,
|
|
706
|
+
variants: {
|
|
707
|
+
A: { label: 'prior_verbose@HEAD', run: { ok: results.A.ok, ms: results.A.ms, sessionId: results.A.sessionId }, usage: a },
|
|
708
|
+
B: { label: 'optimized_on_disk', run: { ok: results.B.ok, ms: results.B.ms, sessionId: results.B.sessionId }, usage: b },
|
|
709
|
+
},
|
|
710
|
+
delta_B_vs_A: delta,
|
|
711
|
+
}, null, 2));
|
|
712
|
+
} else {
|
|
713
|
+
console.log(`live worker ${route.provider}/${route.model}`);
|
|
714
|
+
for (const id of ['A', 'B']) {
|
|
715
|
+
const u = results[id].usage;
|
|
716
|
+
console.log(`variant ${id}: ${results[id].ok ? 'ok' : 'FAIL'} prompt=${u.prompt_tokens} output=${u.output_tokens} total=${u.total_tokens} sessions=${(u.session_ids || []).length}`);
|
|
717
|
+
}
|
|
718
|
+
const pct = delta.pct_total_B_vs_A;
|
|
719
|
+
const pctStr = pct == null ? 'n/a' : `${pct >= 0 ? '+' : ''}${pct.toFixed(1)}%`;
|
|
720
|
+
console.log(`delta B-vs-A: prompt=${delta.prompt_tokens >= 0 ? '+' : ''}${delta.prompt_tokens} output=${delta.output_tokens >= 0 ? '+' : ''}${delta.output_tokens} total=${delta.total_tokens >= 0 ? '+' : ''}${delta.total_tokens} (${pctStr})`);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const ok = results.A?.ok && results.B?.ok && (a.usage_rows > 0 || b.usage_rows > 0);
|
|
724
|
+
process.exit(ok ? 0 : 1);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
main();
|