mixdog 0.9.18 → 0.9.19
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 +3 -2
- package/scripts/output-style-smoke.mjs +2 -2
- package/scripts/recall-bench-cases.json +11 -0
- package/scripts/recall-bench.mjs +91 -2
- package/scripts/tool-efficiency-diag.mjs +4 -1
- package/scripts/tool-smoke.mjs +101 -27
- 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/mixdog-session-runtime.mjs +23 -6
- package/src/rules/agent/00-core.md +4 -3
- package/src/rules/agent/30-explorer.md +53 -22
- package/src/rules/lead/lead-tool.md +3 -2
- package/src/rules/shared/01-tool.md +24 -29
- 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/context-utils.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +250 -35
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +6 -4
- 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.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/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 +145 -58
- package/src/runtime/channels/lib/inbound-routing.mjs +19 -12
- package/src/runtime/channels/lib/memory-client.mjs +32 -14
- package/src/runtime/channels/lib/output-forwarder.mjs +38 -7
- 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/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 +36 -4
- package/src/runtime/memory/lib/recall-format.mjs +106 -6
- 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/config-lifecycle.mjs +1 -1
- package/src/session-runtime/cwd-plugins.mjs +46 -3
- package/src/session-runtime/mcp-glue.mjs +24 -3
- package/src/session-runtime/output-styles.mjs +44 -10
- package/src/session-runtime/workflow.mjs +16 -1
- package/src/standalone/channel-worker.mjs +74 -5
- package/src/standalone/explore-tool.mjs +1 -1
- package/src/tui/App.jsx +57 -77
- package/src/tui/app/channel-pickers.mjs +45 -0
- 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 +9 -2
- package/src/tui/app/use-prompt-handlers.mjs +7 -94
- package/src/tui/app/use-transcript-scroll.mjs +5 -1
- 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 +5330 -5443
- package/src/tui/engine.mjs +49 -10
- 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/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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixdog",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.19",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone mixdog coding-agent CLI/TUI workspace.",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"README.md",
|
|
28
28
|
"scripts/",
|
|
29
|
-
"!scripts/bench/
|
|
29
|
+
"!scripts/bench/",
|
|
30
|
+
"!scripts/recall-bench-*.txt",
|
|
30
31
|
"src/",
|
|
31
32
|
"vendor/"
|
|
32
33
|
],
|
|
@@ -97,7 +97,7 @@ try {
|
|
|
97
97
|
|
|
98
98
|
mkdirSync(join(dataDir, 'output-styles'), { recursive: true });
|
|
99
99
|
writeFileSync(join(dataDir, 'mixdog-config.json'), JSON.stringify({
|
|
100
|
-
agent: { profile: { title: '
|
|
100
|
+
agent: { profile: { title: '홍길동님', language: 'system' } },
|
|
101
101
|
outputStyle: 'custom-smoke',
|
|
102
102
|
}));
|
|
103
103
|
writeFileSync(join(dataDir, 'output-styles', 'custom-smoke.md'), '---\nname: custom-smoke\n---\n\n# Custom Output Style\n\ncustom smoke style\n');
|
|
@@ -105,7 +105,7 @@ try {
|
|
|
105
105
|
assert(customRules.includes('# Custom Output Style'), 'configured outputStyle must select custom style');
|
|
106
106
|
assert(!customRules.includes('Mixdog default — the most detailed of the three styles'), 'custom outputStyle should not append default style');
|
|
107
107
|
const profileMeta = rulesBuilder.buildLeadMetaContent({ PLUGIN_ROOT: join(root, 'src'), DATA_DIR: dataDir });
|
|
108
|
-
assert(profileMeta.includes('Use "
|
|
108
|
+
assert(profileMeta.includes('Use "홍길동님" when directly addressing the user'), 'profile title must inject into Lead BP3 meta');
|
|
109
109
|
assert(profileMeta.includes('do not repeat it in routine progress updates or pre-tool preambles'), 'profile title must not encourage title in preambles');
|
|
110
110
|
assert(/Default user-facing response language from system locale/.test(profileMeta), 'system profile language must resolve from system locale');
|
|
111
111
|
assert(profileMeta.includes('pre-tool preambles (even single-line)'), 'profile language must cover pre-tool preambles');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "id": "browse-last", "label": "query-less recent browse (period=last)", "args": { "period": "last", "limit": 10 }, "expect": "browse" },
|
|
3
|
+
{ "id": "period-24h", "label": "period window 24h", "args": { "period": "24h", "limit": 10 }, "expect": "browse" },
|
|
4
|
+
{ "id": "period-7d", "label": "period window 7d", "args": { "period": "7d", "limit": 10 }, "expect": "browse" },
|
|
5
|
+
{ "id": "scope-all", "label": "all-scope query", "args": { "query": "recall", "projectScope": "all", "limit": 10 }, "expect": "results" },
|
|
6
|
+
{ "id": "recent-dryrun-delta", "label": "recent-work topical: dryrun delta sum", "args": { "query": "드라이런 델타 합산", "limit": 10 }, "expect": { "kind": "results", "topNContains": ["드라이런"], "topN": 5 } },
|
|
7
|
+
{ "id": "recent-remote-singleton", "label": "recent-work topical: remote singleton seat", "args": { "query": "remote 싱글톤 좌석", "limit": 10 }, "expect": { "kind": "results", "topNContains": ["싱글톤"], "topN": 5 } },
|
|
8
|
+
{ "id": "recency-today", "label": "query+period recency: today's work (24h)", "args": { "query": "오늘 진행한 작업", "period": "24h", "limit": 10 }, "expect": { "kind": "results", "recencyOrdered": true } },
|
|
9
|
+
{ "id": "negative-projectaa", "label": "negative: every row must literally mention the term (no filler)", "args": { "query": "ProjectAA 밸런스", "limit": 10 }, "expect": { "kind": "allContain", "allContain": ["projectaa"] } },
|
|
10
|
+
{ "id": "xlang-embedding-crash", "label": "cross-language: embedding worker crash (strict topN)", "args": { "query": "embedding worker crash", "limit": 10 }, "expect": { "kind": "results", "topNContains": ["embedding"], "topN": 3 } }
|
|
11
|
+
]
|
package/scripts/recall-bench.mjs
CHANGED
|
@@ -113,13 +113,87 @@ function scoreTopNContains(lines, substrings, n) {
|
|
|
113
113
|
return { perSubstring, hitAtN, mrr, n };
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
// Parse leading "[YYYY-MM-DD HH:MM]" timestamps from result lines and check
|
|
117
|
+
// they are non-increasing (newest-first). Returns null when fewer than 2
|
|
118
|
+
// lines carry a parseable timestamp (nothing to order).
|
|
119
|
+
function scoreRecencyOrdered(lines) {
|
|
120
|
+
const tsRe = /^\s*\[(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2})/;
|
|
121
|
+
const headerRe = /^\s*##\s/;
|
|
122
|
+
const parse = (raw) => Date.parse(raw.replace(' ', 'T'));
|
|
123
|
+
// Non-increasing check within a single ordered list of {raw,ts}. Returns
|
|
124
|
+
// the first {prev,cur} pair where cur is newer than prev, else null.
|
|
125
|
+
const firstBreak = (stamps) => {
|
|
126
|
+
for (let i = 1; i < stamps.length; i++) {
|
|
127
|
+
if (Number.isFinite(stamps[i].ts) && Number.isFinite(stamps[i - 1].ts) && stamps[i].ts > stamps[i - 1].ts) {
|
|
128
|
+
return { prev: stamps[i - 1].raw, cur: stamps[i].raw };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
};
|
|
133
|
+
const hasSessions = lines.some((l) => headerRe.test(l));
|
|
134
|
+
if (!hasSessions) {
|
|
135
|
+
// Ungrouped: single global non-increasing check over all timestamped lines.
|
|
136
|
+
const stamps = [];
|
|
137
|
+
for (const line of lines) {
|
|
138
|
+
const m = tsRe.exec(line);
|
|
139
|
+
if (m) stamps.push({ raw: m[1], ts: parse(m[1]) });
|
|
140
|
+
}
|
|
141
|
+
if (stamps.length < 2) return { parsed: stamps.length, groups: 0, ordered: true, firstViolation: null };
|
|
142
|
+
const b = firstBreak(stamps);
|
|
143
|
+
return { parsed: stamps.length, groups: 0, ordered: b === null, firstViolation: b };
|
|
144
|
+
}
|
|
145
|
+
// Session-grouped: partition timestamped lines by their "## session" header.
|
|
146
|
+
// Timestamps only compared WITHIN a group; groups themselves must descend by
|
|
147
|
+
// their first line's timestamp.
|
|
148
|
+
const groups = [];
|
|
149
|
+
let cur = null;
|
|
150
|
+
for (const line of lines) {
|
|
151
|
+
if (headerRe.test(line)) { cur = { stamps: [] }; groups.push(cur); continue; }
|
|
152
|
+
const m = tsRe.exec(line);
|
|
153
|
+
if (m && cur) cur.stamps.push({ raw: m[1], ts: parse(m[1]) });
|
|
154
|
+
}
|
|
155
|
+
const nonEmpty = groups.filter((g) => g.stamps.length);
|
|
156
|
+
const parsed = nonEmpty.reduce((s, g) => s + g.stamps.length, 0);
|
|
157
|
+
let firstViolation = null;
|
|
158
|
+
for (const g of nonEmpty) {
|
|
159
|
+
const b = firstBreak(g.stamps);
|
|
160
|
+
if (b) { firstViolation = { ...b, scope: 'within-session' }; break; }
|
|
161
|
+
}
|
|
162
|
+
if (!firstViolation) {
|
|
163
|
+
const heads = nonEmpty.map((g) => g.stamps[0]);
|
|
164
|
+
const b = firstBreak(heads);
|
|
165
|
+
if (b) firstViolation = { ...b, scope: 'across-sessions' };
|
|
166
|
+
}
|
|
167
|
+
return { parsed, groups: nonEmpty.length, ordered: firstViolation === null, firstViolation };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Score expect.allContain: every result line must contain at least one of the
|
|
171
|
+
// given substrings (case-insensitive). Returns offending lines (matching none).
|
|
172
|
+
// Use for negative cases where rows legitimately mention the term.
|
|
173
|
+
function scoreAllContain(lines, substrings) {
|
|
174
|
+
const needles = substrings.map((s) => String(s || '').toLowerCase()).filter(Boolean);
|
|
175
|
+
const offenders = lines.filter((line) => {
|
|
176
|
+
const l = line.toLowerCase();
|
|
177
|
+
return !needles.some((n) => l.includes(n));
|
|
178
|
+
});
|
|
179
|
+
return { needles: substrings, offenders, ok: offenders.length === 0 };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function evaluateCase(kase, { count, ms, isError }, quality, recency, allContain) {
|
|
117
183
|
const warnings = [];
|
|
118
184
|
if (isError) warnings.push('error result');
|
|
119
185
|
if (ms > WARN_LATENCY_MS) warnings.push(`latency ${ms}ms > ${WARN_LATENCY_MS}ms`);
|
|
120
186
|
if ((kase.expect === 'browse' || kase.expect === 'idlookup') && count === 0) {
|
|
121
187
|
warnings.push('0 results for a browse/id-lookup case (expected data present)');
|
|
122
188
|
}
|
|
189
|
+
if (kase.expect === 'empty' && count > 0) {
|
|
190
|
+
warnings.push(`expected empty but got ${count} result(s) — possible filler/unrelated match`);
|
|
191
|
+
}
|
|
192
|
+
if (allContain) {
|
|
193
|
+
for (const line of allContain.offenders) {
|
|
194
|
+
warnings.push(`allContain miss: result line contains none of [${allContain.needles.join(', ')}]: "${line}"`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
123
197
|
if (quality) {
|
|
124
198
|
for (const p of quality.perSubstring) {
|
|
125
199
|
if (!p.hit) {
|
|
@@ -129,6 +203,10 @@ function evaluateCase(kase, { count, ms, isError }, quality) {
|
|
|
129
203
|
}
|
|
130
204
|
}
|
|
131
205
|
}
|
|
206
|
+
if (recency && !recency.ordered && recency.firstViolation) {
|
|
207
|
+
const v = recency.firstViolation;
|
|
208
|
+
warnings.push(`recencyOrdered violation (${v.scope || 'global'}): ${v.cur} newer than prior ${v.prev}`);
|
|
209
|
+
}
|
|
132
210
|
const status = warnings.length ? 'WARN' : 'PASS';
|
|
133
211
|
return { status, warnings };
|
|
134
212
|
}
|
|
@@ -156,7 +234,10 @@ async function runCase(memoryModule, kase) {
|
|
|
156
234
|
const topNContains = expectObj && Array.isArray(expectObj.topNContains) ? expectObj.topNContains : null;
|
|
157
235
|
const cutoffN = expectObj && Number.isInteger(expectObj.topN) ? expectObj.topN : 5;
|
|
158
236
|
const quality = topNContains ? scoreTopNContains(resultLines(text), topNContains, cutoffN) : null;
|
|
159
|
-
const
|
|
237
|
+
const recency = expectObj && expectObj.recencyOrdered ? scoreRecencyOrdered(resultLines(text)) : null;
|
|
238
|
+
const allContainNeedles = expectObj && Array.isArray(expectObj.allContain) ? expectObj.allContain : null;
|
|
239
|
+
const allContain = allContainNeedles ? scoreAllContain(resultLines(text), allContainNeedles) : null;
|
|
240
|
+
const evalResult = evaluateCase({ ...kase, expect: expectKind }, { count, ms, isError }, quality, recency, allContain);
|
|
160
241
|
return {
|
|
161
242
|
id: kase.id,
|
|
162
243
|
label: kase.label,
|
|
@@ -167,6 +248,8 @@ async function runCase(memoryModule, kase) {
|
|
|
167
248
|
errMsg,
|
|
168
249
|
top3: topN(text, 3),
|
|
169
250
|
quality,
|
|
251
|
+
recency,
|
|
252
|
+
allContain,
|
|
170
253
|
status: evalResult.status,
|
|
171
254
|
warnings: evalResult.warnings,
|
|
172
255
|
};
|
|
@@ -182,6 +265,9 @@ function printCase(row) {
|
|
|
182
265
|
process.stdout.write(` substr "${p.needle}" -> rank ${p.rank ?? 'none'}${p.hit ? '' : ' (miss)'}\n`);
|
|
183
266
|
}
|
|
184
267
|
}
|
|
268
|
+
if (row.recency) {
|
|
269
|
+
process.stdout.write(` recency: ${row.recency.ordered ? 'ordered' : 'OUT-OF-ORDER'} (${row.recency.parsed} timestamped lines)\n`);
|
|
270
|
+
}
|
|
185
271
|
if (row.top3.length) {
|
|
186
272
|
for (const line of row.top3) process.stdout.write(` - ${line}\n`);
|
|
187
273
|
} else {
|
|
@@ -218,6 +304,7 @@ function printSummary(rows) {
|
|
|
218
304
|
async function main() {
|
|
219
305
|
const casesPath = argValue('cases', DEFAULT_CASES_PATH);
|
|
220
306
|
const jsonMode = hasFlag('json');
|
|
307
|
+
const strict = hasFlag('strict');
|
|
221
308
|
const cases = loadCases(casesPath);
|
|
222
309
|
|
|
223
310
|
let memoryModule;
|
|
@@ -262,6 +349,8 @@ async function main() {
|
|
|
262
349
|
|
|
263
350
|
const hardErrors = rows.filter((r) => r.isError);
|
|
264
351
|
if (hardErrors.length) process.exitCode = 1;
|
|
352
|
+
// --strict: any WARN row fails the run. Default behavior (errors-only) unchanged.
|
|
353
|
+
if (strict && rows.some((r) => r.status === 'WARN')) process.exitCode = 1;
|
|
265
354
|
}
|
|
266
355
|
|
|
267
356
|
main().catch((e) => {
|
|
@@ -66,7 +66,10 @@ for (const target of ['heavy-worker', 'worker']) {
|
|
|
66
66
|
}
|
|
67
67
|
if (n === 'grep' && ['content', 'content_with_context'].includes(String(a.output_mode || ''))) {
|
|
68
68
|
grepContent++;
|
|
69
|
-
|
|
69
|
+
// Implicit context: output_mode content_with_context carries context
|
|
70
|
+
// without explicit -C/-A/-B flags.
|
|
71
|
+
if (a['-C'] != null || a['-A'] != null || a['-B'] != null
|
|
72
|
+
|| String(a.output_mode) === 'content_with_context') grepContentWithCtx++;
|
|
70
73
|
const gpath = typeof a.path === 'string' ? a.path : JSON.stringify(a.path || '');
|
|
71
74
|
for (let j = i + 1; j < Math.min(i + 4, tools.length); j++) {
|
|
72
75
|
const u = tools[j];
|
package/scripts/tool-smoke.mjs
CHANGED
|
@@ -508,8 +508,8 @@ if (!/^read 2\b/m.test(String(readRegionBatchOut))
|
|
|
508
508
|
|| (String(readRegionBatchOut).match(/scripts\/smoke\.mjs \[full\] \[ok\]/g) || []).length < 2
|
|
509
509
|
|| !/1→import \{ spawnSync \}/.test(String(readRegionBatchOut))
|
|
510
510
|
|| !/3→import \{ fileURLToPath \}/.test(String(readRegionBatchOut))
|
|
511
|
-
|| !/pass offset:2 to continue/.test(String(readRegionBatchOut))
|
|
512
|
-
|| !/pass offset:4 to continue/.test(String(readRegionBatchOut))) {
|
|
511
|
+
|| !/(pass offset:2 to continue|ONE window: offset:2, limit:\d+)/.test(String(readRegionBatchOut))
|
|
512
|
+
|| !/(pass offset:4 to continue|ONE window: offset:4, limit:\d+)/.test(String(readRegionBatchOut))) {
|
|
513
513
|
throw new Error(`read region batch must preserve both requested spans:\n${readRegionBatchOut}`);
|
|
514
514
|
}
|
|
515
515
|
|
|
@@ -534,6 +534,46 @@ if (readStringifiedLineErr || readStringifiedLineArgs.path[0].offset !== 7 || re
|
|
|
534
534
|
throw new Error(`read guard must losslessly convert legacy line/context inside stringified arrays to offset/limit: err=${readStringifiedLineErr} args=${JSON.stringify(readStringifiedLineArgs)}`);
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
+
// Absorb shape 1: region array + top-level offset/limit → top-level becomes
|
|
538
|
+
// the default window for regions that lack their own; no hard error.
|
|
539
|
+
const readRegionPlusTopLevelArgs = {
|
|
540
|
+
path: [{ path: 'scripts/smoke.mjs', offset: 3, limit: 4 }, { path: 'scripts/smoke.mjs' }],
|
|
541
|
+
offset: 0,
|
|
542
|
+
limit: 2,
|
|
543
|
+
};
|
|
544
|
+
const readRegionPlusTopLevelErr = validateBuiltinArgs('read', readRegionPlusTopLevelArgs);
|
|
545
|
+
if (readRegionPlusTopLevelErr
|
|
546
|
+
|| 'offset' in readRegionPlusTopLevelArgs || 'limit' in readRegionPlusTopLevelArgs
|
|
547
|
+
|| readRegionPlusTopLevelArgs.path[0].offset !== 3 || readRegionPlusTopLevelArgs.path[0].limit !== 4
|
|
548
|
+
|| readRegionPlusTopLevelArgs.path[1].offset !== 0 || readRegionPlusTopLevelArgs.path[1].limit !== 2) {
|
|
549
|
+
throw new Error(`read guard must absorb region-array + top-level offset/limit: err=${readRegionPlusTopLevelErr} args=${JSON.stringify(readRegionPlusTopLevelArgs)}`);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Absorb shape 2: parallel offset/limit as JSON-stringified arrays with path[]
|
|
553
|
+
// → zipped into per-file region objects (pairwise recovery), no int error.
|
|
554
|
+
const readZipWindowArgs = {
|
|
555
|
+
path: ['scripts/smoke.mjs', 'scripts/smoke.mjs'],
|
|
556
|
+
offset: '[0, 5]',
|
|
557
|
+
limit: '[2, 3]',
|
|
558
|
+
};
|
|
559
|
+
const readZipWindowErr = validateBuiltinArgs('read', readZipWindowArgs);
|
|
560
|
+
if (readZipWindowErr || !Array.isArray(readZipWindowArgs.path)
|
|
561
|
+
|| readZipWindowArgs.path[0].offset !== 0 || readZipWindowArgs.path[0].limit !== 2
|
|
562
|
+
|| readZipWindowArgs.path[1].offset !== 5 || readZipWindowArgs.path[1].limit !== 3
|
|
563
|
+
|| 'offset' in readZipWindowArgs || 'limit' in readZipWindowArgs) {
|
|
564
|
+
throw new Error(`read guard must zip stringified offset/limit arrays onto path[]: err=${readZipWindowErr} args=${JSON.stringify(readZipWindowArgs)}`);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Absorb shape 3: code_graph file/files as a JSON-stringified array → parsed to
|
|
568
|
+
// a real array before lookup (dispatched into files[]).
|
|
569
|
+
const cgStringifiedFileArgs = { mode: 'symbols', file: JSON.stringify(['a.mjs', 'b.mjs']) };
|
|
570
|
+
const cgStringifiedFileErr = validateBuiltinArgs('code_graph', cgStringifiedFileArgs);
|
|
571
|
+
if (cgStringifiedFileErr || 'file' in cgStringifiedFileArgs
|
|
572
|
+
|| !Array.isArray(cgStringifiedFileArgs.files)
|
|
573
|
+
|| cgStringifiedFileArgs.files[0] !== 'a.mjs' || cgStringifiedFileArgs.files[1] !== 'b.mjs') {
|
|
574
|
+
throw new Error(`code_graph guard must parse JSON-stringified file array: err=${cgStringifiedFileErr} args=${JSON.stringify(cgStringifiedFileArgs)}`);
|
|
575
|
+
}
|
|
576
|
+
|
|
537
577
|
const graphOut = await executeCodeGraphTool('code_graph', {
|
|
538
578
|
mode: 'symbols',
|
|
539
579
|
file: 'scripts/smoke.mjs',
|
|
@@ -549,6 +589,17 @@ if (!/# symbol_search executeBuiltinTool\b/.test(String(graphSymbolBatchOut)) ||
|
|
|
549
589
|
throw new Error(`code_graph symbol_search symbols[] batch execution failed:\n${graphSymbolBatchOut}`);
|
|
550
590
|
}
|
|
551
591
|
|
|
592
|
+
// Absorb shape 3 (real dispatch): file as a JSON-stringified array batches per
|
|
593
|
+
// file instead of hitting "file not found: [...]".
|
|
594
|
+
const graphStringifiedFileOut = await executeCodeGraphTool('code_graph', {
|
|
595
|
+
mode: 'symbols',
|
|
596
|
+
file: JSON.stringify(['scripts/smoke.mjs']),
|
|
597
|
+
}, root);
|
|
598
|
+
if (/file not found/.test(String(graphStringifiedFileOut))
|
|
599
|
+
|| !/binding|spawnSync|symbol/i.test(String(graphStringifiedFileOut))) {
|
|
600
|
+
throw new Error(`code_graph must parse JSON-stringified file array before lookup:\n${graphStringifiedFileOut}`);
|
|
601
|
+
}
|
|
602
|
+
|
|
552
603
|
const graphMissingFileOut = await executeCodeGraphTool('code_graph', {
|
|
553
604
|
mode: 'symbols',
|
|
554
605
|
file: 'src/runtime/loop.mjs',
|
|
@@ -634,6 +685,33 @@ if (!/^Error[\s:[]/.test(String(shellTimeoutOut)) || !/\[timeout: 500ms\b/.test(
|
|
|
634
685
|
throw new Error(`bash timeout must be milliseconds and classified as Error:\n${shellTimeoutOut}`);
|
|
635
686
|
}
|
|
636
687
|
|
|
688
|
+
// Auto-promotion: a sync foreground command still running past the (soft)
|
|
689
|
+
// promotion budget is detached into a tracked background task and returns the
|
|
690
|
+
// same task_id envelope as explicit async — the caller never pre-chose async.
|
|
691
|
+
// Shrink the budget via MIXDOG_SHELL_AUTO_BACKGROUND_MS so the smoke stays fast.
|
|
692
|
+
const _priorAutoBgBudget = process.env.MIXDOG_SHELL_AUTO_BACKGROUND_MS;
|
|
693
|
+
process.env.MIXDOG_SHELL_AUTO_BACKGROUND_MS = '800';
|
|
694
|
+
let shellAutoPromoteOut;
|
|
695
|
+
try {
|
|
696
|
+
shellAutoPromoteOut = await executeBuiltinTool('shell', {
|
|
697
|
+
command: 'Start-Sleep -Seconds 6; Write-Output tool-smoke-autopromote-done',
|
|
698
|
+
cwd: root,
|
|
699
|
+
timeout: 30_000,
|
|
700
|
+
shell: 'powershell',
|
|
701
|
+
}, root);
|
|
702
|
+
} finally {
|
|
703
|
+
if (_priorAutoBgBudget === undefined) delete process.env.MIXDOG_SHELL_AUTO_BACKGROUND_MS;
|
|
704
|
+
else process.env.MIXDOG_SHELL_AUTO_BACKGROUND_MS = _priorAutoBgBudget;
|
|
705
|
+
}
|
|
706
|
+
if (!/auto-backgrounded/i.test(String(shellAutoPromoteOut)) || !/task_id:\s*\S+/i.test(String(shellAutoPromoteOut))) {
|
|
707
|
+
throw new Error(`shell auto-promotion must return a background task envelope (task_id + auto-backgrounded):\n${shellAutoPromoteOut}`);
|
|
708
|
+
}
|
|
709
|
+
// Clean up the promoted job so it doesn't outlive the smoke as an orphan.
|
|
710
|
+
const _autoPromoteTaskId = (/task_id:\s*(\S+)/i.exec(String(shellAutoPromoteOut)) || [])[1];
|
|
711
|
+
if (_autoPromoteTaskId) {
|
|
712
|
+
try { await executeBuiltinTool('task', { action: 'cancel', task_id: _autoPromoteTaskId }, root); } catch {}
|
|
713
|
+
}
|
|
714
|
+
|
|
637
715
|
const legacyEscapedAlternationErr = validateBuiltinArgs('grep', { pattern: 'state\\.items\\.map\\|items\\.map', path: root });
|
|
638
716
|
if (legacyEscapedAlternationErr) {
|
|
639
717
|
throw new Error(`grep legacy \\| alternation should be accepted: ${legacyEscapedAlternationErr}`);
|
|
@@ -1217,8 +1295,10 @@ setInternalToolsProvider({
|
|
|
1217
1295
|
if (workerToolNames.includes('tool_search')) {
|
|
1218
1296
|
throw new Error(`agent session schema must not expose deferred tool_search: ${workerToolNames.join(', ')}`);
|
|
1219
1297
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1298
|
+
for (const name of ['shell', 'task']) {
|
|
1299
|
+
if (!workerToolNames.includes(name)) {
|
|
1300
|
+
throw new Error(`read-write agent session schema must expose ${name} for self-verification: ${workerToolNames.join(', ')}`);
|
|
1301
|
+
}
|
|
1222
1302
|
}
|
|
1223
1303
|
for (const name of ['skills_list', 'skill_view', 'skill_execute']) {
|
|
1224
1304
|
if (workerToolNames.includes(name)) {
|
|
@@ -1266,7 +1346,7 @@ setInternalToolsProvider({
|
|
|
1266
1346
|
const fullTools = (fullAgentSession.tools || []).map((tool) => tool?.name).filter(Boolean);
|
|
1267
1347
|
const publicExploreTools = (publicExploreSession.tools || []).map((tool) => tool?.name).filter(Boolean);
|
|
1268
1348
|
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'];
|
|
1349
|
+
const expectedWriteTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'shell', 'task', 'explore', 'search', 'web_fetch', 'Skill'];
|
|
1270
1350
|
if (JSON.stringify(readTools) !== JSON.stringify(expectedReadTools)) {
|
|
1271
1351
|
throw new Error(`read agent schema must be fixed allow-list: expected=${expectedReadTools.join(', ')} actual=${readTools.join(', ')}`);
|
|
1272
1352
|
}
|
|
@@ -1276,20 +1356,17 @@ setInternalToolsProvider({
|
|
|
1276
1356
|
if (readTools.includes('tool_search') || writeTools.includes('tool_search')) {
|
|
1277
1357
|
throw new Error(`agent session fixed schemas must omit tool_search: read=${readTools.join(', ')} write=${writeTools.join(', ')}`);
|
|
1278
1358
|
}
|
|
1279
|
-
if (readTools.includes('shell')
|
|
1280
|
-
throw new Error(`read
|
|
1359
|
+
if (readTools.includes('shell')) {
|
|
1360
|
+
throw new Error(`read agent schema must omit shell: read=${readTools.join(', ')}`);
|
|
1281
1361
|
}
|
|
1282
1362
|
for (const name of ['shell', 'apply_patch', 'task']) {
|
|
1283
1363
|
if (readTools.includes(name)) {
|
|
1284
1364
|
throw new Error(`read agent schema must omit non-read tool ${name}: read=${readTools.join(', ')}`);
|
|
1285
1365
|
}
|
|
1286
1366
|
}
|
|
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(', ')}`);
|
|
1367
|
+
for (const name of ['apply_patch', 'shell', 'task']) {
|
|
1368
|
+
if (!writeTools.includes(name)) {
|
|
1369
|
+
throw new Error(`read-write agent schema must preserve ${name}: write=${writeTools.join(', ')}`);
|
|
1293
1370
|
}
|
|
1294
1371
|
}
|
|
1295
1372
|
for (const name of ['memory', 'recall', 'reply']) {
|
|
@@ -1334,7 +1411,7 @@ setInternalToolsProvider({
|
|
|
1334
1411
|
try {
|
|
1335
1412
|
const resumed = await resumeSession(resumeAgentSession.id, 'full');
|
|
1336
1413
|
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'];
|
|
1414
|
+
const expectedWriteTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'shell', 'task', 'explore', 'search', 'web_fetch', 'Skill'];
|
|
1338
1415
|
if (JSON.stringify(resumedTools) !== JSON.stringify(expectedWriteTools)) {
|
|
1339
1416
|
throw new Error(`resumed read-write agent schema must keep fixed allow-list: expected=${expectedWriteTools.join(', ')} actual=${resumedTools.join(', ')}`);
|
|
1340
1417
|
}
|
|
@@ -1522,7 +1599,7 @@ const readPathDescription = readPathSchema.description || '';
|
|
|
1522
1599
|
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
1600
|
throw new Error('read schema must keep directory-vs-file guidance');
|
|
1524
1601
|
}
|
|
1525
|
-
if (!/
|
|
1602
|
+
if (!/Not for directory listing/i.test((BUILTIN_TOOLS.find((tool) => tool.name === 'read')?.description) || '')) {
|
|
1526
1603
|
throw new Error('read description must keep directory-vs-file guidance');
|
|
1527
1604
|
}
|
|
1528
1605
|
const readTool = BUILTIN_TOOLS.find((tool) => tool.name === 'read');
|
|
@@ -1533,7 +1610,7 @@ const readArrayItemAnyOf = readArraySchema?.items?.anyOf || [];
|
|
|
1533
1610
|
if (!readArrayItemAnyOf.some((entry) => entry?.type === 'object' && entry?.properties?.offset && entry?.properties?.limit)) {
|
|
1534
1611
|
throw new Error('read schema must expose array-of-region objects for batched spans');
|
|
1535
1612
|
}
|
|
1536
|
-
if (/line\+context/i.test(readDescription) || !/numeric offset\+limit/i.test(readDescription) || !/Batch paths\/regions as real arrays/i.test(readDescription)
|
|
1613
|
+
if (/line\+context/i.test(readDescription) || !/numeric offset\+limit/i.test(readDescription) || !/Batch paths\/regions as real arrays/i.test(readDescription)) {
|
|
1537
1614
|
throw new Error('read description must expose offset/limit as the single window form');
|
|
1538
1615
|
}
|
|
1539
1616
|
if (readProps.line || readProps.context) {
|
|
@@ -1596,9 +1673,6 @@ if (codeGraphSymbolSearchErr) {
|
|
|
1596
1673
|
if (!/code structure\/flow/i.test(codeGraphDescription) || !/symbols\/references\/calls\/deps/i.test(codeGraphDescription)) {
|
|
1597
1674
|
throw new Error('code_graph description must stay structure-oriented and name its symbol modes');
|
|
1598
1675
|
}
|
|
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
1676
|
if (!/repo-local/i.test(codeGraphDescription) || !/NOT web search|not web/i.test(codeGraphDescription)) {
|
|
1603
1677
|
throw new Error('code_graph description must mark itself repo-local (not web search)');
|
|
1604
1678
|
}
|
|
@@ -1853,23 +1927,23 @@ const grepPathDescription = grepTool?.inputSchema?.properties?.path?.description
|
|
|
1853
1927
|
const grepGlobDescription = grepTool?.inputSchema?.properties?.glob?.description || '';
|
|
1854
1928
|
const grepOutputModeDescription = grepTool?.inputSchema?.properties?.output_mode?.description || '';
|
|
1855
1929
|
const grepHeadLimitDescription = grepTool?.inputSchema?.properties?.head_limit?.description || '';
|
|
1856
|
-
if (!/
|
|
1930
|
+
if (!/Array = variants in one call/i.test(grepPatternDescription) || !/Known file or dir/i.test(grepPathDescription)) {
|
|
1857
1931
|
throw new Error('grep schema must keep compact pattern/path guidance');
|
|
1858
1932
|
}
|
|
1859
|
-
if (!/
|
|
1860
|
-
throw new Error('grep glob schema must
|
|
1933
|
+
if (!/narrow scope/i.test(grepGlobDescription)) {
|
|
1934
|
+
throw new Error('grep glob schema must describe scope narrowing');
|
|
1861
1935
|
}
|
|
1862
|
-
if (!/
|
|
1863
|
-
throw new Error('grep output_mode schema must
|
|
1936
|
+
if (!/files_with_matches\/count/i.test(grepOutputModeDescription) || !/content_with_context/i.test(grepOutputModeDescription)) {
|
|
1937
|
+
throw new Error('grep output_mode schema must name its output shapes');
|
|
1864
1938
|
}
|
|
1865
|
-
if (grepTool?.inputSchema?.properties?.head_limit?.minimum !== 0 || !/
|
|
1939
|
+
if (grepTool?.inputSchema?.properties?.head_limit?.minimum !== 0 || !/Max results/i.test(grepHeadLimitDescription)) {
|
|
1866
1940
|
throw new Error('grep head_limit schema must keep locator caps explicit');
|
|
1867
1941
|
}
|
|
1868
1942
|
if (grepTool?.inputSchema?.properties?.type) {
|
|
1869
1943
|
throw new Error('grep type schema must stay hidden; prefer glob for extension narrowing');
|
|
1870
1944
|
}
|
|
1871
|
-
if (!/
|
|
1872
|
-
throw new Error('code_graph schema fields must
|
|
1945
|
+
if (!/Repo-local/i.test(codeGraphProps.mode?.description || '') || !/one call/i.test(codeGraphProps.symbols?.description || '')) {
|
|
1946
|
+
throw new Error('code_graph schema fields must stay compact and repo-local');
|
|
1873
1947
|
}
|
|
1874
1948
|
|
|
1875
1949
|
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,
|
|
@@ -315,7 +315,7 @@ async function profiledImport(label, spec, { optional = false } = {}) {
|
|
|
315
315
|
throw error;
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
|
-
const outputStyleStatus = (dataDir = STANDALONE_DATA_DIR) => outputStyleStatusRaw(STANDALONE_ROOT, dataDir || STANDALONE_DATA_DIR);
|
|
318
|
+
const outputStyleStatus = (dataDir = STANDALONE_DATA_DIR, opts = {}) => outputStyleStatusRaw(STANDALONE_ROOT, dataDir || STANDALONE_DATA_DIR, opts);
|
|
319
319
|
// Workflow/agent pack loaders bound to this runtime's root/data layout.
|
|
320
320
|
const {
|
|
321
321
|
listWorkflowPacks,
|
|
@@ -325,6 +325,7 @@ const {
|
|
|
325
325
|
activeWorkflowSummary,
|
|
326
326
|
loadAgentDefinition,
|
|
327
327
|
workflowContextBlock,
|
|
328
|
+
activeWorkflowContext,
|
|
328
329
|
} = createWorkflowHelpers({
|
|
329
330
|
rootDir: STANDALONE_ROOT,
|
|
330
331
|
dataDir: STANDALONE_DATA_DIR,
|
|
@@ -918,6 +919,16 @@ export async function createMixdogSessionRuntime({
|
|
|
918
919
|
stopRemote('superseded-by-newer-remote-session');
|
|
919
920
|
emitRemoteStateChange(false, 'superseded');
|
|
920
921
|
}
|
|
922
|
+
// Symmetric acquire: the worker took the bridge seat (boot make-before-
|
|
923
|
+
// break or activate). Flip remote ON via the same side-state a user
|
|
924
|
+
// /remote toggles — remoteEnabled + transcript writer — but WITHOUT
|
|
925
|
+
// re-invoking channel start (the worker is already running; startRemote
|
|
926
|
+
// would re-fork/activate). Idempotent: no-op when already enabled.
|
|
927
|
+
if (msg?.params?.state === 'acquired' && !remoteEnabled) {
|
|
928
|
+
remoteEnabled = true;
|
|
929
|
+
ensureRemoteTranscriptWriter();
|
|
930
|
+
emitRemoteStateChange(true, 'acquired');
|
|
931
|
+
}
|
|
921
932
|
return;
|
|
922
933
|
}
|
|
923
934
|
if (msg?.method !== 'notifications/claude/channel') return;
|
|
@@ -1339,18 +1350,24 @@ export async function createMixdogSessionRuntime({
|
|
|
1339
1350
|
await resolveMissingRouteModelForFirstTurn();
|
|
1340
1351
|
requireModelRoute();
|
|
1341
1352
|
bootProfile('session:create:route-ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
1342
|
-
|
|
1353
|
+
// refreshRouteEffort (effort/model-meta) and loadCoreMemoryContext (memory
|
|
1354
|
+
// files) are independent — refreshRouteEffort only touches route effort/
|
|
1355
|
+
// display fields, never provider/model that the memory load reads — so run
|
|
1356
|
+
// them concurrently instead of serially on the boot path.
|
|
1357
|
+
const [, coreMemoryContext] = await Promise.all([
|
|
1358
|
+
refreshRouteEffort(),
|
|
1359
|
+
loadCoreMemoryContext(),
|
|
1360
|
+
]);
|
|
1343
1361
|
bootProfile('session:create:effort-ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
1344
1362
|
const providerImpl = reg.getProvider(route.provider);
|
|
1345
1363
|
if (!providerImpl) {
|
|
1346
1364
|
throw new Error(`Provider "${route.provider}" is not configured.`);
|
|
1347
1365
|
}
|
|
1348
1366
|
bootProfile('session:create:provider-ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
1349
|
-
const coreMemoryContext = await loadCoreMemoryContext();
|
|
1350
1367
|
if (closeRequested) throw new Error('runtime is closing');
|
|
1351
1368
|
const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
|
|
1352
|
-
|
|
1353
|
-
const workflowContext =
|
|
1369
|
+
// Load the active WORKFLOW.md pack once for both summary + context block.
|
|
1370
|
+
const { summary: workflow, context: workflowContext } = activeWorkflowContext(config, dataDir);
|
|
1354
1371
|
const sessionOpts = {
|
|
1355
1372
|
provider: route.provider,
|
|
1356
1373
|
model: route.model,
|
|
@@ -2991,7 +3008,7 @@ export async function createMixdogSessionRuntime({
|
|
|
2991
3008
|
let selectedRoute = resolveRoute(config, requested);
|
|
2992
3009
|
await reg.initProviders(ensureProviderEnabled(config, selectedRoute.provider));
|
|
2993
3010
|
const modelMeta = await lookupModelMeta(selectedRoute.provider, selectedRoute.model);
|
|
2994
|
-
// Guard (A
|
|
3011
|
+
// Guard (option A / strict reject): a free-text string must never become the
|
|
2995
3012
|
// lead model. When the caller did not pin a provider, the route did not
|
|
2996
3013
|
// match a preset, and the model is unknown to both the live provider
|
|
2997
3014
|
// catalog (modelMeta is still the {id,provider} placeholder) and the
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# Agent Constraints
|
|
2
2
|
|
|
3
3
|
- Use English for agent task communication.
|
|
4
|
-
- ONE TURN = ONE BATCH:
|
|
5
|
-
turn
|
|
6
|
-
wasted round-trip.
|
|
4
|
+
- ONE TURN = ONE BATCH: a read-only call that could have ridden the prior
|
|
5
|
+
turn is a wasted round-trip.
|
|
7
6
|
- NEVER PREAMBLE: no status/progress narration, "I will..." setup, or any text
|
|
8
7
|
before tool calls — call needed tools immediately; emit text only for the
|
|
9
8
|
final handoff after tool work is done.
|
|
@@ -15,6 +14,8 @@
|
|
|
15
14
|
searched); state only what changed and where to look. Verification =
|
|
16
15
|
command + result in one line. Same fact twice = delete one.
|
|
17
16
|
- Handoff cap ~30 lines unless `Deliver:` raises it — a ceiling, not a target.
|
|
17
|
+
- A blocked or partial report is a valid completion: state done/missing/
|
|
18
|
+
blocker in fragments — never keep retrieving to avoid reporting.
|
|
18
19
|
- Banned as pure cost: report headings, markdown tables (unless requested),
|
|
19
20
|
prose narration, raw logs/tool traces, speculative next-checks, restated
|
|
20
21
|
brief, articles/politeness.
|