reasonix 0.28.0 → 0.29.1
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/dashboard/app.css +3 -0
- package/dashboard/dist/app.js +61 -26
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/{chunk-COFBA5FV.js → chunk-RPP24CUB.js} +7 -1
- package/dist/cli/{chunk-COFBA5FV.js.map → chunk-RPP24CUB.js.map} +1 -1
- package/dist/cli/index.js +6012 -5524
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-VF7B6BWR.js → prompt-5GLHSLO2.js} +2 -2
- package/dist/index.d.ts +16 -0
- package/dist/index.js +149 -62
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- /package/dist/cli/{prompt-VF7B6BWR.js.map → prompt-5GLHSLO2.js.map} +0 -0
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
CODE_SYSTEM_PROMPT,
|
|
4
4
|
codeSystemPrompt
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-RPP24CUB.js";
|
|
6
6
|
export {
|
|
7
7
|
CODE_SYSTEM_PROMPT,
|
|
8
8
|
codeSystemPrompt
|
|
9
9
|
};
|
|
10
|
-
//# sourceMappingURL=prompt-
|
|
10
|
+
//# sourceMappingURL=prompt-5GLHSLO2.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -586,6 +586,15 @@ interface SessionSummary {
|
|
|
586
586
|
}
|
|
587
587
|
declare class SessionStats {
|
|
588
588
|
readonly turns: TurnStats[];
|
|
589
|
+
/** Cost from prior runs of a resumed session, restored from session meta. */
|
|
590
|
+
private _carryoverCost;
|
|
591
|
+
/** Turn count from prior runs of a resumed session. */
|
|
592
|
+
private _carryoverTurns;
|
|
593
|
+
/** Seed totals from a resumed session's persisted meta — only call once at construction. */
|
|
594
|
+
seedCarryover(opts: {
|
|
595
|
+
totalCostUsd?: number;
|
|
596
|
+
turnCount?: number;
|
|
597
|
+
}): void;
|
|
589
598
|
record(turn: number, model: string, usage: Usage): TurnStats;
|
|
590
599
|
get totalCost(): number;
|
|
591
600
|
get totalClaudeEquivalent(): number;
|
|
@@ -694,6 +703,8 @@ interface ToolDefinition<A = any, R = any> {
|
|
|
694
703
|
readOnly?: boolean;
|
|
695
704
|
/** Per-args check; takes precedence over `readOnly`. e.g. `run_command` + allowlisted argv. */
|
|
696
705
|
readOnlyCheck?: (args: A) => boolean;
|
|
706
|
+
/** Safe to dispatch concurrently with other parallel-safe calls in the same turn. Default false — opt-in only. */
|
|
707
|
+
parallelSafe?: boolean;
|
|
697
708
|
fn: (args: A, ctx?: ToolCallContext) => R | Promise<R>;
|
|
698
709
|
}
|
|
699
710
|
interface ToolRegistryOptions {
|
|
@@ -729,6 +740,8 @@ declare class ToolRegistry {
|
|
|
729
740
|
get size(): number;
|
|
730
741
|
/** True if a registered tool's schema was flattened for the model. */
|
|
731
742
|
wasFlattened(name: string): boolean;
|
|
743
|
+
/** Unknown / unannotated tools default to false — third-party MCP tools must opt in. */
|
|
744
|
+
isParallelSafe(name: string): boolean;
|
|
732
745
|
specs(): ToolSpec[];
|
|
733
746
|
dispatch(name: string, argumentsRaw: string | Record<string, unknown>, opts?: {
|
|
734
747
|
signal?: AbortSignal;
|
|
@@ -840,6 +853,7 @@ declare class CacheFirstLoop {
|
|
|
840
853
|
private modelForCurrentCall;
|
|
841
854
|
/** Returns true ONLY on the tipping call — caller surfaces a one-shot warning. */
|
|
842
855
|
private noteToolFailureSignal;
|
|
856
|
+
private runOneToolCall;
|
|
843
857
|
private buildMessages;
|
|
844
858
|
abort(): void;
|
|
845
859
|
/** Drop the last user message + everything after; caller re-sends. Persists to session file. */
|
|
@@ -1102,6 +1116,8 @@ declare function registerPlanTool(registry: ToolRegistry, opts?: PlanToolOptions
|
|
|
1102
1116
|
/** Side-channel — subagents run inside a tool-dispatch frame, can't go through parent's `LoopEvent` stream. */
|
|
1103
1117
|
interface SubagentEvent {
|
|
1104
1118
|
kind: "start" | "progress" | "end" | "inner" | "phase";
|
|
1119
|
+
/** Stable per-spawn id; lets the UI key parallel runs apart instead of overwriting one shared row. */
|
|
1120
|
+
runId: string;
|
|
1105
1121
|
task: string;
|
|
1106
1122
|
skillName?: string;
|
|
1107
1123
|
model?: string;
|
package/dist/index.js
CHANGED
|
@@ -1046,6 +1046,10 @@ var ToolRegistry = class {
|
|
|
1046
1046
|
wasFlattened(name) {
|
|
1047
1047
|
return Boolean(this._tools.get(name)?.flatSchema);
|
|
1048
1048
|
}
|
|
1049
|
+
/** Unknown / unannotated tools default to false — third-party MCP tools must opt in. */
|
|
1050
|
+
isParallelSafe(name) {
|
|
1051
|
+
return this._tools.get(name)?.parallelSafe === true;
|
|
1052
|
+
}
|
|
1049
1053
|
specs() {
|
|
1050
1054
|
return [...this._tools.values()].map((t) => ({
|
|
1051
1055
|
type: "function",
|
|
@@ -1472,6 +1476,19 @@ function claudeEquivalentCost(usage) {
|
|
|
1472
1476
|
}
|
|
1473
1477
|
var SessionStats = class {
|
|
1474
1478
|
turns = [];
|
|
1479
|
+
/** Cost from prior runs of a resumed session, restored from session meta. */
|
|
1480
|
+
_carryoverCost = 0;
|
|
1481
|
+
/** Turn count from prior runs of a resumed session. */
|
|
1482
|
+
_carryoverTurns = 0;
|
|
1483
|
+
/** Seed totals from a resumed session's persisted meta — only call once at construction. */
|
|
1484
|
+
seedCarryover(opts) {
|
|
1485
|
+
if (typeof opts.totalCostUsd === "number" && opts.totalCostUsd > 0) {
|
|
1486
|
+
this._carryoverCost = opts.totalCostUsd;
|
|
1487
|
+
}
|
|
1488
|
+
if (typeof opts.turnCount === "number" && opts.turnCount > 0) {
|
|
1489
|
+
this._carryoverTurns = opts.turnCount;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1475
1492
|
record(turn, model, usage) {
|
|
1476
1493
|
const cost = costUsd(model, usage);
|
|
1477
1494
|
const stats = {
|
|
@@ -1485,7 +1502,7 @@ var SessionStats = class {
|
|
|
1485
1502
|
return stats;
|
|
1486
1503
|
}
|
|
1487
1504
|
get totalCost() {
|
|
1488
|
-
return this.turns.reduce((sum, t) => sum + t.cost, 0);
|
|
1505
|
+
return this._carryoverCost + this.turns.reduce((sum, t) => sum + t.cost, 0);
|
|
1489
1506
|
}
|
|
1490
1507
|
get totalClaudeEquivalent() {
|
|
1491
1508
|
return this.turns.reduce((sum, t) => sum + claudeEquivalentCost(t.usage), 0);
|
|
@@ -1513,7 +1530,7 @@ var SessionStats = class {
|
|
|
1513
1530
|
summary() {
|
|
1514
1531
|
const last = this.turns[this.turns.length - 1];
|
|
1515
1532
|
return {
|
|
1516
|
-
turns: this.turns.length,
|
|
1533
|
+
turns: this.turns.length + this._carryoverTurns,
|
|
1517
1534
|
totalCostUsd: round(this.totalCost, 6),
|
|
1518
1535
|
totalInputCostUsd: round(this.totalInputCost, 6),
|
|
1519
1536
|
totalOutputCostUsd: round(this.totalOutputCost, 6),
|
|
@@ -2520,6 +2537,13 @@ var CacheFirstLoop = class {
|
|
|
2520
2537
|
const tokensSaved = shrunk.tokensSaved;
|
|
2521
2538
|
for (const msg of messages) this.log.append(msg);
|
|
2522
2539
|
this.resumedMessageCount = messages.length;
|
|
2540
|
+
if (messages.length > 0) {
|
|
2541
|
+
const meta = loadSessionMeta(this.sessionName);
|
|
2542
|
+
this.stats.seedCarryover({
|
|
2543
|
+
totalCostUsd: meta.totalCostUsd,
|
|
2544
|
+
turnCount: meta.turnCount
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2523
2547
|
if (healedCount > 0) {
|
|
2524
2548
|
try {
|
|
2525
2549
|
rewriteSession(this.sessionName, messages);
|
|
@@ -2640,6 +2664,48 @@ var CacheFirstLoop = class {
|
|
|
2640
2664
|
this._escalateThisTurn = true;
|
|
2641
2665
|
return true;
|
|
2642
2666
|
}
|
|
2667
|
+
async runOneToolCall(call, signal) {
|
|
2668
|
+
const name = call.function?.name ?? "";
|
|
2669
|
+
const args = call.function?.arguments ?? "{}";
|
|
2670
|
+
const parsedArgs = safeParseToolArgs(args);
|
|
2671
|
+
const preReport = await runHooks({
|
|
2672
|
+
hooks: this.hooks,
|
|
2673
|
+
payload: {
|
|
2674
|
+
event: "PreToolUse",
|
|
2675
|
+
cwd: this.hookCwd,
|
|
2676
|
+
toolName: name,
|
|
2677
|
+
toolArgs: parsedArgs
|
|
2678
|
+
}
|
|
2679
|
+
});
|
|
2680
|
+
const preWarnings = [...hookWarnings(preReport.outcomes, this._turn)];
|
|
2681
|
+
if (preReport.blocked) {
|
|
2682
|
+
const blocking = preReport.outcomes[preReport.outcomes.length - 1];
|
|
2683
|
+
const reason = (blocking?.stderr || blocking?.stdout || "blocked by PreToolUse hook").trim();
|
|
2684
|
+
return {
|
|
2685
|
+
preWarnings,
|
|
2686
|
+
postWarnings: [],
|
|
2687
|
+
result: `[hook block] ${blocking?.hook.command ?? "<unknown>"}
|
|
2688
|
+
${reason}`
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
const result = await this.tools.dispatch(name, args, {
|
|
2692
|
+
signal,
|
|
2693
|
+
maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
|
|
2694
|
+
confirmationGate: this.confirmationGate
|
|
2695
|
+
});
|
|
2696
|
+
const postReport = await runHooks({
|
|
2697
|
+
hooks: this.hooks,
|
|
2698
|
+
payload: {
|
|
2699
|
+
event: "PostToolUse",
|
|
2700
|
+
cwd: this.hookCwd,
|
|
2701
|
+
toolName: name,
|
|
2702
|
+
toolArgs: parsedArgs,
|
|
2703
|
+
toolResult: result
|
|
2704
|
+
}
|
|
2705
|
+
});
|
|
2706
|
+
const postWarnings = [...hookWarnings(postReport.outcomes, this._turn)];
|
|
2707
|
+
return { preWarnings, postWarnings, result };
|
|
2708
|
+
}
|
|
2643
2709
|
buildMessages(pendingUser) {
|
|
2644
2710
|
const healed = healLoadedMessages(this.log.toMessages(), DEFAULT_MAX_RESULT_CHARS);
|
|
2645
2711
|
const msgs = [...this.prefix.toMessages(), ...healed.messages];
|
|
@@ -3131,71 +3197,69 @@ var CacheFirstLoop = class {
|
|
|
3131
3197
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "context-guard" });
|
|
3132
3198
|
return;
|
|
3133
3199
|
}
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
};
|
|
3144
|
-
const parsedArgs = safeParseToolArgs(args);
|
|
3145
|
-
const preReport = await runHooks({
|
|
3146
|
-
hooks: this.hooks,
|
|
3147
|
-
payload: {
|
|
3148
|
-
event: "PreToolUse",
|
|
3149
|
-
cwd: this.hookCwd,
|
|
3150
|
-
toolName: name,
|
|
3151
|
-
toolArgs: parsedArgs
|
|
3200
|
+
const dispatchSerial = (process.env.REASONIX_TOOL_DISPATCH ?? "auto").toLowerCase() === "serial";
|
|
3201
|
+
const parallelMaxParsed = Number.parseInt(process.env.REASONIX_PARALLEL_MAX ?? "", 10);
|
|
3202
|
+
const parallelMax = Number.isFinite(parallelMaxParsed) && parallelMaxParsed >= 1 ? Math.min(parallelMaxParsed, 16) : 3;
|
|
3203
|
+
let callIdx = 0;
|
|
3204
|
+
while (callIdx < repairedCalls.length) {
|
|
3205
|
+
const chunk = [];
|
|
3206
|
+
if (!dispatchSerial) {
|
|
3207
|
+
while (callIdx < repairedCalls.length && chunk.length < parallelMax && this.tools.isParallelSafe(repairedCalls[callIdx]?.function?.name ?? "")) {
|
|
3208
|
+
chunk.push(repairedCalls[callIdx++]);
|
|
3152
3209
|
}
|
|
3153
|
-
});
|
|
3154
|
-
for (const w of hookWarnings(preReport.outcomes, this._turn)) yield w;
|
|
3155
|
-
let result;
|
|
3156
|
-
if (preReport.blocked) {
|
|
3157
|
-
const blocking = preReport.outcomes[preReport.outcomes.length - 1];
|
|
3158
|
-
const reason = (blocking?.stderr || blocking?.stdout || "blocked by PreToolUse hook").trim();
|
|
3159
|
-
result = `[hook block] ${blocking?.hook.command ?? "<unknown>"}
|
|
3160
|
-
${reason}`;
|
|
3161
|
-
} else {
|
|
3162
|
-
result = await this.tools.dispatch(name, args, {
|
|
3163
|
-
signal,
|
|
3164
|
-
maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
|
|
3165
|
-
confirmationGate: this.confirmationGate
|
|
3166
|
-
});
|
|
3167
|
-
const postReport = await runHooks({
|
|
3168
|
-
hooks: this.hooks,
|
|
3169
|
-
payload: {
|
|
3170
|
-
event: "PostToolUse",
|
|
3171
|
-
cwd: this.hookCwd,
|
|
3172
|
-
toolName: name,
|
|
3173
|
-
toolArgs: parsedArgs,
|
|
3174
|
-
toolResult: result
|
|
3175
|
-
}
|
|
3176
|
-
});
|
|
3177
|
-
for (const w of hookWarnings(postReport.outcomes, this._turn)) yield w;
|
|
3178
3210
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
content: result
|
|
3184
|
-
});
|
|
3185
|
-
if (this.noteToolFailureSignal(result)) {
|
|
3211
|
+
if (chunk.length === 0) {
|
|
3212
|
+
chunk.push(repairedCalls[callIdx++]);
|
|
3213
|
+
}
|
|
3214
|
+
for (const call of chunk) {
|
|
3186
3215
|
yield {
|
|
3187
3216
|
turn: this._turn,
|
|
3188
|
-
role: "
|
|
3189
|
-
content:
|
|
3217
|
+
role: "tool_start",
|
|
3218
|
+
content: "",
|
|
3219
|
+
toolName: call.function?.name ?? "",
|
|
3220
|
+
toolArgs: call.function?.arguments ?? "{}"
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
const settled = await Promise.allSettled(chunk.map((c) => this.runOneToolCall(c, signal)));
|
|
3224
|
+
for (let k = 0; k < chunk.length; k++) {
|
|
3225
|
+
const call = chunk[k];
|
|
3226
|
+
const name = call.function?.name ?? "";
|
|
3227
|
+
const args = call.function?.arguments ?? "{}";
|
|
3228
|
+
const s = settled[k];
|
|
3229
|
+
let result;
|
|
3230
|
+
let preWarnings = [];
|
|
3231
|
+
let postWarnings = [];
|
|
3232
|
+
if (s.status === "fulfilled") {
|
|
3233
|
+
preWarnings = s.value.preWarnings;
|
|
3234
|
+
postWarnings = s.value.postWarnings;
|
|
3235
|
+
result = s.value.result;
|
|
3236
|
+
} else {
|
|
3237
|
+
const err = s.reason instanceof Error ? s.reason : new Error(String(s.reason));
|
|
3238
|
+
result = JSON.stringify({ error: `${err.name}: ${err.message}` });
|
|
3239
|
+
}
|
|
3240
|
+
for (const w of preWarnings) yield w;
|
|
3241
|
+
for (const w of postWarnings) yield w;
|
|
3242
|
+
this.appendAndPersist({
|
|
3243
|
+
role: "tool",
|
|
3244
|
+
tool_call_id: call.id ?? "",
|
|
3245
|
+
name,
|
|
3246
|
+
content: result
|
|
3247
|
+
});
|
|
3248
|
+
if (this.noteToolFailureSignal(result)) {
|
|
3249
|
+
yield {
|
|
3250
|
+
turn: this._turn,
|
|
3251
|
+
role: "warning",
|
|
3252
|
+
content: `\u21E7 auto-escalating to ${ESCALATION_MODEL} for the rest of this turn \u2014 flash hit ${this._turnFailures.formatBreakdown()}. Next turn falls back to ${this.model} unless /pro is armed.`
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
yield {
|
|
3256
|
+
turn: this._turn,
|
|
3257
|
+
role: "tool",
|
|
3258
|
+
content: result,
|
|
3259
|
+
toolName: name,
|
|
3260
|
+
toolArgs: args
|
|
3190
3261
|
};
|
|
3191
3262
|
}
|
|
3192
|
-
yield {
|
|
3193
|
-
turn: this._turn,
|
|
3194
|
-
role: "tool",
|
|
3195
|
-
content: result,
|
|
3196
|
-
toolName: name,
|
|
3197
|
-
toolArgs: args
|
|
3198
|
-
};
|
|
3199
3263
|
}
|
|
3200
3264
|
}
|
|
3201
3265
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "budget" });
|
|
@@ -4604,6 +4668,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
4604
4668
|
};
|
|
4605
4669
|
registry.register({
|
|
4606
4670
|
name: "read_file",
|
|
4671
|
+
parallelSafe: true,
|
|
4607
4672
|
description: `Read a file under the sandbox root. To save context, PREFER to scope the read instead of pulling the whole file:
|
|
4608
4673
|
- head: N \u2192 first N lines (imports, public API, small configs)
|
|
4609
4674
|
- tail: N \u2192 last N lines (recently-added code, log tails)
|
|
@@ -4687,6 +4752,7 @@ ${slice.join("\n")}`;
|
|
|
4687
4752
|
});
|
|
4688
4753
|
registry.register({
|
|
4689
4754
|
name: "list_directory",
|
|
4755
|
+
parallelSafe: true,
|
|
4690
4756
|
description: "List entries in a directory under the sandbox root. Returns one line per entry, marking directories with a trailing slash. Not recursive \u2014 use directory_tree for that.",
|
|
4691
4757
|
readOnly: true,
|
|
4692
4758
|
parameters: {
|
|
@@ -4707,6 +4773,7 @@ ${slice.join("\n")}`;
|
|
|
4707
4773
|
});
|
|
4708
4774
|
registry.register({
|
|
4709
4775
|
name: "directory_tree",
|
|
4776
|
+
parallelSafe: true,
|
|
4710
4777
|
description: `Recursively list entries in a directory. Shows indented tree structure with directories marked '/'. Budget-aware by default:
|
|
4711
4778
|
- maxDepth defaults to 2 (root + one level). A depth-4 tree on a real repo blew ~5K tokens in one call. If you truly need deeper, pass maxDepth:N explicitly.
|
|
4712
4779
|
- Skips ${[...SKIP_DIR_NAMES].sort().join(", ")} unless include_deps:true. Traversing into node_modules / .git / dist is almost always token-waste.
|
|
@@ -4785,6 +4852,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4785
4852
|
});
|
|
4786
4853
|
registry.register({
|
|
4787
4854
|
name: "search_files",
|
|
4855
|
+
parallelSafe: true,
|
|
4788
4856
|
description: "Find files whose NAME matches a substring or regex. Case-insensitive. Walks the directory recursively under the sandbox root. Returns one path per line. Skips dependency / VCS / build directories (node_modules, .git, dist, build, .next, target, .venv) by default.",
|
|
4789
4857
|
readOnly: true,
|
|
4790
4858
|
parameters: {
|
|
@@ -4810,6 +4878,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4810
4878
|
});
|
|
4811
4879
|
registry.register({
|
|
4812
4880
|
name: "search_content",
|
|
4881
|
+
parallelSafe: true,
|
|
4813
4882
|
description: "Recursively grep file CONTENTS for a substring or regex. This is the right tool for 'find all places that call X', 'where is Y referenced', 'what files contain Z'. Different from search_files (which matches FILE NAMES). Returns one match per line in 'path:line: text' format. Skips dependency / VCS / build directories (node_modules, .git, dist, build, .next, target, .venv) and binary files by default.",
|
|
4814
4883
|
readOnly: true,
|
|
4815
4884
|
parameters: {
|
|
@@ -4852,6 +4921,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4852
4921
|
});
|
|
4853
4922
|
registry.register({
|
|
4854
4923
|
name: "get_file_info",
|
|
4924
|
+
parallelSafe: true,
|
|
4855
4925
|
description: "Stat a path under the sandbox root. Returns type (file|directory|symlink), size in bytes, mtime in ISO-8601.",
|
|
4856
4926
|
readOnly: true,
|
|
4857
4927
|
parameters: {
|
|
@@ -5032,6 +5102,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
5032
5102
|
name: "recall_memory",
|
|
5033
5103
|
description: "Read the full body of a memory file when its MEMORY.md one-liner (already in the system prompt) isn't enough detail. Most of the time the index suffices \u2014 only call this when the user's question genuinely requires the full context.",
|
|
5034
5104
|
readOnly: true,
|
|
5105
|
+
parallelSafe: true,
|
|
5035
5106
|
parameters: {
|
|
5036
5107
|
type: "object",
|
|
5037
5108
|
properties: {
|
|
@@ -5458,6 +5529,11 @@ function getSubagentType(name) {
|
|
|
5458
5529
|
}
|
|
5459
5530
|
|
|
5460
5531
|
// src/tools/subagent.ts
|
|
5532
|
+
var runIdCounter = 0;
|
|
5533
|
+
function nextRunId() {
|
|
5534
|
+
runIdCounter++;
|
|
5535
|
+
return `sub-${runIdCounter.toString(36)}`;
|
|
5536
|
+
}
|
|
5461
5537
|
var DEFAULT_SUBAGENT_SYSTEM = `You are a Reasonix subagent. The parent agent spawned you to handle one focused subtask, then return.
|
|
5462
5538
|
|
|
5463
5539
|
Rules:
|
|
@@ -5486,9 +5562,11 @@ async function spawnSubagent(opts) {
|
|
|
5486
5562
|
const sink = opts.sink;
|
|
5487
5563
|
const skillName = opts.skillName;
|
|
5488
5564
|
const startedAt = Date.now();
|
|
5565
|
+
const runId = nextRunId();
|
|
5489
5566
|
const taskPreview = opts.task.length > 30 ? `${opts.task.slice(0, 30)}\u2026` : opts.task;
|
|
5490
5567
|
sink?.current?.({
|
|
5491
5568
|
kind: "start",
|
|
5569
|
+
runId,
|
|
5492
5570
|
task: taskPreview,
|
|
5493
5571
|
skillName,
|
|
5494
5572
|
model,
|
|
@@ -5501,6 +5579,7 @@ async function spawnSubagent(opts) {
|
|
|
5501
5579
|
const errorMessage2 = `subagent allow-list names tool(s) not registered in the parent: ${missing.join(", ")}. Fix the skill's \`allowed-tools\` frontmatter or check spelling.`;
|
|
5502
5580
|
sink?.current?.({
|
|
5503
5581
|
kind: "end",
|
|
5582
|
+
runId,
|
|
5504
5583
|
task: taskPreview,
|
|
5505
5584
|
skillName,
|
|
5506
5585
|
model,
|
|
@@ -5562,12 +5641,13 @@ async function spawnSubagent(opts) {
|
|
|
5562
5641
|
let summarisingEmitted = false;
|
|
5563
5642
|
try {
|
|
5564
5643
|
for await (const ev of childLoop.step(opts.task)) {
|
|
5565
|
-
sink?.current?.({ kind: "inner", task: taskPreview, skillName, model, inner: ev });
|
|
5644
|
+
sink?.current?.({ kind: "inner", runId, task: taskPreview, skillName, model, inner: ev });
|
|
5566
5645
|
if (ev.role === "tool") {
|
|
5567
5646
|
toolIter++;
|
|
5568
5647
|
summarisingEmitted = false;
|
|
5569
5648
|
sink?.current?.({
|
|
5570
5649
|
kind: "progress",
|
|
5650
|
+
runId,
|
|
5571
5651
|
task: taskPreview,
|
|
5572
5652
|
skillName,
|
|
5573
5653
|
model,
|
|
@@ -5579,6 +5659,7 @@ async function spawnSubagent(opts) {
|
|
|
5579
5659
|
summarisingEmitted = true;
|
|
5580
5660
|
sink?.current?.({
|
|
5581
5661
|
kind: "phase",
|
|
5662
|
+
runId,
|
|
5582
5663
|
task: taskPreview,
|
|
5583
5664
|
skillName,
|
|
5584
5665
|
model,
|
|
@@ -5615,6 +5696,7 @@ async function spawnSubagent(opts) {
|
|
|
5615
5696
|
[\u2026truncated ${final.length - maxResultChars} chars; ask the subagent for a tighter summary if you need more.]` : final;
|
|
5616
5697
|
sink?.current?.({
|
|
5617
5698
|
kind: "end",
|
|
5699
|
+
runId,
|
|
5618
5700
|
task: taskPreview,
|
|
5619
5701
|
skillName,
|
|
5620
5702
|
model,
|
|
@@ -5678,6 +5760,7 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
5678
5760
|
const sink = opts.sink;
|
|
5679
5761
|
parentRegistry.register({
|
|
5680
5762
|
name: SUBAGENT_TOOL_NAME,
|
|
5763
|
+
parallelSafe: true,
|
|
5681
5764
|
description: "Spawn an isolated subagent to handle a self-contained subtask in a fresh context, returning only its final answer. Use for: deep codebase exploration that would flood the main context, multi-step research where you only need the conclusion, or any focused subtask whose intermediate reasoning the user does not need to see. The subagent inherits all your tools (filesystem, shell, web, MCP, etc.) but runs in its own isolated message log \u2014 its tool calls and reasoning never enter your context. Only the final assistant message comes back as this tool's result. Keep tasks focused; the subagent has a stricter iter budget than you do.",
|
|
5682
5765
|
parameters: {
|
|
5683
5766
|
type: "object",
|
|
@@ -7103,6 +7186,7 @@ function registerShellTools(registry, opts) {
|
|
|
7103
7186
|
name: "job_output",
|
|
7104
7187
|
description: "Read the latest output of a background job started with `run_background`. By default returns the tail of the buffer (last 80 lines). Pass `since` (the `byteLength` from a previous call) to stream only new content incrementally. Tells you whether the job is still running, so you can stop polling when it's done.",
|
|
7105
7188
|
readOnly: true,
|
|
7189
|
+
parallelSafe: true,
|
|
7106
7190
|
parameters: {
|
|
7107
7191
|
type: "object",
|
|
7108
7192
|
properties: {
|
|
@@ -7147,6 +7231,7 @@ function registerShellTools(registry, opts) {
|
|
|
7147
7231
|
name: "list_jobs",
|
|
7148
7232
|
description: "List every background job started this session \u2014 running and exited \u2014 with id, command, pid, status. Use when you've lost track of which job_id corresponds to which process, or to see what's still alive.",
|
|
7149
7233
|
readOnly: true,
|
|
7234
|
+
parallelSafe: true,
|
|
7150
7235
|
parameters: { type: "object", properties: {} },
|
|
7151
7236
|
fn: async () => {
|
|
7152
7237
|
const all = jobs.list();
|
|
@@ -7404,6 +7489,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
7404
7489
|
name: "web_search",
|
|
7405
7490
|
description: "Search the public web. Returns ranked results with title, url, and snippet. Call this when the answer's correctness depends on current state \u2014 anything that changes over time (events, prices, releases, status of a thing in the real world). Composing such answers from training memory invents stale numbers; search first, then ground the answer in the results. For evergreen / definitional questions you don't need this.",
|
|
7406
7491
|
readOnly: true,
|
|
7492
|
+
parallelSafe: true,
|
|
7407
7493
|
parameters: {
|
|
7408
7494
|
type: "object",
|
|
7409
7495
|
properties: {
|
|
@@ -7427,6 +7513,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
7427
7513
|
name: "web_fetch",
|
|
7428
7514
|
description: "Download a URL and return its visible text content (HTML pages get scripts/styles/nav stripped). Truncated at the tool-result cap. Use after web_search when a snippet isn't enough.",
|
|
7429
7515
|
readOnly: true,
|
|
7516
|
+
parallelSafe: true,
|
|
7430
7517
|
parameters: {
|
|
7431
7518
|
type: "object",
|
|
7432
7519
|
properties: {
|