auto-mode 3.0.1 → 3.1.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 -0
- package/dist/classify-edit-O8JNCVde.js +138 -0
- package/dist/classify-reply-message-CrZ6S-AR.js +28 -0
- package/dist/cli.js +3 -3
- package/dist/{classify-edit-B660kX2I.js → find-secret-DUEWxfkc.js} +945 -718
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/{parse-action-request-CaQy7FA3.js → parse-action-request-DySYvZxl.js} +253 -60
- package/dist/{schemas-B_w83Dze.js → schemas-B80Yxv6m.js} +19 -19
- package/dist/{start-judge-server-BlhDO_4A.js → start-judge-server-CDYdRR4I.js} +2 -2
- package/dist/start-judge-session-C0eRJFlg.js +1 -0
- package/dist/{start-judge-session-vxiG-kDp.js → start-judge-session-vhUxe9-V.js} +14482 -13079
- package/mods/auto-mode/contract/cli-limits.ts +0 -6
- package/mods/auto-mode/contract/message-origins.ts +1 -1
- package/mods/auto-mode/contract/scope-commands.ts +0 -2
- package/mods/auto-mode/hooks/is-scope-command.ts +1 -2
- package/mods/auto-mode/hooks/parse-decision.ts +0 -2
- package/mods/auto-mode/hooks/register.ts +0 -2
- package/package.json +20 -15
- package/policy/judge.md +1 -1
- package/dist/start-judge-session-B-KYQG13.js +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ interface TaskScopeSummary {
|
|
|
68
68
|
interface OutputStream {
|
|
69
69
|
readonly write: (text: string) => unknown;
|
|
70
70
|
}
|
|
71
|
+
type TextFileReader = (path: string) => Promise<string>;
|
|
71
72
|
interface HostEnvironment {
|
|
72
73
|
readonly env: Readonly<Record<string, string | undefined>>;
|
|
73
74
|
readonly home: string;
|
|
@@ -79,6 +80,7 @@ interface Runtime {
|
|
|
79
80
|
readonly timeout: (timeoutMs: number) => AbortSignal;
|
|
80
81
|
readonly fetch: (url: string, init: Readonly<RequestInit>) => Promise<Response>;
|
|
81
82
|
readonly readCheckout: CheckoutReader;
|
|
83
|
+
readonly readTextFile: TextFileReader;
|
|
82
84
|
}
|
|
83
85
|
interface ClaudeRules {
|
|
84
86
|
readonly environment: readonly string[];
|
|
@@ -114,7 +116,7 @@ interface ScopeSource {
|
|
|
114
116
|
readonly paths?: readonly string[];
|
|
115
117
|
}
|
|
116
118
|
type ScopeSourceKind = (typeof SCOPE_SOURCE_KINDS)[number];
|
|
117
|
-
declare const SCOPE_SOURCE_KINDS: readonly ['cwd', 'session', 'globs', 'atc'];
|
|
119
|
+
declare const SCOPE_SOURCE_KINDS: readonly ['cwd', 'session', 'globs', 'launcher', 'atc'];
|
|
118
120
|
interface CaptureConfig {
|
|
119
121
|
readonly enabled: boolean;
|
|
120
122
|
readonly directory?: string | undefined;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as loadConfig$1, i as classifyAction$1, n as readRuntime, r as readHostEnvironment, t as parseActionRequest } from "./parse-action-request-
|
|
1
|
+
import { b as loadConfig$1, i as classifyAction$1, n as readRuntime, r as readHostEnvironment, t as parseActionRequest } from "./parse-action-request-DySYvZxl.js";
|
|
2
2
|
//#region src/index.ts
|
|
3
3
|
function classifyAction(request, config, options = {}) {
|
|
4
4
|
return classifyAction$1(request, config, {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { a as literal, c as object, d as string, f as union, l as record, m as prettifyError, n as array, o as looseObject, p as unknown, r as boolean, s as number, t as _enum, u as strictObject } from "./schemas-
|
|
1
|
+
import { a as literal, c as object, d as string, f as union, l as record, m as prettifyError, n as array, o as looseObject, p as unknown, r as boolean, s as number, t as _enum, u as strictObject } from "./schemas-B80Yxv6m.js";
|
|
2
2
|
import { t as findEditFields } from "./find-edit-fields-CzkhPA4j.js";
|
|
3
|
+
import { t as findSecret } from "./find-secret-DUEWxfkc.js";
|
|
3
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
5
|
import { access, appendFile, link, lstat, mkdir, open, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
6
|
import { basename, dirname, isAbsolute, join, matchesGlob, normalize, parse, relative, resolve } from "node:path";
|
|
@@ -268,7 +269,7 @@ function resolveXDGDir(host, kind) {
|
|
|
268
269
|
return join(root, "auto-mode");
|
|
269
270
|
}
|
|
270
271
|
//#endregion
|
|
271
|
-
//#region node_modules/.bun/ts-pattern@5.9.
|
|
272
|
+
//#region node_modules/.bun/ts-pattern@5.9.2/node_modules/ts-pattern/dist/index.js
|
|
272
273
|
const t = Symbol.for("@ts-pattern/matcher");
|
|
273
274
|
const e = Symbol.for("@ts-pattern/isVariadic");
|
|
274
275
|
const n = "@ts-pattern/anonymous-select-key";
|
|
@@ -692,6 +693,7 @@ const SCOPE_SOURCE_KINDS = [
|
|
|
692
693
|
"cwd",
|
|
693
694
|
"session",
|
|
694
695
|
"globs",
|
|
696
|
+
"launcher",
|
|
695
697
|
"atc"
|
|
696
698
|
];
|
|
697
699
|
const DEFAULT_PROVIDER = findPreset("jev");
|
|
@@ -704,6 +706,7 @@ const DEFAULT_DENIAL_BUDGET = {
|
|
|
704
706
|
const DEFAULT_SCOPE_SOURCES = {
|
|
705
707
|
cwd: { kind: "cwd" },
|
|
706
708
|
session: { kind: "session" },
|
|
709
|
+
launcher: { kind: "launcher" },
|
|
707
710
|
atc: { kind: "atc" }
|
|
708
711
|
};
|
|
709
712
|
const DEFAULT_CONFIG = {
|
|
@@ -1005,7 +1008,7 @@ async function tryClassifyEdit(request, scope, host, reader = {}) {
|
|
|
1005
1008
|
]);
|
|
1006
1009
|
if (target === null || GIT_OVERRIDES.some((name) => host.env[name] !== void 0)) return null;
|
|
1007
1010
|
const [checkout, current] = await Promise.all([files.readCheckout(dirname(target), host.env), request.toolName === "Edit" ? files.readFile(target).catch(() => null) : null]);
|
|
1008
|
-
return (await import("./classify-edit-
|
|
1011
|
+
return (await import("./classify-edit-O8JNCVde.js")).classifyEdit({
|
|
1009
1012
|
toolName: request.toolName,
|
|
1010
1013
|
toolInput: request.toolInput,
|
|
1011
1014
|
requested,
|
|
@@ -1095,6 +1098,11 @@ const SCRATCH_PATHS = [
|
|
|
1095
1098
|
"/dev/stderr"
|
|
1096
1099
|
];
|
|
1097
1100
|
//#endregion
|
|
1101
|
+
//#region src/scope/find-reply-channel.ts
|
|
1102
|
+
function findReplyChannel(tool, input, channels) {
|
|
1103
|
+
return channels.find((channel) => channel.tool === tool && input[channel.field] === channel.recipient) ?? null;
|
|
1104
|
+
}
|
|
1105
|
+
//#endregion
|
|
1098
1106
|
//#region src/shell/is-unresolved-word.ts
|
|
1099
1107
|
function isUnresolvedWord(word) {
|
|
1100
1108
|
return /[$`]/u.test(word);
|
|
@@ -1834,7 +1842,7 @@ function toRepositorySlug(reference) {
|
|
|
1834
1842
|
//#endregion
|
|
1835
1843
|
//#region src/containment/is-in-scope.ts
|
|
1836
1844
|
function isInScope(path, scope, scratchPaths) {
|
|
1837
|
-
if (scratchPaths.some((entry) => isUnder$2(path, entry)) || scope.pathGlobs.some((glob) => matchesGlob(path, glob))) return true;
|
|
1845
|
+
if (scratchPaths.some((entry) => isUnder$2(path, entry)) || scope.paths.some((owned) => isUnder$2(path, owned)) || scope.pathGlobs.some((glob) => matchesGlob(path, glob))) return true;
|
|
1838
1846
|
return scope.worktrees.some((worktree) => {
|
|
1839
1847
|
const isNested = isUnder$2(path, join(worktree, ".worktrees"));
|
|
1840
1848
|
const isOtherOwned = scope.worktrees.some((other) => other !== worktree && isUnder$2(path, other));
|
|
@@ -1842,7 +1850,8 @@ function isInScope(path, scope, scratchPaths) {
|
|
|
1842
1850
|
});
|
|
1843
1851
|
}
|
|
1844
1852
|
function isUnder$2(path, root) {
|
|
1845
|
-
|
|
1853
|
+
const prefix = root.endsWith("/") ? root : `${root}/`;
|
|
1854
|
+
return path === root || path.startsWith(prefix);
|
|
1846
1855
|
}
|
|
1847
1856
|
//#endregion
|
|
1848
1857
|
//#region src/containment/collect-target-findings.ts
|
|
@@ -2688,7 +2697,7 @@ function collectScopeFindings(action, scope, scratchPaths = SCRATCH_PATHS) {
|
|
|
2688
2697
|
}
|
|
2689
2698
|
function collectToolFindings(action, scope) {
|
|
2690
2699
|
const verb = action.tool.split("__").at(-1) ?? "";
|
|
2691
|
-
if (!/merge|create|comment|update|delete|push|close|review/u.test(verb)) return [];
|
|
2700
|
+
if (!/merge|create|comment|update|delete|push|close|review/u.test(verb) || findReplyChannel(action.tool, action.input, scope.replyChannels) !== null) return [];
|
|
2692
2701
|
const number = action.input["pullNumber"] ?? action.input["pull_number"] ?? action.input["issue_number"];
|
|
2693
2702
|
const owner = action.input["owner"];
|
|
2694
2703
|
const name = action.input["repo"];
|
|
@@ -3059,14 +3068,30 @@ function isUnder(path, root) {
|
|
|
3059
3068
|
return path === root || root === "/" || path.startsWith(`${root}/`);
|
|
3060
3069
|
}
|
|
3061
3070
|
//#endregion
|
|
3071
|
+
//#region src/secrets/build-secret-free-text.ts
|
|
3072
|
+
const MAX_SECRET_PASSES = 50;
|
|
3073
|
+
function buildSecretFreeText(text, toReplacement) {
|
|
3074
|
+
let current = text;
|
|
3075
|
+
for (let pass = 0; pass < MAX_SECRET_PASSES; pass += 1) {
|
|
3076
|
+
const finding = findSecret({
|
|
3077
|
+
text: current,
|
|
3078
|
+
path: null
|
|
3079
|
+
});
|
|
3080
|
+
if (finding === null || finding.secret === "" || !current.includes(finding.secret)) return current;
|
|
3081
|
+
current = current.replaceAll(finding.secret, toReplacement(finding.secret));
|
|
3082
|
+
}
|
|
3083
|
+
return current;
|
|
3084
|
+
}
|
|
3085
|
+
//#endregion
|
|
3062
3086
|
//#region src/diagnostics/write-action-diagnostic.ts
|
|
3063
3087
|
async function writeActionDiagnostic(payload, entry, host, warnings) {
|
|
3064
3088
|
const path = host.env["AUTO_MODE_DIAGNOSTICS_PATH"] ?? join(resolveXDGDir(host, "state"), "actions.jsonl");
|
|
3065
3089
|
if (path === "") return;
|
|
3066
3090
|
const agentID = payload.decisionContext?.agentID ?? null;
|
|
3067
3091
|
const agentKey = agentID === null ? payload.sessionID : `${payload.sessionID}\0${agentID}`;
|
|
3092
|
+
const isReviewed = entry.judge !== void 0 || entry.grant !== void 0;
|
|
3068
3093
|
const record = {
|
|
3069
|
-
schemaVersion:
|
|
3094
|
+
schemaVersion: 7,
|
|
3070
3095
|
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3071
3096
|
invocationID: entry.invocationID,
|
|
3072
3097
|
sessionHash: toHash(payload.sessionID),
|
|
@@ -3079,7 +3104,8 @@ async function writeActionDiagnostic(payload, entry, host, warnings) {
|
|
|
3079
3104
|
escalation: entry.escalation ?? false,
|
|
3080
3105
|
diagnostics: entry.diagnostics ?? null,
|
|
3081
3106
|
judge: entry.judge ?? null,
|
|
3082
|
-
grant: entry.grant ?? null
|
|
3107
|
+
grant: entry.grant ?? null,
|
|
3108
|
+
action: isReviewed ? buildLoggedAction(payload) : null
|
|
3083
3109
|
};
|
|
3084
3110
|
try {
|
|
3085
3111
|
await mkdir(dirname(path), {
|
|
@@ -3094,6 +3120,34 @@ async function writeActionDiagnostic(payload, entry, host, warnings) {
|
|
|
3094
3120
|
function toHash(value) {
|
|
3095
3121
|
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
3096
3122
|
}
|
|
3123
|
+
const LOGGED_INPUT_MAX_CHARS = 2e3;
|
|
3124
|
+
function buildLoggedAction(payload) {
|
|
3125
|
+
const redacted = buildSecretFreeValue(payload.toolInput);
|
|
3126
|
+
const input = JSON.stringify(redacted);
|
|
3127
|
+
return {
|
|
3128
|
+
tool: payload.toolName,
|
|
3129
|
+
cwd: payload.cwd,
|
|
3130
|
+
input: hasSecret(redacted) || findSecret({
|
|
3131
|
+
text: input,
|
|
3132
|
+
path: null
|
|
3133
|
+
}) !== null ? null : input.slice(0, LOGGED_INPUT_MAX_CHARS)
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
function buildSecretFreeValue(value) {
|
|
3137
|
+
if (typeof value === "string") return buildSecretFreeText(value, () => "[secret]");
|
|
3138
|
+
if (Array.isArray(value)) return value.map((item) => buildSecretFreeValue(item));
|
|
3139
|
+
if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, buildSecretFreeValue(item)]));
|
|
3140
|
+
return value;
|
|
3141
|
+
}
|
|
3142
|
+
function hasSecret(value) {
|
|
3143
|
+
if (typeof value === "string") return findSecret({
|
|
3144
|
+
text: value,
|
|
3145
|
+
path: null
|
|
3146
|
+
}) !== null;
|
|
3147
|
+
if (Array.isArray(value)) return value.some((item) => hasSecret(item));
|
|
3148
|
+
if (typeof value === "object" && value !== null) return Object.values(value).some((item) => hasSecret(item));
|
|
3149
|
+
return false;
|
|
3150
|
+
}
|
|
3097
3151
|
//#endregion
|
|
3098
3152
|
//#region src/git/build-repository-context.ts
|
|
3099
3153
|
function buildRepositoryContext(facts) {
|
|
@@ -3754,7 +3808,7 @@ function sendJudgeMessage(provider, request, transport, signal) {
|
|
|
3754
3808
|
}
|
|
3755
3809
|
async function sendToAgentSDK(provider, request, transport, signal) {
|
|
3756
3810
|
if (transport.session !== void 0) return transport.session.review(provider.model, request, signal);
|
|
3757
|
-
const session = (await import("./start-judge-session-
|
|
3811
|
+
const session = (await import("./start-judge-session-C0eRJFlg.js")).startJudgeSession(transport.env);
|
|
3758
3812
|
try {
|
|
3759
3813
|
return await session.review(provider.model, request, signal);
|
|
3760
3814
|
} finally {
|
|
@@ -4832,7 +4886,7 @@ function findReviewedRule(request, result, blockThreshold) {
|
|
|
4832
4886
|
const blocking = collectBlockingRules(request, result, blockThreshold);
|
|
4833
4887
|
const chosen = blocking.find((entry) => entry.rule.tier === "hard") ?? blocking[0];
|
|
4834
4888
|
if (chosen === void 0) return null;
|
|
4835
|
-
const misreadClears = chosen.rule.tier === "hard" &&
|
|
4889
|
+
const misreadClears = chosen.rule.tier === "hard" && blocking.every((entry) => entry.rule.source !== "configured" && entry.answer.choice === "allow" && entry.answer.probabilities.block <= .35 && (entry === chosen || entry.rule.tier === "soft"));
|
|
4836
4890
|
return {
|
|
4837
4891
|
rule: chosen.rule,
|
|
4838
4892
|
basis: chosen.answer.choice === "block" ? "matched" : "unresolved",
|
|
@@ -5521,11 +5575,19 @@ function isPlainOperand(word) {
|
|
|
5521
5575
|
function mergeScopeFacts(all) {
|
|
5522
5576
|
const pullRequests = /* @__PURE__ */ new Map();
|
|
5523
5577
|
for (const pull of all.flatMap((facts) => facts.pullRequests)) pullRequests.set(`${pull.repository}#${String(pull.number)}`, pull);
|
|
5578
|
+
const replyChannels = /* @__PURE__ */ new Map();
|
|
5579
|
+
for (const channel of all.flatMap((facts) => facts.replyChannels)) replyChannels.set(JSON.stringify([
|
|
5580
|
+
channel.tool,
|
|
5581
|
+
channel.field,
|
|
5582
|
+
channel.recipient
|
|
5583
|
+
]), channel);
|
|
5524
5584
|
return {
|
|
5525
5585
|
worktrees: [...new Set(all.flatMap((facts) => facts.worktrees))],
|
|
5526
5586
|
branches: [...new Set(all.flatMap((facts) => facts.branches))],
|
|
5527
5587
|
pullRequests: [...pullRequests.values()],
|
|
5528
|
-
pathGlobs: [...new Set(all.flatMap((facts) => facts.pathGlobs))]
|
|
5588
|
+
pathGlobs: [...new Set(all.flatMap((facts) => facts.pathGlobs))],
|
|
5589
|
+
paths: [...new Set(all.flatMap((facts) => facts.paths))],
|
|
5590
|
+
replyChannels: [...replyChannels.values()]
|
|
5529
5591
|
};
|
|
5530
5592
|
}
|
|
5531
5593
|
//#endregion
|
|
@@ -5545,17 +5607,37 @@ function buildTaskScope(input) {
|
|
|
5545
5607
|
currentBranch: input.currentBranch,
|
|
5546
5608
|
remotes: input.remotes,
|
|
5547
5609
|
pullRequests,
|
|
5548
|
-
pathGlobs: merged.pathGlobs
|
|
5610
|
+
pathGlobs: merged.pathGlobs,
|
|
5611
|
+
paths: merged.paths,
|
|
5612
|
+
replyChannels: merged.replyChannels
|
|
5549
5613
|
};
|
|
5550
5614
|
}
|
|
5551
5615
|
//#endregion
|
|
5552
|
-
//#region src/scope/
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5616
|
+
//#region src/scope/types.ts
|
|
5617
|
+
const EMPTY_SCOPE_FACTS = {
|
|
5618
|
+
worktrees: [],
|
|
5619
|
+
branches: [],
|
|
5620
|
+
pullRequests: [],
|
|
5621
|
+
pathGlobs: [],
|
|
5622
|
+
paths: [],
|
|
5623
|
+
replyChannels: []
|
|
5624
|
+
};
|
|
5625
|
+
const EMPTY_SESSION_SCOPE = {
|
|
5626
|
+
worktrees: [],
|
|
5627
|
+
branches: [],
|
|
5628
|
+
pullRequests: []
|
|
5629
|
+
};
|
|
5630
|
+
//#endregion
|
|
5631
|
+
//#region src/scope/build-declared-scope-facts.ts
|
|
5632
|
+
function buildDeclaredScopeFacts(scope, isInRepository) {
|
|
5633
|
+
const checkouts = scope.workspace === void 0 ? scope.worktrees : [scope.workspace, ...scope.worktrees];
|
|
5556
5634
|
return {
|
|
5635
|
+
...EMPTY_SCOPE_FACTS,
|
|
5557
5636
|
worktrees: checkouts.map((checkout) => checkout.path),
|
|
5558
|
-
branches: [...checkouts.flatMap((checkout) => checkout.branch !== null && isInRepository(checkout.path) ? [checkout.branch] : []), ...scope.branches.flatMap((branch) =>
|
|
5637
|
+
branches: [...checkouts.flatMap((checkout) => checkout.branch !== null && isInRepository(checkout.path) ? [checkout.branch] : []), ...scope.branches.flatMap((branch) => {
|
|
5638
|
+
const repo = branch.repo ?? scope.workspace?.path;
|
|
5639
|
+
return repo !== void 0 && isInRepository(repo) ? [branch.name] : [];
|
|
5640
|
+
})],
|
|
5559
5641
|
pullRequests: scope.pullRequests.flatMap((pull) => {
|
|
5560
5642
|
const repository = toRepositorySlug(`https://${getHost(pull.url)}/${pull.repo}`);
|
|
5561
5643
|
return pull.branch === null || repository === null ? [] : [{
|
|
@@ -5563,8 +5645,7 @@ function buildAtcScopeFacts(record, isInRepository) {
|
|
|
5563
5645
|
head: pull.branch,
|
|
5564
5646
|
repository
|
|
5565
5647
|
}];
|
|
5566
|
-
})
|
|
5567
|
-
pathGlobs: []
|
|
5648
|
+
})
|
|
5568
5649
|
};
|
|
5569
5650
|
}
|
|
5570
5651
|
function getHost(url) {
|
|
@@ -5572,33 +5653,33 @@ function getHost(url) {
|
|
|
5572
5653
|
}
|
|
5573
5654
|
//#endregion
|
|
5574
5655
|
//#region src/scope/load-atc-session-record.ts
|
|
5575
|
-
const name$
|
|
5576
|
-
const path = name$
|
|
5577
|
-
const checkoutSchema = object({
|
|
5578
|
-
path,
|
|
5579
|
-
branch: name$
|
|
5656
|
+
const name$2 = string().min(1);
|
|
5657
|
+
const path$1 = name$2.refine(isAbsolute, "an absolute path");
|
|
5658
|
+
const checkoutSchema$1 = object({
|
|
5659
|
+
path: path$1,
|
|
5660
|
+
branch: name$2.nullable()
|
|
5580
5661
|
}).readonly();
|
|
5581
|
-
const branchSchema$
|
|
5582
|
-
name: name$
|
|
5583
|
-
repo: path.optional()
|
|
5662
|
+
const branchSchema$2 = object({
|
|
5663
|
+
name: name$2,
|
|
5664
|
+
repo: path$1.optional()
|
|
5584
5665
|
}).readonly();
|
|
5585
5666
|
const pullNumber = number().int().positive();
|
|
5586
|
-
const pullRequestSchema$
|
|
5587
|
-
repo: name$
|
|
5667
|
+
const pullRequestSchema$2 = object({
|
|
5668
|
+
repo: name$2,
|
|
5588
5669
|
number: pullNumber,
|
|
5589
|
-
url: name$
|
|
5590
|
-
branch: name$
|
|
5670
|
+
url: name$2.optional(),
|
|
5671
|
+
branch: name$2.nullable()
|
|
5591
5672
|
}).readonly();
|
|
5592
5673
|
const scopeSchema = object({
|
|
5593
|
-
workspace: checkoutSchema,
|
|
5594
|
-
worktrees: array(checkoutSchema).readonly(),
|
|
5595
|
-
branches: array(branchSchema$
|
|
5596
|
-
pullRequests: array(pullRequestSchema$
|
|
5674
|
+
workspace: checkoutSchema$1,
|
|
5675
|
+
worktrees: array(checkoutSchema$1).readonly(),
|
|
5676
|
+
branches: array(branchSchema$2).readonly(),
|
|
5677
|
+
pullRequests: array(pullRequestSchema$2).readonly()
|
|
5597
5678
|
}).readonly();
|
|
5598
5679
|
const recordSchema = object({
|
|
5599
5680
|
format: literal("atc.session-record"),
|
|
5600
5681
|
version: literal(1),
|
|
5601
|
-
session: name$
|
|
5682
|
+
session: name$2,
|
|
5602
5683
|
scope: scopeSchema
|
|
5603
5684
|
}).readonly();
|
|
5604
5685
|
async function loadAtcSessionRecord(location, sessionID) {
|
|
@@ -5611,7 +5692,7 @@ async function loadAtcSessionRecord(location, sessionID) {
|
|
|
5611
5692
|
try {
|
|
5612
5693
|
raw = await readFile(location, "utf8");
|
|
5613
5694
|
} catch (error) {
|
|
5614
|
-
return isMissing(error) ? { kind: "absent" } : {
|
|
5695
|
+
return isMissing$1(error) ? { kind: "absent" } : {
|
|
5615
5696
|
kind: "malformed",
|
|
5616
5697
|
diagnostic: `atc session record unreadable: ${location}`
|
|
5617
5698
|
};
|
|
@@ -5646,22 +5727,94 @@ async function loadAtcSessionRecord(location, sessionID) {
|
|
|
5646
5727
|
record: result.data
|
|
5647
5728
|
};
|
|
5648
5729
|
}
|
|
5649
|
-
function isMissing(error) {
|
|
5730
|
+
function isMissing$1(error) {
|
|
5650
5731
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
5651
5732
|
}
|
|
5652
5733
|
//#endregion
|
|
5653
|
-
//#region src/scope/
|
|
5654
|
-
const
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
};
|
|
5660
|
-
const
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5734
|
+
//#region src/scope/load-scope-file.ts
|
|
5735
|
+
const name$1 = string().min(1);
|
|
5736
|
+
const path = name$1.refine(isAbsolute, "an absolute path").transform((value) => resolve(value));
|
|
5737
|
+
const checkoutSchema = strictObject({
|
|
5738
|
+
path,
|
|
5739
|
+
branch: name$1.nullable()
|
|
5740
|
+
}).readonly();
|
|
5741
|
+
const branchSchema$1 = strictObject({
|
|
5742
|
+
name: name$1,
|
|
5743
|
+
repo: path.optional()
|
|
5744
|
+
}).readonly();
|
|
5745
|
+
const pullRequestSchema$1 = strictObject({
|
|
5746
|
+
repo: name$1,
|
|
5747
|
+
number: number().int().positive(),
|
|
5748
|
+
url: name$1.optional(),
|
|
5749
|
+
branch: name$1
|
|
5750
|
+
}).readonly();
|
|
5751
|
+
const replyChannelSchema = strictObject({
|
|
5752
|
+
tool: name$1,
|
|
5753
|
+
field: name$1,
|
|
5754
|
+
recipient: name$1
|
|
5755
|
+
}).readonly();
|
|
5756
|
+
const scopeFileSchema = strictObject({
|
|
5757
|
+
format: literal("auto-mode.scope"),
|
|
5758
|
+
version: literal(1),
|
|
5759
|
+
paths: array(path).readonly().default([]),
|
|
5760
|
+
workspace: checkoutSchema.optional(),
|
|
5761
|
+
worktrees: array(checkoutSchema).readonly().default([]),
|
|
5762
|
+
branches: array(branchSchema$1).readonly().default([]),
|
|
5763
|
+
pullRequests: array(pullRequestSchema$1).readonly().default([]),
|
|
5764
|
+
replyChannels: array(replyChannelSchema).readonly().default([])
|
|
5765
|
+
}).superRefine((file, context) => {
|
|
5766
|
+
if (file.workspace !== void 0) return;
|
|
5767
|
+
for (const [index, branch] of file.branches.entries()) if (branch.repo === void 0) context.addIssue({
|
|
5768
|
+
code: "custom",
|
|
5769
|
+
path: [
|
|
5770
|
+
"branches",
|
|
5771
|
+
index,
|
|
5772
|
+
"repo"
|
|
5773
|
+
],
|
|
5774
|
+
message: "a branch needs a repo when the file declares no workspace"
|
|
5775
|
+
});
|
|
5776
|
+
}).readonly();
|
|
5777
|
+
async function loadScopeFile(location, readTextFile) {
|
|
5778
|
+
if (location === void 0 || location === "") return { kind: "absent" };
|
|
5779
|
+
if (!isAbsolute(location)) return {
|
|
5780
|
+
kind: "malformed",
|
|
5781
|
+
diagnostic: `scope file path is not absolute: ${location}`
|
|
5782
|
+
};
|
|
5783
|
+
let raw;
|
|
5784
|
+
try {
|
|
5785
|
+
raw = await readTextFile(location);
|
|
5786
|
+
} catch (error) {
|
|
5787
|
+
return isMissing(error) ? { kind: "absent" } : {
|
|
5788
|
+
kind: "malformed",
|
|
5789
|
+
diagnostic: `scope file unreadable: ${location}`
|
|
5790
|
+
};
|
|
5791
|
+
}
|
|
5792
|
+
let parsed;
|
|
5793
|
+
try {
|
|
5794
|
+
parsed = JSON.parse(raw);
|
|
5795
|
+
} catch {
|
|
5796
|
+
return {
|
|
5797
|
+
kind: "malformed",
|
|
5798
|
+
diagnostic: `scope file is not JSON: ${location}`
|
|
5799
|
+
};
|
|
5800
|
+
}
|
|
5801
|
+
const result = scopeFileSchema.safeParse(parsed);
|
|
5802
|
+
if (!result.success) {
|
|
5803
|
+
const [issue] = result.error.issues;
|
|
5804
|
+
const refused = issue?.code === "unrecognized_keys" ? [...issue.path, ...issue.keys.slice(0, 1)] : issue?.path;
|
|
5805
|
+
return {
|
|
5806
|
+
kind: "malformed",
|
|
5807
|
+
diagnostic: `scope file ignored: ${refused === void 0 || refused.length === 0 ? "file" : refused.join(".")} does not match version 1: ${location}`
|
|
5808
|
+
};
|
|
5809
|
+
}
|
|
5810
|
+
return {
|
|
5811
|
+
kind: "scope",
|
|
5812
|
+
scope: result.data
|
|
5813
|
+
};
|
|
5814
|
+
}
|
|
5815
|
+
function isMissing(error) {
|
|
5816
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
5817
|
+
}
|
|
5665
5818
|
//#endregion
|
|
5666
5819
|
//#region src/scope/load-session-scope.ts
|
|
5667
5820
|
const name = string().min(1);
|
|
@@ -5701,7 +5854,7 @@ function makeScopeSourceReader(source, readSourceCheckout = readCheckout) {
|
|
|
5701
5854
|
branches: scope.branches.filter((branch) => branch.commonDir === context.commonDir).map((branch) => branch.name),
|
|
5702
5855
|
pullRequests: scope.pullRequests
|
|
5703
5856
|
});
|
|
5704
|
-
}).with("globs", () => () => Promise.resolve(buildFacts({ pathGlobs: source.paths ?? [] }))).with("atc", () => (context) => readAtcScopeFacts(context, readSourceCheckout)).exhaustive();
|
|
5857
|
+
}).with("globs", () => () => Promise.resolve(buildFacts({ pathGlobs: source.paths ?? [] }))).with("launcher", () => (context) => readLauncherScopeFacts(context, readSourceCheckout)).with("atc", () => (context) => readAtcScopeFacts(context, readSourceCheckout)).exhaustive();
|
|
5705
5858
|
}
|
|
5706
5859
|
function buildFacts(facts) {
|
|
5707
5860
|
return {
|
|
@@ -5709,23 +5862,39 @@ function buildFacts(facts) {
|
|
|
5709
5862
|
...facts
|
|
5710
5863
|
};
|
|
5711
5864
|
}
|
|
5712
|
-
async function
|
|
5713
|
-
const loaded = await
|
|
5865
|
+
async function readLauncherScopeFacts(context, readSourceCheckout) {
|
|
5866
|
+
const loaded = await loadScopeFile(context.scopeFilePath, context.readTextFile);
|
|
5714
5867
|
if (loaded.kind === "malformed") context.stderr.write(`auto-mode: ${loaded.diagnostic}\n`);
|
|
5715
|
-
if (loaded.kind !== "
|
|
5716
|
-
|
|
5868
|
+
if (loaded.kind !== "scope") return EMPTY_SCOPE_FACTS;
|
|
5869
|
+
return {
|
|
5870
|
+
...await readDeclaredScopeFacts(loaded.scope, context, readSourceCheckout),
|
|
5871
|
+
paths: loaded.scope.paths,
|
|
5872
|
+
replyChannels: loaded.scope.replyChannels
|
|
5873
|
+
};
|
|
5874
|
+
}
|
|
5875
|
+
async function readDeclaredScopeFacts(scope, context, readSourceCheckout) {
|
|
5876
|
+
const workspace = scope.workspace?.path;
|
|
5717
5877
|
const paths = /* @__PURE__ */ new Set([
|
|
5718
|
-
|
|
5878
|
+
...workspace === void 0 ? [] : [workspace],
|
|
5719
5879
|
...scope.worktrees.map((worktree) => worktree.path),
|
|
5720
|
-
...scope.branches.
|
|
5880
|
+
...scope.branches.flatMap((branch) => {
|
|
5881
|
+
const repo = branch.repo ?? workspace;
|
|
5882
|
+
return repo === void 0 ? [] : [repo];
|
|
5883
|
+
})
|
|
5721
5884
|
]);
|
|
5722
5885
|
const entries = await Promise.all([...paths].map((path) => readCommonDirEntry(path, context.env, readSourceCheckout)));
|
|
5723
5886
|
const commonDirs = new Map(entries);
|
|
5724
|
-
return
|
|
5887
|
+
return buildDeclaredScopeFacts(scope, (path) => context.commonDir !== null && commonDirs.get(path) === context.commonDir);
|
|
5725
5888
|
}
|
|
5726
5889
|
async function readCommonDirEntry(path, env, readSourceCheckout) {
|
|
5727
5890
|
return [path, (await readSourceCheckout(path, env))?.commonDir ?? null];
|
|
5728
5891
|
}
|
|
5892
|
+
async function readAtcScopeFacts(context, readSourceCheckout) {
|
|
5893
|
+
const loaded = await loadAtcSessionRecord(context.atcRecordPath, context.atcSessionID);
|
|
5894
|
+
if (loaded.kind === "malformed") context.stderr.write(`auto-mode: ${loaded.diagnostic}\n`);
|
|
5895
|
+
if (loaded.kind !== "record") return EMPTY_SCOPE_FACTS;
|
|
5896
|
+
return readDeclaredScopeFacts(loaded.record.scope, context, readSourceCheckout);
|
|
5897
|
+
}
|
|
5729
5898
|
//#endregion
|
|
5730
5899
|
//#region src/scope/load-task-scope.ts
|
|
5731
5900
|
async function loadTaskScope(request, sources, host, stderr) {
|
|
@@ -5743,6 +5912,8 @@ async function loadTaskScope(request, sources, host, stderr) {
|
|
|
5743
5912
|
stateDir: request.stateDir,
|
|
5744
5913
|
atcRecordPath: host.env["ATC_SESSION_RECORD"],
|
|
5745
5914
|
atcSessionID: host.env["ATC_SESSION_ID"],
|
|
5915
|
+
scopeFilePath: host.env["AUTO_MODE_SCOPE_FILE"],
|
|
5916
|
+
readTextFile: request.readTextFile,
|
|
5746
5917
|
stderr
|
|
5747
5918
|
};
|
|
5748
5919
|
const sourceFacts = await Promise.all(Object.values(sources).map((source) => makeScopeSourceReader(source, request.readCheckout)(sourceContext)));
|
|
@@ -5829,6 +6000,7 @@ const STAGES = [
|
|
|
5829
6000
|
{ decide: classifyContainment },
|
|
5830
6001
|
{ decide: classifyRegenerable },
|
|
5831
6002
|
{ decide: classifyEditBypass },
|
|
6003
|
+
{ decide: classifyReplyChannel },
|
|
5832
6004
|
{ decide: classifyLocalOnly }
|
|
5833
6005
|
];
|
|
5834
6006
|
const FINAL_STAGE = { decide: classifyJev };
|
|
@@ -5892,7 +6064,8 @@ async function classifyContainment(ctx) {
|
|
|
5892
6064
|
sessionID: request.sessionID,
|
|
5893
6065
|
cwd: request.cwd,
|
|
5894
6066
|
checkout,
|
|
5895
|
-
readCheckout: ctx.readCheckout
|
|
6067
|
+
readCheckout: ctx.readCheckout,
|
|
6068
|
+
readTextFile: ctx.options.runtime.readTextFile
|
|
5896
6069
|
}, ctx.config, host, ctx.options.stderr);
|
|
5897
6070
|
const containment = scope === null ? null : checkContainment(request, scope, host.scratchPaths);
|
|
5898
6071
|
if (scope === null || containment === null) return {
|
|
@@ -5994,6 +6167,20 @@ async function classifyEditBypass(ctx) {
|
|
|
5994
6167
|
}
|
|
5995
6168
|
};
|
|
5996
6169
|
}
|
|
6170
|
+
async function classifyReplyChannel(ctx) {
|
|
6171
|
+
const channel = ctx.hasConfiguredDenies || ctx.scope === null ? null : findReplyChannel(ctx.request.toolName, ctx.request.toolInput, ctx.scope.replyChannels);
|
|
6172
|
+
if (channel === null) return { kind: "pass" };
|
|
6173
|
+
if ((await import("./classify-reply-message-CrZ6S-AR.js")).classifyReplyMessage(ctx.request.toolInput).kind !== "bypass") return { kind: "pass" };
|
|
6174
|
+
return {
|
|
6175
|
+
kind: "decided",
|
|
6176
|
+
outcome: {
|
|
6177
|
+
verdict: { kind: "allow" },
|
|
6178
|
+
decidingStage: "bypass",
|
|
6179
|
+
note: `allowed by the reply channel: ${channel.tool} to ${channel.recipient}`,
|
|
6180
|
+
status: "allow"
|
|
6181
|
+
}
|
|
6182
|
+
};
|
|
6183
|
+
}
|
|
5997
6184
|
function classifyLocalOnly(ctx) {
|
|
5998
6185
|
if (ctx.options.localOnly !== true) return { kind: "pass" };
|
|
5999
6186
|
return {
|
|
@@ -6076,6 +6263,11 @@ function readHostEnvironment() {
|
|
|
6076
6263
|
};
|
|
6077
6264
|
}
|
|
6078
6265
|
//#endregion
|
|
6266
|
+
//#region src/config/read-text-file.ts
|
|
6267
|
+
function readTextFile(path) {
|
|
6268
|
+
return readFile(path, "utf8");
|
|
6269
|
+
}
|
|
6270
|
+
//#endregion
|
|
6079
6271
|
//#region src/config/read-runtime.ts
|
|
6080
6272
|
function readRuntime() {
|
|
6081
6273
|
return {
|
|
@@ -6083,7 +6275,8 @@ function readRuntime() {
|
|
|
6083
6275
|
now: Date.now,
|
|
6084
6276
|
timeout: (timeoutMs) => AbortSignal.timeout(timeoutMs),
|
|
6085
6277
|
fetch: sendThroughGlobalFetch,
|
|
6086
|
-
readCheckout: (start, env) => readCheckout(start, env)
|
|
6278
|
+
readCheckout: (start, env) => readCheckout(start, env),
|
|
6279
|
+
readTextFile
|
|
6087
6280
|
};
|
|
6088
6281
|
}
|
|
6089
6282
|
function sendThroughGlobalFetch(url, init) {
|