auto-mode 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/cli.js +183 -44
- package/dist/index.d.ts +74 -18
- package/dist/index.js +1 -1
- package/dist/{render-verdict-u3_ftSEp.js → render-verdict-lM31QV2u.js} +1085 -789
- package/mods/auto-mode/contract/find-current-direct-user-message.test.ts +26 -0
- package/mods/auto-mode/contract/find-current-direct-user-message.ts +9 -0
- package/mods/auto-mode/contract/is-message-origin.test.ts +10 -0
- package/mods/auto-mode/contract/is-message-origin.ts +6 -0
- package/mods/auto-mode/contract/message-origins.ts +3 -0
- package/mods/auto-mode/contract/scope-commands.ts +6 -0
- package/mods/auto-mode/contract/types.ts +51 -0
- package/mods/auto-mode/hooks/build-action-request.ts +46 -0
- package/mods/auto-mode/hooks/build-prompt-context.ts +20 -8
- package/mods/auto-mode/hooks/is-scope-command.ts +18 -0
- package/mods/auto-mode/hooks/is-tool-input.ts +3 -0
- package/mods/auto-mode/hooks/parse-decision.ts +2 -5
- package/mods/auto-mode/hooks/register.ts +29 -43
- package/mods/auto-mode/hooks/types.ts +3 -6
- package/mods/auto-mode/tsconfig.json +2 -1
- package/package.json +4 -2
- package/policy/judge.md +57 -0
package/README.md
CHANGED
|
@@ -96,7 +96,8 @@ if (request !== null) {
|
|
|
96
96
|
|
|
97
97
|
`classifyAction` runs both tiers and returns the verdict, a note, and diagnostics. A request carries
|
|
98
98
|
the session identity, the action, and the task context; `parseActionRequest` builds one from the
|
|
99
|
-
mod's JSON.
|
|
99
|
+
mod's JSON. Build every request with `parseActionRequest`: it drops a child agent's direct user
|
|
100
|
+
message, and the classifiers trust the request they receive.
|
|
100
101
|
|
|
101
102
|
## Documentation
|
|
102
103
|
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as resolveStateDir, D as DEFAULT_SCOPE_SOURCES, E as DEFAULT_DENIAL_BUDGET, F as string, N as number, O as loadConfig, P as strictObject, a as loadSessionScope, b as splitCommandWords, f as loadRepositoryContext, g as loadPolicy, i as resolveSessionScopePath, j as readHostEnvironment, n as parseActionRequest, o as EMPTY_SESSION_SCOPE, p as loadCheckoutRemotes, r as classifyAction, t as renderVerdict, v as toRepositorySlug, w as findCheckout, y as splitShellCommand } from "./render-verdict-
|
|
3
|
-
import { appendFile, mkdir, open, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
-
import { dirname, isAbsolute, join, normalize } from "node:path";
|
|
2
|
+
import { C as resolveStateDir, D as DEFAULT_SCOPE_SOURCES, E as DEFAULT_DENIAL_BUDGET, F as string, N as number, O as loadConfig, P as strictObject, a as loadSessionScope, b as splitCommandWords, f as loadRepositoryContext, g as loadPolicy, i as resolveSessionScopePath, j as readHostEnvironment, n as parseActionRequest, o as EMPTY_SESSION_SCOPE, p as loadCheckoutRemotes, r as classifyAction, t as renderVerdict, v as toRepositorySlug, w as findCheckout, y as splitShellCommand } from "./render-verdict-lM31QV2u.js";
|
|
3
|
+
import { appendFile, chmod, lstat, mkdir, open, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, normalize, resolve } from "node:path";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
+
import { constants } from "node:fs";
|
|
6
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
8
|
import { text } from "node:stream/consumers";
|
|
8
9
|
import { parseArgs } from "node:util";
|
|
@@ -16,11 +17,13 @@ const DESCRIPTIVE_FIELDS = { Bash: [
|
|
|
16
17
|
function buildRetryKey(request) {
|
|
17
18
|
const ignored = Object.hasOwn(DESCRIPTIVE_FIELDS, request.toolName) ? DESCRIPTIVE_FIELDS[request.toolName] ?? [] : [];
|
|
18
19
|
const input = Object.fromEntries(Object.entries(request.toolInput).filter(([key]) => !ignored.includes(key)));
|
|
20
|
+
const message = request.decisionContext?.lastDirectUserMessage ?? null;
|
|
19
21
|
const canonical = JSON.stringify(sortKeys({
|
|
20
22
|
cwd: request.cwd,
|
|
21
23
|
toolName: request.toolName,
|
|
22
24
|
toolInput: input,
|
|
23
|
-
lastDirectUserMessage:
|
|
25
|
+
lastDirectUserMessage: message?.text ?? null,
|
|
26
|
+
...message?.freshness === "stale" ? { lastDirectUserMessageFreshness: "stale" } : {}
|
|
24
27
|
}));
|
|
25
28
|
return createHash("sha256").update(canonical).digest("hex");
|
|
26
29
|
}
|
|
@@ -136,6 +139,82 @@ async function writeDenialState(path, state) {
|
|
|
136
139
|
await rename(staged, path);
|
|
137
140
|
}
|
|
138
141
|
//#endregion
|
|
142
|
+
//#region src/capture/resolve-capture-dir.ts
|
|
143
|
+
function resolveCaptureDir(capture, host) {
|
|
144
|
+
return capture.dir ?? join(resolveStateDir(host), "captures");
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/capture/find-enclosing-work-tree.ts
|
|
148
|
+
async function findEnclosingWorkTree(path) {
|
|
149
|
+
const real = await resolveExistingPrefix(resolve(path));
|
|
150
|
+
for (let directory = real;; directory = dirname(directory)) {
|
|
151
|
+
if (await hasGitEntry(directory)) return directory;
|
|
152
|
+
if (dirname(directory) === directory) return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function resolveExistingPrefix(path) {
|
|
156
|
+
const missing = [];
|
|
157
|
+
for (let current = path;; current = dirname(current)) {
|
|
158
|
+
const real = await realpath(current).catch(() => null);
|
|
159
|
+
if (real !== null) return join(real, ...missing.toReversed());
|
|
160
|
+
if (dirname(current) === current) return path;
|
|
161
|
+
missing.push(basename(current));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const ABSENT_CODES = /* @__PURE__ */ new Set(["ENOENT", "ENOTDIR"]);
|
|
165
|
+
async function hasGitEntry(directory) {
|
|
166
|
+
try {
|
|
167
|
+
await lstat(join(directory, ".git"));
|
|
168
|
+
return true;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
return !(error instanceof Error && "code" in error && ABSENT_CODES.has(String(error.code)));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/capture/try-write-request-capture.ts
|
|
175
|
+
async function tryWriteRequestCapture(dir, record) {
|
|
176
|
+
if (!isAbsolute(dir)) return {
|
|
177
|
+
kind: "refused",
|
|
178
|
+
reason: `the capture dir ${dir} is not an absolute path`
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
const before = await findEnclosingWorkTree(dir);
|
|
182
|
+
if (before !== null) return {
|
|
183
|
+
kind: "refused",
|
|
184
|
+
reason: `the capture dir is inside the git work tree ${before}`
|
|
185
|
+
};
|
|
186
|
+
await mkdir(dir, {
|
|
187
|
+
recursive: true,
|
|
188
|
+
mode: 448
|
|
189
|
+
});
|
|
190
|
+
const real = await realpath(dir);
|
|
191
|
+
const after = await findEnclosingWorkTree(real);
|
|
192
|
+
if (after !== null) return {
|
|
193
|
+
kind: "refused",
|
|
194
|
+
reason: `the capture dir is inside the git work tree ${after}`
|
|
195
|
+
};
|
|
196
|
+
if (((await stat(real)).mode & 63) !== 0) await chmod(real, 448);
|
|
197
|
+
await writeOwnerFile(join(real, ".gitignore"), "*\n", constants.O_TRUNC);
|
|
198
|
+
const path = join(real, `requests-${record.time.slice(0, 10)}.jsonl`);
|
|
199
|
+
await writeOwnerFile(path, `${JSON.stringify(record)}\n`, constants.O_APPEND);
|
|
200
|
+
return {
|
|
201
|
+
kind: "written",
|
|
202
|
+
path
|
|
203
|
+
};
|
|
204
|
+
} catch {
|
|
205
|
+
return { kind: "failed" };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function writeOwnerFile(path, text, flag) {
|
|
209
|
+
const handle = await open(path, constants.O_WRONLY | constants.O_CREAT | constants.O_NOFOLLOW | flag, 384);
|
|
210
|
+
try {
|
|
211
|
+
if (((await handle.stat()).mode & 63) !== 0) await handle.chmod(384);
|
|
212
|
+
await handle.writeFile(text);
|
|
213
|
+
} finally {
|
|
214
|
+
await handle.close();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
139
218
|
//#region src/config/build-jev-only-config.ts
|
|
140
219
|
function buildJevOnlyConfig(config) {
|
|
141
220
|
if (config.provider.protocol !== "system-one") return null;
|
|
@@ -153,7 +232,7 @@ async function writeActionDiagnostic(payload, entry, host, warnings) {
|
|
|
153
232
|
const path = host.env["AUTO_MODE_DIAGNOSTICS_PATH"] ?? join(resolveStateDir(host), "actions.jsonl");
|
|
154
233
|
if (path === "") return;
|
|
155
234
|
const record = {
|
|
156
|
-
schemaVersion:
|
|
235
|
+
schemaVersion: 4,
|
|
157
236
|
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
158
237
|
invocationID: entry.invocationID,
|
|
159
238
|
sessionHash: toHash(payload.sessionID),
|
|
@@ -163,7 +242,8 @@ async function writeActionDiagnostic(payload, entry, host, warnings) {
|
|
|
163
242
|
decidingStage: entry.decidingStage ?? null,
|
|
164
243
|
denials: entry.denials ?? null,
|
|
165
244
|
escalation: entry.escalation ?? false,
|
|
166
|
-
diagnostics: entry.diagnostics ?? null
|
|
245
|
+
diagnostics: entry.diagnostics ?? null,
|
|
246
|
+
judge: entry.judge ?? null
|
|
167
247
|
};
|
|
168
248
|
try {
|
|
169
249
|
await mkdir(dirname(path), {
|
|
@@ -194,6 +274,22 @@ function parseScopeRecordRequest(body) {
|
|
|
194
274
|
return parsed.success ? parsed.data : null;
|
|
195
275
|
}
|
|
196
276
|
//#endregion
|
|
277
|
+
//#region src/request/to-mod-request.ts
|
|
278
|
+
function toModRequest(request) {
|
|
279
|
+
const context = request.decisionContext;
|
|
280
|
+
return {
|
|
281
|
+
sessionID: request.sessionID,
|
|
282
|
+
...request.toolUseID === void 0 ? {} : { toolUseID: request.toolUseID },
|
|
283
|
+
cwd: request.cwd,
|
|
284
|
+
toolName: request.toolName,
|
|
285
|
+
toolInput: request.toolInput,
|
|
286
|
+
context: {
|
|
287
|
+
...context,
|
|
288
|
+
omittedTaskContext: [...context.omittedTaskContext]
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
//#endregion
|
|
197
293
|
//#region src/scope/read-pull-request.ts
|
|
198
294
|
const LOOKUP_TIMEOUT_MS = 5e3;
|
|
199
295
|
function readPullRequest(repository, number, options = {}) {
|
|
@@ -255,6 +351,17 @@ function collectPullRequestAddresses(text) {
|
|
|
255
351
|
}
|
|
256
352
|
const PULL_REQUEST_URL = /https:\/\/(?<repository>[^/\s]+\/[^/\s]+\/[^/\s]+)\/pull\/(?<number>[1-9]\d*)\b/gu;
|
|
257
353
|
//#endregion
|
|
354
|
+
//#region mods/auto-mode/contract/scope-commands.ts
|
|
355
|
+
const SCOPE_COMMANDS = {
|
|
356
|
+
git: [
|
|
357
|
+
"worktree add",
|
|
358
|
+
"checkout",
|
|
359
|
+
"switch",
|
|
360
|
+
"branch"
|
|
361
|
+
],
|
|
362
|
+
gh: ["pr create"]
|
|
363
|
+
};
|
|
364
|
+
//#endregion
|
|
258
365
|
//#region src/scope/collect-scope-events.ts
|
|
259
366
|
function collectScopeEvents(command, cwd, home) {
|
|
260
367
|
const events = [];
|
|
@@ -264,7 +371,7 @@ function collectScopeEvents(command, cwd, home) {
|
|
|
264
371
|
const [name, ...args] = splitCommandWords(segment.text);
|
|
265
372
|
if (name === "cd") directory = hasPipe ? null : resolvePath(directory, args[0] ?? home, home);
|
|
266
373
|
else if (name === "git" && directory !== null) events.push(...collectGitEvents(args, directory, home));
|
|
267
|
-
else if (name === "gh" && directory !== null &&
|
|
374
|
+
else if (name === "gh" && directory !== null && findScopeCommand(SCOPE_COMMANDS.gh, args) !== null) events.push({
|
|
268
375
|
kind: "pull-request",
|
|
269
376
|
directory,
|
|
270
377
|
head: findOptionValue(args, ["--head", "-H"])
|
|
@@ -272,15 +379,6 @@ function collectScopeEvents(command, cwd, home) {
|
|
|
272
379
|
}
|
|
273
380
|
return events;
|
|
274
381
|
}
|
|
275
|
-
const BRANCH_CREATE_OPTIONS = /* @__PURE__ */ new Set([
|
|
276
|
-
"-b",
|
|
277
|
-
"-B",
|
|
278
|
-
"-c",
|
|
279
|
-
"-C",
|
|
280
|
-
"--create",
|
|
281
|
-
"--force-create"
|
|
282
|
-
]);
|
|
283
|
-
const BRANCH_OTHER_OPTIONS = /^-(?:d|D|m|M|c|C|l|a|r|v|vv|u)$|^--(?:delete|move|copy|list|all|remotes|verbose|show-current|contains|no-contains|merged|no-merged|points-at|format|sort|set-upstream-to|unset-upstream|edit-description)/u;
|
|
284
382
|
function collectGitEvents(args, cwd, home) {
|
|
285
383
|
let directory = cwd;
|
|
286
384
|
let index = 0;
|
|
@@ -288,34 +386,33 @@ function collectGitEvents(args, cwd, home) {
|
|
|
288
386
|
if (args[index] === "-C") directory = resolvePath(directory, args[index + 1] ?? "", home);
|
|
289
387
|
index += args[index] === "-C" || args[index] === "-c" ? 2 : 1;
|
|
290
388
|
}
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (subcommand === "checkout" || subcommand === "switch") {
|
|
303
|
-
const name = findOptionValue(rest, [...BRANCH_CREATE_OPTIONS]);
|
|
304
|
-
return name === null ? [] : [{
|
|
305
|
-
kind: "branch",
|
|
306
|
-
name,
|
|
307
|
-
directory
|
|
308
|
-
}];
|
|
309
|
-
}
|
|
310
|
-
if (subcommand === "branch" && !rest.some((word) => BRANCH_OTHER_OPTIONS.test(word))) {
|
|
311
|
-
const name = rest.find((word) => !word.startsWith("-"));
|
|
312
|
-
return name === void 0 || isUnresolved(name) ? [] : [{
|
|
313
|
-
kind: "branch",
|
|
314
|
-
name,
|
|
315
|
-
directory
|
|
316
|
-
}];
|
|
389
|
+
const found = findScopeCommand(SCOPE_COMMANDS.git, args.slice(index));
|
|
390
|
+
if (directory === null || found === null) return [];
|
|
391
|
+
return GIT_EVENT_COLLECTORS[found.command](found.rest, directory, home);
|
|
392
|
+
}
|
|
393
|
+
function findScopeCommand(commands, args) {
|
|
394
|
+
for (const command of commands) {
|
|
395
|
+
const words = command.split(" ");
|
|
396
|
+
if (words.every((word, index) => args[index] === word)) return {
|
|
397
|
+
command,
|
|
398
|
+
rest: args.slice(words.length)
|
|
399
|
+
};
|
|
317
400
|
}
|
|
318
|
-
return
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
const GIT_EVENT_COLLECTORS = {
|
|
404
|
+
"worktree add": collectWorktreeEvents,
|
|
405
|
+
checkout: collectSwitchEvents,
|
|
406
|
+
switch: collectSwitchEvents,
|
|
407
|
+
branch: collectBranchEvents
|
|
408
|
+
};
|
|
409
|
+
function collectWorktreeEvents(rest, directory, home) {
|
|
410
|
+
const path = findWorktreePath(rest);
|
|
411
|
+
const resolved = path === null ? null : resolvePath(directory, path, home);
|
|
412
|
+
return resolved === null ? [] : [{
|
|
413
|
+
kind: "worktree",
|
|
414
|
+
path: resolved
|
|
415
|
+
}];
|
|
319
416
|
}
|
|
320
417
|
const WORKTREE_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
321
418
|
"-b",
|
|
@@ -330,6 +427,32 @@ function findWorktreePath(args) {
|
|
|
330
427
|
}
|
|
331
428
|
return null;
|
|
332
429
|
}
|
|
430
|
+
const BRANCH_CREATE_OPTIONS = /* @__PURE__ */ new Set([
|
|
431
|
+
"-b",
|
|
432
|
+
"-B",
|
|
433
|
+
"-c",
|
|
434
|
+
"-C",
|
|
435
|
+
"--create",
|
|
436
|
+
"--force-create"
|
|
437
|
+
]);
|
|
438
|
+
const BRANCH_OTHER_OPTIONS = /^-(?:d|D|m|M|c|C|l|a|r|v|vv|u)$|^--(?:delete|move|copy|list|all|remotes|verbose|show-current|contains|no-contains|merged|no-merged|points-at|format|sort|set-upstream-to|unset-upstream|edit-description)/u;
|
|
439
|
+
function collectSwitchEvents(rest, directory) {
|
|
440
|
+
const name = findOptionValue(rest, [...BRANCH_CREATE_OPTIONS]);
|
|
441
|
+
return name === null ? [] : [{
|
|
442
|
+
kind: "branch",
|
|
443
|
+
name,
|
|
444
|
+
directory
|
|
445
|
+
}];
|
|
446
|
+
}
|
|
447
|
+
function collectBranchEvents(rest, directory) {
|
|
448
|
+
if (rest.some((word) => BRANCH_OTHER_OPTIONS.test(word))) return [];
|
|
449
|
+
const name = rest.find((word) => !word.startsWith("-"));
|
|
450
|
+
return name === void 0 || isUnresolved(name) ? [] : [{
|
|
451
|
+
kind: "branch",
|
|
452
|
+
name,
|
|
453
|
+
directory
|
|
454
|
+
}];
|
|
455
|
+
}
|
|
333
456
|
function findOptionValue(args, options) {
|
|
334
457
|
const index = args.findIndex((word) => options.includes(word));
|
|
335
458
|
const value = index === -1 ? void 0 : args[index + 1];
|
|
@@ -617,8 +740,20 @@ async function run(io, explain, localOnly, jevOnly, deadline) {
|
|
|
617
740
|
session: plan.state.session
|
|
618
741
|
},
|
|
619
742
|
escalation: plan.escalation,
|
|
620
|
-
...outcome.diagnostics === void 0 ? {} : { diagnostics: outcome.diagnostics }
|
|
743
|
+
...outcome.diagnostics === void 0 ? {} : { diagnostics: outcome.diagnostics },
|
|
744
|
+
...outcome.judge === void 0 ? {} : { judge: outcome.judge }
|
|
621
745
|
}, host, io.stderr);
|
|
746
|
+
if (config.capture?.enabled === true) {
|
|
747
|
+
const captured = await tryWriteRequestCapture(resolveCaptureDir(config.capture, host), {
|
|
748
|
+
schemaVersion: 1,
|
|
749
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
750
|
+
request: toModRequest(request),
|
|
751
|
+
verdict: plan.verdict,
|
|
752
|
+
decidingStage: plan.escalation ? "budget" : outcome.decidingStage,
|
|
753
|
+
escalation: plan.escalation
|
|
754
|
+
});
|
|
755
|
+
printCaptureNote(io.stderr, captured);
|
|
756
|
+
}
|
|
622
757
|
const note = plan.escalation ? "denial budget exhausted; the user decides this action" : outcome.note;
|
|
623
758
|
return printNote(io.stderr, explain || outcome.unavailable === true, note);
|
|
624
759
|
} finally {
|
|
@@ -642,6 +777,10 @@ async function tryWriteDenialState(stderr, path, state) {
|
|
|
642
777
|
function printVerdict(stdout, verdict) {
|
|
643
778
|
stdout.write(renderVerdict(verdict));
|
|
644
779
|
}
|
|
780
|
+
function printCaptureNote(stderr, captured) {
|
|
781
|
+
if (captured.kind === "refused") stderr.write(`auto-mode: capture skipped: ${captured.reason}\n`);
|
|
782
|
+
if (captured.kind === "failed") stderr.write("auto-mode: capture unavailable\n");
|
|
783
|
+
}
|
|
645
784
|
async function runRecord(io) {
|
|
646
785
|
const raw = await io.stdin();
|
|
647
786
|
let body;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,29 +1,48 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region mods/auto-mode/contract/message-origins.d.ts
|
|
2
|
+
declare const MESSAGE_ORIGINS: readonly ['composer', 'bridge', 'sdk'];
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region mods/auto-mode/contract/types.d.ts
|
|
5
|
+
type MessageOrigin = (typeof MESSAGE_ORIGINS)[number];
|
|
2
6
|
interface UserTask {
|
|
3
7
|
readonly text: string;
|
|
4
|
-
readonly origin:
|
|
8
|
+
readonly origin: MessageOrigin;
|
|
5
9
|
}
|
|
6
|
-
interface
|
|
10
|
+
interface DirectUserMessage extends UserTask {
|
|
11
|
+
readonly freshness?: 'stale' | undefined;
|
|
12
|
+
}
|
|
13
|
+
interface DelegatedTask {
|
|
14
|
+
readonly text: string;
|
|
15
|
+
readonly origin: 'agent.spawn';
|
|
16
|
+
}
|
|
17
|
+
interface TaskOmission {
|
|
18
|
+
readonly field: 'originalUserTask' | 'delegatedTask';
|
|
19
|
+
readonly reason: 'unavailable' | 'budget';
|
|
20
|
+
}
|
|
21
|
+
interface ModContext {
|
|
7
22
|
readonly agentID: string | null;
|
|
8
23
|
readonly originalUserTask: UserTask | null;
|
|
9
|
-
readonly delegatedTask:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} | null;
|
|
13
|
-
readonly lastDirectUserMessage: UserTask | null;
|
|
14
|
-
readonly omittedTaskContext: readonly {
|
|
15
|
-
readonly field: 'originalUserTask' | 'delegatedTask';
|
|
16
|
-
readonly reason: 'unavailable' | 'budget';
|
|
17
|
-
}[];
|
|
24
|
+
readonly delegatedTask: DelegatedTask | null;
|
|
25
|
+
readonly lastDirectUserMessage: DirectUserMessage | null;
|
|
26
|
+
readonly omittedTaskContext: TaskOmission[];
|
|
18
27
|
}
|
|
19
|
-
interface
|
|
28
|
+
interface ModRequest {
|
|
20
29
|
readonly sessionID: string;
|
|
21
30
|
readonly toolUseID?: string | undefined;
|
|
22
31
|
readonly cwd: string;
|
|
23
32
|
readonly toolName: string;
|
|
24
33
|
readonly toolInput: Readonly<Record<string, unknown>>;
|
|
25
|
-
readonly
|
|
34
|
+
readonly context: ModContext;
|
|
26
35
|
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/request/types.d.ts
|
|
38
|
+
type DeepReadonly<T> = T extends readonly (infer Item)[] ? readonly DeepReadonly<Item>[] : T extends object ? { readonly [Key in keyof T]: DeepReadonly<T[Key]>; } : T;
|
|
39
|
+
type DecisionContext = DeepReadonly<ModContext>;
|
|
40
|
+
type ActionRequest = DeepReadonly<Omit<ModRequest, 'context'>> & {
|
|
41
|
+
readonly decisionContext?: DecisionContext | undefined;
|
|
42
|
+
};
|
|
43
|
+
type ParsedActionRequest = ActionRequest & {
|
|
44
|
+
readonly decisionContext: DecisionContext;
|
|
45
|
+
};
|
|
27
46
|
type Verdict = {
|
|
28
47
|
readonly kind: 'allow';
|
|
29
48
|
} | {
|
|
@@ -205,12 +224,27 @@ interface DenialBudget {
|
|
|
205
224
|
readonly perSession: number;
|
|
206
225
|
}
|
|
207
226
|
//#endregion
|
|
227
|
+
//#region src/judge/types.d.ts
|
|
228
|
+
type OverturnBasis = 'consent' | 'misread';
|
|
229
|
+
type JudgeOverturns = 'consent' | 'any';
|
|
230
|
+
type JudgeStatus = 'confirmed' | 'overturned' | 'held' | 'failed';
|
|
231
|
+
type JudgeFailure = 'credential' | 'policy' | 'request' | 'timeout' | 'unreadable';
|
|
232
|
+
interface JudgeDiagnostics {
|
|
233
|
+
readonly status: JudgeStatus;
|
|
234
|
+
readonly failureReason: JudgeFailure | null;
|
|
235
|
+
readonly overturnBasis: OverturnBasis | null;
|
|
236
|
+
readonly model: string;
|
|
237
|
+
readonly rule: string;
|
|
238
|
+
readonly tier: 'hard' | 'soft';
|
|
239
|
+
readonly elapsedMs: number;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
208
242
|
//#region src/config/presets.d.ts
|
|
209
243
|
export declare const PRESETS: Readonly<Record<string, ProviderConfig>>;
|
|
210
244
|
//#endregion
|
|
211
245
|
//#region src/config/config.d.ts
|
|
212
246
|
interface ProviderConfig {
|
|
213
|
-
readonly protocol?: 'messages' | 'system-one';
|
|
247
|
+
readonly protocol?: 'messages' | 'system-one' | 'claude-code';
|
|
214
248
|
readonly baseURL: string;
|
|
215
249
|
readonly model: string;
|
|
216
250
|
readonly apiKeyEnv?: string | undefined;
|
|
@@ -225,9 +259,14 @@ interface ScopeSource {
|
|
|
225
259
|
}
|
|
226
260
|
type ScopeSourceKind = (typeof SCOPE_SOURCE_KINDS)[number];
|
|
227
261
|
declare const SCOPE_SOURCE_KINDS: readonly ['cwd', 'session', 'globs', 'atc'];
|
|
262
|
+
interface CaptureConfig {
|
|
263
|
+
readonly enabled: boolean;
|
|
264
|
+
readonly dir?: string | undefined;
|
|
265
|
+
}
|
|
228
266
|
interface Config {
|
|
229
267
|
readonly provider: ProviderConfig;
|
|
230
268
|
readonly judge?: ProviderConfig | null;
|
|
269
|
+
readonly judgeOverturns?: JudgeOverturns;
|
|
231
270
|
readonly scopeSources?: Readonly<Record<string, ScopeSource>>;
|
|
232
271
|
readonly classifierPath?: string | undefined;
|
|
233
272
|
readonly rulesPath?: string | undefined;
|
|
@@ -235,6 +274,7 @@ interface Config {
|
|
|
235
274
|
readonly claudeSettingsPath?: string | null | undefined;
|
|
236
275
|
readonly blockThreshold?: number | undefined;
|
|
237
276
|
readonly denialBudget?: DenialBudget;
|
|
277
|
+
readonly capture?: CaptureConfig;
|
|
238
278
|
readonly warnings?: readonly string[];
|
|
239
279
|
}
|
|
240
280
|
export declare const DEFAULT_CONFIG: Config;
|
|
@@ -243,7 +283,7 @@ export declare function loadConfig(configPath?: string, host?: Readonly<HostEnvi
|
|
|
243
283
|
export declare function resolveApiKey(provider: ProviderConfig, options?: EvaluationOptions): Promise<string | null>;
|
|
244
284
|
//#endregion
|
|
245
285
|
//#region src/classify-action.d.ts
|
|
246
|
-
type DecidingStage = 'local' | 'containment' | 'bypass' | 'jev' | 'messages' | 'retry';
|
|
286
|
+
type DecidingStage = 'local' | 'containment' | 'bypass' | 'jev' | 'judge' | 'messages' | 'retry';
|
|
247
287
|
interface ActionOutcome {
|
|
248
288
|
readonly verdict: Verdict | null;
|
|
249
289
|
readonly decidingStage: DecidingStage;
|
|
@@ -251,6 +291,7 @@ interface ActionOutcome {
|
|
|
251
291
|
readonly status: 'allow' | 'skipped' | 'failure' | DecisionDiagnostics['status'];
|
|
252
292
|
readonly unavailable?: boolean;
|
|
253
293
|
readonly diagnostics?: DecisionDiagnostics;
|
|
294
|
+
readonly judge?: JudgeDiagnostics;
|
|
254
295
|
}
|
|
255
296
|
interface ClassifyOptions extends EvaluationOptions {
|
|
256
297
|
readonly localOnly?: boolean;
|
|
@@ -272,12 +313,27 @@ export declare function checkContainment(request: Readonly<ActionRequest>, scope
|
|
|
272
313
|
//#region src/model/build-decision-request.d.ts
|
|
273
314
|
export declare function buildDecisionRequest(payload: ActionRequest, policy: string, configuredRules: ClaudeRules, lastUserMessage: string | null, rulesSource?: 'shipped' | 'replacement', repositoryContext?: RepositoryContext | null, mcpServers?: readonly MCPServerFact[]): DecisionRequest;
|
|
274
315
|
//#endregion
|
|
316
|
+
//#region src/policy/build-deny-reason.d.ts
|
|
317
|
+
type DenyBasis = 'matched' | 'unresolved';
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/model/find-denied-rule.d.ts
|
|
320
|
+
interface DeniedRule {
|
|
321
|
+
readonly rule: DecisionRule;
|
|
322
|
+
readonly basis: DenyBasis;
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/judge/classify-with-judge.d.ts
|
|
326
|
+
interface JudgeEvidence extends DeniedRule {
|
|
327
|
+
readonly repositoryContext: RepositoryContext | null;
|
|
328
|
+
}
|
|
329
|
+
//#endregion
|
|
275
330
|
//#region src/model/classify-with-model.d.ts
|
|
276
331
|
interface ModelOutcome {
|
|
277
332
|
readonly verdict: Verdict | null;
|
|
278
333
|
readonly note: string;
|
|
279
334
|
readonly unavailable?: boolean;
|
|
280
335
|
readonly diagnostics?: DecisionDiagnostics;
|
|
336
|
+
readonly review?: JudgeEvidence;
|
|
281
337
|
}
|
|
282
338
|
export declare function classifyWithModel(payload: ActionRequest, config: Config, options?: EvaluationOptions): Promise<ModelOutcome>;
|
|
283
339
|
//#endregion
|
|
@@ -306,10 +362,10 @@ interface PolicyPaths {
|
|
|
306
362
|
readonly classifierPath?: string | undefined;
|
|
307
363
|
readonly rulesPath?: string | undefined;
|
|
308
364
|
}
|
|
309
|
-
export declare function loadPolicy(paths?: PolicyPaths, framework?: 'classifier.md' | 'decision.md'): Promise<string>;
|
|
365
|
+
export declare function loadPolicy(paths?: PolicyPaths, framework?: 'classifier.md' | 'decision.md' | 'judge.md'): Promise<string>;
|
|
310
366
|
//#endregion
|
|
311
367
|
//#region src/request/parse-action-request.d.ts
|
|
312
|
-
export declare function parseActionRequest(body: unknown):
|
|
368
|
+
export declare function parseActionRequest(body: unknown): ParsedActionRequest | null;
|
|
313
369
|
//#endregion
|
|
314
370
|
//#region src/request/render-verdict.d.ts
|
|
315
371
|
export declare function renderVerdict(verdict: Verdict): string;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as resolveConfigPath, M as PRESETS, O as loadConfig, S as tryClassifyEdit, T as DEFAULT_CONFIG, _ as checkContainment, c as classifyLocally, d as sendDecision, g as loadPolicy, h as buildDecisionRequest, k as resolveApiKey, l as classifyWithModel, m as DecisionRequestError, n as parseActionRequest, r as classifyAction, s as buildTaskScope, t as renderVerdict, u as parseModelVerdict, x as loadClaudeSettings } from "./render-verdict-
|
|
1
|
+
import { A as resolveConfigPath, M as PRESETS, O as loadConfig, S as tryClassifyEdit, T as DEFAULT_CONFIG, _ as checkContainment, c as classifyLocally, d as sendDecision, g as loadPolicy, h as buildDecisionRequest, k as resolveApiKey, l as classifyWithModel, m as DecisionRequestError, n as parseActionRequest, r as classifyAction, s as buildTaskScope, t as renderVerdict, u as parseModelVerdict, x as loadClaudeSettings } from "./render-verdict-lM31QV2u.js";
|
|
2
2
|
//#region src/config/load-claude-rules.ts
|
|
3
3
|
async function loadClaudeRules(path, host) {
|
|
4
4
|
return (await loadClaudeSettings(path, host)).rules;
|