mixdog 0.9.89 → 0.9.91
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 +5 -1
- package/src/output-styles/detailed.md +27 -0
- package/src/output-styles/extreme-minimal.md +8 -8
- package/src/output-styles/minimal.md +7 -10
- package/src/output-styles/simple.md +13 -21
- package/src/rules/lead/01-general.md +9 -1
- package/src/rules/lead/lead-brief.md +15 -14
- package/src/rules/shared/01-tool.md +31 -37
- package/src/runtime/agent/orchestrator/agent-runtime/commit-message-completion.mjs +67 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +35 -1
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +28 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +116 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +74 -11
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -10
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +15 -3
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +16 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +176 -16
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +30 -1
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +96 -5
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +72 -1
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +48 -9
- package/src/runtime/channels/backends/discord.mjs +21 -1
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/lib/trace-store.mjs +25 -3
- package/src/runtime/memory/tool-defs.mjs +5 -5
- package/src/runtime/search/tool-defs.mjs +2 -18
- package/src/runtime/shared/tool-execution-contract.mjs +1 -1
- package/src/session-runtime/output-styles.mjs +7 -6
- package/src/session-runtime/tool-defs.mjs +0 -1
- package/src/session-runtime/tool-surface.mjs +9 -0
- package/src/session-runtime/workflow.mjs +7 -0
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/workflows/default/WORKFLOW.md +2 -1
- package/src/workflows/solo/WORKFLOW.md +7 -5
- package/src/output-styles/default.md +0 -40
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixdog",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.91",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone mixdog coding-agent CLI/TUI workspace.",
|
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"test:route-scope": "node --test scripts/route-scope-isolation-test.mjs",
|
|
101
101
|
"test:schedule-reload": "node --test scripts/schedule-reload-arm-test.mjs",
|
|
102
102
|
"test:media": "node --test src/runtime/media/store.test.mjs src/runtime/media/renditions.test.mjs",
|
|
103
|
+
"test:shell-harness": "node --test scripts/shell-harness-regression-test.mjs",
|
|
103
104
|
"failures": "node scripts/tool-failures.mjs",
|
|
104
105
|
"trace:llm": "node scripts/llm-trace-summary.mjs",
|
|
105
106
|
"diag:sessions": "node scripts/session-diag.mjs",
|
|
@@ -163,5 +164,8 @@
|
|
|
163
164
|
"acorn": "^8.17.0",
|
|
164
165
|
"esbuild": "^0.28.1",
|
|
165
166
|
"eslint-scope": "^9.1.2"
|
|
167
|
+
},
|
|
168
|
+
"optionalDependencies": {
|
|
169
|
+
"@vscode/ripgrep": "^1.18.0"
|
|
166
170
|
}
|
|
167
171
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: detailed
|
|
3
|
+
title: Detailed
|
|
4
|
+
description: Detailed engineering summaries
|
|
5
|
+
aliases: verbose, full
|
|
6
|
+
keep-coding-instructions: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Output Style
|
|
10
|
+
|
|
11
|
+
Detailed — the fullest style, yet still summary-form, never essay-form.
|
|
12
|
+
Depth comes from picking the right facts, not explaining more.
|
|
13
|
+
|
|
14
|
+
- Lead with the outcome in one short sentence, then only the detail that
|
|
15
|
+
matters: what changed and the key facts (paths, commands, errors).
|
|
16
|
+
Conclusions, not reasoning; cite a symbol/path only as an anchor. Complete
|
|
17
|
+
sentences in the user's language; commands, code, and errors verbatim.
|
|
18
|
+
- Say each point once. Size budget: roughly TWICE Simple — ~2 rendered lines
|
|
19
|
+
per point, whole report ~10–15 lines.
|
|
20
|
+
- Short labels such as `Changes` or `Risks / next steps` in final reports
|
|
21
|
+
only; none on interim progress; collapse trivial tasks to a couple of
|
|
22
|
+
sentences. Never dump raw tool output.
|
|
23
|
+
- Do not hide blockers or failures; one short clause each.
|
|
24
|
+
- One bullet = one idea, at most 2 rendered lines, opened with a short
|
|
25
|
+
**bold key point**; blank line between multi-line items; nest one
|
|
26
|
+
sub-level at most.
|
|
27
|
+
- Never name this style unless asked.
|
|
@@ -11,12 +11,12 @@ keep-coding-instructions: true
|
|
|
11
11
|
Extreme minimal — the most compressed style: exactly one sentence, under 100
|
|
12
12
|
characters.
|
|
13
13
|
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
- No headings, bullets,
|
|
19
|
-
|
|
14
|
+
- A SINGLE sentence, always under 100 characters — never a second sentence or
|
|
15
|
+
a run-on that smuggles in extra facts.
|
|
16
|
+
- Net result only: drop file lists, methods, and follow-ups unless one is
|
|
17
|
+
the single decisive fact.
|
|
18
|
+
- No headings, bullets, labels, or sections — one plain sentence, even when
|
|
19
|
+
the request says "report".
|
|
20
20
|
- Preferred pattern: `<target> changed.`
|
|
21
|
-
- Preserve
|
|
22
|
-
|
|
21
|
+
- Preserve one decisive path, command, symbol, or error verbatim, only if it
|
|
22
|
+
fits the limit.
|
|
@@ -9,15 +9,12 @@ keep-coding-instructions: true
|
|
|
9
9
|
|
|
10
10
|
Minimal — a very short summary: one or two sentences, nothing more.
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
the request says "report" or "summary".
|
|
20
|
-
- Preferred pattern: `<target> changed. <verification> passed.` If
|
|
21
|
-
verification was not run, say so.
|
|
12
|
+
- One short sentence with the net result; a second only for a fact that
|
|
13
|
+
genuinely needs it — never a run-on.
|
|
14
|
+
- Roughly HALF Simple: 1–2 plain sentences (~2–3 rendered lines) however
|
|
15
|
+
large the task, concept-level only.
|
|
16
|
+
- Never itemize: no headings, bullets, labels, sections, or file-by-file
|
|
17
|
+
detail — even when the request says "report".
|
|
18
|
+
- Preferred pattern: `<target> changed.`
|
|
22
19
|
- Preserve only the single decisive path, command, symbol, API name, code, or
|
|
23
20
|
error verbatim.
|
|
@@ -2,32 +2,24 @@
|
|
|
2
2
|
name: simple
|
|
3
3
|
title: Simple
|
|
4
4
|
description: Outcome-first concise handoffs for coding work
|
|
5
|
-
aliases: concise, handoff
|
|
5
|
+
aliases: concise, handoff, default
|
|
6
6
|
keep-coding-instructions: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Output Style
|
|
10
10
|
|
|
11
|
-
Practical concise — outcome-first handoffs
|
|
12
|
-
|
|
11
|
+
Practical concise — outcome-first handoffs: summarize the result, do not
|
|
12
|
+
narrate the work.
|
|
13
13
|
|
|
14
14
|
- Open with the outcome in one sentence: done, blocked, or awaiting a decision.
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
phrase; blank line between multi-line list items — never a dense wall of
|
|
26
|
-
text.
|
|
27
|
-
- Final handoffs may use labels like `Changes`, `Verification`, and
|
|
28
|
-
`Risks / next steps`; do not label interim progress.
|
|
29
|
-
- Synthesize agent or retrieval results; never forward raw reports, long file
|
|
30
|
-
lists, tool traces, or session metadata.
|
|
31
|
-
- Do not hide blockers, failed verification, or required follow-up — state
|
|
32
|
-
them in one short clause; if verification was not run, say so once.
|
|
15
|
+
- Concept-level summary of what changed, not a per-file changelog; cite a
|
|
16
|
+
path (`file:line`) only as an anchor. Complete sentences in the user's
|
|
17
|
+
language; paths, commands, symbols, code, and errors verbatim.
|
|
18
|
+
- 1–3 short bullets or 2–3 sentences, each point once; whole reply ~5–7
|
|
19
|
+
lines (HALF Detailed, TWICE Minimal).
|
|
20
|
+
- One idea per bullet, ONE line each, led by a short bold key phrase; blank
|
|
21
|
+
line between multi-line items.
|
|
22
|
+
- Final handoffs may use short labels like `Changes` or `Risks / next
|
|
23
|
+
steps`; none on interim progress. Never dump raw tool output.
|
|
24
|
+
- Do not hide blockers or failures; one short clause each.
|
|
33
25
|
- Never name this style unless asked.
|
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
multi-provider agent workflows. Never identify as generic OpenAI/ChatGPT.
|
|
5
5
|
- A preamble is at most one useful sentence, with no direct names, honorifics,
|
|
6
6
|
headings, labels, or routine lookup narration.
|
|
7
|
-
- Destructive/hard-to-reverse action needs explicit confirmation
|
|
7
|
+
- Destructive/hard-to-reverse action needs explicit confirmation and explicit
|
|
8
|
+
validated target paths — never `~`, a root, or unresolved variables/globs;
|
|
9
|
+
report material deletions with recoverability.
|
|
8
10
|
- Act proactively; ask only for decisions.
|
|
11
|
+
- Mid-task input: a replacement supersedes current work, an addition folds
|
|
12
|
+
into it, a status question gets a brief answer while work continues; after
|
|
13
|
+
context compaction continue from the summary — never restart or redo
|
|
14
|
+
finished work.
|
|
15
|
+
- When blocked, exhaust safe in-scope checks once and report the blocker;
|
|
16
|
+
never spend turns without a tool call or new evidence.
|
|
9
17
|
- Your final message ends the turn: answer only when the work is done. After a
|
|
10
18
|
failed tool call, fix and re-run it, or state plainly that it is unresolved.
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
# Lead Brief
|
|
2
2
|
|
|
3
|
-
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
- Minimum chars, maximum info: one-line fragments. `Task:` is mandatory and
|
|
4
|
+
lossless: each role
|
|
5
|
+
constructs it from the original request and official spec/test acceptance
|
|
6
|
+
criteria, preserving intent, required and forbidden outcomes,
|
|
7
|
+
completion/stop boundary, user-supplied exact targets, and exact
|
|
8
|
+
replacements/outputs. Never infer exactness from task name, file count, or
|
|
9
|
+
difficulty.
|
|
9
10
|
- Omit role-known rules, repeated context/facts, and padding; split scope
|
|
10
11
|
without discarding requirements.
|
|
11
|
-
- Other fields are task-specific deltas
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
method is required.
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
- Never `send` mid-run; batch one follow-up after completion; interrupt only
|
|
18
|
-
cancel. Agent communication is English.
|
|
12
|
+
- Other fields are task-specific deltas — `Anchors:` (`file:line` plus a
|
|
13
|
+
one-line conclusion, never log/code bodies), `Allow/Forbid:`, `Deliver:`
|
|
14
|
+
(sets handoff shape/size); omit empty fields. State outcomes, not methods,
|
|
15
|
+
unless the method is required.
|
|
16
|
+
- Full brief only for a fresh spawn or `respawned: true`; live follow-ups
|
|
17
|
+
carry only the delta; a dead-tag send is cold and must re-supply anchors.
|
|
18
|
+
- Never `send` mid-run; batch one follow-up after completion; interrupt only
|
|
19
|
+
to cancel. Agent communication is English.
|
|
@@ -1,40 +1,34 @@
|
|
|
1
1
|
# Tool Use
|
|
2
2
|
|
|
3
|
-
- Before the first call, gather every known facet in one tool message;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
`
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
arrays covering the whole logical unit — in one `path[]` call, and graph
|
|
20
|
-
targets in arrays. Don't reread returned spans. Put all new edits in one
|
|
21
|
-
patch.
|
|
3
|
+
- Before the first call, gather every known facet in one tool message; one
|
|
4
|
+
shortest route per facet: broad/uncertain→`explore` (roles without it:
|
|
5
|
+
`find`); partial path/name→`find`; verified root+wildcard→`glob`;
|
|
6
|
+
quoted/non-identifier literal or regex→`grep`; exact code identifier/
|
|
7
|
+
relation→`code_graph` before grep; known file/span→`read` directly without
|
|
8
|
+
`grep`; verified directory→`list`; known edit→`apply_patch` (span already
|
|
9
|
+
seen; else `read`/`grep` first); program/state change→`shell`; web/current
|
|
10
|
+
external info→`search`.
|
|
11
|
+
- Shortest total calls, maximum batching — every turn: all independent calls
|
|
12
|
+
in one concurrent message (shell included); combine variants/symbols/
|
|
13
|
+
scopes/paths/queries per call; same-file regions as one real
|
|
14
|
+
`{path,offset,limit}` array; graph targets as arrays; `explore` facets in
|
|
15
|
+
one `query[]` (max 8, no rephrased duplicates); all new edits in one patch.
|
|
16
|
+
Distinct facets, not alternative routes; sequential singles only for a
|
|
17
|
+
genuinely dependent step (unconditional follow-ups are not dependent —
|
|
18
|
+
batch them); only apply_patch executes in order.
|
|
22
19
|
- Verified paths: project root, session cwd, user-provided, tool-returned.
|
|
23
|
-
`find` first for guessed path/name fragments
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- `apply_patch`
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- A command promoted to background is a decision point: continue only if
|
|
39
|
-
observed progress fits the budget, otherwise switch routes. Waiting is an
|
|
40
|
-
explicit choice.
|
|
20
|
+
`find` first for guessed path/name fragments; on ENOENT, find the basename.
|
|
21
|
+
Retry `EXPLORATION_FAILED` once with changed tokens.
|
|
22
|
+
- Stop when evidence covers the deliverable: a returned `path:line` or
|
|
23
|
+
nonzero `content_with_context` result is final — act on it (inspecting it
|
|
24
|
+
via read/code_graph is valid); only zero/error results justify changed
|
|
25
|
+
tokens or scope. Don't re-locate, re-verify, or reread returned spans.
|
|
26
|
+
- `apply_patch` is the primary edit tool: send the patch as soon as target
|
|
27
|
+
path and new content are known. Hunk context comes verbatim from the newest
|
|
28
|
+
tool output of that span (`read`/`grep`/your own patch — post-patch content
|
|
29
|
+
after edits), never retyped from memory; one look-up beats a failed patch.
|
|
30
|
+
A same-turn shell after `apply_patch` runs once the patch lands.
|
|
31
|
+
- After starting or receiving a background task, end the turn — its
|
|
32
|
+
completion notification resumes the work. Never poll, sleep-loop, or block;
|
|
33
|
+
explicit wait only for a result the current turn cannot proceed without.
|
|
34
|
+
Long commands whose output the next step does not need go async.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { loadConfig } from '../config.mjs';
|
|
2
|
+
import { getProvider, initProviders } from '../providers/registry.mjs';
|
|
3
|
+
import { resolveMaintenanceRoute } from './maintenance-route.mjs';
|
|
4
|
+
|
|
5
|
+
export const COMMIT_MESSAGE_SYSTEM_PROMPT = 'You are generating one git commit message from the provided diff. First line: imperative mood, at most 72 characters, no trailing period. Optionally add a blank line and a short body (wrapped at 72 characters) explaining WHY. Output ONLY the commit message - no preamble, no code fences, no quotes.';
|
|
6
|
+
|
|
7
|
+
export function commitMessageSystemPrompt(style = '') {
|
|
8
|
+
const hint = String(style || '').trim();
|
|
9
|
+
return hint ? `${COMMIT_MESSAGE_SYSTEM_PROMPT}\n${hint}` : COMMIT_MESSAGE_SYSTEM_PROMPT;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function resultText(result) {
|
|
13
|
+
if (typeof result === 'string') return result;
|
|
14
|
+
if (typeof result?.content === 'string') return result.content;
|
|
15
|
+
if (Array.isArray(result?.content)) {
|
|
16
|
+
return result.content
|
|
17
|
+
.map((part) => part?.type === 'text' ? String(part.text || '') : '')
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.join('\n');
|
|
20
|
+
}
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createCommitMessageCompletion(deps = {}) {
|
|
25
|
+
const load = deps.loadConfig || loadConfig;
|
|
26
|
+
const resolveRoute = deps.resolveMaintenanceRoute || resolveMaintenanceRoute;
|
|
27
|
+
const initialize = deps.initProviders || initProviders;
|
|
28
|
+
const providerFor = deps.getProvider || getProvider;
|
|
29
|
+
|
|
30
|
+
return async function generateCommitMessage(source, options = {}) {
|
|
31
|
+
const text = String(source || '').trim();
|
|
32
|
+
if (!text) return '';
|
|
33
|
+
const signal = options.signal || null;
|
|
34
|
+
const config = load();
|
|
35
|
+
// Commit messages are maintenance-class work: they ride the same
|
|
36
|
+
// route as session titles instead of the main conversation model.
|
|
37
|
+
const route = resolveRoute({
|
|
38
|
+
agent: 'title-agent',
|
|
39
|
+
config,
|
|
40
|
+
});
|
|
41
|
+
if (!route || typeof route !== 'object') {
|
|
42
|
+
throw new Error('Commit message maintenance route is unresolved.');
|
|
43
|
+
}
|
|
44
|
+
const providerName = String(route.provider || '').trim();
|
|
45
|
+
const model = String(route.model || '').trim();
|
|
46
|
+
if (!providerName || !model) {
|
|
47
|
+
throw new Error('Commit message maintenance route requires provider and model.');
|
|
48
|
+
}
|
|
49
|
+
await initialize(config.providers || {}, { signal });
|
|
50
|
+
const provider = providerFor(providerName);
|
|
51
|
+
if (!provider || typeof provider.send !== 'function') {
|
|
52
|
+
throw new Error(`Commit message provider is unavailable: ${providerName}`);
|
|
53
|
+
}
|
|
54
|
+
const response = await provider.send([
|
|
55
|
+
{ role: 'system', content: commitMessageSystemPrompt(options.style) },
|
|
56
|
+
{ role: 'user', content: text },
|
|
57
|
+
], model, undefined, {
|
|
58
|
+
signal,
|
|
59
|
+
effort: String(route.effort || '').trim() || 'low',
|
|
60
|
+
fast: route.fast === true,
|
|
61
|
+
maxOutputTokens: 400,
|
|
62
|
+
});
|
|
63
|
+
return resultText(response).trim();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const generateCommitMessage = createCommitMessageCompletion();
|
|
@@ -128,6 +128,12 @@ export {
|
|
|
128
128
|
// this catches tight deterministic-failure loops (e.g. a command that errors
|
|
129
129
|
// the same way every time) far earlier than 100 iterations.
|
|
130
130
|
const REPEAT_FAIL_LIMIT = 3;
|
|
131
|
+
// Structured provider continuations (endTurn=false / pause_turn) are honored,
|
|
132
|
+
// but must not sustain an unbounded text-only loop: a lead session was
|
|
133
|
+
// observed burning a 30-minute agent budget (26K output tokens, zero tool
|
|
134
|
+
// calls) on back-to-back continuations. After this many continuations with no
|
|
135
|
+
// intervening tool batch, the current text is accepted as the final answer.
|
|
136
|
+
const PROVIDER_CONTINUATION_NO_TOOL_LIMIT = Math.max(1, Number(process.env.MIXDOG_PROVIDER_CONTINUATION_NO_TOOL_LIMIT) || 8);
|
|
131
137
|
// A provider max-output stop is not a completed assistant turn, even when it
|
|
132
138
|
// contains useful text. Preserve each partial in the provider transcript and
|
|
133
139
|
// grant at most three direct continuations before surfacing a hard truncation.
|
|
@@ -384,6 +390,12 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
384
390
|
// hard iteration cap remains the sole bound on how long a provider may
|
|
385
391
|
// keep declaring "not done" inside one user turn.
|
|
386
392
|
let _providerContinuationCount = 0;
|
|
393
|
+
// Continuations since the last executed tool batch — bounds the text-only
|
|
394
|
+
// continuation runaway (see PROVIDER_CONTINUATION_NO_TOOL_LIMIT).
|
|
395
|
+
let _continuationsSinceToolBatch = 0;
|
|
396
|
+
// Loop-level transport replays consumed this ask (see send-with-recovery
|
|
397
|
+
// TRANSPORT_RETRY_MAX): bounded per turn, reset only with a fresh ask.
|
|
398
|
+
let _transportRetriesUsed = 0;
|
|
387
399
|
// Claude Code parity: queued prompt/task notifications are attached after a
|
|
388
400
|
// tool batch, before the continuation provider send. Normal batches drain
|
|
389
401
|
// up to 'next'; a Sleep-like tool grants a 'later' flush.
|
|
@@ -627,6 +639,7 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
627
639
|
() => sendWithRecovery({
|
|
628
640
|
provider, messages, model, sendTools, tools: sendTools, opts,
|
|
629
641
|
sessionId, sessionRef, nextIteration, contextOverflowRetryUsed,
|
|
642
|
+
transportRetriesUsed: _transportRetriesUsed, signal,
|
|
630
643
|
}),
|
|
631
644
|
);
|
|
632
645
|
if (_sendResult.action === 'retry') {
|
|
@@ -635,6 +648,11 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
635
648
|
reactiveOverflowRetryPending = true;
|
|
636
649
|
continue;
|
|
637
650
|
}
|
|
651
|
+
if (_sendResult.action === 'retry_transport') {
|
|
652
|
+
_transportRetriesUsed += 1;
|
|
653
|
+
delete opts.cacheBreakIntent;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
638
656
|
response = _sendResult.response;
|
|
639
657
|
opts.onToolCall = undefined;
|
|
640
658
|
delete opts.cacheBreakIntent;
|
|
@@ -821,11 +839,26 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
821
839
|
const continuationSignal = !isOutputLimitStop && stopReason !== 'refusal'
|
|
822
840
|
? providerContinuationSignal(response)
|
|
823
841
|
: null;
|
|
824
|
-
if (continuationSignal &&
|
|
842
|
+
if (continuationSignal && _continuationsSinceToolBatch >= PROVIDER_CONTINUATION_NO_TOOL_LIMIT) {
|
|
843
|
+
// Text-only continuation runaway: stop honoring the signal and
|
|
844
|
+
// fall through to the terminal handling below, which accepts
|
|
845
|
+
// the current content as the final answer (or ends the loop).
|
|
846
|
+
process.stderr.write(`[loop] provider continuation cap ${PROVIDER_CONTINUATION_NO_TOOL_LIMIT} reached without tool calls (sess=${sessionId || 'unknown'}); accepting current text as final.\n`);
|
|
847
|
+
try {
|
|
848
|
+
appendAgentTrace({
|
|
849
|
+
sessionId,
|
|
850
|
+
iteration: iterations,
|
|
851
|
+
kind: 'steer',
|
|
852
|
+
payload: { tag: 'provider_continuation_no_tool_cap', count: _continuationsSinceToolBatch },
|
|
853
|
+
agent: sessionAgent || null,
|
|
854
|
+
});
|
|
855
|
+
} catch { /* best-effort */ }
|
|
856
|
+
} else if (continuationSignal && pushIntermediateAssistantResponse(response)) {
|
|
825
857
|
if (hasContent && !suppressMidTurnText) {
|
|
826
858
|
try { opts.onAssistantText?.(response.content); } catch { /* best-effort */ }
|
|
827
859
|
}
|
|
828
860
|
_providerContinuationCount += 1;
|
|
861
|
+
_continuationsSinceToolBatch += 1;
|
|
829
862
|
_emptyNudgeStreak = 0;
|
|
830
863
|
try {
|
|
831
864
|
appendAgentTrace({
|
|
@@ -1018,6 +1051,7 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
1018
1051
|
// Settle the stop hook on the batch that actually executed.
|
|
1019
1052
|
_toolFailureStopHook.endBatch(_callsToExecute);
|
|
1020
1053
|
_toolBatchJustCompleted = true;
|
|
1054
|
+
_continuationsSinceToolBatch = 0;
|
|
1021
1055
|
_lastToolBatchHadSleep = _callsToExecute.some(isSleepLikeToolCall);
|
|
1022
1056
|
}
|
|
1023
1057
|
// Classify WHY the loop ended so agent-tool can promote an empty/abnormal
|
|
@@ -28,6 +28,30 @@ const STORED_TOOL_ARG_LIMIT = 10_000;
|
|
|
28
28
|
const STORED_TOOL_ARG_PREVIEW_HEAD = 360;
|
|
29
29
|
const STORED_TOOL_ARG_PREVIEW_TAIL = 160;
|
|
30
30
|
|
|
31
|
+
// File paths a compacted patch touched, so the marker itself tells the model
|
|
32
|
+
// WHICH files to re-read instead of replaying the marker as patch input
|
|
33
|
+
// (measured: the compacted-placeholder resubmission was 39% of apply_patch
|
|
34
|
+
// failures). Marker contract: the returned text may not contain ']' or a
|
|
35
|
+
// newline, so bracket characters are stripped from paths.
|
|
36
|
+
function _compactedPatchTargets(value) {
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
const add = (raw) => {
|
|
39
|
+
const p = String(raw || '').trim().replace(/[\[\]\r\n]/g, '');
|
|
40
|
+
if (p && p !== '/dev/null' && !seen.has(p)) seen.add(p);
|
|
41
|
+
};
|
|
42
|
+
const v4a = /^\*\*\*\s*(?:Update|Add|Delete) File:\s*(.+)$/gim;
|
|
43
|
+
for (let m; seen.size < 12 && (m = v4a.exec(value));) add(m[1]);
|
|
44
|
+
if (!seen.size) {
|
|
45
|
+
const uni = /^(?:\+\+\+|---)\s+(?:[ab]\/)?(\S+)/gm;
|
|
46
|
+
for (let m; seen.size < 12 && (m = uni.exec(value));) add(m[1]);
|
|
47
|
+
}
|
|
48
|
+
const all = [...seen];
|
|
49
|
+
const shown = all.slice(0, 4).map((p) => (p.length > 70 ? `…${p.slice(-70)}` : p));
|
|
50
|
+
if (!shown.length) return '';
|
|
51
|
+
const more = all.length > shown.length ? ` +${all.length - shown.length} more` : '';
|
|
52
|
+
return `${shown.join(', ')}${more}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
31
55
|
function compactStoredToolArgString(value, key = '', opts = {}) {
|
|
32
56
|
if (typeof value !== 'string') return value;
|
|
33
57
|
const isBody = STORED_TOOL_ARG_BODY_KEY_RE.test(key);
|
|
@@ -39,8 +63,11 @@ function compactStoredToolArgString(value, key = '', opts = {}) {
|
|
|
39
63
|
// Body markers carry the recovery instruction inline: the compaction
|
|
40
64
|
// detectors only require the `[mixdog compacted ...]` shape (no ']' or
|
|
41
65
|
// newline inside), so the longer text stays fully compatible.
|
|
66
|
+
const targets = /^patch$/i.test(key) ? _compactedPatchTargets(value) : '';
|
|
42
67
|
const marker = isBody
|
|
43
|
-
?
|
|
68
|
+
? (targets
|
|
69
|
+
? `[mixdog compacted ${key}: ${value.length} chars, sha256:${hash}; already applied to ${targets} - do not copy; re-read those files and write a fresh patch]`
|
|
70
|
+
: `[mixdog compacted ${key}: ${value.length} chars, sha256:${hash}; already applied - do not copy; re-read the file and write a fresh patch]`)
|
|
44
71
|
: `[mixdog compacted ${key || 'string'}: ${value.length} chars, sha256:${hash}; do not copy]`;
|
|
45
72
|
// Body args (patch / old_string / new_string / content / rewrite) are
|
|
46
73
|
// apply_patch / edit inputs. Keeping a head/tail preview leaves real patch
|
|
@@ -318,7 +318,11 @@ export function createSession(opts) {
|
|
|
318
318
|
const hasCallerAllow = Array.isArray(opts.schemaAllowedTools);
|
|
319
319
|
const tools = finalizeSessionToolList(toolsForRouting, {
|
|
320
320
|
schemaAllowedTools: hasCallerAllow ? opts.schemaAllowedTools : null,
|
|
321
|
-
disallowedTools:
|
|
321
|
+
disallowedTools: [
|
|
322
|
+
...(Array.isArray(opts.disallowedTools) ? opts.disallowedTools : []),
|
|
323
|
+
...(hiddenAgent ? ['Skill'] : []),
|
|
324
|
+
...(!ownerIsAgent && workflowDisallowsAgentTool(opts.workflow) ? ['agent'] : []),
|
|
325
|
+
],
|
|
322
326
|
ownerIsAgent,
|
|
323
327
|
resolvedAgent,
|
|
324
328
|
});
|
|
@@ -499,6 +503,17 @@ const ACTIVE_OWNER_HB_FRESH_MS = 2 * 60 * 1000; // heartbeat freshness window
|
|
|
499
503
|
const PREPARED_RESUME_LIMIT = 8;
|
|
500
504
|
const _preparedResumes = new Map();
|
|
501
505
|
|
|
506
|
+
// A workflow that delegates to NOBODY (agents: declared empty — e.g. Solo)
|
|
507
|
+
// must not put the `agent` tool in the session tool list: policy rejects
|
|
508
|
+
// every call, so a schema-visible tool is a guaranteed error turn plus dead
|
|
509
|
+
// schema weight. Field source: workflowSummary() carries agentsConfigured /
|
|
510
|
+
// agents; older persisted sessions lack them and keep the tool (safe).
|
|
511
|
+
function workflowDisallowsAgentTool(workflow) {
|
|
512
|
+
return Boolean(workflow && typeof workflow === 'object'
|
|
513
|
+
&& workflow.agentsConfigured === true
|
|
514
|
+
&& Array.isArray(workflow.agents) && workflow.agents.length === 0);
|
|
515
|
+
}
|
|
516
|
+
|
|
502
517
|
function _prepareResumeTools(session, preset) {
|
|
503
518
|
const ownerIsAgent = isAgentOwner(session);
|
|
504
519
|
const skills = ownerIsAgent ? [] : collectPromptSkillsCached(session.cwd);
|
|
@@ -521,7 +536,10 @@ function _prepareResumeTools(session, preset) {
|
|
|
521
536
|
ownerIsAgent,
|
|
522
537
|
tools: finalizeSessionToolList(toolsForRouting, {
|
|
523
538
|
schemaAllowedTools: Array.isArray(session.schemaAllowedTools) ? session.schemaAllowedTools : null,
|
|
524
|
-
disallowedTools:
|
|
539
|
+
disallowedTools: [
|
|
540
|
+
...(getHiddenAgent(session.agent || null) ? ['Skill'] : []),
|
|
541
|
+
...(!isAgentOwner(session) && workflowDisallowsAgentTool(session.workflow) ? ['agent'] : []),
|
|
542
|
+
],
|
|
525
543
|
ownerIsAgent,
|
|
526
544
|
resolvedAgent: session.agent || null,
|
|
527
545
|
}),
|