@nexrall/code-core 1.4.65 → 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/README.md +2 -2
- package/dist/agent/agentTypes.d.ts +8 -4
- package/dist/agent/agentTypes.d.ts.map +1 -1
- package/dist/agent/agentTypes.js +20 -147
- 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 +418 -3032
- package/dist/agent/memory.d.ts +11 -0
- package/dist/agent/memory.d.ts.map +1 -1
- package/dist/agent/memory.js +23 -3
- package/dist/agent/planMode.d.ts +14 -0
- package/dist/agent/planMode.d.ts.map +1 -1
- package/dist/agent/planMode.js +146 -17
- package/dist/agent/securityLint.js +2 -2
- 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 +1 -1
- 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 +11 -7
- package/dist/permissions/destructive.d.ts.map +1 -1
- package/dist/permissions/destructive.js +59 -8
- package/dist/permissions/destructiveTokens.d.ts +34 -0
- package/dist/permissions/destructiveTokens.d.ts.map +1 -0
- package/dist/permissions/destructiveTokens.js +475 -0
- package/dist/permissions/modePolicy.d.ts +12 -8
- package/dist/permissions/modePolicy.d.ts.map +1 -1
- package/dist/permissions/modePolicy.js +19 -12
- 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 +47 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -7
package/dist/agent/memory.d.ts
CHANGED
|
@@ -48,6 +48,17 @@ export declare function memoryStats(scope: MemoryScope, workDir?: string): {
|
|
|
48
48
|
entries: number;
|
|
49
49
|
archived: number;
|
|
50
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Keep only the lines of a consolidation reply that are real memory entries.
|
|
53
|
+
*
|
|
54
|
+
* The prompt says "Output ONLY the bullet lines", but models do not always comply: a reply
|
|
55
|
+
* that opens with "I'll read the full, untruncated memory text before consolidating…" (seen
|
|
56
|
+
* in a user's global.md and a project file) used to be saved verbatim as an ACTIVE fact, then
|
|
57
|
+
* injected into every later session as ground truth. An entry is a line of the exact shape
|
|
58
|
+
* memory_write produces — `- [YYYY-MM-DD] …` — so anything else (preamble, "Here are the
|
|
59
|
+
* merged bullets:", a code fence, a trailing remark) is dropped rather than persisted.
|
|
60
|
+
*/
|
|
61
|
+
export declare function extractConsolidatedEntries(reply: string): string[];
|
|
51
62
|
/**
|
|
52
63
|
* Summarize the OLDEST half of a memory file's ACTIVE entries into a few consolidated
|
|
53
64
|
* bullets via an LLM call, keeping the most recent entries verbatim — same "compact,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/agent/memory.ts"],"names":[],"mappings":"AAiEA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE/C;;;sDAGsD;AACtD,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,gBAAgB,QAAS,CAAC;AACvC,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAMnD,eAAO,MAAM,qBAAqB,QAAmC,CAAC;AAKtE,eAAO,MAAM,wBAAwB,QAAmC,CAAC;AAoFzE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;kCAI8B;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;6DAEyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,MAAM,EAChB,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CA2D5B;AAED,MAAM,WAAW,iBAAiB;IAChC;;;iDAG6C;IAC7C,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,MAAM,CAKrG;AAED;gFACgF;AAChF,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,MAAM,CASpF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAGtE;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAUpE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAC7C,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/agent/memory.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/memory.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.readMemory = readMemory;
|
|
|
40
40
|
exports.readAllMemory = readAllMemory;
|
|
41
41
|
exports.clearMemory = clearMemory;
|
|
42
42
|
exports.memoryStats = memoryStats;
|
|
43
|
+
exports.extractConsolidatedEntries = extractConsolidatedEntries;
|
|
43
44
|
exports.compactMemoryIfNeeded = compactMemoryIfNeeded;
|
|
44
45
|
exports.isSafeAgentName = isSafeAgentName;
|
|
45
46
|
exports.agentMemoryPath = agentMemoryPath;
|
|
@@ -318,6 +319,22 @@ function memoryStats(scope, workDir) {
|
|
|
318
319
|
archived: archived.filter((l) => /^-\s\[\d{4}-\d{2}-\d{2}\]/.test(l)).length,
|
|
319
320
|
};
|
|
320
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Keep only the lines of a consolidation reply that are real memory entries.
|
|
324
|
+
*
|
|
325
|
+
* The prompt says "Output ONLY the bullet lines", but models do not always comply: a reply
|
|
326
|
+
* that opens with "I'll read the full, untruncated memory text before consolidating…" (seen
|
|
327
|
+
* in a user's global.md and a project file) used to be saved verbatim as an ACTIVE fact, then
|
|
328
|
+
* injected into every later session as ground truth. An entry is a line of the exact shape
|
|
329
|
+
* memory_write produces — `- [YYYY-MM-DD] …` — so anything else (preamble, "Here are the
|
|
330
|
+
* merged bullets:", a code fence, a trailing remark) is dropped rather than persisted.
|
|
331
|
+
*/
|
|
332
|
+
function extractConsolidatedEntries(reply) {
|
|
333
|
+
return reply
|
|
334
|
+
.split('\n')
|
|
335
|
+
.map((l) => l.trimEnd())
|
|
336
|
+
.filter((l) => /^-\s\[\d{4}-\d{2}-\d{2}\]\s*\S/.test(l));
|
|
337
|
+
}
|
|
321
338
|
/**
|
|
322
339
|
* Summarize the OLDEST half of a memory file's ACTIVE entries into a few consolidated
|
|
323
340
|
* bullets via an LLM call, keeping the most recent entries verbatim — same "compact,
|
|
@@ -370,9 +387,12 @@ async function compactMemoryIfNeeded(scope, workDir, summarize) {
|
|
|
370
387
|
`draws from, and preserve any leading "[src: ...]" provenance tag verbatim when present. ` +
|
|
371
388
|
`Output ONLY the bullet lines, nothing else.\n\n${older.join('\n')}`;
|
|
372
389
|
try {
|
|
373
|
-
const summarized = (await summarize(prompt))
|
|
374
|
-
|
|
375
|
-
|
|
390
|
+
const summarized = extractConsolidatedEntries(await summarize(prompt));
|
|
391
|
+
// Zero valid bullets means the model answered in prose (or refused): treat it
|
|
392
|
+
// exactly like a failed call and keep the original entries. Writing the reply
|
|
393
|
+
// anyway would REPLACE the older half with chatter.
|
|
394
|
+
if (summarized.length > 0) {
|
|
395
|
+
active = [...summarized, ...recent];
|
|
376
396
|
changed = true;
|
|
377
397
|
}
|
|
378
398
|
}
|
package/dist/agent/planMode.d.ts
CHANGED
|
@@ -10,6 +10,20 @@ export interface PlanModeRefusal {
|
|
|
10
10
|
* "Provably" is doing real work: unknown verbs are refused, not guessed at.
|
|
11
11
|
*/
|
|
12
12
|
export declare function isReadOnlyCommand(command: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Is a (possibly piped / chained) command read-only on every segment?
|
|
15
|
+
*
|
|
16
|
+
* This is what the permission GATE uses to skip a prompt in ask/edit mode. Plan mode
|
|
17
|
+
* keeps the stricter whole-shape refusal above (`checkPlanMode`): a temporary lock can
|
|
18
|
+
* afford to say "no pipelines", an approval gate that says it about `git log | head`
|
|
19
|
+
* just trains people to click through prompts.
|
|
20
|
+
*
|
|
21
|
+
* Fail-closed throughout: anything that is not plainly a chain of read-only commands —
|
|
22
|
+
* a write redirect, `$(…)`, backticks, a lone `&`, an unknown verb, a segment split
|
|
23
|
+
* wrongly by a quote — returns false, which means "ask the user". The cost of a false
|
|
24
|
+
* false is one extra keypress; the cost of a false true is `rm` running unasked.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isReadOnlyPipeline(command: string): boolean;
|
|
13
27
|
/**
|
|
14
28
|
* Should this tool call be refused because the session is in plan mode?
|
|
15
29
|
* Returns null when the call is allowed.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planMode.d.ts","sourceRoot":"","sources":["../../src/agent/planMode.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"planMode.d.ts","sourceRoot":"","sources":["../../src/agent/planMode.ts"],"names":[],"mappings":"AAiJA,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,MAAM,EAAE,eAAe,GAAG,oBAAoB,CAAC;IAC/C,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;CACjB;AAqFD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CA8D1D;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAO3D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,eAAe,GAAG,IAAI,CAoCxB;AAED,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,QAqBvB,CAAC"}
|
package/dist/agent/planMode.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
exports.PLAN_MODE_INSTRUCTIONS = void 0;
|
|
21
21
|
exports.isReadOnlyCommand = isReadOnlyCommand;
|
|
22
|
+
exports.isReadOnlyPipeline = isReadOnlyPipeline;
|
|
22
23
|
exports.checkPlanMode = checkPlanMode;
|
|
23
24
|
/**
|
|
24
25
|
* Tools that cannot alter anything outside the conversation.
|
|
@@ -35,6 +36,7 @@ const READ_ONLY_TOOLS = new Set([
|
|
|
35
36
|
// list, not the repo, so it stays available — a plan mode that cannot draft
|
|
36
37
|
// a checklist is missing the point of plan mode.
|
|
37
38
|
'todo_write', 'todo_read',
|
|
39
|
+
'cron_list', 'mcp_list_resources', 'mcp_read_resource',
|
|
38
40
|
// Reading memory is fine; memory_write is NOT here on purpose. Memory is
|
|
39
41
|
// durable state that survives the session, so writing it is a real mutation
|
|
40
42
|
// even though no file in the repo changes.
|
|
@@ -71,10 +73,31 @@ const READ_ONLY_BASH = new Set([
|
|
|
71
73
|
'grep', 'egrep', 'fgrep', 'rg', 'ag', 'ack', 'find', 'fd', 'locate',
|
|
72
74
|
'diff', 'comm', 'cmp', 'sort', 'uniq', 'cut', 'tr', 'column', 'jq', 'yq',
|
|
73
75
|
'date', 'whoami', 'hostname', 'uname', 'env', 'printenv', 'id', 'groups',
|
|
74
|
-
'ps', '
|
|
75
|
-
|
|
76
|
+
'ps', 'uptime', 'free', 'node', 'python', 'python3',
|
|
77
|
+
// `less`/`more`/`top` are deliberately NOT here: pagers run `!cmd` from inside and
|
|
78
|
+
// `top` never exits. `env` is listed but only bare — see toolFlagsOk.
|
|
79
|
+
'tree', 'nl', 'tac', 'strings', 'md5sum', 'sha256sum',
|
|
76
80
|
'true', 'false', 'test', 'sleep', 'seq', 'expr',
|
|
81
|
+
// Changes this shell's cwd only. Needed so `cd x && ls` is not refused.
|
|
82
|
+
'cd',
|
|
77
83
|
]);
|
|
84
|
+
/**
|
|
85
|
+
* A verb written with a path is only trusted when it lives in a system bin dir.
|
|
86
|
+
* Stripping to the basename (the old behaviour) meant `./ls`, `/tmp/evil/cat` and
|
|
87
|
+
* `build/git` — attacker-written files — were classified by their NAME and waved
|
|
88
|
+
* through as read-only.
|
|
89
|
+
*/
|
|
90
|
+
const SYSTEM_BIN_DIRS = new Set([
|
|
91
|
+
'/bin', '/usr/bin', '/usr/local/bin', '/sbin', '/usr/sbin', '/opt/homebrew/bin',
|
|
92
|
+
]);
|
|
93
|
+
/**
|
|
94
|
+
* Environment assignments that change WHAT a read-only verb runs: the search path,
|
|
95
|
+
* loader, pager/editor/external-diff hooks, and the interpreters' option vars.
|
|
96
|
+
* `GIT_EXTERNAL_DIFF=./x git diff` and `PATH=/tmp ls` run the attacker's program
|
|
97
|
+
* while every word the classifier looks at is innocent.
|
|
98
|
+
*/
|
|
99
|
+
const UNSAFE_ENV = /^(PATH|HOME|SHELL|IFS|ENV|BASH_ENV|PAGER|MANPAGER|EDITOR|VISUAL|BROWSER|LD_\w*|DYLD_\w*|GIT_\w*|NODE_\w*|NPM_\w*|npm_\w*|PYTHON\w*|BASH\w*|RUBY\w*|PERL\w*|LESS\w*|XDG_\w*|\w*OPTS?)=/i;
|
|
100
|
+
const GH_NOUNS = new Set(['pr', 'issue', 'run', 'repo', 'release', 'workflow', 'gist', 'label']);
|
|
78
101
|
/**
|
|
79
102
|
* Subcommands that are read-only for tools whose safety depends on the verb.
|
|
80
103
|
*
|
|
@@ -117,6 +140,83 @@ const READ_ONLY_NESTED = {
|
|
|
117
140
|
* `|` is included: `cat x | tee y` writes, and so does `... | sh`.
|
|
118
141
|
*/
|
|
119
142
|
const SHELL_CONTROL = /[\n\r;&|`]|\$\(|>>|>|<\(/;
|
|
143
|
+
const isShort = (f) => f.startsWith('-') && !f.startsWith('--');
|
|
144
|
+
const flagName = (f) => f.split('=')[0];
|
|
145
|
+
/**
|
|
146
|
+
* Flags of an allowlisted VERB that turn it into a writer or an executor. The verb
|
|
147
|
+
* list says `sort` is read-only; `sort -o f` writes f. Each entry below is a real way
|
|
148
|
+
* to mutate through a verb the list trusts — the list alone was the hole.
|
|
149
|
+
*/
|
|
150
|
+
function toolFlagsOk(verb, args) {
|
|
151
|
+
const flags = args.filter((a) => a.startsWith('-'));
|
|
152
|
+
const pos = args.filter((a) => !a.startsWith('-'));
|
|
153
|
+
switch (verb) {
|
|
154
|
+
case 'env': return args.length === 0; // `env CMD` RUNS CMD
|
|
155
|
+
case 'sort':
|
|
156
|
+
return !flags.some((f) => (isShort(f) && /^-[A-Za-z]*o/.test(f)) || f.startsWith('--output') || f.startsWith('--compress-program'));
|
|
157
|
+
case 'uniq': return pos.length <= 1; // second positional is an OUTPUT file
|
|
158
|
+
case 'find':
|
|
159
|
+
return !args.some((a) => ['-exec', '-execdir', '-delete', '-ok', '-okdir', '-fprint', '-fprint0', '-fprintf', '-fls'].includes(a));
|
|
160
|
+
case 'fd':
|
|
161
|
+
return !flags.some((f) => f.startsWith('--exec') || (isShort(f) && /^-[A-Za-z]*[xX]/.test(f)));
|
|
162
|
+
case 'rg': return !flags.some((f) => flagName(f) === '--pre' || f.startsWith('--hostname-bin'));
|
|
163
|
+
case 'ag':
|
|
164
|
+
case 'ack': return !flags.some((f) => f.startsWith('--pager'));
|
|
165
|
+
case 'yq': return !flags.some((f) => (isShort(f) && /^-[A-Za-z]*i/.test(f)) || f.startsWith('--inplace'));
|
|
166
|
+
case 'tree': return !flags.some((f) => isShort(f) && /^-[A-Za-z]*o/.test(f));
|
|
167
|
+
case 'date': return !flags.some((f) => (isShort(f) && /^-[A-Za-z]*s/.test(f)) || f.startsWith('--set'));
|
|
168
|
+
case 'hostname': return pos.length === 0; // `hostname NAME` renames the machine
|
|
169
|
+
case 'file': return !flags.some((f) => (isShort(f) && /^-[A-Za-z]*C/.test(f)) || f.startsWith('--compile'));
|
|
170
|
+
default: return true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Every flag must be a known read-only one; anything unfamiliar fails closed. */
|
|
174
|
+
function flagsWithin(flags, shorts, longs) {
|
|
175
|
+
return flags.every((f) => {
|
|
176
|
+
if (isShort(f))
|
|
177
|
+
return [...f.slice(1)].every((c) => shorts.includes(c) || /\d/.test(c));
|
|
178
|
+
return longs.includes(flagName(f));
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const LISTISH = ['-l', '--list', '--contains', '--no-contains', '--merged', '--no-merged', '--points-at'];
|
|
182
|
+
/**
|
|
183
|
+
* Same idea one level down: `git branch` lists, `git branch newbranch` creates,
|
|
184
|
+
* `git branch -D main` deletes — one subcommand, three effects.
|
|
185
|
+
*/
|
|
186
|
+
function subcommandFlagsOk(verb, sub, args) {
|
|
187
|
+
const flags = args.filter((a) => a.startsWith('-'));
|
|
188
|
+
const pos = args.filter((a) => !a.startsWith('-')); // pos[0] === sub
|
|
189
|
+
const listish = flags.some((f) => LISTISH.includes(flagName(f)) || (isShort(f) && f.includes('l')));
|
|
190
|
+
if (verb === 'git') {
|
|
191
|
+
if (flags.some((f) => f.startsWith('--output') || f === '--ext-diff' || f === '-O' || f.startsWith('--open-files-in-pager')))
|
|
192
|
+
return false;
|
|
193
|
+
switch (sub) {
|
|
194
|
+
case 'branch':
|
|
195
|
+
return flagsWithin(flags, 'arvlq', ['--all', '--remotes', '--verbose', '--list', '--show-current', '--contains', '--no-contains', '--merged', '--no-merged', '--points-at', '--sort', '--format', '--column', '--no-column', '--color', '--no-color', '--abbrev', '--no-abbrev', '--ignore-case', '--omit-empty'])
|
|
196
|
+
&& (pos.length <= 1 || listish);
|
|
197
|
+
case 'tag':
|
|
198
|
+
return flagsWithin(flags, 'lnv', ['--list', '--contains', '--no-contains', '--merged', '--no-merged', '--points-at', '--sort', '--format', '--column', '--no-column', '--color', '--no-color', '--verify', '--ignore-case', '--omit-empty'])
|
|
199
|
+
&& (pos.length <= 1 || listish || flags.some((f) => f === '-v' || f === '--verify'));
|
|
200
|
+
case 'remote':
|
|
201
|
+
return flagsWithin(flags, 'vn', ['--verbose']) && (pos[1] === undefined || pos[1] === 'show' || pos[1] === 'get-url');
|
|
202
|
+
case 'config':
|
|
203
|
+
return flagsWithin(flags, 'lz', ['--get', '--get-all', '--get-regexp', '--list', '--show-origin', '--show-scope', '--global', '--local', '--system', '--worktree', '--name-only', '--null', '--includes', '--no-includes', '--type'])
|
|
204
|
+
&& pos.length <= 2; // `config` itself + at most one key
|
|
205
|
+
case 'reflog': return pos[1] !== 'expire' && pos[1] !== 'delete';
|
|
206
|
+
default: return true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (sub === 'config' && (verb === 'npm' || verb === 'pnpm' || verb === 'yarn')) {
|
|
210
|
+
return pos[1] === undefined || pos[1] === 'get' || pos[1] === 'list' || pos[1] === 'ls';
|
|
211
|
+
}
|
|
212
|
+
if (verb === 'go') {
|
|
213
|
+
return !flags.some((f) => {
|
|
214
|
+
const n = f.replace(/^--?/, '-');
|
|
215
|
+
return n === '-w' || n === '-u' || n.startsWith('-vettool') || n.startsWith('-toolexec') || n === '-exec';
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
120
220
|
function firstWords(command) {
|
|
121
221
|
return command.trim().split(/\s+/).filter(Boolean);
|
|
122
222
|
}
|
|
@@ -137,15 +237,21 @@ function isReadOnlyCommand(command) {
|
|
|
137
237
|
return false;
|
|
138
238
|
// `VAR=x cmd` — skip leading environment assignments to find the real verb.
|
|
139
239
|
let i = 0;
|
|
140
|
-
while (i < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[i]))
|
|
240
|
+
while (i < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[i])) {
|
|
241
|
+
if (UNSAFE_ENV.test(words[i]))
|
|
242
|
+
return false;
|
|
141
243
|
i++;
|
|
244
|
+
}
|
|
142
245
|
if (i >= words.length)
|
|
143
246
|
return false;
|
|
144
247
|
let verb = words[i];
|
|
145
|
-
//
|
|
248
|
+
// /usr/bin/git → git, but ONLY for a system bin dir (see SYSTEM_BIN_DIRS).
|
|
146
249
|
const slash = verb.lastIndexOf('/');
|
|
147
|
-
if (slash >= 0)
|
|
250
|
+
if (slash >= 0) {
|
|
251
|
+
if (!SYSTEM_BIN_DIRS.has(verb.slice(0, slash)))
|
|
252
|
+
return false;
|
|
148
253
|
verb = verb.slice(slash + 1);
|
|
254
|
+
}
|
|
149
255
|
// `sudo anything` is refused outright regardless of the verb behind it: plan
|
|
150
256
|
// mode is a promise about this machine, and privilege escalation is exactly
|
|
151
257
|
// where a mistaken allow is least recoverable.
|
|
@@ -159,16 +265,12 @@ function isReadOnlyCommand(command) {
|
|
|
159
265
|
// Bare `git` / `npm` just prints help — harmless.
|
|
160
266
|
return true;
|
|
161
267
|
}
|
|
162
|
-
if (subs.has(sub))
|
|
163
|
-
|
|
164
|
-
if (verb === 'git' && sub === 'config') {
|
|
165
|
-
const args = words.slice(i + 2).filter((w) => !w.startsWith('-'));
|
|
166
|
-
return args.length <= 1;
|
|
167
|
-
}
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
268
|
+
if (subs.has(sub))
|
|
269
|
+
return subcommandFlagsOk(verb, sub, words.slice(i + 1));
|
|
170
270
|
const nested = READ_ONLY_NESTED[verb];
|
|
171
|
-
|
|
271
|
+
// `gh api list -X DELETE` has "list" in second position too — the noun must be a
|
|
272
|
+
// real resource noun, and `api` (arbitrary HTTP verbs) is not one.
|
|
273
|
+
if (nested && rest[1] && nested.has(rest[1]) && (verb !== 'gh' || GH_NOUNS.has(sub)))
|
|
172
274
|
return true;
|
|
173
275
|
return false;
|
|
174
276
|
}
|
|
@@ -179,15 +281,42 @@ function isReadOnlyCommand(command) {
|
|
|
179
281
|
if (verb === 'node' || verb === 'python' || verb === 'python3') {
|
|
180
282
|
return words.slice(i + 1).every((w) => w === '--version' || w === '-V' || w === '-v');
|
|
181
283
|
}
|
|
182
|
-
|
|
183
|
-
if (verb === 'find' && words.some((w) => w === '-exec' || w === '-execdir' || w === '-delete')) {
|
|
284
|
+
if (!toolFlagsOk(verb, words.slice(i + 1)))
|
|
184
285
|
return false;
|
|
185
|
-
}
|
|
186
286
|
// Reading a file with `test`/`stat` is fine, but `tee` is not in the list at
|
|
187
287
|
// all, and `echo`/`printf` are only safe because redirection is already
|
|
188
288
|
// rejected by SHELL_CONTROL above.
|
|
189
289
|
return true;
|
|
190
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Output redirections that cannot write anywhere interesting. Without this `ls 2>&1`
|
|
293
|
+
* and `cmd 2>/dev/null` — extremely common — would all count as "redirection".
|
|
294
|
+
*/
|
|
295
|
+
const HARMLESS_REDIRECT = /\s*\d?>\s*&\d\b|\s*\d?>>?\s*\/dev\/null(?=\s|$)/g;
|
|
296
|
+
/**
|
|
297
|
+
* Is a (possibly piped / chained) command read-only on every segment?
|
|
298
|
+
*
|
|
299
|
+
* This is what the permission GATE uses to skip a prompt in ask/edit mode. Plan mode
|
|
300
|
+
* keeps the stricter whole-shape refusal above (`checkPlanMode`): a temporary lock can
|
|
301
|
+
* afford to say "no pipelines", an approval gate that says it about `git log | head`
|
|
302
|
+
* just trains people to click through prompts.
|
|
303
|
+
*
|
|
304
|
+
* Fail-closed throughout: anything that is not plainly a chain of read-only commands —
|
|
305
|
+
* a write redirect, `$(…)`, backticks, a lone `&`, an unknown verb, a segment split
|
|
306
|
+
* wrongly by a quote — returns false, which means "ask the user". The cost of a false
|
|
307
|
+
* false is one extra keypress; the cost of a false true is `rm` running unasked.
|
|
308
|
+
*/
|
|
309
|
+
function isReadOnlyPipeline(command) {
|
|
310
|
+
const cmd = command.replace(HARMLESS_REDIRECT, ' ').trim();
|
|
311
|
+
if (!cmd)
|
|
312
|
+
return false;
|
|
313
|
+
if (/[>`]|\$\(|<\(/.test(cmd))
|
|
314
|
+
return false;
|
|
315
|
+
const segs = cmd.split(/\|\||&&|[;|\n\r]/).map((x) => x.trim()).filter(Boolean);
|
|
316
|
+
if (!segs.length)
|
|
317
|
+
return false;
|
|
318
|
+
return segs.every((seg) => !seg.includes('&') && isReadOnlyCommand(seg));
|
|
319
|
+
}
|
|
191
320
|
/**
|
|
192
321
|
* Should this tool call be refused because the session is in plan mode?
|
|
193
322
|
* Returns null when the call is allowed.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
//
|
|
20
20
|
// Consequently every pattern here is tuned for PRECISION over recall. A check that
|
|
21
21
|
// fires on ordinary code was removed rather than loosened. Deep analysis is the
|
|
22
|
-
//
|
|
22
|
+
// a dedicated security review's job; this only catches the flagrant cases at the
|
|
23
23
|
// moment they are written.
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
exports.checkSecurity = checkSecurity;
|
|
@@ -116,7 +116,7 @@ const RULES = [
|
|
|
116
116
|
// So the interpolation must name something that plausibly came from the
|
|
117
117
|
// outside: req/request/params/query/body/input/user/args, or a bare
|
|
118
118
|
// `'...' + ident`. This trades recall for precision on purpose — thorough SQL
|
|
119
|
-
// review is
|
|
119
|
+
// review is a dedicated security review's job, not an inline regex's.
|
|
120
120
|
//
|
|
121
121
|
// THE GAP IS PER-ALTERNATIVE, and that asymmetry is load-bearing. The concat
|
|
122
122
|
// branch originally shared the no-quote gap, which made it miss the QUOTED
|
|
@@ -59,5 +59,12 @@ export declare const _sharedTasksInternal: {
|
|
|
59
59
|
CLAIM_STALE_MS: number;
|
|
60
60
|
MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS: number;
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Remove a task outright — the veto path for a `TaskCreated` hook. Claude Code creates the
|
|
64
|
+
* task, and when a hook refuses it the task is DELETED and the reason goes back to the
|
|
65
|
+
* model as the tool's error; so "no task remains" is the observable contract of a veto,
|
|
66
|
+
* and this is the only place that needs to make it true.
|
|
67
|
+
*/
|
|
68
|
+
export declare function deleteSharedTask(workDir: string, taskId: string): boolean;
|
|
62
69
|
export {};
|
|
63
70
|
//# sourceMappingURL=sharedTasks.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedTasks.d.ts","sourceRoot":"","sources":["../../src/agent/sharedTasks.ts"],"names":[],"mappings":"AA6DA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7E,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,4HAA4H;IAC5H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8HAA8H;IAC9H,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD,iBAAS,SAAS,IAAI,MAAM,CAE3B;AAED,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGzC;AAED,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEzD;AA4BD,0HAA0H;AAC1H,iBAAS,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAUnD;AAED,4GAA4G;AAC5G,iBAAS,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAG/D;AAED,0IAA0I;AAC1I,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAW7D;AAED,sJAAsJ;AACtJ,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAC/C,UAAU,CAUZ;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,GAAG,SAAS,GAAG,aAAa,CAAA;CAAE,CAAC;AAEvF;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA+BxG;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACrF,OAAO,CAAC,WAAW,CAAC,CAoBtB;AAED,eAAO,MAAM,oBAAoB;;;;;;;;CAAqH,CAAC"}
|
|
1
|
+
{"version":3,"file":"sharedTasks.d.ts","sourceRoot":"","sources":["../../src/agent/sharedTasks.ts"],"names":[],"mappings":"AA6DA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7E,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,4HAA4H;IAC5H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8HAA8H;IAC9H,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD,iBAAS,SAAS,IAAI,MAAM,CAE3B;AAED,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGzC;AAED,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEzD;AA4BD,0HAA0H;AAC1H,iBAAS,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAUnD;AAED,4GAA4G;AAC5G,iBAAS,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAG/D;AAED,0IAA0I;AAC1I,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAW7D;AAED,sJAAsJ;AACtJ,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAC/C,UAAU,CAUZ;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,GAAG,SAAS,GAAG,aAAa,CAAA;CAAE,CAAC;AAEvF;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA+BxG;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACrF,OAAO,CAAC,WAAW,CAAC,CAoBtB;AAED,eAAO,MAAM,oBAAoB;;;;;;;;CAAqH,CAAC;AAEvJ;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzE"}
|
|
@@ -38,6 +38,7 @@ exports.listSharedTasks = listSharedTasks;
|
|
|
38
38
|
exports.createSharedTask = createSharedTask;
|
|
39
39
|
exports.claimTask = claimTask;
|
|
40
40
|
exports.updateSharedTaskStatus = updateSharedTaskStatus;
|
|
41
|
+
exports.deleteSharedTask = deleteSharedTask;
|
|
41
42
|
/**
|
|
42
43
|
* Shared task list — lets MULTIPLE independent sessions (two CLI windows, or
|
|
43
44
|
* CLI + VS Code + Desktop, all working the same project) coordinate who is
|
|
@@ -281,4 +282,19 @@ async function updateSharedTaskStatus(workDir, taskId, status, opts = {}) {
|
|
|
281
282
|
}, workDir);
|
|
282
283
|
}
|
|
283
284
|
exports._sharedTasksInternal = { tasksRoot, scopeDir, taskPath, isClaimAbandoned, isUnblocked, CLAIM_STALE_MS, MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS };
|
|
285
|
+
/**
|
|
286
|
+
* Remove a task outright — the veto path for a `TaskCreated` hook. Claude Code creates the
|
|
287
|
+
* task, and when a hook refuses it the task is DELETED and the reason goes back to the
|
|
288
|
+
* model as the tool's error; so "no task remains" is the observable contract of a veto,
|
|
289
|
+
* and this is the only place that needs to make it true.
|
|
290
|
+
*/
|
|
291
|
+
function deleteSharedTask(workDir, taskId) {
|
|
292
|
+
try {
|
|
293
|
+
fs.unlinkSync(taskPath(workDir, taskId));
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
284
300
|
//# sourceMappingURL=sharedTasks.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the fan-out limit: env → settings.json → default.
|
|
3
|
+
*
|
|
4
|
+
* Was env-ONLY, the same gap as the sub-task timeout: a project on a small machine that
|
|
5
|
+
* wanted 2, or a big one that wanted 6, had to set a shell variable, and nobody reading
|
|
6
|
+
* settings.json could see what the limit even was. Capped at HARD_MAX_CONCURRENT_SUBTASKS
|
|
7
|
+
* (32 — above Claude Code's default 20, so a user who wants that width can have it)
|
|
8
|
+
* because this bounds real shared resources (CPU, the API rate limit, file handles) and a
|
|
9
|
+
* typo like 400 should degrade to "a lot" rather than fork-bomb the machine.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveMaxConcurrentSubtasks(settingsRaw?: Record<string, unknown>): number;
|
|
12
|
+
/** Resolve the session-total sub-agent ceiling: env → settings.json → default. */
|
|
13
|
+
export declare function resolveMaxSubagentsPerSession(settingsRaw?: Record<string, unknown>): number;
|
|
14
|
+
/**
|
|
15
|
+
* Minimal concurrency gate. Hand-rolled rather than pulling in `p-limit` because
|
|
16
|
+
* the CLI ships as a single esbuild bundle with no node_modules, and this is a
|
|
17
|
+
* dozen lines.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createLimiter(max: number): <T>(fn: () => Promise<T>) => Promise<T>;
|
|
20
|
+
/** In-flight count per depth — used only to detect queueing, so the notice is accurate. */
|
|
21
|
+
export declare const _inFlightByDepth: Map<number, number>;
|
|
22
|
+
export declare function subTaskLimiter(depth: number, workDir?: string): {
|
|
23
|
+
run: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
24
|
+
max: number;
|
|
25
|
+
};
|
|
26
|
+
/** Test-only: forget the memoised limiter so a new limit can take effect. */
|
|
27
|
+
export declare function _resetSubTaskLimiter(): void;
|
|
28
|
+
export declare const PROCESS_BUDGET_KEY = "\0process";
|
|
29
|
+
/**
|
|
30
|
+
* Start a fresh sub-agent budget. Call when a NEW conversation begins.
|
|
31
|
+
*
|
|
32
|
+
* Exported for clients that reuse one process across conversations (the VS Code extension
|
|
33
|
+
* host). A client that never calls it gets process-lifetime semantics, which is correct
|
|
34
|
+
* for a one-shot CLI invocation.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resetSessionSubAgentBudget(sessionKey?: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Claim one slot against the session total. Returns an error string when exhausted.
|
|
39
|
+
*
|
|
40
|
+
* `notify` surfaces exhaustion to the HUMAN exactly once. Without it only the model is
|
|
41
|
+
* told, and a model instructed to "do the remaining work directly" complies silently — so
|
|
42
|
+
* the user never learns delegation was capped, which is the one thing a runaway guard has
|
|
43
|
+
* to make visible.
|
|
44
|
+
*/
|
|
45
|
+
export declare function claimSessionSubAgentSlot(workDir?: string, notify?: (msg: string) => void, sessionKey?: string): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Give back a slot claimed for a spawn that never started an agent.
|
|
48
|
+
*
|
|
49
|
+
* The claim happens at the dispatch site, BEFORE the concurrency limiter, so that a spawn
|
|
50
|
+
* which is already over budget is refused immediately instead of queueing behind running
|
|
51
|
+
* siblings. That ordering is what makes the guard useful in a runaway — but it also means
|
|
52
|
+
* the claim precedes runSubTask's own validation, which rejects on several paths without
|
|
53
|
+
* ever starting a loop (empty prompt, a `deny` rule, an unusable resume id).
|
|
54
|
+
*
|
|
55
|
+
* Without a refund those rejections spend budget: a model retrying against a deny rule would
|
|
56
|
+
* silently burn the whole allowance on spawns that never ran, then lose delegation for the
|
|
57
|
+
* session with a notice blaming a runaway that never happened. The counter's contract is
|
|
58
|
+
* "sub-agents STARTED", and this is what keeps that true while preserving fast refusal.
|
|
59
|
+
*
|
|
60
|
+
* Floored at 0 so a double refund can never manufacture budget.
|
|
61
|
+
*/
|
|
62
|
+
export declare function refundSessionSubAgentSlot(sessionKey?: string): void;
|
|
63
|
+
/** Test-only: observe the session counter without exporting the mutable binding. */
|
|
64
|
+
export declare function _sessionSubAgentCount(sessionKey?: string): number;
|
|
65
|
+
//# sourceMappingURL=subAgentBudget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subAgentBudget.d.ts","sourceRoot":"","sources":["../../src/agent/subAgentBudget.ts"],"names":[],"mappings":"AAyCA;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAW9F;AAoBD,kFAAkF;AAClF,wBAAgB,6BAA6B,CAAC,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAa/F;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAgBlF;AA8BD,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB,qBAA4B,CAAC;AAE1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG;IAC/D,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7C,GAAG,EAAE,MAAM,CAAC;CACb,CAuBA;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,IAAI,CAM3C;AAkBD,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAQ9C;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAGpE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB7H;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAGnE;AAED,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjE"}
|