mixdog 0.9.142 → 0.9.144
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 -1
- package/scripts/agent-lead-e2e-probe.mjs +1 -6
- package/scripts/agent-turn-trace-probe.mjs +3 -5
- package/src/lib/rules-builder.cjs +44 -15
- package/src/rules/lead/lead-brief.md +7 -8
- package/src/rules/shared/10-tool-workflow.md +28 -8
- package/src/rules/shared/20-research.md +4 -0
- package/src/rules/shared/30-exploration.md +23 -35
- package/src/rules/shared/40-editing.md +4 -0
- package/src/rules/shared/50-execution.md +1 -0
- package/src/rules/shared/60-verification.md +9 -0
- package/src/rules/shared/70-delivery.md +1 -2
- package/src/rules/shared/80-memory.md +6 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +0 -39
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +28 -22
- package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +22 -1
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/evidence-union.test.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +1 -16
- package/src/runtime/agent/orchestrator/session/token-native.mjs +40 -40
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +10 -10
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -19
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +17 -37
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +1 -32
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +10 -15
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +10 -10
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +4 -9
- package/src/runtime/shared/child-spawn-remote.mjs +7 -8
- package/src/runtime/shared/resource-admission.mjs +20 -0
- package/src/runtime/shared/resource-admission.test.mjs +43 -0
- package/src/runtime/shared/{session-shard-health.mjs → session-runtime-health.mjs} +14 -14
- package/src/runtime/shared/session-runtime-health.test.mjs +30 -0
- package/src/session-runtime/agent-disable.test.mjs +5 -3
- package/src/session-runtime/runtime-core.mjs +21 -41
- package/src/session-runtime/session-lifecycle.mjs +0 -32
- package/src/session-runtime/session-turn-api.mjs +1 -1
- package/src/session-runtime/tool-policy-surface.test.mjs +64 -11
- package/src/session-runtime/workflow.mjs +8 -3
- package/src/standalone/agent-tool/helpers.mjs +1 -1
- package/src/standalone/agent-tool/spawn-flow.mjs +1 -30
- package/src/standalone/agent-tool.mjs +1 -7
- package/src/standalone/channel-worker.mjs +3 -3
- package/src/standalone/daemon.mjs +14 -18
- package/src/standalone/session-client.mjs +1 -1
- package/src/standalone/{session-runtime-pool-health.test.mjs → session-runtime-host-health.test.mjs} +22 -23
- package/src/standalone/{session-runtime-pool.mjs → session-runtime-host.mjs} +69 -291
- package/src/standalone/session-runtime-record.mjs +2 -2
- package/src/standalone/session-runtime-worker.mjs +20 -21
- package/src/standalone/session-service.mjs +3 -28
- package/src/standalone/session-state-patch.mjs +1 -1
- package/src/standalone/session-transport.mjs +1 -1
- package/src/tui/session/turn.mjs +5 -0
- package/src/tui/session-local.mjs +1 -3
- package/src/workflows/default/WORKFLOW.md +5 -4
- package/src/workflows/solo/WORKFLOW.md +3 -5
- package/scripts/agent-long-prompt-repro.mjs +0 -82
- package/scripts/agent-shard-spread-perf.mjs +0 -245
- package/scripts/fixtures/session-shard-fixture-worker.mjs +0 -13
- package/src/runtime/shared/session-shard-health.test.mjs +0 -30
- package/src/standalone/agent-tool/shard-spread.mjs +0 -709
- package/src/standalone/agent-tool/shard-spread.test.mjs +0 -68
package/package.json
CHANGED
|
@@ -15,12 +15,7 @@ import { homedir, tmpdir } from 'node:os';
|
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
|
|
17
17
|
const ROOT = mkdtempSync(join(tmpdir(), 'mixdog-lead-e2e-'));
|
|
18
|
-
|
|
19
|
-
// discovery) and opt into agent shard spread, so the spawned worker runs on
|
|
20
|
-
// the INSTALLED daemon's shard pool — the deployed remote-completion path.
|
|
21
|
-
const LIVE_DAEMON = process.env.MIXDOG_LEAD_E2E_LIVE_DAEMON === '1';
|
|
22
|
-
if (LIVE_DAEMON) process.env.MIXDOG_AGENT_SHARD_SPREAD = '1';
|
|
23
|
-
else process.env.MIXDOG_RUNTIME_ROOT = ROOT;
|
|
18
|
+
process.env.MIXDOG_RUNTIME_ROOT = ROOT;
|
|
24
19
|
process.env.MIXDOG_BOOT_CORE_MEMORY = '0';
|
|
25
20
|
process.env.MIXDOG_DAEMON_SKIP_MEMORY = '1';
|
|
26
21
|
process.env.MIXDOG_FEATURE_MEMORY = '0';
|
|
@@ -17,9 +17,7 @@ const ROOT = mkdtempSync(join(tmpdir(), 'mixdog-turn-trace-'));
|
|
|
17
17
|
process.env.MIXDOG_RUNTIME_ROOT = ROOT;
|
|
18
18
|
process.env.MIXDOG_DAEMON_SKIP_MEMORY = '1';
|
|
19
19
|
process.env.MIXDOG_BOOT_CORE_MEMORY = '0';
|
|
20
|
-
|
|
21
|
-
// The probe's whole point is the trace: explicit path (shared by daemon and
|
|
22
|
-
// shard children via env inheritance) + timing rows.
|
|
20
|
+
// The probe's whole point is the trace: explicit path plus timing rows.
|
|
23
21
|
const TRACE_PATH = join(ROOT, 'agent-trace.jsonl');
|
|
24
22
|
delete process.env.MIXDOG_AGENT_TRACE_DISABLE;
|
|
25
23
|
process.env.MIXDOG_AGENT_TRACE_PATH = TRACE_PATH;
|
|
@@ -75,7 +73,7 @@ try {
|
|
|
75
73
|
'2) read README.md (first 40 lines)',
|
|
76
74
|
'3) run the shell command: node -v',
|
|
77
75
|
'4) read apps/desktop/package.json',
|
|
78
|
-
'5) grep the string "
|
|
76
|
+
'5) grep the string "createSessionRuntimeHost" under src/standalone (files list only)',
|
|
79
77
|
'Then reply with one line: DONE <package name> <node version>. Do not edit anything.',
|
|
80
78
|
].join('\n'),
|
|
81
79
|
}, { invocationSource: 'model-tool', cwd: REPO });
|
|
@@ -90,7 +88,7 @@ try {
|
|
|
90
88
|
process.stdout.write(`wall=${((Date.now() - t0) / 1000).toFixed(1)}s\n--- result ---\n${last.slice(0, 400)}\n`);
|
|
91
89
|
try { agent.closeAll('turn-trace probe end'); } catch { /* teardown */ }
|
|
92
90
|
|
|
93
|
-
//
|
|
91
|
+
// The runtime worker flushes its local trace buffer on a short timer.
|
|
94
92
|
await sleep(9_000);
|
|
95
93
|
const rows = existsSync(TRACE_PATH)
|
|
96
94
|
? readFileSync(TRACE_PATH, 'utf8').split('\n').filter(Boolean).flatMap((line) => {
|
|
@@ -202,12 +202,32 @@ function stripFrontmatter(markdown) {
|
|
|
202
202
|
return String(markdown || '').replace(/^---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)/, '').trim();
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
205
|
+
// Tool dependency is declared as metadata, not matched against prose. A
|
|
206
|
+
// `<!-- tools: a, b -->` marker binds the block that follows it: the block
|
|
207
|
+
// survives while any listed tool is on the session surface and disappears
|
|
208
|
+
// once every one of them is omitted. Markers never reach the model.
|
|
209
|
+
const TOOL_MARKER_RE = /^[ \t]*<!--[ \t]*tools:[ \t]*([^>]*?)[ \t]*-->[ \t]*$/;
|
|
210
|
+
|
|
211
|
+
function markerTools(line) {
|
|
212
|
+
const match = TOOL_MARKER_RE.exec(String(line ?? ''));
|
|
213
|
+
if (!match) return null;
|
|
214
|
+
return match[1].split(',').map((name) => name.trim().toLowerCase()).filter(Boolean);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// A marked block runs from the line after the marker through every deeper
|
|
218
|
+
// indented continuation line, ending at the next marker, blank line, or a
|
|
219
|
+
// line at the same or shallower indent.
|
|
220
|
+
function markedBlockEnd(lines, start) {
|
|
221
|
+
const indent = lines[start].search(/\S/);
|
|
222
|
+
let end = start + 1;
|
|
223
|
+
while (end < lines.length) {
|
|
224
|
+
const line = lines[end];
|
|
225
|
+
if (!line.trim() || markerTools(line)) break;
|
|
226
|
+
if (line.search(/\S/) <= indent) break;
|
|
227
|
+
end += 1;
|
|
228
|
+
}
|
|
229
|
+
return end;
|
|
230
|
+
}
|
|
211
231
|
|
|
212
232
|
function omitKeySet(omitTools) {
|
|
213
233
|
return new Set((Array.isArray(omitTools) ? omitTools : []).map((name) => String(name || '').toLowerCase()).filter(Boolean));
|
|
@@ -216,16 +236,25 @@ function omitKeySet(omitTools) {
|
|
|
216
236
|
/** Drop routing clauses for tools that are not on the session surface. */
|
|
217
237
|
function omitToolRoutes(text, omitTools = []) {
|
|
218
238
|
const deny = omitKeySet(omitTools);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
239
|
+
const lines = String(text || '').split(/\r?\n/);
|
|
240
|
+
const kept = [];
|
|
241
|
+
let index = 0;
|
|
242
|
+
while (index < lines.length) {
|
|
243
|
+
const tools = markerTools(lines[index]);
|
|
244
|
+
if (!tools) {
|
|
245
|
+
kept.push(lines[index]);
|
|
246
|
+
index += 1;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
index += 1;
|
|
250
|
+
if (index >= lines.length) break;
|
|
251
|
+
const end = markedBlockEnd(lines, index);
|
|
252
|
+
if (!tools.length || !tools.every((name) => deny.has(name))) {
|
|
253
|
+
kept.push(...lines.slice(index, end));
|
|
254
|
+
}
|
|
255
|
+
index = end;
|
|
226
256
|
}
|
|
227
|
-
|
|
228
|
-
return out.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n');
|
|
257
|
+
return kept.join('\n').replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
229
258
|
}
|
|
230
259
|
|
|
231
260
|
// Framing line under the style header: the block owns user-facing prose only,
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# Lead Brief
|
|
2
2
|
|
|
3
|
-
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
without discarding requirements.
|
|
3
|
+
- Every role's `Task:` is mandatory and lossless — build it from the original
|
|
4
|
+
request and the official spec/test acceptance criteria, preserving intent,
|
|
5
|
+
required and forbidden outcomes, completion/stop boundary, user-supplied
|
|
6
|
+
exact targets, and exact replacements/outputs.
|
|
7
|
+
- Never infer exactness from task name, file count, or difficulty.
|
|
8
|
+
- Minimum chars, maximum info: one-line fragments, no role-known rules, no
|
|
9
|
+
repeated context or facts, no padding.
|
|
11
10
|
- Other fields are task-specific deltas — `Anchors:` (`file:line` plus a
|
|
12
11
|
one-line conclusion, never log/code bodies), `Allow/Forbid:`, `Deliver:`
|
|
13
12
|
(sets handoff shape/size); omit empty fields. State outcomes, not methods,
|
|
@@ -1,20 +1,40 @@
|
|
|
1
1
|
# Tool Workflow
|
|
2
2
|
|
|
3
|
-
-
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
- Investigate, build, and verify only what the requested outcome requires, at
|
|
4
|
+
the level it requires; trust internal and framework guarantees.
|
|
5
|
+
- Minimize tool turns through maximal useful parallelism. Cost is counted in
|
|
6
|
+
rounds, not calls: a batch is one round, so a call-count saving never
|
|
7
|
+
justifies a worse-routed call. Plan the fewest evidence-complete dependent
|
|
8
|
+
rounds first, then the fewest calls within each round.
|
|
9
|
+
- In each round, issue every necessary non-overlapping call whose inputs are
|
|
10
|
+
already known; defer a call only when its target or arguments require an
|
|
11
|
+
earlier result.
|
|
12
|
+
- Route each remaining evidence facet once to its primary owner, preferring the
|
|
13
|
+
operation that directly returns the evidence needed for the next decision. A
|
|
14
|
+
summary, overview, or enumeration is not a prerequisite when that operation's
|
|
15
|
+
complete inputs are already known; if independently required, batch it with
|
|
16
|
+
the detailed operation.
|
|
17
|
+
- Never duplicate a facet, widen retrieval speculatively, or cap fanout; apply
|
|
18
|
+
one analysis to many targets as one parameterized call when supported.
|
|
9
19
|
1. Determine the required outcome and missing information; requirements are
|
|
10
20
|
not evidence.
|
|
11
21
|
2. If needed, gather only missing information through Research or Exploration;
|
|
12
22
|
use Execution when the information can only be produced by running a program
|
|
13
|
-
or observing runtime state.
|
|
14
|
-
obtained.
|
|
23
|
+
or observing runtime state.
|
|
15
24
|
3. Perform the required answer, edit, or execution in the fewest safe coherent
|
|
16
25
|
calls.
|
|
17
26
|
4. Verify only affected facets and essential invariants when required.
|
|
27
|
+
- Known state — system guarantees, supplied facts, visible tool returns,
|
|
28
|
+
applied patches, and passed checks — is never re-found, re-derived, or
|
|
29
|
+
re-verified at any granularity: no re-query call, no confirmation subcommand
|
|
30
|
+
inside a shell command, no availability probe for what the operation itself
|
|
31
|
+
would report, no reopening a file to confirm an edit, no rerun of a passed
|
|
32
|
+
check.
|
|
33
|
+
- Mine each returned result fully before opening the next round. A follow-up
|
|
34
|
+
is valid only for evidence a result omitted, invalidated, or newly made
|
|
35
|
+
necessary; an independently required call that no result created belonged in
|
|
36
|
+
the earlier batch.
|
|
37
|
+
- Evidence that determines the answer, edit, or deliverable ends retrieval.
|
|
18
38
|
- Treat failure as new evidence and repeat steps 1–4 only for affected facets.
|
|
19
39
|
Report a blocker when no deterministic next action remains.
|
|
20
40
|
- Use only named tools present in the current tool surface.
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
<!-- tools: web_search, web_fetch -->
|
|
1
2
|
# Research
|
|
2
3
|
|
|
4
|
+
<!-- tools: web_search, web_fetch -->
|
|
3
5
|
- Research routes:
|
|
6
|
+
<!-- tools: web_search -->
|
|
4
7
|
current or external information discovery→`web_search`;
|
|
8
|
+
<!-- tools: web_fetch -->
|
|
5
9
|
page or documentation body retrieval from a known URL→`web_fetch`.
|
|
6
10
|
|
|
@@ -2,43 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
- Use read-only means for inspection; never mutate to clear an obstacle or
|
|
4
4
|
unexpected state. Preserve evidence before a required mutation can destroy it.
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
- Ownership is exclusive: each evidence type has one owner, another tool's
|
|
6
|
+
ability to reach the same target is never an alternative route, and a
|
|
7
|
+
successful owner result closes that facet — only a different evidence type
|
|
8
|
+
routes elsewhere.
|
|
9
|
+
- Route the missing evidence to its primary owner:
|
|
10
|
+
repository state, history, or diff→`git`;
|
|
11
|
+
exact symbol declaration, body, usage, or relation→`code_graph`;
|
|
12
|
+
literal, regex, or text location→`grep`;
|
|
13
|
+
known-file content, range, or image→`read`;
|
|
14
|
+
wildcard or recursive file paths→`glob`;
|
|
8
15
|
known directory's immediate entries→`list`;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
insufficient or unnecessary→`read`.
|
|
14
|
-
- Read-only tools — `find`, `glob`, `list`, `grep`, `code_graph`, `read` —
|
|
15
|
-
always batch safely in parallel.
|
|
16
|
-
- Paths reachable by expanding an environment variable or the home directory
|
|
17
|
-
are resolved locations, not unknowns.
|
|
18
|
-
- In the first response, launch all investigations knowable from the request
|
|
19
|
-
alone (enumeration, content probes, file samples) as one batch; each
|
|
20
|
-
follow-up batch exists only for questions the previous results created.
|
|
21
|
-
- Batching never licenses a guessed `glob.path`
|
|
22
|
-
(unknown location → `find` first; omit path for the current Project).
|
|
16
|
+
unknown file or directory location→`find`.
|
|
17
|
+
- Use a path locator only when the owner's required target is unknown. Paths
|
|
18
|
+
reachable by expanding an environment variable or the home directory are
|
|
19
|
+
resolved locations, not unknowns.
|
|
23
20
|
- Enumerate sibling directories or same-kind files with one wildcard call
|
|
24
21
|
(`glob`, or `read` with a glob for content sampling), never a
|
|
25
22
|
directory-by-directory `list` walk or one `read` per file.
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
paths and omit optional scopes equal to its root; explicit paths may be
|
|
36
|
-
outside cwd only for targets outside the project.
|
|
37
|
-
- Do not re-read content already returned by any tool or reopen a successfully
|
|
38
|
-
edited file solely to confirm the edit. Read only missing context or content
|
|
39
|
-
invalidated by a reported failure, partial operation, or external change.
|
|
40
|
-
- `code_graph references` supplies the declaration and scoped usages and ends
|
|
41
|
-
that facet; values/locations end at the context `grep` returns; `read` covers
|
|
42
|
-
only omitted lines or missing anchored ranges. Any visible returned span can
|
|
43
|
-
supply exact source context; do not fetch it again.
|
|
23
|
+
- Treat supplied target locations as resolved; access them directly without
|
|
24
|
+
locator searches. Within the current project, pass project-relative paths and
|
|
25
|
+
omit optional scopes equal to its root; explicit paths may be outside cwd
|
|
26
|
+
only for targets outside the project.
|
|
27
|
+
- Before deciding how to parse, count, transform, or summarize files whose
|
|
28
|
+
format has not been inspected, inspect the original content itself.
|
|
29
|
+
- Returned declarations, bodies, usages, relations, and contextual spans from
|
|
30
|
+
any tool — not only `read` — are source context; `read` covers only omitted
|
|
31
|
+
lines or missing anchored ranges.
|
|
44
32
|
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# Editing
|
|
2
2
|
|
|
3
|
+
- A required new file is created directly: Add File is itself the atomic
|
|
4
|
+
absence check, so inspect only if it reports the target already exists.
|
|
3
5
|
- Source: use exact current target text from any visible evidence, including
|
|
4
6
|
user input, tool output, or an applied edit result; never reconstruct it from
|
|
5
7
|
another file, a sample, or expectation.
|
|
6
8
|
- Placement: with `edit`, use an exact unique target string, expanding exact
|
|
7
9
|
surrounding text when needed; with `apply_patch`, use exact unchanged context
|
|
8
10
|
and add a class/function locator when context alone is not unique.
|
|
11
|
+
- Apply all determined changes in the fewest safe calls the active tool
|
|
12
|
+
supports; a file written in one call is written complete.
|
|
9
13
|
- Batch scope: never split one file across concurrent edit calls. Group
|
|
10
14
|
same-intent changes with exact context into coherent calls; issue disjoint
|
|
11
15
|
calls together in one turn, and defer ambiguous or result-dependent changes.
|
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
invariants; use an umbrella suite only when the user explicitly requests it
|
|
6
6
|
or a documented project or release process requires it.
|
|
7
7
|
- Issue all independent checks in one turn.
|
|
8
|
+
- Blocking checks cover only essential integrity, security, compatibility, and
|
|
9
|
+
buildability invariants. Treat mutable behavior, UX, exact text, snapshots,
|
|
10
|
+
and implementation shape as advisory specifications; update them when the
|
|
11
|
+
requested behavior changes instead of preserving obsolete behavior.
|
|
12
|
+
- A check runs at the strictness the task requires; never raise a tool's own
|
|
13
|
+
severity beyond it.
|
|
8
14
|
- If verification fails, collect all failures, leave Verification, complete all
|
|
9
15
|
determinable fixes, then re-enter Verification for the resulting state.
|
|
16
|
+
- A successful verification closes the task unless later changes affect it;
|
|
17
|
+
rerun a failed action only after its inputs or subject change, otherwise
|
|
18
|
+
report it unresolved.
|
|
10
19
|
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
<!-- tools: recall, memory -->
|
|
1
2
|
# Memory
|
|
2
3
|
|
|
4
|
+
<!-- tools: recall -->
|
|
3
5
|
- past facts recorded in prior work or sessions→`recall`
|
|
4
6
|
(stored history only, never current local state).
|
|
7
|
+
<!-- tools: memory -->
|
|
5
8
|
- Durable memory creation or update→`memory`; store a compact English
|
|
6
9
|
statement.
|
|
10
|
+
<!-- tools: memory -->
|
|
7
11
|
- Use judgment to decide whether a durable memory should be stored, whether
|
|
8
12
|
user confirmation is needed, and which scope best fits the context.
|
|
13
|
+
<!-- tools: memory -->
|
|
9
14
|
- Omit `project_id` for the current Project, use `"common"` for shared memory,
|
|
10
|
-
or
|
|
11
|
-
read-only.
|
|
15
|
+
or an explicit Project slug for another named Project; `*` is read-only.
|
|
@@ -334,45 +334,6 @@ export function makeAgentDispatch(opts = {}) {
|
|
|
334
334
|
// pooled or resumed. Cache prefix matching happens at the provider
|
|
335
335
|
// layer (account-level), not the session level.
|
|
336
336
|
const finalPrompt = prompt;
|
|
337
|
-
// Agent shard spread: inside a shard child the ephemeral hidden-role
|
|
338
|
-
// session runs on a peer shard through the daemon session protocol —
|
|
339
|
-
// the same single spawn path the agent tool uses. ONE path per mode:
|
|
340
|
-
// a remote failure fails the dispatch (no in-process fallback).
|
|
341
|
-
const spreadMod = await import('../../../../standalone/agent-tool/shard-spread.mjs')
|
|
342
|
-
.catch(() => null);
|
|
343
|
-
if (spreadMod?.agentShardSpreadEnabled?.()) {
|
|
344
|
-
const raw = await spreadMod.dispatchHiddenAgentRemote({
|
|
345
|
-
spec: {
|
|
346
|
-
agent,
|
|
347
|
-
presetName,
|
|
348
|
-
preset,
|
|
349
|
-
runtimeSpec,
|
|
350
|
-
permission,
|
|
351
|
-
cwd,
|
|
352
|
-
sourceType: opts.sourceType,
|
|
353
|
-
sourceName: sourceNameArg || opts.sourceName,
|
|
354
|
-
parentSessionId: opts.parentSessionId || null,
|
|
355
|
-
ownerSessionId: opts.ownerSessionId === undefined ? (opts.parentSessionId || null) : opts.ownerSessionId,
|
|
356
|
-
clientHostPid: opts.clientHostPid,
|
|
357
|
-
skipRoleReminder: isPoolC,
|
|
358
|
-
schemaAllowedTools: resolveHiddenRoleSchemaAllowedTools(hidden),
|
|
359
|
-
taskType: opts.taskType,
|
|
360
|
-
maxLoopIterations: opts.maxLoopIterations,
|
|
361
|
-
},
|
|
362
|
-
provider: preset.provider,
|
|
363
|
-
model: preset.model,
|
|
364
|
-
cwd,
|
|
365
|
-
prompt: finalPrompt,
|
|
366
|
-
parentSignals: [opts.parentSignal, callParentSignal],
|
|
367
|
-
watchdogPolicy: resolveAgentWatchdogPolicy(agent, {
|
|
368
|
-
idleTimeoutMs: Number.isFinite(callIdleTimeoutMs)
|
|
369
|
-
? callIdleTimeoutMs
|
|
370
|
-
: opts.idleTimeoutMs,
|
|
371
|
-
firstResponseTimeoutMs: opts.firstResponseTimeoutMs,
|
|
372
|
-
}),
|
|
373
|
-
});
|
|
374
|
-
return opts.brief === false ? raw : applyBriefCap(raw);
|
|
375
|
-
}
|
|
376
337
|
const { session } = prepareAgentSession({
|
|
377
338
|
agent,
|
|
378
339
|
presetName,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
+
import { resourceAdmission } from '../../../shared/resource-admission.mjs';
|
|
3
4
|
|
|
4
5
|
// Normal provider traffic is not concurrency-gated. Independent sessions and
|
|
5
6
|
// accounts start immediately; a finite limit exists only when an operator
|
|
@@ -577,28 +578,33 @@ export function wrapProviderAdmission(provider, providerName, scheduler = provid
|
|
|
577
578
|
const opts = sendOpts || {};
|
|
578
579
|
const signal = opts.signal || null;
|
|
579
580
|
const key = providerAdmissionKey(providerName, this);
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
581
|
+
// Provider queueing and network wait consume no local CPU slot. The
|
|
582
|
+
// resource controller reacquires the agent lease before model output
|
|
583
|
+
// continues through local context/tool processing.
|
|
584
|
+
return resourceAdmission.runYielded(() =>
|
|
585
|
+
scheduler.run(key, (admissionSignal) => {
|
|
586
|
+
// Admission is the common request-clock boundary for WS/SSE/HTTP.
|
|
587
|
+
// Queue wait therefore cannot consume first-byte or agent-watchdog
|
|
588
|
+
// time. Provider-local retry remains the sole retry owner.
|
|
589
|
+
try { opts.onStageChange?.('requesting'); } catch {}
|
|
590
|
+
return originalSend.call(this, messages, model, tools, {
|
|
591
|
+
...opts,
|
|
592
|
+
signal: admissionSignal,
|
|
593
|
+
});
|
|
594
|
+
}, {
|
|
595
|
+
signal,
|
|
596
|
+
ownerKey: opts.admissionOwner || opts.sessionId || null,
|
|
597
|
+
priority: opts.admissionPriority || 'user-visible',
|
|
598
|
+
onCooldownWait: (waitMs) => {
|
|
599
|
+
// Display-only: the TUI/desktop 'reconnecting' stage already
|
|
600
|
+
// renders a custom verb, so no new stage vocabulary is needed.
|
|
601
|
+
const secs = Math.max(1, Math.ceil(waitMs / 1000));
|
|
602
|
+
try {
|
|
603
|
+
opts.onStageChange?.('reconnecting', { message: `Rate-limited — waiting ~${secs}s for the provider window` });
|
|
604
|
+
} catch { /* display-only */ }
|
|
605
|
+
},
|
|
606
|
+
})
|
|
607
|
+
);
|
|
602
608
|
};
|
|
603
609
|
return provider;
|
|
604
610
|
}
|
|
@@ -65,6 +65,26 @@ function firstExclusiveRequired(branches) {
|
|
|
65
65
|
return [];
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
const ARRAY_DROP_NOTE = 'This provider accepts a single value here, not an array.';
|
|
69
|
+
|
|
70
|
+
function describesArray(schema) {
|
|
71
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return false;
|
|
72
|
+
return schema.type === 'array' || (Array.isArray(schema.type) && schema.type.includes('array'));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Flattening keeps one branch, so a description that still promises the dropped
|
|
76
|
+
// shape would advertise more than the wire schema accepts. Project the loss
|
|
77
|
+
// into the text the model actually reads.
|
|
78
|
+
function projectDroppedBranches(schema, dropped) {
|
|
79
|
+
if (describesArray(schema) || !dropped.some(describesArray)) return schema;
|
|
80
|
+
const description = String(schema.description || '').trim();
|
|
81
|
+
if (description.includes(ARRAY_DROP_NOTE)) return schema;
|
|
82
|
+
return {
|
|
83
|
+
...schema,
|
|
84
|
+
description: description ? `${description} ${ARRAY_DROP_NOTE}` : ARRAY_DROP_NOTE,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
68
88
|
function normalizeGrokPropertySchema(schema) {
|
|
69
89
|
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return schema;
|
|
70
90
|
const branches = [
|
|
@@ -75,7 +95,8 @@ function normalizeGrokPropertySchema(schema) {
|
|
|
75
95
|
const first = branches.find(branch => branch && typeof branch === 'object' && !Array.isArray(branch));
|
|
76
96
|
if (first) {
|
|
77
97
|
const { anyOf: _anyOf, oneOf: _oneOf, ...siblings } = schema;
|
|
78
|
-
|
|
98
|
+
const dropped = branches.filter(branch => branch !== first);
|
|
99
|
+
return normalizeGrokPropertySchema(projectDroppedBranches({ ...first, ...siblings }, dropped));
|
|
79
100
|
}
|
|
80
101
|
}
|
|
81
102
|
if (!schema.properties || typeof schema.properties !== 'object') return schema;
|
|
@@ -203,7 +203,13 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
203
203
|
};
|
|
204
204
|
const sessionRef = opts.session || null;
|
|
205
205
|
let _providerPrefixGuardState = sessionRef?._providerPrefixGuardState || null;
|
|
206
|
-
|
|
206
|
+
// Provider tool snapshots are request-loop state, never durable session
|
|
207
|
+
// state. Older builds persisted this field, which let a resumed session
|
|
208
|
+
// keep advertising a retired schema even after session.tools was rebuilt.
|
|
209
|
+
if (sessionRef && Object.prototype.hasOwnProperty.call(sessionRef, '_providerToolSurfaceSnapshot')) {
|
|
210
|
+
delete sessionRef._providerToolSurfaceSnapshot;
|
|
211
|
+
}
|
|
212
|
+
let _fixedProviderToolSurface = null;
|
|
207
213
|
const loopUsageMetricsEpoch = () => Number(sessionRef?.usageMetricsEpoch) || 0;
|
|
208
214
|
const loopUsageMetricsTurnId = () => Number(sessionRef?.usageMetricsTurnId) || 0;
|
|
209
215
|
// Sub-agent (worker/heavy-worker/reviewer/…) sessions
|
|
@@ -496,7 +502,6 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
496
502
|
});
|
|
497
503
|
if (!_fixedProviderToolSurface) {
|
|
498
504
|
_fixedProviderToolSurface = _candidateSendTools;
|
|
499
|
-
if (sessionRef) sessionRef._providerToolSurfaceSnapshot = _fixedProviderToolSurface;
|
|
500
505
|
}
|
|
501
506
|
sendTools = _fixedProviderToolSurface;
|
|
502
507
|
requestToolScope = {
|
|
@@ -142,7 +142,7 @@ test('apply_patch, shell, and mutating git batches invalidate all earlier eviden
|
|
|
142
142
|
['apply_patch', {}],
|
|
143
143
|
['shell', {}],
|
|
144
144
|
['git', { command: 'git commit -m test' }],
|
|
145
|
-
['git', { command: "git reflog delete 'HEAD@{1}'"
|
|
145
|
+
['git', { command: "git reflog delete 'HEAD@{1}'" }],
|
|
146
146
|
]) {
|
|
147
147
|
const messages = [
|
|
148
148
|
call('read_1', 'read', { file_path: 'src/a.mjs' }),
|
|
@@ -164,6 +164,67 @@ test('agent loop heals one rejected tail image and the next turn stays usable',
|
|
|
164
164
|
assert.equal(nextCalls, 1);
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
+
test('agent loop keeps provider tool snapshots turn-local', async () => {
|
|
168
|
+
const oldTool = {
|
|
169
|
+
name: 'shell',
|
|
170
|
+
description: 'old schema',
|
|
171
|
+
inputSchema: { type: 'object', properties: { retired: { type: 'boolean' } } },
|
|
172
|
+
};
|
|
173
|
+
const currentTool = {
|
|
174
|
+
name: 'shell',
|
|
175
|
+
description: 'current schema',
|
|
176
|
+
inputSchema: { type: 'object', properties: { command: { type: 'string' } } },
|
|
177
|
+
};
|
|
178
|
+
const nextTool = {
|
|
179
|
+
name: 'shell',
|
|
180
|
+
description: 'next schema',
|
|
181
|
+
inputSchema: { type: 'object', properties: { command: { type: 'string' }, timeout_ms: { type: 'number' } } },
|
|
182
|
+
};
|
|
183
|
+
const sentToolDescriptions = [];
|
|
184
|
+
const provider = {
|
|
185
|
+
async send(_messages, _model, tools) {
|
|
186
|
+
sentToolDescriptions.push(tools?.[0]?.description);
|
|
187
|
+
return { content: 'done', toolCalls: [], stopReason: 'end_turn' };
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
const session = {
|
|
191
|
+
id: 'provider-tool-snapshot-lifetime-test',
|
|
192
|
+
owner: 'cli',
|
|
193
|
+
contextWindow: 200_000,
|
|
194
|
+
rawContextWindow: 200_000,
|
|
195
|
+
compaction: { auto: false },
|
|
196
|
+
_providerToolSurfaceSnapshot: [oldTool],
|
|
197
|
+
};
|
|
198
|
+
const messages = [
|
|
199
|
+
{ role: 'system', content: 'system' },
|
|
200
|
+
{ role: 'user', content: 'first turn' },
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
await agentLoop(
|
|
204
|
+
provider,
|
|
205
|
+
messages,
|
|
206
|
+
'fake-model',
|
|
207
|
+
[currentTool],
|
|
208
|
+
null,
|
|
209
|
+
process.cwd(),
|
|
210
|
+
{ session, sessionId: session.id },
|
|
211
|
+
);
|
|
212
|
+
assert.equal(Object.hasOwn(session, '_providerToolSurfaceSnapshot'), false);
|
|
213
|
+
|
|
214
|
+
messages.push({ role: 'user', content: 'next turn' });
|
|
215
|
+
await agentLoop(
|
|
216
|
+
provider,
|
|
217
|
+
messages,
|
|
218
|
+
'fake-model',
|
|
219
|
+
[nextTool],
|
|
220
|
+
null,
|
|
221
|
+
process.cwd(),
|
|
222
|
+
{ session, sessionId: session.id },
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
assert.deepEqual(sentToolDescriptions, ['current schema', 'next schema']);
|
|
226
|
+
});
|
|
227
|
+
|
|
167
228
|
test('mid-stream xAI generation crash is retryable even as invalid_request_error', () => {
|
|
168
229
|
const err = new Error('xAI Responses stream error: Internal error during token generation');
|
|
169
230
|
err.providerWireError = true;
|