mixdog 0.9.110 → 0.9.111
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 +1 -2
- package/scripts/routing-corpus.mjs +5 -6
- package/scripts/run-suite.mjs +0 -1
- package/src/app.mjs +0 -1
- package/src/defaults/agents.json +0 -12
- package/src/defaults/skills/setup/SKILL.md +1 -1
- package/src/headless-command.mjs +1 -3
- package/src/headless-role.mjs +2 -4
- package/src/help.mjs +1 -1
- package/src/rules/shared/01-tool.md +2 -7
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +6 -6
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +2 -25
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +3 -3
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -1
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +1 -1
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +0 -1
- package/src/runtime/agent/orchestrator/config.mjs +6 -6
- package/src/runtime/agent/orchestrator/context/collect.mjs +1 -2
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +17 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +19 -9
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +7 -25
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +0 -12
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -29
- package/src/runtime/agent/orchestrator/stall-policy.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +4 -6
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +2 -15
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +22 -35
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -5
- package/src/runtime/channels/lib/output-forwarder.mjs +0 -13
- package/src/runtime/channels/lib/tool-format.mjs +1 -2
- package/src/runtime/search/index.mjs +12 -6
- package/src/runtime/search/tool-defs.mjs +2 -2
- package/src/runtime/shared/agent-route-config.mjs +2 -3
- package/src/runtime/shared/child-spawn-gate.mjs +1 -1
- package/src/runtime/shared/resource-admission.mjs +1 -1
- package/src/runtime/shared/tool-card-model.mjs +3 -5
- package/src/runtime/shared/tool-primitives.mjs +0 -1
- package/src/runtime/shared/tool-result-summary.mjs +0 -7
- package/src/runtime/shared/tool-surface.mjs +1 -14
- package/src/session-runtime/runtime-core.mjs +1 -24
- package/src/session-runtime/settings-api.mjs +0 -8
- package/src/session-runtime/tool-catalog-data.mjs +4 -7
- package/src/session-runtime/tool-catalog-schema.mjs +1 -1
- package/src/session-runtime/workflow-agents-api.mjs +0 -3
- package/src/session-runtime/workflow.mjs +3 -7
- package/src/standalone/agent-tool/helpers.mjs +0 -1
- package/src/standalone/agent-tool/render.mjs +1 -1
- package/src/standalone/agent-tool/shard-spread.mjs +2 -2
- package/src/standalone/session-client.mjs +79 -20
- package/src/standalone/session-protocol.mjs +0 -1
- package/src/standalone/session-transport.mjs +19 -3
- package/src/tui/app/model-options.mjs +2 -3
- package/src/tui/app/onboarding-steps.mjs +1 -1
- package/src/tui/app/settings-picker.mjs +0 -12
- package/src/tui/app/transcript-row-estimate.mjs +1 -1
- package/src/tui/app/use-transcript-activity.mjs +7 -17
- package/src/tui/dist/index.mjs +9 -51
- package/src/tui/session/agent-envelope.mjs +1 -1
- package/src/tui/session/live-share.mjs +1 -1
- package/src/tui/session/session-api.mjs +0 -10
- package/src/tui/session/turn.mjs +2 -2
- package/src/tui/session-local.mjs +8 -11
- package/src/ui/statusline-agents.mjs +1 -2
- package/src/ui/statusline-format.mjs +1 -1
- package/src/ui/statusline.mjs +1 -6
- package/src/agents/explore/AGENT.md +0 -8
- package/src/agents/explore/agent.json +0 -6
- package/src/rules/agent/30-explorer.md +0 -55
- package/src/standalone/explore-tool.mjs +0 -770
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixdog",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.111",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone mixdog coding-agent CLI/TUI workspace.",
|
|
@@ -125,7 +125,6 @@
|
|
|
125
125
|
"bench:run": "node scripts/bench-run.mjs",
|
|
126
126
|
"bench:recall": "node scripts/recall-bench.mjs",
|
|
127
127
|
"bench:tui-load": "node scripts/tui-runtime-load-bench.mjs",
|
|
128
|
-
"bench:explore": "node scripts/explore-bench.mjs",
|
|
129
128
|
"bench:output-style": "node scripts/output-style-bench.mjs",
|
|
130
129
|
"bench:session-context": "node scripts/session-context-bench.mjs",
|
|
131
130
|
"bench:session-transport": "node scripts/session-transport-bench.mjs",
|
|
@@ -68,7 +68,6 @@ function argsSummary(tool, args) {
|
|
|
68
68
|
const values = args.symbols || args.files || args.symbol || args.file || '';
|
|
69
69
|
return clip(`${args.mode || '?'}:${Array.isArray(values) ? `[${values.length}]${values[0] || ''}` : values}`);
|
|
70
70
|
}
|
|
71
|
-
case 'explore': return arr(args.query);
|
|
72
71
|
case 'find': return clip(args.query);
|
|
73
72
|
case 'glob': return clip(Array.isArray(args.pattern) ? args.pattern[0] : args.pattern);
|
|
74
73
|
case 'list': return clip(basename(String(args.path || '')));
|
|
@@ -95,7 +94,7 @@ function targetValues(tool, args) {
|
|
|
95
94
|
? (args.files != null ? 'files' : 'file')
|
|
96
95
|
: symbolMode || args.mode === 'symbols' ? (args.symbols != null ? 'symbols' : 'symbol') : 'file';
|
|
97
96
|
} else if (tool === 'grep' || tool === 'glob') key = 'pattern';
|
|
98
|
-
else if (tool === 'find'
|
|
97
|
+
else if (tool === 'find') key = 'query';
|
|
99
98
|
const value = args[key];
|
|
100
99
|
return Array.isArray(value) ? value : value == null ? [] : [value];
|
|
101
100
|
}
|
|
@@ -110,7 +109,7 @@ function batchFields(tool, args) {
|
|
|
110
109
|
? (args?.files != null ? 'files' : args?.file != null ? 'file' : args?.symbols != null ? 'symbols' : 'symbol')
|
|
111
110
|
: null].filter(Boolean);
|
|
112
111
|
}
|
|
113
|
-
return [tool === 'read' || tool === 'list' ? 'path' : tool === 'find'
|
|
112
|
+
return [tool === 'read' || tool === 'list' ? 'path' : tool === 'find' ? 'query' : null].filter(Boolean);
|
|
114
113
|
}
|
|
115
114
|
function compatibleBatchCalls(tool, left, right) {
|
|
116
115
|
if (tool === 'read') {
|
|
@@ -149,7 +148,7 @@ function batchSpec(tool, args, forcedField = null) {
|
|
|
149
148
|
: (args.symbols != null ? 'symbols' : 'symbol');
|
|
150
149
|
return { field, values: targetValues(tool, args) };
|
|
151
150
|
}
|
|
152
|
-
const fieldName = forcedField || (tool === 'read' || tool === 'list' ? 'path' : tool === 'grep' || tool === 'glob' ? 'pattern' : tool === 'find'
|
|
151
|
+
const fieldName = forcedField || (tool === 'read' || tool === 'list' ? 'path' : tool === 'grep' || tool === 'glob' ? 'pattern' : tool === 'find' ? 'query' : null);
|
|
153
152
|
if (!fieldName) return null;
|
|
154
153
|
const value = args[fieldName];
|
|
155
154
|
return { field: fieldName, values: Array.isArray(value) ? value : value == null ? [] : [value] };
|
|
@@ -166,7 +165,7 @@ function sameIterationBatchObservations(sequence) {
|
|
|
166
165
|
if (isMutation(next.tool)) break;
|
|
167
166
|
group.push(next);
|
|
168
167
|
}
|
|
169
|
-
for (const candidate of ['read', 'grep', 'find', 'glob', 'list', '
|
|
168
|
+
for (const candidate of ['read', 'grep', 'find', 'glob', 'list', 'code_graph']) {
|
|
170
169
|
if (candidate === 'read') {
|
|
171
170
|
const calls = group.filter((entry) => entry.tool === 'read' && !entry.failed).map((entry) => ({ entry, targets: readTargets(entry.rawArgs) })).filter(({ targets }) => targets?.length);
|
|
172
171
|
if (calls.some(({ entry, targets }, index) => calls.some(({ entry: other, targets: otherTargets }, otherIndex) => (
|
|
@@ -238,7 +237,7 @@ function buildCase(sid, toolRows) {
|
|
|
238
237
|
const observations = [];
|
|
239
238
|
if (sequence.some((s) => s.tool === 'code_graph' && s.rawArgs?.mode === 'find_symbol' && !s.rawArgs?.file && !s.rawArgs?.files)) flags.push('find_symbol_noscope');
|
|
240
239
|
// Exact duplicate requests are the only relookup signal available in tool
|
|
241
|
-
// traces. Do not infer waste from counts, roles, turns, or
|
|
240
|
+
// traces. Do not infer waste from counts, roles, turns, or locator→inspection:
|
|
242
241
|
// exploration followed by inspection can be the intended route.
|
|
243
242
|
const seenRequests = new Set();
|
|
244
243
|
const readWindows = [];
|
package/scripts/run-suite.mjs
CHANGED
|
@@ -25,7 +25,6 @@ export const SUITES = {
|
|
|
25
25
|
'dead-owner-attach-test.mjs',
|
|
26
26
|
'debounced-skills-async-save-test.mjs',
|
|
27
27
|
'dispatch-persist-recovery-test.mjs',
|
|
28
|
-
'explore-prompt-policy-test.mjs',
|
|
29
28
|
'find-fuzzy-hidden-test.mjs',
|
|
30
29
|
'ingest-pure-conversation-smoke.mjs',
|
|
31
30
|
'internal-tools-normalization-test.mjs',
|
package/src/app.mjs
CHANGED
package/src/defaults/agents.json
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"agents": [
|
|
3
|
-
{
|
|
4
|
-
"agent": "explorer",
|
|
5
|
-
"slot": "explore",
|
|
6
|
-
"systemFile": "rules/agent/30-explorer.md",
|
|
7
|
-
"description": "Filesystem navigation agent invoked by the `explore` MCP tool",
|
|
8
|
-
"invokedBy": "explore",
|
|
9
|
-
"toolSchemaProfile": "read",
|
|
10
|
-
"schemaAllowedTools": ["grep", "find", "glob", "code_graph"],
|
|
11
|
-
"kind": "retrieval",
|
|
12
|
-
"permission": "read",
|
|
13
|
-
"stallCap": { "idleSeconds": 240, "toolRunningSeconds": 180 }
|
|
14
|
-
},
|
|
15
3
|
{
|
|
16
4
|
"agent": "cycle1-agent",
|
|
17
5
|
"slot": "cycle1",
|
|
@@ -73,7 +73,7 @@ apply it, and verify the result.
|
|
|
73
73
|
### Workflow / agent definitions
|
|
74
74
|
|
|
75
75
|
1. Workflows and agents are Markdown packs. Built-in services are Web Search,
|
|
76
|
-
|
|
76
|
+
and Maintainer. Mixdog ships editable starter agents `worker`,
|
|
77
77
|
`heavy-worker`, and `reviewer`; custom packs live at
|
|
78
78
|
`<mixdogData>/workflows/<id>/WORKFLOW.md` and
|
|
79
79
|
`<mixdogData>/agents/<id>/AGENT.md`.
|
package/src/headless-command.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const VALUE_OPTIONS = new Set(['--provider', '--model', '--effort', '--workflow']);
|
|
2
2
|
const FLAG_OPTIONS = new Set([
|
|
3
3
|
'--readonly', '--help', '-h', '--plain', '--react', '--remote', '--onboarding', '--fast',
|
|
4
|
-
'--
|
|
4
|
+
'--web-search', '--memory',
|
|
5
5
|
]);
|
|
6
6
|
const HEADLESS_ROLE_ALIASES = new Map([
|
|
7
|
-
['explorer', 'explore'], ['explore', 'explore'],
|
|
8
7
|
['maint', 'maintainer'], ['maintenance', 'maintainer'], ['maintainer', 'maintainer'],
|
|
9
8
|
['worker', 'worker'],
|
|
10
9
|
['heavy', 'heavy-worker'], ['heavyworker', 'heavy-worker'], ['heavy-worker', 'heavy-worker'],
|
|
@@ -108,7 +107,6 @@ export function classifyCliInvocation(argv = []) {
|
|
|
108
107
|
model: parsed.values['--model'],
|
|
109
108
|
effort: parsed.values['--effort'],
|
|
110
109
|
fast: argv.includes('--fast'),
|
|
111
|
-
explore: argv.includes('--explore'),
|
|
112
110
|
webSearch: argv.includes('--web-search'),
|
|
113
111
|
memory: argv.includes('--memory'),
|
|
114
112
|
toolMode: argv.includes('--readonly') ? 'readonly' : 'full',
|
package/src/headless-role.mjs
CHANGED
|
@@ -83,7 +83,6 @@ export async function runHeadlessRole({
|
|
|
83
83
|
model,
|
|
84
84
|
effort,
|
|
85
85
|
fast,
|
|
86
|
-
explore = false,
|
|
87
86
|
webSearch = false,
|
|
88
87
|
memory = false,
|
|
89
88
|
cwd = process.cwd(),
|
|
@@ -93,11 +92,10 @@ export async function runHeadlessRole({
|
|
|
93
92
|
} = {}) {
|
|
94
93
|
const cleanAgent = clean(agent);
|
|
95
94
|
const cleanMessage = clean(message);
|
|
96
|
-
// Classic headless surface:
|
|
97
|
-
// and opt back in per run (--
|
|
95
|
+
// Classic headless surface: web search and memory tools start OFF
|
|
96
|
+
// and opt back in per run (--web-search / --memory). An explicit
|
|
98
97
|
// MIXDOG_FEATURE_* value from the caller environment always wins.
|
|
99
98
|
for (const [key, enabled] of [
|
|
100
|
-
['MIXDOG_FEATURE_EXPLORE', explore],
|
|
101
99
|
['MIXDOG_FEATURE_WEB_SEARCH', webSearch],
|
|
102
100
|
['MIXDOG_FEATURE_MEMORY', memory],
|
|
103
101
|
]) {
|
package/src/help.mjs
CHANGED
|
@@ -21,7 +21,7 @@ export const HELP_LINES = [
|
|
|
21
21
|
'',
|
|
22
22
|
'Headless role commands require an explicit provider/model pair and run with',
|
|
23
23
|
'ephemeral config/data; host behavioral config and personal state are not loaded.',
|
|
24
|
-
'Roles:
|
|
24
|
+
'Roles: worker, heavy-worker, reviewer, maintainer, web-researcher.',
|
|
25
25
|
'',
|
|
26
26
|
'Slash commands (inside mixdog):',
|
|
27
27
|
' /clear start a fresh chat (alias: /new)',
|
|
@@ -11,12 +11,6 @@
|
|
|
11
11
|
durable compact English memory→`memory`; explicit project change→`cwd`;
|
|
12
12
|
explicit user-requested conversation reset→`session_manage`.
|
|
13
13
|
Use only named tools present in the current tool surface.
|
|
14
|
-
`explore`, when exposed, is a fast path only for facets whose repository
|
|
15
|
-
coordinates remain unknown: call it first once for all such independent
|
|
16
|
-
facets in one query array. It returns direct `path:line` anchors, not
|
|
17
|
-
analysis or solutions; anchors are tool-verified coordinates that END
|
|
18
|
-
their location facets — never re-locate or second-guess them; resume
|
|
19
|
-
baseline routing from them.
|
|
20
14
|
- Act only on verified identities (cwd/project/user/tool-returned) — paths,
|
|
21
15
|
module specifiers, symbols, data/record shapes alike; a guessed identity is
|
|
22
16
|
verified by one lookup or sample only when the next call or edit references
|
|
@@ -38,7 +32,8 @@
|
|
|
38
32
|
window. The moment evidence determines the answer, edit, or deliverable,
|
|
39
33
|
stop retrieving; patch if needed.
|
|
40
34
|
- Once the edit or deliverable is determined, finish in one assistant turn:
|
|
41
|
-
|
|
35
|
+
issue `apply_patch` calls serially, never in parallel; use one cohesive call
|
|
36
|
+
with one file section per target, all patches first, then one
|
|
42
37
|
batched verification `shell` that runs the real required postconditions
|
|
43
38
|
on every changed file and produced artifact, never echoes a claim;
|
|
44
39
|
runtime waits for every patch and skips the shell
|
|
@@ -47,7 +47,7 @@ import { resourceAdmission } from '../../../shared/resource-admission.mjs';
|
|
|
47
47
|
export { resolveMaintenanceRoute } from './maintenance-route.mjs';
|
|
48
48
|
|
|
49
49
|
// Cap agent role synthesis to ~3000 tokens (~12 KB at the 4 B/tok
|
|
50
|
-
// working average). Pool B
|
|
50
|
+
// working average). Pool B recall/search answers occasionally land
|
|
51
51
|
// 8-10k-token walls that then ride in the Lead context for the rest of the
|
|
52
52
|
// turn; the cap keeps those outliers bounded without touching the 95%+ of
|
|
53
53
|
// answers already under the threshold.
|
|
@@ -68,7 +68,7 @@ function formatCompactElapsedSeconds(ms) {
|
|
|
68
68
|
|
|
69
69
|
// True when an abort explicitly opted into partial salvage — the error object
|
|
70
70
|
// or the abort reason carries `salvagePartial: true`. A DEADLINE-driven caller
|
|
71
|
-
//
|
|
71
|
+
// A hard timeout sets it so partial output the sub-agent already produced
|
|
72
72
|
// are returned instead of discarded; user cancellation (ESC) never sets it and
|
|
73
73
|
// keeps the throw-everything behaviour.
|
|
74
74
|
function salvagePartialRequested(error, signal) {
|
|
@@ -151,7 +151,7 @@ export function resolveHiddenRoleSchemaAllowedTools(hidden) {
|
|
|
151
151
|
* against config.presets.
|
|
152
152
|
* - null — unresolved.
|
|
153
153
|
*
|
|
154
|
-
*
|
|
154
|
+
* Hidden maintenance roles mirror public spawning precedence:
|
|
155
155
|
* `agents.<role>` (including the `agents.maintenance` alias) → workflow route →
|
|
156
156
|
* maintenance route → Main. The cycle1/2/3 agents share the memory knob via
|
|
157
157
|
* their `maintKey: 'memory'` override. Scheduler and webhook are unchanged.
|
|
@@ -293,7 +293,7 @@ export function makeAgentDispatch(opts = {}) {
|
|
|
293
293
|
agentId: agent,
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
-
// Callers
|
|
296
|
+
// Callers may pass an explicit
|
|
297
297
|
// `cwd` to scope the agent's filesystem view. Absolute path expected
|
|
298
298
|
// (aiWrapped already expands `~` and resolves relatives). When unset
|
|
299
299
|
// we pass `null` through instead of falling back to `process.cwd()`
|
|
@@ -418,7 +418,7 @@ export function makeAgentDispatch(opts = {}) {
|
|
|
418
418
|
const _idleController = (agentWatchdogPolicyActive(_watchdogPolicy) && _linkSignal)
|
|
419
419
|
? new AbortController()
|
|
420
420
|
: null;
|
|
421
|
-
// Do not link factory parent, per-call
|
|
421
|
+
// Do not link factory parent, per-call cancellation, and the
|
|
422
422
|
// watchdog one at a time: each link replaces the previous listener in
|
|
423
423
|
// runtime-liveness. One composite survives askSession's controller
|
|
424
424
|
// swap and makes every source reach the provider call.
|
|
@@ -497,7 +497,7 @@ export function makeAgentDispatch(opts = {}) {
|
|
|
497
497
|
});
|
|
498
498
|
process.stderr.write(`[agent-dispatch] agent=${agent} session=${session.id} elapsed=${Date.now() - _agentDispatchT0}ms\n`);
|
|
499
499
|
const raw = result?.content || '';
|
|
500
|
-
// Brief cap. Agent role answers (
|
|
500
|
+
// Brief cap. Agent role answers (recall/search)
|
|
501
501
|
// occasionally balloon to 8-10k token walls that then ride in the
|
|
502
502
|
// parent Lead's context for the rest of the turn. A 3000-token
|
|
503
503
|
// (~12 KB) ceiling trims the long tail while leaving the vast
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agent loop ceilings. Lead and
|
|
3
|
-
* runaway guard. Explorer is the sole bounded exception: locator work gets at
|
|
4
|
-
* at most five tool-capable turns, followed by the loop's tool-less report turn.
|
|
2
|
+
* Agent loop ceilings. Lead and delegated agents share one high runaway guard.
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
5
|
function envPositiveInt(name, fallback) {
|
|
@@ -15,29 +13,8 @@ function envPositiveInt(name, fallback) {
|
|
|
15
13
|
// to raise/lower the safety ceiling, never used as a general task-length budget.
|
|
16
14
|
export const LEAD_MAX_LOOP_ITERATIONS = envPositiveInt('MIXDOG_AGENT_MAX_LOOP', 200);
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
// bounded miss recovery. The override may shorten this but never add a sixth.
|
|
20
|
-
export const EXPLORE_MAX_LOOP_ITERATIONS = Math.min(
|
|
21
|
-
5,
|
|
22
|
-
envPositiveInt('MIXDOG_EXPLORE_MAX_LOOP', 5),
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Resolve the hard cap used by agentLoop for this session.
|
|
27
|
-
*
|
|
28
|
-
* Explorer: the lowest positive explicit/session value, clamped to its
|
|
29
|
-
* dedicated five-turn ceiling. Others: explicit → session-pinned → shared guard.
|
|
30
|
-
*/
|
|
16
|
+
/** Resolve the hard cap used by agentLoop for this session. */
|
|
31
17
|
export function resolveSessionMaxLoopIterations(sessionRef, explicit) {
|
|
32
|
-
const sessionAgent = String(sessionRef?.agent || '').trim().toLowerCase();
|
|
33
|
-
if (sessionAgent === 'explorer' || sessionAgent === 'explore') {
|
|
34
|
-
const requested = Number.isFinite(explicit) && explicit > 0
|
|
35
|
-
? Math.floor(explicit)
|
|
36
|
-
: Number.isFinite(sessionRef?.maxLoopIterations) && sessionRef.maxLoopIterations > 0
|
|
37
|
-
? Math.floor(sessionRef.maxLoopIterations)
|
|
38
|
-
: EXPLORE_MAX_LOOP_ITERATIONS;
|
|
39
|
-
return Math.min(EXPLORE_MAX_LOOP_ITERATIONS, requested);
|
|
40
|
-
}
|
|
41
18
|
if (Number.isFinite(explicit) && explicit > 0) return Math.floor(explicit);
|
|
42
19
|
if (Number.isFinite(sessionRef?.maxLoopIterations) && sessionRef.maxLoopIterations > 0) {
|
|
43
20
|
return Math.floor(sessionRef.maxLoopIterations);
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
// Ordering guarantee, stated in stall-policy.mjs: the provider layer — which
|
|
18
18
|
// can retry in place or fall back to non-streaming — must fire STRICTLY before
|
|
19
19
|
// the agent watchdog's terminal abort. Role abort budgets (worker/reviewer
|
|
20
|
-
// 300s
|
|
20
|
+
// 300s) sat at or BELOW the provider semantic-idle window
|
|
21
21
|
// (300s), inverting that order: the watchdog aborted the shared signal first,
|
|
22
22
|
// so the provider's recovery never ran and the `agent_stall` failure — which
|
|
23
23
|
// the classifier calls retryable — died on throwIfAborted instead. Hold the
|
|
@@ -140,7 +140,7 @@ export function watchdogPartialHandoffFromError(error, session, messageStartInde
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// Salvage path for NON-watchdog aborts that explicitly opt in (the abort error
|
|
143
|
-
// / abort reason carries `salvagePartial: true` — e.g.
|
|
143
|
+
// / abort reason carries `salvagePartial: true` — e.g. a bounded wall-clock
|
|
144
144
|
// hard timeout). Same collection rule as the watchdog handoff: only assistant
|
|
145
145
|
// text appended during this run. Plain user cancellation never opts in, so ESC
|
|
146
146
|
// still discards the run.
|
|
@@ -266,7 +266,7 @@ export function resolveAgentWatchdogPolicy(agent, overrides = {}) {
|
|
|
266
266
|
? Math.min(DEFAULT_STALE_TIMEOUT_MS, backstopMs)
|
|
267
267
|
: DEFAULT_STALE_TIMEOUT_MS;
|
|
268
268
|
// Same floor for the public backstop: a workflow role (worker 300s,
|
|
269
|
-
//
|
|
269
|
+
// role-specific caps must not undercut the provider window either.
|
|
270
270
|
idleStaleMs = Math.max(idleStaleMs, PROVIDER_RECOVERY_FLOOR_MS);
|
|
271
271
|
}
|
|
272
272
|
|
|
@@ -29,7 +29,7 @@ export function resolveMaintenanceRoute({ preset, optsPreset, agent, config: cfg
|
|
|
29
29
|
try {
|
|
30
30
|
const config = cfgIn || loadConfig({ secrets: false });
|
|
31
31
|
const key = hidden.maintKey || hidden.slot;
|
|
32
|
-
const role = key === '
|
|
32
|
+
const role = key === 'memory' ? 'maintainer' : '';
|
|
33
33
|
if (!role) return config?.maintenance?.[key] ?? null;
|
|
34
34
|
const candidates = [
|
|
35
35
|
...configuredAgentRouteCandidates(config, role),
|
|
@@ -49,7 +49,7 @@ function normalizeAgentCompactionConfig(value = {}, { memoryEnabled = true } = {
|
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* @param {object} opts
|
|
52
|
-
* @param {string} opts.agent — canonical agent name ('worker', '
|
|
52
|
+
* @param {string} opts.agent — canonical agent name ('worker', 'reviewer', ...)
|
|
53
53
|
* @param {string} opts.presetName — resolved preset identifier
|
|
54
54
|
* @param {object} opts.preset — resolved preset object from agent-config
|
|
55
55
|
* @param {object} opts.runtimeSpec — resolveRuntimeSpec output; must carry .scopeKey / .lane
|
|
@@ -137,7 +137,6 @@ const TOOL_ARG_KEYS = {
|
|
|
137
137
|
list: ['path', 'head_limit', 'offset'],
|
|
138
138
|
recall: ['query', 'limit', 'session_id', 'cwd'],
|
|
139
139
|
search: ['query', 'limit', 'cwd'],
|
|
140
|
-
explore: ['query', 'queries', 'limit', 'cwd'],
|
|
141
140
|
code_graph: ['mode', 'file', 'files', 'symbol', 'symbols', 'body', 'language', 'limit', 'depth', 'page', 'cwd'],
|
|
142
141
|
shell: ['command', 'cwd', 'timeout', 'mode', 'run_in_background', 'persistent', 'session_id'],
|
|
143
142
|
task: ['task_id', 'action', 'timeout_ms', 'poll_ms'],
|
|
@@ -22,13 +22,12 @@ export function getPluginData() {
|
|
|
22
22
|
// Canonical maintenance defaults. Single source of truth — imported by
|
|
23
23
|
// llm/index.mjs and setup-server.mjs so UI/runtime cannot drift from config.
|
|
24
24
|
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// maintenance aliases.
|
|
25
|
+
// Maintainer starts without a route so it dynamically inherits the Main route.
|
|
26
|
+
// Its explicit route lives canonically in `agents.maintainer`; load-time
|
|
27
|
+
// migration still accepts the older workflow / maintenance aliases.
|
|
29
28
|
// Webhook endpoints may omit a model and use the fallback route below.
|
|
30
29
|
// Legacy route slots accepted only at config ingress for migration.
|
|
31
|
-
const MAINTENANCE_SLOTS = Object.freeze(['
|
|
30
|
+
const MAINTENANCE_SLOTS = Object.freeze(['memory']);
|
|
32
31
|
|
|
33
32
|
// --- User profile (statusline /profile) -------------------------------------
|
|
34
33
|
// Supported response languages for the /profile picker. `system` is the default
|
|
@@ -340,7 +339,8 @@ function canonicalizeShellStorage(value) {
|
|
|
340
339
|
function canonicalizeModulesStorage(value) {
|
|
341
340
|
const modules = configObject(value);
|
|
342
341
|
delete modules.memory;
|
|
343
|
-
|
|
342
|
+
delete modules.explore;
|
|
343
|
+
for (const name of ['search']) {
|
|
344
344
|
if (!Object.prototype.hasOwnProperty.call(modules, name)) continue;
|
|
345
345
|
const raw = modules[name];
|
|
346
346
|
modules[name] = {
|
|
@@ -711,8 +711,7 @@ export function loadScopedRoleInstructions(agent, provider = null) {
|
|
|
711
711
|
agentRuleSectionsToEmit = hiddenPairs.map(p => `## ${p.name}\n\n${p.body}`);
|
|
712
712
|
agentSectionsToEmit = agentSections;
|
|
713
713
|
} else if (agent && classification.retrieval.has(agent)) {
|
|
714
|
-
// Retrieval agents
|
|
715
|
-
// (rules/agent/30-explorer.md) in BP2.
|
|
714
|
+
// Retrieval agents get their own contract section in BP2.
|
|
716
715
|
const self = hiddenPairs.find(p => p.name === agent);
|
|
717
716
|
agentRuleSectionsToEmit = self ? [`## ${self.name}\n\n${self.body}`] : [];
|
|
718
717
|
agentSectionsToEmit = agentSections.filter(s =>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Internal hidden agents — Mixdog-managed, user-untouchable.
|
|
3
3
|
*
|
|
4
4
|
* Unlike public workflow agents, these hidden agents are NEVER exposed to callers of the `agent` tool. They are
|
|
5
|
-
* invoked only by internal handlers (
|
|
5
|
+
* invoked only by internal handlers (recall / search) and carry
|
|
6
6
|
* their own system prompt + tool-set policy.
|
|
7
7
|
*
|
|
8
8
|
* Lookup order (agent-dispatch.resolveMaintenanceRoute):
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* found" error rather than silently mis-dispatching.
|
|
21
21
|
*
|
|
22
22
|
* Kind classification:
|
|
23
|
-
* - 'retrieval' : short-lived hidden retrieval agents
|
|
23
|
+
* - 'retrieval' : short-lived hidden retrieval agents.
|
|
24
24
|
* - 'maintenance' : background-trigger hidden agents (memory cycle and
|
|
25
25
|
* title generation). Receive only their own self section.
|
|
26
26
|
*
|
|
@@ -184,7 +184,7 @@ export function listHiddenAgentsByKind(kind) {
|
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
186
|
* Return the agents/<name>.md sections a hidden agent shares in its BP2 catalog
|
|
187
|
-
* (in addition to its own self section). Drives
|
|
187
|
+
* (in addition to its own self section). Drives hidden-role cache
|
|
188
188
|
* alignment declaratively instead of a hard-coded agent-name branch in
|
|
189
189
|
* collect.mjs. Returns [] when the agent declares none.
|
|
190
190
|
*/
|
|
@@ -105,6 +105,16 @@ const WS_MIDSTREAM_POLICY = {
|
|
|
105
105
|
const HANDSHAKE_MAX_ATTEMPTS = MIDSTREAM_WS_TRANSIENT_RETRY_LIMIT + 1;
|
|
106
106
|
const HANDSHAKE_BACKOFF_BASE_MS = 200;
|
|
107
107
|
const HANDSHAKE_BACKOFF_CAP_MS = 3200;
|
|
108
|
+
// These statuses are decisions made by the CURRENT attempt. They must never
|
|
109
|
+
// be replaced by a stale transient from an earlier retry: auth recovery,
|
|
110
|
+
// transport fallback, and Retry-After handling all depend on the live error.
|
|
111
|
+
const CURRENT_ATTEMPT_DECISION_STATUSES = new Set([401, 403, 426, 429]);
|
|
112
|
+
|
|
113
|
+
function _mustSurfaceCurrentAttempt(err, externalSignal) {
|
|
114
|
+
return CURRENT_ATTEMPT_DECISION_STATUSES.has(Number(err?.httpStatus || 0))
|
|
115
|
+
|| externalSignal?.aborted
|
|
116
|
+
|| err?.unsafeToRetry === true;
|
|
117
|
+
}
|
|
108
118
|
|
|
109
119
|
function _classifyHandshakeError(err, { retry429 = true } = {}) {
|
|
110
120
|
return classifyHandshakeError(err, { retry429 });
|
|
@@ -738,12 +748,12 @@ export async function sendViaWebSocket({
|
|
|
738
748
|
// A later auth/upgrade decision must win over an earlier transient
|
|
739
749
|
// failure so the caller can refresh or switch transport. Likewise,
|
|
740
750
|
// never replace a current cancellation with stale retry history.
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
751
|
+
if (_mustSurfaceCurrentAttempt(err, externalSignal)) {
|
|
752
|
+
if (retryable && attemptIndex >= MAX_MIDSTREAM_RETRIES) {
|
|
753
|
+
try { err.midstreamRetries = attemptIndex; } catch {}
|
|
754
|
+
try { err.midstreamClassifier = classifier; } catch {}
|
|
755
|
+
try { err.wsRetriesExhausted = true; } catch {}
|
|
756
|
+
}
|
|
747
757
|
emitSendSpan('error');
|
|
748
758
|
throw _stampTool(_stampLiveText(err));
|
|
749
759
|
}
|
|
@@ -1126,12 +1136,7 @@ export async function sendViaWebSocket({
|
|
|
1126
1136
|
// Not retryable, OR we've already exhausted the retry budget.
|
|
1127
1137
|
// Do not let stale retry history mask a current auth/upgrade
|
|
1128
1138
|
// decision, cancellation, or newly unsafe-to-replay outcome.
|
|
1129
|
-
|
|
1130
|
-
const surfaceCurrent = currentStatus === 401
|
|
1131
|
-
|| currentStatus === 426
|
|
1132
|
-
|| externalSignal?.aborted
|
|
1133
|
-
|| err?.unsafeToRetry === true;
|
|
1134
|
-
if (surfaceCurrent) {
|
|
1139
|
+
if (_mustSurfaceCurrentAttempt(err, externalSignal)) {
|
|
1135
1140
|
emitSendSpan('error');
|
|
1136
1141
|
throw _stampTool(_stampLiveText(err));
|
|
1137
1142
|
}
|
|
@@ -44,6 +44,10 @@ export const WS_IDLE_MS = resolveTimeoutMs(
|
|
|
44
44
|
);
|
|
45
45
|
const WS_HANDSHAKE_TIMEOUT_MS = PROVIDER_WS_HANDSHAKE_TIMEOUT_MS;
|
|
46
46
|
const WS_ACQUIRE_TIMEOUT_MS = PROVIDER_WS_ACQUIRE_TIMEOUT_MS;
|
|
47
|
+
// Enforced by `ws` while fragments are assembled, before a complete payload
|
|
48
|
+
// reaches the stream consumer or is decoded. Shared by Codex, direct OpenAI,
|
|
49
|
+
// and xAI so no provider can opt into an unbounded receive allocation.
|
|
50
|
+
export const WS_MAX_INCOMING_FRAME_BYTES = 16 * 1024 * 1024;
|
|
47
51
|
// A write that never reaches the ws callback is indistinguishable from a
|
|
48
52
|
// wedged transport. Keep it under the same short bound as socket acquisition
|
|
49
53
|
// so the caller can discard the entry and reconnect before arming stream
|
|
@@ -459,7 +463,11 @@ function _openSocket({ auth, sessionToken, turnState, externalSignal, cacheKey,
|
|
|
459
463
|
}
|
|
460
464
|
(ok ? resolve : reject)(val);
|
|
461
465
|
};
|
|
462
|
-
const socket = new WebSocket(url, {
|
|
466
|
+
const socket = new WebSocket(url, {
|
|
467
|
+
headers,
|
|
468
|
+
handshakeTimeout: WS_HANDSHAKE_TIMEOUT_MS,
|
|
469
|
+
maxPayload: WS_MAX_INCOMING_FRAME_BYTES,
|
|
470
|
+
});
|
|
463
471
|
acquireTimer = setTimeout(() => {
|
|
464
472
|
if (settled) return;
|
|
465
473
|
if (process.env.MIXDOG_DEBUG_AGENT) {
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
streamStalledError,
|
|
34
34
|
} from '../stall-policy.mjs';
|
|
35
35
|
import { customToolCallFromResponseItem } from './custom-tool-wire.mjs';
|
|
36
|
-
import { _wsErrLabel } from './openai-ws-pool.mjs';
|
|
36
|
+
import { _wsErrLabel, WS_MAX_INCOMING_FRAME_BYTES } from './openai-ws-pool.mjs';
|
|
37
37
|
import {
|
|
38
38
|
_sansInput,
|
|
39
39
|
_stableStringify,
|
|
@@ -100,10 +100,9 @@ export const WS_PRE_RESPONSE_CREATED_MS = (() => {
|
|
|
100
100
|
// including metadata/keepalive, proves the socket is live.
|
|
101
101
|
export const WS_INTER_CHUNK_MS = PROVIDER_WS_INTER_CHUNK_TIMEOUT_MS;
|
|
102
102
|
// Bound the second allocation performed when ws RawData is decoded to UTF-8.
|
|
103
|
-
// The
|
|
104
|
-
// must stay before data.toString()
|
|
105
|
-
//
|
|
106
|
-
const OPENAI_WS_MAX_INCOMING_FRAME_BYTES = 16 * 1024 * 1024;
|
|
103
|
+
// The pool also applies this bound as `maxPayload` before assembly; this
|
|
104
|
+
// defense-in-depth check must stay before data.toString() for injected sockets
|
|
105
|
+
// and test seams.
|
|
107
106
|
const X_CODEX_TURN_STATE_HEADER = 'x-codex-turn-state';
|
|
108
107
|
const WS_TRACE_ENABLED = process.env.MIXDOG_WS_TRACE === '1';
|
|
109
108
|
|
|
@@ -288,9 +287,10 @@ export async function _streamResponse({
|
|
|
288
287
|
}
|
|
289
288
|
const preResponseCreatedMs = _positiveInt(_timeouts?.preResponseCreatedMs, WS_PRE_RESPONSE_CREATED_MS);
|
|
290
289
|
const interChunkMs = _positiveInt(_timeouts?.interChunkMs, WS_INTER_CHUNK_MS);
|
|
291
|
-
const maxIncomingFrameBytes =
|
|
292
|
-
|
|
293
|
-
|
|
290
|
+
const maxIncomingFrameBytes = _positiveInt(
|
|
291
|
+
_timeouts?.maxIncomingFrameBytes,
|
|
292
|
+
WS_MAX_INCOMING_FRAME_BYTES,
|
|
293
|
+
);
|
|
294
294
|
// First-MEANINGFUL-frame deadline. Distinct from preResponseCreatedMs (a
|
|
295
295
|
// short pre-created byte-silence window that resetIdle clears on the FIRST
|
|
296
296
|
// frame of any kind): this timer is cleared only by a meaningful response
|
|
@@ -804,7 +804,7 @@ export async function _streamResponse({
|
|
|
804
804
|
const frameBytes = _incomingFrameByteLength(data);
|
|
805
805
|
if (maxIncomingFrameBytes > 0 && frameBytes != null && frameBytes > maxIncomingFrameBytes) {
|
|
806
806
|
const err = new Error(
|
|
807
|
-
|
|
807
|
+
`${errLabel} response frame is too large (${frameBytes} bytes; limit ${maxIncomingFrameBytes} bytes); request is retryable`,
|
|
808
808
|
);
|
|
809
809
|
err.code = 'EOPENAIWSFRAMETOOLARGE';
|
|
810
810
|
err.wsFrameTooLarge = true;
|
|
@@ -1375,6 +1375,16 @@ export async function _streamResponse({
|
|
|
1375
1375
|
if (WS_TRACE_ENABLED) _writeWsLifecycleTrace('error');
|
|
1376
1376
|
if (done) return;
|
|
1377
1377
|
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
1378
|
+
// `ws` rejects an over-limit fragmented message before assembly
|
|
1379
|
+
// with this typed code. Preserve the same retry classification as
|
|
1380
|
+
// the defense-in-depth complete-frame check above.
|
|
1381
|
+
if (wrapped.code === 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH') {
|
|
1382
|
+
try {
|
|
1383
|
+
wrapped.wsFrameTooLarge = true;
|
|
1384
|
+
wrapped.retryable = true;
|
|
1385
|
+
midState.wsFrameTooLarge = true;
|
|
1386
|
+
} catch {}
|
|
1387
|
+
}
|
|
1378
1388
|
if (terminalError) {
|
|
1379
1389
|
// Preserve the first terminalError; chain the later socket
|
|
1380
1390
|
// error in via `cause` (or `suppressed` if cause already set)
|