mixdog 0.9.19 → 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 +3 -2
- 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/reactive-compact-persist-smoke.mjs +22 -6
- package/scripts/recall-bench-cases.json +0 -1
- 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 +1 -1
- package/scripts/tool-smoke.mjs +150 -45
- package/src/help.mjs +2 -5
- package/src/lib/mixdog-debug.cjs +13 -0
- package/src/mixdog-session-runtime.mjs +7 -3328
- package/src/rules/lead/02-channels.md +3 -3
- package/src/runtime/agent/orchestrator/context/collect.mjs +33 -9
- 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/eager-dispatch.mjs +153 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +49 -0
- 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.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/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/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/channels/index.mjs +6 -2183
- package/src/runtime/channels/lib/inbound-handler.mjs +328 -0
- package/src/runtime/channels/lib/interaction-handlers.mjs +260 -0
- package/src/runtime/channels/lib/network-retry.mjs +23 -0
- package/src/runtime/channels/lib/owned-runtime.mjs +547 -0
- 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-store.mjs +4 -4
- package/src/runtime/memory/lib/query-handlers.mjs +2 -2
- package/src/runtime/memory/lib/session-ingest-runtime.mjs +401 -0
- package/src/session-runtime/boot-profile.mjs +36 -0
- package/src/session-runtime/channel-config-api.mjs +70 -0
- package/src/session-runtime/context-status.mjs +181 -0
- package/src/session-runtime/env.mjs +17 -0
- package/src/session-runtime/lifecycle-api.mjs +242 -0
- package/src/session-runtime/model-route-api.mjs +198 -0
- 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/standalone/agent-tool.mjs +2 -2
- package/src/standalone/channel-worker.mjs +4 -0
- package/src/standalone/memory-runtime-proxy.mjs +56 -6
- package/src/tui/App.jsx +39 -28
- package/src/tui/app/core-memory-picker.mjs +1 -1
- package/src/tui/app/use-mouse-input.mjs +6 -0
- package/src/tui/app/use-transcript-scroll.mjs +77 -3
- package/src/tui/dist/index.mjs +1990 -1527
- 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 +68 -2620
- package/src/tui/index.jsx +7 -0
- 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/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';
|
|
@@ -644,6 +645,72 @@ if (!/^Error[\s:[]/.test(String(stalePatchOut)) || !/apply_patch/i.test(String(s
|
|
|
644
645
|
throw new Error(`apply_patch stale context must return an Error result, not throw or pass:\n${stalePatchOut}`);
|
|
645
646
|
}
|
|
646
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
|
+
|
|
647
714
|
const shellOutPromise = executeBuiltinTool('shell', {
|
|
648
715
|
command: 'node --version',
|
|
649
716
|
cwd: root,
|
|
@@ -1485,7 +1552,10 @@ setInternalToolsProvider({
|
|
|
1485
1552
|
const resumed = await resumeSession(session.id, 'full');
|
|
1486
1553
|
const resumedTools = (resumed?.tools || []).map((tool) => tool?.name).filter(Boolean);
|
|
1487
1554
|
const expected = expectedForHiddenAgent(permission, schemaAllowedTools);
|
|
1488
|
-
|
|
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))) {
|
|
1489
1559
|
throw new Error(`hidden agent ${agent} schema mismatch: expected=${expected.join(', ')} tools=${tools.join(', ')} resumed=${resumedTools.join(', ')}`);
|
|
1490
1560
|
}
|
|
1491
1561
|
const leaked = tools.filter((name) => hiddenBadTools.has(name) && !(expected || []).includes(name));
|
|
@@ -1644,15 +1714,25 @@ if (/line\/context/i.test(JSON.stringify(readTool?.inputSchema || {}))) {
|
|
|
1644
1714
|
// session's provider/model in place, or a mid-conversation model/provider
|
|
1645
1715
|
// switch silently forces a full prompt-cache rewrite (seen as a
|
|
1646
1716
|
// promptΔ spike + cache_ratio=0% turn in session-bench).
|
|
1647
|
-
|
|
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');
|
|
1648
1728
|
const setRouteBlock = runtimeSrc.match(/async setRoute\(next, options = \{\}\) \{[\s\S]*?\n \},\n/)?.[0] || '';
|
|
1649
1729
|
if (!/applyToCurrentSession = options\?\.applyToCurrentSession === true/.test(setRouteBlock)) {
|
|
1650
1730
|
throw new Error('setRoute must default applyToCurrentSession to false (model changes apply to the next session only)');
|
|
1651
1731
|
}
|
|
1652
|
-
if (!/if \(!applyToCurrentSession\)/.test(setRouteBlock) || !/return route;/.test(setRouteBlock)) {
|
|
1732
|
+
if (!/if \(!applyToCurrentSession\)/.test(setRouteBlock) || !/return (?:route|getRoute\(\));/.test(setRouteBlock)) {
|
|
1653
1733
|
throw new Error('setRoute must early-return before touching the live session when applyToCurrentSession is false');
|
|
1654
1734
|
}
|
|
1655
|
-
const engineSrc =
|
|
1735
|
+
const engineSrc = [readMjsSources('src/tui/engine.mjs'), readMjsSources('src/tui/engine')].join('\n');
|
|
1656
1736
|
if (/setRoute\(\{ model: m \}, \{ applyToCurrentSession: true \}\)/.test(engineSrc)) {
|
|
1657
1737
|
throw new Error('TUI setModel must not force applyToCurrentSession:true (model changes must apply to the next session only)');
|
|
1658
1738
|
}
|
|
@@ -1774,16 +1854,23 @@ const toolSearchSession = {
|
|
|
1774
1854
|
deferredToolCatalog: smokeCatalog.slice(),
|
|
1775
1855
|
deferredSelectedTools: [...fullDefaults],
|
|
1776
1856
|
};
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
}
|
|
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)}`);
|
|
1862
|
+
}
|
|
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)}`);
|
|
1782
1865
|
}
|
|
1783
|
-
if (
|
|
1784
|
-
throw new Error(`tool_search query
|
|
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)}`);
|
|
1785
1868
|
}
|
|
1869
|
+
// query "select:a,b" is the explicit query-side loader (aliases expand).
|
|
1786
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
|
+
}
|
|
1787
1874
|
for (const name of ['shell', 'task', 'recall']) {
|
|
1788
1875
|
if (!bulkSelectResult.activeTools.includes(name)) {
|
|
1789
1876
|
throw new Error(`tool_search bulk select missing ${name}: ${JSON.stringify(bulkSelectResult)}`);
|
|
@@ -1858,7 +1945,8 @@ if (nativeGrokPatchTool?.type !== 'function' || nativeGrokPatchTool?.format || n
|
|
|
1858
1945
|
if (nativeGrokPatchTool.parameters?.properties?.patch?.type !== 'string') {
|
|
1859
1946
|
throw new Error(`Grok native tool_search apply_patch must preserve patch JSON schema: ${JSON.stringify(nativeGrokPatchTool)}`);
|
|
1860
1947
|
}
|
|
1861
|
-
|
|
1948
|
+
// Native query-select discovers (without mutating active schemas); aliases expand.
|
|
1949
|
+
const nativeSelectQuerySession = {
|
|
1862
1950
|
tools: smokeCatalog.filter((tool) => fullDefaults.has(tool?.name)),
|
|
1863
1951
|
deferredToolCatalog: smokeCatalog.slice(),
|
|
1864
1952
|
deferredSelectedTools: [...fullDefaults],
|
|
@@ -1866,16 +1954,20 @@ const nativeRunQuerySession = {
|
|
|
1866
1954
|
deferredProviderMode: 'native',
|
|
1867
1955
|
deferredNativeTools: true,
|
|
1868
1956
|
};
|
|
1869
|
-
const
|
|
1870
|
-
for (const name of ['
|
|
1871
|
-
if (!
|
|
1872
|
-
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)}`);
|
|
1873
1961
|
}
|
|
1874
1962
|
}
|
|
1875
|
-
if (
|
|
1876
|
-
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)}`);
|
|
1877
1968
|
}
|
|
1878
|
-
|
|
1969
|
+
// A plain query never auto-loads/discovers, even on native providers.
|
|
1970
|
+
const nativePlainQuerySession = {
|
|
1879
1971
|
tools: smokeCatalog.filter((tool) => fullDefaults.has(tool?.name)),
|
|
1880
1972
|
deferredToolCatalog: smokeCatalog.slice(),
|
|
1881
1973
|
deferredSelectedTools: [...fullDefaults],
|
|
@@ -1883,35 +1975,48 @@ const nativeWebQuerySession = {
|
|
|
1883
1975
|
deferredProviderMode: 'native',
|
|
1884
1976
|
deferredNativeTools: true,
|
|
1885
1977
|
};
|
|
1886
|
-
const
|
|
1887
|
-
|
|
1888
|
-
if (
|
|
1889
|
-
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)}`);
|
|
1890
1982
|
}
|
|
1891
1983
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
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
|
+
],
|
|
1899
2012
|
};
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
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}`);
|
|
1903
2017
|
}
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
deferredToolCatalog: smokeCatalog.slice(),
|
|
1907
|
-
deferredSelectedTools: [...fullDefaults],
|
|
1908
|
-
deferredDiscoveredTools: [],
|
|
1909
|
-
deferredProviderMode: 'native',
|
|
1910
|
-
deferredNativeTools: true,
|
|
1911
|
-
};
|
|
1912
|
-
const ambiguousStatusResult = JSON.parse(__renderToolSearchForTest({ query: 'status' }, ambiguousStatusSession, 'full'));
|
|
1913
|
-
if (ambiguousStatusResult.selected || ambiguousStatusResult.discoveredTools.length) {
|
|
1914
|
-
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');
|
|
1915
2020
|
}
|
|
1916
2021
|
const replyTool = CHANNEL_TOOL_DEFS.find((tool) => tool.name === 'reply');
|
|
1917
2022
|
if (!/configured channel/i.test(replyTool?.description || '') || !/local .*paths/i.test(replyTool?.inputSchema?.properties?.files?.description || '')) {
|
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
|
];
|
package/src/lib/mixdog-debug.cjs
CHANGED
|
@@ -36,6 +36,10 @@ const CANONICAL_PLUGIN_LOG_NAMES = new Set([
|
|
|
36
36
|
'session-start-critical.log',
|
|
37
37
|
'webhook.log',
|
|
38
38
|
'perf.log',
|
|
39
|
+
'tool-events.log',
|
|
40
|
+
'memory-runtime-proxy.log',
|
|
41
|
+
'channels-worker-standalone.log',
|
|
42
|
+
'mixdog-tui.stderr.log',
|
|
39
43
|
]);
|
|
40
44
|
|
|
41
45
|
/**
|
|
@@ -113,6 +117,12 @@ function rotateBoundedLog(filePath, maxBytes, keepBytes) {
|
|
|
113
117
|
} catch { /* missing file ok */ }
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
// Shared bound for unbounded per-writer plugin logs (tool-events,
|
|
121
|
+
// memory-runtime-proxy, channels-worker-standalone). Keep a tail so recent
|
|
122
|
+
// context survives rotation while the file stays under the 10 MB cap.
|
|
123
|
+
const PLUGIN_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
124
|
+
const PLUGIN_LOG_KEEP_BYTES = 2 * 1024 * 1024;
|
|
125
|
+
|
|
116
126
|
/**
|
|
117
127
|
* Ship-mode durable fail-open record (size-capped). No-op when line empty.
|
|
118
128
|
*/
|
|
@@ -131,4 +141,7 @@ module.exports = {
|
|
|
131
141
|
pruneStalePluginDataLogSiblings,
|
|
132
142
|
appendSessionStartCriticalLog,
|
|
133
143
|
DEFAULT_STALE_LOG_SIBLING_MAX,
|
|
144
|
+
rotateBoundedLog,
|
|
145
|
+
PLUGIN_LOG_MAX_BYTES,
|
|
146
|
+
PLUGIN_LOG_KEEP_BYTES,
|
|
134
147
|
};
|