reasonix 0.27.3 → 0.29.0
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/dist/cli/{chunk-R2L5YEEF.js → chunk-COFBA5FV.js} +7 -2
- package/dist/cli/chunk-COFBA5FV.js.map +1 -0
- package/dist/cli/index.js +311 -116
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-YUL7CYKY.js → prompt-VF7B6BWR.js} +2 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.js +249 -64
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-R2L5YEEF.js.map +0 -1
- /package/dist/cli/{prompt-YUL7CYKY.js.map → prompt-VF7B6BWR.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
memoryEnabled,
|
|
13
13
|
readProjectMemory,
|
|
14
14
|
sanitizeMemoryName
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-COFBA5FV.js";
|
|
16
16
|
|
|
17
17
|
// src/cli/index.ts
|
|
18
18
|
import { Command } from "commander";
|
|
@@ -2742,6 +2742,10 @@ var ToolRegistry = class {
|
|
|
2742
2742
|
wasFlattened(name) {
|
|
2743
2743
|
return Boolean(this._tools.get(name)?.flatSchema);
|
|
2744
2744
|
}
|
|
2745
|
+
/** Unknown / unannotated tools default to false — third-party MCP tools must opt in. */
|
|
2746
|
+
isParallelSafe(name) {
|
|
2747
|
+
return this._tools.get(name)?.parallelSafe === true;
|
|
2748
|
+
}
|
|
2745
2749
|
specs() {
|
|
2746
2750
|
return [...this._tools.values()].map((t3) => ({
|
|
2747
2751
|
type: "function",
|
|
@@ -4433,6 +4437,48 @@ var CacheFirstLoop = class {
|
|
|
4433
4437
|
this._escalateThisTurn = true;
|
|
4434
4438
|
return true;
|
|
4435
4439
|
}
|
|
4440
|
+
async runOneToolCall(call, signal) {
|
|
4441
|
+
const name = call.function?.name ?? "";
|
|
4442
|
+
const args = call.function?.arguments ?? "{}";
|
|
4443
|
+
const parsedArgs = safeParseToolArgs(args);
|
|
4444
|
+
const preReport = await runHooks({
|
|
4445
|
+
hooks: this.hooks,
|
|
4446
|
+
payload: {
|
|
4447
|
+
event: "PreToolUse",
|
|
4448
|
+
cwd: this.hookCwd,
|
|
4449
|
+
toolName: name,
|
|
4450
|
+
toolArgs: parsedArgs
|
|
4451
|
+
}
|
|
4452
|
+
});
|
|
4453
|
+
const preWarnings = [...hookWarnings(preReport.outcomes, this._turn)];
|
|
4454
|
+
if (preReport.blocked) {
|
|
4455
|
+
const blocking = preReport.outcomes[preReport.outcomes.length - 1];
|
|
4456
|
+
const reason = (blocking?.stderr || blocking?.stdout || "blocked by PreToolUse hook").trim();
|
|
4457
|
+
return {
|
|
4458
|
+
preWarnings,
|
|
4459
|
+
postWarnings: [],
|
|
4460
|
+
result: `[hook block] ${blocking?.hook.command ?? "<unknown>"}
|
|
4461
|
+
${reason}`
|
|
4462
|
+
};
|
|
4463
|
+
}
|
|
4464
|
+
const result = await this.tools.dispatch(name, args, {
|
|
4465
|
+
signal,
|
|
4466
|
+
maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
|
|
4467
|
+
confirmationGate: this.confirmationGate
|
|
4468
|
+
});
|
|
4469
|
+
const postReport = await runHooks({
|
|
4470
|
+
hooks: this.hooks,
|
|
4471
|
+
payload: {
|
|
4472
|
+
event: "PostToolUse",
|
|
4473
|
+
cwd: this.hookCwd,
|
|
4474
|
+
toolName: name,
|
|
4475
|
+
toolArgs: parsedArgs,
|
|
4476
|
+
toolResult: result
|
|
4477
|
+
}
|
|
4478
|
+
});
|
|
4479
|
+
const postWarnings = [...hookWarnings(postReport.outcomes, this._turn)];
|
|
4480
|
+
return { preWarnings, postWarnings, result };
|
|
4481
|
+
}
|
|
4436
4482
|
buildMessages(pendingUser) {
|
|
4437
4483
|
const healed = healLoadedMessages(this.log.toMessages(), DEFAULT_MAX_RESULT_CHARS);
|
|
4438
4484
|
const msgs = [...this.prefix.toMessages(), ...healed.messages];
|
|
@@ -4924,71 +4970,69 @@ var CacheFirstLoop = class {
|
|
|
4924
4970
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "context-guard" });
|
|
4925
4971
|
return;
|
|
4926
4972
|
}
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
};
|
|
4937
|
-
const parsedArgs = safeParseToolArgs(args);
|
|
4938
|
-
const preReport = await runHooks({
|
|
4939
|
-
hooks: this.hooks,
|
|
4940
|
-
payload: {
|
|
4941
|
-
event: "PreToolUse",
|
|
4942
|
-
cwd: this.hookCwd,
|
|
4943
|
-
toolName: name,
|
|
4944
|
-
toolArgs: parsedArgs
|
|
4973
|
+
const dispatchSerial = (process.env.REASONIX_TOOL_DISPATCH ?? "auto").toLowerCase() === "serial";
|
|
4974
|
+
const parallelMaxParsed = Number.parseInt(process.env.REASONIX_PARALLEL_MAX ?? "", 10);
|
|
4975
|
+
const parallelMax = Number.isFinite(parallelMaxParsed) && parallelMaxParsed >= 1 ? Math.min(parallelMaxParsed, 16) : 3;
|
|
4976
|
+
let callIdx = 0;
|
|
4977
|
+
while (callIdx < repairedCalls.length) {
|
|
4978
|
+
const chunk = [];
|
|
4979
|
+
if (!dispatchSerial) {
|
|
4980
|
+
while (callIdx < repairedCalls.length && chunk.length < parallelMax && this.tools.isParallelSafe(repairedCalls[callIdx]?.function?.name ?? "")) {
|
|
4981
|
+
chunk.push(repairedCalls[callIdx++]);
|
|
4945
4982
|
}
|
|
4946
|
-
});
|
|
4947
|
-
for (const w of hookWarnings(preReport.outcomes, this._turn)) yield w;
|
|
4948
|
-
let result;
|
|
4949
|
-
if (preReport.blocked) {
|
|
4950
|
-
const blocking = preReport.outcomes[preReport.outcomes.length - 1];
|
|
4951
|
-
const reason = (blocking?.stderr || blocking?.stdout || "blocked by PreToolUse hook").trim();
|
|
4952
|
-
result = `[hook block] ${blocking?.hook.command ?? "<unknown>"}
|
|
4953
|
-
${reason}`;
|
|
4954
|
-
} else {
|
|
4955
|
-
result = await this.tools.dispatch(name, args, {
|
|
4956
|
-
signal,
|
|
4957
|
-
maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
|
|
4958
|
-
confirmationGate: this.confirmationGate
|
|
4959
|
-
});
|
|
4960
|
-
const postReport = await runHooks({
|
|
4961
|
-
hooks: this.hooks,
|
|
4962
|
-
payload: {
|
|
4963
|
-
event: "PostToolUse",
|
|
4964
|
-
cwd: this.hookCwd,
|
|
4965
|
-
toolName: name,
|
|
4966
|
-
toolArgs: parsedArgs,
|
|
4967
|
-
toolResult: result
|
|
4968
|
-
}
|
|
4969
|
-
});
|
|
4970
|
-
for (const w of hookWarnings(postReport.outcomes, this._turn)) yield w;
|
|
4971
4983
|
}
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
content: result
|
|
4977
|
-
});
|
|
4978
|
-
if (this.noteToolFailureSignal(result)) {
|
|
4984
|
+
if (chunk.length === 0) {
|
|
4985
|
+
chunk.push(repairedCalls[callIdx++]);
|
|
4986
|
+
}
|
|
4987
|
+
for (const call of chunk) {
|
|
4979
4988
|
yield {
|
|
4980
4989
|
turn: this._turn,
|
|
4981
|
-
role: "
|
|
4982
|
-
content:
|
|
4990
|
+
role: "tool_start",
|
|
4991
|
+
content: "",
|
|
4992
|
+
toolName: call.function?.name ?? "",
|
|
4993
|
+
toolArgs: call.function?.arguments ?? "{}"
|
|
4994
|
+
};
|
|
4995
|
+
}
|
|
4996
|
+
const settled = await Promise.allSettled(chunk.map((c) => this.runOneToolCall(c, signal)));
|
|
4997
|
+
for (let k = 0; k < chunk.length; k++) {
|
|
4998
|
+
const call = chunk[k];
|
|
4999
|
+
const name = call.function?.name ?? "";
|
|
5000
|
+
const args = call.function?.arguments ?? "{}";
|
|
5001
|
+
const s = settled[k];
|
|
5002
|
+
let result;
|
|
5003
|
+
let preWarnings = [];
|
|
5004
|
+
let postWarnings = [];
|
|
5005
|
+
if (s.status === "fulfilled") {
|
|
5006
|
+
preWarnings = s.value.preWarnings;
|
|
5007
|
+
postWarnings = s.value.postWarnings;
|
|
5008
|
+
result = s.value.result;
|
|
5009
|
+
} else {
|
|
5010
|
+
const err = s.reason instanceof Error ? s.reason : new Error(String(s.reason));
|
|
5011
|
+
result = JSON.stringify({ error: `${err.name}: ${err.message}` });
|
|
5012
|
+
}
|
|
5013
|
+
for (const w of preWarnings) yield w;
|
|
5014
|
+
for (const w of postWarnings) yield w;
|
|
5015
|
+
this.appendAndPersist({
|
|
5016
|
+
role: "tool",
|
|
5017
|
+
tool_call_id: call.id ?? "",
|
|
5018
|
+
name,
|
|
5019
|
+
content: result
|
|
5020
|
+
});
|
|
5021
|
+
if (this.noteToolFailureSignal(result)) {
|
|
5022
|
+
yield {
|
|
5023
|
+
turn: this._turn,
|
|
5024
|
+
role: "warning",
|
|
5025
|
+
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.`
|
|
5026
|
+
};
|
|
5027
|
+
}
|
|
5028
|
+
yield {
|
|
5029
|
+
turn: this._turn,
|
|
5030
|
+
role: "tool",
|
|
5031
|
+
content: result,
|
|
5032
|
+
toolName: name,
|
|
5033
|
+
toolArgs: args
|
|
4983
5034
|
};
|
|
4984
5035
|
}
|
|
4985
|
-
yield {
|
|
4986
|
-
turn: this._turn,
|
|
4987
|
-
role: "tool",
|
|
4988
|
-
content: result,
|
|
4989
|
-
toolName: name,
|
|
4990
|
-
toolArgs: args
|
|
4991
|
-
};
|
|
4992
5036
|
}
|
|
4993
5037
|
}
|
|
4994
5038
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "budget" });
|
|
@@ -5633,6 +5677,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
5633
5677
|
};
|
|
5634
5678
|
registry.register({
|
|
5635
5679
|
name: "read_file",
|
|
5680
|
+
parallelSafe: true,
|
|
5636
5681
|
description: `Read a file under the sandbox root. To save context, PREFER to scope the read instead of pulling the whole file:
|
|
5637
5682
|
- head: N \u2192 first N lines (imports, public API, small configs)
|
|
5638
5683
|
- tail: N \u2192 last N lines (recently-added code, log tails)
|
|
@@ -5716,6 +5761,7 @@ ${slice.join("\n")}`;
|
|
|
5716
5761
|
});
|
|
5717
5762
|
registry.register({
|
|
5718
5763
|
name: "list_directory",
|
|
5764
|
+
parallelSafe: true,
|
|
5719
5765
|
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.",
|
|
5720
5766
|
readOnly: true,
|
|
5721
5767
|
parameters: {
|
|
@@ -5736,6 +5782,7 @@ ${slice.join("\n")}`;
|
|
|
5736
5782
|
});
|
|
5737
5783
|
registry.register({
|
|
5738
5784
|
name: "directory_tree",
|
|
5785
|
+
parallelSafe: true,
|
|
5739
5786
|
description: `Recursively list entries in a directory. Shows indented tree structure with directories marked '/'. Budget-aware by default:
|
|
5740
5787
|
- 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.
|
|
5741
5788
|
- Skips ${[...SKIP_DIR_NAMES].sort().join(", ")} unless include_deps:true. Traversing into node_modules / .git / dist is almost always token-waste.
|
|
@@ -5814,6 +5861,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
5814
5861
|
});
|
|
5815
5862
|
registry.register({
|
|
5816
5863
|
name: "search_files",
|
|
5864
|
+
parallelSafe: true,
|
|
5817
5865
|
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.",
|
|
5818
5866
|
readOnly: true,
|
|
5819
5867
|
parameters: {
|
|
@@ -5839,6 +5887,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
5839
5887
|
});
|
|
5840
5888
|
registry.register({
|
|
5841
5889
|
name: "search_content",
|
|
5890
|
+
parallelSafe: true,
|
|
5842
5891
|
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.",
|
|
5843
5892
|
readOnly: true,
|
|
5844
5893
|
parameters: {
|
|
@@ -5881,6 +5930,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
5881
5930
|
});
|
|
5882
5931
|
registry.register({
|
|
5883
5932
|
name: "get_file_info",
|
|
5933
|
+
parallelSafe: true,
|
|
5884
5934
|
description: "Stat a path under the sandbox root. Returns type (file|directory|symlink), size in bytes, mtime in ISO-8601.",
|
|
5885
5935
|
readOnly: true,
|
|
5886
5936
|
parameters: {
|
|
@@ -6061,6 +6111,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
6061
6111
|
name: "recall_memory",
|
|
6062
6112
|
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.",
|
|
6063
6113
|
readOnly: true,
|
|
6114
|
+
parallelSafe: true,
|
|
6064
6115
|
parameters: {
|
|
6065
6116
|
type: "object",
|
|
6066
6117
|
properties: {
|
|
@@ -6372,7 +6423,52 @@ function registerPlanTool(registry, opts = {}) {
|
|
|
6372
6423
|
return registry;
|
|
6373
6424
|
}
|
|
6374
6425
|
|
|
6426
|
+
// src/tools/subagent-types.ts
|
|
6427
|
+
var EXPLORE_SYSTEM = `You are an exploration subagent. Wide-net read-only investigation; return one distilled answer.
|
|
6428
|
+
|
|
6429
|
+
How to operate:
|
|
6430
|
+
- Read-only tools only (read_file, search_files, search_content, directory_tree, list_directory, get_file_info).
|
|
6431
|
+
- For "find all places that call / reference / use X" \u2014 use search_content (content grep), NOT search_files (which only matches names).
|
|
6432
|
+
- Cast a wide net first to map the territory, then read the 3-10 most relevant files in full. Stop as soon as you can answer.
|
|
6433
|
+
- The parent does not see your tool calls \u2014 over-exploration is pure waste.
|
|
6434
|
+
|
|
6435
|
+
Final answer:
|
|
6436
|
+
- One paragraph or short bullets; lead with the conclusion.
|
|
6437
|
+
- Cite file:line ranges when they back the claim.
|
|
6438
|
+
- No follow-up offers, no "let me know if you need more" \u2014 the parent will ask again.
|
|
6439
|
+
|
|
6440
|
+
${NEGATIVE_CLAIM_RULE}
|
|
6441
|
+
|
|
6442
|
+
${TUI_FORMATTING_RULES}`;
|
|
6443
|
+
var VERIFY_SYSTEM = `You are a verify subagent. Narrow check \u2014 return YES / NO / INCONCLUSIVE with evidence. Do not expand scope.
|
|
6444
|
+
|
|
6445
|
+
How to operate:
|
|
6446
|
+
- Read only what's needed to verify the specific claim. No exploration past the claim.
|
|
6447
|
+
- Use search_content / read_file to confirm the exact behavior, type, or call site in question.
|
|
6448
|
+
- Cap at 6-8 tool calls. If you can't verify in that, return INCONCLUSIVE plus what's missing.
|
|
6449
|
+
|
|
6450
|
+
Final answer:
|
|
6451
|
+
- Lead with VERIFIED / NOT VERIFIED / INCONCLUSIVE.
|
|
6452
|
+
- Cite file:line for the evidence.
|
|
6453
|
+
- One paragraph or a few bullets. No follow-up offers.
|
|
6454
|
+
|
|
6455
|
+
${NEGATIVE_CLAIM_RULE}
|
|
6456
|
+
|
|
6457
|
+
${TUI_FORMATTING_RULES}`;
|
|
6458
|
+
var TYPES = {
|
|
6459
|
+
explore: { system: EXPLORE_SYSTEM, maxToolIters: 20 },
|
|
6460
|
+
verify: { system: VERIFY_SYSTEM, maxToolIters: 8 }
|
|
6461
|
+
};
|
|
6462
|
+
var SUBAGENT_TYPE_NAMES = Object.freeze(
|
|
6463
|
+
Object.keys(TYPES)
|
|
6464
|
+
);
|
|
6465
|
+
|
|
6375
6466
|
// src/tools/subagent.ts
|
|
6467
|
+
var runIdCounter = 0;
|
|
6468
|
+
function nextRunId() {
|
|
6469
|
+
runIdCounter++;
|
|
6470
|
+
return `sub-${runIdCounter.toString(36)}`;
|
|
6471
|
+
}
|
|
6376
6472
|
var DEFAULT_SUBAGENT_SYSTEM = `You are a Reasonix subagent. The parent agent spawned you to handle one focused subtask, then return.
|
|
6377
6473
|
|
|
6378
6474
|
Rules:
|
|
@@ -6399,16 +6495,53 @@ async function spawnSubagent(opts) {
|
|
|
6399
6495
|
const sink = opts.sink;
|
|
6400
6496
|
const skillName = opts.skillName;
|
|
6401
6497
|
const startedAt = Date.now();
|
|
6498
|
+
const runId = nextRunId();
|
|
6402
6499
|
const taskPreview = opts.task.length > 30 ? `${opts.task.slice(0, 30)}\u2026` : opts.task;
|
|
6403
6500
|
sink?.current?.({
|
|
6404
6501
|
kind: "start",
|
|
6502
|
+
runId,
|
|
6405
6503
|
task: taskPreview,
|
|
6406
6504
|
skillName,
|
|
6407
6505
|
model: model2,
|
|
6408
6506
|
iter: 0,
|
|
6409
6507
|
elapsedMs: 0
|
|
6410
6508
|
});
|
|
6411
|
-
|
|
6509
|
+
if (opts.allowedTools) {
|
|
6510
|
+
const missing = opts.allowedTools.filter((n) => !opts.parentRegistry.has(n));
|
|
6511
|
+
if (missing.length > 0) {
|
|
6512
|
+
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.`;
|
|
6513
|
+
sink?.current?.({
|
|
6514
|
+
kind: "end",
|
|
6515
|
+
runId,
|
|
6516
|
+
task: taskPreview,
|
|
6517
|
+
skillName,
|
|
6518
|
+
model: model2,
|
|
6519
|
+
iter: 0,
|
|
6520
|
+
elapsedMs: Date.now() - startedAt,
|
|
6521
|
+
error: errorMessage2,
|
|
6522
|
+
turns: 0,
|
|
6523
|
+
costUsd: 0,
|
|
6524
|
+
usage: new Usage()
|
|
6525
|
+
});
|
|
6526
|
+
return {
|
|
6527
|
+
success: false,
|
|
6528
|
+
output: "",
|
|
6529
|
+
error: errorMessage2,
|
|
6530
|
+
turns: 0,
|
|
6531
|
+
toolIters: 0,
|
|
6532
|
+
elapsedMs: Date.now() - startedAt,
|
|
6533
|
+
costUsd: 0,
|
|
6534
|
+
model: model2,
|
|
6535
|
+
skillName,
|
|
6536
|
+
usage: new Usage()
|
|
6537
|
+
};
|
|
6538
|
+
}
|
|
6539
|
+
}
|
|
6540
|
+
const childTools = opts.allowedTools ? forkRegistryWithAllowList(
|
|
6541
|
+
opts.parentRegistry,
|
|
6542
|
+
new Set(opts.allowedTools),
|
|
6543
|
+
NEVER_INHERITED_TOOLS
|
|
6544
|
+
) : forkRegistryExcluding(opts.parentRegistry, NEVER_INHERITED_TOOLS);
|
|
6412
6545
|
const childPrefix = new ImmutablePrefix({
|
|
6413
6546
|
system: opts.system,
|
|
6414
6547
|
toolSpecs: childTools.specs()
|
|
@@ -6441,12 +6574,13 @@ async function spawnSubagent(opts) {
|
|
|
6441
6574
|
let summarisingEmitted = false;
|
|
6442
6575
|
try {
|
|
6443
6576
|
for await (const ev of childLoop.step(opts.task)) {
|
|
6444
|
-
sink?.current?.({ kind: "inner", task: taskPreview, skillName, model: model2, inner: ev });
|
|
6577
|
+
sink?.current?.({ kind: "inner", runId, task: taskPreview, skillName, model: model2, inner: ev });
|
|
6445
6578
|
if (ev.role === "tool") {
|
|
6446
6579
|
toolIter++;
|
|
6447
6580
|
summarisingEmitted = false;
|
|
6448
6581
|
sink?.current?.({
|
|
6449
6582
|
kind: "progress",
|
|
6583
|
+
runId,
|
|
6450
6584
|
task: taskPreview,
|
|
6451
6585
|
skillName,
|
|
6452
6586
|
model: model2,
|
|
@@ -6458,6 +6592,7 @@ async function spawnSubagent(opts) {
|
|
|
6458
6592
|
summarisingEmitted = true;
|
|
6459
6593
|
sink?.current?.({
|
|
6460
6594
|
kind: "phase",
|
|
6595
|
+
runId,
|
|
6461
6596
|
task: taskPreview,
|
|
6462
6597
|
skillName,
|
|
6463
6598
|
model: model2,
|
|
@@ -6494,6 +6629,7 @@ async function spawnSubagent(opts) {
|
|
|
6494
6629
|
[\u2026truncated ${final.length - maxResultChars} chars; ask the subagent for a tighter summary if you need more.]` : final;
|
|
6495
6630
|
sink?.current?.({
|
|
6496
6631
|
kind: "end",
|
|
6632
|
+
runId,
|
|
6497
6633
|
task: taskPreview,
|
|
6498
6634
|
skillName,
|
|
6499
6635
|
model: model2,
|
|
@@ -6560,6 +6696,19 @@ function forkRegistryExcluding(parent, exclude) {
|
|
|
6560
6696
|
if (parent.planMode) child.setPlanMode(true);
|
|
6561
6697
|
return child;
|
|
6562
6698
|
}
|
|
6699
|
+
function forkRegistryWithAllowList(parent, allow, alsoExclude) {
|
|
6700
|
+
const child = new ToolRegistry();
|
|
6701
|
+
for (const spec of parent.specs()) {
|
|
6702
|
+
const name = spec.function.name;
|
|
6703
|
+
if (!allow.has(name)) continue;
|
|
6704
|
+
if (alsoExclude.has(name)) continue;
|
|
6705
|
+
const def2 = parent.get(name);
|
|
6706
|
+
if (!def2) continue;
|
|
6707
|
+
child.register(def2);
|
|
6708
|
+
}
|
|
6709
|
+
if (parent.planMode) child.setPlanMode(true);
|
|
6710
|
+
return child;
|
|
6711
|
+
}
|
|
6563
6712
|
|
|
6564
6713
|
// src/tools/shell.ts
|
|
6565
6714
|
import * as pathMod7 from "path";
|
|
@@ -7831,6 +7980,7 @@ function registerShellTools(registry, opts) {
|
|
|
7831
7980
|
name: "job_output",
|
|
7832
7981
|
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.",
|
|
7833
7982
|
readOnly: true,
|
|
7983
|
+
parallelSafe: true,
|
|
7834
7984
|
parameters: {
|
|
7835
7985
|
type: "object",
|
|
7836
7986
|
properties: {
|
|
@@ -7875,6 +8025,7 @@ function registerShellTools(registry, opts) {
|
|
|
7875
8025
|
name: "list_jobs",
|
|
7876
8026
|
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.",
|
|
7877
8027
|
readOnly: true,
|
|
8028
|
+
parallelSafe: true,
|
|
7878
8029
|
parameters: { type: "object", properties: {} },
|
|
7879
8030
|
fn: async () => {
|
|
7880
8031
|
const all = jobs2.list();
|
|
@@ -8132,6 +8283,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
8132
8283
|
name: "web_search",
|
|
8133
8284
|
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.",
|
|
8134
8285
|
readOnly: true,
|
|
8286
|
+
parallelSafe: true,
|
|
8135
8287
|
parameters: {
|
|
8136
8288
|
type: "object",
|
|
8137
8289
|
properties: {
|
|
@@ -8155,6 +8307,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
8155
8307
|
name: "web_fetch",
|
|
8156
8308
|
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.",
|
|
8157
8309
|
readOnly: true,
|
|
8310
|
+
parallelSafe: true,
|
|
8158
8311
|
parameters: {
|
|
8159
8312
|
type: "object",
|
|
8160
8313
|
properties: {
|
|
@@ -15452,6 +15605,7 @@ async function registerSemanticSearchTool(registry, opts) {
|
|
|
15452
15605
|
name: "semantic_search",
|
|
15453
15606
|
description: "FIRST CHOICE for descriptive queries. Use this BEFORE search_content (grep) when the user describes WHAT code does ('where do we handle X', 'which file owns Y', 'how does Z work', 'find the logic that \u2026'). Returns ranked snippets ordered by semantic relevance \u2014 finds the right file even when your description shares no words with the code. Falls back to search_content / search_files only for: exact identifiers, regex patterns, or counting occurrences of a known token. If your first instinct is grep on a paraphrased question, you are wrong \u2014 try semantic_search first.",
|
|
15454
15607
|
readOnly: true,
|
|
15608
|
+
parallelSafe: true,
|
|
15455
15609
|
parameters: {
|
|
15456
15610
|
type: "object",
|
|
15457
15611
|
properties: {
|
|
@@ -16830,6 +16984,7 @@ function registerSkillTools(registry, opts = {}) {
|
|
|
16830
16984
|
name: "run_skill",
|
|
16831
16985
|
description: "Invoke a playbook from the Skills index pinned in the system prompt. Each entry is a self-contained instruction block. Pass `name` as the BARE skill identifier (e.g. 'explore'), NOT the `[\u{1F9EC} subagent]` tag that appears after it in the index. Entries tagged `[\u{1F9EC} subagent]` spawn an isolated subagent \u2014 only the final distilled answer comes back, the model's tool calls + reasoning during the run never enter your context. Plain skills are inlined: the body becomes a tool result you read and follow. For subagent skills, supply 'arguments' describing the concrete task \u2014 they'll be the only context the subagent has.",
|
|
16832
16986
|
readOnly: true,
|
|
16987
|
+
parallelSafe: true,
|
|
16833
16988
|
parameters: {
|
|
16834
16989
|
type: "object",
|
|
16835
16990
|
properties: {
|
|
@@ -24998,9 +25153,7 @@ function subagentPhaseLabel(phase, iter, elapsedMs) {
|
|
|
24998
25153
|
if (iter === 0) return "thinking\u2026";
|
|
24999
25154
|
return "working through tools\u2026";
|
|
25000
25155
|
}
|
|
25001
|
-
function SubagentRow({
|
|
25002
|
-
activity
|
|
25003
|
-
}) {
|
|
25156
|
+
function SubagentRow({ activity }) {
|
|
25004
25157
|
useTick();
|
|
25005
25158
|
const seconds = (activity.elapsedMs / 1e3).toFixed(1);
|
|
25006
25159
|
const phase = subagentPhaseLabel(activity.phase, activity.iter, activity.elapsedMs);
|
|
@@ -25021,6 +25174,46 @@ function SubagentRow({
|
|
|
25021
25174
|
}
|
|
25022
25175
|
), /* @__PURE__ */ React58.createElement(Text2, { color: FG.faint }, "task ", /* @__PURE__ */ React58.createElement(Text2, { color: FG.sub }, activity.task)), /* @__PURE__ */ React58.createElement(Text2, { color: FG.faint }, "last ", last ? /* @__PURE__ */ React58.createElement(React58.Fragment, null, /* @__PURE__ */ React58.createElement(Text2, { color: last.color }, `${last.glyph} `), /* @__PURE__ */ React58.createElement(Text2, { color: FG.body }, last.label), last.meta ? /* @__PURE__ */ React58.createElement(Text2, { color: FG.faint }, ` ${last.meta}`) : null) : /* @__PURE__ */ React58.createElement(Text2, { color: FG.faint }, "queued\u2026")), /* @__PURE__ */ React58.createElement(Text2, { color: TONE.brand }, "\u25B6 ", phase));
|
|
25023
25176
|
}
|
|
25177
|
+
function SubagentLiveStack({
|
|
25178
|
+
activities,
|
|
25179
|
+
max = 3
|
|
25180
|
+
}) {
|
|
25181
|
+
const tick = useTick();
|
|
25182
|
+
if (activities.length === 0) return null;
|
|
25183
|
+
if (activities.length === 1) return /* @__PURE__ */ React58.createElement(SubagentRow, { activity: activities[0] });
|
|
25184
|
+
const visible = activities.slice(0, max);
|
|
25185
|
+
const overflow = activities.length - visible.length;
|
|
25186
|
+
const summarising = activities.filter((a) => a.phase === "summarising").length;
|
|
25187
|
+
const metaParts = [`${activities.length} running`];
|
|
25188
|
+
if (summarising > 0) metaParts.push(`${summarising} summarising`);
|
|
25189
|
+
return /* @__PURE__ */ React58.createElement(Card, { tone: CARD.subagent.color }, /* @__PURE__ */ React58.createElement(
|
|
25190
|
+
CardHeader,
|
|
25191
|
+
{
|
|
25192
|
+
glyph: "\u232C",
|
|
25193
|
+
tone: CARD.subagent.color,
|
|
25194
|
+
title: "subagents",
|
|
25195
|
+
titleColor: PILL_SECTION.plan.fg,
|
|
25196
|
+
titleBg: PILL_SECTION.plan.bg,
|
|
25197
|
+
subtitle: metaParts.join(" \xB7 "),
|
|
25198
|
+
right: /* @__PURE__ */ React58.createElement(Spinner, { kind: "braille", color: CARD.subagent.color })
|
|
25199
|
+
}
|
|
25200
|
+
), visible.map((a, i) => /* @__PURE__ */ React58.createElement(CompactSubagentLine, { key: a.runId, activity: a, tick, index: i })), overflow > 0 ? /* @__PURE__ */ React58.createElement(Text2, { color: FG.faint }, ` +${overflow} more running\u2026`) : null);
|
|
25201
|
+
}
|
|
25202
|
+
function CompactSubagentLine({
|
|
25203
|
+
activity,
|
|
25204
|
+
tick,
|
|
25205
|
+
index
|
|
25206
|
+
}) {
|
|
25207
|
+
const summarising = activity.phase === "summarising";
|
|
25208
|
+
const spinnerFrame = SPINNER_FRAMES[(tick + index) % SPINNER_FRAMES.length] ?? "\xB7";
|
|
25209
|
+
const glyph = summarising ? "\u25B6" : spinnerFrame;
|
|
25210
|
+
const glyphColor = summarising ? TONE.brand : CARD.subagent.color;
|
|
25211
|
+
const seconds = (activity.elapsedMs / 1e3).toFixed(1).padStart(5);
|
|
25212
|
+
const title = activity.skillName ?? truncate3(activity.task, 28);
|
|
25213
|
+
const titlePadded = title.padEnd(28);
|
|
25214
|
+
const last = activity.lastInner;
|
|
25215
|
+
return /* @__PURE__ */ React58.createElement(Box2, { flexDirection: "row" }, /* @__PURE__ */ React58.createElement(Text2, { color: glyphColor, bold: true }, ` ${glyph} `), /* @__PURE__ */ React58.createElement(Text2, { color: FG.body }, titlePadded), /* @__PURE__ */ React58.createElement(Text2, { color: FG.faint }, ` iter ${String(activity.iter).padStart(2)} \xB7 ${seconds}s \xB7 `), last ? /* @__PURE__ */ React58.createElement(React58.Fragment, null, /* @__PURE__ */ React58.createElement(Text2, { color: last.color }, `${last.glyph} `), /* @__PURE__ */ React58.createElement(Text2, { color: FG.body }, truncate3(last.label, 18)), last.meta ? /* @__PURE__ */ React58.createElement(Text2, { color: FG.faint }, ` ${last.meta}`) : null) : /* @__PURE__ */ React58.createElement(Text2, { color: FG.faint }, "queued\u2026"));
|
|
25216
|
+
}
|
|
25024
25217
|
function truncate3(text, max) {
|
|
25025
25218
|
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
25026
25219
|
}
|
|
@@ -28873,7 +29066,7 @@ function useSubagent({
|
|
|
28873
29066
|
log,
|
|
28874
29067
|
getWalletCurrency
|
|
28875
29068
|
}) {
|
|
28876
|
-
const [
|
|
29069
|
+
const [activities, setActivities] = useState17([]);
|
|
28877
29070
|
const sinkRef = useRef8({ current: null });
|
|
28878
29071
|
const getWalletCurrencyRef = useRef8(getWalletCurrency);
|
|
28879
29072
|
useEffect13(() => {
|
|
@@ -28882,24 +29075,11 @@ function useSubagent({
|
|
|
28882
29075
|
useEffect13(() => {
|
|
28883
29076
|
sinkRef.current.current = (ev) => {
|
|
28884
29077
|
if (ev.kind === "start") {
|
|
28885
|
-
|
|
28886
|
-
|
|
28887
|
-
|
|
28888
|
-
|
|
28889
|
-
|
|
28890
|
-
model: ev.model,
|
|
28891
|
-
phase: "exploring",
|
|
28892
|
-
lastInner: null
|
|
28893
|
-
});
|
|
28894
|
-
return;
|
|
28895
|
-
}
|
|
28896
|
-
if (ev.kind === "progress") {
|
|
28897
|
-
setActivity(
|
|
28898
|
-
(prev) => prev ? {
|
|
28899
|
-
...prev,
|
|
28900
|
-
iter: ev.iter ?? prev.iter,
|
|
28901
|
-
elapsedMs: ev.elapsedMs ?? prev.elapsedMs
|
|
28902
|
-
} : {
|
|
29078
|
+
setActivities((prev) => {
|
|
29079
|
+
if (prev.some((a) => a.runId === ev.runId)) return prev;
|
|
29080
|
+
const next = {
|
|
29081
|
+
runId: ev.runId,
|
|
29082
|
+
startedAt: Date.now() - (ev.elapsedMs ?? 0),
|
|
28903
29083
|
task: ev.task,
|
|
28904
29084
|
iter: ev.iter ?? 0,
|
|
28905
29085
|
elapsedMs: ev.elapsedMs ?? 0,
|
|
@@ -28907,45 +29087,59 @@ function useSubagent({
|
|
|
28907
29087
|
model: ev.model,
|
|
28908
29088
|
phase: "exploring",
|
|
28909
29089
|
lastInner: null
|
|
28910
|
-
}
|
|
28911
|
-
|
|
28912
|
-
|
|
28913
|
-
}
|
|
28914
|
-
if (ev.kind === "phase") {
|
|
28915
|
-
setActivity((prev) => prev ? { ...prev, phase: ev.phase } : prev);
|
|
29090
|
+
};
|
|
29091
|
+
return [...prev, next];
|
|
29092
|
+
});
|
|
28916
29093
|
return;
|
|
28917
29094
|
}
|
|
28918
|
-
if (ev.kind === "
|
|
28919
|
-
|
|
28920
|
-
|
|
28921
|
-
|
|
29095
|
+
if (ev.kind === "end") {
|
|
29096
|
+
setActivities((prev) => prev.filter((a) => a.runId !== ev.runId));
|
|
29097
|
+
const seconds = ((ev.elapsedMs ?? 0) / 1e3).toFixed(1);
|
|
29098
|
+
const costTail = ev.costUsd !== void 0 && ev.costUsd > 0 ? ` \xB7 ${formatCost(ev.costUsd, getWalletCurrencyRef.current?.())}` : "";
|
|
29099
|
+
const summary = ev.error ? `\u232C subagent "${ev.task}" failed after ${seconds}s \xB7 ${ev.iter ?? 0} tool call(s) \u2014 ${ev.error}` : `\u232C subagent "${ev.task}" done in ${seconds}s \xB7 ${ev.iter ?? 0} tool call(s) \xB7 ${ev.turns ?? 0} turn(s)${costTail}`;
|
|
29100
|
+
log.pushInfo(summary);
|
|
29101
|
+
if (!ev.error && ev.usage && ev.model) {
|
|
29102
|
+
appendUsage({
|
|
29103
|
+
session: session ?? null,
|
|
29104
|
+
model: ev.model,
|
|
29105
|
+
usage: ev.usage,
|
|
29106
|
+
kind: "subagent",
|
|
29107
|
+
subagent: {
|
|
29108
|
+
skillName: ev.skillName,
|
|
29109
|
+
taskPreview: ev.task.slice(0, 60),
|
|
29110
|
+
toolIters: ev.iter ?? 0,
|
|
29111
|
+
durationMs: ev.elapsedMs ?? 0
|
|
29112
|
+
}
|
|
29113
|
+
});
|
|
29114
|
+
}
|
|
28922
29115
|
return;
|
|
28923
29116
|
}
|
|
28924
|
-
|
|
28925
|
-
|
|
28926
|
-
|
|
28927
|
-
|
|
28928
|
-
|
|
28929
|
-
|
|
28930
|
-
|
|
28931
|
-
|
|
28932
|
-
|
|
28933
|
-
usage: ev.usage,
|
|
28934
|
-
kind: "subagent",
|
|
28935
|
-
subagent: {
|
|
28936
|
-
skillName: ev.skillName,
|
|
28937
|
-
taskPreview: ev.task.slice(0, 60),
|
|
28938
|
-
toolIters: ev.iter ?? 0,
|
|
28939
|
-
durationMs: ev.elapsedMs ?? 0
|
|
29117
|
+
setActivities(
|
|
29118
|
+
(prev) => prev.map((a) => {
|
|
29119
|
+
if (a.runId !== ev.runId) return a;
|
|
29120
|
+
if (ev.kind === "progress") {
|
|
29121
|
+
return {
|
|
29122
|
+
...a,
|
|
29123
|
+
iter: ev.iter ?? a.iter,
|
|
29124
|
+
elapsedMs: ev.elapsedMs ?? a.elapsedMs
|
|
29125
|
+
};
|
|
28940
29126
|
}
|
|
28941
|
-
|
|
28942
|
-
|
|
29127
|
+
if (ev.kind === "phase") {
|
|
29128
|
+
return { ...a, phase: ev.phase ?? a.phase };
|
|
29129
|
+
}
|
|
29130
|
+
if (ev.kind === "inner" && ev.inner) {
|
|
29131
|
+
const summary = summariseInner(ev.inner);
|
|
29132
|
+
return summary ? { ...a, lastInner: summary } : a;
|
|
29133
|
+
}
|
|
29134
|
+
return a;
|
|
29135
|
+
})
|
|
29136
|
+
);
|
|
28943
29137
|
};
|
|
28944
29138
|
return () => {
|
|
28945
29139
|
sinkRef.current.current = null;
|
|
28946
29140
|
};
|
|
28947
29141
|
}, [session, log]);
|
|
28948
|
-
return {
|
|
29142
|
+
return { activities, sinkRef };
|
|
28949
29143
|
}
|
|
28950
29144
|
|
|
28951
29145
|
// src/cli/ui/App.tsx
|
|
@@ -29043,7 +29237,7 @@ function AppInner({
|
|
|
29043
29237
|
};
|
|
29044
29238
|
}, [stdout4]);
|
|
29045
29239
|
const walletCurrencyRef = useRef9(void 0);
|
|
29046
|
-
const {
|
|
29240
|
+
const { activities: subagentActivities, sinkRef: subagentSinkRef } = useSubagent({
|
|
29047
29241
|
session,
|
|
29048
29242
|
log,
|
|
29049
29243
|
getWalletCurrency: () => walletCurrencyRef.current
|
|
@@ -29195,6 +29389,7 @@ function AppInner({
|
|
|
29195
29389
|
// Per-skill model override (frontmatter `model: ...`),
|
|
29196
29390
|
// else falls through to spawnSubagent's default.
|
|
29197
29391
|
model: skill2.model,
|
|
29392
|
+
allowedTools: skill2.allowedTools,
|
|
29198
29393
|
sink: subagentSinkRef.current,
|
|
29199
29394
|
// Stamped onto every event so the TUI sink + usage log can
|
|
29200
29395
|
// attribute the run to a skill without extra bookkeeping.
|
|
@@ -30812,7 +31007,7 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
30812
31007
|
dashboardUrl,
|
|
30813
31008
|
languageVersion
|
|
30814
31009
|
}
|
|
30815
|
-
) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !pendingReviseEditor && !pendingSessionsPicker && !pendingMcpHub && !stagedInput && !pendingEditReview && ongoingTool ? /* @__PURE__ */ React65.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !pendingReviseEditor && !pendingSessionsPicker && !pendingMcpHub && !stagedInput && !pendingEditReview &&
|
|
31010
|
+
) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !pendingReviseEditor && !pendingSessionsPicker && !pendingMcpHub && !stagedInput && !pendingEditReview && ongoingTool ? /* @__PURE__ */ React65.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !pendingReviseEditor && !pendingSessionsPicker && !pendingMcpHub && !stagedInput && !pendingEditReview && subagentActivities.length > 0 ? /* @__PURE__ */ React65.createElement(SubagentLiveStack, { activities: subagentActivities, max: 3 }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !pendingReviseEditor && !pendingSessionsPicker && !pendingMcpHub && !stagedInput && !pendingEditReview && !ongoingTool && statusLine ? /* @__PURE__ */ React65.createElement(ThinkingRow, { text: statusLine }) : null, !PLAIN_UI && undoBanner && !pendingShell && !pendingPlan && !pendingReviseEditor && !pendingSessionsPicker && !pendingMcpHub && !stagedInput && !pendingEditReview && !pendingChoice && !stagedChoiceCustom && !pendingRevision && !stagedCheckpointRevise && !pendingCheckpoint ? /* @__PURE__ */ React65.createElement(UndoBanner, { banner: undoBanner }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !pendingReviseEditor && !pendingSessionsPicker && !pendingMcpHub && !stagedInput && !pendingEditReview && busy && !isStreaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React65.createElement(ThinkingRow, { text: "processing\u2026" }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !pendingReviseEditor && !pendingSessionsPicker && !pendingMcpHub && !stagedInput && !pendingEditReview ? /* @__PURE__ */ React65.createElement(PlanLiveRow, null) : null, /* @__PURE__ */ React65.createElement(ToastRail, null)), stagedInput ? /* @__PURE__ */ React65.createElement(
|
|
30816
31011
|
PlanRefineInput,
|
|
30817
31012
|
{
|
|
30818
31013
|
mode: stagedInput.mode,
|
|
@@ -31416,7 +31611,7 @@ async function chatCommand(opts) {
|
|
|
31416
31611
|
import { readFileSync as readFileSync24 } from "fs";
|
|
31417
31612
|
import { basename as basename2, resolve as resolve10 } from "path";
|
|
31418
31613
|
async function codeCommand(opts = {}) {
|
|
31419
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
31614
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-VF7B6BWR.js");
|
|
31420
31615
|
const rootDir = resolve10(opts.dir ?? process.cwd());
|
|
31421
31616
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename2(rootDir))}`;
|
|
31422
31617
|
const tools = new ToolRegistry();
|