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,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Static smoke for internal-comms token-optimization rules (min chars / max
|
|
3
|
+
// info). Asserts the Lead brief contract and the agent handoff contract are
|
|
4
|
+
// present and injected, without any model call. Live token A/B is a separate
|
|
5
|
+
// bench (scripts/internal-comms-bench.mjs).
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { dirname, join, resolve } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const rulesBuilder = require('../src/lib/rules-builder.cjs');
|
|
15
|
+
|
|
16
|
+
function assert(condition, message) {
|
|
17
|
+
if (!condition) throw new Error(message);
|
|
18
|
+
}
|
|
19
|
+
function readSrc(...parts) {
|
|
20
|
+
return readFileSync(join(root, 'src', ...parts), 'utf8');
|
|
21
|
+
}
|
|
22
|
+
// Rules wrap across lines; collapse whitespace so phrase asserts are line-agnostic.
|
|
23
|
+
function flat(text) {
|
|
24
|
+
return String(text || '').replace(/\s+/g, ' ');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// --- Lead brief contract: canonical in lead-tool.md, referenced from WORKFLOW -
|
|
28
|
+
const workflow = readSrc('workflows', 'default', 'WORKFLOW.md');
|
|
29
|
+
const leadTool = readSrc('rules', 'lead', 'lead-tool.md');
|
|
30
|
+
const BRIEF_FIELDS = ['Goal:', 'Anchors:', 'Allow/Forbid:', 'Deliver:', 'Verify:'];
|
|
31
|
+
// Canonical brief contract lives in lead-tool.md (Lead tool-use rules).
|
|
32
|
+
assert(/minimum characters, maximum information/i.test(flat(leadTool)), 'lead-tool.md: brief must state min-char/max-info principle');
|
|
33
|
+
for (const field of BRIEF_FIELDS) assert(leadTool.includes(field), `lead-tool.md: brief missing labeled field ${field}`);
|
|
34
|
+
assert(leadTool.includes('Stop:'), 'lead-tool.md: brief must add Stop: for heavy-worker bound');
|
|
35
|
+
assert(/role-known|already (?:owns|knows)|wasted cost|wasted/i.test(flat(leadTool)), 'lead-tool.md: brief must ban restating known rules/background as cost');
|
|
36
|
+
// WORKFLOW.md must not duplicate the field list; it defers to the lead-tool contract.
|
|
37
|
+
assert(/lead-tool brief contract/i.test(flat(workflow)), 'WORKFLOW.md: must defer to the lead-tool brief contract');
|
|
38
|
+
assert(!BRIEF_FIELDS.every((field) => workflow.includes(field)), 'WORKFLOW.md: must not duplicate the full brief field list');
|
|
39
|
+
|
|
40
|
+
// --- Agent handoff contract (00-common.md) ---------------------------------
|
|
41
|
+
const common = readSrc('rules', 'agent', '00-common.md');
|
|
42
|
+
assert(/minimum characters, maximum information/i.test(flat(common)), '00-common: handoff must state token-optimized principle');
|
|
43
|
+
assert(/fragments/i.test(common), '00-common: handoff must require fragments');
|
|
44
|
+
assert(/file:line/i.test(common), '00-common: handoff must anchor evidence to file:line');
|
|
45
|
+
assert(/Banned as pure cost/i.test(flat(common)), '00-common: handoff must list banned cost items');
|
|
46
|
+
for (const banned of ['headings', 'tables', 'narration', 'raw logs', 'next-checks']) {
|
|
47
|
+
assert(common.toLowerCase().includes(banned), `00-common: banned list missing ${banned}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Per-role output contracts --------------------------------------------
|
|
51
|
+
const roles = {
|
|
52
|
+
'worker/AGENT.md': readSrc('agents', 'worker', 'AGENT.md'),
|
|
53
|
+
'heavy-worker/AGENT.md': readSrc('agents', 'heavy-worker', 'AGENT.md'),
|
|
54
|
+
'reviewer/AGENT.md': readSrc('agents', 'reviewer', 'AGENT.md'),
|
|
55
|
+
'debugger/AGENT.md': readSrc('agents', 'debugger', 'AGENT.md'),
|
|
56
|
+
};
|
|
57
|
+
for (const [name, text] of Object.entries(roles)) {
|
|
58
|
+
assert(/file:line/i.test(text), `${name}: role output must anchor to file:line`);
|
|
59
|
+
assert(/fragments|no report bloat|no prose|no narration|no preamble/i.test(flat(text)), `${name}: role output must forbid prose bloat`);
|
|
60
|
+
}
|
|
61
|
+
assert(/severity-ordered/i.test(flat(roles['reviewer/AGENT.md'])), 'reviewer: must keep severity-ordered findings');
|
|
62
|
+
assert(/confirmed[\s\S]*inferences/i.test(roles['debugger/AGENT.md']), 'debugger: must separate confirmed facts vs inferences');
|
|
63
|
+
assert(/Stop:|how-to-verify|how to verify/i.test(flat(roles['heavy-worker/AGENT.md'])), 'heavy-worker: must bound scope / state how to verify');
|
|
64
|
+
|
|
65
|
+
// --- Injection: Lead rules actually carry the brief contract ---------------
|
|
66
|
+
const dataDir = mkdtempSync(join(tmpdir(), 'mixdog-internal-comms-smoke-'));
|
|
67
|
+
try {
|
|
68
|
+
const leadRules = rulesBuilder.buildInjectionContent({ PLUGIN_ROOT: join(root, 'src'), DATA_DIR: dataDir });
|
|
69
|
+
assert(/minimum characters, maximum information/i.test(flat(leadRules)), 'injected Lead rules must carry the brief token principle');
|
|
70
|
+
for (const field of BRIEF_FIELDS) assert(leadRules.includes(field), `injected Lead rules missing brief field ${field}`);
|
|
71
|
+
} finally {
|
|
72
|
+
rmSync(dataDir, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
process.stdout.write('internal comms smoke passed\n');
|
|
@@ -315,7 +315,7 @@ function agentSummary(call) {
|
|
|
315
315
|
id: call?.id || call?.callId || null,
|
|
316
316
|
name: call?.name || call?.toolName || 'tool',
|
|
317
317
|
type: input?.type || input?.action || null,
|
|
318
|
-
|
|
318
|
+
agent: input?.agent || null,
|
|
319
319
|
tag: input?.tag || null,
|
|
320
320
|
mode: input?.mode || null,
|
|
321
321
|
wait: input?.wait ?? null,
|
|
@@ -491,15 +491,15 @@ async function runScenario(name) {
|
|
|
491
491
|
.filter(Number.isFinite);
|
|
492
492
|
const firstMutationIter = mutationIters.length ? Math.min(...mutationIters) : null;
|
|
493
493
|
const implementationSpawnCalls = spawnCalls.filter((call) => {
|
|
494
|
-
const
|
|
494
|
+
const agent = String(call.agent || '').toLowerCase();
|
|
495
495
|
const tag = String(call.tag || '').toLowerCase();
|
|
496
|
-
if (!['worker', 'heavy-worker', 'debugger'].includes(
|
|
496
|
+
if (!['worker', 'heavy-worker', 'debugger'].includes(agent)) return false;
|
|
497
497
|
return !/(verify|smoke|review|policy)/.test(tag);
|
|
498
498
|
});
|
|
499
499
|
const preEditImplementationSpawns = firstMutationIter == null
|
|
500
500
|
? implementationSpawnCalls.length
|
|
501
501
|
: implementationSpawnCalls.filter((call) => Number(call.iter) < firstMutationIter).length;
|
|
502
|
-
const distinctRoles = [...new Set(agentCalls.map((call) => call.
|
|
502
|
+
const distinctRoles = [...new Set(agentCalls.map((call) => call.agent).filter(Boolean))];
|
|
503
503
|
const distinctTags = [...new Set(agentCalls.map((call) => call.tag).filter(Boolean))];
|
|
504
504
|
const firstAgentIter = Math.min(...agentCalls.map((call) => Number(call.iter)).filter(Number.isFinite));
|
|
505
505
|
const firstIterAgentCount = Number.isFinite(firstAgentIter)
|
|
@@ -36,7 +36,7 @@ async function main() {
|
|
|
36
36
|
const leadToolRules = readFileSync('src/rules/lead/lead-tool.md', 'utf8');
|
|
37
37
|
const workflowRules = readFileSync('src/workflows/default/WORKFLOW.md', 'utf8');
|
|
38
38
|
assert(/Use `agent` for scoped implementation/i.test(leadToolRules), 'lead rules must direct scoped work to agents');
|
|
39
|
-
assert(/
|
|
39
|
+
assert(/PARALLEL across independent scopes/i.test(workflowRules), 'workflow rules must keep independent work parallel');
|
|
40
40
|
assert(/always start background tasks/i.test(AGENT_TOOL.description || '') && /distinct tags/i.test(AGENT_TOOL.description || '') && /completion notification/i.test(AGENT_TOOL.description || ''), 'agent tool description must expose async parallel tags');
|
|
41
41
|
|
|
42
42
|
mkdirSync(dataDir, { recursive: true });
|
|
@@ -107,7 +107,7 @@ async function main() {
|
|
|
107
107
|
if (/debug/i.test(prompt)) {
|
|
108
108
|
return { content: `debugger ${readFileSync(join(cwdOverride, 'notes.txt'), 'utf8').trim()}` };
|
|
109
109
|
}
|
|
110
|
-
return { content: `ack ${session?.
|
|
110
|
+
return { content: `ack ${session?.agent || 'worker'}` };
|
|
111
111
|
} finally {
|
|
112
112
|
activeAsks -= 1;
|
|
113
113
|
if (session) {
|
|
@@ -152,7 +152,7 @@ async function main() {
|
|
|
152
152
|
|
|
153
153
|
const spawnOut = await agentRunner.execute({
|
|
154
154
|
type: 'spawn',
|
|
155
|
-
|
|
155
|
+
agent: 'worker',
|
|
156
156
|
tag: 'impl1',
|
|
157
157
|
cwd: root,
|
|
158
158
|
prompt: 'write implementation: update feature.txt with apply_patch',
|
|
@@ -169,14 +169,14 @@ async function main() {
|
|
|
169
169
|
|
|
170
170
|
const reviewOut = await agentRunner.execute({
|
|
171
171
|
type: 'spawn',
|
|
172
|
-
|
|
172
|
+
agent: 'reviewer',
|
|
173
173
|
tag: 'rev1',
|
|
174
174
|
cwd: root,
|
|
175
175
|
prompt: 'review feature.txt for the worker change',
|
|
176
176
|
}, { invocationSource: 'model-tool', cwd: root });
|
|
177
177
|
const debugOut = await agentRunner.execute({
|
|
178
178
|
type: 'spawn',
|
|
179
|
-
|
|
179
|
+
agent: 'debugger',
|
|
180
180
|
tag: 'dbg1',
|
|
181
181
|
cwd: root,
|
|
182
182
|
prompt: 'debug notes.txt timeout clue',
|
|
@@ -187,21 +187,21 @@ async function main() {
|
|
|
187
187
|
const parallelSpawns = await Promise.all([
|
|
188
188
|
agentRunner.execute({
|
|
189
189
|
type: 'spawn',
|
|
190
|
-
|
|
190
|
+
agent: 'worker',
|
|
191
191
|
tag: 'parWorker',
|
|
192
192
|
cwd: root,
|
|
193
193
|
prompt: 'parallel slow worker task',
|
|
194
194
|
}, { invocationSource: 'model-tool', cwd: root }),
|
|
195
195
|
agentRunner.execute({
|
|
196
196
|
type: 'spawn',
|
|
197
|
-
|
|
197
|
+
agent: 'reviewer',
|
|
198
198
|
tag: 'parReviewer',
|
|
199
199
|
cwd: root,
|
|
200
200
|
prompt: 'parallel slow review task',
|
|
201
201
|
}, { invocationSource: 'model-tool', cwd: root }),
|
|
202
202
|
agentRunner.execute({
|
|
203
203
|
type: 'spawn',
|
|
204
|
-
|
|
204
|
+
agent: 'debugger',
|
|
205
205
|
tag: 'parDebugger',
|
|
206
206
|
cwd: root,
|
|
207
207
|
prompt: 'parallel slow debug task',
|
|
@@ -217,7 +217,7 @@ async function main() {
|
|
|
217
217
|
|
|
218
218
|
const busyOut = await agentRunner.execute({
|
|
219
219
|
type: 'spawn',
|
|
220
|
-
|
|
220
|
+
agent: 'worker',
|
|
221
221
|
tag: 'busy1',
|
|
222
222
|
cwd: root,
|
|
223
223
|
prompt: 'long busy worker task',
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Lead output-style verbosity bench (default >= simple >= minimal >= oneline).
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import {
|
|
6
|
+
copyFileSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
mkdtempSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
} from 'node:fs';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
import { dirname, join, resolve } from 'node:path';
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const REPO_ROOT = resolve(__dir, '..');
|
|
21
|
+
const PLUGIN_ROOT = join(REPO_ROOT, 'src');
|
|
22
|
+
const STYLES = ['default', 'simple', 'minimal', 'oneline'];
|
|
23
|
+
const DEFAULT_PROMPT =
|
|
24
|
+
'Reply in plain English only. What is 2+2? Give a short summary suitable for a status report (no tools, no file reads).';
|
|
25
|
+
const MODEL_ALIASES = {
|
|
26
|
+
opus: { provider: 'anthropic-oauth', model: 'claude-opus-4-8' },
|
|
27
|
+
sonnet: { provider: 'anthropic-oauth', model: 'claude-sonnet-5' },
|
|
28
|
+
gpt: { provider: 'openai-oauth', model: 'gpt-5.5' },
|
|
29
|
+
'gpt-5.5': { provider: 'openai-oauth', model: 'gpt-5.5' },
|
|
30
|
+
grok: { provider: 'grok-oauth', model: 'grok-composer-2.5-fast' },
|
|
31
|
+
};
|
|
32
|
+
// Filenames verified in provider ensureAuth / token paths (resolvePluginData / getPluginData).
|
|
33
|
+
const AUTH_ARTIFACT_BY_PROVIDER = {
|
|
34
|
+
'grok-oauth': ['grok-oauth.json', 'grok-oauth-models.json'],
|
|
35
|
+
'anthropic-oauth': ['anthropic-oauth-credentials.json', 'anthropic-oauth-models.json'],
|
|
36
|
+
'openai-oauth': ['openai-oauth.json', 'openai-oauth-models.json'],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function argValue(name, fallback = null) {
|
|
40
|
+
const idx = process.argv.indexOf(name);
|
|
41
|
+
if (idx >= 0 && idx + 1 < process.argv.length) return process.argv[idx + 1];
|
|
42
|
+
const pref = `${name}=`;
|
|
43
|
+
const hit = process.argv.find((a) => a.startsWith(pref));
|
|
44
|
+
return hit ? hit.slice(pref.length) : fallback;
|
|
45
|
+
}
|
|
46
|
+
function hasFlag(name) { return process.argv.includes(name); }
|
|
47
|
+
function resolveModelOpts(modelArg, providerArg) {
|
|
48
|
+
const key = String(modelArg || '').trim().toLowerCase();
|
|
49
|
+
if (MODEL_ALIASES[key] && !providerArg) return { ...MODEL_ALIASES[key] };
|
|
50
|
+
return { provider: providerArg || null, model: modelArg || null };
|
|
51
|
+
}
|
|
52
|
+
function defaultUserDataDir() {
|
|
53
|
+
return process.env.MIXDOG_DATA_DIR || join(process.env.MIXDOG_HOME || join(homedir(), '.mixdog'), 'data');
|
|
54
|
+
}
|
|
55
|
+
function readUnifiedConfig(dataDir) {
|
|
56
|
+
try {
|
|
57
|
+
const unified = JSON.parse(readFileSync(join(dataDir, 'mixdog-config.json'), 'utf8'));
|
|
58
|
+
return unified && typeof unified === 'object' ? unified : {};
|
|
59
|
+
} catch { return {}; }
|
|
60
|
+
}
|
|
61
|
+
function outputStyleBodyFromMeta(meta) {
|
|
62
|
+
const marker = '# Output Style';
|
|
63
|
+
const idx = String(meta || '').lastIndexOf(marker);
|
|
64
|
+
return idx < 0 ? '' : String(meta).slice(idx).trim();
|
|
65
|
+
}
|
|
66
|
+
function measureOutputText(text) {
|
|
67
|
+
const trimmed = String(text || '').trim();
|
|
68
|
+
const lines = trimmed ? trimmed.split(/\r?\n/) : [];
|
|
69
|
+
const bullets = lines.filter((l) => /^\s*[-*•]\s+/.test(l)).length;
|
|
70
|
+
const withoutCode = trimmed.replace(/`[^`]*`/g, '');
|
|
71
|
+
const sentenceMarks = withoutCode.match(/[.!?。!?]+(?=\s|$)/g) || [];
|
|
72
|
+
return {
|
|
73
|
+
chars: trimmed.length,
|
|
74
|
+
lines: lines.length,
|
|
75
|
+
bullets,
|
|
76
|
+
sentences: sentenceMarks.length || (trimmed ? 1 : 0),
|
|
77
|
+
text: trimmed,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function runInjectionScaffold() {
|
|
81
|
+
const rulesBuilder = createRequire(import.meta.url)(join(PLUGIN_ROOT, 'lib', 'rules-builder.cjs'));
|
|
82
|
+
const baseDir = mkdtempSync(join(REPO_ROOT, '.tmp-output-style-bench-'));
|
|
83
|
+
const templatePath = join(PLUGIN_ROOT, 'defaults', 'mixdog-config.template.json');
|
|
84
|
+
const baseConfig = existsSync(templatePath)
|
|
85
|
+
? JSON.parse(readFileSync(templatePath, 'utf8'))
|
|
86
|
+
: { outputStyle: 'default' };
|
|
87
|
+
const markers = {
|
|
88
|
+
default: 'most detailed of the three',
|
|
89
|
+
simple: 'Practical concise',
|
|
90
|
+
minimal: 'a very short summary',
|
|
91
|
+
oneline: 'exactly one sentence',
|
|
92
|
+
};
|
|
93
|
+
const snippets = {};
|
|
94
|
+
try {
|
|
95
|
+
for (const styleId of STYLES) {
|
|
96
|
+
const dataDir = join(baseDir, styleId);
|
|
97
|
+
mkdirSync(dataDir, { recursive: true });
|
|
98
|
+
writeFileSync(join(dataDir, 'mixdog-config.json'), JSON.stringify({ ...baseConfig, outputStyle: styleId }, null, 2));
|
|
99
|
+
snippets[styleId] = outputStyleBodyFromMeta(rulesBuilder.buildLeadMetaContent({ PLUGIN_ROOT, DATA_DIR: dataDir }));
|
|
100
|
+
if (!snippets[styleId].includes(markers[styleId])) throw new Error(`${styleId} injection marker missing`);
|
|
101
|
+
}
|
|
102
|
+
if (new Set(STYLES.map((id) => snippets[id])).size !== STYLES.length) throw new Error('injection bodies not distinct');
|
|
103
|
+
return { snippets };
|
|
104
|
+
} finally {
|
|
105
|
+
rmSync(baseDir, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function authArtifactNamesForSandbox(realDataDir, provider) {
|
|
109
|
+
const names = new Set();
|
|
110
|
+
for (const file of AUTH_ARTIFACT_BY_PROVIDER[provider] || []) names.add(file);
|
|
111
|
+
try {
|
|
112
|
+
for (const entry of readdirSync(realDataDir, { withFileTypes: true })) {
|
|
113
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
|
|
114
|
+
if (/oauth/i.test(entry.name) || /credentials/i.test(entry.name)) names.add(entry.name);
|
|
115
|
+
}
|
|
116
|
+
} catch { /* missing real data dir */ }
|
|
117
|
+
return [...names];
|
|
118
|
+
}
|
|
119
|
+
function copyAuthArtifacts(realDataDir, sandboxDataDir, provider) {
|
|
120
|
+
const copied = [];
|
|
121
|
+
const skipped = [];
|
|
122
|
+
for (const name of authArtifactNamesForSandbox(realDataDir, provider)) {
|
|
123
|
+
const src = join(realDataDir, name);
|
|
124
|
+
const dest = join(sandboxDataDir, name);
|
|
125
|
+
if (!existsSync(src)) {
|
|
126
|
+
skipped.push(name);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
copyFileSync(src, dest);
|
|
131
|
+
copied.push(name);
|
|
132
|
+
} catch {
|
|
133
|
+
skipped.push(name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { copied, skipped };
|
|
137
|
+
}
|
|
138
|
+
function prepareStyleSandbox(baseSandbox, styleId, userUnified, realDataDir, provider) {
|
|
139
|
+
const dataDir = join(baseSandbox, styleId);
|
|
140
|
+
mkdirSync(dataDir, { recursive: true });
|
|
141
|
+
writeFileSync(join(dataDir, 'mixdog-config.json'), JSON.stringify({ ...userUnified, outputStyle: styleId }, null, 2));
|
|
142
|
+
copyAuthArtifacts(realDataDir, dataDir, provider);
|
|
143
|
+
return dataDir;
|
|
144
|
+
}
|
|
145
|
+
function findPresetRoute(config, key) {
|
|
146
|
+
const wanted = String(key || '').trim().toLowerCase();
|
|
147
|
+
if (!wanted) return null;
|
|
148
|
+
const presets = Array.isArray(config?.presets) ? config.presets : [];
|
|
149
|
+
return presets.find((p) => {
|
|
150
|
+
const id = String(p?.id || '').trim().toLowerCase();
|
|
151
|
+
const name = String(p?.name || '').trim().toLowerCase();
|
|
152
|
+
return id === wanted || name === wanted;
|
|
153
|
+
}) || null;
|
|
154
|
+
}
|
|
155
|
+
function resolveLeadProviderModel(userUnified, cli) {
|
|
156
|
+
if (cli.provider && cli.model) return { provider: cli.provider, model: cli.model };
|
|
157
|
+
const leadPreset = findPresetRoute(userUnified, 'workflow-lead')
|
|
158
|
+
|| findPresetRoute(userUnified, userUnified.default)
|
|
159
|
+
|| findPresetRoute(userUnified, 'gpt-5.5');
|
|
160
|
+
if (leadPreset?.provider && leadPreset?.model) return { provider: leadPreset.provider, model: leadPreset.model };
|
|
161
|
+
const alias = MODEL_ALIASES.gpt;
|
|
162
|
+
return { provider: cli.provider || alias.provider, model: cli.model || alias.model };
|
|
163
|
+
}
|
|
164
|
+
function runLiveLeadTurn({ dataDir, prompt, provider, model, cwd, effort, fast }) {
|
|
165
|
+
const cfgUrl = pathToFileURL(join(PLUGIN_ROOT, 'runtime/agent/orchestrator/config.mjs')).href;
|
|
166
|
+
const regUrl = pathToFileURL(join(PLUGIN_ROOT, 'runtime/agent/orchestrator/providers/registry.mjs')).href;
|
|
167
|
+
const mgrUrl = pathToFileURL(join(PLUGIN_ROOT, 'runtime/agent/orchestrator/session/manager.mjs')).href;
|
|
168
|
+
const driver = [
|
|
169
|
+
`import * as cfgMod from ${JSON.stringify(cfgUrl)};`,
|
|
170
|
+
`import * as reg from ${JSON.stringify(regUrl)};`,
|
|
171
|
+
`import { createSession, askSession, closeSession } from ${JSON.stringify(mgrUrl)};`,
|
|
172
|
+
`const config = cfgMod.loadConfig({ secrets: true });`,
|
|
173
|
+
`await reg.initProviders(config.providers || {});`,
|
|
174
|
+
`const sessionOpts = { provider: ${JSON.stringify(provider)}, model: ${JSON.stringify(model)},`,
|
|
175
|
+
` owner: 'cli', agent: 'lead', lane: 'cli', sourceType: 'lead', sourceName: 'output-style-bench',`,
|
|
176
|
+
` cwd: ${JSON.stringify(cwd)}, tools: 'full', fast: ${fast ? 'true' : 'false'} };`,
|
|
177
|
+
effort ? `sessionOpts.effort = ${JSON.stringify(effort)};` : '',
|
|
178
|
+
`const session = createSession(sessionOpts);`,
|
|
179
|
+
`let result;`,
|
|
180
|
+
`try { result = await askSession(session.id, ${JSON.stringify(prompt)}, null, null, ${JSON.stringify(cwd)}); }`,
|
|
181
|
+
`finally { try { closeSession(session.id, 'output-style-bench'); } catch {} }`,
|
|
182
|
+
`process.stdout.write(JSON.stringify({ text: String(result?.text || result?.content || '').trim(), sessionId: session.id }));`,
|
|
183
|
+
].filter(Boolean).join('\n');
|
|
184
|
+
const raw = execFileSync('node', ['--input-type=module', '-e', driver], {
|
|
185
|
+
encoding: 'utf8',
|
|
186
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
187
|
+
env: { ...process.env, MIXDOG_ROOT: PLUGIN_ROOT, MIXDOG_DATA_DIR: dataDir },
|
|
188
|
+
});
|
|
189
|
+
const jsonStart = raw.lastIndexOf('{');
|
|
190
|
+
if (jsonStart < 0) throw new Error(`live driver failed: ${raw.slice(0, 400)}`);
|
|
191
|
+
return JSON.parse(raw.slice(jsonStart));
|
|
192
|
+
}
|
|
193
|
+
function styleCharCountsFromResults(results) {
|
|
194
|
+
return Object.fromEntries(
|
|
195
|
+
STYLES.map((id) => [id, results.find((r) => r.style === id)?.metrics.chars ?? 0]),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
function evaluateVerbosityOrdering(results) {
|
|
199
|
+
const counts = STYLES.map((id) => results.find((r) => r.style === id)?.metrics.chars ?? 0);
|
|
200
|
+
let orderingOk = true;
|
|
201
|
+
for (let i = 1; i < counts.length; i += 1) {
|
|
202
|
+
if (counts[i - 1] < counts[i]) orderingOk = false;
|
|
203
|
+
}
|
|
204
|
+
const chain = STYLES.map((id, i) => `${id}=${counts[i]}`).join(' ');
|
|
205
|
+
const passLabel = STYLES.map((id) => `chars(${id})`).join(' >= ');
|
|
206
|
+
const verdict = orderingOk
|
|
207
|
+
? `PASS ${passLabel}`
|
|
208
|
+
: `WARN ordering violated: ${chain}`;
|
|
209
|
+
return { orderingOk, verdict, counts: styleCharCountsFromResults(results) };
|
|
210
|
+
}
|
|
211
|
+
function printUsage() {
|
|
212
|
+
process.stdout.write(`output-style-bench — Lead verbosity ladder (default >= simple >= minimal >= oneline).
|
|
213
|
+
|
|
214
|
+
Output style is Lead-only (buildLeadMetaContent when owner is not agent).
|
|
215
|
+
runHeadlessRole worker paths (owner=agent) do NOT inject outputStyle.
|
|
216
|
+
|
|
217
|
+
Usage:
|
|
218
|
+
node scripts/output-style-bench.mjs [--json]
|
|
219
|
+
node scripts/output-style-bench.mjs --run [--model gpt] [--provider P] [--effort E] [--fast] [--prompt "..."] [--json]
|
|
220
|
+
|
|
221
|
+
--run required for live calls. Temp MIXDOG_DATA_DIR: outputStyle override + read-only copy of OAuth/credential JSON from your real data dir.
|
|
222
|
+
`);
|
|
223
|
+
}
|
|
224
|
+
function main() {
|
|
225
|
+
const jsonMode = hasFlag('--json');
|
|
226
|
+
const doRun = hasFlag('--run');
|
|
227
|
+
const prompt = argValue('--prompt', DEFAULT_PROMPT);
|
|
228
|
+
const cli = resolveModelOpts(argValue('--model', null), argValue('--provider', null));
|
|
229
|
+
const effort = argValue('--effort', null);
|
|
230
|
+
const fast = hasFlag('--fast');
|
|
231
|
+
const cwd = process.cwd();
|
|
232
|
+
let scaffold;
|
|
233
|
+
try { scaffold = runInjectionScaffold(); }
|
|
234
|
+
catch (e) { process.stderr.write(`[output-style-bench] scaffold FAILED: ${e.message}\n`); process.exit(1); }
|
|
235
|
+
if (!doRun) {
|
|
236
|
+
printUsage();
|
|
237
|
+
const charCounts = Object.fromEntries(STYLES.map((id) => [id, scaffold.snippets[id].length]));
|
|
238
|
+
if (jsonMode) {
|
|
239
|
+
console.log(JSON.stringify({ mode: 'scaffold', role: 'lead', owner: 'cli', charCounts, liveCommand: 'node scripts/output-style-bench.mjs --run --model gpt' }, null, 2));
|
|
240
|
+
} else {
|
|
241
|
+
process.stdout.write(`[output-style-bench] scaffold ok: ${STYLES.map((id) => `${id}=${charCounts[id]}`).join(', ')}\n`);
|
|
242
|
+
}
|
|
243
|
+
process.exit(0);
|
|
244
|
+
}
|
|
245
|
+
const realDataDir = defaultUserDataDir();
|
|
246
|
+
const userUnified = readUnifiedConfig(realDataDir);
|
|
247
|
+
const route = resolveLeadProviderModel(userUnified, cli);
|
|
248
|
+
const baseSandbox = mkdtempSync(join(REPO_ROOT, '.tmp-output-style-bench-live-'));
|
|
249
|
+
const results = [];
|
|
250
|
+
try {
|
|
251
|
+
for (const styleId of STYLES) {
|
|
252
|
+
const dataDir = prepareStyleSandbox(baseSandbox, styleId, userUnified, realDataDir, route.provider);
|
|
253
|
+
process.stderr.write(`[output-style-bench] style=${styleId} ${route.provider}/${route.model}\n`);
|
|
254
|
+
try {
|
|
255
|
+
const live = runLiveLeadTurn({ dataDir, prompt, ...route, cwd, effort, fast });
|
|
256
|
+
const metrics = measureOutputText(live.text);
|
|
257
|
+
results.push({ style: styleId, ok: true, sessionId: live.sessionId, metrics, raw: metrics.text });
|
|
258
|
+
} catch (err) {
|
|
259
|
+
results.push({
|
|
260
|
+
style: styleId,
|
|
261
|
+
ok: false,
|
|
262
|
+
error: String(err.stdout || err.stderr || err.message).slice(0, 2000),
|
|
263
|
+
metrics: measureOutputText(''),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} finally {
|
|
268
|
+
rmSync(baseSandbox, { recursive: true, force: true });
|
|
269
|
+
}
|
|
270
|
+
const { orderingOk, verdict } = evaluateVerbosityOrdering(results);
|
|
271
|
+
if (jsonMode) {
|
|
272
|
+
console.log(JSON.stringify({ mode: 'live', role: 'lead', prompt, route, results, verdict, orderingOk }, null, 2));
|
|
273
|
+
} else {
|
|
274
|
+
console.log(`live role=lead ${route.provider}/${route.model}`);
|
|
275
|
+
for (const r of results) {
|
|
276
|
+
const m = r.metrics;
|
|
277
|
+
console.log(`- ${r.style}: ${r.ok ? 'ok' : 'FAIL'} chars=${m.chars} lines=${m.lines} bullets=${m.bullets} sentences=${m.sentences}`);
|
|
278
|
+
if (r.ok) console.log(` ${m.text}`);
|
|
279
|
+
else console.log(` error: ${String(r.error || '').slice(0, 300)}`);
|
|
280
|
+
}
|
|
281
|
+
console.log(verdict);
|
|
282
|
+
}
|
|
283
|
+
process.exit(results.every((r) => r.ok) ? 0 : 1);
|
|
284
|
+
}
|
|
285
|
+
main();
|
|
@@ -49,7 +49,8 @@ const defaultStyle = readFileSync(join(root, 'src', 'output-styles', 'default.md
|
|
|
49
49
|
for (const required of [
|
|
50
50
|
'name: default',
|
|
51
51
|
'Concise engineering summaries',
|
|
52
|
-
'Mixdog default —
|
|
52
|
+
'Mixdog default — the most detailed of the three styles',
|
|
53
|
+
'match length to the work',
|
|
53
54
|
'Use labels such as',
|
|
54
55
|
'`바뀐 점`, `확인한 것`,',
|
|
55
56
|
'Synthesize agent or retrieval results',
|
|
@@ -60,7 +61,7 @@ for (const required of [
|
|
|
60
61
|
assert(!defaultStyle.includes('Claude Code-compact'), 'default.md must not reference the old compact style name');
|
|
61
62
|
assert(!defaultStyle.includes('Hard cap user-visible replies'), 'default.md must not hard-cap replies to two short sentences');
|
|
62
63
|
assert(!defaultStyle.includes('Be short and direct.'), 'default.md must not keep the old generic concise preset');
|
|
63
|
-
assert(!defaultStyle.includes('Practical concise — outcome-first'), 'default.md must not use the simple preset body');
|
|
64
|
+
assert(!defaultStyle.includes('Practical concise — outcome-first handoffs'), 'default.md must not use the simple preset body');
|
|
64
65
|
|
|
65
66
|
const simpleStyle = readFileSync(join(root, 'src', 'output-styles', 'simple.md'), 'utf8');
|
|
66
67
|
for (const required of [
|
|
@@ -75,11 +76,12 @@ for (const required of [
|
|
|
75
76
|
]) {
|
|
76
77
|
assert(simpleStyle.includes(required), `simple.md missing: ${required}`);
|
|
77
78
|
}
|
|
78
|
-
assert(!simpleStyle.includes('Mixdog default —
|
|
79
|
+
assert(!simpleStyle.includes('Mixdog default — the most detailed of the three styles'), 'simple.md must not duplicate default preset body');
|
|
79
80
|
for (const [name, style] of Object.entries({
|
|
80
81
|
default: defaultStyle,
|
|
81
82
|
simple: simpleStyle,
|
|
82
|
-
|
|
83
|
+
minimal: readFileSync(join(root, 'src', 'output-styles', 'minimal.md'), 'utf8'),
|
|
84
|
+
oneline: readFileSync(join(root, 'src', 'output-styles', 'oneline.md'), 'utf8'),
|
|
83
85
|
})) {
|
|
84
86
|
assert(!style.includes('pre-tool preamble'), `${name} output style must not own language preamble rules`);
|
|
85
87
|
assert(!style.includes('selected/default user language'), `${name} output style must not duplicate profile language rules`);
|
|
@@ -101,7 +103,7 @@ try {
|
|
|
101
103
|
writeFileSync(join(dataDir, 'output-styles', 'custom-smoke.md'), '---\nname: custom-smoke\n---\n\n# Custom Output Style\n\ncustom smoke style\n');
|
|
102
104
|
const customRules = rulesBuilder.buildInjectionContent({ PLUGIN_ROOT: join(root, 'src'), DATA_DIR: dataDir });
|
|
103
105
|
assert(customRules.includes('# Custom Output Style'), 'configured outputStyle must select custom style');
|
|
104
|
-
assert(!customRules.includes('Mixdog default —
|
|
106
|
+
assert(!customRules.includes('Mixdog default — the most detailed of the three styles'), 'custom outputStyle should not append default style');
|
|
105
107
|
const profileMeta = rulesBuilder.buildLeadMetaContent({ PLUGIN_ROOT: join(root, 'src'), DATA_DIR: dataDir });
|
|
106
108
|
assert(profileMeta.includes('Use "재영님" when directly addressing the user'), 'profile title must inject into Lead BP3 meta');
|
|
107
109
|
assert(profileMeta.includes('Do not repeat it in routine progress updates or pre-tool preambles'), 'profile title must not encourage title in preambles');
|
|
@@ -111,12 +113,13 @@ try {
|
|
|
111
113
|
rmSync(dataDir, { recursive: true, force: true });
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
// assertCleanOutput encodes the Simple style compact contract (not Default, which may be longer).
|
|
114
117
|
const goodOutputs = {
|
|
115
|
-
explanation: '
|
|
116
|
-
implementation: '- 바뀐 점: `
|
|
117
|
-
crowded: '- 바뀐 점:
|
|
118
|
-
blocker: '`
|
|
119
|
-
semicolon: '
|
|
118
|
+
explanation: 'Simple 스타일은 결과 한 줄과 근거 한 가지로 끝내고, 최종 handoff만 짧은 bullet 라벨을 씁니다.',
|
|
119
|
+
implementation: '- 바뀐 점: `scripts/output-style-smoke.mjs`의 default/simple 문자열 검증을 현재 프리셋에 맞췄습니다.\n- 확인한 것: `node scripts/output-style-smoke.mjs`를 실행했습니다.',
|
|
120
|
+
crowded: '- 바뀐 점: compact guardrail은 Simple handoff용 controlled detail을 검증합니다.\n- 확인한 것: bad 샘플 거부 규칙은 그대로입니다.',
|
|
121
|
+
blocker: '`scripts/output-style-smoke.mjs`를 찾을 수 없어 변경이 막혔습니다.',
|
|
122
|
+
semicolon: '스모크 스크립트를 갱신했고; Simple 계약에 맞는 예시 응답만 통과합니다.',
|
|
120
123
|
};
|
|
121
124
|
for (const [name, output] of Object.entries(goodOutputs)) assertCleanOutput(name, output, { allowedLabels: DEFAULT_REPORT_LABELS });
|
|
122
125
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// patch-replay.mjs — re-run captured apply_patch FAILURES against current code.
|
|
3
|
+
// Failures frozen by patch.mjs (MIXDOG_PATCH_REPLAY_CAPTURE=1) into
|
|
4
|
+
// <data>/history/patch-replays/*.json with original args + target file snapshots.
|
|
5
|
+
// Replays into a throwaway temp copy (never touches the repo) and reports pass/fail.
|
|
6
|
+
// node scripts/patch-replay.mjs --list
|
|
7
|
+
// node scripts/patch-replay.mjs --replay <id>
|
|
8
|
+
// node scripts/patch-replay.mjs --replay-all [--json]
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
10
|
+
import { homedir, tmpdir } from 'node:os';
|
|
11
|
+
import { dirname, join, resolve } from 'node:path';
|
|
12
|
+
import { executePatchTool } from '../src/runtime/agent/orchestrator/tools/patch.mjs';
|
|
13
|
+
|
|
14
|
+
function argValue(name, fallback = null) {
|
|
15
|
+
const idx = process.argv.indexOf(name);
|
|
16
|
+
if (idx >= 0 && idx + 1 < process.argv.length) return process.argv[idx + 1];
|
|
17
|
+
const pref = `${name}=`;
|
|
18
|
+
const hit = process.argv.find((a) => a.startsWith(pref));
|
|
19
|
+
return hit ? hit.slice(pref.length) : fallback;
|
|
20
|
+
}
|
|
21
|
+
function hasFlag(name) { return process.argv.includes(name); }
|
|
22
|
+
|
|
23
|
+
function replayDir() {
|
|
24
|
+
if (process.env.MIXDOG_PATCH_REPLAY_DIR) return resolve(process.env.MIXDOG_PATCH_REPLAY_DIR);
|
|
25
|
+
const data = process.env.MIXDOG_DATA_DIR || resolve(homedir(), '.mixdog', 'data');
|
|
26
|
+
return resolve(data, 'history', 'patch-replays');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function loadRecords() {
|
|
30
|
+
const dir = replayDir();
|
|
31
|
+
if (!existsSync(dir)) return [];
|
|
32
|
+
return readdirSync(dir).filter((f) => f.endsWith('.json')).map((f) => {
|
|
33
|
+
try { return { file: join(dir, f), ...JSON.parse(readFileSync(join(dir, f), 'utf8')) }; }
|
|
34
|
+
catch { return null; }
|
|
35
|
+
}).filter(Boolean).sort((a, b) => Number(b.ts || 0) - Number(a.ts || 0));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isErr(text) { return /^Error[\s:[]/.test(String(text || '').trimStart()); }
|
|
39
|
+
|
|
40
|
+
async function replayOne(rec) {
|
|
41
|
+
const tmp = mkdtempSync(join(tmpdir(), 'mixdog-patch-replay-'));
|
|
42
|
+
try {
|
|
43
|
+
for (const [rel, content] of Object.entries(rec.file_snapshots || {})) {
|
|
44
|
+
if (content == null) continue;
|
|
45
|
+
const abs = join(tmp, rel);
|
|
46
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
47
|
+
writeFileSync(abs, content);
|
|
48
|
+
}
|
|
49
|
+
const args = { ...(rec.args || {}), base_path: tmp };
|
|
50
|
+
let result;
|
|
51
|
+
try { result = await executePatchTool('apply_patch', args, tmp, {}); }
|
|
52
|
+
catch (e) { result = `Error: ${e?.message || String(e)}`; }
|
|
53
|
+
return { id: rec.id, ok: !isErr(result), before: rec.error_first_line, after: String(result).split('\n')[0].slice(0, 200) };
|
|
54
|
+
} finally {
|
|
55
|
+
try { rmSync(tmp, { recursive: true, force: true }); } catch {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const jsonMode = hasFlag('--json');
|
|
60
|
+
const records = loadRecords();
|
|
61
|
+
|
|
62
|
+
if (hasFlag('--list') || (!hasFlag('--replay-all') && !argValue('--replay'))) {
|
|
63
|
+
if (jsonMode) { console.log(JSON.stringify(records.map(({ file_snapshots, args, ...m }) => m), null, 2)); process.exit(0); }
|
|
64
|
+
console.log(`captured apply_patch failures: ${records.length} (dir: ${replayDir()})`);
|
|
65
|
+
for (const r of records.slice(0, 50)) {
|
|
66
|
+
console.log(`- ${r.id} targets=${(r.targets || []).length} ${new Date(r.ts).toISOString()}`);
|
|
67
|
+
console.log(` ${String(r.error_first_line || '').slice(0, 140)}`);
|
|
68
|
+
}
|
|
69
|
+
if (!records.length) console.log('(none - set MIXDOG_PATCH_REPLAY_CAPTURE=1 to capture)');
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const one = argValue('--replay', null);
|
|
74
|
+
const targets = one ? records.filter((r) => r.id === one || r.id.startsWith(one)) : records;
|
|
75
|
+
if (!targets.length) { console.error(one ? `no replay matched: ${one}` : 'no captured failures'); process.exit(1); }
|
|
76
|
+
|
|
77
|
+
const results = [];
|
|
78
|
+
for (const rec of targets) results.push(await replayOne(rec));
|
|
79
|
+
const passed = results.filter((r) => r.ok).length;
|
|
80
|
+
|
|
81
|
+
if (jsonMode) {
|
|
82
|
+
console.log(JSON.stringify({ total: results.length, passed, failed: results.length - passed, results }, null, 2));
|
|
83
|
+
} else {
|
|
84
|
+
console.log(`patch-replay: ${passed}/${results.length} now succeed`);
|
|
85
|
+
for (const r of results) {
|
|
86
|
+
console.log(`- ${r.id}: ${r.ok ? 'PASS' : 'still fails'}`);
|
|
87
|
+
if (!r.ok) console.log(` after: ${r.after}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
process.exitCode = passed === results.length ? 0 : 1;
|