@nexrall/code-core 1.4.31 → 1.4.33

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.
@@ -1 +1 @@
1
- {"version":3,"file":"agentTypes.d.ts","sourceRoot":"","sources":["../../src/agent/agentTypes.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IAClC,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACpD;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AA4U5D;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAyKD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,CAqB5G;AAED,yFAAyF;AACzF,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,IAAI,SAAS,EAAE,CAE3C;AACD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,CAY1D;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAIjG"}
1
+ {"version":3,"file":"agentTypes.d.ts","sourceRoot":"","sources":["../../src/agent/agentTypes.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IAClC,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACpD;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AA4U5D;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAoLD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,CAqB5G;AAED,yFAAyF;AACzF,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,IAAI,SAAS,EAAE,CAE3C;AACD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,CAY1D;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAIjG"}
@@ -478,6 +478,17 @@ function loadDir(dir, source, into, warnings) {
478
478
  message: `lists unknown tool name(s): ${unknown.join(', ')}. An allowlist only grants, so these silently do nothing and the agent cannot use them. Tool names are lower_snake_case (read_file, search_files, glob, bash).`,
479
479
  });
480
480
  }
481
+ // `task` is a VALID tool name (the main agent has it), which is exactly why it
482
+ // needs its own warning rather than falling under the typo check above: listing it
483
+ // looks like it works and grants nothing. Nesting is capped by MAX_TASK_DEPTH, so
484
+ // sub-agents are never sent the tool at all.
485
+ if ((tools ?? []).includes('task')) {
486
+ warnings.push({
487
+ file: full,
488
+ agent: name,
489
+ message: 'lists `task`, which cannot be granted: sub-agents are never sent that tool because they cannot spawn further sub-agents. Remove it — delegation stays with the main agent.',
490
+ });
491
+ }
481
492
  const strayKeys = Object.keys(meta).filter((k) => !KNOWN_META_KEYS.has(k));
482
493
  if (strayKeys.length) {
483
494
  warnings.push({
@@ -1,5 +1,7 @@
1
1
  import type { Message, ToolResult, AgentLoopOptions, EnvContext } from '../types';
2
2
  import { type AgentMemoryScope } from './agentTypes';
3
+ import { bashNeedsRepoLock } from '../permissions/bashClassify';
4
+ export { bashNeedsRepoLock };
3
5
  /**
4
6
  * Fingerprint one round's tool failures, for the repeated-failure runaway guard.
5
7
  *
@@ -85,17 +87,6 @@ export declare function stopReasonNotice(reason: StopReason, ctx?: {
85
87
  repeatError?: string | null;
86
88
  }): string | null;
87
89
  export declare function resolveMaxIterations(optionValue: number | undefined, settingsRaw: Record<string, unknown>): number;
88
- /**
89
- * Whether a bash command should take the repo-state lock.
90
- *
91
- * Exported for tests: the failure mode (two concurrent `git commit`) is timing-dependent
92
- * and painful to reproduce through the real loop.
93
- *
94
- * Scans EVERY segment, not just the first word, because the mutating verb is routinely
95
- * not leading: `cd packages/core && npm publish`, `FOO=1 git push`, `ls && git commit`.
96
- * Checking only the first token would miss most real commands.
97
- */
98
- export declare function bashNeedsRepoLock(command: string): boolean;
99
90
  /**
100
91
  * Every path a tool call will touch, so all of them can be locked.
101
92
  *
@@ -125,6 +116,21 @@ export declare function resolveMaxConcurrentSubtasks(settingsRaw?: Record<string
125
116
  export declare function createLimiter(max: number): <T>(fn: () => Promise<T>) => Promise<T>;
126
117
  /** Test-only: forget the memoised limiter so a new limit can take effect. */
127
118
  export declare function _resetSubTaskLimiter(): void;
119
+ export declare const NO_NESTED_SUBAGENTS_MSG: string;
120
+ /**
121
+ * Whether a run at `depth` may spawn sub-agents.
122
+ *
123
+ * The single source of truth for THREE things that must agree: the `<available_subagents>`
124
+ * catalogue in the system prompt, whether the backend is asked to send the `task` tool
125
+ * schema at all, and which prompt block teaches delegation. They used to be decided
126
+ * independently, and the result was a sub-agent that got the tool plus instructions to use
127
+ * it but no catalogue — then a permission-gate refusal telling it not to ask for approval.
128
+ *
129
+ * `depth < MAX_TASK_DEPTH` mirrors runSubTask's own guard (which refuses at
130
+ * `depth >= MAX_TASK_DEPTH`), because the children this run would create land at
131
+ * `depth + 1`. Pure and exported so raising MAX_TASK_DEPTH is one edit, not four.
132
+ */
133
+ export declare function canSpawnSubAgents(depth: number, allowedTools?: Set<string>): boolean;
128
134
  /**
129
135
  * How long a sub-agent may make NO progress before it is stopped.
130
136
  *
@@ -1 +1 @@
1
- {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"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"}
@@ -33,16 +33,16 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = exports.AGENT_MEMORY_TOOL_SCHEMA = exports.AGENT_MEMORY_TOOL = exports._stallLimits = void 0;
36
+ exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = exports.NO_NESTED_SUBAGENTS_MSG = exports.AGENT_MEMORY_TOOL_SCHEMA = exports.AGENT_MEMORY_TOOL = exports._stallLimits = exports.bashNeedsRepoLock = void 0;
37
37
  exports.errorRoundSignature = errorRoundSignature;
38
38
  exports.executeAgentMemoryWrite = executeAgentMemoryWrite;
39
39
  exports.stopReasonNotice = stopReasonNotice;
40
40
  exports.resolveMaxIterations = resolveMaxIterations;
41
- exports.bashNeedsRepoLock = bashNeedsRepoLock;
42
41
  exports.lockPathsFor = lockPathsFor;
43
42
  exports.resolveMaxConcurrentSubtasks = resolveMaxConcurrentSubtasks;
44
43
  exports.createLimiter = createLimiter;
45
44
  exports._resetSubTaskLimiter = _resetSubTaskLimiter;
45
+ exports.canSpawnSubAgents = canSpawnSubAgents;
46
46
  exports.resolveSubtaskTimeoutMs = resolveSubtaskTimeoutMs;
47
47
  exports.extractSubTaskText = extractSubTaskText;
48
48
  exports.capSubTaskText = capSubTaskText;
@@ -68,6 +68,11 @@ const executor_1 = require("../tools/executor");
68
68
  const agentTypes_1 = require("./agentTypes");
69
69
  const skills_1 = require("./skills");
70
70
  const rules_1 = require("../permissions/rules");
71
+ // bashNeedsRepoLock lives in permissions/bashClassify.ts, not here: the permission
72
+ // gate needs the same "does this command mutate shared state?" answer, and importing
73
+ // it from loop.ts would make permissions depend on the agent loop (a cycle).
74
+ const bashClassify_1 = require("../permissions/bashClassify");
75
+ Object.defineProperty(exports, "bashNeedsRepoLock", { enumerable: true, get: function () { return bashClassify_1.bashNeedsRepoLock; } });
71
76
  const planMode_1 = require("./planMode");
72
77
  const agentRegistry_1 = require("./agentRegistry");
73
78
  const sandbox_1 = require("../tools/sandbox");
@@ -422,62 +427,6 @@ const WRITE_TOOLS = new Set([
422
427
  * they serialise against each other while everything else stays parallel.
423
428
  */
424
429
  const REPO_STATE_LOCK = '\u0000repo-state';
425
- /**
426
- * Commands that must not run concurrently with another instance of themselves.
427
- *
428
- * `bash` was excluded from the write-tool lock entirely, which was the largest hole in
429
- * the concurrency story: it is the most mutating tool there is, and with 4 sub-agents
430
- * fanned out two of them could run `git commit` simultaneously (one dies on
431
- * `.git/index.lock`), or two `npm install` (corrupted node_modules / lockfile), or two
432
- * `npm test` writing the same coverage and snapshot files (wrong results, not an error).
433
- * The comment above withFileLock explains carefully why two edit_file calls must be
434
- * sequenced, then a `bash -c 'echo x > file'` did exactly that with nothing holding it.
435
- *
436
- * Deliberately a VERB ALLOWLIST rather than serialising all of bash: an explorer's
437
- * `grep`/`rg`/`ls`/`cat` are read-only and are precisely the calls that benefit most
438
- * from running in parallel. Serialising those would trade a real correctness bug for a
439
- * real performance regression. These verbs are the ones that take a global lock, write
440
- * a lockfile, or mutate a shared build/dependency directory.
441
- */
442
- const MUTATING_BASH_VERBS = new Set([
443
- 'git', 'npm', 'pnpm', 'yarn', 'bun', 'cargo', 'go', 'pip', 'pip3', 'poetry', 'uv',
444
- 'bundle', 'composer', 'gradle', 'mvn', 'make', 'terraform', 'helm', 'docker',
445
- 'vsce', 'tsc', 'pytest', 'dotnet', 'swift', 'gem',
446
- ]);
447
- /**
448
- * Whether a bash command should take the repo-state lock.
449
- *
450
- * Exported for tests: the failure mode (two concurrent `git commit`) is timing-dependent
451
- * and painful to reproduce through the real loop.
452
- *
453
- * Scans EVERY segment, not just the first word, because the mutating verb is routinely
454
- * not leading: `cd packages/core && npm publish`, `FOO=1 git push`, `ls && git commit`.
455
- * Checking only the first token would miss most real commands.
456
- */
457
- function bashNeedsRepoLock(command) {
458
- if (!command)
459
- return false;
460
- // Split on shell separators, then take the first bare word of each segment, skipping
461
- // env-var assignments (FOO=bar git push) and `sudo`/`time`-style prefixes.
462
- const segments = command.split(/\|\||&&|[;|&\n()]|\$\(/);
463
- for (const seg of segments) {
464
- for (const tokenRaw of seg.trim().split(/\s+/)) {
465
- const token = tokenRaw.replace(/^["']|["']$/g, '');
466
- if (!token)
467
- continue;
468
- if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(token))
469
- continue; // env assignment
470
- if (token === 'sudo' || token === 'time' || token === 'nice' || token === 'env')
471
- continue;
472
- // Use the basename so `/usr/bin/git` and `./node_modules/.bin/tsc` still match.
473
- const verb = token.split('/').pop() ?? token;
474
- if (MUTATING_BASH_VERBS.has(verb))
475
- return true;
476
- break; // only the command word of each segment matters
477
- }
478
- }
479
- return false;
480
- }
481
430
  /**
482
431
  * Every path a tool call will touch, so all of them can be locked.
483
432
  *
@@ -681,6 +630,37 @@ function humanDescription(name, input) {
681
630
  // sub-agents", and quietly making the worst-case fan-out quadratic. The value and
682
631
  // the documented behaviour now agree.
683
632
  const MAX_TASK_DEPTH = 1;
633
+ // The ONE explanation for "you cannot spawn a sub-agent", shared by both places that
634
+ // can refuse it: the permission gate (an allowlist without `task`) and the depth guard
635
+ // inside runSubTask (a general-purpose sub-task, whose `allowed` set is null, reaches
636
+ // that instead). The same impossibility used to produce two different messages, and the
637
+ // allowlist one framed it as "a restriction of the agent definition" that "needs a
638
+ // different agent" — sending the model off to look for an agent that can nest, when none
639
+ // can. Nesting is structural, so say that once and say it the same way.
640
+ exports.NO_NESTED_SUBAGENTS_MSG = 'Sub-agents cannot spawn further sub-agents — this is a structural limit, not a ' +
641
+ 'permission, so no other agent type and no approval can lift it. Do this work directly ' +
642
+ 'with the tools you have, or report back so the main agent can delegate it.';
643
+ /**
644
+ * Whether a run at `depth` may spawn sub-agents.
645
+ *
646
+ * The single source of truth for THREE things that must agree: the `<available_subagents>`
647
+ * catalogue in the system prompt, whether the backend is asked to send the `task` tool
648
+ * schema at all, and which prompt block teaches delegation. They used to be decided
649
+ * independently, and the result was a sub-agent that got the tool plus instructions to use
650
+ * it but no catalogue — then a permission-gate refusal telling it not to ask for approval.
651
+ *
652
+ * `depth < MAX_TASK_DEPTH` mirrors runSubTask's own guard (which refuses at
653
+ * `depth >= MAX_TASK_DEPTH`), because the children this run would create land at
654
+ * `depth + 1`. Pure and exported so raising MAX_TASK_DEPTH is one edit, not four.
655
+ */
656
+ function canSpawnSubAgents(depth, allowedTools) {
657
+ if (depth >= MAX_TASK_DEPTH)
658
+ return false;
659
+ // An allowlist that omits `task` is the other reason a run cannot delegate. No
660
+ // allowlist at all (the main agent, or a general-purpose sub-task) means no
661
+ // restriction from this clause — the depth check above still applies.
662
+ return allowedTools ? allowedTools.has('task') : true;
663
+ }
684
664
  let _subTaskCounter = 0; // unique per-process id → per-sub-agent todo scope
685
665
  // A sub-agent that stalls (hung tool, model provider stuck, infinite tool-call
686
666
  // loop bypassing the iteration budget somehow) used to have NO ceiling of its
@@ -906,7 +886,7 @@ async function runSubTask(input, options, agentTypes) {
906
886
  return { error: 'task tool requires a non-empty prompt' };
907
887
  const depth = options._depth ?? 0;
908
888
  if (depth >= MAX_TASK_DEPTH) {
909
- return { error: 'Sub-agents cannot spawn further sub-agents. Do this work directly, or report back so the main agent can delegate it.' };
889
+ return { error: exports.NO_NESTED_SUBAGENTS_MSG };
910
890
  }
911
891
  // Resolve an optional custom agent type (subagent_type).
912
892
  //
@@ -1025,6 +1005,21 @@ async function runSubTask(input, options, agentTypes) {
1025
1005
  '`memory:` scope (project, user or local). Report anything worth remembering in your final ' +
1026
1006
  'message instead — the main agent decides what to persist.');
1027
1007
  }
1008
+ // `task` is not an allowlist matter — it is the depth ceiling, so it is refused here
1009
+ // unconditionally rather than via `allowed`. gatedPermission is only ever built inside
1010
+ // runSubTask, so every run it governs is already at depth >= 1 and can never spawn.
1011
+ //
1012
+ // Checking the allowlist instead left a gap: a hand-written `tools: read_file, task`
1013
+ // made the name PRESENT, so neither this branch nor the allowlist branch below fired,
1014
+ // and the call was permitted here only to be refused one frame deeper by runSubTask's
1015
+ // depth guard. Same outcome, but the invariant stopped being local. Refusing on the
1016
+ // tool name keeps it local and cannot be re-opened by an agent file.
1017
+ //
1018
+ // The generic allowlist text is also wrong for this case: it says "report back that
1019
+ // the task needs a different agent", pointing at an escape hatch that cannot exist.
1020
+ if (req.tool === 'task') {
1021
+ throw new ToolNotAllowedError(exports.NO_NESTED_SUBAGENTS_MSG);
1022
+ }
1028
1023
  if (allowed && !allowed.has(req.tool)) {
1029
1024
  throw new ToolNotAllowedError(`The "${agent.name}" sub-agent is not allowed to use \`${req.tool}\` — it is not in that agent's ` +
1030
1025
  'tool allowlist. This is a restriction of the agent definition, NOT a user decision: do not ask ' +
@@ -1953,7 +1948,10 @@ async function runAgentLoop(initialMessages, options) {
1953
1948
  // error path would name it again. Enforcement still happens at dispatch
1954
1949
  // (runSubTask) — this is the cosmetic half; that is the load-bearing half.
1955
1950
  const agentTypes = (0, agentTypes_1.loadAgentTypes)(options.workDir).filter((t) => (0, rules_1.evaluatePermission)(settings.permissions, 'task', { subagent_type: t.name }, options.workDir) !== 'deny');
1956
- const agentsCatalogue = depth === 0 ? (0, agentTypes_1.summariseAgents)(agentTypes) : '';
1951
+ // Can THIS run delegate at all? Previously only the catalogue was gated (`depth === 0`)
1952
+ // while the tool schema and its instructions went to every run — see canSpawnSubAgents.
1953
+ const maySpawn = canSpawnSubAgents(depth, options._allowedTools);
1954
+ const agentsCatalogue = maySpawn ? (0, agentTypes_1.summariseAgents)(agentTypes) : '';
1957
1955
  // Skills catalogue — unlike agentsCatalogue, available at every depth: a skill is
1958
1956
  // just a reusable prompt template (via use_skill), not another spawn point, so
1959
1957
  // sub-agents benefit from the same playbooks without the recursion concerns that
@@ -2276,6 +2274,9 @@ async function runAgentLoop(initialMessages, options) {
2276
2274
  ? options._allowedTools.has('memory_write')
2277
2275
  : true, // no allowlist = main agent = may write
2278
2276
  hasOwnAgentStore: !!options._agentMemory,
2277
+ // Withholds the `task` schema and its instructions when this run cannot
2278
+ // delegate — see canSpawnSubAgents.
2279
+ canSpawnSubAgents: maySpawn,
2279
2280
  agents: agentsCatalogue || undefined,
2280
2281
  skills: skillsCatalogue || undefined,
2281
2282
  // Only allow a post-render restart when the caller actually implements the
@@ -2650,7 +2651,7 @@ async function runAgentLoop(initialMessages, options) {
2650
2651
  // This matters far more with sub-agents than without: up to 4 run
2651
2652
  // concurrently, each issuing its own tool calls into this same process.
2652
2653
  const locks = name === 'bash'
2653
- ? (bashNeedsRepoLock(String(input.command ?? '')) ? [REPO_STATE_LOCK] : [])
2654
+ ? ((0, bashClassify_1.bashNeedsRepoLock)(String(input.command ?? '')) ? [REPO_STATE_LOCK] : [])
2654
2655
  : lockPathsFor(name, input, options.workDir);
2655
2656
  result = locks.length ? await withFileLocks(locks, run) : await run();
2656
2657
  }
@@ -55,6 +55,17 @@ export interface StreamChatOptions {
55
55
  canWriteSharedMemory?: boolean;
56
56
  /** Whether this run is a sub-agent with its own `memory:` store (agent_memory_write). */
57
57
  hasOwnAgentStore?: boolean;
58
+ /**
59
+ * Whether THIS run may spawn sub-agents, i.e. whether the backend should send the
60
+ * `task` tool schema and the prompt block that teaches it.
61
+ *
62
+ * Same rule as canWriteSharedMemory, applied to a worse case: `task` was in the
63
+ * backend's static catalogue, so every sub-agent SAW the tool and was told to use it,
64
+ * then got refused by its own allowlist at the permission gate. Withholding the schema
65
+ * is the fix — a tool the model cannot see is one it cannot try. Undefined means
66
+ * "main agent" (may spawn).
67
+ */
68
+ canSpawnSubAgents?: boolean;
58
69
  /**
59
70
  * Opt in to restarting a turn that died AFTER partial output was rendered.
60
71
  *
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAA8B,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAiErH,eAAO,MAAM,QAAQ,QAAmB,CAAC;AAIzC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5D,OAAO,EAAE,CAAC,EAAE,EACZ,UAAU,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GACtD,CAAC,EAAE,CAeL;AAiGD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACnC,oFAAoF;IACpF,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IACjG,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6IAA6I;IAC7I,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,yFAAyF;IACzF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;;;;;OAeG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAiDD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAC7B,OAAO,CAAC,OAAO,CAAC,CAwlClB;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9D;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAYxD;AAID,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CA0BlD;AAID,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA+B1E;AAID,wBAAsB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA0BhF"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAA8B,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAiErH,eAAO,MAAM,QAAQ,QAAmB,CAAC;AAIzC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5D,OAAO,EAAE,CAAC,EAAE,EACZ,UAAU,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GACtD,CAAC,EAAE,CAeL;AAiGD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACnC,oFAAoF;IACpF,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IACjG,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6IAA6I;IAC7I,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,yFAAyF;IACzF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;;;;OAeG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAiDD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAC7B,OAAO,CAAC,OAAO,CAAC,CAwlClB;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9D;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAYxD;AAID,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CA0BlD;AAID,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA+B1E;AAID,wBAAsB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA0BhF"}
@@ -243,7 +243,7 @@ function sleep(ms) {
243
243
  * the nested product (36) exists to bound pathological cases, not to be reached. */
244
244
  const MAX_TOTAL_ATTEMPTS = (MAX_RETRIES + 1) * (MAX_RETRIES + 1);
245
245
  async function streamChat(messages, options, onEvent) {
246
- const { model, env, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents, skills, allowRestartAfterRender, canWriteSharedMemory, hasOwnAgentStore } = options;
246
+ const { model, env, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents, skills, allowRestartAfterRender, canWriteSharedMemory, hasOwnAgentStore, canSpawnSubAgents } = options;
247
247
  // IDEMPOTENCY KEY for this logical turn, generated ONCE and reused by every retry
248
248
  // below (that reuse is the entire point — a fresh id per attempt would be useless).
249
249
  //
@@ -331,7 +331,7 @@ async function streamChat(messages, options, onEvent) {
331
331
  {
332
332
  method: 'POST',
333
333
  headers: authHeaders(),
334
- body: JSON.stringify({ messages, model, env, editorContext, nexrallMd, mode, effort, clientType, extraTools, agents, skills, turnId, canWriteSharedMemory, hasOwnAgentStore }),
334
+ body: JSON.stringify({ messages, model, env, editorContext, nexrallMd, mode, effort, clientType, extraTools, agents, skills, turnId, canWriteSharedMemory, hasOwnAgentStore, canSpawnSubAgents }),
335
335
  signal: controller.signal,
336
336
  },
337
337
  ];
package/dist/index.d.ts CHANGED
@@ -20,6 +20,8 @@ export * from './agent/agentTypes';
20
20
  export * from './agent/planMode';
21
21
  export * from './agent/agentRegistry';
22
22
  export * from './permissions/rules';
23
+ export * from './permissions/modePolicy';
24
+ export * from './permissions/bashClassify';
23
25
  export * from './permissions/destructive';
24
26
  export * from './plugins/index';
25
27
  export * from './plugins/installer';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -36,6 +36,8 @@ __exportStar(require("./agent/agentTypes"), exports);
36
36
  __exportStar(require("./agent/planMode"), exports);
37
37
  __exportStar(require("./agent/agentRegistry"), exports);
38
38
  __exportStar(require("./permissions/rules"), exports);
39
+ __exportStar(require("./permissions/modePolicy"), exports);
40
+ __exportStar(require("./permissions/bashClassify"), exports);
39
41
  __exportStar(require("./permissions/destructive"), exports);
40
42
  __exportStar(require("./plugins/index"), exports);
41
43
  __exportStar(require("./plugins/installer"), exports);
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Whether a bash command should take the repo-state lock.
3
+ *
4
+ * Exported for tests: the failure mode (two concurrent `git commit`) is timing-dependent
5
+ * and painful to reproduce through the real loop.
6
+ *
7
+ * Scans EVERY segment, not just the first word, because the mutating verb is routinely
8
+ * not leading: `cd packages/core && npm publish`, `FOO=1 git push`, `ls && git commit`.
9
+ * Checking only the first token would miss most real commands.
10
+ */
11
+ export declare function bashNeedsRepoLock(command: string): boolean;
12
+ //# sourceMappingURL=bashClassify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bashClassify.d.ts","sourceRoot":"","sources":["../../src/permissions/bashClassify.ts"],"names":[],"mappings":"AAqCA;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAkB1D"}
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ // ─── Classifying shell commands ───────────────────────────────────────────────
3
+ //
4
+ // Lives in permissions/ rather than agent/loop.ts because TWO subsystems need the same
5
+ // answer to "does this command mutate shared state?":
6
+ //
7
+ // • the concurrency locks — two `git commit` must not interleave;
8
+ // • the permission gate — `auto` mode runs `npm test` without asking but must still
9
+ // prompt for anything with wider side effects.
10
+ //
11
+ // Keeping one classifier means those can never disagree. Two copies would be a second
12
+ // source of truth that has to stay in sync forever, which is the exact class of bug this
13
+ // codebase keeps turning up (a prompt promising what the gate didn't enforce; a mode the
14
+ // gate never read).
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.bashNeedsRepoLock = bashNeedsRepoLock;
17
+ /**
18
+ * Commands that must not run concurrently with another instance of themselves.
19
+ *
20
+ * `bash` was excluded from the write-tool lock entirely, which was the largest hole in
21
+ * the concurrency story: it is the most mutating tool there is, and with 4 sub-agents
22
+ * fanned out two of them could run `git commit` simultaneously (one dies on
23
+ * `.git/index.lock`), or two `npm install` (corrupted node_modules / lockfile), or two
24
+ * `npm test` writing the same coverage and snapshot files (wrong results, not an error).
25
+ * The comment above withFileLock explains carefully why two edit_file calls must be
26
+ * sequenced, then a `bash -c 'echo x > file'` did exactly that with nothing holding it.
27
+ *
28
+ * Deliberately a VERB ALLOWLIST rather than serialising all of bash: an explorer's
29
+ * `grep`/`rg`/`ls`/`cat` are read-only and are precisely the calls that benefit most
30
+ * from running in parallel. Serialising those would trade a real correctness bug for a
31
+ * real performance regression. These verbs are the ones that take a global lock, write
32
+ * a lockfile, or mutate a shared build/dependency directory.
33
+ */
34
+ const MUTATING_BASH_VERBS = new Set([
35
+ 'git', 'npm', 'pnpm', 'yarn', 'bun', 'cargo', 'go', 'pip', 'pip3', 'poetry', 'uv',
36
+ 'bundle', 'composer', 'gradle', 'mvn', 'make', 'terraform', 'helm', 'docker',
37
+ 'vsce', 'tsc', 'pytest', 'dotnet', 'swift', 'gem',
38
+ ]);
39
+ /**
40
+ * Whether a bash command should take the repo-state lock.
41
+ *
42
+ * Exported for tests: the failure mode (two concurrent `git commit`) is timing-dependent
43
+ * and painful to reproduce through the real loop.
44
+ *
45
+ * Scans EVERY segment, not just the first word, because the mutating verb is routinely
46
+ * not leading: `cd packages/core && npm publish`, `FOO=1 git push`, `ls && git commit`.
47
+ * Checking only the first token would miss most real commands.
48
+ */
49
+ function bashNeedsRepoLock(command) {
50
+ if (!command)
51
+ return false;
52
+ // Split on shell separators, then take the first bare word of each segment, skipping
53
+ // env-var assignments (FOO=bar git push) and `sudo`/`time`-style prefixes.
54
+ const segments = command.split(/\|\||&&|[;|&\n()]|\$\(/);
55
+ for (const seg of segments) {
56
+ for (const tokenRaw of seg.trim().split(/\s+/)) {
57
+ const token = tokenRaw.replace(/^["']|["']$/g, '');
58
+ if (!token)
59
+ continue;
60
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(token))
61
+ continue; // env assignment
62
+ if (token === 'sudo' || token === 'time' || token === 'nice' || token === 'env')
63
+ continue;
64
+ // Use the basename so `/usr/bin/git` and `./node_modules/.bin/tsc` still match.
65
+ const verb = token.split('/').pop() ?? token;
66
+ if (MUTATING_BASH_VERBS.has(verb))
67
+ return true;
68
+ break; // only the command word of each segment matters
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+ //# sourceMappingURL=bashClassify.js.map
@@ -0,0 +1,55 @@
1
+ /** Permission modes, in increasing order of autonomy. */
2
+ export type PermissionMode = 'plan' | 'ask' | 'edit' | 'auto';
3
+ /** What the gate should do with one tool call. */
4
+ export type PolicyDecision = 'allow' | 'ask' | 'deny' | 'confirm';
5
+ export interface PolicyInput {
6
+ tool: string;
7
+ input: Record<string, unknown>;
8
+ mode: PermissionMode;
9
+ /** True when the user has already opted into blanket approval (--yolo / bypass). */
10
+ bypass?: boolean;
11
+ /** True when this call is irreversible (DB drop, force-push, rm -rf …). */
12
+ destructive?: boolean;
13
+ }
14
+ /**
15
+ * Tools that only ever READ. Always allowed, in every mode including plan.
16
+ *
17
+ * Kept here rather than in each client so "read-only" cannot mean two different things
18
+ * in two windows. Note `memory_write` is NOT here even though the CLI's old list had it
19
+ * — it mutates durable cross-session state, and listing it under "read-only / safe"
20
+ * meant it silently bypassed plan mode's read-only guarantee.
21
+ */
22
+ export declare const READ_ONLY_TOOLS: Set<string>;
23
+ /** Tools that write to the filesystem. */
24
+ export declare const FILE_WRITE_TOOLS: Set<string>;
25
+ /** Tools that move/delete files — riskier than an in-place edit. */
26
+ export declare const FILE_MUTATE_TOOLS: Set<string>;
27
+ /** Tools that run a shell command. */
28
+ export declare const BASH_TOOLS: Set<string>;
29
+ /**
30
+ * Is this bash command read-only?
31
+ *
32
+ * Deliberately delegates to `bashNeedsRepoLock`, the classifier written for the
33
+ * concurrency work: it already distinguishes commands that mutate shared state
34
+ * (`git`, `npm`, `cargo`, `make` …) from ones that only read (`grep`, `ls`, `cat`,
35
+ * `rg`), scanning every shell segment so `cd x && npm publish` is caught.
36
+ *
37
+ * Reusing it is the point. Writing a second classifier here would create two
38
+ * definitions of "mutating" that must agree forever — the exact failure this whole
39
+ * audit keeps finding. If the lock treats a command as mutating, so does the gate.
40
+ */
41
+ export declare function isReadOnlyBash(command: string): boolean;
42
+ /**
43
+ * The single source of truth for "should we prompt?".
44
+ *
45
+ * Order matters and mirrors Claude Code's: destructive actions are checked BEFORE any
46
+ * blanket approval, so an irreversible command can never ride `--yolo` or `auto`
47
+ * straight to execution. That ordering has a body count in the industry (dropped
48
+ * production databases), and it is the one rule no convenience setting may override.
49
+ */
50
+ export declare function decide(req: PolicyInput): PolicyDecision;
51
+ /** Human-readable one-liner for the footer / status bar. */
52
+ export declare function describeMode(mode: PermissionMode): string;
53
+ /** Normalise arbitrary input to a known mode, defaulting to the safe middle. */
54
+ export declare function parseMode(raw: unknown): PermissionMode;
55
+ //# sourceMappingURL=modePolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modePolicy.d.ts","sourceRoot":"","sources":["../../src/permissions/modePolicy.ts"],"names":[],"mappings":"AAsBA,yDAAyD;AACzD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,kDAAkD;AAClD,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,KAAK,GACL,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,oFAAoF;IACpF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,aAU1B,CAAC;AAEH,0CAA0C;AAC1C,eAAO,MAAM,gBAAgB,aAE3B,CAAC;AAEH,oEAAoE;AACpE,eAAO,MAAM,iBAAiB,aAE5B,CAAC;AAEH,sCAAsC;AACtC,eAAO,MAAM,UAAU,aAAsD,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,CAmDvD;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAOzD;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAGtD"}
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASH_TOOLS = exports.FILE_MUTATE_TOOLS = exports.FILE_WRITE_TOOLS = exports.READ_ONLY_TOOLS = void 0;
4
+ exports.isReadOnlyBash = isReadOnlyBash;
5
+ exports.decide = decide;
6
+ exports.describeMode = describeMode;
7
+ exports.parseMode = parseMode;
8
+ const bashClassify_1 = require("./bashClassify");
9
+ /**
10
+ * Tools that only ever READ. Always allowed, in every mode including plan.
11
+ *
12
+ * Kept here rather than in each client so "read-only" cannot mean two different things
13
+ * in two windows. Note `memory_write` is NOT here even though the CLI's old list had it
14
+ * — it mutates durable cross-session state, and listing it under "read-only / safe"
15
+ * meant it silently bypassed plan mode's read-only guarantee.
16
+ */
17
+ exports.READ_ONLY_TOOLS = new Set([
18
+ 'read_file', 'list_directory', 'search_files', 'glob', 'grep',
19
+ 'fetch_url', 'web_search',
20
+ 'bash_output', 'kill_shell',
21
+ 'todo_read', 'todo_write',
22
+ 'memory_read',
23
+ 'use_skill',
24
+ 'notebook_read',
25
+ 'get_diagnostics', 'go_to_definition', 'find_references',
26
+ 'get_symbols', 'get_workspace_symbols', 'get_hover',
27
+ ]);
28
+ /** Tools that write to the filesystem. */
29
+ exports.FILE_WRITE_TOOLS = new Set([
30
+ 'write_file', 'create_file', 'edit_file', 'multi_edit', 'notebook_edit',
31
+ ]);
32
+ /** Tools that move/delete files — riskier than an in-place edit. */
33
+ exports.FILE_MUTATE_TOOLS = new Set([
34
+ 'delete_file', 'move_file', 'copy_file',
35
+ ]);
36
+ /** Tools that run a shell command. */
37
+ exports.BASH_TOOLS = new Set(['bash', 'run_command', 'execute_command']);
38
+ /**
39
+ * Is this bash command read-only?
40
+ *
41
+ * Deliberately delegates to `bashNeedsRepoLock`, the classifier written for the
42
+ * concurrency work: it already distinguishes commands that mutate shared state
43
+ * (`git`, `npm`, `cargo`, `make` …) from ones that only read (`grep`, `ls`, `cat`,
44
+ * `rg`), scanning every shell segment so `cd x && npm publish` is caught.
45
+ *
46
+ * Reusing it is the point. Writing a second classifier here would create two
47
+ * definitions of "mutating" that must agree forever — the exact failure this whole
48
+ * audit keeps finding. If the lock treats a command as mutating, so does the gate.
49
+ */
50
+ function isReadOnlyBash(command) {
51
+ return !(0, bashClassify_1.bashNeedsRepoLock)(command);
52
+ }
53
+ /**
54
+ * The single source of truth for "should we prompt?".
55
+ *
56
+ * Order matters and mirrors Claude Code's: destructive actions are checked BEFORE any
57
+ * blanket approval, so an irreversible command can never ride `--yolo` or `auto`
58
+ * straight to execution. That ordering has a body count in the industry (dropped
59
+ * production databases), and it is the one rule no convenience setting may override.
60
+ */
61
+ function decide(req) {
62
+ const { tool, mode } = req;
63
+ const command = String(req.input.command ?? req.input.cmd ?? '');
64
+ // 1. Reads are always fine — an agent that cannot read cannot help.
65
+ // Checked before plan mode too: plan mode is read-ONLY, not read-nothing.
66
+ if (exports.READ_ONLY_TOOLS.has(tool))
67
+ return 'allow';
68
+ if (exports.BASH_TOOLS.has(tool) && isReadOnlyBash(command) && !req.destructive)
69
+ return 'allow';
70
+ // 2. Irreversible actions ALWAYS need an explicit human "yes".
71
+ // Before bypass, before mode. `rm -rf`, `git push --force`, `DROP TABLE` are not
72
+ // things any mode should silently perform, and the strong confirm treatment (type
73
+ // the word "yes", no "approve all") is what makes it a real speed bump.
74
+ if (req.destructive)
75
+ return 'confirm';
76
+ // 3. Plan mode: refuse every remaining (i.e. mutating) tool outright.
77
+ // A refusal, not a prompt — the guarantee is "nothing changes until I approve the
78
+ // plan", and a prompt would let one distracted keypress break it.
79
+ if (mode === 'plan')
80
+ return 'deny';
81
+ // 4. Explicit blanket approval (--yolo / bypassPermissions). Deliberately after the
82
+ // destructive check above.
83
+ if (req.bypass)
84
+ return 'allow';
85
+ // 5. Mode policy for the mutating tools that remain.
86
+ if (exports.FILE_WRITE_TOOLS.has(tool) || exports.FILE_MUTATE_TOOLS.has(tool)) {
87
+ // `edit` and `auto` both pre-authorise ordinary file edits — that is the whole
88
+ // reason to leave `ask`.
89
+ return mode === 'ask' ? 'ask' : 'allow';
90
+ }
91
+ if (exports.BASH_TOOLS.has(tool)) {
92
+ // A mutating-but-recoverable command: `npm test`, `npm install`, `git commit`,
93
+ // `cargo build`. `auto` runs these, which is the behaviour its own prompt block
94
+ // already advertises. `edit` still asks, matching Claude Code's acceptEdits —
95
+ // "file changes flow, anything with side effects beyond the working directory
96
+ // still prompts" — because shell commands reach the network, global caches and
97
+ // other checkouts, not just this repo.
98
+ return mode === 'auto' ? 'allow' : 'ask';
99
+ }
100
+ // 6. Anything unrecognised — a newly added tool, an MCP tool, a plugin tool — asks,
101
+ // in EVERY mode including `auto`.
102
+ //
103
+ // Fail-CLOSED on permission, deliberately: a tool nobody has classified must not
104
+ // inherit autonomy merely because the user chose a permissive mode. An MCP server
105
+ // is third-party code whose blast radius this policy cannot see, so `auto` meaning
106
+ // "run anything, including things I've never heard of" would make the mode
107
+ // unauditable. Same principle as forcing an unparseable agent definition to
108
+ // read-only: the less we know, the less it gets.
109
+ return 'ask';
110
+ }
111
+ /** Human-readable one-liner for the footer / status bar. */
112
+ function describeMode(mode) {
113
+ switch (mode) {
114
+ case 'plan': return 'plan — read-only, produces a plan for approval';
115
+ case 'ask': return 'ask — approves reads; asks before edits and commands';
116
+ case 'edit': return 'edit — edits files freely; asks before shell commands';
117
+ case 'auto': return 'auto — edits and runs commands; still confirms irreversible actions';
118
+ }
119
+ }
120
+ /** Normalise arbitrary input to a known mode, defaulting to the safe middle. */
121
+ function parseMode(raw) {
122
+ const s = String(raw ?? '').trim().toLowerCase();
123
+ return s === 'plan' || s === 'ask' || s === 'edit' || s === 'auto' ? s : 'ask';
124
+ }
125
+ //# sourceMappingURL=modePolicy.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexrall/code-core",
3
- "version": "1.4.31",
3
+ "version": "1.4.33",
4
4
  "description": "Core agent loop, tools, and extension primitives for Nexrall Code — embed an AI coding agent in any Node.js application.",
5
5
  "license": "MIT",
6
6
  "author": "Nexrall <support@nexrall.com> (https://nexrall.com)",