@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.
Files changed (83) hide show
  1. package/README.md +2 -2
  2. package/dist/agent/agentTypes.d.ts +8 -4
  3. package/dist/agent/agentTypes.d.ts.map +1 -1
  4. package/dist/agent/agentTypes.js +20 -147
  5. package/dist/agent/askOnce.d.ts +12 -0
  6. package/dist/agent/askOnce.d.ts.map +1 -0
  7. package/dist/agent/askOnce.js +41 -0
  8. package/dist/agent/compaction.d.ts +244 -0
  9. package/dist/agent/compaction.d.ts.map +1 -0
  10. package/dist/agent/compaction.js +976 -0
  11. package/dist/agent/fileLocks.d.ts +34 -0
  12. package/dist/agent/fileLocks.d.ts.map +1 -0
  13. package/dist/agent/fileLocks.js +114 -0
  14. package/dist/agent/hooks.d.ts +324 -0
  15. package/dist/agent/hooks.d.ts.map +1 -0
  16. package/dist/agent/hooks.js +1228 -0
  17. package/dist/agent/iterationPolicy.d.ts +121 -0
  18. package/dist/agent/iterationPolicy.d.ts.map +1 -0
  19. package/dist/agent/iterationPolicy.js +297 -0
  20. package/dist/agent/lifecycleHost.d.ts +55 -0
  21. package/dist/agent/lifecycleHost.d.ts.map +1 -0
  22. package/dist/agent/lifecycleHost.js +294 -0
  23. package/dist/agent/loop.d.ts +11 -491
  24. package/dist/agent/loop.d.ts.map +1 -1
  25. package/dist/agent/loop.js +418 -3032
  26. package/dist/agent/memory.d.ts +11 -0
  27. package/dist/agent/memory.d.ts.map +1 -1
  28. package/dist/agent/memory.js +23 -3
  29. package/dist/agent/planMode.d.ts +14 -0
  30. package/dist/agent/planMode.d.ts.map +1 -1
  31. package/dist/agent/planMode.js +146 -17
  32. package/dist/agent/securityLint.js +2 -2
  33. package/dist/agent/sharedTasks.d.ts +7 -0
  34. package/dist/agent/sharedTasks.d.ts.map +1 -1
  35. package/dist/agent/sharedTasks.js +16 -0
  36. package/dist/agent/subAgentBudget.d.ts +65 -0
  37. package/dist/agent/subAgentBudget.d.ts.map +1 -0
  38. package/dist/agent/subAgentBudget.js +269 -0
  39. package/dist/agent/subTask.d.ts +6 -0
  40. package/dist/agent/subTask.d.ts.map +1 -0
  41. package/dist/agent/subTask.js +713 -0
  42. package/dist/agent/subTaskSupport.d.ts +156 -0
  43. package/dist/agent/subTaskSupport.d.ts.map +1 -0
  44. package/dist/agent/subTaskSupport.js +409 -0
  45. package/dist/agent/toolDescriptions.d.ts +3 -0
  46. package/dist/agent/toolDescriptions.d.ts.map +1 -0
  47. package/dist/agent/toolDescriptions.js +116 -0
  48. package/dist/api/client.d.ts +1 -1
  49. package/dist/api/client.d.ts.map +1 -1
  50. package/dist/api/client.js +17 -0
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +3 -0
  54. package/dist/mcp/client.d.ts +104 -0
  55. package/dist/mcp/client.d.ts.map +1 -1
  56. package/dist/mcp/client.js +136 -2
  57. package/dist/mcp/httpClient.d.ts +17 -1
  58. package/dist/mcp/httpClient.d.ts.map +1 -1
  59. package/dist/mcp/httpClient.js +120 -19
  60. package/dist/mcp/manager.d.ts +77 -2
  61. package/dist/mcp/manager.d.ts.map +1 -1
  62. package/dist/mcp/manager.js +275 -9
  63. package/dist/mcp/sseClient.d.ts +7 -1
  64. package/dist/mcp/sseClient.d.ts.map +1 -1
  65. package/dist/mcp/sseClient.js +45 -1
  66. package/dist/mcp/stats.d.ts +41 -0
  67. package/dist/mcp/stats.d.ts.map +1 -0
  68. package/dist/mcp/stats.js +108 -0
  69. package/dist/permissions/destructive.d.ts +11 -7
  70. package/dist/permissions/destructive.d.ts.map +1 -1
  71. package/dist/permissions/destructive.js +59 -8
  72. package/dist/permissions/destructiveTokens.d.ts +34 -0
  73. package/dist/permissions/destructiveTokens.d.ts.map +1 -0
  74. package/dist/permissions/destructiveTokens.js +475 -0
  75. package/dist/permissions/modePolicy.d.ts +12 -8
  76. package/dist/permissions/modePolicy.d.ts.map +1 -1
  77. package/dist/permissions/modePolicy.js +19 -12
  78. package/dist/permissions/rules.d.ts +4 -1
  79. package/dist/permissions/rules.d.ts.map +1 -1
  80. package/dist/permissions/rules.js +29 -0
  81. package/dist/types.d.ts +47 -3
  82. package/dist/types.d.ts.map +1 -1
  83. package/package.json +8 -7
@@ -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":"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"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/agent/memory.ts"],"names":[],"mappings":"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"}
@@ -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)).trim();
374
- if (summarized) {
375
- active = [...summarized.split('\n').filter((l) => l.trim().length > 0), ...recent];
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
  }
@@ -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":"AAwHA,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,MAAM,EAAE,eAAe,GAAG,oBAAoB,CAAC;IAC/C,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAgE1D;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"}
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"}
@@ -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', 'top', 'uptime', 'free', 'node', 'python', 'python3',
75
- 'tree', 'less', 'more', 'nl', 'tac', 'strings', 'md5sum', 'sha256sum',
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
- // Strip a path prefix: /usr/bin/git → git
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
- // `git config --global x y` WRITES. Only the read form (no value) passes.
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
- if (nested && rest[1] && nested.has(rest[1]))
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
- // `find` can execute and delete.
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
- // `security-auditor` agent's job; this only catches the flagrant cases at the
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 the security-auditor agent's job, not an inline regex's.
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"}