@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.
- package/dist/agent/agentTypes.d.ts.map +1 -1
- package/dist/agent/agentTypes.js +11 -0
- package/dist/agent/loop.d.ts +17 -11
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +62 -61
- package/dist/api/client.d.ts +11 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/permissions/bashClassify.d.ts +12 -0
- package/dist/permissions/bashClassify.d.ts.map +1 -0
- package/dist/permissions/bashClassify.js +73 -0
- package/dist/permissions/modePolicy.d.ts +55 -0
- package/dist/permissions/modePolicy.d.ts.map +1 -0
- package/dist/permissions/modePolicy.js +125 -0
- package/package.json +1 -1
|
@@ -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;
|
|
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"}
|
package/dist/agent/agentTypes.js
CHANGED
|
@@ -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({
|
package/dist/agent/loop.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/agent/loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAKP,UAAU,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,UAAU,CAAC;AAIlB,OAAO,EAAkE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/loop.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
}
|
package/dist/api/client.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/api/client.d.ts.map
CHANGED
|
@@ -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"}
|
package/dist/api/client.js
CHANGED
|
@@ -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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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.
|
|
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)",
|