mixdog 0.9.18 → 0.9.20
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/LICENSE +21 -0
- package/README.md +93 -29
- package/package.json +5 -3
- package/scripts/build-runtime-windows.ps1 +242 -242
- package/scripts/build-tui.mjs +6 -0
- package/scripts/hook-bus-test.mjs +8 -0
- package/scripts/log-writer-guard-smoke.mjs +131 -0
- package/scripts/output-style-smoke.mjs +2 -2
- package/scripts/reactive-compact-persist-smoke.mjs +22 -6
- package/scripts/recall-bench-cases.json +10 -0
- package/scripts/recall-bench.mjs +91 -2
- package/scripts/recall-quality-cases.json +1 -2
- package/scripts/recall-usecase-cases.json +1 -1
- package/scripts/session-ingest-compaction-smoke.mjs +241 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -106
- package/scripts/tool-efficiency-diag.mjs +5 -2
- package/scripts/tool-smoke.mjs +251 -72
- package/src/agents/debugger/AGENT.md +3 -3
- package/src/agents/heavy-worker/AGENT.md +7 -10
- package/src/agents/maintainer/AGENT.md +1 -2
- package/src/agents/reviewer/AGENT.md +1 -2
- package/src/agents/worker/AGENT.md +5 -8
- package/src/defaults/agents.json +3 -0
- package/src/help.mjs +2 -5
- package/src/lib/mixdog-debug.cjs +13 -0
- package/src/mixdog-session-runtime.mjs +7 -3311
- package/src/rules/agent/00-core.md +4 -3
- package/src/rules/agent/30-explorer.md +53 -22
- package/src/rules/lead/02-channels.md +3 -3
- package/src/rules/lead/lead-tool.md +3 -2
- package/src/rules/shared/01-tool.md +24 -29
- package/src/runtime/agent/orchestrator/context/collect.mjs +33 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +24 -3
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +663 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +153 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +250 -35
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/loop.mjs +8 -1860
- package/src/runtime/agent/orchestrator/session/manager/agent-runtime-singleton.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +686 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +60 -12
- package/src/runtime/agent/orchestrator/session/manager/env-utils.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +159 -0
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +143 -0
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +268 -0
- package/src/runtime/agent/orchestrator/session/manager/provider-cache-key.mjs +22 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +124 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +258 -0
- package/src/runtime/agent/orchestrator/session/manager/session-errors.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-id.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +475 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +6 -4
- package/src/runtime/agent/orchestrator/session/manager.mjs +88 -2285
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +440 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +153 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +642 -0
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +23 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +198 -6
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +13 -15
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +45 -3
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +195 -3
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +108 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +15 -3
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +24 -2
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +38 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +72 -8
- package/src/runtime/agent/orchestrator/tools/shell-policy-danger-target.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +1 -1
- package/src/runtime/channels/backends/discord-gateway.mjs +14 -1
- package/src/runtime/channels/backends/discord.mjs +43 -1
- package/src/runtime/channels/index.mjs +6 -2096
- package/src/runtime/channels/lib/inbound-handler.mjs +328 -0
- package/src/runtime/channels/lib/inbound-routing.mjs +19 -12
- package/src/runtime/channels/lib/interaction-handlers.mjs +260 -0
- package/src/runtime/channels/lib/memory-client.mjs +32 -14
- package/src/runtime/channels/lib/network-retry.mjs +23 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +38 -7
- package/src/runtime/channels/lib/owned-runtime.mjs +547 -0
- package/src/runtime/channels/lib/owner-heartbeat.mjs +13 -4
- package/src/runtime/channels/lib/runtime-paths.mjs +35 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +17 -7
- package/src/runtime/channels/lib/transcript-binding.mjs +336 -0
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +39 -2
- package/src/runtime/channels/lib/worker-bootstrap.mjs +97 -0
- package/src/runtime/channels/lib/worker-ipc.mjs +201 -0
- package/src/runtime/channels/lib/worker-main.mjs +771 -0
- package/src/runtime/memory/index.mjs +73 -1725
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -2
- package/src/runtime/memory/lib/embedding-worker.mjs +47 -6
- package/src/runtime/memory/lib/http-router.mjs +772 -0
- package/src/runtime/memory/lib/memory-action-handlers.mjs +901 -0
- package/src/runtime/memory/lib/memory-embed.mjs +28 -7
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +8 -2
- package/src/runtime/memory/lib/memory-recall-store.mjs +182 -9
- package/src/runtime/memory/lib/query-handlers.mjs +38 -6
- package/src/runtime/memory/lib/recall-format.mjs +106 -6
- package/src/runtime/memory/lib/session-ingest-runtime.mjs +401 -0
- package/src/runtime/memory/lib/session-ingest.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +10 -4
- package/src/runtime/shared/background-tasks.mjs +4 -2
- package/src/runtime/shared/tool-execution-contract.mjs +1 -1
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +30 -1
- package/src/session-runtime/boot-profile.mjs +36 -0
- package/src/session-runtime/channel-config-api.mjs +70 -0
- package/src/session-runtime/config-lifecycle.mjs +1 -1
- package/src/session-runtime/context-status.mjs +181 -0
- package/src/session-runtime/cwd-plugins.mjs +46 -3
- package/src/session-runtime/env.mjs +17 -0
- package/src/session-runtime/lifecycle-api.mjs +242 -0
- package/src/session-runtime/mcp-glue.mjs +24 -3
- package/src/session-runtime/model-route-api.mjs +198 -0
- package/src/session-runtime/output-styles.mjs +44 -10
- package/src/session-runtime/provider-auth-api.mjs +135 -0
- package/src/session-runtime/resource-api.mjs +282 -0
- package/src/session-runtime/runtime-core.mjs +2046 -0
- package/src/session-runtime/session-turn-api.mjs +274 -0
- package/src/session-runtime/tool-catalog.mjs +18 -264
- package/src/session-runtime/tool-defs.mjs +2 -2
- package/src/session-runtime/workflow-agents-api.mjs +238 -0
- package/src/session-runtime/workflow.mjs +16 -1
- package/src/standalone/agent-tool.mjs +2 -2
- package/src/standalone/channel-worker.mjs +78 -5
- package/src/standalone/explore-tool.mjs +1 -1
- package/src/standalone/memory-runtime-proxy.mjs +56 -6
- package/src/tui/App.jsx +88 -97
- package/src/tui/app/channel-pickers.mjs +45 -0
- package/src/tui/app/core-memory-picker.mjs +1 -1
- package/src/tui/app/slash-commands.mjs +0 -1
- package/src/tui/app/slash-dispatch.mjs +0 -16
- package/src/tui/app/transcript-window.mjs +44 -1
- package/src/tui/app/use-mouse-input.mjs +15 -2
- package/src/tui/app/use-prompt-handlers.mjs +7 -94
- package/src/tui/app/use-transcript-scroll.mjs +82 -4
- package/src/tui/app/use-transcript-window.mjs +65 -5
- package/src/tui/components/PromptInput.jsx +33 -64
- package/src/tui/components/ToolExecution.jsx +2 -2
- package/src/tui/dist/index.mjs +7908 -7558
- package/src/tui/engine/context-state.mjs +145 -0
- package/src/tui/engine/prompt-history.mjs +27 -0
- package/src/tui/engine/session-api-ext.mjs +478 -0
- package/src/tui/engine/session-api.mjs +545 -0
- package/src/tui/engine/session-flow.mjs +485 -0
- package/src/tui/engine/turn.mjs +1078 -0
- package/src/tui/engine.mjs +69 -2582
- package/src/tui/index.jsx +7 -0
- package/src/tui/lib/voice-setup.mjs +166 -0
- package/src/tui/paste-attachments.mjs +12 -5
- package/src/tui/prompt-history-store.mjs +125 -12
- package/vendor/ink/build/ink.js +16 -1
- package/vendor/ink/build/output.js +30 -4
- package/vendor/ink/build/render.js +5 -0
- package/scripts/bench/cache-probe-tasks.json +0 -8
- package/scripts/bench/lead-review-tasks-r3.json +0 -20
- package/scripts/bench/lead-review-tasks.json +0 -20
- package/scripts/bench/r4-mixed-tasks.json +0 -20
- package/scripts/bench/r5-orchestrated-task.json +0 -7
- package/scripts/bench/review-tasks.json +0 -20
- package/scripts/bench/round-codex.json +0 -114
- package/scripts/bench/round-mixdog-lead-r3.json +0 -269
- package/scripts/bench/round-mixdog-lead.json +0 -269
- package/scripts/bench/round-mixdog.json +0 -126
- package/scripts/bench/round-r10-bigsample.json +0 -679
- package/scripts/bench/round-r11-codexalign.json +0 -257
- package/scripts/bench/round-r13-clientmeta.json +0 -464
- package/scripts/bench/round-r14-betafeatures.json +0 -466
- package/scripts/bench/round-r15-fulldefault.json +0 -462
- package/scripts/bench/round-r16-sessionid.json +0 -466
- package/scripts/bench/round-r17-wirebytes.json +0 -456
- package/scripts/bench/round-r18-prewarm.json +0 -468
- package/scripts/bench/round-r19-clean.json +0 -472
- package/scripts/bench/round-r20-prewarm-clean.json +0 -475
- package/scripts/bench/round-r21-delta-retry.json +0 -473
- package/scripts/bench/round-r22-full-probe.json +0 -693
- package/scripts/bench/round-r23-itemprobe.json +0 -701
- package/scripts/bench/round-r24-shapefix.json +0 -677
- package/scripts/bench/round-r25-serial.json +0 -464
- package/scripts/bench/round-r26-parallel3.json +0 -671
- package/scripts/bench/round-r27-parallel10.json +0 -894
- package/scripts/bench/round-r28-parallel10-stagger.json +0 -882
- package/scripts/bench/round-r29-parallel10-stagger166.json +0 -886
- package/scripts/bench/round-r30-instid.json +0 -253
- package/scripts/bench/round-r31-upgradeprobe.json +0 -256
- package/scripts/bench/round-r32-vs-codex-lead.json +0 -254
- package/scripts/bench/round-r33-vs-codex-codex.json +0 -115
- package/scripts/bench/round-r34-orchestrated.json +0 -120
- package/scripts/bench/round-r35-orchestrated-codex.json +0 -61
- package/scripts/bench/round-r36-orchestrated-capped.json +0 -128
- package/scripts/bench/round-r4-codex.json +0 -114
- package/scripts/bench/round-r4-mixed.json +0 -225
- package/scripts/bench/round-r5-gpt-lead.json +0 -259
- package/scripts/bench/round-r6-codex.json +0 -114
- package/scripts/bench/round-r6-solo.json +0 -257
- package/scripts/bench/round-r7-full.json +0 -254
- package/scripts/bench/round-r8-fulldefault.json +0 -255
- package/src/tui/components/prompt-input/voice-indicator.mjs +0 -39
- package/src/tui/lib/voice-recorder.mjs +0 -469
- package/src/workflows/sequential/WORKFLOW.md +0 -51
package/scripts/tool-smoke.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { __applyStandaloneToolDefaultsForTest, __renderToolSearchForTest, compactToolSearchDescription, defaultDeferredToolNames, SKILL_TOOL, TOOL_SEARCH_TOOL } from '../src/mixdog-session-runtime.mjs';
|
|
7
|
+
import { applyInitialDeferredToolManifestToBp1, buildDeferredToolManifest } from '../src/runtime/agent/orchestrator/context/collect.mjs';
|
|
7
8
|
import { buildExplorerPrompt, EXPLORE_TOOL, MAX_FANOUT_QUERIES, normalizeExploreQueries } from '../src/standalone/explore-tool.mjs';
|
|
8
9
|
import { AGENT_TOOL, createStandaloneAgent } from '../src/standalone/agent-tool.mjs';
|
|
9
10
|
import { parseHeadlessRoleCommand } from '../src/app.mjs';
|
|
@@ -508,8 +509,8 @@ if (!/^read 2\b/m.test(String(readRegionBatchOut))
|
|
|
508
509
|
|| (String(readRegionBatchOut).match(/scripts\/smoke\.mjs \[full\] \[ok\]/g) || []).length < 2
|
|
509
510
|
|| !/1→import \{ spawnSync \}/.test(String(readRegionBatchOut))
|
|
510
511
|
|| !/3→import \{ fileURLToPath \}/.test(String(readRegionBatchOut))
|
|
511
|
-
|| !/pass offset:2 to continue/.test(String(readRegionBatchOut))
|
|
512
|
-
|| !/pass offset:4 to continue/.test(String(readRegionBatchOut))) {
|
|
512
|
+
|| !/(pass offset:2 to continue|ONE window: offset:2, limit:\d+)/.test(String(readRegionBatchOut))
|
|
513
|
+
|| !/(pass offset:4 to continue|ONE window: offset:4, limit:\d+)/.test(String(readRegionBatchOut))) {
|
|
513
514
|
throw new Error(`read region batch must preserve both requested spans:\n${readRegionBatchOut}`);
|
|
514
515
|
}
|
|
515
516
|
|
|
@@ -534,6 +535,46 @@ if (readStringifiedLineErr || readStringifiedLineArgs.path[0].offset !== 7 || re
|
|
|
534
535
|
throw new Error(`read guard must losslessly convert legacy line/context inside stringified arrays to offset/limit: err=${readStringifiedLineErr} args=${JSON.stringify(readStringifiedLineArgs)}`);
|
|
535
536
|
}
|
|
536
537
|
|
|
538
|
+
// Absorb shape 1: region array + top-level offset/limit → top-level becomes
|
|
539
|
+
// the default window for regions that lack their own; no hard error.
|
|
540
|
+
const readRegionPlusTopLevelArgs = {
|
|
541
|
+
path: [{ path: 'scripts/smoke.mjs', offset: 3, limit: 4 }, { path: 'scripts/smoke.mjs' }],
|
|
542
|
+
offset: 0,
|
|
543
|
+
limit: 2,
|
|
544
|
+
};
|
|
545
|
+
const readRegionPlusTopLevelErr = validateBuiltinArgs('read', readRegionPlusTopLevelArgs);
|
|
546
|
+
if (readRegionPlusTopLevelErr
|
|
547
|
+
|| 'offset' in readRegionPlusTopLevelArgs || 'limit' in readRegionPlusTopLevelArgs
|
|
548
|
+
|| readRegionPlusTopLevelArgs.path[0].offset !== 3 || readRegionPlusTopLevelArgs.path[0].limit !== 4
|
|
549
|
+
|| readRegionPlusTopLevelArgs.path[1].offset !== 0 || readRegionPlusTopLevelArgs.path[1].limit !== 2) {
|
|
550
|
+
throw new Error(`read guard must absorb region-array + top-level offset/limit: err=${readRegionPlusTopLevelErr} args=${JSON.stringify(readRegionPlusTopLevelArgs)}`);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Absorb shape 2: parallel offset/limit as JSON-stringified arrays with path[]
|
|
554
|
+
// → zipped into per-file region objects (pairwise recovery), no int error.
|
|
555
|
+
const readZipWindowArgs = {
|
|
556
|
+
path: ['scripts/smoke.mjs', 'scripts/smoke.mjs'],
|
|
557
|
+
offset: '[0, 5]',
|
|
558
|
+
limit: '[2, 3]',
|
|
559
|
+
};
|
|
560
|
+
const readZipWindowErr = validateBuiltinArgs('read', readZipWindowArgs);
|
|
561
|
+
if (readZipWindowErr || !Array.isArray(readZipWindowArgs.path)
|
|
562
|
+
|| readZipWindowArgs.path[0].offset !== 0 || readZipWindowArgs.path[0].limit !== 2
|
|
563
|
+
|| readZipWindowArgs.path[1].offset !== 5 || readZipWindowArgs.path[1].limit !== 3
|
|
564
|
+
|| 'offset' in readZipWindowArgs || 'limit' in readZipWindowArgs) {
|
|
565
|
+
throw new Error(`read guard must zip stringified offset/limit arrays onto path[]: err=${readZipWindowErr} args=${JSON.stringify(readZipWindowArgs)}`);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Absorb shape 3: code_graph file/files as a JSON-stringified array → parsed to
|
|
569
|
+
// a real array before lookup (dispatched into files[]).
|
|
570
|
+
const cgStringifiedFileArgs = { mode: 'symbols', file: JSON.stringify(['a.mjs', 'b.mjs']) };
|
|
571
|
+
const cgStringifiedFileErr = validateBuiltinArgs('code_graph', cgStringifiedFileArgs);
|
|
572
|
+
if (cgStringifiedFileErr || 'file' in cgStringifiedFileArgs
|
|
573
|
+
|| !Array.isArray(cgStringifiedFileArgs.files)
|
|
574
|
+
|| cgStringifiedFileArgs.files[0] !== 'a.mjs' || cgStringifiedFileArgs.files[1] !== 'b.mjs') {
|
|
575
|
+
throw new Error(`code_graph guard must parse JSON-stringified file array: err=${cgStringifiedFileErr} args=${JSON.stringify(cgStringifiedFileArgs)}`);
|
|
576
|
+
}
|
|
577
|
+
|
|
537
578
|
const graphOut = await executeCodeGraphTool('code_graph', {
|
|
538
579
|
mode: 'symbols',
|
|
539
580
|
file: 'scripts/smoke.mjs',
|
|
@@ -549,6 +590,17 @@ if (!/# symbol_search executeBuiltinTool\b/.test(String(graphSymbolBatchOut)) ||
|
|
|
549
590
|
throw new Error(`code_graph symbol_search symbols[] batch execution failed:\n${graphSymbolBatchOut}`);
|
|
550
591
|
}
|
|
551
592
|
|
|
593
|
+
// Absorb shape 3 (real dispatch): file as a JSON-stringified array batches per
|
|
594
|
+
// file instead of hitting "file not found: [...]".
|
|
595
|
+
const graphStringifiedFileOut = await executeCodeGraphTool('code_graph', {
|
|
596
|
+
mode: 'symbols',
|
|
597
|
+
file: JSON.stringify(['scripts/smoke.mjs']),
|
|
598
|
+
}, root);
|
|
599
|
+
if (/file not found/.test(String(graphStringifiedFileOut))
|
|
600
|
+
|| !/binding|spawnSync|symbol/i.test(String(graphStringifiedFileOut))) {
|
|
601
|
+
throw new Error(`code_graph must parse JSON-stringified file array before lookup:\n${graphStringifiedFileOut}`);
|
|
602
|
+
}
|
|
603
|
+
|
|
552
604
|
const graphMissingFileOut = await executeCodeGraphTool('code_graph', {
|
|
553
605
|
mode: 'symbols',
|
|
554
606
|
file: 'src/runtime/loop.mjs',
|
|
@@ -593,6 +645,72 @@ if (!/^Error[\s:[]/.test(String(stalePatchOut)) || !/apply_patch/i.test(String(s
|
|
|
593
645
|
throw new Error(`apply_patch stale context must return an Error result, not throw or pass:\n${stalePatchOut}`);
|
|
594
646
|
}
|
|
595
647
|
|
|
648
|
+
// Malformed-but-unambiguous patch openings must be absorbed (dry-run, so no
|
|
649
|
+
// write). Each targets the same known-good smoke.mjs line the cases above use.
|
|
650
|
+
const smokeBody = `@@
|
|
651
|
+
-process.stdout.write('smoke passed ✓\\n');
|
|
652
|
+
+process.stdout.write('smoke passed ok\\n');
|
|
653
|
+
*** End Patch
|
|
654
|
+
`;
|
|
655
|
+
const absorbCases = [
|
|
656
|
+
['leading blank lines', `\n\n*** Begin Patch\n*** Update File: scripts/smoke.mjs\n${smokeBody}`],
|
|
657
|
+
['decorated begin header', `*** Begin Patch (V4A) ***\n*** Update File: scripts/smoke.mjs\n${smokeBody}`],
|
|
658
|
+
['bare file path opening', `*** Begin Patch\nscripts/smoke.mjs\n${smokeBody}`],
|
|
659
|
+
['File: prefixed opening', `*** Begin Patch\nFile: scripts/smoke.mjs\n${smokeBody}`],
|
|
660
|
+
['unified body in envelope', `*** Begin Patch\n--- scripts/smoke.mjs\n+++ scripts/smoke.mjs\n${smokeBody}`],
|
|
661
|
+
];
|
|
662
|
+
for (const [label, patch] of absorbCases) {
|
|
663
|
+
const out = await executePatchTool('apply_patch', { base_path: root, dry_run: true, fuzzy: false, patch }, root);
|
|
664
|
+
assertOk(`apply_patch absorbs ${label}`, out, /checked|validated|dry|OK/i);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const ambiguousPatchOut = await executePatchTool('apply_patch', {
|
|
668
|
+
base_path: root,
|
|
669
|
+
dry_run: true,
|
|
670
|
+
fuzzy: false,
|
|
671
|
+
patch: `*** Begin Patch\nthis line is not a valid opening\n${smokeBody}`,
|
|
672
|
+
}, root);
|
|
673
|
+
if (!/^Error[\s:[]/.test(String(ambiguousPatchOut)) || !/before a file header|V4A/i.test(String(ambiguousPatchOut))) {
|
|
674
|
+
throw new Error(`apply_patch must keep erroring on genuinely ambiguous openings:\n${ambiguousPatchOut}`);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Unified-looking first body line but real V4A file sections appear later: the
|
|
678
|
+
// envelope must NOT be stripped to unified — it stays ambiguous and errors.
|
|
679
|
+
const mixedPatchOut = await executePatchTool('apply_patch', {
|
|
680
|
+
base_path: root,
|
|
681
|
+
dry_run: true,
|
|
682
|
+
fuzzy: false,
|
|
683
|
+
patch: `*** Begin Patch\n--- scripts/smoke.mjs\n*** Update File: scripts/smoke.mjs\n${smokeBody}`,
|
|
684
|
+
}, root);
|
|
685
|
+
if (!/^Error[\s:[]/.test(String(mixedPatchOut)) || !/before a file header|V4A/i.test(String(mixedPatchOut))) {
|
|
686
|
+
throw new Error(`apply_patch must keep erroring on mixed unified/V4A openings:\n${mixedPatchOut}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Compacted-history placeholder guard: EVERY [mixdog compacted …] variant must
|
|
690
|
+
// be rejected with the corrective message BEFORE format dispatch/salvage, both
|
|
691
|
+
// as the first line and standalone mid-body (after a *** Begin Patch header).
|
|
692
|
+
const compactedGuardCases = [
|
|
693
|
+
['legacy key: prefix', '[mixdog compacted patch: 4096 chars, sha256:deadbeefdeadbeef]\n*** Begin Patch\n*** Update File: a.txt\n+x\n*** End Patch\n'],
|
|
694
|
+
['variant key form', '[mixdog compacted patch v4a, sha256:deadbeefdeadbeef]\n*** Begin Patch\n*** Update File: a.txt\n+x\n*** End Patch\n'],
|
|
695
|
+
['no chars/sha detail', '[mixdog compacted old_string]\n'],
|
|
696
|
+
['mid-body standalone', '*** Begin Patch\n*** Update File: a.txt\n[mixdog compacted patch v4a, sha256:deadbeefdeadbeef]\n*** End Patch\n'],
|
|
697
|
+
];
|
|
698
|
+
for (const [label, patch] of compactedGuardCases) {
|
|
699
|
+
const out = await executePatchTool('apply_patch', { base_path: root, dry_run: true, fuzzy: false, patch }, root);
|
|
700
|
+
if (!/^Error[\s:[]/.test(String(out)) || !/compacted-history placeholder/i.test(String(out))) {
|
|
701
|
+
throw new Error(`apply_patch must reject compacted placeholder (${label}):\n${out}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
// A legit unified edit whose body content mentions the literal text on a diff
|
|
705
|
+
// line (+/-/space) must still parse — the guard only trips on non-diff lines.
|
|
706
|
+
const compactedFalsePositiveOut = await executePatchTool('apply_patch', {
|
|
707
|
+
base_path: root,
|
|
708
|
+
dry_run: true,
|
|
709
|
+
fuzzy: false,
|
|
710
|
+
patch: `*** Begin Patch\n*** Add File: compacted-note.txt\n+[mixdog compacted patch: 10 chars, sha256:abc]\n*** End Patch\n`,
|
|
711
|
+
}, root);
|
|
712
|
+
assertOk('apply_patch keeps diff-line placeholder text', compactedFalsePositiveOut, /checked|validated|dry|OK/i);
|
|
713
|
+
|
|
596
714
|
const shellOutPromise = executeBuiltinTool('shell', {
|
|
597
715
|
command: 'node --version',
|
|
598
716
|
cwd: root,
|
|
@@ -634,6 +752,33 @@ if (!/^Error[\s:[]/.test(String(shellTimeoutOut)) || !/\[timeout: 500ms\b/.test(
|
|
|
634
752
|
throw new Error(`bash timeout must be milliseconds and classified as Error:\n${shellTimeoutOut}`);
|
|
635
753
|
}
|
|
636
754
|
|
|
755
|
+
// Auto-promotion: a sync foreground command still running past the (soft)
|
|
756
|
+
// promotion budget is detached into a tracked background task and returns the
|
|
757
|
+
// same task_id envelope as explicit async — the caller never pre-chose async.
|
|
758
|
+
// Shrink the budget via MIXDOG_SHELL_AUTO_BACKGROUND_MS so the smoke stays fast.
|
|
759
|
+
const _priorAutoBgBudget = process.env.MIXDOG_SHELL_AUTO_BACKGROUND_MS;
|
|
760
|
+
process.env.MIXDOG_SHELL_AUTO_BACKGROUND_MS = '800';
|
|
761
|
+
let shellAutoPromoteOut;
|
|
762
|
+
try {
|
|
763
|
+
shellAutoPromoteOut = await executeBuiltinTool('shell', {
|
|
764
|
+
command: 'Start-Sleep -Seconds 6; Write-Output tool-smoke-autopromote-done',
|
|
765
|
+
cwd: root,
|
|
766
|
+
timeout: 30_000,
|
|
767
|
+
shell: 'powershell',
|
|
768
|
+
}, root);
|
|
769
|
+
} finally {
|
|
770
|
+
if (_priorAutoBgBudget === undefined) delete process.env.MIXDOG_SHELL_AUTO_BACKGROUND_MS;
|
|
771
|
+
else process.env.MIXDOG_SHELL_AUTO_BACKGROUND_MS = _priorAutoBgBudget;
|
|
772
|
+
}
|
|
773
|
+
if (!/auto-backgrounded/i.test(String(shellAutoPromoteOut)) || !/task_id:\s*\S+/i.test(String(shellAutoPromoteOut))) {
|
|
774
|
+
throw new Error(`shell auto-promotion must return a background task envelope (task_id + auto-backgrounded):\n${shellAutoPromoteOut}`);
|
|
775
|
+
}
|
|
776
|
+
// Clean up the promoted job so it doesn't outlive the smoke as an orphan.
|
|
777
|
+
const _autoPromoteTaskId = (/task_id:\s*(\S+)/i.exec(String(shellAutoPromoteOut)) || [])[1];
|
|
778
|
+
if (_autoPromoteTaskId) {
|
|
779
|
+
try { await executeBuiltinTool('task', { action: 'cancel', task_id: _autoPromoteTaskId }, root); } catch {}
|
|
780
|
+
}
|
|
781
|
+
|
|
637
782
|
const legacyEscapedAlternationErr = validateBuiltinArgs('grep', { pattern: 'state\\.items\\.map\\|items\\.map', path: root });
|
|
638
783
|
if (legacyEscapedAlternationErr) {
|
|
639
784
|
throw new Error(`grep legacy \\| alternation should be accepted: ${legacyEscapedAlternationErr}`);
|
|
@@ -1217,8 +1362,10 @@ setInternalToolsProvider({
|
|
|
1217
1362
|
if (workerToolNames.includes('tool_search')) {
|
|
1218
1363
|
throw new Error(`agent session schema must not expose deferred tool_search: ${workerToolNames.join(', ')}`);
|
|
1219
1364
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1365
|
+
for (const name of ['shell', 'task']) {
|
|
1366
|
+
if (!workerToolNames.includes(name)) {
|
|
1367
|
+
throw new Error(`read-write agent session schema must expose ${name} for self-verification: ${workerToolNames.join(', ')}`);
|
|
1368
|
+
}
|
|
1222
1369
|
}
|
|
1223
1370
|
for (const name of ['skills_list', 'skill_view', 'skill_execute']) {
|
|
1224
1371
|
if (workerToolNames.includes(name)) {
|
|
@@ -1266,7 +1413,7 @@ setInternalToolsProvider({
|
|
|
1266
1413
|
const fullTools = (fullAgentSession.tools || []).map((tool) => tool?.name).filter(Boolean);
|
|
1267
1414
|
const publicExploreTools = (publicExploreSession.tools || []).map((tool) => tool?.name).filter(Boolean);
|
|
1268
1415
|
const expectedReadTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'explore', 'search', 'web_fetch', 'Skill'];
|
|
1269
|
-
const expectedWriteTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'explore', 'search', 'web_fetch', 'Skill'];
|
|
1416
|
+
const expectedWriteTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'shell', 'task', 'explore', 'search', 'web_fetch', 'Skill'];
|
|
1270
1417
|
if (JSON.stringify(readTools) !== JSON.stringify(expectedReadTools)) {
|
|
1271
1418
|
throw new Error(`read agent schema must be fixed allow-list: expected=${expectedReadTools.join(', ')} actual=${readTools.join(', ')}`);
|
|
1272
1419
|
}
|
|
@@ -1276,20 +1423,17 @@ setInternalToolsProvider({
|
|
|
1276
1423
|
if (readTools.includes('tool_search') || writeTools.includes('tool_search')) {
|
|
1277
1424
|
throw new Error(`agent session fixed schemas must omit tool_search: read=${readTools.join(', ')} write=${writeTools.join(', ')}`);
|
|
1278
1425
|
}
|
|
1279
|
-
if (readTools.includes('shell')
|
|
1280
|
-
throw new Error(`read
|
|
1426
|
+
if (readTools.includes('shell')) {
|
|
1427
|
+
throw new Error(`read agent schema must omit shell: read=${readTools.join(', ')}`);
|
|
1281
1428
|
}
|
|
1282
1429
|
for (const name of ['shell', 'apply_patch', 'task']) {
|
|
1283
1430
|
if (readTools.includes(name)) {
|
|
1284
1431
|
throw new Error(`read agent schema must omit non-read tool ${name}: read=${readTools.join(', ')}`);
|
|
1285
1432
|
}
|
|
1286
1433
|
}
|
|
1287
|
-
for (const name of ['apply_patch', 'task']) {
|
|
1288
|
-
if (
|
|
1289
|
-
throw new Error(`read-write agent schema must preserve
|
|
1290
|
-
}
|
|
1291
|
-
if (name !== 'apply_patch' && writeTools.includes(name)) {
|
|
1292
|
-
throw new Error(`read-write agent schema must omit non-edit tool ${name}: write=${writeTools.join(', ')}`);
|
|
1434
|
+
for (const name of ['apply_patch', 'shell', 'task']) {
|
|
1435
|
+
if (!writeTools.includes(name)) {
|
|
1436
|
+
throw new Error(`read-write agent schema must preserve ${name}: write=${writeTools.join(', ')}`);
|
|
1293
1437
|
}
|
|
1294
1438
|
}
|
|
1295
1439
|
for (const name of ['memory', 'recall', 'reply']) {
|
|
@@ -1334,7 +1478,7 @@ setInternalToolsProvider({
|
|
|
1334
1478
|
try {
|
|
1335
1479
|
const resumed = await resumeSession(resumeAgentSession.id, 'full');
|
|
1336
1480
|
const resumedTools = (resumed?.tools || []).map((tool) => tool?.name).filter(Boolean);
|
|
1337
|
-
const expectedWriteTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'explore', 'search', 'web_fetch', 'Skill'];
|
|
1481
|
+
const expectedWriteTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'shell', 'task', 'explore', 'search', 'web_fetch', 'Skill'];
|
|
1338
1482
|
if (JSON.stringify(resumedTools) !== JSON.stringify(expectedWriteTools)) {
|
|
1339
1483
|
throw new Error(`resumed read-write agent schema must keep fixed allow-list: expected=${expectedWriteTools.join(', ')} actual=${resumedTools.join(', ')}`);
|
|
1340
1484
|
}
|
|
@@ -1408,7 +1552,10 @@ setInternalToolsProvider({
|
|
|
1408
1552
|
const resumed = await resumeSession(session.id, 'full');
|
|
1409
1553
|
const resumedTools = (resumed?.tools || []).map((tool) => tool?.name).filter(Boolean);
|
|
1410
1554
|
const expected = expectedForHiddenAgent(permission, schemaAllowedTools);
|
|
1411
|
-
|
|
1555
|
+
// Order-insensitive: the session tool surface follows catalog order, while
|
|
1556
|
+
// schemaAllowedTools declares an allow-set; only set equality is contractual.
|
|
1557
|
+
const asSet = (list) => JSON.stringify(list.slice().sort());
|
|
1558
|
+
if (expected && (asSet(tools) !== asSet(expected) || asSet(resumedTools) !== asSet(expected))) {
|
|
1412
1559
|
throw new Error(`hidden agent ${agent} schema mismatch: expected=${expected.join(', ')} tools=${tools.join(', ')} resumed=${resumedTools.join(', ')}`);
|
|
1413
1560
|
}
|
|
1414
1561
|
const leaked = tools.filter((name) => hiddenBadTools.has(name) && !(expected || []).includes(name));
|
|
@@ -1522,7 +1669,7 @@ const readPathDescription = readPathSchema.description || '';
|
|
|
1522
1669
|
if (!/File path/i.test(readPathDescription) || !/\{path,offset,limit\}\[\]/i.test(readPathDescription) || !/Pass arrays directly/i.test(readPathDescription) || !/legacy recovery only/i.test(readPathDescription)) {
|
|
1523
1670
|
throw new Error('read schema must keep directory-vs-file guidance');
|
|
1524
1671
|
}
|
|
1525
|
-
if (!/
|
|
1672
|
+
if (!/Not for directory listing/i.test((BUILTIN_TOOLS.find((tool) => tool.name === 'read')?.description) || '')) {
|
|
1526
1673
|
throw new Error('read description must keep directory-vs-file guidance');
|
|
1527
1674
|
}
|
|
1528
1675
|
const readTool = BUILTIN_TOOLS.find((tool) => tool.name === 'read');
|
|
@@ -1533,7 +1680,7 @@ const readArrayItemAnyOf = readArraySchema?.items?.anyOf || [];
|
|
|
1533
1680
|
if (!readArrayItemAnyOf.some((entry) => entry?.type === 'object' && entry?.properties?.offset && entry?.properties?.limit)) {
|
|
1534
1681
|
throw new Error('read schema must expose array-of-region objects for batched spans');
|
|
1535
1682
|
}
|
|
1536
|
-
if (/line\+context/i.test(readDescription) || !/numeric offset\+limit/i.test(readDescription) || !/Batch paths\/regions as real arrays/i.test(readDescription)
|
|
1683
|
+
if (/line\+context/i.test(readDescription) || !/numeric offset\+limit/i.test(readDescription) || !/Batch paths\/regions as real arrays/i.test(readDescription)) {
|
|
1537
1684
|
throw new Error('read description must expose offset/limit as the single window form');
|
|
1538
1685
|
}
|
|
1539
1686
|
if (readProps.line || readProps.context) {
|
|
@@ -1567,15 +1714,25 @@ if (/line\/context/i.test(JSON.stringify(readTool?.inputSchema || {}))) {
|
|
|
1567
1714
|
// session's provider/model in place, or a mid-conversation model/provider
|
|
1568
1715
|
// switch silently forces a full prompt-cache rewrite (seen as a
|
|
1569
1716
|
// promptΔ spike + cache_ratio=0% turn in session-bench).
|
|
1570
|
-
|
|
1717
|
+
// God-file splits move implementation into module dirs; scan facade + all
|
|
1718
|
+
// split modules so these source-text guards survive refactors.
|
|
1719
|
+
const readMjsSources = (rel) => {
|
|
1720
|
+
const abs = resolve(root, rel);
|
|
1721
|
+
if (rel.endsWith('.mjs')) return readFileSync(abs, 'utf8');
|
|
1722
|
+
return readdirSync(abs, { recursive: true })
|
|
1723
|
+
.filter((f) => String(f).endsWith('.mjs'))
|
|
1724
|
+
.map((f) => readFileSync(resolve(abs, String(f)), 'utf8'))
|
|
1725
|
+
.join('\n');
|
|
1726
|
+
};
|
|
1727
|
+
const runtimeSrc = [readMjsSources('src/mixdog-session-runtime.mjs'), readMjsSources('src/session-runtime')].join('\n');
|
|
1571
1728
|
const setRouteBlock = runtimeSrc.match(/async setRoute\(next, options = \{\}\) \{[\s\S]*?\n \},\n/)?.[0] || '';
|
|
1572
1729
|
if (!/applyToCurrentSession = options\?\.applyToCurrentSession === true/.test(setRouteBlock)) {
|
|
1573
1730
|
throw new Error('setRoute must default applyToCurrentSession to false (model changes apply to the next session only)');
|
|
1574
1731
|
}
|
|
1575
|
-
if (!/if \(!applyToCurrentSession\)/.test(setRouteBlock) || !/return route;/.test(setRouteBlock)) {
|
|
1732
|
+
if (!/if \(!applyToCurrentSession\)/.test(setRouteBlock) || !/return (?:route|getRoute\(\));/.test(setRouteBlock)) {
|
|
1576
1733
|
throw new Error('setRoute must early-return before touching the live session when applyToCurrentSession is false');
|
|
1577
1734
|
}
|
|
1578
|
-
const engineSrc =
|
|
1735
|
+
const engineSrc = [readMjsSources('src/tui/engine.mjs'), readMjsSources('src/tui/engine')].join('\n');
|
|
1579
1736
|
if (/setRoute\(\{ model: m \}, \{ applyToCurrentSession: true \}\)/.test(engineSrc)) {
|
|
1580
1737
|
throw new Error('TUI setModel must not force applyToCurrentSession:true (model changes must apply to the next session only)');
|
|
1581
1738
|
}
|
|
@@ -1596,9 +1753,6 @@ if (codeGraphSymbolSearchErr) {
|
|
|
1596
1753
|
if (!/code structure\/flow/i.test(codeGraphDescription) || !/symbols\/references\/calls\/deps/i.test(codeGraphDescription)) {
|
|
1597
1754
|
throw new Error('code_graph description must stay structure-oriented and name its symbol modes');
|
|
1598
1755
|
}
|
|
1599
|
-
if (!/before text grep/i.test(codeGraphDescription)) {
|
|
1600
|
-
throw new Error('code_graph description must steer symbol/caller lookups to structure lookup before grep');
|
|
1601
|
-
}
|
|
1602
1756
|
if (!/repo-local/i.test(codeGraphDescription) || !/NOT web search|not web/i.test(codeGraphDescription)) {
|
|
1603
1757
|
throw new Error('code_graph description must mark itself repo-local (not web search)');
|
|
1604
1758
|
}
|
|
@@ -1700,16 +1854,23 @@ const toolSearchSession = {
|
|
|
1700
1854
|
deferredToolCatalog: smokeCatalog.slice(),
|
|
1701
1855
|
deferredSelectedTools: [...fullDefaults],
|
|
1702
1856
|
};
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
}
|
|
1857
|
+
// A plain query is a case-insensitive substring filter over name+description.
|
|
1858
|
+
// It lists matches only — it never auto-loads or ranks.
|
|
1859
|
+
const listQueryResult = JSON.parse(__renderToolSearchForTest({ query: 'shell' }, toolSearchSession, 'full'));
|
|
1860
|
+
if (listQueryResult.selected) {
|
|
1861
|
+
throw new Error(`tool_search plain query must not auto-load: ${JSON.stringify(listQueryResult.selected)}`);
|
|
1708
1862
|
}
|
|
1709
|
-
if (!
|
|
1710
|
-
throw new Error(`tool_search query should
|
|
1863
|
+
if (!listQueryResult.matches.some((row) => row.name === 'shell')) {
|
|
1864
|
+
throw new Error(`tool_search plain query should list substring matches: ${JSON.stringify(listQueryResult.matches)}`);
|
|
1711
1865
|
}
|
|
1866
|
+
if (listQueryResult.activeTools.includes('shell') || (Array.isArray(listQueryResult.discoveredTools) && listQueryResult.discoveredTools.includes('shell'))) {
|
|
1867
|
+
throw new Error(`tool_search plain query must not activate/discover tools: ${JSON.stringify(listQueryResult)}`);
|
|
1868
|
+
}
|
|
1869
|
+
// query "select:a,b" is the explicit query-side loader (aliases expand).
|
|
1712
1870
|
const bulkSelectResult = JSON.parse(__renderToolSearchForTest({ query: 'select:shell,recall' }, toolSearchSession, 'full'));
|
|
1871
|
+
if (bulkSelectResult.selected?.mode !== 'select') {
|
|
1872
|
+
throw new Error(`tool_search query-select must report select mode: ${JSON.stringify(bulkSelectResult.selected)}`);
|
|
1873
|
+
}
|
|
1713
1874
|
for (const name of ['shell', 'task', 'recall']) {
|
|
1714
1875
|
if (!bulkSelectResult.activeTools.includes(name)) {
|
|
1715
1876
|
throw new Error(`tool_search bulk select missing ${name}: ${JSON.stringify(bulkSelectResult)}`);
|
|
@@ -1784,7 +1945,8 @@ if (nativeGrokPatchTool?.type !== 'function' || nativeGrokPatchTool?.format || n
|
|
|
1784
1945
|
if (nativeGrokPatchTool.parameters?.properties?.patch?.type !== 'string') {
|
|
1785
1946
|
throw new Error(`Grok native tool_search apply_patch must preserve patch JSON schema: ${JSON.stringify(nativeGrokPatchTool)}`);
|
|
1786
1947
|
}
|
|
1787
|
-
|
|
1948
|
+
// Native query-select discovers (without mutating active schemas); aliases expand.
|
|
1949
|
+
const nativeSelectQuerySession = {
|
|
1788
1950
|
tools: smokeCatalog.filter((tool) => fullDefaults.has(tool?.name)),
|
|
1789
1951
|
deferredToolCatalog: smokeCatalog.slice(),
|
|
1790
1952
|
deferredSelectedTools: [...fullDefaults],
|
|
@@ -1792,16 +1954,20 @@ const nativeRunQuerySession = {
|
|
|
1792
1954
|
deferredProviderMode: 'native',
|
|
1793
1955
|
deferredNativeTools: true,
|
|
1794
1956
|
};
|
|
1795
|
-
const
|
|
1796
|
-
for (const name of ['
|
|
1797
|
-
if (!
|
|
1798
|
-
throw new Error(`native tool_search
|
|
1957
|
+
const nativeSelectQueryResult = JSON.parse(__renderToolSearchForTest({ query: 'select:search' }, nativeSelectQuerySession, 'full'));
|
|
1958
|
+
for (const name of ['search', 'web_fetch']) {
|
|
1959
|
+
if (!nativeSelectQueryResult.discoveredTools.includes(name)) {
|
|
1960
|
+
throw new Error(`native tool_search query-select should discover ${name}: ${JSON.stringify(nativeSelectQueryResult)}`);
|
|
1799
1961
|
}
|
|
1800
1962
|
}
|
|
1801
|
-
if (
|
|
1802
|
-
throw new Error(`native tool_search
|
|
1963
|
+
if (nativeSelectQueryResult.activeTools.includes('search') || nativeSelectQueryResult.activeTools.includes('web_fetch')) {
|
|
1964
|
+
throw new Error(`native tool_search must not mutate active schemas: ${JSON.stringify(nativeSelectQueryResult)}`);
|
|
1965
|
+
}
|
|
1966
|
+
if (!nativeSelectQueryResult.nativeToolSearch?.toolReferences?.includes('search')) {
|
|
1967
|
+
throw new Error(`native query-select must return nativeToolSearch payload: ${JSON.stringify(nativeSelectQueryResult.nativeToolSearch)}`);
|
|
1803
1968
|
}
|
|
1804
|
-
|
|
1969
|
+
// A plain query never auto-loads/discovers, even on native providers.
|
|
1970
|
+
const nativePlainQuerySession = {
|
|
1805
1971
|
tools: smokeCatalog.filter((tool) => fullDefaults.has(tool?.name)),
|
|
1806
1972
|
deferredToolCatalog: smokeCatalog.slice(),
|
|
1807
1973
|
deferredSelectedTools: [...fullDefaults],
|
|
@@ -1809,35 +1975,48 @@ const nativeWebQuerySession = {
|
|
|
1809
1975
|
deferredProviderMode: 'native',
|
|
1810
1976
|
deferredNativeTools: true,
|
|
1811
1977
|
};
|
|
1812
|
-
const
|
|
1813
|
-
|
|
1814
|
-
if (
|
|
1815
|
-
throw new Error(`native tool_search
|
|
1978
|
+
for (const q of ['run tests', 'web docs', 'memory previous', 'status']) {
|
|
1979
|
+
const r = JSON.parse(__renderToolSearchForTest({ query: q }, nativePlainQuerySession, 'full'));
|
|
1980
|
+
if (r.selected || r.discoveredTools.length) {
|
|
1981
|
+
throw new Error(`native tool_search plain query "${q}" must not auto-load/discover: ${JSON.stringify(r)}`);
|
|
1816
1982
|
}
|
|
1817
1983
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1984
|
+
// Skill-style deferred manifest: `- name: description` lines, `<`/`>` sanitized,
|
|
1985
|
+
// bare names allowed, header instructs direct calls, empty pool → ''.
|
|
1986
|
+
const manifestText = buildDeferredToolManifest([
|
|
1987
|
+
{ name: 'shell', description: 'Run commands.' },
|
|
1988
|
+
{ name: 'search', description: 'Web <search> now.' },
|
|
1989
|
+
'recall',
|
|
1990
|
+
]);
|
|
1991
|
+
if (!/<available-deferred-tools>/.test(manifestText) || !/- shell: Run commands\./.test(manifestText)) {
|
|
1992
|
+
throw new Error(`deferred manifest must render "- name: description" lines: ${manifestText}`);
|
|
1993
|
+
}
|
|
1994
|
+
if (!/call any tool listed below directly/i.test(manifestText)) {
|
|
1995
|
+
throw new Error(`deferred manifest must tell the model it can call listed tools directly: ${manifestText}`);
|
|
1996
|
+
}
|
|
1997
|
+
if (!/^- recall$/m.test(manifestText)) {
|
|
1998
|
+
throw new Error(`deferred manifest must allow bare names without descriptions: ${manifestText}`);
|
|
1999
|
+
}
|
|
2000
|
+
if (/[<>]/.test(manifestText.replace(/<\/?available-deferred-tools>/g, ''))) {
|
|
2001
|
+
throw new Error(`deferred manifest must sanitize angle brackets in descriptions: ${manifestText}`);
|
|
2002
|
+
}
|
|
2003
|
+
if (buildDeferredToolManifest([]) !== '') {
|
|
2004
|
+
throw new Error('empty deferred pool must yield an empty manifest');
|
|
2005
|
+
}
|
|
2006
|
+
const bp1ManifestSession = {
|
|
2007
|
+
messages: [{ role: 'system', content: 'BASE PROMPT' }],
|
|
2008
|
+
deferredToolCatalog: [
|
|
2009
|
+
{ name: 'shell', description: 'Run commands.' },
|
|
2010
|
+
{ name: 'recall', description: 'Recall prior work.' },
|
|
2011
|
+
],
|
|
1825
2012
|
};
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
2013
|
+
applyInitialDeferredToolManifestToBp1(bp1ManifestSession, ['shell', 'recall']);
|
|
2014
|
+
const bp1ManifestText = bp1ManifestSession.messages[0].content;
|
|
2015
|
+
if (!/- shell: Run commands\./.test(bp1ManifestText) || !/- recall: Recall prior work\./.test(bp1ManifestText)) {
|
|
2016
|
+
throw new Error(`BP1 deferred manifest must carry catalog descriptions: ${bp1ManifestText}`);
|
|
1829
2017
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
deferredToolCatalog: smokeCatalog.slice(),
|
|
1833
|
-
deferredSelectedTools: [...fullDefaults],
|
|
1834
|
-
deferredDiscoveredTools: [],
|
|
1835
|
-
deferredProviderMode: 'native',
|
|
1836
|
-
deferredNativeTools: true,
|
|
1837
|
-
};
|
|
1838
|
-
const ambiguousStatusResult = JSON.parse(__renderToolSearchForTest({ query: 'status' }, ambiguousStatusSession, 'full'));
|
|
1839
|
-
if (ambiguousStatusResult.selected || ambiguousStatusResult.discoveredTools.length) {
|
|
1840
|
-
throw new Error(`tool_search ambiguous status query must not auto-load: ${JSON.stringify(ambiguousStatusResult)}`);
|
|
2018
|
+
if (bp1ManifestSession.deferredToolBp1Applied !== true) {
|
|
2019
|
+
throw new Error('BP1 deferred manifest injection must mark deferredToolBp1Applied');
|
|
1841
2020
|
}
|
|
1842
2021
|
const replyTool = CHANNEL_TOOL_DEFS.find((tool) => tool.name === 'reply');
|
|
1843
2022
|
if (!/configured channel/i.test(replyTool?.description || '') || !/local .*paths/i.test(replyTool?.inputSchema?.properties?.files?.description || '')) {
|
|
@@ -1853,23 +2032,23 @@ const grepPathDescription = grepTool?.inputSchema?.properties?.path?.description
|
|
|
1853
2032
|
const grepGlobDescription = grepTool?.inputSchema?.properties?.glob?.description || '';
|
|
1854
2033
|
const grepOutputModeDescription = grepTool?.inputSchema?.properties?.output_mode?.description || '';
|
|
1855
2034
|
const grepHeadLimitDescription = grepTool?.inputSchema?.properties?.head_limit?.description || '';
|
|
1856
|
-
if (!/
|
|
2035
|
+
if (!/Array = variants in one call/i.test(grepPatternDescription) || !/Known file or dir/i.test(grepPathDescription)) {
|
|
1857
2036
|
throw new Error('grep schema must keep compact pattern/path guidance');
|
|
1858
2037
|
}
|
|
1859
|
-
if (!/
|
|
1860
|
-
throw new Error('grep glob schema must
|
|
2038
|
+
if (!/narrow scope/i.test(grepGlobDescription)) {
|
|
2039
|
+
throw new Error('grep glob schema must describe scope narrowing');
|
|
1861
2040
|
}
|
|
1862
|
-
if (!/
|
|
1863
|
-
throw new Error('grep output_mode schema must
|
|
2041
|
+
if (!/files_with_matches\/count/i.test(grepOutputModeDescription) || !/content_with_context/i.test(grepOutputModeDescription)) {
|
|
2042
|
+
throw new Error('grep output_mode schema must name its output shapes');
|
|
1864
2043
|
}
|
|
1865
|
-
if (grepTool?.inputSchema?.properties?.head_limit?.minimum !== 0 || !/
|
|
2044
|
+
if (grepTool?.inputSchema?.properties?.head_limit?.minimum !== 0 || !/Max results/i.test(grepHeadLimitDescription)) {
|
|
1866
2045
|
throw new Error('grep head_limit schema must keep locator caps explicit');
|
|
1867
2046
|
}
|
|
1868
2047
|
if (grepTool?.inputSchema?.properties?.type) {
|
|
1869
2048
|
throw new Error('grep type schema must stay hidden; prefer glob for extension narrowing');
|
|
1870
2049
|
}
|
|
1871
|
-
if (!/
|
|
1872
|
-
throw new Error('code_graph schema fields must
|
|
2050
|
+
if (!/Repo-local/i.test(codeGraphProps.mode?.description || '') || !/one call/i.test(codeGraphProps.symbols?.description || '')) {
|
|
2051
|
+
throw new Error('code_graph schema fields must stay compact and repo-local');
|
|
1873
2052
|
}
|
|
1874
2053
|
|
|
1875
2054
|
const longToolSearchText = compactToolSearchDescription(`${patchDescription}\n${patchDescription}`);
|
|
@@ -8,7 +8,7 @@ Root-cause analysis agent.
|
|
|
8
8
|
|
|
9
9
|
Smallest confirmed cause chain before fixes. Return likely cause, evidence
|
|
10
10
|
(`file:line`), smallest next check/fix. Mark confirmed facts vs inferences;
|
|
11
|
-
avoid broad speculation.
|
|
11
|
+
avoid broad speculation.
|
|
12
12
|
|
|
13
|
-
Converge, don't sweep: when new
|
|
14
|
-
cause chain so far
|
|
13
|
+
Converge, don't sweep: when new evidence stops accruing, report the best
|
|
14
|
+
cause chain so far.
|
|
@@ -5,16 +5,13 @@ permission: read-write
|
|
|
5
5
|
# Heavy Worker
|
|
6
6
|
Broad implementation agent.
|
|
7
7
|
|
|
8
|
-
Bounded slices; smallest coherent change, not rewrite. Stop
|
|
9
|
-
|
|
8
|
+
Bounded slices; smallest coherent change, not rewrite. Stop when scope is
|
|
9
|
+
unclear or the blast radius grows.
|
|
10
10
|
|
|
11
|
-
EDIT-FIRST DISCIPLINE. Survey the slice with
|
|
12
|
-
patch the first bounded piece — edit incrementally, don't read
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
runtime reminder, patch the piece you understand or report blocked — a valid
|
|
16
|
-
completion. Self-check comes AFTER edits; deep verification is Lead's.
|
|
11
|
+
EDIT-FIRST DISCIPLINE. Survey the slice with one batched retrieval round,
|
|
12
|
+
then patch the first bounded piece — edit incrementally, don't read
|
|
13
|
+
exhaustively. Repeated read-only turns without an edit = stalling; on a
|
|
14
|
+
runtime reminder, patch the piece you understand or report blocked.
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
`file:line`; no narration or bloat.
|
|
16
|
+
Self-verify edits with shell (targeted test/build).
|
|
20
17
|
|
|
@@ -7,5 +7,4 @@ permission: read-write
|
|
|
7
7
|
Maintenance and cleanup agent.
|
|
8
8
|
|
|
9
9
|
Smallest coherent change; no scope growth. Repeated read-only turns without
|
|
10
|
-
an edit = stalling; patch or return blocked
|
|
11
|
-
completion. Hand off as fragments (`file:line`); no narration.
|
|
10
|
+
an edit = stalling; patch or return blocked.
|
|
@@ -8,5 +8,4 @@ Regression/risk review agent.
|
|
|
8
8
|
|
|
9
9
|
Find actionable correctness/regression/security/verification risks. Findings
|
|
10
10
|
first, severity-ordered, one line with `file:line`; skip non-risky nits. If
|
|
11
|
-
clean, one line + only material residual risk.
|
|
12
|
-
no report bloat.
|
|
11
|
+
clean, one line + only material residual risk.
|
|
@@ -7,13 +7,10 @@ Scoped implementation agent.
|
|
|
7
7
|
|
|
8
8
|
Smallest scoped change; no drive-by cleanup. Stop when done/blocked.
|
|
9
9
|
|
|
10
|
-
EDIT-FIRST DISCIPLINE. Brief anchors (`file:line`) are pre-verified — trust
|
|
11
|
-
patch. No anchor:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
runtime reminder, patch now or return blocked with what's missing — a blocked
|
|
15
|
-
report is a valid completion. Threshold is "plausible", not "proven";
|
|
16
|
-
self-check comes AFTER the edit, and Lead/Reviewer own final verification.
|
|
10
|
+
EDIT-FIRST DISCIPLINE. Brief anchors (`file:line`) are pre-verified — trust
|
|
11
|
+
and patch. No anchor: one locating batch, then `apply_patch`. Repeated
|
|
12
|
+
read-only turns without an edit = stalling; on a runtime reminder, patch now
|
|
13
|
+
or return blocked. Threshold is "plausible", not "proven".
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
Self-verify edits with shell (node --check/test).
|
|
19
16
|
|
package/src/defaults/agents.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"description": "Filesystem navigation agent invoked by the `explore` MCP tool",
|
|
8
8
|
"invokedBy": "explore",
|
|
9
9
|
"toolSchemaProfile": "read",
|
|
10
|
+
"schemaAllowedTools": ["grep", "find", "glob", "code_graph"],
|
|
10
11
|
"kind": "retrieval",
|
|
11
12
|
"permission": "read",
|
|
12
13
|
"stallCap": { "idleSeconds": 240, "toolRunningSeconds": 180 }
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"description": "Scheduled-task executor invoked by scheduler tick",
|
|
55
56
|
"invokedBy": "scheduler",
|
|
56
57
|
"toolSchemaProfile": "read-write-search",
|
|
58
|
+
"schemaAllowedTools": ["code_graph", "find", "glob", "list", "grep", "read", "apply_patch", "search", "web_fetch"],
|
|
57
59
|
"kind": "maintenance",
|
|
58
60
|
"permission": "read-write",
|
|
59
61
|
"inboundEvent": true,
|
|
@@ -67,6 +69,7 @@
|
|
|
67
69
|
"description": "Webhook payload handler invoked by inbound webhook events",
|
|
68
70
|
"invokedBy": "webhook",
|
|
69
71
|
"toolSchemaProfile": "read-write-search",
|
|
72
|
+
"schemaAllowedTools": ["code_graph", "find", "glob", "list", "grep", "read", "apply_patch", "search", "web_fetch"],
|
|
70
73
|
"kind": "maintenance",
|
|
71
74
|
"permission": "read-write",
|
|
72
75
|
"inboundEvent": true,
|
package/src/help.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { stdout } from 'node:process';
|
|
2
2
|
|
|
3
|
-
/** Help text
|
|
3
|
+
/** Help text printed by `--help`. */
|
|
4
4
|
export const HELP_LINES = [
|
|
5
5
|
'mixdog — standalone mixdog CLI/TUI coding agent.',
|
|
6
6
|
'',
|
|
@@ -11,16 +11,13 @@ export const HELP_LINES = [
|
|
|
11
11
|
' mixdog --help',
|
|
12
12
|
'',
|
|
13
13
|
'Slash commands (inside mixdog):',
|
|
14
|
-
' /help show this help',
|
|
15
14
|
' /clear reset the conversation and clear the screen',
|
|
16
15
|
' /compact compact older conversation context',
|
|
17
16
|
' /model <name> switch model/preset for subsequent turns',
|
|
18
17
|
' /OutputStyle [name] show or switch Lead output style',
|
|
19
18
|
' /providers manage provider auth and local endpoints',
|
|
20
19
|
' /agents show available workflow agents',
|
|
21
|
-
' /
|
|
22
|
-
' /mode <name> switch tool surface: full | readonly',
|
|
23
|
-
' /exit quit',
|
|
20
|
+
' /quit quit (aliases: /exit, /q)',
|
|
24
21
|
'',
|
|
25
22
|
'History: use ↑ / ↓ to recall previous inputs.',
|
|
26
23
|
];
|