@nexrall/code-core 1.4.66 → 1.4.67
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/agent/agentTypes.d.ts +6 -2
- package/dist/agent/agentTypes.d.ts.map +1 -1
- package/dist/agent/agentTypes.js +13 -4
- package/dist/agent/askOnce.d.ts +12 -0
- package/dist/agent/askOnce.d.ts.map +1 -0
- package/dist/agent/askOnce.js +41 -0
- package/dist/agent/compaction.d.ts +244 -0
- package/dist/agent/compaction.d.ts.map +1 -0
- package/dist/agent/compaction.js +976 -0
- package/dist/agent/fileLocks.d.ts +34 -0
- package/dist/agent/fileLocks.d.ts.map +1 -0
- package/dist/agent/fileLocks.js +114 -0
- package/dist/agent/hooks.d.ts +324 -0
- package/dist/agent/hooks.d.ts.map +1 -0
- package/dist/agent/hooks.js +1228 -0
- package/dist/agent/iterationPolicy.d.ts +121 -0
- package/dist/agent/iterationPolicy.d.ts.map +1 -0
- package/dist/agent/iterationPolicy.js +297 -0
- package/dist/agent/lifecycleHost.d.ts +55 -0
- package/dist/agent/lifecycleHost.d.ts.map +1 -0
- package/dist/agent/lifecycleHost.js +294 -0
- package/dist/agent/loop.d.ts +11 -491
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +417 -3031
- package/dist/agent/planMode.d.ts.map +1 -1
- package/dist/agent/planMode.js +1 -0
- package/dist/agent/sharedTasks.d.ts +7 -0
- package/dist/agent/sharedTasks.d.ts.map +1 -1
- package/dist/agent/sharedTasks.js +16 -0
- package/dist/agent/subAgentBudget.d.ts +65 -0
- package/dist/agent/subAgentBudget.d.ts.map +1 -0
- package/dist/agent/subAgentBudget.js +269 -0
- package/dist/agent/subTask.d.ts +6 -0
- package/dist/agent/subTask.d.ts.map +1 -0
- package/dist/agent/subTask.js +713 -0
- package/dist/agent/subTaskSupport.d.ts +156 -0
- package/dist/agent/subTaskSupport.d.ts.map +1 -0
- package/dist/agent/subTaskSupport.js +409 -0
- package/dist/agent/toolDescriptions.d.ts +3 -0
- package/dist/agent/toolDescriptions.d.ts.map +1 -0
- package/dist/agent/toolDescriptions.js +116 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +17 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/mcp/client.d.ts +104 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +136 -2
- package/dist/mcp/httpClient.d.ts +17 -1
- package/dist/mcp/httpClient.d.ts.map +1 -1
- package/dist/mcp/httpClient.js +120 -19
- package/dist/mcp/manager.d.ts +77 -2
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +275 -9
- package/dist/mcp/sseClient.d.ts +7 -1
- package/dist/mcp/sseClient.d.ts.map +1 -1
- package/dist/mcp/sseClient.js +45 -1
- package/dist/mcp/stats.d.ts +41 -0
- package/dist/mcp/stats.d.ts.map +1 -0
- package/dist/mcp/stats.js +108 -0
- package/dist/permissions/destructive.d.ts +2 -0
- package/dist/permissions/destructive.d.ts.map +1 -1
- package/dist/permissions/destructive.js +6 -2
- package/dist/permissions/destructiveTokens.d.ts +5 -0
- package/dist/permissions/destructiveTokens.d.ts.map +1 -1
- package/dist/permissions/destructiveTokens.js +9 -3
- package/dist/permissions/modePolicy.d.ts.map +1 -1
- package/dist/permissions/modePolicy.js +5 -2
- package/dist/permissions/rules.d.ts +4 -1
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +29 -0
- package/dist/types.d.ts +45 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { Message, ToolResult } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Deepest nesting level allowed to spawn: env → settings.json → default 3.
|
|
4
|
+
*
|
|
5
|
+
* Was a hard-coded 1, i.e. "sub-agents are leaves". That was the right default while the
|
|
6
|
+
* three capability decisions disagreed with each other (see canSpawnSubAgents), but it is
|
|
7
|
+
* no longer where the ecosystem is: Claude Code lifted the no-nesting rule and, after a
|
|
8
|
+
* brief period with it disabled entirely, settled on a configurable default of 3.
|
|
9
|
+
*
|
|
10
|
+
* 3, not 5, deliberately. Depth is a budget you SPEND, not headroom you fill: every level
|
|
11
|
+
* is a context window that receives only a dispatch prompt on the way down and returns
|
|
12
|
+
* only a summary on the way up, so the deeper frames pay full freight to carry less
|
|
13
|
+
* information. 3 covers orchestrator → worker → helper, which is where the observed value
|
|
14
|
+
* is; beyond that latency and token cost tend to exceed the benefit.
|
|
15
|
+
*
|
|
16
|
+
* Depth 1 remains available (`maxSubagentDepth: 1`) for anyone who wants leaves-only.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveMaxSubagentDepth(settingsRaw?: Record<string, unknown>): number;
|
|
19
|
+
/**
|
|
20
|
+
* The ONE explanation for "this run may not spawn a sub-agent", shared by every place
|
|
21
|
+
* that can refuse it, so the same impossibility never gets two different stories.
|
|
22
|
+
*
|
|
23
|
+
* Parameterised on the REASON because the reasons are no longer interchangeable. It used
|
|
24
|
+
* to be a flat constant reading "Sub-agents cannot spawn further sub-agents — this is a
|
|
25
|
+
* structural limit"; with nesting configurable that sentence is now false for most runs,
|
|
26
|
+
* and telling a depth-1 agent its limit is structural when the user could raise it by one
|
|
27
|
+
* line of settings is the same class of misdirection as the "needs a different agent" text
|
|
28
|
+
* this replaced. A refusal has to be accurate about whether it can be lifted, or the model
|
|
29
|
+
* either gives up when it shouldn't or hunts for an escape that doesn't exist.
|
|
30
|
+
*/
|
|
31
|
+
export declare function noSpawnReason(kind: 'depth' | 'denied', limit?: number): string;
|
|
32
|
+
/**
|
|
33
|
+
* Whether a run at `depth` may spawn sub-agents.
|
|
34
|
+
*
|
|
35
|
+
* The single source of truth for THREE things that must agree: the `<available_subagents>`
|
|
36
|
+
* catalogue in the system prompt, whether the backend is asked to send the `task` tool
|
|
37
|
+
* schema at all, and which prompt block teaches delegation. They used to be decided
|
|
38
|
+
* independently, and the result was a sub-agent that got the tool plus instructions to use
|
|
39
|
+
* it but no catalogue — then a permission-gate refusal telling it not to ask for approval.
|
|
40
|
+
*
|
|
41
|
+
* `depth < limit` because the children this run would create land at `depth + 1`; the
|
|
42
|
+
* guard inside runSubTask mirrors it as `depth >= limit`.
|
|
43
|
+
*
|
|
44
|
+
* `limit` is injected rather than read from module state so this stays pure and testable.
|
|
45
|
+
* Callers pass the resolved per-workspace value; it defaults to the built-in for the
|
|
46
|
+
* handful of call sites that have no settings in hand.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* A child sub-agent's effective tool allowlist: its own, narrowed by its parent's.
|
|
50
|
+
*
|
|
51
|
+
* Exported and pure because it is a SECURITY boundary and was previously verified only by
|
|
52
|
+
* grepping the source for the intersection expression — which matched happily while the
|
|
53
|
+
* code threw a TypeError on one of its own four cases. A boundary needs behavioural tests.
|
|
54
|
+
*
|
|
55
|
+
* `null` means "no allowlist" (unrestricted), and it is returned only when BOTH sides say
|
|
56
|
+
* so. The four cases:
|
|
57
|
+
* own + parent → intersection (a child can narrow, never widen)
|
|
58
|
+
* own only → own (the main agent, which has no allowlist, spawning a specialist)
|
|
59
|
+
* parent only → a COPY of parent (an unnamed/general-purpose child inherits the
|
|
60
|
+
* restriction instead of resetting to full access — this is the
|
|
61
|
+
* escalation path, since `general-purpose` declares no tools at all)
|
|
62
|
+
* neither → null
|
|
63
|
+
*
|
|
64
|
+
* The parent-only case must COPY: the caller adds AGENT_MEMORY_TOOL to the returned set,
|
|
65
|
+
* which would otherwise mutate the parent's live allowlist.
|
|
66
|
+
*/
|
|
67
|
+
export declare function intersectAllowlists(own: Set<string> | null, parent: Set<string> | undefined): Set<string> | null;
|
|
68
|
+
export declare function canSpawnSubAgents(depth: number, allowedTools?: Set<string>, limit?: number): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* How long a sub-agent may make NO progress before it is stopped.
|
|
71
|
+
*
|
|
72
|
+
* Precedence matches every other tunable in this file
|
|
73
|
+
* (env → settings.json → default) — it used to be env-ONLY, which meant a project that
|
|
74
|
+
* legitimately needed longer sub-tasks had no way to say so in the file where every
|
|
75
|
+
* other such preference lives, and the limit was invisible to anyone reading settings.
|
|
76
|
+
*
|
|
77
|
+
* Exported for tests: the behaviour that matters (a long-but-productive sub-agent is
|
|
78
|
+
* NOT killed) takes minutes of wall clock to exercise through the real loop.
|
|
79
|
+
*/
|
|
80
|
+
export declare function resolveSubtaskTimeoutMs(settingsRaw: Record<string, unknown>): number;
|
|
81
|
+
/**
|
|
82
|
+
* Thrown by a sub-agent's permission gate when the AGENT DEFINITION forbids a
|
|
83
|
+
* tool — as opposed to the user declining it.
|
|
84
|
+
*
|
|
85
|
+
* The distinction matters to the model, which is why this is an exception rather
|
|
86
|
+
* than a `false`: both used to collapse into "Permission denied by user", so an
|
|
87
|
+
* agent blocked by its own allowlist (very often a mistyped tool name) was told
|
|
88
|
+
* the human had refused. The rational response to that is to ask again, which
|
|
89
|
+
* can never succeed. Carrying a reason lets the tool_result say what is actually
|
|
90
|
+
* true and what to do instead.
|
|
91
|
+
*/
|
|
92
|
+
export declare class ToolNotAllowedError extends Error {
|
|
93
|
+
constructor(message: string);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Reduce a sub-agent's message history to the text its parent should receive.
|
|
97
|
+
*
|
|
98
|
+
* Pure + exported so the salvage rules can be tested without running a real
|
|
99
|
+
* sub-agent (which needs a live model stream and, for the timeout path, ten
|
|
100
|
+
* minutes of wall clock).
|
|
101
|
+
*
|
|
102
|
+
* `preferLast` — the normal completion path — returns the final assistant
|
|
103
|
+
* message, which is the sub-agent's actual answer.
|
|
104
|
+
*
|
|
105
|
+
* `preferLast: false` is the SALVAGE path, used when the sub-agent was cut off.
|
|
106
|
+
* A stopped sub-agent usually has no closing summary at all (it was killed
|
|
107
|
+
* mid-tool-round), so the last assistant message is frequently empty or a
|
|
108
|
+
* fragment. Concatenating what it did produce is far more useful to the parent
|
|
109
|
+
* model than nothing: it can build on the work instead of redoing it.
|
|
110
|
+
*/
|
|
111
|
+
export declare function extractSubTaskText(messages: Message[], preferLast?: boolean): string;
|
|
112
|
+
/** Apply the parent-context cap to a sub-task's text, keeping head + tail. */
|
|
113
|
+
export declare function capSubTaskText(text: string, max?: number): string;
|
|
114
|
+
/**
|
|
115
|
+
* Summarise what a cut-short sub-agent actually accomplished, so the parent model
|
|
116
|
+
* can continue from it rather than starting over.
|
|
117
|
+
*
|
|
118
|
+
* This is the whole point of the salvage path. Previously a timed-out sub-agent
|
|
119
|
+
* returned ONLY an error string: ten minutes of work, dozens of tool calls and
|
|
120
|
+
* any files it wrote were invisible to the parent, which typically responded by
|
|
121
|
+
* re-running the same work from scratch — while the tokens for the discarded run
|
|
122
|
+
* had already been billed in full.
|
|
123
|
+
*/
|
|
124
|
+
export declare function summariseSubTaskProgress(messages: Message[]): string;
|
|
125
|
+
/**
|
|
126
|
+
* The last N tool results from a transcript, newest last, each truncated.
|
|
127
|
+
*
|
|
128
|
+
* Pure + exported so the salvage rules are testable without a real sub-agent.
|
|
129
|
+
*
|
|
130
|
+
* Truncation keeps the HEAD of each result: tool output is overwhelmingly
|
|
131
|
+
* front-loaded (a file starts with its imports, a failing command starts with its
|
|
132
|
+
* error), and a head slice is the half that identifies what was found.
|
|
133
|
+
*/
|
|
134
|
+
export declare function lastToolResults(messages: Message[], count: number, maxChars: number): string;
|
|
135
|
+
export declare const HARD_STOPPED: unique symbol;
|
|
136
|
+
export declare function hardStopGraceMs(): number;
|
|
137
|
+
export declare function raceHardStop<T>(run: Promise<T>, abort: {
|
|
138
|
+
aborted: boolean;
|
|
139
|
+
}): Promise<T | typeof HARD_STOPPED>;
|
|
140
|
+
/** Returned instead of waiting forever for a tool that ignored Stop. */
|
|
141
|
+
export declare const STOP_TIMEOUT_RESULT: ToolResult;
|
|
142
|
+
export declare function stopAwaiting<T>(run: Promise<T>, abort?: {
|
|
143
|
+
aborted: boolean;
|
|
144
|
+
}): Promise<T | typeof STOP_TIMEOUT_RESULT>;
|
|
145
|
+
/** Default turn limit for a sub-agent whose definition sets no `maxTurns`. */
|
|
146
|
+
export declare const DEFAULT_SUBAGENT_MAX_TURNS = 200;
|
|
147
|
+
/** Stop reasons that mean the sub-agent did NOT deliver a finished report. */
|
|
148
|
+
export declare const ABNORMAL_SUBAGENT_STOPS: Set<string>;
|
|
149
|
+
/**
|
|
150
|
+
* Prepended to EVERY sub-agent's instructions (named or not) — Claude Code's
|
|
151
|
+
* "you are a sub-agent; your final message is the report" contract.
|
|
152
|
+
*/
|
|
153
|
+
export declare const SUBAGENT_PREAMBLE: string;
|
|
154
|
+
export declare function formatTokenCount(n: number): string;
|
|
155
|
+
export declare function formatElapsed(ms: number): string;
|
|
156
|
+
//# sourceMappingURL=subTaskSupport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subTaskSupport.d.ts","sourceRoot":"","sources":["../../src/agent/subTaskSupport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAmCpD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAOzF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAe9E;AAED;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EACvB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,GAC9B,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAIpB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAC1B,KAAK,GAAE,MAA+B,GACrC,OAAO,CAMT;AAaD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAOpF;AAMD;;;;;;;;;;GAUG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAQD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,UAAU,UAAO,GAAG,MAAM,CAYjF;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAc,GAAG,MAAM,CAKtE;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAgCpE;AAMD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6B5F;AAqBD,eAAO,MAAM,YAAY,eAAyB,CAAC;AACnD,wBAAgB,eAAe,IAAI,MAAM,CAGxC;AACD,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,CAAC,GAAG,OAAO,YAAY,CAAC,CAW9G;AAED,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,EAAE,UAGjC,CAAC;AAEF,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,CAAC,GAAG,OAAO,mBAAmB,CAAC,CAUtH;AAED,8EAA8E;AAC9E,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB,aAMlC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAIlB,CAAC;AAEb,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAGhD"}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SUBAGENT_PREAMBLE = exports.ABNORMAL_SUBAGENT_STOPS = exports.DEFAULT_SUBAGENT_MAX_TURNS = exports.STOP_TIMEOUT_RESULT = exports.HARD_STOPPED = exports.ToolNotAllowedError = void 0;
|
|
4
|
+
exports.resolveMaxSubagentDepth = resolveMaxSubagentDepth;
|
|
5
|
+
exports.noSpawnReason = noSpawnReason;
|
|
6
|
+
exports.intersectAllowlists = intersectAllowlists;
|
|
7
|
+
exports.canSpawnSubAgents = canSpawnSubAgents;
|
|
8
|
+
exports.resolveSubtaskTimeoutMs = resolveSubtaskTimeoutMs;
|
|
9
|
+
exports.extractSubTaskText = extractSubTaskText;
|
|
10
|
+
exports.capSubTaskText = capSubTaskText;
|
|
11
|
+
exports.summariseSubTaskProgress = summariseSubTaskProgress;
|
|
12
|
+
exports.lastToolResults = lastToolResults;
|
|
13
|
+
exports.hardStopGraceMs = hardStopGraceMs;
|
|
14
|
+
exports.raceHardStop = raceHardStop;
|
|
15
|
+
exports.stopAwaiting = stopAwaiting;
|
|
16
|
+
exports.formatTokenCount = formatTokenCount;
|
|
17
|
+
exports.formatElapsed = formatElapsed;
|
|
18
|
+
const safeSlice_1 = require("../util/safeSlice");
|
|
19
|
+
// ─── Sub-task runner ──────────────────────────────────────────────────────────
|
|
20
|
+
// Nesting depth. The main agent runs at depth 0, the sub-agents it spawns at depth 1.
|
|
21
|
+
//
|
|
22
|
+
// A run at `depth` may spawn when `depth < limit`, so a limit of N means N generations
|
|
23
|
+
// below the main agent. This used to be a hard-coded 1 with the historical note "This is
|
|
24
|
+
// 1, not 2" — that note was about making the CONSTANT agree with a system prompt that
|
|
25
|
+
// promised "sub-agents cannot spawn further sub-agents". Both sides of that agreement have
|
|
26
|
+
// now moved: nesting is a supported, configured capability, and the prompt is generated
|
|
27
|
+
// from the same predicate that enforces it (canSpawnSubAgents), so the two cannot drift
|
|
28
|
+
// again regardless of the value.
|
|
29
|
+
//
|
|
30
|
+
// What made the old value load-bearing was that fan-out had no TOTAL bound — only a
|
|
31
|
+
// per-moment concurrency gate. Depth × fan-out is multiplicative, so raising depth without
|
|
32
|
+
// a session ceiling converts a bounded queue into an unbounded tree. That ceiling
|
|
33
|
+
// (resolveMaxSubagentsPerSession) is what makes a depth > 1 safe to default to.
|
|
34
|
+
const DEFAULT_MAX_TASK_DEPTH = 3;
|
|
35
|
+
/**
|
|
36
|
+
* Hard ceiling on `maxSubagentDepth`, independent of what anyone configures.
|
|
37
|
+
*
|
|
38
|
+
* 5 matches the deepest tier Anthropic shipped for Claude Code. It exists because depth
|
|
39
|
+
* is MULTIPLICATIVE with fan-out: at 4-wide, depth 5 is 4^5 = 1024 possible loops. The
|
|
40
|
+
* per-depth limiter and the session ceiling below are what actually bound that, but a
|
|
41
|
+
* typo'd `maxSubagentDepth: 50` should degrade to "deep" rather than to a fork bomb —
|
|
42
|
+
* same reasoning as the clamp on maxConcurrentSubtasks.
|
|
43
|
+
*/
|
|
44
|
+
const HARD_MAX_TASK_DEPTH = 5;
|
|
45
|
+
/**
|
|
46
|
+
* Deepest nesting level allowed to spawn: env → settings.json → default 3.
|
|
47
|
+
*
|
|
48
|
+
* Was a hard-coded 1, i.e. "sub-agents are leaves". That was the right default while the
|
|
49
|
+
* three capability decisions disagreed with each other (see canSpawnSubAgents), but it is
|
|
50
|
+
* no longer where the ecosystem is: Claude Code lifted the no-nesting rule and, after a
|
|
51
|
+
* brief period with it disabled entirely, settled on a configurable default of 3.
|
|
52
|
+
*
|
|
53
|
+
* 3, not 5, deliberately. Depth is a budget you SPEND, not headroom you fill: every level
|
|
54
|
+
* is a context window that receives only a dispatch prompt on the way down and returns
|
|
55
|
+
* only a summary on the way up, so the deeper frames pay full freight to carry less
|
|
56
|
+
* information. 3 covers orchestrator → worker → helper, which is where the observed value
|
|
57
|
+
* is; beyond that latency and token cost tend to exceed the benefit.
|
|
58
|
+
*
|
|
59
|
+
* Depth 1 remains available (`maxSubagentDepth: 1`) for anyone who wants leaves-only.
|
|
60
|
+
*/
|
|
61
|
+
function resolveMaxSubagentDepth(settingsRaw = {}) {
|
|
62
|
+
const clamp = (n) => Math.max(1, Math.min(Math.floor(n), HARD_MAX_TASK_DEPTH));
|
|
63
|
+
const fromEnv = Number(process.env.NEXRALL_MAX_SUBAGENT_DEPTH);
|
|
64
|
+
if (Number.isFinite(fromEnv) && fromEnv > 0)
|
|
65
|
+
return clamp(fromEnv);
|
|
66
|
+
const fromSettings = Number(settingsRaw.maxSubagentDepth);
|
|
67
|
+
if (Number.isFinite(fromSettings) && fromSettings > 0)
|
|
68
|
+
return clamp(fromSettings);
|
|
69
|
+
return DEFAULT_MAX_TASK_DEPTH;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The ONE explanation for "this run may not spawn a sub-agent", shared by every place
|
|
73
|
+
* that can refuse it, so the same impossibility never gets two different stories.
|
|
74
|
+
*
|
|
75
|
+
* Parameterised on the REASON because the reasons are no longer interchangeable. It used
|
|
76
|
+
* to be a flat constant reading "Sub-agents cannot spawn further sub-agents — this is a
|
|
77
|
+
* structural limit"; with nesting configurable that sentence is now false for most runs,
|
|
78
|
+
* and telling a depth-1 agent its limit is structural when the user could raise it by one
|
|
79
|
+
* line of settings is the same class of misdirection as the "needs a different agent" text
|
|
80
|
+
* this replaced. A refusal has to be accurate about whether it can be lifted, or the model
|
|
81
|
+
* either gives up when it shouldn't or hunts for an escape that doesn't exist.
|
|
82
|
+
*/
|
|
83
|
+
function noSpawnReason(kind, limit) {
|
|
84
|
+
if (kind === 'denied') {
|
|
85
|
+
return ('This sub-agent\'s definition does not grant `task`, so it may not delegate. That is a ' +
|
|
86
|
+
'deliberate restriction on this agent type — do not ask for approval and do not look for ' +
|
|
87
|
+
'a way around it. Do the work with the tools you have, or report back what is missing.');
|
|
88
|
+
}
|
|
89
|
+
return (`Maximum sub-agent nesting depth (${limit ?? DEFAULT_MAX_TASK_DEPTH}) reached, so this run ` +
|
|
90
|
+
'is a leaf and cannot delegate further. Depth is a budget, not a bug: finish this work ' +
|
|
91
|
+
'yourself, or report back so a shallower frame can decide. (The ceiling is ' +
|
|
92
|
+
'"maxSubagentDepth" in .nexrall/settings.json, but raising it mid-task will not help you — ' +
|
|
93
|
+
'it applies from the next session.)');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether a run at `depth` may spawn sub-agents.
|
|
97
|
+
*
|
|
98
|
+
* The single source of truth for THREE things that must agree: the `<available_subagents>`
|
|
99
|
+
* catalogue in the system prompt, whether the backend is asked to send the `task` tool
|
|
100
|
+
* schema at all, and which prompt block teaches delegation. They used to be decided
|
|
101
|
+
* independently, and the result was a sub-agent that got the tool plus instructions to use
|
|
102
|
+
* it but no catalogue — then a permission-gate refusal telling it not to ask for approval.
|
|
103
|
+
*
|
|
104
|
+
* `depth < limit` because the children this run would create land at `depth + 1`; the
|
|
105
|
+
* guard inside runSubTask mirrors it as `depth >= limit`.
|
|
106
|
+
*
|
|
107
|
+
* `limit` is injected rather than read from module state so this stays pure and testable.
|
|
108
|
+
* Callers pass the resolved per-workspace value; it defaults to the built-in for the
|
|
109
|
+
* handful of call sites that have no settings in hand.
|
|
110
|
+
*/
|
|
111
|
+
/**
|
|
112
|
+
* A child sub-agent's effective tool allowlist: its own, narrowed by its parent's.
|
|
113
|
+
*
|
|
114
|
+
* Exported and pure because it is a SECURITY boundary and was previously verified only by
|
|
115
|
+
* grepping the source for the intersection expression — which matched happily while the
|
|
116
|
+
* code threw a TypeError on one of its own four cases. A boundary needs behavioural tests.
|
|
117
|
+
*
|
|
118
|
+
* `null` means "no allowlist" (unrestricted), and it is returned only when BOTH sides say
|
|
119
|
+
* so. The four cases:
|
|
120
|
+
* own + parent → intersection (a child can narrow, never widen)
|
|
121
|
+
* own only → own (the main agent, which has no allowlist, spawning a specialist)
|
|
122
|
+
* parent only → a COPY of parent (an unnamed/general-purpose child inherits the
|
|
123
|
+
* restriction instead of resetting to full access — this is the
|
|
124
|
+
* escalation path, since `general-purpose` declares no tools at all)
|
|
125
|
+
* neither → null
|
|
126
|
+
*
|
|
127
|
+
* The parent-only case must COPY: the caller adds AGENT_MEMORY_TOOL to the returned set,
|
|
128
|
+
* which would otherwise mutate the parent's live allowlist.
|
|
129
|
+
*/
|
|
130
|
+
function intersectAllowlists(own, parent) {
|
|
131
|
+
if (own && parent)
|
|
132
|
+
return new Set([...own].filter((t) => parent.has(t)));
|
|
133
|
+
if (own)
|
|
134
|
+
return own;
|
|
135
|
+
return parent ? new Set(parent) : null;
|
|
136
|
+
}
|
|
137
|
+
function canSpawnSubAgents(depth, allowedTools, limit = DEFAULT_MAX_TASK_DEPTH) {
|
|
138
|
+
if (depth >= limit)
|
|
139
|
+
return false;
|
|
140
|
+
// An allowlist that omits `task` is the other reason a run cannot delegate. No
|
|
141
|
+
// allowlist at all (the main agent, or a general-purpose sub-task) means no
|
|
142
|
+
// restriction from this clause — the depth check above still applies.
|
|
143
|
+
return allowedTools ? allowedTools.has('task') : true;
|
|
144
|
+
}
|
|
145
|
+
// A sub-agent that stalls (hung tool, model provider stuck, infinite tool-call
|
|
146
|
+
// loop bypassing the iteration budget somehow) used to have NO ceiling of its
|
|
147
|
+
// own — it shared only the PARENT's overall iteration budget, so a genuinely
|
|
148
|
+
// stuck sub-agent could silently occupy the whole run with no distinct signal
|
|
149
|
+
// pointing at it specifically. Give every sub-task an explicit wall-clock cap:
|
|
150
|
+
// if it hasn't finished by then, fail it clearly instead of hanging the parent
|
|
151
|
+
// turn indefinitely. Overridable via env for slow CI machines / huge sub-tasks.
|
|
152
|
+
const DEFAULT_SUBTASK_TIMEOUT_MS = 10 * 60 * 1000; // 10 min of NO PROGRESS (see the watchdog)
|
|
153
|
+
/**
|
|
154
|
+
* How long a sub-agent may make NO progress before it is stopped.
|
|
155
|
+
*
|
|
156
|
+
* Precedence matches every other tunable in this file
|
|
157
|
+
* (env → settings.json → default) — it used to be env-ONLY, which meant a project that
|
|
158
|
+
* legitimately needed longer sub-tasks had no way to say so in the file where every
|
|
159
|
+
* other such preference lives, and the limit was invisible to anyone reading settings.
|
|
160
|
+
*
|
|
161
|
+
* Exported for tests: the behaviour that matters (a long-but-productive sub-agent is
|
|
162
|
+
* NOT killed) takes minutes of wall clock to exercise through the real loop.
|
|
163
|
+
*/
|
|
164
|
+
function resolveSubtaskTimeoutMs(settingsRaw) {
|
|
165
|
+
const fromEnv = Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS);
|
|
166
|
+
if (Number.isFinite(fromEnv) && fromEnv > 0)
|
|
167
|
+
return Math.floor(fromEnv);
|
|
168
|
+
const raw = settingsRaw.subtaskTimeoutMs;
|
|
169
|
+
const fromSettings = Number(raw);
|
|
170
|
+
if (Number.isFinite(fromSettings) && fromSettings > 0)
|
|
171
|
+
return Math.floor(fromSettings);
|
|
172
|
+
return DEFAULT_SUBTASK_TIMEOUT_MS;
|
|
173
|
+
}
|
|
174
|
+
/** Cap on the text a sub-task hands back, so one verbose sub-agent can't blow up
|
|
175
|
+
* the PARENT's context in a single tool_result. */
|
|
176
|
+
const SUBTASK_MAX = 48000; // chars (~12k tokens)
|
|
177
|
+
/**
|
|
178
|
+
* Thrown by a sub-agent's permission gate when the AGENT DEFINITION forbids a
|
|
179
|
+
* tool — as opposed to the user declining it.
|
|
180
|
+
*
|
|
181
|
+
* The distinction matters to the model, which is why this is an exception rather
|
|
182
|
+
* than a `false`: both used to collapse into "Permission denied by user", so an
|
|
183
|
+
* agent blocked by its own allowlist (very often a mistyped tool name) was told
|
|
184
|
+
* the human had refused. The rational response to that is to ask again, which
|
|
185
|
+
* can never succeed. Carrying a reason lets the tool_result say what is actually
|
|
186
|
+
* true and what to do instead.
|
|
187
|
+
*/
|
|
188
|
+
class ToolNotAllowedError extends Error {
|
|
189
|
+
constructor(message) {
|
|
190
|
+
super(message);
|
|
191
|
+
this.name = 'ToolNotAllowedError';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
exports.ToolNotAllowedError = ToolNotAllowedError;
|
|
195
|
+
// sliceSafeEnd/sliceSafeStart moved to ../util/safeSlice so every module that
|
|
196
|
+
// truncates model-facing/wire-facing text (loop.ts and tools/executor.ts) shares
|
|
197
|
+
// ONE surrogate-safe implementation instead of drifting copies. See that file's
|
|
198
|
+
// header for why raw `.slice()` on these strings caused a 400
|
|
199
|
+
// "no low surrogate in string" from the Anthropic API.
|
|
200
|
+
/**
|
|
201
|
+
* Reduce a sub-agent's message history to the text its parent should receive.
|
|
202
|
+
*
|
|
203
|
+
* Pure + exported so the salvage rules can be tested without running a real
|
|
204
|
+
* sub-agent (which needs a live model stream and, for the timeout path, ten
|
|
205
|
+
* minutes of wall clock).
|
|
206
|
+
*
|
|
207
|
+
* `preferLast` — the normal completion path — returns the final assistant
|
|
208
|
+
* message, which is the sub-agent's actual answer.
|
|
209
|
+
*
|
|
210
|
+
* `preferLast: false` is the SALVAGE path, used when the sub-agent was cut off.
|
|
211
|
+
* A stopped sub-agent usually has no closing summary at all (it was killed
|
|
212
|
+
* mid-tool-round), so the last assistant message is frequently empty or a
|
|
213
|
+
* fragment. Concatenating what it did produce is far more useful to the parent
|
|
214
|
+
* model than nothing: it can build on the work instead of redoing it.
|
|
215
|
+
*/
|
|
216
|
+
function extractSubTaskText(messages, preferLast = true) {
|
|
217
|
+
const assistants = messages.filter((m) => m.role === 'assistant');
|
|
218
|
+
const textOf = (m) => (m?.content ?? [])
|
|
219
|
+
.filter((b) => b.type === 'text' && typeof b.text === 'string')
|
|
220
|
+
.map((b) => b.text)
|
|
221
|
+
.join('')
|
|
222
|
+
.trim();
|
|
223
|
+
if (preferLast)
|
|
224
|
+
return textOf(assistants[assistants.length - 1]);
|
|
225
|
+
return assistants.map(textOf).filter(Boolean).join('\n\n').trim();
|
|
226
|
+
}
|
|
227
|
+
/** Apply the parent-context cap to a sub-task's text, keeping head + tail. */
|
|
228
|
+
function capSubTaskText(text, max = SUBTASK_MAX) {
|
|
229
|
+
if (text.length <= max)
|
|
230
|
+
return text;
|
|
231
|
+
const head = (0, safeSlice_1.sliceSafeEnd)(text, Math.floor(max * 0.6));
|
|
232
|
+
const tail = (0, safeSlice_1.sliceSafeStart)(text, text.length - Math.floor(max * 0.4));
|
|
233
|
+
return `${head}\n\n[… sub-task output truncated (${text.length} chars) — kept the beginning and end …]\n\n${tail}`;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Summarise what a cut-short sub-agent actually accomplished, so the parent model
|
|
237
|
+
* can continue from it rather than starting over.
|
|
238
|
+
*
|
|
239
|
+
* This is the whole point of the salvage path. Previously a timed-out sub-agent
|
|
240
|
+
* returned ONLY an error string: ten minutes of work, dozens of tool calls and
|
|
241
|
+
* any files it wrote were invisible to the parent, which typically responded by
|
|
242
|
+
* re-running the same work from scratch — while the tokens for the discarded run
|
|
243
|
+
* had already been billed in full.
|
|
244
|
+
*/
|
|
245
|
+
function summariseSubTaskProgress(messages) {
|
|
246
|
+
const toolNames = [];
|
|
247
|
+
for (const m of messages) {
|
|
248
|
+
if (m.role !== 'assistant' || !Array.isArray(m.content))
|
|
249
|
+
continue;
|
|
250
|
+
for (const b of m.content) {
|
|
251
|
+
if (b?.type === 'tool_use' && typeof b.name === 'string')
|
|
252
|
+
toolNames.push(b.name);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (toolNames.length === 0)
|
|
256
|
+
return '';
|
|
257
|
+
// The FINDINGS, not just the activity log.
|
|
258
|
+
//
|
|
259
|
+
// An inventory of tool names ("read_file ×9, bash ×14") tells the parent that work
|
|
260
|
+
// happened but nothing about what was learned, so it re-derives everything. A stalled
|
|
261
|
+
// research agent's value is almost entirely in what its last few tool calls RETURNED
|
|
262
|
+
// — the file it had just read, the command output it was about to interpret — because
|
|
263
|
+
// its own prose summary is exactly the thing it never got to write.
|
|
264
|
+
const recentFindings = lastToolResults(messages, SALVAGE_RESULT_COUNT, SALVAGE_RESULT_CHARS);
|
|
265
|
+
// Collapse to "name ×N" so a 40-call run reads as a short inventory rather
|
|
266
|
+
// than forty repeated lines of the same tool name.
|
|
267
|
+
const counts = new Map();
|
|
268
|
+
for (const n of toolNames)
|
|
269
|
+
counts.set(n, (counts.get(n) ?? 0) + 1);
|
|
270
|
+
const inventory = [...counts.entries()]
|
|
271
|
+
.sort((a, b) => b[1] - a[1])
|
|
272
|
+
.map(([name, n]) => (n > 1 ? `${name} ×${n}` : name))
|
|
273
|
+
.join(', ');
|
|
274
|
+
const header = `Tool calls completed before it was stopped (${toolNames.length} total): ${inventory}.`;
|
|
275
|
+
return recentFindings
|
|
276
|
+
? `${header}\n\nWhat its most recent tool calls actually returned (use this instead of repeating them):\n${recentFindings}`
|
|
277
|
+
: header;
|
|
278
|
+
}
|
|
279
|
+
/** How many trailing tool results to salvage, and how much of each. */
|
|
280
|
+
const SALVAGE_RESULT_COUNT = 4;
|
|
281
|
+
const SALVAGE_RESULT_CHARS = 2000;
|
|
282
|
+
/**
|
|
283
|
+
* The last N tool results from a transcript, newest last, each truncated.
|
|
284
|
+
*
|
|
285
|
+
* Pure + exported so the salvage rules are testable without a real sub-agent.
|
|
286
|
+
*
|
|
287
|
+
* Truncation keeps the HEAD of each result: tool output is overwhelmingly
|
|
288
|
+
* front-loaded (a file starts with its imports, a failing command starts with its
|
|
289
|
+
* error), and a head slice is the half that identifies what was found.
|
|
290
|
+
*/
|
|
291
|
+
function lastToolResults(messages, count, maxChars) {
|
|
292
|
+
// Map tool_use id → tool name, so a salvaged result can say WHICH tool produced it.
|
|
293
|
+
const nameById = new Map();
|
|
294
|
+
for (const m of messages) {
|
|
295
|
+
if (m.role !== 'assistant' || !Array.isArray(m.content))
|
|
296
|
+
continue;
|
|
297
|
+
for (const b of m.content) {
|
|
298
|
+
if (b?.type === 'tool_use' && b.id && typeof b.name === 'string')
|
|
299
|
+
nameById.set(b.id, b.name);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const out = [];
|
|
303
|
+
// Walk backwards and stop early: only the most recent results are worth the tokens,
|
|
304
|
+
// and a long research run may hold hundreds.
|
|
305
|
+
for (let i = messages.length - 1; i >= 0 && out.length < count; i--) {
|
|
306
|
+
const m = messages[i];
|
|
307
|
+
if (m.role !== 'user' || !Array.isArray(m.content))
|
|
308
|
+
continue;
|
|
309
|
+
for (const b of [...m.content].reverse()) {
|
|
310
|
+
if (out.length >= count)
|
|
311
|
+
break;
|
|
312
|
+
if (b?.type !== 'tool_result')
|
|
313
|
+
continue;
|
|
314
|
+
const text = toolResultText(b);
|
|
315
|
+
if (!text)
|
|
316
|
+
continue;
|
|
317
|
+
const name = nameById.get(String(b.tool_use_id ?? '')) ?? 'tool';
|
|
318
|
+
const body = text.length > maxChars
|
|
319
|
+
? `${(0, safeSlice_1.sliceSafeEnd)(text, maxChars)}\n… [truncated]`
|
|
320
|
+
: text;
|
|
321
|
+
out.push(`• ${name}:\n${body}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return out.reverse().join('\n\n');
|
|
325
|
+
}
|
|
326
|
+
/** Extract readable text from a tool_result block, whose content may be string or blocks. */
|
|
327
|
+
function toolResultText(block) {
|
|
328
|
+
const c = block.content;
|
|
329
|
+
if (typeof c === 'string')
|
|
330
|
+
return c.trim();
|
|
331
|
+
if (Array.isArray(c)) {
|
|
332
|
+
return c
|
|
333
|
+
.filter((x) => x?.type === 'text' && typeof x.text === 'string')
|
|
334
|
+
.map((x) => x.text)
|
|
335
|
+
.join('\n')
|
|
336
|
+
.trim();
|
|
337
|
+
}
|
|
338
|
+
return '';
|
|
339
|
+
}
|
|
340
|
+
// ── Hard stop ─────────────────────────────────────────────────────────────────
|
|
341
|
+
// The stall watchdog and the parent's Stop only SET a flag; runAgentLoop notices it at
|
|
342
|
+
// the next boundary. A tool that never returns (an MCP server that ignores the abort,
|
|
343
|
+
// a stuck editor-side call) meant that boundary never came and the parent hung forever.
|
|
344
|
+
// After the flag is set, the child gets this long to wind down before we stop waiting.
|
|
345
|
+
exports.HARD_STOPPED = Symbol('hard-stopped');
|
|
346
|
+
function hardStopGraceMs() {
|
|
347
|
+
const v = Number(process.env.NEXRALL_SUBAGENT_HARD_STOP_MS);
|
|
348
|
+
return Number.isFinite(v) && v > 0 ? v : 30000;
|
|
349
|
+
}
|
|
350
|
+
function raceHardStop(run, abort) {
|
|
351
|
+
return new Promise((resolve, reject) => {
|
|
352
|
+
let grace = null;
|
|
353
|
+
const poll = setInterval(() => {
|
|
354
|
+
if (abort.aborted && !grace) {
|
|
355
|
+
grace = setTimeout(() => { clearInterval(poll); resolve(exports.HARD_STOPPED); }, hardStopGraceMs());
|
|
356
|
+
}
|
|
357
|
+
}, 250);
|
|
358
|
+
const settle = () => { clearInterval(poll); if (grace)
|
|
359
|
+
clearTimeout(grace); };
|
|
360
|
+
run.then((v) => { settle(); resolve(v); }, (e) => { settle(); reject(e); });
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/** Returned instead of waiting forever for a tool that ignored Stop. */
|
|
364
|
+
exports.STOP_TIMEOUT_RESULT = {
|
|
365
|
+
error: 'Interrupted: this tool did not respond to Stop, so the agent stopped waiting for it.',
|
|
366
|
+
interrupted: true,
|
|
367
|
+
};
|
|
368
|
+
const STOP_GRACE_MS = 5000;
|
|
369
|
+
function stopAwaiting(run, abort) {
|
|
370
|
+
if (!abort)
|
|
371
|
+
return run;
|
|
372
|
+
return new Promise((resolve, reject) => {
|
|
373
|
+
let grace = null;
|
|
374
|
+
const poll = setInterval(() => {
|
|
375
|
+
if (abort.aborted && !grace)
|
|
376
|
+
grace = setTimeout(() => { clearInterval(poll); resolve(exports.STOP_TIMEOUT_RESULT); }, STOP_GRACE_MS);
|
|
377
|
+
}, 200);
|
|
378
|
+
const settle = () => { clearInterval(poll); if (grace)
|
|
379
|
+
clearTimeout(grace); };
|
|
380
|
+
run.then((v) => { settle(); resolve(v); }, (e) => { settle(); reject(e); });
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/** Default turn limit for a sub-agent whose definition sets no `maxTurns`. */
|
|
384
|
+
exports.DEFAULT_SUBAGENT_MAX_TURNS = 200;
|
|
385
|
+
/** Stop reasons that mean the sub-agent did NOT deliver a finished report. */
|
|
386
|
+
exports.ABNORMAL_SUBAGENT_STOPS = new Set([
|
|
387
|
+
'budget', 'stalled', 'stalled-repeat', 'output-limit', 'empty-response',
|
|
388
|
+
'no-balance', 'no-team-budget', 'team-unavailable', 'reported-elsewhere',
|
|
389
|
+
// A hook stopping the batch means the sub-agent stopped short of its report, so the
|
|
390
|
+
// parent must not read the result as a finished one.
|
|
391
|
+
'hook-blocked',
|
|
392
|
+
]);
|
|
393
|
+
/**
|
|
394
|
+
* Prepended to EVERY sub-agent's instructions (named or not) — Claude Code's
|
|
395
|
+
* "you are a sub-agent; your final message is the report" contract.
|
|
396
|
+
*/
|
|
397
|
+
exports.SUBAGENT_PREAMBLE = [
|
|
398
|
+
'# You are a sub-agent',
|
|
399
|
+
'The main agent started you for ONE delegated task. You cannot see its conversation with the user — only the task you were given — and you cannot ask the user anything: if something is ambiguous, make the most reasonable assumption and state it.',
|
|
400
|
+
'Your FINAL message is the only thing returned to the main agent, and the user does not see it directly. Make it a concise, self-contained report: what you found or changed (with file paths and line numbers), what you verified and how, and anything left undone or uncertain. Do not pad it, and do not paste whole files — cite locations instead.',
|
|
401
|
+
].join('\n');
|
|
402
|
+
function formatTokenCount(n) {
|
|
403
|
+
return n >= 1000000 ? `${(n / 1000000).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}K` : String(n);
|
|
404
|
+
}
|
|
405
|
+
function formatElapsed(ms) {
|
|
406
|
+
const s = Math.round(ms / 1000);
|
|
407
|
+
return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`;
|
|
408
|
+
}
|
|
409
|
+
//# sourceMappingURL=subTaskSupport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolDescriptions.d.ts","sourceRoot":"","sources":["../../src/agent/toolDescriptions.ts"],"names":[],"mappings":"AASA,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,iBAAiB,EAAE,WAAW,EAAE,GAAG,MAAM,CAMxF;AAID,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA0HrF"}
|