reasonix 0.28.0 → 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/index.js +220 -113
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +127 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -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: {
|
|
@@ -6413,6 +6464,11 @@ var SUBAGENT_TYPE_NAMES = Object.freeze(
|
|
|
6413
6464
|
);
|
|
6414
6465
|
|
|
6415
6466
|
// src/tools/subagent.ts
|
|
6467
|
+
var runIdCounter = 0;
|
|
6468
|
+
function nextRunId() {
|
|
6469
|
+
runIdCounter++;
|
|
6470
|
+
return `sub-${runIdCounter.toString(36)}`;
|
|
6471
|
+
}
|
|
6416
6472
|
var DEFAULT_SUBAGENT_SYSTEM = `You are a Reasonix subagent. The parent agent spawned you to handle one focused subtask, then return.
|
|
6417
6473
|
|
|
6418
6474
|
Rules:
|
|
@@ -6439,9 +6495,11 @@ async function spawnSubagent(opts) {
|
|
|
6439
6495
|
const sink = opts.sink;
|
|
6440
6496
|
const skillName = opts.skillName;
|
|
6441
6497
|
const startedAt = Date.now();
|
|
6498
|
+
const runId = nextRunId();
|
|
6442
6499
|
const taskPreview = opts.task.length > 30 ? `${opts.task.slice(0, 30)}\u2026` : opts.task;
|
|
6443
6500
|
sink?.current?.({
|
|
6444
6501
|
kind: "start",
|
|
6502
|
+
runId,
|
|
6445
6503
|
task: taskPreview,
|
|
6446
6504
|
skillName,
|
|
6447
6505
|
model: model2,
|
|
@@ -6454,6 +6512,7 @@ async function spawnSubagent(opts) {
|
|
|
6454
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.`;
|
|
6455
6513
|
sink?.current?.({
|
|
6456
6514
|
kind: "end",
|
|
6515
|
+
runId,
|
|
6457
6516
|
task: taskPreview,
|
|
6458
6517
|
skillName,
|
|
6459
6518
|
model: model2,
|
|
@@ -6515,12 +6574,13 @@ async function spawnSubagent(opts) {
|
|
|
6515
6574
|
let summarisingEmitted = false;
|
|
6516
6575
|
try {
|
|
6517
6576
|
for await (const ev of childLoop.step(opts.task)) {
|
|
6518
|
-
sink?.current?.({ kind: "inner", task: taskPreview, skillName, model: model2, inner: ev });
|
|
6577
|
+
sink?.current?.({ kind: "inner", runId, task: taskPreview, skillName, model: model2, inner: ev });
|
|
6519
6578
|
if (ev.role === "tool") {
|
|
6520
6579
|
toolIter++;
|
|
6521
6580
|
summarisingEmitted = false;
|
|
6522
6581
|
sink?.current?.({
|
|
6523
6582
|
kind: "progress",
|
|
6583
|
+
runId,
|
|
6524
6584
|
task: taskPreview,
|
|
6525
6585
|
skillName,
|
|
6526
6586
|
model: model2,
|
|
@@ -6532,6 +6592,7 @@ async function spawnSubagent(opts) {
|
|
|
6532
6592
|
summarisingEmitted = true;
|
|
6533
6593
|
sink?.current?.({
|
|
6534
6594
|
kind: "phase",
|
|
6595
|
+
runId,
|
|
6535
6596
|
task: taskPreview,
|
|
6536
6597
|
skillName,
|
|
6537
6598
|
model: model2,
|
|
@@ -6568,6 +6629,7 @@ async function spawnSubagent(opts) {
|
|
|
6568
6629
|
[\u2026truncated ${final.length - maxResultChars} chars; ask the subagent for a tighter summary if you need more.]` : final;
|
|
6569
6630
|
sink?.current?.({
|
|
6570
6631
|
kind: "end",
|
|
6632
|
+
runId,
|
|
6571
6633
|
task: taskPreview,
|
|
6572
6634
|
skillName,
|
|
6573
6635
|
model: model2,
|
|
@@ -7918,6 +7980,7 @@ function registerShellTools(registry, opts) {
|
|
|
7918
7980
|
name: "job_output",
|
|
7919
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.",
|
|
7920
7982
|
readOnly: true,
|
|
7983
|
+
parallelSafe: true,
|
|
7921
7984
|
parameters: {
|
|
7922
7985
|
type: "object",
|
|
7923
7986
|
properties: {
|
|
@@ -7962,6 +8025,7 @@ function registerShellTools(registry, opts) {
|
|
|
7962
8025
|
name: "list_jobs",
|
|
7963
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.",
|
|
7964
8027
|
readOnly: true,
|
|
8028
|
+
parallelSafe: true,
|
|
7965
8029
|
parameters: { type: "object", properties: {} },
|
|
7966
8030
|
fn: async () => {
|
|
7967
8031
|
const all = jobs2.list();
|
|
@@ -8219,6 +8283,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
8219
8283
|
name: "web_search",
|
|
8220
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.",
|
|
8221
8285
|
readOnly: true,
|
|
8286
|
+
parallelSafe: true,
|
|
8222
8287
|
parameters: {
|
|
8223
8288
|
type: "object",
|
|
8224
8289
|
properties: {
|
|
@@ -8242,6 +8307,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
8242
8307
|
name: "web_fetch",
|
|
8243
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.",
|
|
8244
8309
|
readOnly: true,
|
|
8310
|
+
parallelSafe: true,
|
|
8245
8311
|
parameters: {
|
|
8246
8312
|
type: "object",
|
|
8247
8313
|
properties: {
|
|
@@ -15539,6 +15605,7 @@ async function registerSemanticSearchTool(registry, opts) {
|
|
|
15539
15605
|
name: "semantic_search",
|
|
15540
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.",
|
|
15541
15607
|
readOnly: true,
|
|
15608
|
+
parallelSafe: true,
|
|
15542
15609
|
parameters: {
|
|
15543
15610
|
type: "object",
|
|
15544
15611
|
properties: {
|
|
@@ -16917,6 +16984,7 @@ function registerSkillTools(registry, opts = {}) {
|
|
|
16917
16984
|
name: "run_skill",
|
|
16918
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.",
|
|
16919
16986
|
readOnly: true,
|
|
16987
|
+
parallelSafe: true,
|
|
16920
16988
|
parameters: {
|
|
16921
16989
|
type: "object",
|
|
16922
16990
|
properties: {
|
|
@@ -25085,9 +25153,7 @@ function subagentPhaseLabel(phase, iter, elapsedMs) {
|
|
|
25085
25153
|
if (iter === 0) return "thinking\u2026";
|
|
25086
25154
|
return "working through tools\u2026";
|
|
25087
25155
|
}
|
|
25088
|
-
function SubagentRow({
|
|
25089
|
-
activity
|
|
25090
|
-
}) {
|
|
25156
|
+
function SubagentRow({ activity }) {
|
|
25091
25157
|
useTick();
|
|
25092
25158
|
const seconds = (activity.elapsedMs / 1e3).toFixed(1);
|
|
25093
25159
|
const phase = subagentPhaseLabel(activity.phase, activity.iter, activity.elapsedMs);
|
|
@@ -25108,6 +25174,46 @@ function SubagentRow({
|
|
|
25108
25174
|
}
|
|
25109
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));
|
|
25110
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
|
+
}
|
|
25111
25217
|
function truncate3(text, max) {
|
|
25112
25218
|
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
25113
25219
|
}
|
|
@@ -28960,7 +29066,7 @@ function useSubagent({
|
|
|
28960
29066
|
log,
|
|
28961
29067
|
getWalletCurrency
|
|
28962
29068
|
}) {
|
|
28963
|
-
const [
|
|
29069
|
+
const [activities, setActivities] = useState17([]);
|
|
28964
29070
|
const sinkRef = useRef8({ current: null });
|
|
28965
29071
|
const getWalletCurrencyRef = useRef8(getWalletCurrency);
|
|
28966
29072
|
useEffect13(() => {
|
|
@@ -28969,24 +29075,11 @@ function useSubagent({
|
|
|
28969
29075
|
useEffect13(() => {
|
|
28970
29076
|
sinkRef.current.current = (ev) => {
|
|
28971
29077
|
if (ev.kind === "start") {
|
|
28972
|
-
|
|
28973
|
-
|
|
28974
|
-
|
|
28975
|
-
|
|
28976
|
-
|
|
28977
|
-
model: ev.model,
|
|
28978
|
-
phase: "exploring",
|
|
28979
|
-
lastInner: null
|
|
28980
|
-
});
|
|
28981
|
-
return;
|
|
28982
|
-
}
|
|
28983
|
-
if (ev.kind === "progress") {
|
|
28984
|
-
setActivity(
|
|
28985
|
-
(prev) => prev ? {
|
|
28986
|
-
...prev,
|
|
28987
|
-
iter: ev.iter ?? prev.iter,
|
|
28988
|
-
elapsedMs: ev.elapsedMs ?? prev.elapsedMs
|
|
28989
|
-
} : {
|
|
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),
|
|
28990
29083
|
task: ev.task,
|
|
28991
29084
|
iter: ev.iter ?? 0,
|
|
28992
29085
|
elapsedMs: ev.elapsedMs ?? 0,
|
|
@@ -28994,45 +29087,59 @@ function useSubagent({
|
|
|
28994
29087
|
model: ev.model,
|
|
28995
29088
|
phase: "exploring",
|
|
28996
29089
|
lastInner: null
|
|
28997
|
-
}
|
|
28998
|
-
|
|
28999
|
-
|
|
29000
|
-
}
|
|
29001
|
-
if (ev.kind === "phase") {
|
|
29002
|
-
setActivity((prev) => prev ? { ...prev, phase: ev.phase } : prev);
|
|
29090
|
+
};
|
|
29091
|
+
return [...prev, next];
|
|
29092
|
+
});
|
|
29003
29093
|
return;
|
|
29004
29094
|
}
|
|
29005
|
-
if (ev.kind === "
|
|
29006
|
-
|
|
29007
|
-
|
|
29008
|
-
|
|
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
|
+
}
|
|
29009
29115
|
return;
|
|
29010
29116
|
}
|
|
29011
|
-
|
|
29012
|
-
|
|
29013
|
-
|
|
29014
|
-
|
|
29015
|
-
|
|
29016
|
-
|
|
29017
|
-
|
|
29018
|
-
|
|
29019
|
-
|
|
29020
|
-
usage: ev.usage,
|
|
29021
|
-
kind: "subagent",
|
|
29022
|
-
subagent: {
|
|
29023
|
-
skillName: ev.skillName,
|
|
29024
|
-
taskPreview: ev.task.slice(0, 60),
|
|
29025
|
-
toolIters: ev.iter ?? 0,
|
|
29026
|
-
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
|
+
};
|
|
29027
29126
|
}
|
|
29028
|
-
|
|
29029
|
-
|
|
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
|
+
);
|
|
29030
29137
|
};
|
|
29031
29138
|
return () => {
|
|
29032
29139
|
sinkRef.current.current = null;
|
|
29033
29140
|
};
|
|
29034
29141
|
}, [session, log]);
|
|
29035
|
-
return {
|
|
29142
|
+
return { activities, sinkRef };
|
|
29036
29143
|
}
|
|
29037
29144
|
|
|
29038
29145
|
// src/cli/ui/App.tsx
|
|
@@ -29130,7 +29237,7 @@ function AppInner({
|
|
|
29130
29237
|
};
|
|
29131
29238
|
}, [stdout4]);
|
|
29132
29239
|
const walletCurrencyRef = useRef9(void 0);
|
|
29133
|
-
const {
|
|
29240
|
+
const { activities: subagentActivities, sinkRef: subagentSinkRef } = useSubagent({
|
|
29134
29241
|
session,
|
|
29135
29242
|
log,
|
|
29136
29243
|
getWalletCurrency: () => walletCurrencyRef.current
|
|
@@ -30900,7 +31007,7 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
30900
31007
|
dashboardUrl,
|
|
30901
31008
|
languageVersion
|
|
30902
31009
|
}
|
|
30903
|
-
) : 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(
|
|
30904
31011
|
PlanRefineInput,
|
|
30905
31012
|
{
|
|
30906
31013
|
mode: stagedInput.mode,
|