@nexrall/code-core 1.4.74 → 1.4.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/agent/askOnce.d.ts +10 -0
- package/dist/agent/askOnce.d.ts.map +1 -1
- package/dist/agent/askOnce.js +10 -2
- package/dist/agent/planMode.d.ts.map +1 -1
- package/dist/agent/planMode.js +16 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/permissions/autoMode.d.ts +89 -0
- package/dist/permissions/autoMode.d.ts.map +1 -0
- package/dist/permissions/autoMode.js +346 -0
- package/dist/permissions/rules.d.ts +6 -0
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +33 -0
- package/dist/plugins/eval.d.ts +199 -0
- package/dist/plugins/eval.d.ts.map +1 -0
- package/dist/plugins/eval.js +470 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ npm install @nexrall/code-core
|
|
|
12
12
|
|--------|-------------|
|
|
13
13
|
| `@nexrall/code-core` | Everything via the root export |
|
|
14
14
|
| `@nexrall/code-core/agent` | `runAgentLoop` — the main agentic loop |
|
|
15
|
+
| `@nexrall/code-core/agent-types` | `loadAgentTypes`, `findAgentType` — sub-agent type definitions |
|
|
15
16
|
| `@nexrall/code-core/tools` | `executeTool` — built-in tool executor (bash, file I/O, search…) |
|
|
16
17
|
| `@nexrall/code-core/symbols` | `getSymbols`, `getWorkspaceSymbols` — regex-based LSP-lite scanner |
|
|
17
18
|
| `@nexrall/code-core/checkpoint` | `CheckpointManager` — persistent rewind / rollback |
|
|
@@ -33,7 +34,7 @@ const messages = [
|
|
|
33
34
|
];
|
|
34
35
|
|
|
35
36
|
const opts: AgentLoopOptions = {
|
|
36
|
-
model: '
|
|
37
|
+
model: 'claude-sonnet-5-5', // any catalogue model id; the tier aliases still work
|
|
37
38
|
workDir: process.cwd(),
|
|
38
39
|
env: { platform: 'node', cwd: process.cwd(), shell: 'bash' },
|
|
39
40
|
clientType: 'cli',
|
|
@@ -53,13 +54,13 @@ const history = await runAgentLoop(messages, opts);
|
|
|
53
54
|
console.log('Done —', history.length, 'messages');
|
|
54
55
|
```
|
|
55
56
|
|
|
56
|
-
> **Requires authentication.** The agent streams through the Nexrall API — users must be logged in via `
|
|
57
|
+
> **Requires authentication.** The agent streams through the Nexrall API — users must be logged in via `nex auth` (or set the `NEXRALL_TOKEN` env var).
|
|
57
58
|
|
|
58
59
|
## Agent loop options
|
|
59
60
|
|
|
60
61
|
```ts
|
|
61
62
|
interface AgentLoopOptions {
|
|
62
|
-
model
|
|
63
|
+
model?: string; // e.g. 'claude-sonnet-5-5', 'gpt-6-astra', 'deepseek-flash'
|
|
63
64
|
workDir: string;
|
|
64
65
|
env: EnvContext;
|
|
65
66
|
clientType?: 'cli' | 'vscode';
|
package/dist/agent/askOnce.d.ts
CHANGED
|
@@ -7,6 +7,16 @@ export interface AskOnceOptions {
|
|
|
7
7
|
abortSignal?: {
|
|
8
8
|
aborted: boolean;
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* S20: the server-reported usage (and cost, when it sent one) of this call.
|
|
12
|
+
* Optional and additive — the auto-mode classifier uses it to enforce its
|
|
13
|
+
* per-session budget; every existing caller ignores it.
|
|
14
|
+
*/
|
|
15
|
+
onUsage?: (u: {
|
|
16
|
+
input_tokens?: number;
|
|
17
|
+
output_tokens?: number;
|
|
18
|
+
cost?: number;
|
|
19
|
+
}) => void;
|
|
10
20
|
}
|
|
11
21
|
export declare function askModelOnce(prompt: string, opts: AskOnceOptions): Promise<string | null>;
|
|
12
22
|
//# sourceMappingURL=askOnce.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"askOnce.d.ts","sourceRoot":"","sources":["../../src/agent/askOnce.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAgB,UAAU,EAAW,MAAM,UAAU,CAAC;AAKlE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"askOnce.d.ts","sourceRoot":"","sources":["../../src/agent/askOnce.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAgB,UAAU,EAAW,MAAM,UAAU,CAAC;AAKlE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACzF;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgD/F"}
|
package/dist/agent/askOnce.js
CHANGED
|
@@ -28,8 +28,16 @@ async function askModelOnce(prompt, opts) {
|
|
|
28
28
|
env: opts.env ?? { cwd: opts.cwd },
|
|
29
29
|
abortSignal: ctl,
|
|
30
30
|
toolAllowlist: [NO_TOOLS_MARKER],
|
|
31
|
-
}, () => {
|
|
32
|
-
|
|
31
|
+
}, (e) => {
|
|
32
|
+
// The final message carries the text; this tap only forwards usage.
|
|
33
|
+
if (opts.onUsage && e && e.type === 'usage') {
|
|
34
|
+
const ev = e;
|
|
35
|
+
opts.onUsage({
|
|
36
|
+
input_tokens: ev.usage?.input_tokens,
|
|
37
|
+
output_tokens: ev.usage?.output_tokens,
|
|
38
|
+
...(typeof ev.cost === 'number' ? { cost: ev.cost } : {}),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
33
41
|
});
|
|
34
42
|
const blocks = Array.isArray(msg.content) ? msg.content : [];
|
|
35
43
|
const text = blocks
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planMode.d.ts","sourceRoot":"","sources":["../../src/agent/planMode.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"planMode.d.ts","sourceRoot":"","sources":["../../src/agent/planMode.ts"],"names":[],"mappings":"AAqPA,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,MAAM,EAAE,eAAe,GAAG,oBAAoB,CAAC;IAC/C,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;CACjB;AAuKD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAgE1D;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAU3D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,GAAG,IAAI,CAoClG;AAED,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,QAqBvB,CAAC"}
|
package/dist/agent/planMode.js
CHANGED
|
@@ -163,6 +163,10 @@ const SYSTEM_BIN_DIRS = new Set(['/bin', '/usr/bin', '/usr/local/bin', '/sbin',
|
|
|
163
163
|
*/
|
|
164
164
|
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;
|
|
165
165
|
const GH_NOUNS = new Set(['pr', 'issue', 'run', 'repo', 'release', 'workflow', 'gist', 'label']);
|
|
166
|
+
/** glab's resource nouns — the guard against `glab api <verb>` (arbitrary HTTP), same as gh. */
|
|
167
|
+
const GLAB_NOUNS = new Set(['mr', 'issue', 'ci', 'repo', 'release', 'pipeline', 'job', 'variable', 'snippet']);
|
|
168
|
+
/** Which tools need a noun guard on their nested form (see the nested branch below). */
|
|
169
|
+
const NESTED_NOUNS = { gh: GH_NOUNS, glab: GLAB_NOUNS };
|
|
166
170
|
/**
|
|
167
171
|
* Subcommands that are read-only for tools whose safety depends on the verb.
|
|
168
172
|
*
|
|
@@ -208,10 +212,17 @@ const READ_ONLY_SUBCOMMANDS = {
|
|
|
208
212
|
// gh: read verbs only. `gh run rerun`, `gh pr merge` etc. are excluded, and
|
|
209
213
|
// the nested-verb check in isReadOnlyCommand handles `gh pr view` vs `gh pr merge`.
|
|
210
214
|
gh: new Set(['browse']),
|
|
215
|
+
// glab: everything read-only lives in the nested form (`glab mr view`), so the
|
|
216
|
+
// top-level set is empty and bare `glab` (help) is the only direct read.
|
|
217
|
+
glab: new Set([]),
|
|
211
218
|
};
|
|
212
219
|
/** Second-level read verbs for CLIs shaped as `<tool> <noun> <verb>`. */
|
|
213
220
|
const READ_ONLY_NESTED = {
|
|
214
221
|
gh: new Set(['view', 'list', 'status', 'diff', 'checks']),
|
|
222
|
+
// glab mirrors gh's readable set: `glab mr view/diff`, `glab ci status`. Writes
|
|
223
|
+
// (`glab mr note`, `merge`, `close`) are absent on purpose — S16b posts a review
|
|
224
|
+
// comment through the main agent, never through read-only classification.
|
|
225
|
+
glab: new Set(['view', 'list', 'diff', 'status']),
|
|
215
226
|
};
|
|
216
227
|
/**
|
|
217
228
|
* Shell metacharacters that can smuggle a second command past verb inspection.
|
|
@@ -418,9 +429,11 @@ function isReadOnlyCommand(command) {
|
|
|
418
429
|
if (subs.has(sub))
|
|
419
430
|
return subcommandFlagsOk(verb, sub, words.slice(i + 1));
|
|
420
431
|
const nested = READ_ONLY_NESTED[verb];
|
|
421
|
-
// `gh api list -X DELETE` has "list" in second
|
|
422
|
-
// real resource noun
|
|
423
|
-
|
|
432
|
+
// `gh api list -X DELETE` (and `glab api done ...`) has "list"/"diff" in second
|
|
433
|
+
// position too — the noun must be a real resource noun; `api` (arbitrary HTTP
|
|
434
|
+
// verbs) is not one. Tools without a noun list behave as before.
|
|
435
|
+
const nouns = NESTED_NOUNS[verb];
|
|
436
|
+
if (nested && rest[1] && nested.has(rest[1]) && (!nouns || nouns.has(sub)))
|
|
424
437
|
return true;
|
|
425
438
|
return false;
|
|
426
439
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -32,10 +32,12 @@ export * from './permissions/rules';
|
|
|
32
32
|
export * from './permissions/modePolicy';
|
|
33
33
|
export * from './permissions/bashClassify';
|
|
34
34
|
export * from './permissions/destructive';
|
|
35
|
+
export * from './permissions/autoMode';
|
|
35
36
|
export * from './plugins/index';
|
|
36
37
|
export * from './plugins/installer';
|
|
37
38
|
export * from './plugins/sources';
|
|
38
39
|
export * from './plugins/data';
|
|
40
|
+
export * from './plugins/eval';
|
|
39
41
|
export * from './agent/trust';
|
|
40
42
|
export * from './agent/crossProcessLock';
|
|
41
43
|
export * from './agent/worktree';
|
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,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,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,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,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;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,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,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -48,10 +48,12 @@ __exportStar(require("./permissions/rules"), exports);
|
|
|
48
48
|
__exportStar(require("./permissions/modePolicy"), exports);
|
|
49
49
|
__exportStar(require("./permissions/bashClassify"), exports);
|
|
50
50
|
__exportStar(require("./permissions/destructive"), exports);
|
|
51
|
+
__exportStar(require("./permissions/autoMode"), exports);
|
|
51
52
|
__exportStar(require("./plugins/index"), exports);
|
|
52
53
|
__exportStar(require("./plugins/installer"), exports);
|
|
53
54
|
__exportStar(require("./plugins/sources"), exports);
|
|
54
55
|
__exportStar(require("./plugins/data"), exports);
|
|
56
|
+
__exportStar(require("./plugins/eval"), exports);
|
|
55
57
|
__exportStar(require("./agent/trust"), exports);
|
|
56
58
|
__exportStar(require("./agent/crossProcessLock"), exports);
|
|
57
59
|
__exportStar(require("./agent/worktree"), exports);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export interface AutoModeLimitDef {
|
|
2
|
+
/** Short name shown by `nex auto-mode defaults` and in reports. */
|
|
3
|
+
label: string;
|
|
4
|
+
/** The prose the classifier reads. One sentence, concrete. */
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The built-in hard limits (CC's `$defaults` for `hard_deny`): unconditional —
|
|
9
|
+
* user intent does NOT clear these. Keep this list SHORT and beyond argument;
|
|
10
|
+
* it exists for the actions no request should ever authorize.
|
|
11
|
+
*/
|
|
12
|
+
export declare const AUTO_MODE_HARD_DENY_DEFAULTS: readonly AutoModeLimitDef[];
|
|
13
|
+
/**
|
|
14
|
+
* The built-in soft limits (CC's `$defaults` for `soft_deny`): block UNLESS the
|
|
15
|
+
* user's own latest message directly and specifically asks for this exact
|
|
16
|
+
* action, or an allow exception matches.
|
|
17
|
+
*/
|
|
18
|
+
export declare const AUTO_MODE_SOFT_DENY_DEFAULTS: readonly AutoModeLimitDef[];
|
|
19
|
+
/** The `$defaults` sentinel (CC's spelling) inside soft_deny / hard_deny lists. */
|
|
20
|
+
export declare const AUTO_MODE_DEFAULTS_SENTINEL = "$defaults";
|
|
21
|
+
export declare const AUTO_MODE_DEFAULT_MAX_CLASSIFICATIONS = 400;
|
|
22
|
+
export declare const AUTO_MODE_DEFAULT_MAX_BUDGET_USD = 0.25;
|
|
23
|
+
export interface AutoModeConfig {
|
|
24
|
+
/** Whether the classifier runs in auto mode at all. Default true. */
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
/** Model for the review call; undefined = the session model. */
|
|
27
|
+
classifierModel?: string;
|
|
28
|
+
/** Prose: trusted infra (repos, buckets, domains, deploy targets). */
|
|
29
|
+
environment: string[];
|
|
30
|
+
/** Pre-approved patterns; clear soft blocks (never hard ones). */
|
|
31
|
+
allow: string[];
|
|
32
|
+
/** Effective soft limits (tier values + built-ins when $defaults applies). */
|
|
33
|
+
softDeny: string[];
|
|
34
|
+
/** Effective hard limits. */
|
|
35
|
+
hardDeny: string[];
|
|
36
|
+
/** Suspend EVERY bash allow rule while classifying (CC's flag). Default false. */
|
|
37
|
+
classifyAllShell: boolean;
|
|
38
|
+
maxClassifications: number;
|
|
39
|
+
maxBudgetUsd: number;
|
|
40
|
+
/** Which file (or '--settings') set which key — for `nex auto-mode config`. */
|
|
41
|
+
sources: Array<{
|
|
42
|
+
file: string;
|
|
43
|
+
keys: string[];
|
|
44
|
+
}>;
|
|
45
|
+
/** Project/local settings that tried to set `autoMode`; ignored for safety. */
|
|
46
|
+
ignoredProjectFiles: string[];
|
|
47
|
+
/** Non-fatal problems (bad types, unknown keys) — reported, never thrown. */
|
|
48
|
+
warnings: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Load the effective auto-mode config.
|
|
52
|
+
*
|
|
53
|
+
* Tier order — LOW to HIGH precedence (a higher tier that defines a key wins
|
|
54
|
+
* WHOLESALE for that key): user < managed < session (`--settings`). Project and
|
|
55
|
+
* settings.local are read ONLY to report that their `autoMode` is ignored;
|
|
56
|
+
* they can never contribute a value. That asymmetry is the whole security
|
|
57
|
+
* model: settings travel with a checkout, and a checkout that could grant
|
|
58
|
+
* itself trust would make the classifier theatre.
|
|
59
|
+
*/
|
|
60
|
+
export declare function loadAutoModeConfig(workDir: string): AutoModeConfig;
|
|
61
|
+
export interface ClassifyPromptInput {
|
|
62
|
+
tool: string;
|
|
63
|
+
input: Record<string, unknown>;
|
|
64
|
+
workDir: string;
|
|
65
|
+
/** Workspace extensions from --add-dir (trusted like the project). */
|
|
66
|
+
extraDirs?: string[];
|
|
67
|
+
/** Recent user messages, oldest → newest; the LAST is the current instruction. */
|
|
68
|
+
intent?: string[];
|
|
69
|
+
cfg: AutoModeConfig;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The review request. Deliberately one flat prompt — the classifier is a fresh,
|
|
73
|
+
* tool-less call (askModelOnce), so everything it may consult must be here:
|
|
74
|
+
* the limits, the environment, the action, and the human's actual words.
|
|
75
|
+
*/
|
|
76
|
+
export declare function classifyPrompt(a: ClassifyPromptInput): string;
|
|
77
|
+
export type AutoModeVerdictAction = 'allow' | 'block-soft' | 'block-hard' | 'unavailable';
|
|
78
|
+
export interface AutoModeVerdict {
|
|
79
|
+
action: AutoModeVerdictAction;
|
|
80
|
+
reason?: string;
|
|
81
|
+
}
|
|
82
|
+
/** A local guard failed us (or the host never called in) — see the design doc. */
|
|
83
|
+
export declare const AUTO_MODE_UNAVAILABLE: AutoModeVerdict;
|
|
84
|
+
/**
|
|
85
|
+
* Parse one line of classifier output. Anything that is not a clear ALLOW is
|
|
86
|
+
* NOT an allow — a mangled or empty reply fails closed (`unavailable`).
|
|
87
|
+
*/
|
|
88
|
+
export declare function parseClassifierReply(text: string | null | undefined): AutoModeVerdict;
|
|
89
|
+
//# sourceMappingURL=autoMode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoMode.d.ts","sourceRoot":"","sources":["../../src/permissions/autoMode.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,gBAAgB,EAmBnE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,gBAAgB,EAiBnE,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,cAAc,CAAC;AAEvD,eAAO,MAAM,qCAAqC,MAAM,CAAC;AACzD,eAAO,MAAM,gCAAgC,OAAO,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,kEAAkE;IAClE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,kFAAkF;IAClF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACjD,+EAA+E;IAC/E,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAgED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAqFlE;AASD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,EAAE,cAAc,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,mBAAmB,GAAG,MAAM,CA4C7D;AAED,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,YAAY,GAAG,YAAY,GAAG,aAAa,CAAC;AAE1F,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,qBAAqB,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,EAAE,eAGnC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,eAAe,CAcrF"}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// S20 — auto-mode config + the classifier's prompt/parse (see the design doc
|
|
3
|
+
// nexrall/docs/AUTO_MODE_CLASSIFIER.md).
|
|
4
|
+
//
|
|
5
|
+
// The pure half: where `autoMode` settings may come from (user + managed +
|
|
6
|
+
// --settings ONLY — never project/local, so a cloned repo cannot loosen the
|
|
7
|
+
// gate), how tiers merge, the built-in prose limits, the classifier prompt and
|
|
8
|
+
// the reply parser. The model CALL and the runtime (cache, budget, fast paths)
|
|
9
|
+
// live in the hosts — cli/src/permissions/autoClassify.ts for the CLI.
|
|
10
|
+
//
|
|
11
|
+
// The lists below are PROSE FOR A JUDGE, not regexes: they say what the
|
|
12
|
+
// classifier must protect, in the same words a human would, because the check
|
|
13
|
+
// is a model call and not another denylist (the destructive-token guard is
|
|
14
|
+
// that — and it stays the code-level floor, unchanged, underneath this).
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.AUTO_MODE_UNAVAILABLE = exports.AUTO_MODE_DEFAULT_MAX_BUDGET_USD = exports.AUTO_MODE_DEFAULT_MAX_CLASSIFICATIONS = exports.AUTO_MODE_DEFAULTS_SENTINEL = exports.AUTO_MODE_SOFT_DENY_DEFAULTS = exports.AUTO_MODE_HARD_DENY_DEFAULTS = void 0;
|
|
50
|
+
exports.loadAutoModeConfig = loadAutoModeConfig;
|
|
51
|
+
exports.classifyPrompt = classifyPrompt;
|
|
52
|
+
exports.parseClassifierReply = parseClassifierReply;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const os = __importStar(require("os"));
|
|
56
|
+
const rules_1 = require("./rules");
|
|
57
|
+
/**
|
|
58
|
+
* The built-in hard limits (CC's `$defaults` for `hard_deny`): unconditional —
|
|
59
|
+
* user intent does NOT clear these. Keep this list SHORT and beyond argument;
|
|
60
|
+
* it exists for the actions no request should ever authorize.
|
|
61
|
+
*/
|
|
62
|
+
exports.AUTO_MODE_HARD_DENY_DEFAULTS = [
|
|
63
|
+
{
|
|
64
|
+
label: 'Data exfiltration',
|
|
65
|
+
text: 'Sending file contents, repository contents, environment variables, credentials or tokens to any network ' +
|
|
66
|
+
'destination the user has not explicitly named (e.g. curl/wget with a body built from local files or env).',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
label: 'Credential and system files',
|
|
70
|
+
text: 'Writing to or modifying security-sensitive locations outside the project: ~/.ssh, ~/.aws, ~/.config/gh, ' +
|
|
71
|
+
'~/.netrc, ~/.npmrc credential entries, shell rc files (.zshrc/.bashrc), /etc, or the OS keychain.',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: 'Safety-system tampering',
|
|
75
|
+
text: "Disabling or weakening this session's own safety controls: editing permission rules or autoMode settings to " +
|
|
76
|
+
'grant more access, clearing audit trails, or killing the permission gate.',
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
/**
|
|
80
|
+
* The built-in soft limits (CC's `$defaults` for `soft_deny`): block UNLESS the
|
|
81
|
+
* user's own latest message directly and specifically asks for this exact
|
|
82
|
+
* action, or an allow exception matches.
|
|
83
|
+
*/
|
|
84
|
+
exports.AUTO_MODE_SOFT_DENY_DEFAULTS = [
|
|
85
|
+
{
|
|
86
|
+
label: 'Git history rewrite',
|
|
87
|
+
text: 'Rewriting shared history: git push --force/--force-with-lease to a shared branch, or reset --hard that ' +
|
|
88
|
+
'discards committed work.',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
label: 'Database and infra destruction',
|
|
92
|
+
text: 'Destructive database or infrastructure operations against shared or production systems: DROP/DELETE/TRUNCATE ' +
|
|
93
|
+
'on non-scratch data, terraform destroy, kubectl delete of shared resources, deleting cloud buckets.',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
label: 'Mass deletion outside scratch space',
|
|
97
|
+
text: 'Deleting many files or whole directories outside the project workspace and scratch/temp locations.',
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
/** The `$defaults` sentinel (CC's spelling) inside soft_deny / hard_deny lists. */
|
|
101
|
+
exports.AUTO_MODE_DEFAULTS_SENTINEL = '$defaults';
|
|
102
|
+
exports.AUTO_MODE_DEFAULT_MAX_CLASSIFICATIONS = 400;
|
|
103
|
+
exports.AUTO_MODE_DEFAULT_MAX_BUDGET_USD = 0.25;
|
|
104
|
+
function readTier(file) {
|
|
105
|
+
try {
|
|
106
|
+
const obj = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
107
|
+
const am = obj?.autoMode;
|
|
108
|
+
return am && typeof am === 'object' && !Array.isArray(am) ? am : null;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Accept CC's snake_case spellings and our camelCase ones. */
|
|
115
|
+
function pickKey(am, ...names) {
|
|
116
|
+
for (const n of names) {
|
|
117
|
+
if (Object.prototype.hasOwnProperty.call(am, n))
|
|
118
|
+
return { key: n, value: am[n] };
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
function asProseList(value, key, warnings) {
|
|
123
|
+
if (!Array.isArray(value)) {
|
|
124
|
+
warnings.push(`autoMode.${key} must be an array of strings — ignored`);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return value
|
|
128
|
+
.filter((v) => typeof v === 'string')
|
|
129
|
+
.map((v) => v.trim())
|
|
130
|
+
.filter(Boolean);
|
|
131
|
+
}
|
|
132
|
+
function asBool(value, key, warnings) {
|
|
133
|
+
if (typeof value === 'boolean')
|
|
134
|
+
return value;
|
|
135
|
+
if (value === 'true')
|
|
136
|
+
return true;
|
|
137
|
+
if (value === 'false')
|
|
138
|
+
return false;
|
|
139
|
+
warnings.push(`autoMode.${key} must be true or false — ignored`);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function asPositiveNumber(value, key, warnings) {
|
|
143
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
144
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
145
|
+
warnings.push(`autoMode.${key} must be a non-negative number — ignored`);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return n;
|
|
149
|
+
}
|
|
150
|
+
/** Expand `$defaults` in a list, or supply defaults when the key was never set. */
|
|
151
|
+
function expandDefaults(list, defaults) {
|
|
152
|
+
if (list === null)
|
|
153
|
+
return defaults.map((d) => `[${d.label}] ${d.text}`);
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const item of list) {
|
|
156
|
+
if (item === exports.AUTO_MODE_DEFAULTS_SENTINEL)
|
|
157
|
+
out.push(...defaults.map((d) => `[${d.label}] ${d.text}`));
|
|
158
|
+
else
|
|
159
|
+
out.push(item);
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Load the effective auto-mode config.
|
|
165
|
+
*
|
|
166
|
+
* Tier order — LOW to HIGH precedence (a higher tier that defines a key wins
|
|
167
|
+
* WHOLESALE for that key): user < managed < session (`--settings`). Project and
|
|
168
|
+
* settings.local are read ONLY to report that their `autoMode` is ignored;
|
|
169
|
+
* they can never contribute a value. That asymmetry is the whole security
|
|
170
|
+
* model: settings travel with a checkout, and a checkout that could grant
|
|
171
|
+
* itself trust would make the classifier theatre.
|
|
172
|
+
*/
|
|
173
|
+
function loadAutoModeConfig(workDir) {
|
|
174
|
+
const warnings = [];
|
|
175
|
+
const sources = [];
|
|
176
|
+
const ignoredProjectFiles = [];
|
|
177
|
+
const userFile = path.join(os.homedir(), '.nexrall', 'settings.json');
|
|
178
|
+
const managedFile = (0, rules_1.managedSettingsPath)();
|
|
179
|
+
const tiers = [
|
|
180
|
+
{ file: userFile, autoMode: readTier(userFile) },
|
|
181
|
+
{ file: managedFile, autoMode: readTier(managedFile) },
|
|
182
|
+
{ file: '--settings', autoMode: (0, rules_1.getSessionSettings)()?.autoMode ?? null },
|
|
183
|
+
];
|
|
184
|
+
// A non-object there (e.g. the string "off") is still an attempt we must not
|
|
185
|
+
// swallow silently.
|
|
186
|
+
for (const cand of [
|
|
187
|
+
path.join(workDir, '.nexrall', 'settings.json'),
|
|
188
|
+
path.join(workDir, '.nexrall', 'settings.local.json'),
|
|
189
|
+
]) {
|
|
190
|
+
try {
|
|
191
|
+
const obj = JSON.parse(fs.readFileSync(cand, 'utf-8'));
|
|
192
|
+
if (obj && Object.prototype.hasOwnProperty.call(obj, 'autoMode'))
|
|
193
|
+
ignoredProjectFiles.push(cand);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
/* no file / unreadable — nothing ignored */
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Per-key merge, low → high.
|
|
200
|
+
let enabled = null;
|
|
201
|
+
let classifierModel;
|
|
202
|
+
let environment = null;
|
|
203
|
+
let allow = null;
|
|
204
|
+
let softList = null;
|
|
205
|
+
let hardList = null;
|
|
206
|
+
let classifyAllShell = null;
|
|
207
|
+
let maxClassifications = null;
|
|
208
|
+
let maxBudgetUsd = null;
|
|
209
|
+
for (const tier of tiers) {
|
|
210
|
+
if (!tier.autoMode)
|
|
211
|
+
continue;
|
|
212
|
+
const am = tier.autoMode;
|
|
213
|
+
const keys = [];
|
|
214
|
+
const take = (r, key) => {
|
|
215
|
+
if (r !== null && r !== undefined)
|
|
216
|
+
keys.push(key);
|
|
217
|
+
return r;
|
|
218
|
+
};
|
|
219
|
+
const en = pickKey(am, 'classifier', 'enabled');
|
|
220
|
+
if (en)
|
|
221
|
+
enabled = take(asBool(en.value, 'classifier', warnings) ?? enabled, en.key);
|
|
222
|
+
const cm = pickKey(am, 'classifierModel', 'classifier_model');
|
|
223
|
+
if (cm && typeof cm.value === 'string' && cm.value.trim()) {
|
|
224
|
+
classifierModel = cm.value.trim();
|
|
225
|
+
keys.push(cm.key);
|
|
226
|
+
}
|
|
227
|
+
const env = pickKey(am, 'environment');
|
|
228
|
+
if (env)
|
|
229
|
+
environment = take(asProseList(env.value, 'environment', warnings) ?? environment, 'environment');
|
|
230
|
+
const al = pickKey(am, 'allow');
|
|
231
|
+
if (al)
|
|
232
|
+
allow = take(asProseList(al.value, 'allow', warnings) ?? allow, 'allow');
|
|
233
|
+
const sd = pickKey(am, 'soft_deny', 'softDeny');
|
|
234
|
+
if (sd)
|
|
235
|
+
softList = take(asProseList(sd.value, sd.key, warnings) ?? softList, sd.key);
|
|
236
|
+
const hd = pickKey(am, 'hard_deny', 'hardDeny');
|
|
237
|
+
if (hd)
|
|
238
|
+
hardList = take(asProseList(hd.value, hd.key, warnings) ?? hardList, hd.key);
|
|
239
|
+
const ca = pickKey(am, 'classifyAllShell', 'classify_all_shell');
|
|
240
|
+
if (ca)
|
|
241
|
+
classifyAllShell = take(asBool(ca.value, ca.key, warnings) ?? classifyAllShell, ca.key);
|
|
242
|
+
const mc = pickKey(am, 'maxClassifications', 'max_classifications');
|
|
243
|
+
if (mc)
|
|
244
|
+
maxClassifications = take(asPositiveNumber(mc.value, mc.key, warnings) ?? maxClassifications, mc.key);
|
|
245
|
+
const mb = pickKey(am, 'maxBudgetUsd', 'max_budget_usd');
|
|
246
|
+
if (mb)
|
|
247
|
+
maxBudgetUsd = take(asPositiveNumber(mb.value, mb.key, warnings) ?? maxBudgetUsd, mb.key);
|
|
248
|
+
if (keys.length)
|
|
249
|
+
sources.push({ file: tier.file, keys });
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
enabled: enabled ?? true,
|
|
253
|
+
...(classifierModel ? { classifierModel } : {}),
|
|
254
|
+
environment: environment ?? [],
|
|
255
|
+
allow: allow ?? [],
|
|
256
|
+
softDeny: expandDefaults(softList, exports.AUTO_MODE_SOFT_DENY_DEFAULTS),
|
|
257
|
+
hardDeny: expandDefaults(hardList, exports.AUTO_MODE_HARD_DENY_DEFAULTS),
|
|
258
|
+
classifyAllShell: classifyAllShell ?? false,
|
|
259
|
+
maxClassifications: maxClassifications ?? exports.AUTO_MODE_DEFAULT_MAX_CLASSIFICATIONS,
|
|
260
|
+
maxBudgetUsd: maxBudgetUsd ?? exports.AUTO_MODE_DEFAULT_MAX_BUDGET_USD,
|
|
261
|
+
sources,
|
|
262
|
+
ignoredProjectFiles,
|
|
263
|
+
warnings,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// ─── The classifier's prompt + reply ─────────────────────────────────────────
|
|
267
|
+
/** Clip helper local to this module (the prompt must never grow unbounded). */
|
|
268
|
+
function clip(text, max) {
|
|
269
|
+
return text.length > max ? text.slice(0, max - 1) + '\u2026' : text;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The review request. Deliberately one flat prompt — the classifier is a fresh,
|
|
273
|
+
* tool-less call (askModelOnce), so everything it may consult must be here:
|
|
274
|
+
* the limits, the environment, the action, and the human's actual words.
|
|
275
|
+
*/
|
|
276
|
+
function classifyPrompt(a) {
|
|
277
|
+
const list = (items) => (items.length ? items.map((t) => `- ${t}`).join('\n') : ' (none)');
|
|
278
|
+
const env = [
|
|
279
|
+
`The project directory ${a.workDir}${a.extraDirs?.length ? ` and the added directories ${a.extraDirs.join(', ')}` : ''}`,
|
|
280
|
+
"the current git repository's own configured remotes",
|
|
281
|
+
...a.cfg.environment,
|
|
282
|
+
];
|
|
283
|
+
const intent = a.intent?.length
|
|
284
|
+
? a.intent.map((t, i) => `[${i + 1}] ${clip(t.replace(/\s+/g, ' ').trim(), 300)}`).join('\n')
|
|
285
|
+
: ' (none)';
|
|
286
|
+
let action;
|
|
287
|
+
try {
|
|
288
|
+
action = clip(JSON.stringify(a.input), 2000);
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
action = '(unserializable input)';
|
|
292
|
+
}
|
|
293
|
+
return ('You are the AUTO-MODE SAFETY CLASSIFIER for a coding agent.\n' +
|
|
294
|
+
'The agent runs in "auto" mode: it will execute the action below WITHOUT asking the human, unless you block it.\n' +
|
|
295
|
+
"Judge the ACTION's real-world effect, not its wording.\n\n" +
|
|
296
|
+
'HARD LIMITS — block unconditionally, even if the user asked:\n' +
|
|
297
|
+
list(a.cfg.hardDeny) +
|
|
298
|
+
"\n\nSOFT LIMITS — block UNLESS the user's latest message directly and specifically asks for this exact action, " +
|
|
299
|
+
'or an ALLOW EXCEPTION matches:\n' +
|
|
300
|
+
list(a.cfg.softDeny) +
|
|
301
|
+
'\n\nALLOW EXCEPTIONS (pre-approved by the user; clear SOFT limits, never hard ones):\n' +
|
|
302
|
+
list(a.cfg.allow) +
|
|
303
|
+
'\n\nTRUSTED ENVIRONMENT (routine here; staying inside it is not a limit violation):\n' +
|
|
304
|
+
list(env) +
|
|
305
|
+
'\n\nTHE ACTION:\ntool: ' +
|
|
306
|
+
a.tool +
|
|
307
|
+
'\ninput: ' +
|
|
308
|
+
action +
|
|
309
|
+
'\n\nRECENT USER MESSAGES (intent; the LAST one is the current instruction):\n' +
|
|
310
|
+
intent +
|
|
311
|
+
'\n\nReply with EXACTLY ONE line, one of:\n' +
|
|
312
|
+
'ALLOW\n' +
|
|
313
|
+
'BLOCK: <one short reason>\n' +
|
|
314
|
+
'BLOCK-HARD: <one short reason>\n' +
|
|
315
|
+
'Use BLOCK only for a soft limit without clear user intent, BLOCK-HARD for a hard limit. ' +
|
|
316
|
+
'For a routine, reversible action inside the workspace, reply ALLOW. No other text.');
|
|
317
|
+
}
|
|
318
|
+
/** A local guard failed us (or the host never called in) — see the design doc. */
|
|
319
|
+
exports.AUTO_MODE_UNAVAILABLE = {
|
|
320
|
+
action: 'unavailable',
|
|
321
|
+
reason: 'classifier could not review the action',
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* Parse one line of classifier output. Anything that is not a clear ALLOW is
|
|
325
|
+
* NOT an allow — a mangled or empty reply fails closed (`unavailable`).
|
|
326
|
+
*/
|
|
327
|
+
function parseClassifierReply(text) {
|
|
328
|
+
if (typeof text !== 'string')
|
|
329
|
+
return { ...exports.AUTO_MODE_UNAVAILABLE, reason: 'no reply from the classifier' };
|
|
330
|
+
const first = text
|
|
331
|
+
.split('\n')
|
|
332
|
+
.find((l) => l.trim().length > 0)
|
|
333
|
+
?.trim() ?? '';
|
|
334
|
+
if (!first)
|
|
335
|
+
return { ...exports.AUTO_MODE_UNAVAILABLE, reason: 'empty reply from the classifier' };
|
|
336
|
+
const hard = /^BLOCK[\s_-]*HARD\b[:\s-]*(.*)$/i.exec(first);
|
|
337
|
+
if (hard)
|
|
338
|
+
return { action: 'block-hard', reason: hard[1].trim() || 'hard limit' };
|
|
339
|
+
const soft = /^BLOCK\b[:\s-]*(.*)$/i.exec(first);
|
|
340
|
+
if (soft)
|
|
341
|
+
return { action: 'block-soft', reason: soft[1].trim() || 'soft limit' };
|
|
342
|
+
if (/^ALLOW\b/i.test(first))
|
|
343
|
+
return { action: 'allow' };
|
|
344
|
+
return { ...exports.AUTO_MODE_UNAVAILABLE, reason: `unrecognized classifier reply: ${clip(first, 80)}` };
|
|
345
|
+
}
|
|
346
|
+
//# sourceMappingURL=autoMode.js.map
|
|
@@ -9,11 +9,17 @@ export interface MergedSettings {
|
|
|
9
9
|
/** Raw merged object (for hooks, allowedTools, etc.). */
|
|
10
10
|
raw: Record<string, unknown>;
|
|
11
11
|
}
|
|
12
|
+
export declare function managedSettingsPath(): string;
|
|
12
13
|
/** Replace the rules supplied for this process by flags. Not persisted anywhere. */
|
|
13
14
|
export declare function setSessionRules(r: Partial<PermissionRules>): void;
|
|
14
15
|
export declare function setSessionSettings(obj: Record<string, unknown> | null): void;
|
|
15
16
|
export declare function getSessionSettings(): Record<string, unknown> | null;
|
|
16
17
|
export declare function loadSettings(workDir: string): MergedSettings;
|
|
18
|
+
/** The first ALLOW rule that matches this call, or null. S20's classifier gate
|
|
19
|
+
* reads this to tell a NARROW pre-approval (wins, no review) from a BROAD one
|
|
20
|
+
* (suspended while the classifier is on — see isBroadAllowRule). */
|
|
21
|
+
export declare function matchedAllowRule(rules: PermissionRules, tool: string, input: Record<string, unknown>, workDir: string): string | null;
|
|
22
|
+
export declare function isBroadAllowRule(rule: string): boolean;
|
|
17
23
|
/**
|
|
18
24
|
* Evaluate a tool call against merged rules. Returns the matching decision, or
|
|
19
25
|
* null when no rule applies (caller decides the default, usually "ask").
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/permissions/rules.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,eAAe,CAAC;IAC7B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/permissions/rules.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,eAAe,CAAC;IAC7B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAQ5C;AAsBD,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAEjE;AAQD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI,CAE5E;AACD,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAEnE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA8B5D;AAqJD;;oEAEoE;AACpE,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAEf;AAYD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,GACd,kBAAkB,GAAG,IAAI,CAK3B"}
|