auto-mode 0.5.0 → 0.6.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 CHANGED
@@ -59,8 +59,9 @@ assessment.
59
59
  ## Decisions and failures
60
60
 
61
61
  Jev evaluates each named rule with a typed choice. auto-mode returns a denial for a confident block,
62
- approval when every rule confidently allows the action, and manual approval for uncertain decisions.
63
- The confidence threshold is configurable and needs evaluation against your actions.
62
+ approval when every rule confidently allows the action, and a denial for uncertain decisions. Every
63
+ denial names its rule, the harm, and what clears it, and the agent continues on another path. The
64
+ confidence threshold is configurable and needs evaluation against your actions.
64
65
 
65
66
  A missing key, failed API call, malformed response, or oversized request follows `onFailure`. The
66
67
  default `defer` writes no verdict and keeps the prompt; `deny` fails closed. Failures write a
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as parseActionRequest, r as classifyAction, s as loadPolicy, t as renderVerdict, u as loadConfig } from "./render-verdict-FdG6gahX.js";
2
+ import { n as parseActionRequest, r as classifyAction, s as loadPolicy, t as renderVerdict, u as loadConfig } from "./render-verdict-9pnm4JBS.js";
3
3
  import { appendFile, mkdir } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join } from "node:path";
@@ -138,8 +138,7 @@ async function run(explain, localOnly, jevOnly, deadline) {
138
138
  }
139
139
  }
140
140
  function writeVerdict(verdict) {
141
- const rendered = renderVerdict(verdict);
142
- if (rendered !== null) process.stdout.write(rendered);
141
+ process.stdout.write(renderVerdict(verdict));
143
142
  }
144
143
  function printNote(explain, message) {
145
144
  if (explain) process.stderr.write(`auto-mode: ${message}\n`);
package/dist/index.d.ts CHANGED
@@ -57,8 +57,6 @@ interface ActionRequest {
57
57
  }
58
58
  type Verdict = {
59
59
  readonly kind: 'allow';
60
- } | {
61
- readonly kind: 'ask';
62
60
  } | {
63
61
  readonly kind: 'deny';
64
62
  readonly rule: string;
@@ -80,7 +78,7 @@ interface DecisionAnswer {
80
78
  }
81
79
  type DecisionFailureReason = 'request-too-large' | 'aborted' | 'network' | 'http-status' | 'invalid-response';
82
80
  interface DecisionDiagnostics {
83
- readonly status: 'allow' | 'deny' | 'ask' | 'failure' | 'timeout' | 'cancelled';
81
+ readonly status: 'allow' | 'deny' | 'failure' | 'timeout' | 'cancelled';
84
82
  readonly stage: 'credential' | 'evidence' | 'request' | 'response';
85
83
  readonly keyResolved: boolean;
86
84
  readonly keySource: 'environment' | 'command' | 'none';
@@ -134,7 +132,7 @@ export declare function loadPolicy(paths?: PolicyPaths, framework?: 'classifier.
134
132
  export declare function parseActionRequest(body: unknown): ActionRequest | null;
135
133
  //#endregion
136
134
  //#region src/request/render-verdict.d.ts
137
- export declare function renderVerdict(verdict: Verdict): string | null;
135
+ export declare function renderVerdict(verdict: Verdict): string;
138
136
  //#endregion
139
137
  //#region src/rules/classify-locally.d.ts
140
138
  type LocalVerdict = {
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as classifyWithModel, c as DEFAULT_CONFIG, d as resolveApiKey, f as resolveConfigPath, i as classifyLocally, l as PRESETS, n as parseActionRequest, o as parseModelVerdict, r as classifyAction, s as loadPolicy, t as renderVerdict, u as loadConfig } from "./render-verdict-FdG6gahX.js";
1
+ import { a as classifyWithModel, c as DEFAULT_CONFIG, d as resolveApiKey, f as resolveConfigPath, i as classifyLocally, l as PRESETS, n as parseActionRequest, o as parseModelVerdict, r as classifyAction, s as loadPolicy, t as renderVerdict, u as loadConfig } from "./render-verdict-9pnm4JBS.js";
2
2
  export { DEFAULT_CONFIG, PRESETS, classifyAction, classifyLocally, classifyWithModel, loadConfig, loadPolicy, parseActionRequest, parseModelVerdict, renderVerdict, resolveApiKey, resolveConfigPath };
@@ -5739,25 +5739,27 @@ function resolveApiKey(provider, options = {}) {
5739
5739
  return readApiKeyFromCommand(provider.apiKeyCommand, options);
5740
5740
  }
5741
5741
  //#endregion
5742
+ //#region src/policy/resolve-shipped-policy-dir.ts
5743
+ function resolveShippedPolicyDir() {
5744
+ let dir = import.meta.dirname;
5745
+ for (;;) {
5746
+ if (existsSync(join(dir, "policy", "classifier.md"))) return join(dir, "policy");
5747
+ const parent = dirname(dir);
5748
+ if (parent === dir || dir === parse(dir).root) throw new Error("auto-mode cannot find its shipped policy/ directory");
5749
+ dir = parent;
5750
+ }
5751
+ }
5752
+ //#endregion
5742
5753
  //#region src/policy/load-policy.ts
5743
5754
  const RULES_MARKER = "<rules>";
5744
5755
  async function loadPolicy(paths = {}, framework = "classifier.md") {
5745
- const shipped = findShippedPolicyDir();
5756
+ const shipped = resolveShippedPolicyDir();
5746
5757
  const classifierPath = paths.classifierPath ?? join(shipped, framework);
5747
5758
  const rulesPath = paths.rulesPath ?? join(shipped, "rules.md");
5748
5759
  const [classifier, rules] = await Promise.all([readFile(classifierPath, "utf8"), readFile(rulesPath, "utf8")]);
5749
5760
  if (!classifier.includes(RULES_MARKER)) throw new Error(`${classifierPath} has no ${RULES_MARKER} line, so the rules cannot be placed`);
5750
5761
  return classifier.replace(RULES_MARKER, rules.trim());
5751
5762
  }
5752
- function findShippedPolicyDir() {
5753
- let dir = import.meta.dirname;
5754
- for (;;) {
5755
- if (existsSync(join(dir, "policy", "classifier.md"))) return join(dir, "policy");
5756
- const parent = dirname(dir);
5757
- if (parent === dir || dir === parse(dir).root) throw new Error("auto-mode cannot find its shipped policy/ directory");
5758
- dir = parent;
5759
- }
5760
- }
5761
5763
  //#endregion
5762
5764
  //#region src/model/anthropic-client.ts
5763
5765
  async function sendMessage(provider, apiKey, request) {
@@ -5946,12 +5948,38 @@ function buildEvidenceGuidance(name) {
5946
5948
  return "";
5947
5949
  }
5948
5950
  //#endregion
5951
+ //#region src/model/classify-decision-answers.ts
5952
+ function classifyDecisionAnswers(request, result, minConfidence) {
5953
+ const entries = Object.entries(request.rules).map(([id, rule]) => {
5954
+ const answer = result.answers[id];
5955
+ if (answer === void 0) throw new Error("Decision answer missing");
5956
+ return {
5957
+ rule,
5958
+ answer
5959
+ };
5960
+ });
5961
+ for (const tier of ["hard", "soft"]) for (const entry of entries) if (entry.rule.tier === tier && entry.answer.choice === "block" && entry.answer.confidence >= minConfidence && entry.answer.probabilities.block >= minConfidence) return {
5962
+ kind: "block",
5963
+ rule: entry.rule
5964
+ };
5965
+ let uncertain = null;
5966
+ for (const entry of entries) {
5967
+ const answer = entry.answer;
5968
+ if (!(answer.choice === "allow" && answer.confidence >= minConfidence && answer.probabilities.allow >= minConfidence) && (uncertain === null || answer.probabilities.block > uncertain.answer.probabilities.block)) uncertain = entry;
5969
+ }
5970
+ return uncertain === null ? { kind: "allow" } : {
5971
+ kind: "uncertain",
5972
+ rule: uncertain.rule
5973
+ };
5974
+ }
5975
+ //#endregion
5949
5976
  //#region src/model/collect-decision-contributors.ts
5950
- function collectDecisionContributors(request, result, verdict, minConfidence) {
5977
+ function collectDecisionContributors(request, result, minConfidence) {
5978
+ const decision = classifyDecisionAnswers(request, result, minConfidence);
5951
5979
  return Object.entries(request.rules).flatMap(([id, rule]) => {
5952
5980
  const answer = result.answers[id];
5953
5981
  if (answer === void 0) throw new Error("Decision answer missing");
5954
- return (verdict.kind === "deny" ? rule.name === verdict.rule : verdict.kind === "ask" && (answer.choice !== "allow" || answer.confidence < minConfidence || answer.probabilities.allow < minConfidence)) ? [{
5982
+ return (decision.kind === "block" ? rule === decision.rule : decision.kind === "uncertain" && (answer.choice !== "allow" || answer.confidence < minConfidence || answer.probabilities.allow < minConfidence)) ? [{
5955
5983
  rule: rule.source === "shipped" ? rule.name : id,
5956
5984
  source: rule.source,
5957
5985
  tier: rule.tier,
@@ -6041,206 +6069,583 @@ function parseReference(content, prefix) {
6041
6069
  return branch !== "" && branch.length <= 256 && !/\s/u.test(branch) ? branch : null;
6042
6070
  }
6043
6071
  //#endregion
6044
- //#region src/model/pick-decision-verdict.ts
6045
- function pickDecisionVerdict(request, result, minConfidence) {
6046
- const entries = Object.entries(request.rules);
6047
- for (const tier of ["hard", "soft"]) for (const [id, rule] of entries) {
6048
- const answer = result.answers[id];
6049
- if (answer === void 0) throw new Error("Decision answer missing");
6050
- if (rule.tier === tier && answer.choice === "block" && answer.confidence >= minConfidence && answer.probabilities.block >= minConfidence) return {
6051
- kind: "deny",
6052
- rule: rule.name,
6053
- reason: `The action matches ${rule.name}.`
6054
- };
6055
- }
6056
- for (const [id] of entries) {
6057
- const answer = result.answers[id];
6058
- if (answer === void 0) throw new Error("Decision answer missing");
6059
- if (answer.choice !== "allow" || answer.confidence < minConfidence || answer.probabilities.allow < minConfidence) return { kind: "ask" };
6060
- }
6061
- return { kind: "allow" };
6062
- }
6063
- //#endregion
6064
- //#region src/model/send-decision.ts
6065
- async function sendDecision(provider, apiKey, request, signal) {
6066
- const input = {
6067
- model: provider.model,
6068
- state: { ...request.state },
6069
- questions: request.questions
6070
- };
6071
- let body = JSON.stringify(input);
6072
- for (const field of ["delegatedTask", "originalUserTask"]) {
6073
- const context = input.state.taskContext;
6074
- if (Buffer.byteLength(body) <= 1e5 || context === void 0 || context[field] === null) continue;
6075
- input.state.taskContext = {
6076
- ...context,
6077
- [field]: null,
6078
- omittedTaskContext: [...context.omittedTaskContext.filter((item) => item.field !== field), {
6079
- field,
6080
- reason: "budget"
6081
- }]
6082
- };
6083
- body = JSON.stringify(input);
6072
+ //#region node_modules/.bun/ts-pattern@5.9.0/node_modules/ts-pattern/dist/index.js
6073
+ const t = Symbol.for("@ts-pattern/matcher");
6074
+ const e = Symbol.for("@ts-pattern/isVariadic");
6075
+ const n = "@ts-pattern/anonymous-select-key";
6076
+ const r = (t) => Boolean(t && "object" == typeof t);
6077
+ const i = (e) => e && !!e[t];
6078
+ const o = (n, s, c) => {
6079
+ if (i(n)) {
6080
+ const { matched: r, selections: i } = n[t]().match(s);
6081
+ return r && i && Object.keys(i).forEach((t) => c(t, i[t])), r;
6084
6082
  }
6085
- const requestBytes = Buffer.byteLength(body);
6086
- if (requestBytes > 1e5) throw new DecisionRequestError("request-too-large", requestBytes, "Decision input exceeds 100000 bytes; refusing to truncate the action or user message");
6087
- const controller = new AbortController();
6088
- const stopRequest = () => {
6089
- controller.abort();
6090
- };
6091
- signal?.addEventListener("abort", stopRequest, { once: true });
6092
- if (signal?.aborted === true) controller.abort();
6093
- const timer = setTimeout(() => {
6094
- controller.abort();
6095
- }, provider.timeoutMs);
6096
- try {
6097
- let response;
6098
- try {
6099
- response = await fetch(`${provider.baseURL.replace(/\/$/, "")}/v1/systemone`, {
6100
- method: "POST",
6101
- signal: controller.signal,
6102
- headers: {
6103
- "content-type": "application/json",
6104
- authorization: `Bearer ${apiKey}`
6105
- },
6106
- body
6107
- });
6108
- } catch (error) {
6109
- if (error instanceof Error && error.name === "AbortError") throw new DecisionRequestError("aborted", requestBytes, "Decision request aborted", { cause: error });
6110
- throw new DecisionRequestError("network", requestBytes, "Decision API request failed");
6083
+ if (r(n)) {
6084
+ if (!r(s)) return !1;
6085
+ if (Array.isArray(n)) {
6086
+ if (!Array.isArray(s)) return !1;
6087
+ let t = [], r = [], u = [];
6088
+ for (const o of n.keys()) {
6089
+ const s = n[o];
6090
+ i(s) && s[e] ? u.push(s) : u.length ? r.push(s) : t.push(s);
6091
+ }
6092
+ if (u.length) {
6093
+ if (u.length > 1) throw new Error("Pattern error: Using `...P.array(...)` several times in a single pattern is not allowed.");
6094
+ if (s.length < t.length + r.length) return !1;
6095
+ const e = s.slice(0, t.length), n = 0 === r.length ? [] : s.slice(-r.length), i = s.slice(t.length, 0 === r.length ? Infinity : -r.length);
6096
+ return t.every((t, n) => o(t, e[n], c)) && r.every((t, e) => o(t, n[e], c)) && (0 === u.length || o(u[0], i, c));
6097
+ }
6098
+ return n.length === s.length && n.every((t, e) => o(t, s[e], c));
6111
6099
  }
6112
- if (!response.ok) throw new DecisionRequestError("http-status", requestBytes, `Decision API returned HTTP ${response.status}`);
6113
- const probability = number().min(0).max(1);
6114
- const answer = object({
6115
- type: literal("choice"),
6116
- choice: _enum([
6117
- "allow",
6118
- "block",
6119
- "ask"
6120
- ]),
6121
- confidence: probability,
6122
- probabilities: strictObject({
6123
- allow: probability,
6124
- block: probability,
6125
- ask: probability
6126
- })
6127
- });
6128
- const schema = object({
6129
- model: string().min(1),
6130
- answers: record(string(), answer),
6131
- usage: object({ input_tokens: number().int().nonnegative() })
6100
+ return Reflect.ownKeys(n).every((e) => {
6101
+ const r = n[e];
6102
+ return (e in s || i(u = r) && "optional" === u[t]().matcherType) && o(r, s[e], c);
6103
+ var u;
6132
6104
  });
6133
- let responseBody;
6134
- try {
6135
- responseBody = await response.json();
6136
- } catch (error) {
6137
- if (error instanceof Error && error.name === "AbortError") throw new DecisionRequestError("aborted", requestBytes, "Decision request aborted", { cause: error });
6138
- throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned invalid JSON");
6139
- }
6140
- const parsedResponse = schema.safeParse(responseBody);
6141
- if (!parsedResponse.success) throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned a malformed response");
6142
- const parsed = parsedResponse.data;
6143
- if (Object.keys(parsed.answers).length !== Object.keys(request.questions).length) throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned an incomplete answer set");
6144
- for (const id of Object.keys(request.questions)) {
6145
- const value = parsed.answers[id];
6146
- if (value === void 0) throw new DecisionRequestError("invalid-response", requestBytes, "Decision API omitted a requested answer");
6147
- const distribution = Object.values(value.probabilities);
6148
- if (Math.abs(distribution.reduce((sum, p) => sum + p, 0) - 1) > .010000001 || value.probabilities[value.choice] < Math.max(...distribution)) throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned invalid probabilities");
6149
- }
6150
- return {
6151
- model: parsed.model,
6152
- answers: parsed.answers,
6153
- inputTokens: parsed.usage.input_tokens,
6154
- requestBytes
6155
- };
6156
- } finally {
6157
- clearTimeout(timer);
6158
- signal?.removeEventListener("abort", stopRequest);
6159
6105
  }
6106
+ return Object.is(s, n);
6107
+ };
6108
+ const s = (e) => {
6109
+ var n, o, u;
6110
+ return r(e) ? i(e) ? null != (n = null == (o = (u = e[t]()).getSelectionKeys) ? void 0 : o.call(u)) ? n : [] : Array.isArray(e) ? c(e, s) : c(Object.values(e), s) : [];
6111
+ };
6112
+ const c = (t, e) => t.reduce((t, n) => t.concat(e(n)), []);
6113
+ function a(t) {
6114
+ return Object.assign(t, {
6115
+ optional: () => h(t),
6116
+ and: (e) => d(t, e),
6117
+ or: (e) => y(t, e),
6118
+ select: (e) => void 0 === e ? v(t) : v(e, t)
6119
+ });
6160
6120
  }
6161
- //#endregion
6162
- //#region src/model/classify-with-jev.ts
6163
- async function classifyWithJev(payload, config, options = {}) {
6164
- const start = performance.now();
6165
- let key = null;
6166
- let stage = "credential";
6167
- const minConfidence = config.minConfidence ?? .8;
6168
- let keySource = "none";
6169
- const envKey = config.provider.apiKeyEnv === void 0 ? void 0 : process.env[config.provider.apiKeyEnv];
6170
- if (envKey !== void 0 && envKey !== "") keySource = "environment";
6171
- else if (config.provider.apiKeyCommand !== void 0 && config.provider.apiKeyCommand !== "") keySource = "command";
6172
- try {
6173
- key = await resolveApiKey(config.provider, options);
6174
- if (key === null) throw new Error("no API key: set the configured environment variable or key command");
6175
- stage = "evidence";
6176
- const directUserText = payload.decisionContext?.agentID === null ? payload.decisionContext.lastDirectUserMessage?.text ?? null : null;
6177
- const command = payload.toolInput["command"] ?? payload.toolInput["cmd"];
6178
- const hasGitCommand = typeof command === "string" && /\bgit\s/u.test(command);
6179
- const isFileEdit = payload.toolName === "Write" || payload.toolName === "Edit";
6180
- const needsRepositoryContext = hasGitCommand || isFileEdit;
6181
- const [policy, rules, repositoryContext] = await Promise.all([
6182
- loadPolicy({
6183
- classifierPath: config.classifierPath,
6184
- rulesPath: config.rulesPath
6185
- }, "decision.md"),
6186
- loadClaudeRules(config.claudeSettingsPath),
6187
- needsRepositoryContext ? loadRepositoryContext(payload.cwd) : Promise.resolve(null)
6188
- ]);
6189
- const request = buildDecisionRequest(payload, policy, rules, directUserText, config.rulesPath === void 0 ? "shipped" : "replacement", repositoryContext);
6190
- const remainingMs = options.deadlineAt === void 0 ? config.provider.timeoutMs : options.deadlineAt - Date.now();
6191
- if (remainingMs <= 0 || options.signal?.aborted === true) throw new DOMException("Evaluation deadline expired", "AbortError");
6192
- const provider = {
6193
- ...config.provider,
6194
- timeoutMs: Math.min(config.provider.timeoutMs, remainingMs)
6195
- };
6196
- stage = "request";
6197
- const result = await sendDecision(provider, key, request, options.signal);
6198
- stage = "response";
6199
- const verdict = pickDecisionVerdict(request, result, minConfidence);
6200
- return {
6201
- verdict,
6202
- diagnostics: {
6203
- status: verdict.kind,
6204
- stage,
6205
- keyResolved: true,
6206
- keySource,
6207
- failureReason: null,
6208
- requestBytes: result.requestBytes,
6209
- elapsedMs: Math.round(performance.now() - start),
6210
- minConfidence,
6211
- contributors: collectDecisionContributors(request, result, verdict, minConfidence)
6212
- },
6213
- note: formatClassifierNote(`${config.provider.model}: ${verdict.kind} (${Math.round(performance.now() - start)}ms, ${result.inputTokens} input tokens)`, key)
6214
- };
6215
- } catch (error) {
6216
- let reason = "classifier failed";
6217
- let status = "failure";
6218
- if (options.signal?.aborted === true) {
6219
- reason = "evaluation cancelled";
6220
- status = "cancelled";
6221
- } else if (options.deadlineAt !== void 0 && Date.now() >= options.deadlineAt) {
6222
- reason = "evaluation deadline expired";
6223
- status = "timeout";
6224
- } else if (error instanceof Error) {
6225
- const isTimeout = error.name === "AbortError" || error instanceof DecisionRequestError && error.reason === "aborted";
6226
- if (isTimeout) status = "timeout";
6227
- reason = isTimeout ? `timed out after ${config.provider.timeoutMs}ms` : error.message;
6228
- }
6229
- const note = formatClassifierNote(`${config.provider.model} unavailable: ${reason}`, key);
6230
- return {
6231
- verdict: config.onFailure === "deny" ? {
6232
- kind: "deny",
6233
- rule: "Classifier Unavailable",
6234
- reason: note
6235
- } : null,
6236
- note,
6237
- unavailable: true,
6238
- diagnostics: {
6239
- status,
6240
- stage,
6241
- keyResolved: key !== null,
6242
- keySource,
6243
- failureReason: error instanceof DecisionRequestError ? error.reason : null,
6121
+ function h(e) {
6122
+ return a({ [t]: () => ({
6123
+ match: (t) => {
6124
+ let n = {};
6125
+ const r = (t, e) => {
6126
+ n[t] = e;
6127
+ };
6128
+ return void 0 === t ? (s(e).forEach((t) => r(t, void 0)), {
6129
+ matched: !0,
6130
+ selections: n
6131
+ }) : {
6132
+ matched: o(e, t, r),
6133
+ selections: n
6134
+ };
6135
+ },
6136
+ getSelectionKeys: () => s(e),
6137
+ matcherType: "optional"
6138
+ }) });
6139
+ }
6140
+ function d(...e) {
6141
+ return a({ [t]: () => ({
6142
+ match: (t) => {
6143
+ let n = {};
6144
+ const r = (t, e) => {
6145
+ n[t] = e;
6146
+ };
6147
+ return {
6148
+ matched: e.every((e) => o(e, t, r)),
6149
+ selections: n
6150
+ };
6151
+ },
6152
+ getSelectionKeys: () => c(e, s),
6153
+ matcherType: "and"
6154
+ }) });
6155
+ }
6156
+ function y(...e) {
6157
+ return a({ [t]: () => ({
6158
+ match: (t) => {
6159
+ let n = {};
6160
+ const r = (t, e) => {
6161
+ n[t] = e;
6162
+ };
6163
+ return c(e, s).forEach((t) => r(t, void 0)), {
6164
+ matched: e.some((e) => o(e, t, r)),
6165
+ selections: n
6166
+ };
6167
+ },
6168
+ getSelectionKeys: () => c(e, s),
6169
+ matcherType: "or"
6170
+ }) });
6171
+ }
6172
+ function p(e) {
6173
+ return { [t]: () => ({ match: (t) => ({ matched: Boolean(e(t)) }) }) };
6174
+ }
6175
+ function v(...e) {
6176
+ const r = "string" == typeof e[0] ? e[0] : void 0, i = 2 === e.length ? e[1] : "string" == typeof e[0] ? void 0 : e[0];
6177
+ return a({ [t]: () => ({
6178
+ match: (t) => {
6179
+ let e = { [null != r ? r : n]: t };
6180
+ return {
6181
+ matched: void 0 === i || o(i, t, (t, n) => {
6182
+ e[t] = n;
6183
+ }),
6184
+ selections: e
6185
+ };
6186
+ },
6187
+ getSelectionKeys: () => [null != r ? r : n].concat(void 0 === i ? [] : s(i))
6188
+ }) });
6189
+ }
6190
+ function b(t) {
6191
+ return !0;
6192
+ }
6193
+ function w(t) {
6194
+ return "number" == typeof t;
6195
+ }
6196
+ function S(t) {
6197
+ return "string" == typeof t;
6198
+ }
6199
+ function j(t) {
6200
+ return "bigint" == typeof t;
6201
+ }
6202
+ a(p(b));
6203
+ a(p(b));
6204
+ const x = (t) => Object.assign(a(t), {
6205
+ startsWith: (e) => {
6206
+ return x(d(t, (n = e, p((t) => S(t) && t.startsWith(n)))));
6207
+ var n;
6208
+ },
6209
+ endsWith: (e) => {
6210
+ return x(d(t, (n = e, p((t) => S(t) && t.endsWith(n)))));
6211
+ var n;
6212
+ },
6213
+ minLength: (e) => x(d(t, ((t) => p((e) => S(e) && e.length >= t))(e))),
6214
+ length: (e) => x(d(t, ((t) => p((e) => S(e) && e.length === t))(e))),
6215
+ maxLength: (e) => x(d(t, ((t) => p((e) => S(e) && e.length <= t))(e))),
6216
+ includes: (e) => {
6217
+ return x(d(t, (n = e, p((t) => S(t) && t.includes(n)))));
6218
+ var n;
6219
+ },
6220
+ regex: (e) => {
6221
+ return x(d(t, (n = e, p((t) => S(t) && Boolean(t.match(n))))));
6222
+ var n;
6223
+ }
6224
+ });
6225
+ x(p(S));
6226
+ const N = (t) => Object.assign(a(t), {
6227
+ between: (e, n) => N(d(t, ((t, e) => p((n) => w(n) && t <= n && e >= n))(e, n))),
6228
+ lt: (e) => N(d(t, ((t) => p((e) => w(e) && e < t))(e))),
6229
+ gt: (e) => N(d(t, ((t) => p((e) => w(e) && e > t))(e))),
6230
+ lte: (e) => N(d(t, ((t) => p((e) => w(e) && e <= t))(e))),
6231
+ gte: (e) => N(d(t, ((t) => p((e) => w(e) && e >= t))(e))),
6232
+ int: () => N(d(t, p((t) => w(t) && Number.isInteger(t)))),
6233
+ finite: () => N(d(t, p((t) => w(t) && Number.isFinite(t)))),
6234
+ positive: () => N(d(t, p((t) => w(t) && t > 0))),
6235
+ negative: () => N(d(t, p((t) => w(t) && t < 0)))
6236
+ });
6237
+ N(p(w));
6238
+ const k = (t) => Object.assign(a(t), {
6239
+ between: (e, n) => k(d(t, ((t, e) => p((n) => j(n) && t <= n && e >= n))(e, n))),
6240
+ lt: (e) => k(d(t, ((t) => p((e) => j(e) && e < t))(e))),
6241
+ gt: (e) => k(d(t, ((t) => p((e) => j(e) && e > t))(e))),
6242
+ lte: (e) => k(d(t, ((t) => p((e) => j(e) && e <= t))(e))),
6243
+ gte: (e) => k(d(t, ((t) => p((e) => j(e) && e >= t))(e))),
6244
+ positive: () => k(d(t, p((t) => j(t) && t > 0))),
6245
+ negative: () => k(d(t, p((t) => j(t) && t < 0)))
6246
+ });
6247
+ k(p(j)), a(p(function(t) {
6248
+ return "boolean" == typeof t;
6249
+ })), a(p(function(t) {
6250
+ return "symbol" == typeof t;
6251
+ })), a(p(function(t) {
6252
+ return null == t;
6253
+ })), a(p(function(t) {
6254
+ return null != t;
6255
+ }));
6256
+ var I = class extends Error {
6257
+ constructor(t) {
6258
+ let e;
6259
+ try {
6260
+ e = JSON.stringify(t);
6261
+ } catch (n) {
6262
+ e = t;
6263
+ }
6264
+ super(`Pattern matching error: no pattern matches value ${e}`), this.input = void 0, this.input = t;
6265
+ }
6266
+ };
6267
+ const L = {
6268
+ matched: !1,
6269
+ value: void 0
6270
+ };
6271
+ function M(t) {
6272
+ return new R(t, L);
6273
+ }
6274
+ var R = class R {
6275
+ constructor(t, e) {
6276
+ this.input = void 0, this.state = void 0, this.input = t, this.state = e;
6277
+ }
6278
+ with(...t) {
6279
+ if (this.state.matched) return this;
6280
+ const e = t[t.length - 1], r = [t[0]];
6281
+ let i;
6282
+ 3 === t.length && "function" == typeof t[1] ? i = t[1] : t.length > 2 && r.push(...t.slice(1, t.length - 1));
6283
+ let s = !1, c = {};
6284
+ const u = (t, e) => {
6285
+ s = !0, c[t] = e;
6286
+ }, a = !r.some((t) => o(t, this.input, u)) || i && !Boolean(i(this.input)) ? L : {
6287
+ matched: !0,
6288
+ value: e(s ? n in c ? c[n] : c : this.input, this.input)
6289
+ };
6290
+ return new R(this.input, a);
6291
+ }
6292
+ when(t, e) {
6293
+ if (this.state.matched) return this;
6294
+ const n = Boolean(t(this.input));
6295
+ return new R(this.input, n ? {
6296
+ matched: !0,
6297
+ value: e(this.input, this.input)
6298
+ } : L);
6299
+ }
6300
+ otherwise(t) {
6301
+ return this.state.matched ? this.state.value : t(this.input);
6302
+ }
6303
+ exhaustive(t = F) {
6304
+ return this.state.matched ? this.state.value : t(this.input);
6305
+ }
6306
+ run() {
6307
+ return this.exhaustive();
6308
+ }
6309
+ returnType() {
6310
+ return this;
6311
+ }
6312
+ narrow() {
6313
+ return this;
6314
+ }
6315
+ };
6316
+ function F(t) {
6317
+ throw new I(t);
6318
+ }
6319
+ //#endregion
6320
+ //#region src/policy/deny-reasons.ts
6321
+ const DENY_REASONS = {
6322
+ "Data Exfiltration": {
6323
+ tier: "hard",
6324
+ harm: "sending sensitive data, such as credentials or private code, off the machine",
6325
+ clears: "No instruction or configuration clears this rule."
6326
+ },
6327
+ "Secret Persistence": {
6328
+ tier: "hard",
6329
+ harm: "writing a credential in plain text into a tracked file, an image, CI configuration, or a shipped log",
6330
+ clears: "No instruction or configuration clears this rule."
6331
+ },
6332
+ "Policy Tampering": {
6333
+ tier: "hard",
6334
+ harm: "changing auto-mode's own configuration, policy, or hook, or the settings that decide whether it runs",
6335
+ clears: "Only a direct user message that explains why this match is wrong clears it; bare consent does not."
6336
+ },
6337
+ "Audit Tampering": {
6338
+ tier: "hard",
6339
+ harm: "deleting or rewriting logs, shell history, audit records, or git history to hide what happened",
6340
+ clears: "Only a direct user message that explains why this match is wrong clears it; bare consent does not."
6341
+ },
6342
+ "Destructive Payload": {
6343
+ tier: "hard",
6344
+ harm: "writing or running code whose purpose is damage or spread",
6345
+ clears: "No instruction or configuration clears this rule."
6346
+ },
6347
+ "Irreversible Deletion": {
6348
+ tier: "soft",
6349
+ harm: "deleting data that no backup, remote, or build step can restore",
6350
+ clears: "A direct user instruction that asks for this deletion and names the path clears it."
6351
+ },
6352
+ "Uncommitted Work Destruction": {
6353
+ tier: "soft",
6354
+ harm: "discarding working-tree changes that exist nowhere else",
6355
+ clears: "A direct user instruction that says those changes go away clears it."
6356
+ },
6357
+ "History Rewrite": {
6358
+ tier: "soft",
6359
+ harm: "rewriting history that others may already hold",
6360
+ clears: "A direct user instruction that names the force push, the rewrite, or the remote branch clears it."
6361
+ },
6362
+ "Default Branch Write": {
6363
+ tier: "soft",
6364
+ harm: "committing, pushing, or merging directly to the default branch",
6365
+ clears: "A direct user instruction that names the branch clears it."
6366
+ },
6367
+ "Publish and Release": {
6368
+ tier: "soft",
6369
+ harm: "putting an artifact where other people install it",
6370
+ clears: "A direct user instruction that says publish, release, or deploy and names the target clears it."
6371
+ },
6372
+ "Outbound Communication": {
6373
+ tier: "soft",
6374
+ harm: "sending content that reaches another person",
6375
+ clears: "A direct user instruction that says the message is sent, and to whom or where, clears it."
6376
+ },
6377
+ "Remote Code Execution": {
6378
+ tier: "soft",
6379
+ harm: "running code fetched from a source the user did not name",
6380
+ clears: "A direct user instruction that names the source clears it."
6381
+ },
6382
+ "Obfuscated Command": {
6383
+ tier: "soft",
6384
+ harm: "running a command whose payload cannot be read",
6385
+ clears: "A direct user message that supplies the decoded command or states what it runs clears it."
6386
+ },
6387
+ "Network Exposure": {
6388
+ tier: "soft",
6389
+ harm: "making this machine or its files reachable from outside it",
6390
+ clears: "A direct user instruction that names the service and says it becomes reachable clears it."
6391
+ },
6392
+ "Unnamed Destination": {
6393
+ tier: "soft",
6394
+ harm: "sending content to a network destination the user never named",
6395
+ clears: "A direct user instruction that names the destination clears it."
6396
+ },
6397
+ "Shared Infrastructure": {
6398
+ tier: "soft",
6399
+ harm: "changing a cluster, a cloud account, a remote host, or a repository's settings",
6400
+ clears: "A direct user instruction that names the environment or the resource clears it."
6401
+ },
6402
+ "Destructive Database Operation": {
6403
+ tier: "soft",
6404
+ harm: "deleting or overwriting stored data wholesale in a database that is not local",
6405
+ clears: "A direct user instruction that names the table or the database, and the operation, clears it."
6406
+ },
6407
+ "Persistent Configuration": {
6408
+ tier: "soft",
6409
+ harm: "a change that outlives the session or widens access on this machine",
6410
+ clears: "A direct user instruction that names the file or the setting clears it."
6411
+ },
6412
+ "Credential Exploration": {
6413
+ tier: "soft",
6414
+ harm: "reading or searching for credentials the task never called for",
6415
+ clears: "A direct user instruction that names the file or the search clears it."
6416
+ },
6417
+ "Interrupted Action Retry": {
6418
+ tier: "soft",
6419
+ harm: "running again an action the user interrupted",
6420
+ clears: "A direct user instruction that asks for the action to run again clears it."
6421
+ },
6422
+ "Security Control Removal": {
6423
+ tier: "soft",
6424
+ harm: "removing a control that protects someone other than the agent",
6425
+ clears: "A direct user instruction that names the control clears it."
6426
+ },
6427
+ "Mass Modification": {
6428
+ tier: "soft",
6429
+ harm: "changing many things at once with no way to restore them",
6430
+ clears: "A direct user instruction that names the scope clears it."
6431
+ }
6432
+ };
6433
+ //#endregion
6434
+ //#region src/policy/build-deny-reason.ts
6435
+ function buildDenyReason(rule, basis) {
6436
+ const template = getReasonTemplate(rule);
6437
+ return `${basis === "matched" ? `The action matches this rule: ${template.harm}.` : `The supplied evidence cannot rule out ${template.harm}.`} ${template.clears}`;
6438
+ }
6439
+ function getReasonTemplate(rule) {
6440
+ return M(rule.source).with("shipped", () => {
6441
+ const template = DENY_REASONS[rule.name];
6442
+ if (template === void 0) throw new Error(`No deny reason for the shipped rule ${rule.name}`);
6443
+ return template;
6444
+ }).with("configured", () => rule.tier === "hard" ? {
6445
+ harm: "an action that a hard deny entry in the user's auto-mode configuration covers",
6446
+ clears: "Only a change to that configuration clears it."
6447
+ } : {
6448
+ harm: "an action that a soft deny entry in the user's auto-mode configuration covers",
6449
+ clears: "A direct user instruction that asks for this specific action clears it."
6450
+ }).with("replacement", () => ({
6451
+ harm: "the harm this rule of the configured replacement policy describes",
6452
+ clears: "The replacement policy defines what clears it."
6453
+ })).exhaustive();
6454
+ }
6455
+ //#endregion
6456
+ //#region src/model/pick-decision-verdict.ts
6457
+ function pickDecisionVerdict(request, result, minConfidence) {
6458
+ return M(classifyDecisionAnswers(request, result, minConfidence)).with({ kind: "allow" }, () => ({ kind: "allow" })).with({ kind: "block" }, (decision) => ({
6459
+ kind: "deny",
6460
+ rule: decision.rule.name,
6461
+ reason: buildDenyReason(decision.rule, "matched")
6462
+ })).with({ kind: "uncertain" }, (decision) => ({
6463
+ kind: "deny",
6464
+ rule: decision.rule.name,
6465
+ reason: buildDenyReason(decision.rule, "unresolved")
6466
+ })).exhaustive();
6467
+ }
6468
+ //#endregion
6469
+ //#region src/model/send-decision.ts
6470
+ async function sendDecision(provider, apiKey, request, signal) {
6471
+ const input = {
6472
+ model: provider.model,
6473
+ state: { ...request.state },
6474
+ questions: request.questions
6475
+ };
6476
+ let body = JSON.stringify(input);
6477
+ for (const field of ["delegatedTask", "originalUserTask"]) {
6478
+ const context = input.state.taskContext;
6479
+ if (Buffer.byteLength(body) <= 1e5 || context === void 0 || context[field] === null) continue;
6480
+ input.state.taskContext = {
6481
+ ...context,
6482
+ [field]: null,
6483
+ omittedTaskContext: [...context.omittedTaskContext.filter((item) => item.field !== field), {
6484
+ field,
6485
+ reason: "budget"
6486
+ }]
6487
+ };
6488
+ body = JSON.stringify(input);
6489
+ }
6490
+ const requestBytes = Buffer.byteLength(body);
6491
+ if (requestBytes > 1e5) throw new DecisionRequestError("request-too-large", requestBytes, "Decision input exceeds 100000 bytes; refusing to truncate the action or user message");
6492
+ const controller = new AbortController();
6493
+ const stopRequest = () => {
6494
+ controller.abort();
6495
+ };
6496
+ signal?.addEventListener("abort", stopRequest, { once: true });
6497
+ if (signal?.aborted === true) controller.abort();
6498
+ const timer = setTimeout(() => {
6499
+ controller.abort();
6500
+ }, provider.timeoutMs);
6501
+ try {
6502
+ let response;
6503
+ try {
6504
+ response = await fetch(`${provider.baseURL.replace(/\/$/, "")}/v1/systemone`, {
6505
+ method: "POST",
6506
+ signal: controller.signal,
6507
+ headers: {
6508
+ "content-type": "application/json",
6509
+ authorization: `Bearer ${apiKey}`
6510
+ },
6511
+ body
6512
+ });
6513
+ } catch (error) {
6514
+ if (error instanceof Error && error.name === "AbortError") throw new DecisionRequestError("aborted", requestBytes, "Decision request aborted", { cause: error });
6515
+ throw new DecisionRequestError("network", requestBytes, "Decision API request failed");
6516
+ }
6517
+ if (!response.ok) throw new DecisionRequestError("http-status", requestBytes, `Decision API returned HTTP ${response.status}`);
6518
+ const probability = number().min(0).max(1);
6519
+ const answer = object({
6520
+ type: literal("choice"),
6521
+ choice: _enum([
6522
+ "allow",
6523
+ "block",
6524
+ "ask"
6525
+ ]),
6526
+ confidence: probability,
6527
+ probabilities: strictObject({
6528
+ allow: probability,
6529
+ block: probability,
6530
+ ask: probability
6531
+ })
6532
+ });
6533
+ const schema = object({
6534
+ model: string().min(1),
6535
+ answers: record(string(), answer),
6536
+ usage: object({ input_tokens: number().int().nonnegative() })
6537
+ });
6538
+ let responseBody;
6539
+ try {
6540
+ responseBody = await response.json();
6541
+ } catch (error) {
6542
+ if (error instanceof Error && error.name === "AbortError") throw new DecisionRequestError("aborted", requestBytes, "Decision request aborted", { cause: error });
6543
+ throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned invalid JSON");
6544
+ }
6545
+ const parsedResponse = schema.safeParse(responseBody);
6546
+ if (!parsedResponse.success) throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned a malformed response");
6547
+ const parsed = parsedResponse.data;
6548
+ if (Object.keys(parsed.answers).length !== Object.keys(request.questions).length) throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned an incomplete answer set");
6549
+ for (const id of Object.keys(request.questions)) {
6550
+ const value = parsed.answers[id];
6551
+ if (value === void 0) throw new DecisionRequestError("invalid-response", requestBytes, "Decision API omitted a requested answer");
6552
+ const distribution = Object.values(value.probabilities);
6553
+ if (Math.abs(distribution.reduce((sum, p) => sum + p, 0) - 1) > .010000001 || value.probabilities[value.choice] < Math.max(...distribution)) throw new DecisionRequestError("invalid-response", requestBytes, "Decision API returned invalid probabilities");
6554
+ }
6555
+ return {
6556
+ model: parsed.model,
6557
+ answers: parsed.answers,
6558
+ inputTokens: parsed.usage.input_tokens,
6559
+ requestBytes
6560
+ };
6561
+ } finally {
6562
+ clearTimeout(timer);
6563
+ signal?.removeEventListener("abort", stopRequest);
6564
+ }
6565
+ }
6566
+ //#endregion
6567
+ //#region src/model/classify-with-jev.ts
6568
+ async function classifyWithJev(payload, config, options = {}) {
6569
+ const start = performance.now();
6570
+ let key = null;
6571
+ let stage = "credential";
6572
+ const minConfidence = config.minConfidence ?? .8;
6573
+ let keySource = "none";
6574
+ const envKey = config.provider.apiKeyEnv === void 0 ? void 0 : process.env[config.provider.apiKeyEnv];
6575
+ if (envKey !== void 0 && envKey !== "") keySource = "environment";
6576
+ else if (config.provider.apiKeyCommand !== void 0 && config.provider.apiKeyCommand !== "") keySource = "command";
6577
+ try {
6578
+ key = await resolveApiKey(config.provider, options);
6579
+ if (key === null) throw new Error("no API key: set the configured environment variable or key command");
6580
+ stage = "evidence";
6581
+ const directUserText = payload.decisionContext?.agentID === null ? payload.decisionContext.lastDirectUserMessage?.text ?? null : null;
6582
+ const command = payload.toolInput["command"] ?? payload.toolInput["cmd"];
6583
+ const hasGitCommand = typeof command === "string" && /\bgit\s/u.test(command);
6584
+ const isFileEdit = payload.toolName === "Write" || payload.toolName === "Edit";
6585
+ const needsRepositoryContext = hasGitCommand || isFileEdit;
6586
+ const [policy, rules, repositoryContext] = await Promise.all([
6587
+ loadPolicy({
6588
+ classifierPath: config.classifierPath,
6589
+ rulesPath: config.rulesPath
6590
+ }, "decision.md"),
6591
+ loadClaudeRules(config.claudeSettingsPath),
6592
+ needsRepositoryContext ? loadRepositoryContext(payload.cwd) : Promise.resolve(null)
6593
+ ]);
6594
+ const request = buildDecisionRequest(payload, policy, rules, directUserText, config.rulesPath === void 0 ? "shipped" : "replacement", repositoryContext);
6595
+ const remainingMs = options.deadlineAt === void 0 ? config.provider.timeoutMs : options.deadlineAt - Date.now();
6596
+ if (remainingMs <= 0 || options.signal?.aborted === true) throw new DOMException("Evaluation deadline expired", "AbortError");
6597
+ const provider = {
6598
+ ...config.provider,
6599
+ timeoutMs: Math.min(config.provider.timeoutMs, remainingMs)
6600
+ };
6601
+ stage = "request";
6602
+ const result = await sendDecision(provider, key, request, options.signal);
6603
+ stage = "response";
6604
+ const verdict = pickDecisionVerdict(request, result, minConfidence);
6605
+ return {
6606
+ verdict,
6607
+ diagnostics: {
6608
+ status: verdict.kind,
6609
+ stage,
6610
+ keyResolved: true,
6611
+ keySource,
6612
+ failureReason: null,
6613
+ requestBytes: result.requestBytes,
6614
+ elapsedMs: Math.round(performance.now() - start),
6615
+ minConfidence,
6616
+ contributors: collectDecisionContributors(request, result, minConfidence)
6617
+ },
6618
+ note: formatClassifierNote(`${config.provider.model}: ${verdict.kind} (${Math.round(performance.now() - start)}ms, ${result.inputTokens} input tokens)`, key)
6619
+ };
6620
+ } catch (error) {
6621
+ let reason = "classifier failed";
6622
+ let status = "failure";
6623
+ if (options.signal?.aborted === true) {
6624
+ reason = "evaluation cancelled";
6625
+ status = "cancelled";
6626
+ } else if (options.deadlineAt !== void 0 && Date.now() >= options.deadlineAt) {
6627
+ reason = "evaluation deadline expired";
6628
+ status = "timeout";
6629
+ } else if (error instanceof Error) {
6630
+ const isTimeout = error.name === "AbortError" || error instanceof DecisionRequestError && error.reason === "aborted";
6631
+ if (isTimeout) status = "timeout";
6632
+ reason = isTimeout ? `timed out after ${config.provider.timeoutMs}ms` : error.message;
6633
+ }
6634
+ const note = formatClassifierNote(`${config.provider.model} unavailable: ${reason}`, key);
6635
+ return {
6636
+ verdict: config.onFailure === "deny" ? {
6637
+ kind: "deny",
6638
+ rule: "Classifier Unavailable",
6639
+ reason: note
6640
+ } : null,
6641
+ note,
6642
+ unavailable: true,
6643
+ diagnostics: {
6644
+ status,
6645
+ stage,
6646
+ keyResolved: key !== null,
6647
+ keySource,
6648
+ failureReason: error instanceof DecisionRequestError ? error.reason : null,
6244
6649
  requestBytes: error instanceof DecisionRequestError ? error.requestBytes : null,
6245
6650
  elapsedMs: Math.round(performance.now() - start),
6246
6651
  minConfidence,
@@ -6260,10 +6665,20 @@ function parseModelVerdict(text) {
6260
6665
  const rule = RULE.exec(text)?.[1]?.trim();
6261
6666
  if (rule === void 0 || rule === "") return { kind: "allow" };
6262
6667
  const stripped = (REASON.exec(text)?.[1]?.trim())?.replace(/^\[[^\]]*\]\s*/, "") ?? "";
6668
+ if (stripped !== "") return {
6669
+ kind: "deny",
6670
+ rule,
6671
+ reason: stripped
6672
+ };
6673
+ const template = DENY_REASONS[rule];
6263
6674
  return {
6264
6675
  kind: "deny",
6265
6676
  rule,
6266
- reason: stripped === "" ? "the policy blocks this action." : stripped
6677
+ reason: template === void 0 ? "The action matches this rule." : buildDenyReason({
6678
+ name: rule,
6679
+ tier: template.tier,
6680
+ source: "shipped"
6681
+ }, "matched")
6267
6682
  };
6268
6683
  }
6269
6684
  //#endregion
@@ -6321,6 +6736,11 @@ function toMessage(error) {
6321
6736
  return error instanceof Error ? error.message : String(error);
6322
6737
  }
6323
6738
  //#endregion
6739
+ //#region src/policy/read-denial-guidance.ts
6740
+ async function readDenialGuidance() {
6741
+ return (await readFile(join(resolveShippedPolicyDir(), "denial.md"), "utf8")).trim();
6742
+ }
6743
+ //#endregion
6324
6744
  //#region node_modules/.bun/tiny-invariant@1.3.3/node_modules/tiny-invariant/dist/esm/tiny-invariant.js
6325
6745
  var isProduction = process.env.NODE_ENV === "production";
6326
6746
  var prefix = "Invariant failed";
@@ -6557,12 +6977,9 @@ async function classifyAction(request, config, options = {}) {
6557
6977
  if (config.provider.protocol === "system-one") try {
6558
6978
  configured = await loadClaudeRules(config.claudeSettingsPath);
6559
6979
  } catch {
6980
+ const guidance = config.onFailure === "deny" ? await tryReadDenialGuidance() : null;
6560
6981
  return {
6561
- verdict: config.onFailure === "deny" ? {
6562
- kind: "deny",
6563
- rule: "Classifier Unavailable",
6564
- reason: "Claude settings unreadable"
6565
- } : null,
6982
+ verdict: config.onFailure === "deny" ? buildGuidedDeny("Classifier Unavailable", "Claude settings unreadable.", guidance) : null,
6566
6983
  note: "Claude settings unreadable; classifier unavailable",
6567
6984
  status: "failure",
6568
6985
  unavailable: true
@@ -6579,12 +6996,28 @@ async function classifyAction(request, config, options = {}) {
6579
6996
  note: `${request.toolName} needs the model tier, which this run skipped`,
6580
6997
  status: "skipped"
6581
6998
  };
6582
- const outcome = await classifyWithModel(request, config, options);
6999
+ const [outcome, guidance] = await Promise.all([classifyWithModel(request, config, options), tryReadDenialGuidance()]);
6583
7000
  return {
6584
7001
  ...outcome,
7002
+ verdict: outcome.verdict?.kind === "deny" ? buildGuidedDeny(outcome.verdict.rule, outcome.verdict.reason, guidance) : outcome.verdict,
6585
7003
  status: outcome.diagnostics?.status ?? outcome.verdict?.kind ?? "failure"
6586
7004
  };
6587
7005
  }
7006
+ function buildGuidedDeny(rule, reason, guidance) {
7007
+ const sentence = /[.!?]$/u.test(reason) ? reason : `${reason}.`;
7008
+ return {
7009
+ kind: "deny",
7010
+ rule,
7011
+ reason: guidance === null ? sentence : `${sentence} ${guidance}`
7012
+ };
7013
+ }
7014
+ async function tryReadDenialGuidance() {
7015
+ try {
7016
+ return await readDenialGuidance();
7017
+ } catch {
7018
+ return null;
7019
+ }
7020
+ }
6588
7021
  //#endregion
6589
7022
  //#region src/request/parse-action-request.ts
6590
7023
  const userTaskSchema = strictObject({
@@ -6635,257 +7068,9 @@ function parseActionRequest(body) {
6635
7068
  };
6636
7069
  }
6637
7070
  //#endregion
6638
- //#region node_modules/.bun/ts-pattern@5.9.0/node_modules/ts-pattern/dist/index.js
6639
- const t = Symbol.for("@ts-pattern/matcher");
6640
- const e = Symbol.for("@ts-pattern/isVariadic");
6641
- const n = "@ts-pattern/anonymous-select-key";
6642
- const r = (t) => Boolean(t && "object" == typeof t);
6643
- const i = (e) => e && !!e[t];
6644
- const o = (n, s, c) => {
6645
- if (i(n)) {
6646
- const { matched: r, selections: i } = n[t]().match(s);
6647
- return r && i && Object.keys(i).forEach((t) => c(t, i[t])), r;
6648
- }
6649
- if (r(n)) {
6650
- if (!r(s)) return !1;
6651
- if (Array.isArray(n)) {
6652
- if (!Array.isArray(s)) return !1;
6653
- let t = [], r = [], u = [];
6654
- for (const o of n.keys()) {
6655
- const s = n[o];
6656
- i(s) && s[e] ? u.push(s) : u.length ? r.push(s) : t.push(s);
6657
- }
6658
- if (u.length) {
6659
- if (u.length > 1) throw new Error("Pattern error: Using `...P.array(...)` several times in a single pattern is not allowed.");
6660
- if (s.length < t.length + r.length) return !1;
6661
- const e = s.slice(0, t.length), n = 0 === r.length ? [] : s.slice(-r.length), i = s.slice(t.length, 0 === r.length ? Infinity : -r.length);
6662
- return t.every((t, n) => o(t, e[n], c)) && r.every((t, e) => o(t, n[e], c)) && (0 === u.length || o(u[0], i, c));
6663
- }
6664
- return n.length === s.length && n.every((t, e) => o(t, s[e], c));
6665
- }
6666
- return Reflect.ownKeys(n).every((e) => {
6667
- const r = n[e];
6668
- return (e in s || i(u = r) && "optional" === u[t]().matcherType) && o(r, s[e], c);
6669
- var u;
6670
- });
6671
- }
6672
- return Object.is(s, n);
6673
- };
6674
- const s = (e) => {
6675
- var n, o, u;
6676
- return r(e) ? i(e) ? null != (n = null == (o = (u = e[t]()).getSelectionKeys) ? void 0 : o.call(u)) ? n : [] : Array.isArray(e) ? c(e, s) : c(Object.values(e), s) : [];
6677
- };
6678
- const c = (t, e) => t.reduce((t, n) => t.concat(e(n)), []);
6679
- function a(t) {
6680
- return Object.assign(t, {
6681
- optional: () => h(t),
6682
- and: (e) => d(t, e),
6683
- or: (e) => y(t, e),
6684
- select: (e) => void 0 === e ? v(t) : v(e, t)
6685
- });
6686
- }
6687
- function h(e) {
6688
- return a({ [t]: () => ({
6689
- match: (t) => {
6690
- let n = {};
6691
- const r = (t, e) => {
6692
- n[t] = e;
6693
- };
6694
- return void 0 === t ? (s(e).forEach((t) => r(t, void 0)), {
6695
- matched: !0,
6696
- selections: n
6697
- }) : {
6698
- matched: o(e, t, r),
6699
- selections: n
6700
- };
6701
- },
6702
- getSelectionKeys: () => s(e),
6703
- matcherType: "optional"
6704
- }) });
6705
- }
6706
- function d(...e) {
6707
- return a({ [t]: () => ({
6708
- match: (t) => {
6709
- let n = {};
6710
- const r = (t, e) => {
6711
- n[t] = e;
6712
- };
6713
- return {
6714
- matched: e.every((e) => o(e, t, r)),
6715
- selections: n
6716
- };
6717
- },
6718
- getSelectionKeys: () => c(e, s),
6719
- matcherType: "and"
6720
- }) });
6721
- }
6722
- function y(...e) {
6723
- return a({ [t]: () => ({
6724
- match: (t) => {
6725
- let n = {};
6726
- const r = (t, e) => {
6727
- n[t] = e;
6728
- };
6729
- return c(e, s).forEach((t) => r(t, void 0)), {
6730
- matched: e.some((e) => o(e, t, r)),
6731
- selections: n
6732
- };
6733
- },
6734
- getSelectionKeys: () => c(e, s),
6735
- matcherType: "or"
6736
- }) });
6737
- }
6738
- function p(e) {
6739
- return { [t]: () => ({ match: (t) => ({ matched: Boolean(e(t)) }) }) };
6740
- }
6741
- function v(...e) {
6742
- const r = "string" == typeof e[0] ? e[0] : void 0, i = 2 === e.length ? e[1] : "string" == typeof e[0] ? void 0 : e[0];
6743
- return a({ [t]: () => ({
6744
- match: (t) => {
6745
- let e = { [null != r ? r : n]: t };
6746
- return {
6747
- matched: void 0 === i || o(i, t, (t, n) => {
6748
- e[t] = n;
6749
- }),
6750
- selections: e
6751
- };
6752
- },
6753
- getSelectionKeys: () => [null != r ? r : n].concat(void 0 === i ? [] : s(i))
6754
- }) });
6755
- }
6756
- function b(t) {
6757
- return !0;
6758
- }
6759
- function w(t) {
6760
- return "number" == typeof t;
6761
- }
6762
- function S(t) {
6763
- return "string" == typeof t;
6764
- }
6765
- function j(t) {
6766
- return "bigint" == typeof t;
6767
- }
6768
- a(p(b));
6769
- a(p(b));
6770
- const x = (t) => Object.assign(a(t), {
6771
- startsWith: (e) => {
6772
- return x(d(t, (n = e, p((t) => S(t) && t.startsWith(n)))));
6773
- var n;
6774
- },
6775
- endsWith: (e) => {
6776
- return x(d(t, (n = e, p((t) => S(t) && t.endsWith(n)))));
6777
- var n;
6778
- },
6779
- minLength: (e) => x(d(t, ((t) => p((e) => S(e) && e.length >= t))(e))),
6780
- length: (e) => x(d(t, ((t) => p((e) => S(e) && e.length === t))(e))),
6781
- maxLength: (e) => x(d(t, ((t) => p((e) => S(e) && e.length <= t))(e))),
6782
- includes: (e) => {
6783
- return x(d(t, (n = e, p((t) => S(t) && t.includes(n)))));
6784
- var n;
6785
- },
6786
- regex: (e) => {
6787
- return x(d(t, (n = e, p((t) => S(t) && Boolean(t.match(n))))));
6788
- var n;
6789
- }
6790
- });
6791
- x(p(S));
6792
- const N = (t) => Object.assign(a(t), {
6793
- between: (e, n) => N(d(t, ((t, e) => p((n) => w(n) && t <= n && e >= n))(e, n))),
6794
- lt: (e) => N(d(t, ((t) => p((e) => w(e) && e < t))(e))),
6795
- gt: (e) => N(d(t, ((t) => p((e) => w(e) && e > t))(e))),
6796
- lte: (e) => N(d(t, ((t) => p((e) => w(e) && e <= t))(e))),
6797
- gte: (e) => N(d(t, ((t) => p((e) => w(e) && e >= t))(e))),
6798
- int: () => N(d(t, p((t) => w(t) && Number.isInteger(t)))),
6799
- finite: () => N(d(t, p((t) => w(t) && Number.isFinite(t)))),
6800
- positive: () => N(d(t, p((t) => w(t) && t > 0))),
6801
- negative: () => N(d(t, p((t) => w(t) && t < 0)))
6802
- });
6803
- N(p(w));
6804
- const k = (t) => Object.assign(a(t), {
6805
- between: (e, n) => k(d(t, ((t, e) => p((n) => j(n) && t <= n && e >= n))(e, n))),
6806
- lt: (e) => k(d(t, ((t) => p((e) => j(e) && e < t))(e))),
6807
- gt: (e) => k(d(t, ((t) => p((e) => j(e) && e > t))(e))),
6808
- lte: (e) => k(d(t, ((t) => p((e) => j(e) && e <= t))(e))),
6809
- gte: (e) => k(d(t, ((t) => p((e) => j(e) && e >= t))(e))),
6810
- positive: () => k(d(t, p((t) => j(t) && t > 0))),
6811
- negative: () => k(d(t, p((t) => j(t) && t < 0)))
6812
- });
6813
- k(p(j)), a(p(function(t) {
6814
- return "boolean" == typeof t;
6815
- })), a(p(function(t) {
6816
- return "symbol" == typeof t;
6817
- })), a(p(function(t) {
6818
- return null == t;
6819
- })), a(p(function(t) {
6820
- return null != t;
6821
- }));
6822
- var I = class extends Error {
6823
- constructor(t) {
6824
- let e;
6825
- try {
6826
- e = JSON.stringify(t);
6827
- } catch (n) {
6828
- e = t;
6829
- }
6830
- super(`Pattern matching error: no pattern matches value ${e}`), this.input = void 0, this.input = t;
6831
- }
6832
- };
6833
- const L = {
6834
- matched: !1,
6835
- value: void 0
6836
- };
6837
- function M(t) {
6838
- return new R(t, L);
6839
- }
6840
- var R = class R {
6841
- constructor(t, e) {
6842
- this.input = void 0, this.state = void 0, this.input = t, this.state = e;
6843
- }
6844
- with(...t) {
6845
- if (this.state.matched) return this;
6846
- const e = t[t.length - 1], r = [t[0]];
6847
- let i;
6848
- 3 === t.length && "function" == typeof t[1] ? i = t[1] : t.length > 2 && r.push(...t.slice(1, t.length - 1));
6849
- let s = !1, c = {};
6850
- const u = (t, e) => {
6851
- s = !0, c[t] = e;
6852
- }, a = !r.some((t) => o(t, this.input, u)) || i && !Boolean(i(this.input)) ? L : {
6853
- matched: !0,
6854
- value: e(s ? n in c ? c[n] : c : this.input, this.input)
6855
- };
6856
- return new R(this.input, a);
6857
- }
6858
- when(t, e) {
6859
- if (this.state.matched) return this;
6860
- const n = Boolean(t(this.input));
6861
- return new R(this.input, n ? {
6862
- matched: !0,
6863
- value: e(this.input, this.input)
6864
- } : L);
6865
- }
6866
- otherwise(t) {
6867
- return this.state.matched ? this.state.value : t(this.input);
6868
- }
6869
- exhaustive(t = F) {
6870
- return this.state.matched ? this.state.value : t(this.input);
6871
- }
6872
- run() {
6873
- return this.exhaustive();
6874
- }
6875
- returnType() {
6876
- return this;
6877
- }
6878
- narrow() {
6879
- return this;
6880
- }
6881
- };
6882
- function F(t) {
6883
- throw new I(t);
6884
- }
6885
- //#endregion
6886
7071
  //#region src/request/render-verdict.ts
6887
7072
  function renderVerdict(verdict) {
6888
- return M(verdict).with({ kind: "ask" }, () => null).with({ kind: "allow" }, () => JSON.stringify({ decision: "allow" })).with({ kind: "deny" }, (denied) => JSON.stringify({
7073
+ return M(verdict).with({ kind: "allow" }, () => JSON.stringify({ decision: "allow" })).with({ kind: "deny" }, (denied) => JSON.stringify({
6889
7074
  decision: "deny",
6890
7075
  reason: `[${denied.rule}] ${denied.reason}`
6891
7076
  })).exhaustive();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-mode",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "A permission classifier library and Claude Code mod, so agents that are not Claude can run unattended",
5
5
  "keywords": [
6
6
  "agent",
@@ -0,0 +1 @@
1
+ Do not retry this action, and do not reach the same result another way: not with a different command, tool, script, file, or agent. Continue the task on a safer path that does not need this action. If no safer path exists for this step, finish the rest of the task first, then tell the user what you need from them for this step.