auto-mode 0.14.0 → 0.15.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/dist/{classify-edit-D8GC6b91.js → classify-edit-LpoeenGt.js} +4 -1
- package/dist/cli.js +134 -118
- package/dist/index.d.ts +14 -2
- package/dist/index.js +1 -1
- package/dist/{render-verdict-QLyj-g7d.js → render-verdict-Bj2tGqBI.js} +358 -231
- package/mods/auto-mode/hooks/testing.d.ts +76 -24
- package/mods/auto-mode/hooks/types.ts +1 -1
- package/package.json +2 -2
- package/policy/classifier.md +4 -1
- package/policy/decision.md +2 -0
- package/policy/rules.md +18 -1
- package/mods/auto-mode/hooks/build-prompt-context.claude-check.ts +0 -93
- package/mods/auto-mode/hooks/register.claude-check.ts +0 -1226
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import { t as getEditFields } from "./get-edit-fields-BtAvHIsq.js";
|
|
2
2
|
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, matchesGlob, normalize, parse, relative, resolve } from "node:path";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
4
|
import { spawn } from "node:child_process";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
//#region src/bypass/resolve-edit-target.ts
|
|
9
|
-
async function resolveEditTarget(path) {
|
|
10
|
-
const missing = [];
|
|
11
|
-
for (let current = path;; current = dirname(current)) try {
|
|
12
|
-
const real = await realpath(current);
|
|
13
|
-
return join(real, ...missing.toReversed());
|
|
14
|
-
} catch {
|
|
15
|
-
if (await lstat(current).catch(() => null) !== null || dirname(current) === current) return null;
|
|
16
|
-
missing.push(basename(current));
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
//#endregion
|
|
20
8
|
//#region node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/util.js
|
|
21
9
|
function getEnumValues(entries) {
|
|
22
10
|
const numericValues = Object.values(entries).filter((v) => typeof v === "number");
|
|
@@ -5586,9 +5574,15 @@ function findPreset(id) {
|
|
|
5586
5574
|
return Object.hasOwn(PRESETS, id) ? PRESETS[id] : void 0;
|
|
5587
5575
|
}
|
|
5588
5576
|
//#endregion
|
|
5577
|
+
//#region src/config/to-timer-delay.ts
|
|
5578
|
+
function toTimerDelay(ms) {
|
|
5579
|
+
return Math.max(1, Math.ceil(ms));
|
|
5580
|
+
}
|
|
5581
|
+
//#endregion
|
|
5589
5582
|
//#region src/config/read-api-key-from-command.ts
|
|
5590
5583
|
function readApiKeyFromCommand(command, options = {}) {
|
|
5591
|
-
const
|
|
5584
|
+
const now = options.now ?? Date.now;
|
|
5585
|
+
const remainingMs = options.deadlineAt === void 0 ? 5e3 : options.deadlineAt - now();
|
|
5592
5586
|
if (remainingMs <= 0 || options.signal?.aborted === true) return Promise.resolve(null);
|
|
5593
5587
|
return new Promise((resolve) => {
|
|
5594
5588
|
const grouped = process.platform !== "win32";
|
|
@@ -5598,7 +5592,11 @@ function readApiKeyFromCommand(command, options = {}) {
|
|
|
5598
5592
|
"ignore",
|
|
5599
5593
|
"pipe",
|
|
5600
5594
|
"ignore"
|
|
5601
|
-
]
|
|
5595
|
+
],
|
|
5596
|
+
...options.host === void 0 ? {} : { env: {
|
|
5597
|
+
HOME: options.host.home,
|
|
5598
|
+
...options.host.env
|
|
5599
|
+
} }
|
|
5602
5600
|
});
|
|
5603
5601
|
let output = "";
|
|
5604
5602
|
let settled = false;
|
|
@@ -5606,7 +5604,7 @@ function readApiKeyFromCommand(command, options = {}) {
|
|
|
5606
5604
|
const setResult = (value) => {
|
|
5607
5605
|
if (!settled) {
|
|
5608
5606
|
settled = true;
|
|
5609
|
-
|
|
5607
|
+
timer.removeEventListener("abort", stopChild);
|
|
5610
5608
|
options.signal?.removeEventListener("abort", stopChild);
|
|
5611
5609
|
resolve(value);
|
|
5612
5610
|
}
|
|
@@ -5621,8 +5619,10 @@ function readApiKeyFromCommand(command, options = {}) {
|
|
|
5621
5619
|
child.kill("SIGKILL");
|
|
5622
5620
|
}
|
|
5623
5621
|
};
|
|
5624
|
-
const timer =
|
|
5622
|
+
const timer = (options.timeout ?? ((ms) => AbortSignal.timeout(ms)))(toTimerDelay(Math.min(5e3, remainingMs)));
|
|
5623
|
+
timer.addEventListener("abort", stopChild, { once: true });
|
|
5625
5624
|
options.signal?.addEventListener("abort", stopChild, { once: true });
|
|
5625
|
+
if (timer.aborted) stopChild();
|
|
5626
5626
|
child.stdout.setEncoding("utf8");
|
|
5627
5627
|
child.stdout.on("data", (chunk) => {
|
|
5628
5628
|
if (stopped) return;
|
|
@@ -5639,6 +5639,23 @@ function readApiKeyFromCommand(command, options = {}) {
|
|
|
5639
5639
|
});
|
|
5640
5640
|
}
|
|
5641
5641
|
//#endregion
|
|
5642
|
+
//#region src/containment/scratch-paths.ts
|
|
5643
|
+
const SCRATCH_PATHS = [
|
|
5644
|
+
"/tmp",
|
|
5645
|
+
"/dev/null",
|
|
5646
|
+
"/dev/stdout",
|
|
5647
|
+
"/dev/stderr"
|
|
5648
|
+
];
|
|
5649
|
+
//#endregion
|
|
5650
|
+
//#region src/config/read-host-environment.ts
|
|
5651
|
+
function readHostEnvironment() {
|
|
5652
|
+
return {
|
|
5653
|
+
env: process.env,
|
|
5654
|
+
home: homedir(),
|
|
5655
|
+
scratchPaths: SCRATCH_PATHS
|
|
5656
|
+
};
|
|
5657
|
+
}
|
|
5658
|
+
//#endregion
|
|
5642
5659
|
//#region src/config/config.ts
|
|
5643
5660
|
const SCOPE_SOURCE_KINDS = [
|
|
5644
5661
|
"cwd",
|
|
@@ -5664,12 +5681,13 @@ const DEFAULT_CONFIG = {
|
|
|
5664
5681
|
denialBudget: DEFAULT_DENIAL_BUDGET,
|
|
5665
5682
|
scopeSources: DEFAULT_SCOPE_SOURCES
|
|
5666
5683
|
};
|
|
5667
|
-
function resolveConfigPath() {
|
|
5668
|
-
const xdg =
|
|
5669
|
-
const base = xdg !== void 0 && xdg !== "" ? xdg : join(
|
|
5684
|
+
function resolveConfigPath(host = readHostEnvironment()) {
|
|
5685
|
+
const xdg = host.env["XDG_CONFIG_HOME"];
|
|
5686
|
+
const base = xdg !== void 0 && xdg !== "" ? xdg : join(host.home, ".config");
|
|
5670
5687
|
return join(base, "auto-mode", "config.json");
|
|
5671
5688
|
}
|
|
5672
|
-
async function loadConfig(
|
|
5689
|
+
async function loadConfig(configPath, host = readHostEnvironment()) {
|
|
5690
|
+
const path = configPath ?? resolveConfigPath(host);
|
|
5673
5691
|
let raw;
|
|
5674
5692
|
try {
|
|
5675
5693
|
raw = await readFile(path, "utf8");
|
|
@@ -5683,7 +5701,7 @@ async function loadConfig(path = resolveConfigPath()) {
|
|
|
5683
5701
|
} catch {
|
|
5684
5702
|
throw new Error(`${path} is not valid JSON`);
|
|
5685
5703
|
}
|
|
5686
|
-
return buildConfig(json, path);
|
|
5704
|
+
return buildConfig(json, path, host.home);
|
|
5687
5705
|
}
|
|
5688
5706
|
const text = string().min(1);
|
|
5689
5707
|
const positive = number().positive();
|
|
@@ -5707,13 +5725,13 @@ const configFileSchema = strictObject({
|
|
|
5707
5725
|
claudeSettingsPath: text.nullable().optional()
|
|
5708
5726
|
}).optional()
|
|
5709
5727
|
});
|
|
5710
|
-
function buildConfig(json, path) {
|
|
5728
|
+
function buildConfig(json, path, home) {
|
|
5711
5729
|
const parsed = configFileSchema.safeParse(json);
|
|
5712
5730
|
if (!parsed.success) throw new Error(`${path} is not a valid config: ${prettifyError(parsed.error)}`);
|
|
5713
5731
|
const file = parsed.data;
|
|
5714
5732
|
const warnings = [];
|
|
5715
5733
|
const classifiers = buildRegistry(file.classifiers ?? {}, (id, entry) => parseClassifierEntry(id, entry));
|
|
5716
|
-
const scopeSources = buildRegistry(file.scopeSources ?? {}, (id, entry) => parseScopeSourceEntry(id, entry));
|
|
5734
|
+
const scopeSources = buildRegistry(file.scopeSources ?? {}, (id, entry) => parseScopeSourceEntry(id, entry, home));
|
|
5717
5735
|
const configuredSources = file.scopeSources === void 0 ? null : scopeSources.entries;
|
|
5718
5736
|
warnings.push(...classifiers.dropped.map((line) => `${path}: classifiers.${line}`), ...scopeSources.dropped.map((line) => `${path}: scopeSources.${line}`), ...collectScopeWarnings(configuredSources).map((line) => `${path}: scopeSources ${line}`));
|
|
5719
5737
|
const decision = file.decision ?? {};
|
|
@@ -5723,10 +5741,10 @@ function buildConfig(json, path) {
|
|
|
5723
5741
|
provider: resolveRole("classifier", decision.classifier ?? "jev", classifiers.entries, classifiers.droppedIDs, path),
|
|
5724
5742
|
judge: judgeID === null ? null : resolveRole("judge", judgeID, classifiers.entries, classifiers.droppedIDs, path),
|
|
5725
5743
|
scopeSources: configuredSources ?? DEFAULT_SCOPE_SOURCES,
|
|
5726
|
-
classifierPath: expandHomePath(policy.frameworkPath ?? void 0),
|
|
5727
|
-
rulesPath: expandHomePath(policy.rulesPath ?? void 0),
|
|
5744
|
+
classifierPath: expandHomePath(policy.frameworkPath ?? void 0, home),
|
|
5745
|
+
rulesPath: expandHomePath(policy.rulesPath ?? void 0, home),
|
|
5728
5746
|
onFailure: decision.onFailure ?? DEFAULT_CONFIG.onFailure,
|
|
5729
|
-
claudeSettingsPath: policy.claudeSettingsPath === null ? null : expandHomePath(policy.claudeSettingsPath),
|
|
5747
|
+
claudeSettingsPath: policy.claudeSettingsPath === null ? null : expandHomePath(policy.claudeSettingsPath, home),
|
|
5730
5748
|
minConfidence: decision.minConfidence ?? DEFAULT_CONFIG.minConfidence,
|
|
5731
5749
|
denialBudget: {
|
|
5732
5750
|
consecutive: decision.denialBudget?.consecutive ?? DEFAULT_DENIAL_BUDGET.consecutive,
|
|
@@ -5801,14 +5819,14 @@ const scopeSourceEntrySchema = strictObject({
|
|
|
5801
5819
|
kind: text.optional(),
|
|
5802
5820
|
paths: array(text).min(1).optional()
|
|
5803
5821
|
});
|
|
5804
|
-
function parseScopeSourceEntry(id, entry) {
|
|
5822
|
+
function parseScopeSourceEntry(id, entry, home) {
|
|
5805
5823
|
const parsed = scopeSourceEntrySchema.safeParse(entry);
|
|
5806
5824
|
if (!parsed.success) return formatFirstIssue(parsed.error.issues);
|
|
5807
5825
|
const kind = SCOPE_SOURCE_KINDS.find((known) => known === (parsed.data.kind ?? id));
|
|
5808
5826
|
if (kind === void 0) return `unknown kind '${parsed.data.kind ?? id}'; known kinds are ${SCOPE_SOURCE_KINDS.join(", ")}`;
|
|
5809
5827
|
if (kind === "globs") return parsed.data.paths === void 0 ? "kind 'globs' needs paths" : {
|
|
5810
5828
|
kind,
|
|
5811
|
-
paths: parsed.data.paths.map((glob) => expandHomePath(glob) ?? glob)
|
|
5829
|
+
paths: parsed.data.paths.map((glob) => expandHomePath(glob, home) ?? glob)
|
|
5812
5830
|
};
|
|
5813
5831
|
return parsed.data.paths === void 0 ? { kind } : `kind '${kind}' takes no paths`;
|
|
5814
5832
|
}
|
|
@@ -5829,18 +5847,113 @@ function parseBuiltIn(id) {
|
|
|
5829
5847
|
if (typeof resolved === "string") throw new TypeError(`built-in kind ${id} does not resolve: ${resolved}`);
|
|
5830
5848
|
return resolved;
|
|
5831
5849
|
}
|
|
5832
|
-
function expandHomePath(path) {
|
|
5833
|
-
return path?.startsWith("~/") === true ? join(
|
|
5850
|
+
function expandHomePath(path, home) {
|
|
5851
|
+
return path?.startsWith("~/") === true ? join(home, path.slice(2)) : path;
|
|
5834
5852
|
}
|
|
5835
5853
|
function resolveApiKey(provider, options = {}) {
|
|
5836
|
-
const
|
|
5854
|
+
const host = options.host ?? readHostEnvironment();
|
|
5855
|
+
const fromEnv = provider.apiKeyEnv === void 0 ? void 0 : host.env[provider.apiKeyEnv];
|
|
5837
5856
|
if (fromEnv !== void 0 && fromEnv !== "") return Promise.resolve(fromEnv);
|
|
5838
5857
|
if (provider.apiKeyCommand === void 0 || provider.apiKeyCommand === "") return Promise.resolve(null);
|
|
5839
|
-
return readApiKeyFromCommand(provider.apiKeyCommand,
|
|
5858
|
+
return readApiKeyFromCommand(provider.apiKeyCommand, {
|
|
5859
|
+
...options,
|
|
5860
|
+
host
|
|
5861
|
+
});
|
|
5862
|
+
}
|
|
5863
|
+
//#endregion
|
|
5864
|
+
//#region src/scope/find-checkout.ts
|
|
5865
|
+
const GIT_OVERRIDES$1 = [
|
|
5866
|
+
"GIT_DIR",
|
|
5867
|
+
"GIT_WORK_TREE",
|
|
5868
|
+
"GIT_COMMON_DIR"
|
|
5869
|
+
];
|
|
5870
|
+
async function findCheckout(start, env) {
|
|
5871
|
+
if (GIT_OVERRIDES$1.some((name) => env[name] !== void 0)) return null;
|
|
5872
|
+
for (let directory = start;; directory = dirname(directory)) {
|
|
5873
|
+
const gitPath = join(directory, ".git");
|
|
5874
|
+
const info = await stat(gitPath).catch(() => null);
|
|
5875
|
+
if (info !== null) {
|
|
5876
|
+
const gitDir = info.isDirectory() ? gitPath : await findLinkedGitDir(directory, gitPath);
|
|
5877
|
+
return gitDir === null ? null : {
|
|
5878
|
+
worktree: directory,
|
|
5879
|
+
commonDir: await findCommonDir(gitDir)
|
|
5880
|
+
};
|
|
5881
|
+
}
|
|
5882
|
+
if (dirname(directory) === directory) return null;
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
async function findLinkedGitDir(directory, gitPath) {
|
|
5886
|
+
const link = await readFile(gitPath, "utf8").catch(() => "");
|
|
5887
|
+
const path = /^gitdir: (?<path>.+)$/mu.exec(link)?.groups?.["path"];
|
|
5888
|
+
return path === void 0 ? null : resolve(directory, path.trim());
|
|
5889
|
+
}
|
|
5890
|
+
async function findCommonDir(gitDir) {
|
|
5891
|
+
const common = await readFile(join(gitDir, "commondir"), "utf8").catch(() => null);
|
|
5892
|
+
return common === null ? gitDir : resolve(gitDir, common.trim());
|
|
5893
|
+
}
|
|
5894
|
+
//#endregion
|
|
5895
|
+
//#region src/state/resolve-state-dir.ts
|
|
5896
|
+
function resolveStateDir(host) {
|
|
5897
|
+
const stateHome = host.env["XDG_STATE_HOME"];
|
|
5898
|
+
const base = stateHome === void 0 || stateHome === "" ? join(host.home, ".local", "state") : stateHome;
|
|
5899
|
+
return join(base, "auto-mode");
|
|
5900
|
+
}
|
|
5901
|
+
//#endregion
|
|
5902
|
+
//#region src/bypass/resolve-edit-target.ts
|
|
5903
|
+
async function resolveEditTarget(path) {
|
|
5904
|
+
const missing = [];
|
|
5905
|
+
for (let current = path;; current = dirname(current)) try {
|
|
5906
|
+
const real = await realpath(current);
|
|
5907
|
+
return join(real, ...missing.toReversed());
|
|
5908
|
+
} catch {
|
|
5909
|
+
if (await lstat(current).catch(() => null) !== null || dirname(current) === current) return null;
|
|
5910
|
+
missing.push(basename(current));
|
|
5911
|
+
}
|
|
5912
|
+
}
|
|
5913
|
+
//#endregion
|
|
5914
|
+
//#region src/bypass/try-classify-edit.ts
|
|
5915
|
+
const DISK_EDIT_FILE_READER = {
|
|
5916
|
+
resolveEditTarget,
|
|
5917
|
+
findCheckout,
|
|
5918
|
+
readFile: (path) => readFile(path, "utf8")
|
|
5919
|
+
};
|
|
5920
|
+
const GIT_OVERRIDES = [
|
|
5921
|
+
"GIT_DIR",
|
|
5922
|
+
"GIT_WORK_TREE",
|
|
5923
|
+
"GIT_COMMON_DIR"
|
|
5924
|
+
];
|
|
5925
|
+
async function tryClassifyEdit(request, scope, host, files = DISK_EDIT_FILE_READER) {
|
|
5926
|
+
const fields = getEditFields(request.toolName);
|
|
5927
|
+
const path = fields === null ? void 0 : request.toolInput[fields.path];
|
|
5928
|
+
if (typeof path !== "string" || path === "") return null;
|
|
5929
|
+
try {
|
|
5930
|
+
const ownDirs = [dirname(resolveConfigPath(host)), resolveStateDir(host)];
|
|
5931
|
+
const requested = resolve(request.cwd, path);
|
|
5932
|
+
const [target, worktrees, protectedDirs] = await Promise.all([
|
|
5933
|
+
files.resolveEditTarget(requested),
|
|
5934
|
+
Promise.all(scope.worktrees.map((worktree) => files.resolveEditTarget(worktree))),
|
|
5935
|
+
Promise.all(ownDirs.map((dir) => files.resolveEditTarget(dir)))
|
|
5936
|
+
]);
|
|
5937
|
+
if (target === null || GIT_OVERRIDES.some((name) => host.env[name] !== void 0)) return null;
|
|
5938
|
+
const [checkout, current] = await Promise.all([files.findCheckout(dirname(target), host.env), request.toolName === "Edit" ? files.readFile(target).catch(() => null) : null]);
|
|
5939
|
+
return (await import("./classify-edit-LpoeenGt.js")).classifyEdit({
|
|
5940
|
+
toolName: request.toolName,
|
|
5941
|
+
toolInput: request.toolInput,
|
|
5942
|
+
requested,
|
|
5943
|
+
target,
|
|
5944
|
+
checkout: checkout?.worktree ?? null,
|
|
5945
|
+
current
|
|
5946
|
+
}, {
|
|
5947
|
+
worktrees: worktrees.filter((worktree) => worktree !== null),
|
|
5948
|
+
protectedDirs: [...ownDirs, ...protectedDirs.filter((dir) => dir !== null)]
|
|
5949
|
+
});
|
|
5950
|
+
} catch {
|
|
5951
|
+
return null;
|
|
5952
|
+
}
|
|
5840
5953
|
}
|
|
5841
5954
|
//#endregion
|
|
5842
5955
|
//#region src/config/load-claude-rules.ts
|
|
5843
|
-
async function loadClaudeRules(path) {
|
|
5956
|
+
async function loadClaudeRules(path, host) {
|
|
5844
5957
|
const empty = {
|
|
5845
5958
|
environment: [],
|
|
5846
5959
|
allow: [],
|
|
@@ -5848,8 +5961,8 @@ async function loadClaudeRules(path) {
|
|
|
5848
5961
|
hard_deny: []
|
|
5849
5962
|
};
|
|
5850
5963
|
if (path === null) return empty;
|
|
5851
|
-
const configDir =
|
|
5852
|
-
const base = configDir === void 0 || configDir === "" ? join(
|
|
5964
|
+
const configDir = host.env["CLAUDE_CONFIG_DIR"];
|
|
5965
|
+
const base = configDir === void 0 || configDir === "" ? join(host.home, ".claude") : configDir;
|
|
5853
5966
|
const settingsPath = path ?? join(base, "settings.json");
|
|
5854
5967
|
let raw;
|
|
5855
5968
|
try {
|
|
@@ -5987,13 +6100,13 @@ function toRepositorySlug(reference) {
|
|
|
5987
6100
|
}
|
|
5988
6101
|
//#endregion
|
|
5989
6102
|
//#region src/containment/collect-scope-findings.ts
|
|
5990
|
-
function collectScopeFindings(action, scope) {
|
|
6103
|
+
function collectScopeFindings(action, scope, scratchPaths = SCRATCH_PATHS) {
|
|
5991
6104
|
const filePath = action.input["file_path"] ?? action.input["notebook_path"];
|
|
5992
6105
|
const command = action.input["command"];
|
|
5993
6106
|
const findings = [
|
|
5994
|
-
...FILE_WRITE_TOOLS.has(action.tool) && typeof filePath === "string" ? collectTargetFindings(action.cwd, filePath, scope) : [],
|
|
6107
|
+
...FILE_WRITE_TOOLS.has(action.tool) && typeof filePath === "string" ? collectTargetFindings(action.cwd, filePath, scope, scratchPaths) : [],
|
|
5995
6108
|
...action.tool.startsWith("mcp__") ? collectToolFindings(action, scope) : [],
|
|
5996
|
-
...action.tool === "Bash" && typeof command === "string" ? collectCommandFindings(command, action.cwd, scope) : []
|
|
6109
|
+
...action.tool === "Bash" && typeof command === "string" ? collectCommandFindings(command, action.cwd, scope, scratchPaths) : []
|
|
5997
6110
|
];
|
|
5998
6111
|
return findings.filter((finding, index) => findings.findIndex((other) => other.kind === finding.kind && other.target === finding.target) === index);
|
|
5999
6112
|
}
|
|
@@ -6003,9 +6116,9 @@ const FILE_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
6003
6116
|
"Write",
|
|
6004
6117
|
"NotebookEdit"
|
|
6005
6118
|
]);
|
|
6006
|
-
function collectTargetFindings(directory, target, scope) {
|
|
6119
|
+
function collectTargetFindings(directory, target, scope, scratchPaths) {
|
|
6007
6120
|
const path = resolvePath(directory, target, scope.home);
|
|
6008
|
-
return path === null ? [] : collectPathFindings(path, scope);
|
|
6121
|
+
return path === null ? [] : collectPathFindings(path, scope, scratchPaths);
|
|
6009
6122
|
}
|
|
6010
6123
|
const CREDENTIAL_PATHS = [
|
|
6011
6124
|
".ssh",
|
|
@@ -6016,12 +6129,12 @@ const CREDENTIAL_PATHS = [
|
|
|
6016
6129
|
".npmrc",
|
|
6017
6130
|
".config/gh"
|
|
6018
6131
|
];
|
|
6019
|
-
function collectPathFindings(path, scope) {
|
|
6132
|
+
function collectPathFindings(path, scope, scratchPaths) {
|
|
6020
6133
|
if (CREDENTIAL_PATHS.some((entry) => isUnder(path, join(scope.home, entry)))) return [{
|
|
6021
6134
|
kind: "credential",
|
|
6022
6135
|
target: path
|
|
6023
6136
|
}];
|
|
6024
|
-
return isInScope(path, scope) ? [] : [{
|
|
6137
|
+
return isInScope(path, scope, scratchPaths) ? [] : [{
|
|
6025
6138
|
kind: "path",
|
|
6026
6139
|
target: path
|
|
6027
6140
|
}];
|
|
@@ -6052,24 +6165,24 @@ const DESTINATION_WRITERS = /* @__PURE__ */ new Set([
|
|
|
6052
6165
|
"rsync",
|
|
6053
6166
|
"ln"
|
|
6054
6167
|
]);
|
|
6055
|
-
function collectCommandFindings(command, cwd, scope) {
|
|
6168
|
+
function collectCommandFindings(command, cwd, scope, scratchPaths) {
|
|
6056
6169
|
const findings = [];
|
|
6057
6170
|
const hasPipe = /(?<!\|)\|(?!\|)/u.test(command);
|
|
6058
6171
|
let directory = cwd;
|
|
6059
6172
|
for (const segment of splitShellCommand(command).segments) {
|
|
6060
6173
|
const [name, ...args] = splitCommandWords(segment.text);
|
|
6061
|
-
for (const target of collectRedirectTargets(segment.text)) findings.push(...collectTargetFindings(directory, target, scope));
|
|
6174
|
+
for (const target of collectRedirectTargets(segment.text)) findings.push(...collectTargetFindings(directory, target, scope, scratchPaths));
|
|
6062
6175
|
if (name === void 0) continue;
|
|
6063
6176
|
if (name === "cd" && hasPipe) directory = null;
|
|
6064
6177
|
else if (name === "cd") directory = args[0] === void 0 ? scope.home : resolvePath(directory, args[0], scope.home);
|
|
6065
|
-
else findings.push(...collectProgramFindings(name, args, directory, cwd, scope));
|
|
6178
|
+
else findings.push(...collectProgramFindings(name, args, directory, cwd, scope, scratchPaths));
|
|
6066
6179
|
}
|
|
6067
6180
|
return findings;
|
|
6068
6181
|
}
|
|
6069
|
-
function collectProgramFindings(name, args, directory, cwd, scope) {
|
|
6070
|
-
const resolve = (path) => collectTargetFindings(directory, path, scope);
|
|
6182
|
+
function collectProgramFindings(name, args, directory, cwd, scope, scratchPaths) {
|
|
6183
|
+
const resolve = (path) => collectTargetFindings(directory, path, scope, scratchPaths);
|
|
6071
6184
|
const operands = args.filter((word) => !word.startsWith("-"));
|
|
6072
|
-
if (name === "git") return collectGitFindings(args, directory, cwd, scope);
|
|
6185
|
+
if (name === "git") return collectGitFindings(args, directory, cwd, scope, scratchPaths);
|
|
6073
6186
|
if (name === "gh") return collectGhFindings(args, scope);
|
|
6074
6187
|
if (PATH_WRITERS.has(name)) return operands.flatMap((path) => resolve(path));
|
|
6075
6188
|
if (DESTINATION_WRITERS.has(name)) return collectCopyFindings(name, args, operands).flatMap((path) => resolve(path));
|
|
@@ -6207,7 +6320,7 @@ const GIT_DIRECTORY_WRITES = /* @__PURE__ */ new Set([
|
|
|
6207
6320
|
"stash",
|
|
6208
6321
|
"push"
|
|
6209
6322
|
]);
|
|
6210
|
-
function collectGitFindings(args, directory, cwd, scope) {
|
|
6323
|
+
function collectGitFindings(args, directory, cwd, scope, scratchPaths) {
|
|
6211
6324
|
let gitDirectory = directory;
|
|
6212
6325
|
let index = 0;
|
|
6213
6326
|
while (index < args.length && args[index]?.startsWith("-") === true) {
|
|
@@ -6219,11 +6332,11 @@ function collectGitFindings(args, directory, cwd, scope) {
|
|
|
6219
6332
|
const rest = args.slice(index + 1);
|
|
6220
6333
|
const operands = rest.filter((word) => !word.startsWith("-"));
|
|
6221
6334
|
const findings = [];
|
|
6222
|
-
if (gitDirectory !== null && GIT_DIRECTORY_WRITES.has(subcommand) && !isInScope(gitDirectory, scope)) findings.push({
|
|
6335
|
+
if (gitDirectory !== null && GIT_DIRECTORY_WRITES.has(subcommand) && !isInScope(gitDirectory, scope, scratchPaths)) findings.push({
|
|
6223
6336
|
kind: "path",
|
|
6224
6337
|
target: gitDirectory
|
|
6225
6338
|
});
|
|
6226
|
-
if (subcommand === "worktree" && operands[0] === "remove" && operands[1] !== void 0) findings.push(...collectTargetFindings(gitDirectory, operands[1], scope));
|
|
6339
|
+
if (subcommand === "worktree" && operands[0] === "remove" && operands[1] !== void 0) findings.push(...collectTargetFindings(gitDirectory, operands[1], scope, scratchPaths));
|
|
6227
6340
|
if (subcommand === "branch" && rest.some((word) => /^-(?:d|D|-delete)$/u.test(word))) for (const branch of operands.filter((word) => !scope.branches.includes(word))) findings.push({
|
|
6228
6341
|
kind: "branch",
|
|
6229
6342
|
target: branch
|
|
@@ -6458,14 +6571,8 @@ function resolvePath(cwd, path, home) {
|
|
|
6458
6571
|
const absolute = isAbsolute(expanded) || cwd === null ? expanded : join(cwd, expanded);
|
|
6459
6572
|
return normalize(absolute).replace(/(?<=.)\/$/u, "");
|
|
6460
6573
|
}
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
"/dev/null",
|
|
6464
|
-
"/dev/stdout",
|
|
6465
|
-
"/dev/stderr"
|
|
6466
|
-
];
|
|
6467
|
-
function isInScope(path, scope) {
|
|
6468
|
-
if (SCRATCH_PATHS.some((entry) => isUnder(path, entry)) || scope.pathGlobs.some((glob) => matchesGlob(path, glob))) return true;
|
|
6574
|
+
function isInScope(path, scope, scratchPaths) {
|
|
6575
|
+
if (scratchPaths.some((entry) => isUnder(path, entry)) || scope.pathGlobs.some((glob) => matchesGlob(path, glob))) return true;
|
|
6469
6576
|
return scope.worktrees.some((worktree) => {
|
|
6470
6577
|
const isNested = isUnder(path, join(worktree, ".worktrees"));
|
|
6471
6578
|
const isOtherOwned = scope.worktrees.some((other) => other !== worktree && isUnder(path, other));
|
|
@@ -6478,12 +6585,12 @@ function isUnder(path, root) {
|
|
|
6478
6585
|
//#endregion
|
|
6479
6586
|
//#region src/containment/check-containment.ts
|
|
6480
6587
|
const CONTAINMENT_RULE = "Outside Task Scope";
|
|
6481
|
-
function checkContainment(request, scope) {
|
|
6588
|
+
function checkContainment(request, scope, scratchPaths) {
|
|
6482
6589
|
const findings = collectScopeFindings({
|
|
6483
6590
|
tool: request.toolName,
|
|
6484
6591
|
cwd: request.cwd,
|
|
6485
6592
|
input: request.toolInput
|
|
6486
|
-
}, scope);
|
|
6593
|
+
}, scope, scratchPaths);
|
|
6487
6594
|
if (findings.length === 0) return null;
|
|
6488
6595
|
return {
|
|
6489
6596
|
rule: CONTAINMENT_RULE,
|
|
@@ -6521,12 +6628,12 @@ function buildTaskScopeSummary(scope) {
|
|
|
6521
6628
|
}
|
|
6522
6629
|
//#endregion
|
|
6523
6630
|
//#region src/policy/resolve-shipped-policy-dir.ts
|
|
6524
|
-
function resolveShippedPolicyDir() {
|
|
6525
|
-
let dir =
|
|
6631
|
+
function resolveShippedPolicyDir(startDir = import.meta.dirname, stopDir = parse(startDir).root) {
|
|
6632
|
+
let dir = startDir;
|
|
6526
6633
|
for (;;) {
|
|
6527
6634
|
if (existsSync(join(dir, "policy", "classifier.md"))) return join(dir, "policy");
|
|
6528
6635
|
const parent = dirname(dir);
|
|
6529
|
-
if (parent === dir || dir ===
|
|
6636
|
+
if (parent === dir || dir === stopDir) throw new Error("auto-mode cannot find its shipped policy/ directory");
|
|
6530
6637
|
dir = parent;
|
|
6531
6638
|
}
|
|
6532
6639
|
}
|
|
@@ -6543,11 +6650,13 @@ async function loadPolicy(paths = {}, framework = "classifier.md") {
|
|
|
6543
6650
|
}
|
|
6544
6651
|
//#endregion
|
|
6545
6652
|
//#region src/model/anthropic-client.ts
|
|
6546
|
-
async function sendMessage(provider, apiKey, request) {
|
|
6653
|
+
async function sendMessage(provider, apiKey, request, signal) {
|
|
6547
6654
|
const controller = new AbortController();
|
|
6548
|
-
const
|
|
6655
|
+
const stopRequest = () => {
|
|
6549
6656
|
controller.abort();
|
|
6550
|
-
}
|
|
6657
|
+
};
|
|
6658
|
+
signal.addEventListener("abort", stopRequest, { once: true });
|
|
6659
|
+
if (signal.aborted) controller.abort();
|
|
6551
6660
|
try {
|
|
6552
6661
|
const response = await fetch(`${provider.baseURL.replace(/\/$/, "")}/v1/messages`, {
|
|
6553
6662
|
method: "POST",
|
|
@@ -6580,7 +6689,7 @@ async function sendMessage(provider, apiKey, request) {
|
|
|
6580
6689
|
}
|
|
6581
6690
|
return readResult(body);
|
|
6582
6691
|
} finally {
|
|
6583
|
-
|
|
6692
|
+
signal.removeEventListener("abort", stopRequest);
|
|
6584
6693
|
}
|
|
6585
6694
|
}
|
|
6586
6695
|
const contentBlockSchema = union([object({
|
|
@@ -6623,7 +6732,7 @@ function readResult(body) {
|
|
|
6623
6732
|
//#endregion
|
|
6624
6733
|
//#region src/model/build-request.ts
|
|
6625
6734
|
const MAX_INPUT_CHARS = 4e3;
|
|
6626
|
-
function buildUserMessage(payload, transcript, reasoning, repositoryContext = null) {
|
|
6735
|
+
function buildUserMessage(payload, transcript, reasoning, repositoryContext = null, mcpServers = []) {
|
|
6627
6736
|
const history = transcript.length === 0 ? "(no transcript available from this harness)" : transcript.map((entry) => `${entry.role}: ${entry.text}`).join("\n\n");
|
|
6628
6737
|
const input = JSON.stringify(payload.toolInput, null, 2).slice(0, MAX_INPUT_CHARS);
|
|
6629
6738
|
const closing = reasoning ? "Work through the classification process, then end your reply with the output contract tags." : "Reply with the output contract tags and nothing else.";
|
|
@@ -6631,7 +6740,7 @@ function buildUserMessage(payload, transcript, reasoning, repositoryContext = nu
|
|
|
6631
6740
|
${history}
|
|
6632
6741
|
</transcript>
|
|
6633
6742
|
|
|
6634
|
-
${repositoryContext === null ? "" : `<repository>\n${JSON.stringify(repositoryContext, null, 2)}\n</repository>\n\n`}<action>
|
|
6743
|
+
${repositoryContext === null ? "" : `<repository>\n${JSON.stringify(repositoryContext, null, 2)}\n</repository>\n\n`}${mcpServers.length === 0 ? "" : `<mcp-servers>\n${JSON.stringify(mcpServers, null, 2)}\n</mcp-servers>\n\n`}<action>
|
|
6635
6744
|
tool: ${payload.toolName}
|
|
6636
6745
|
cwd: ${payload.cwd}
|
|
6637
6746
|
input:
|
|
@@ -6663,7 +6772,7 @@ function buildTaskContext(context) {
|
|
|
6663
6772
|
}
|
|
6664
6773
|
//#endregion
|
|
6665
6774
|
//#region src/model/build-decision-request.ts
|
|
6666
|
-
function buildDecisionRequest(payload, policy, configuredRules, lastUserMessage, rulesSource = "replacement", repositoryContext = null) {
|
|
6775
|
+
function buildDecisionRequest(payload, policy, configuredRules, lastUserMessage, rulesSource = "replacement", repositoryContext = null, mcpServers = []) {
|
|
6667
6776
|
const rules = {};
|
|
6668
6777
|
let tier = null;
|
|
6669
6778
|
for (const section of policy.split(/(?=^## |^### )/mu)) if (section.startsWith("## HARD BLOCK rules")) tier = "hard";
|
|
@@ -6711,6 +6820,7 @@ function buildDecisionRequest(payload, policy, configuredRules, lastUserMessage,
|
|
|
6711
6820
|
rulesSource,
|
|
6712
6821
|
configuredRules,
|
|
6713
6822
|
...repositoryContext === null ? {} : { repositoryContext },
|
|
6823
|
+
...mcpServers.length === 0 ? {} : { mcpServers },
|
|
6714
6824
|
lastUserMessage: payload.decisionContext === void 0 || payload.decisionContext.agentID === null ? lastUserMessage : null,
|
|
6715
6825
|
...payload.decisionContext === void 0 ? {} : { taskContext: buildTaskContext(payload.decisionContext) },
|
|
6716
6826
|
action: {
|
|
@@ -6729,6 +6839,17 @@ function buildEvidenceGuidance(name) {
|
|
|
6729
6839
|
return "";
|
|
6730
6840
|
}
|
|
6731
6841
|
//#endregion
|
|
6842
|
+
//#region node_modules/.bun/tiny-invariant@1.3.3/node_modules/tiny-invariant/dist/esm/tiny-invariant.js
|
|
6843
|
+
var isProduction = process.env.NODE_ENV === "production";
|
|
6844
|
+
var prefix = "Invariant failed";
|
|
6845
|
+
function invariant(condition, message) {
|
|
6846
|
+
if (condition) return;
|
|
6847
|
+
if (isProduction) throw new Error(prefix);
|
|
6848
|
+
var provided = typeof message === "function" ? message() : message;
|
|
6849
|
+
var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
|
|
6850
|
+
throw new Error(value);
|
|
6851
|
+
}
|
|
6852
|
+
//#endregion
|
|
6732
6853
|
//#region src/model/classify-decision-answers.ts
|
|
6733
6854
|
function classifyDecisionAnswers(request, result, minConfidence) {
|
|
6734
6855
|
const entries = Object.entries(request.rules).map(([id, rule]) => {
|
|
@@ -6759,7 +6880,7 @@ function collectDecisionContributors(request, result, minConfidence) {
|
|
|
6759
6880
|
const decision = classifyDecisionAnswers(request, result, minConfidence);
|
|
6760
6881
|
return Object.entries(request.rules).flatMap(([id, rule]) => {
|
|
6761
6882
|
const answer = result.answers[id];
|
|
6762
|
-
|
|
6883
|
+
invariant(answer, "the classification refused a result with a rule unanswered");
|
|
6763
6884
|
return (decision.kind === "block" ? rule === decision.rule : decision.kind === "uncertain" && (answer.choice !== "allow" || answer.confidence < minConfidence || answer.probabilities.allow < minConfidence)) ? [{
|
|
6764
6885
|
rule: rule.source === "shipped" ? rule.name : id,
|
|
6765
6886
|
source: rule.source,
|
|
@@ -6788,6 +6909,83 @@ function formatClassifierNote(note, apiKey) {
|
|
|
6788
6909
|
return apiKey === null || apiKey === "" ? note : note.split(apiKey).join("[redacted]");
|
|
6789
6910
|
}
|
|
6790
6911
|
//#endregion
|
|
6912
|
+
//#region src/model/load-mcp-servers.ts
|
|
6913
|
+
const serverSchema = looseObject({
|
|
6914
|
+
type: string().optional(),
|
|
6915
|
+
url: string().optional()
|
|
6916
|
+
});
|
|
6917
|
+
const serversSchema = record(string(), unknown()).optional();
|
|
6918
|
+
const approvalSchema = looseObject({
|
|
6919
|
+
enableAllProjectMcpServers: boolean().optional(),
|
|
6920
|
+
enabledMcpjsonServers: array(string()).optional(),
|
|
6921
|
+
disabledMcpjsonServers: array(string()).optional()
|
|
6922
|
+
});
|
|
6923
|
+
const stateSchema = looseObject({
|
|
6924
|
+
mcpServers: serversSchema,
|
|
6925
|
+
projects: record(string(), looseObject({
|
|
6926
|
+
...approvalSchema.shape,
|
|
6927
|
+
mcpServers: serversSchema
|
|
6928
|
+
})).optional()
|
|
6929
|
+
});
|
|
6930
|
+
const projectFileSchema = looseObject({ mcpServers: serversSchema });
|
|
6931
|
+
async function loadMCPServers(cwd, host) {
|
|
6932
|
+
const configDir = host.env["CLAUDE_CONFIG_DIR"];
|
|
6933
|
+
const hasConfigDir = configDir !== void 0 && configDir !== "";
|
|
6934
|
+
const statePath = hasConfigDir ? join(configDir, ".claude.json") : join(host.home, ".claude.json");
|
|
6935
|
+
const settingsDir = hasConfigDir ? configDir : join(host.home, ".claude");
|
|
6936
|
+
const settingsPath = join(settingsDir, "settings.json");
|
|
6937
|
+
const [state, settings] = await Promise.all([readJSON(statePath, stateSchema), readJSON(settingsPath, approvalSchema)]);
|
|
6938
|
+
const directory = resolve(cwd);
|
|
6939
|
+
const projectDir = (await findCheckout(directory, host.env))?.worktree ?? directory;
|
|
6940
|
+
const project = state?.projects?.[projectDir];
|
|
6941
|
+
const projectFile = await readJSON(join(projectDir, ".mcp.json"), projectFileSchema);
|
|
6942
|
+
const approvals = [settings, project].filter((entry) => entry !== void 0 && entry !== null);
|
|
6943
|
+
const isDisabled = (name) => approvals.some((entry) => entry.disabledMcpjsonServers?.includes(name) === true);
|
|
6944
|
+
const isEnabled = (name) => approvals.some((entry) => entry.enableAllProjectMcpServers === true || entry.enabledMcpjsonServers?.includes(name) === true);
|
|
6945
|
+
const facts = [
|
|
6946
|
+
...buildServerFacts(project?.mcpServers, "local"),
|
|
6947
|
+
...buildServerFacts(projectFile?.mcpServers, "project").filter((server) => isEnabled(server.name) && !isDisabled(server.name)),
|
|
6948
|
+
...buildServerFacts(state?.mcpServers, "user")
|
|
6949
|
+
];
|
|
6950
|
+
return facts.filter((server, index) => facts.findIndex((other) => other.name === server.name) === index);
|
|
6951
|
+
}
|
|
6952
|
+
async function readJSON(path, schema) {
|
|
6953
|
+
try {
|
|
6954
|
+
const text = await readFile(path, "utf8");
|
|
6955
|
+
const parsed = schema.safeParse(JSON.parse(text));
|
|
6956
|
+
return parsed.success ? parsed.data : null;
|
|
6957
|
+
} catch {
|
|
6958
|
+
return null;
|
|
6959
|
+
}
|
|
6960
|
+
}
|
|
6961
|
+
const MAX_NAME_LENGTH = 128;
|
|
6962
|
+
const TRANSPORTS = {
|
|
6963
|
+
stdio: "stdio",
|
|
6964
|
+
http: "http",
|
|
6965
|
+
"streamable-http": "http",
|
|
6966
|
+
sse: "sse",
|
|
6967
|
+
ws: "ws"
|
|
6968
|
+
};
|
|
6969
|
+
function buildServerFacts(servers, scope) {
|
|
6970
|
+
return Object.entries(servers ?? {}).flatMap(([name, entry]) => {
|
|
6971
|
+
const server = serverSchema.safeParse(entry);
|
|
6972
|
+
if (!server.success || name === "" || name.length > MAX_NAME_LENGTH) return [];
|
|
6973
|
+
const transport = TRANSPORTS[server.data.type ?? (server.data.url === void 0 ? "stdio" : "")];
|
|
6974
|
+
if (transport === void 0) return [];
|
|
6975
|
+
return [{
|
|
6976
|
+
name,
|
|
6977
|
+
scope,
|
|
6978
|
+
transport,
|
|
6979
|
+
host: transport === "stdio" || server.data.url === void 0 ? null : findEndpointHost(server.data.url)
|
|
6980
|
+
}];
|
|
6981
|
+
});
|
|
6982
|
+
}
|
|
6983
|
+
function findEndpointHost(url) {
|
|
6984
|
+
const parsed = URL.parse(url);
|
|
6985
|
+
if (parsed === null || !/^(?:https?|wss?):$/u.test(parsed.protocol) || /[${}]/u.test(parsed.host)) return null;
|
|
6986
|
+
return parsed.host;
|
|
6987
|
+
}
|
|
6988
|
+
//#endregion
|
|
6791
6989
|
//#region src/scope/load-checkout-remotes.ts
|
|
6792
6990
|
async function loadCheckoutRemotes(commonDir) {
|
|
6793
6991
|
const config = await readFile(join(commonDir, "config"), "utf8").catch(() => "");
|
|
@@ -6829,12 +7027,12 @@ function parseConfigValue(raw) {
|
|
|
6829
7027
|
}
|
|
6830
7028
|
//#endregion
|
|
6831
7029
|
//#region src/model/load-repository-context.ts
|
|
6832
|
-
async function loadRepositoryContext(cwd) {
|
|
7030
|
+
async function loadRepositoryContext(cwd, env, stopDir = parse(resolve(cwd)).root) {
|
|
6833
7031
|
if ([
|
|
6834
7032
|
"GIT_DIR",
|
|
6835
7033
|
"GIT_WORK_TREE",
|
|
6836
7034
|
"GIT_COMMON_DIR"
|
|
6837
|
-
].some((name) =>
|
|
7035
|
+
].some((name) => env[name] !== void 0)) return null;
|
|
6838
7036
|
let directory = resolve(cwd);
|
|
6839
7037
|
for (;;) {
|
|
6840
7038
|
const gitPath = join(directory, ".git");
|
|
@@ -6843,7 +7041,7 @@ async function loadRepositoryContext(cwd) {
|
|
|
6843
7041
|
} catch (error) {
|
|
6844
7042
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) return null;
|
|
6845
7043
|
const parent = dirname(directory);
|
|
6846
|
-
if (parent === directory) return null;
|
|
7044
|
+
if (parent === directory || directory === resolve(stopDir)) return null;
|
|
6847
7045
|
directory = parent;
|
|
6848
7046
|
continue;
|
|
6849
7047
|
}
|
|
@@ -6914,8 +7112,8 @@ function normalizeRemoteURL(url) {
|
|
|
6914
7112
|
}
|
|
6915
7113
|
//#endregion
|
|
6916
7114
|
//#region src/model/load-repository-evidence.ts
|
|
6917
|
-
async function loadRepositoryEvidence(cwd, taskScope) {
|
|
6918
|
-
const context = await loadRepositoryContext(cwd);
|
|
7115
|
+
async function loadRepositoryEvidence(cwd, taskScope, env, stopDir) {
|
|
7116
|
+
const context = await loadRepositoryContext(cwd, env, stopDir);
|
|
6919
7117
|
if (taskScope === void 0) return context;
|
|
6920
7118
|
return {
|
|
6921
7119
|
...context ?? {
|
|
@@ -7324,6 +7522,28 @@ function pickDecisionVerdict(request, result, minConfidence) {
|
|
|
7324
7522
|
})).exhaustive();
|
|
7325
7523
|
}
|
|
7326
7524
|
//#endregion
|
|
7525
|
+
//#region src/model/decision-response-schema.ts
|
|
7526
|
+
const probability = number().min(0).max(1);
|
|
7527
|
+
const answerSchema = object({
|
|
7528
|
+
type: literal("choice"),
|
|
7529
|
+
choice: _enum([
|
|
7530
|
+
"allow",
|
|
7531
|
+
"block",
|
|
7532
|
+
"ask"
|
|
7533
|
+
]),
|
|
7534
|
+
confidence: probability,
|
|
7535
|
+
probabilities: strictObject({
|
|
7536
|
+
allow: probability,
|
|
7537
|
+
block: probability,
|
|
7538
|
+
ask: probability
|
|
7539
|
+
})
|
|
7540
|
+
});
|
|
7541
|
+
const decisionResponseSchema = object({
|
|
7542
|
+
model: string().min(1),
|
|
7543
|
+
answers: record(string(), answerSchema),
|
|
7544
|
+
usage: object({ input_tokens: number().int().nonnegative() })
|
|
7545
|
+
});
|
|
7546
|
+
//#endregion
|
|
7327
7547
|
//#region src/model/send-decision.ts
|
|
7328
7548
|
async function sendDecision(provider, apiKey, request, signal) {
|
|
7329
7549
|
const input = {
|
|
@@ -7351,11 +7571,8 @@ async function sendDecision(provider, apiKey, request, signal) {
|
|
|
7351
7571
|
const stopRequest = () => {
|
|
7352
7572
|
controller.abort();
|
|
7353
7573
|
};
|
|
7354
|
-
signal
|
|
7355
|
-
if (signal
|
|
7356
|
-
const timer = setTimeout(() => {
|
|
7357
|
-
controller.abort();
|
|
7358
|
-
}, provider.timeoutMs);
|
|
7574
|
+
signal.addEventListener("abort", stopRequest, { once: true });
|
|
7575
|
+
if (signal.aborted) controller.abort();
|
|
7359
7576
|
try {
|
|
7360
7577
|
let response;
|
|
7361
7578
|
try {
|
|
@@ -7373,26 +7590,6 @@ async function sendDecision(provider, apiKey, request, signal) {
|
|
|
7373
7590
|
throw new DecisionRequestError("network", requestBytes, "Decision API request failed");
|
|
7374
7591
|
}
|
|
7375
7592
|
if (!response.ok) throw new DecisionRequestError("http-status", requestBytes, `Decision API returned HTTP ${response.status}`);
|
|
7376
|
-
const probability = number().min(0).max(1);
|
|
7377
|
-
const answer = object({
|
|
7378
|
-
type: literal("choice"),
|
|
7379
|
-
choice: _enum([
|
|
7380
|
-
"allow",
|
|
7381
|
-
"block",
|
|
7382
|
-
"ask"
|
|
7383
|
-
]),
|
|
7384
|
-
confidence: probability,
|
|
7385
|
-
probabilities: strictObject({
|
|
7386
|
-
allow: probability,
|
|
7387
|
-
block: probability,
|
|
7388
|
-
ask: probability
|
|
7389
|
-
})
|
|
7390
|
-
});
|
|
7391
|
-
const schema = object({
|
|
7392
|
-
model: string().min(1),
|
|
7393
|
-
answers: record(string(), answer),
|
|
7394
|
-
usage: object({ input_tokens: number().int().nonnegative() })
|
|
7395
|
-
});
|
|
7396
7593
|
let responseBody;
|
|
7397
7594
|
try {
|
|
7398
7595
|
responseBody = await response.json();
|
|
@@ -7400,7 +7597,7 @@ async function sendDecision(provider, apiKey, request, signal) {
|
|
|
7400
7597
|
if (error instanceof Error && error.name === "AbortError") throw new DecisionRequestError("aborted", requestBytes, "Decision request aborted", { cause: error });
|
|
7401
7598
|
throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned invalid JSON");
|
|
7402
7599
|
}
|
|
7403
|
-
const parsedResponse =
|
|
7600
|
+
const parsedResponse = decisionResponseSchema.safeParse(responseBody);
|
|
7404
7601
|
if (!parsedResponse.success) throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned a malformed response");
|
|
7405
7602
|
const parsed = parsedResponse.data;
|
|
7406
7603
|
if (Object.keys(parsed.answers).length !== Object.keys(request.questions).length) throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned an incomplete answer set");
|
|
@@ -7417,19 +7614,19 @@ async function sendDecision(provider, apiKey, request, signal) {
|
|
|
7417
7614
|
requestBytes
|
|
7418
7615
|
};
|
|
7419
7616
|
} finally {
|
|
7420
|
-
|
|
7421
|
-
signal?.removeEventListener("abort", stopRequest);
|
|
7617
|
+
signal.removeEventListener("abort", stopRequest);
|
|
7422
7618
|
}
|
|
7423
7619
|
}
|
|
7424
7620
|
//#endregion
|
|
7425
7621
|
//#region src/model/classify-with-jev.ts
|
|
7426
|
-
async function classifyWithJev(payload, config, options
|
|
7622
|
+
async function classifyWithJev(payload, config, options) {
|
|
7427
7623
|
const start = performance.now();
|
|
7624
|
+
const now = options.now ?? Date.now;
|
|
7428
7625
|
let key = null;
|
|
7429
7626
|
let stage = "credential";
|
|
7430
7627
|
const minConfidence = config.minConfidence ?? .8;
|
|
7431
7628
|
let keySource = "none";
|
|
7432
|
-
const envKey = config.provider.apiKeyEnv === void 0 ? void 0 :
|
|
7629
|
+
const envKey = config.provider.apiKeyEnv === void 0 ? void 0 : options.host.env[config.provider.apiKeyEnv];
|
|
7433
7630
|
if (envKey !== void 0 && envKey !== "") keySource = "environment";
|
|
7434
7631
|
else if (config.provider.apiKeyCommand !== void 0 && config.provider.apiKeyCommand !== "") keySource = "command";
|
|
7435
7632
|
try {
|
|
@@ -7437,23 +7634,22 @@ async function classifyWithJev(payload, config, options = {}) {
|
|
|
7437
7634
|
if (key === null) throw new Error("no API key: set the configured environment variable or key command");
|
|
7438
7635
|
stage = "evidence";
|
|
7439
7636
|
const directUserText = payload.decisionContext?.agentID === null ? payload.decisionContext.lastDirectUserMessage?.text ?? null : null;
|
|
7440
|
-
const [policy, rules, repositoryContext] = await Promise.all([
|
|
7637
|
+
const [policy, rules, repositoryContext, mcpServers] = await Promise.all([
|
|
7441
7638
|
loadPolicy({
|
|
7442
7639
|
classifierPath: config.classifierPath,
|
|
7443
7640
|
rulesPath: config.rulesPath
|
|
7444
7641
|
}, "decision.md"),
|
|
7445
|
-
loadClaudeRules(config.claudeSettingsPath),
|
|
7446
|
-
loadRepositoryEvidence(payload.cwd, options.taskScope).catch(() => null)
|
|
7642
|
+
loadClaudeRules(config.claudeSettingsPath, options.host),
|
|
7643
|
+
loadRepositoryEvidence(payload.cwd, options.taskScope, options.host.env).catch(() => null),
|
|
7644
|
+
loadMCPServers(payload.cwd, options.host).catch(() => [])
|
|
7447
7645
|
]);
|
|
7448
|
-
const request = buildDecisionRequest(payload, policy, rules, directUserText, config.rulesPath === void 0 ? "shipped" : "replacement", repositoryContext);
|
|
7449
|
-
const remainingMs = options.deadlineAt === void 0 ? config.provider.timeoutMs : options.deadlineAt -
|
|
7646
|
+
const request = buildDecisionRequest(payload, policy, rules, directUserText, config.rulesPath === void 0 ? "shipped" : "replacement", repositoryContext, mcpServers);
|
|
7647
|
+
const remainingMs = options.deadlineAt === void 0 ? config.provider.timeoutMs : options.deadlineAt - now();
|
|
7450
7648
|
if (remainingMs <= 0 || options.signal?.aborted === true) throw new DOMException("Evaluation deadline expired", "AbortError");
|
|
7451
|
-
const
|
|
7452
|
-
|
|
7453
|
-
timeoutMs: Math.min(config.provider.timeoutMs, remainingMs)
|
|
7454
|
-
};
|
|
7649
|
+
const timer = (options.timeout ?? ((ms) => AbortSignal.timeout(ms)))(toTimerDelay(Math.min(config.provider.timeoutMs, remainingMs)));
|
|
7650
|
+
const signal = options.signal === void 0 ? timer : AbortSignal.any([timer, options.signal]);
|
|
7455
7651
|
stage = "request";
|
|
7456
|
-
const result = await sendDecision(provider, key, request,
|
|
7652
|
+
const result = await sendDecision(config.provider, key, request, signal);
|
|
7457
7653
|
stage = "response";
|
|
7458
7654
|
const verdict = pickDecisionVerdict(request, result, minConfidence);
|
|
7459
7655
|
return {
|
|
@@ -7477,7 +7673,7 @@ async function classifyWithJev(payload, config, options = {}) {
|
|
|
7477
7673
|
if (options.signal?.aborted === true) {
|
|
7478
7674
|
reason = "evaluation cancelled";
|
|
7479
7675
|
status = "cancelled";
|
|
7480
|
-
} else if (options.deadlineAt !== void 0 &&
|
|
7676
|
+
} else if (options.deadlineAt !== void 0 && now() >= options.deadlineAt) {
|
|
7481
7677
|
reason = "evaluation deadline expired";
|
|
7482
7678
|
status = "timeout";
|
|
7483
7679
|
} else if (error instanceof Error) {
|
|
@@ -7538,8 +7734,15 @@ function parseModelVerdict(text) {
|
|
|
7538
7734
|
//#endregion
|
|
7539
7735
|
//#region src/model/classify-with-model.ts
|
|
7540
7736
|
async function classifyWithModel(payload, config, options = {}) {
|
|
7541
|
-
|
|
7542
|
-
|
|
7737
|
+
const host = options.host ?? readHostEnvironment();
|
|
7738
|
+
if (config.provider.protocol === "system-one") return classifyWithJev(payload, config, {
|
|
7739
|
+
...options,
|
|
7740
|
+
host
|
|
7741
|
+
});
|
|
7742
|
+
const apiKey = await resolveApiKey(config.provider, {
|
|
7743
|
+
...options,
|
|
7744
|
+
host
|
|
7745
|
+
});
|
|
7543
7746
|
if (apiKey === null) return buildFailure(config, "no API key: set the configured environment variable or key command");
|
|
7544
7747
|
let system;
|
|
7545
7748
|
try {
|
|
@@ -7555,13 +7758,14 @@ async function classifyWithModel(payload, config, options = {}) {
|
|
|
7555
7758
|
role: "user",
|
|
7556
7759
|
text: directMessage.text
|
|
7557
7760
|
}];
|
|
7558
|
-
const repositoryContext = await loadRepositoryEvidence(payload.cwd, options.taskScope).catch(() => null);
|
|
7559
|
-
const user = buildUserMessage(payload, transcript, config.provider.reasoning, repositoryContext);
|
|
7761
|
+
const [repositoryContext, mcpServers] = await Promise.all([loadRepositoryEvidence(payload.cwd, options.taskScope, host.env).catch(() => null), loadMCPServers(payload.cwd, host).catch(() => [])]);
|
|
7762
|
+
const user = buildUserMessage(payload, transcript, config.provider.reasoning, repositoryContext, mcpServers);
|
|
7560
7763
|
try {
|
|
7764
|
+
const timeout = options.timeout ?? ((ms) => AbortSignal.timeout(ms));
|
|
7561
7765
|
const result = await sendMessage(config.provider, apiKey, {
|
|
7562
7766
|
system,
|
|
7563
7767
|
user
|
|
7564
|
-
});
|
|
7768
|
+
}, timeout(toTimerDelay(config.provider.timeoutMs)));
|
|
7565
7769
|
if (result.text.trim() === "") return buildFailure(config, `${config.provider.model} returned no text; raise maxTokens`);
|
|
7566
7770
|
const verdict = parseModelVerdict(formatClassifierNote(result.text, apiKey));
|
|
7567
7771
|
const cache = `${result.cachedInputTokens} cached / ${result.cacheWriteTokens} written / ${result.newInputTokens} new / ${result.outputTokens} out`;
|
|
@@ -7593,19 +7797,8 @@ function toMessage(error) {
|
|
|
7593
7797
|
}
|
|
7594
7798
|
//#endregion
|
|
7595
7799
|
//#region src/policy/read-denial-guidance.ts
|
|
7596
|
-
async function readDenialGuidance() {
|
|
7597
|
-
return (await readFile(join(
|
|
7598
|
-
}
|
|
7599
|
-
//#endregion
|
|
7600
|
-
//#region node_modules/.bun/tiny-invariant@1.3.3/node_modules/tiny-invariant/dist/esm/tiny-invariant.js
|
|
7601
|
-
var isProduction = process.env.NODE_ENV === "production";
|
|
7602
|
-
var prefix = "Invariant failed";
|
|
7603
|
-
function invariant(condition, message) {
|
|
7604
|
-
if (condition) return;
|
|
7605
|
-
if (isProduction) throw new Error(prefix);
|
|
7606
|
-
var provided = typeof message === "function" ? message() : message;
|
|
7607
|
-
var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
|
|
7608
|
-
throw new Error(value);
|
|
7800
|
+
async function readDenialGuidance(policyDir = resolveShippedPolicyDir()) {
|
|
7801
|
+
return (await readFile(join(policyDir, "denial.md"), "utf8")).trim();
|
|
7609
7802
|
}
|
|
7610
7803
|
//#endregion
|
|
7611
7804
|
//#region src/rules/local-allowlists.ts
|
|
@@ -7769,37 +7962,6 @@ function toUnquoted(word) {
|
|
|
7769
7962
|
return (first === "'" || first === "\"") && word.at(-1) === first ? word.slice(1, -1) : word;
|
|
7770
7963
|
}
|
|
7771
7964
|
//#endregion
|
|
7772
|
-
//#region src/scope/find-checkout.ts
|
|
7773
|
-
const GIT_OVERRIDES$1 = [
|
|
7774
|
-
"GIT_DIR",
|
|
7775
|
-
"GIT_WORK_TREE",
|
|
7776
|
-
"GIT_COMMON_DIR"
|
|
7777
|
-
];
|
|
7778
|
-
async function findCheckout(start) {
|
|
7779
|
-
if (GIT_OVERRIDES$1.some((name) => process.env[name] !== void 0)) return null;
|
|
7780
|
-
for (let directory = start;; directory = dirname(directory)) {
|
|
7781
|
-
const gitPath = join(directory, ".git");
|
|
7782
|
-
const info = await stat(gitPath).catch(() => null);
|
|
7783
|
-
if (info !== null) {
|
|
7784
|
-
const gitDir = info.isDirectory() ? gitPath : await findLinkedGitDir(directory, gitPath);
|
|
7785
|
-
return gitDir === null ? null : {
|
|
7786
|
-
worktree: directory,
|
|
7787
|
-
commonDir: await findCommonDir(gitDir)
|
|
7788
|
-
};
|
|
7789
|
-
}
|
|
7790
|
-
if (dirname(directory) === directory) return null;
|
|
7791
|
-
}
|
|
7792
|
-
}
|
|
7793
|
-
async function findLinkedGitDir(directory, gitPath) {
|
|
7794
|
-
const link = await readFile(gitPath, "utf8").catch(() => "");
|
|
7795
|
-
const path = /^gitdir: (?<path>.+)$/mu.exec(link)?.groups?.["path"];
|
|
7796
|
-
return path === void 0 ? null : resolve(directory, path.trim());
|
|
7797
|
-
}
|
|
7798
|
-
async function findCommonDir(gitDir) {
|
|
7799
|
-
const common = await readFile(join(gitDir, "commondir"), "utf8").catch(() => null);
|
|
7800
|
-
return common === null ? gitDir : resolve(gitDir, common.trim());
|
|
7801
|
-
}
|
|
7802
|
-
//#endregion
|
|
7803
7965
|
//#region src/scope/merge-scope-facts.ts
|
|
7804
7966
|
function mergeScopeFacts(all) {
|
|
7805
7967
|
const pullRequests = /* @__PURE__ */ new Map();
|
|
@@ -7989,7 +8151,7 @@ function resolveSessionScopePath(stateDir, sessionID) {
|
|
|
7989
8151
|
}
|
|
7990
8152
|
//#endregion
|
|
7991
8153
|
//#region src/scope/pick-scope-source-reader.ts
|
|
7992
|
-
function pickScopeSourceReader(source) {
|
|
8154
|
+
function pickScopeSourceReader(source, findSourceCheckout = findCheckout) {
|
|
7993
8155
|
if (source.kind === "cwd") return (context) => Promise.resolve(buildFacts({
|
|
7994
8156
|
worktrees: [context.worktree],
|
|
7995
8157
|
branches: context.branch === null ? [] : [context.branch]
|
|
@@ -8005,7 +8167,7 @@ function pickScopeSourceReader(source) {
|
|
|
8005
8167
|
if (source.kind === "globs") return () => Promise.resolve(buildFacts({ pathGlobs: source.paths ?? [] }));
|
|
8006
8168
|
return async (context) => {
|
|
8007
8169
|
const loaded = await loadAtcSessionRecord(context.atcRecordPath, context.atcSessionID);
|
|
8008
|
-
if (loaded.kind === "malformed")
|
|
8170
|
+
if (loaded.kind === "malformed") context.stderr.write(`auto-mode: ${loaded.diagnostic}\n`);
|
|
8009
8171
|
if (loaded.kind !== "record") return EMPTY_SCOPE_FACTS;
|
|
8010
8172
|
const scope = loaded.record.scope;
|
|
8011
8173
|
const paths = /* @__PURE__ */ new Set([
|
|
@@ -8013,13 +8175,13 @@ function pickScopeSourceReader(source) {
|
|
|
8013
8175
|
...scope.worktrees.map((worktree) => worktree.path),
|
|
8014
8176
|
...scope.branches.map((branch) => branch.repo ?? scope.workspace.path)
|
|
8015
8177
|
]);
|
|
8016
|
-
const entries = await Promise.all([...paths].map((path) => findCommonDirEntry(path)));
|
|
8178
|
+
const entries = await Promise.all([...paths].map((path) => findCommonDirEntry(path, context.env, findSourceCheckout)));
|
|
8017
8179
|
const commonDirs = new Map(entries);
|
|
8018
8180
|
return buildAtcScopeFacts(loaded.record, (path) => context.commonDir !== null && commonDirs.get(path) === context.commonDir);
|
|
8019
8181
|
};
|
|
8020
8182
|
}
|
|
8021
|
-
async function findCommonDirEntry(path) {
|
|
8022
|
-
return [path, (await
|
|
8183
|
+
async function findCommonDirEntry(path, env, findSourceCheckout) {
|
|
8184
|
+
return [path, (await findSourceCheckout(path, env))?.commonDir ?? null];
|
|
8023
8185
|
}
|
|
8024
8186
|
function buildFacts(facts) {
|
|
8025
8187
|
return {
|
|
@@ -8029,23 +8191,25 @@ function buildFacts(facts) {
|
|
|
8029
8191
|
}
|
|
8030
8192
|
//#endregion
|
|
8031
8193
|
//#region src/scope/load-task-scope.ts
|
|
8032
|
-
async function loadTaskScope(request, sources) {
|
|
8194
|
+
async function loadTaskScope(request, sources, host, stderr) {
|
|
8033
8195
|
const cwd = resolve(request.cwd);
|
|
8034
|
-
const [checkout, context] = await Promise.all([findCheckout(cwd), loadRepositoryContext(cwd)]);
|
|
8196
|
+
const [checkout, context] = await Promise.all([findCheckout(cwd, host.env), loadRepositoryContext(cwd, host.env)]);
|
|
8035
8197
|
const branch = context?.branch ?? null;
|
|
8036
8198
|
const sourceContext = {
|
|
8199
|
+
env: host.env,
|
|
8037
8200
|
sessionID: request.sessionID,
|
|
8038
8201
|
cwd,
|
|
8039
8202
|
worktree: checkout?.worktree ?? cwd,
|
|
8040
8203
|
commonDir: checkout?.commonDir ?? null,
|
|
8041
8204
|
branch,
|
|
8042
8205
|
stateDir: request.stateDir,
|
|
8043
|
-
atcRecordPath:
|
|
8044
|
-
atcSessionID:
|
|
8206
|
+
atcRecordPath: host.env["ATC_SESSION_RECORD"],
|
|
8207
|
+
atcSessionID: host.env["ATC_SESSION_ID"],
|
|
8208
|
+
stderr
|
|
8045
8209
|
};
|
|
8046
8210
|
const [remotes, ...facts] = await Promise.all([checkout === null ? [] : loadCheckoutRemotes(checkout.commonDir), ...Object.values(sources).map((source) => pickScopeSourceReader(source)(sourceContext))]);
|
|
8047
8211
|
return buildTaskScope({
|
|
8048
|
-
home:
|
|
8212
|
+
home: host.home,
|
|
8049
8213
|
currentBranch: branch,
|
|
8050
8214
|
defaultBranch: context?.defaultBranch ?? null,
|
|
8051
8215
|
remotes,
|
|
@@ -8053,18 +8217,12 @@ async function loadTaskScope(request, sources) {
|
|
|
8053
8217
|
});
|
|
8054
8218
|
}
|
|
8055
8219
|
//#endregion
|
|
8056
|
-
//#region src/state/resolve-state-dir.ts
|
|
8057
|
-
function resolveStateDir() {
|
|
8058
|
-
const stateHome = process.env["XDG_STATE_HOME"];
|
|
8059
|
-
const base = stateHome === void 0 || stateHome === "" ? join(homedir(), ".local", "state") : stateHome;
|
|
8060
|
-
return join(base, "auto-mode");
|
|
8061
|
-
}
|
|
8062
|
-
//#endregion
|
|
8063
8220
|
//#region src/classify-action.ts
|
|
8064
8221
|
async function classifyAction(request, config, options = {}) {
|
|
8222
|
+
const host = options.host ?? readHostEnvironment();
|
|
8065
8223
|
let configured = null;
|
|
8066
8224
|
if (config.provider.protocol === "system-one") try {
|
|
8067
|
-
configured = await loadClaudeRules(config.claudeSettingsPath);
|
|
8225
|
+
configured = await loadClaudeRules(config.claudeSettingsPath, host);
|
|
8068
8226
|
} catch {
|
|
8069
8227
|
const guidance = config.onFailure === "deny" ? await tryReadDenialGuidance() : null;
|
|
8070
8228
|
return {
|
|
@@ -8078,8 +8236,8 @@ async function classifyAction(request, config, options = {}) {
|
|
|
8078
8236
|
const hasConfiguredDenies = configured !== null && (configured.hard_deny.length > 0 || configured.soft_deny.length > 0);
|
|
8079
8237
|
const local = hasConfiguredDenies ? { kind: "escalate" } : classifyLocally(request);
|
|
8080
8238
|
if (local.kind === "allow" && local.exception === READ_ONLY_EXCEPTION) return buildLocalAllow(local.exception);
|
|
8081
|
-
const scope = await tryLoadTaskScope(request, config);
|
|
8082
|
-
const containment = scope === null ? null : checkContainment(request, scope);
|
|
8239
|
+
const scope = await tryLoadTaskScope(request, config, host, options.stderr ?? process.stderr);
|
|
8240
|
+
const containment = scope === null ? null : checkContainment(request, scope, host.scratchPaths);
|
|
8083
8241
|
if (containment !== null) {
|
|
8084
8242
|
const guidance = await tryReadDenialGuidance();
|
|
8085
8243
|
return {
|
|
@@ -8090,7 +8248,7 @@ async function classifyAction(request, config, options = {}) {
|
|
|
8090
8248
|
};
|
|
8091
8249
|
}
|
|
8092
8250
|
if (local.kind === "allow") return buildLocalAllow(local.exception);
|
|
8093
|
-
const edit = hasConfiguredDenies || scope === null ? null : await tryClassifyEdit(request, scope);
|
|
8251
|
+
const edit = hasConfiguredDenies || scope === null ? null : await tryClassifyEdit(request, scope, host);
|
|
8094
8252
|
if (edit?.kind === "bypass") return {
|
|
8095
8253
|
verdict: { kind: "allow" },
|
|
8096
8254
|
decidingStage: "bypass",
|
|
@@ -8106,7 +8264,8 @@ async function classifyAction(request, config, options = {}) {
|
|
|
8106
8264
|
const taskScope = scope === null ? void 0 : buildTaskScopeSummary(scope);
|
|
8107
8265
|
const [outcome, guidance] = await Promise.all([classifyWithModel(request, config, {
|
|
8108
8266
|
...options,
|
|
8109
|
-
taskScope
|
|
8267
|
+
taskScope,
|
|
8268
|
+
host
|
|
8110
8269
|
}), tryReadDenialGuidance()]);
|
|
8111
8270
|
return {
|
|
8112
8271
|
...outcome,
|
|
@@ -8116,47 +8275,13 @@ async function classifyAction(request, config, options = {}) {
|
|
|
8116
8275
|
};
|
|
8117
8276
|
}
|
|
8118
8277
|
const READ_ONLY_EXCEPTION = "Read-only actions";
|
|
8119
|
-
async function tryLoadTaskScope(request, config) {
|
|
8278
|
+
async function tryLoadTaskScope(request, config, host, stderr) {
|
|
8120
8279
|
try {
|
|
8121
8280
|
return await loadTaskScope({
|
|
8122
8281
|
sessionID: request.sessionID,
|
|
8123
8282
|
cwd: request.cwd,
|
|
8124
|
-
stateDir: resolveStateDir()
|
|
8125
|
-
}, config.scopeSources ?? DEFAULT_SCOPE_SOURCES);
|
|
8126
|
-
} catch {
|
|
8127
|
-
return null;
|
|
8128
|
-
}
|
|
8129
|
-
}
|
|
8130
|
-
const GIT_OVERRIDES = [
|
|
8131
|
-
"GIT_DIR",
|
|
8132
|
-
"GIT_WORK_TREE",
|
|
8133
|
-
"GIT_COMMON_DIR"
|
|
8134
|
-
];
|
|
8135
|
-
async function tryClassifyEdit(request, scope) {
|
|
8136
|
-
const fields = getEditFields(request.toolName);
|
|
8137
|
-
const path = fields === null ? void 0 : request.toolInput[fields.path];
|
|
8138
|
-
if (typeof path !== "string" || path === "") return null;
|
|
8139
|
-
try {
|
|
8140
|
-
const ownDirs = [dirname(resolveConfigPath()), resolveStateDir()];
|
|
8141
|
-
const requested = resolve(request.cwd, path);
|
|
8142
|
-
const [target, worktrees, protectedDirs] = await Promise.all([
|
|
8143
|
-
resolveEditTarget(requested),
|
|
8144
|
-
Promise.all(scope.worktrees.map((worktree) => resolveEditTarget(worktree))),
|
|
8145
|
-
Promise.all(ownDirs.map((dir) => resolveEditTarget(dir)))
|
|
8146
|
-
]);
|
|
8147
|
-
if (target === null || GIT_OVERRIDES.some((name) => process.env[name] !== void 0)) return null;
|
|
8148
|
-
const [checkout, current] = await Promise.all([findCheckout(dirname(target)), request.toolName === "Edit" ? readFile(target, "utf8").catch(() => null) : null]);
|
|
8149
|
-
return (await import("./classify-edit-D8GC6b91.js")).classifyEdit({
|
|
8150
|
-
toolName: request.toolName,
|
|
8151
|
-
toolInput: request.toolInput,
|
|
8152
|
-
requested,
|
|
8153
|
-
target,
|
|
8154
|
-
checkout: checkout?.worktree ?? null,
|
|
8155
|
-
current
|
|
8156
|
-
}, {
|
|
8157
|
-
worktrees: worktrees.filter((worktree) => worktree !== null),
|
|
8158
|
-
protectedDirs: [...ownDirs, ...protectedDirs.filter((dir) => dir !== null)]
|
|
8159
|
-
});
|
|
8283
|
+
stateDir: resolveStateDir(host)
|
|
8284
|
+
}, config.scopeSources ?? DEFAULT_SCOPE_SOURCES, host, stderr);
|
|
8160
8285
|
} catch {
|
|
8161
8286
|
return null;
|
|
8162
8287
|
}
|
|
@@ -8185,7 +8310,7 @@ async function tryReadDenialGuidance() {
|
|
|
8185
8310
|
}
|
|
8186
8311
|
}
|
|
8187
8312
|
//#endregion
|
|
8188
|
-
//#region src/request/
|
|
8313
|
+
//#region src/request/action-request-schema.ts
|
|
8189
8314
|
const userTaskSchema = strictObject({
|
|
8190
8315
|
text: string(),
|
|
8191
8316
|
origin: _enum([
|
|
@@ -8209,7 +8334,7 @@ const decisionContextSchema = strictObject({
|
|
|
8209
8334
|
lastDirectUserMessage: userTaskSchema.nullable(),
|
|
8210
8335
|
omittedTaskContext: array(omissionSchema)
|
|
8211
8336
|
});
|
|
8212
|
-
const
|
|
8337
|
+
const actionRequestSchema = strictObject({
|
|
8213
8338
|
sessionID: string().min(1),
|
|
8214
8339
|
toolUseID: string().min(1).optional(),
|
|
8215
8340
|
cwd: string().min(1),
|
|
@@ -8217,8 +8342,10 @@ const requestSchema = strictObject({
|
|
|
8217
8342
|
toolInput: looseObject({}),
|
|
8218
8343
|
context: decisionContextSchema
|
|
8219
8344
|
});
|
|
8345
|
+
//#endregion
|
|
8346
|
+
//#region src/request/parse-action-request.ts
|
|
8220
8347
|
function parseActionRequest(body) {
|
|
8221
|
-
const parsed =
|
|
8348
|
+
const parsed = actionRequestSchema.safeParse(body);
|
|
8222
8349
|
if (!parsed.success) return null;
|
|
8223
8350
|
const context = parsed.data.context;
|
|
8224
8351
|
return {
|
|
@@ -8242,4 +8369,4 @@ function renderVerdict(verdict) {
|
|
|
8242
8369
|
})).exhaustive();
|
|
8243
8370
|
}
|
|
8244
8371
|
//#endregion
|
|
8245
|
-
export { resolveConfigPath as C,
|
|
8372
|
+
export { resolveConfigPath as C, strictObject as D, number as E, string as O, resolveApiKey as S, PRESETS as T, findCheckout as _, loadSessionScope as a, DEFAULT_SCOPE_SOURCES as b, classifyWithModel as c, loadCheckoutRemotes as d, loadPolicy as f, resolveStateDir as g, splitCommandWords as h, resolveSessionScopePath as i, parseModelVerdict as l, splitShellCommand as m, parseActionRequest as n, EMPTY_SESSION_SCOPE as o, toRepositorySlug as p, classifyAction as r, classifyLocally as s, renderVerdict as t, loadRepositoryContext as u, DEFAULT_CONFIG as v, readHostEnvironment as w, loadConfig as x, DEFAULT_DENIAL_BUDGET as y };
|