auto-mode 0.6.0 → 0.8.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
@@ -31,14 +31,14 @@ Create the classifier configuration:
31
31
 
32
32
  ```json
33
33
  {
34
- "preset": "jev",
35
- "provider": { "apiKeyEnv": "TYPESAFE_API_KEY" }
34
+ "classifiers": { "jev": { "apiKeyEnv": "TYPESAFE_API_KEY" } },
35
+ "decision": { "classifier": "jev" }
36
36
  }
37
37
  ```
38
38
 
39
- Save it as `~/.config/auto-mode/config.json` and export your TypeSafe API key, or set
40
- `provider.apiKeyCommand` to a command that prints it.
41
- [Configuration](./docs/guides/configuration.md) covers credentials and provider overrides.
39
+ Save it as `~/.config/auto-mode/config.json` and export your TypeSafe API key, or set the entry's
40
+ `apiKeyCommand` to a command that prints it. [Configuration](./docs/guides/configuration.md) covers
41
+ the registries, credentials, and migrating an older file.
42
42
 
43
43
  ## Standing permissions
44
44
 
@@ -63,9 +63,10 @@ approval when every rule confidently allows the action, and a denial for uncerta
63
63
  denial names its rule, the harm, and what clears it, and the agent continues on another path. The
64
64
  confidence threshold is configurable and needs evaluation against your actions.
65
65
 
66
- A missing key, failed API call, malformed response, or oversized request follows `onFailure`. The
67
- default `defer` writes no verdict and keeps the prompt; `deny` fails closed. Failures write a
68
- diagnostic to stderr. auto-mode never truncates a Jev action to make it fit.
66
+ A missing key, failed API call, malformed response, or oversized request follows
67
+ `decision.onFailure`. The default `defer` writes no verdict and keeps the prompt; `deny` fails
68
+ closed. Failures write a diagnostic to stderr. auto-mode never truncates a Jev action to make it
69
+ fit.
69
70
 
70
71
  ## Commands
71
72
 
@@ -76,10 +77,11 @@ diagnostic to stderr. auto-mode never truncates a Jev action to make it fit.
76
77
  | `auto-mode run --local-only` | Skip the model tier |
77
78
  | `auto-mode run --jev-only` | Require Jev with a 5-second API timeout |
78
79
  | `auto-mode print-prompt` | Print the selected provider's base policy |
80
+ | `auto-mode config migrate` | Rewrite an older config file in place |
79
81
 
80
82
  The mod evaluates only ask decisions and preserves existing allow and deny decisions. It runs the
81
- CLI as a bounded child process with `--jev-only`. The `spark`, `claude`, and `glm` presets use the
82
- Messages API and serve library callers.
83
+ CLI as a bounded child process with `--jev-only`. The `spark`, `claude`, `glm`, and `messages`
84
+ classifier kinds use the Messages API and serve library callers.
83
85
 
84
86
  ## Library
85
87
 
package/dist/cli.js CHANGED
@@ -1,11 +1,147 @@
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-9pnm4JBS.js";
3
- import { appendFile, mkdir } from "node:fs/promises";
2
+ import { _ as string, f as resolveConfigPath, g as strictObject, h as number, l as DEFAULT_DENIAL_BUDGET, n as parseActionRequest, p as normalizeConfigFile, r as classifyAction, s as loadPolicy, t as renderVerdict, u as loadConfig } from "./render-verdict-DePMDXKz.js";
3
+ import { appendFile, constants, copyFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
6
  import { createHash, randomUUID } from "node:crypto";
7
7
  import { text } from "node:stream/consumers";
8
8
  import { parseArgs } from "node:util";
9
+ //#region src/budget/build-retry-key.ts
10
+ const DESCRIPTIVE_FIELDS = { Bash: [
11
+ "description",
12
+ "timeout",
13
+ "run_in_background"
14
+ ] };
15
+ function buildRetryKey(request) {
16
+ const ignored = Object.hasOwn(DESCRIPTIVE_FIELDS, request.toolName) ? DESCRIPTIVE_FIELDS[request.toolName] ?? [] : [];
17
+ const input = Object.fromEntries(Object.entries(request.toolInput).filter(([key]) => !ignored.includes(key)));
18
+ const canonical = JSON.stringify(sortKeys({
19
+ cwd: request.cwd,
20
+ toolName: request.toolName,
21
+ toolInput: input,
22
+ lastDirectUserMessage: request.decisionContext?.lastDirectUserMessage?.text ?? null
23
+ }));
24
+ return createHash("sha256").update(canonical).digest("hex");
25
+ }
26
+ function sortKeys(value) {
27
+ if (Array.isArray(value)) return value.map((item) => sortKeys(item));
28
+ if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a < b ? -1 : 1).map(([key, item]) => [key, sortKeys(item)]));
29
+ return value;
30
+ }
31
+ //#endregion
32
+ //#region src/budget/find-retry-deny.ts
33
+ function findRetryDeny(state, retryKey) {
34
+ if (state.lastDenied === null || state.lastDenied.retryKey !== retryKey) return null;
35
+ return {
36
+ kind: "deny",
37
+ rule: state.lastDenied.rule,
38
+ reason: state.lastDenied.reason
39
+ };
40
+ }
41
+ //#endregion
42
+ //#region src/budget/types.ts
43
+ const EMPTY_DENIAL_STATE = {
44
+ consecutive: 0,
45
+ session: 0,
46
+ lastDenied: null
47
+ };
48
+ //#endregion
49
+ //#region src/budget/load-denial-state.ts
50
+ const count = number().int().nonnegative();
51
+ const stateSchema = strictObject({
52
+ consecutive: count,
53
+ session: count,
54
+ lastDenied: strictObject({
55
+ retryKey: string().min(1),
56
+ rule: string(),
57
+ reason: string()
58
+ }).nullable()
59
+ });
60
+ async function loadDenialState(path) {
61
+ let raw;
62
+ try {
63
+ raw = await readFile(path, "utf8");
64
+ } catch {
65
+ return EMPTY_DENIAL_STATE;
66
+ }
67
+ try {
68
+ const parsed = stateSchema.safeParse(JSON.parse(raw));
69
+ return parsed.success ? parsed.data : EMPTY_DENIAL_STATE;
70
+ } catch {
71
+ return EMPTY_DENIAL_STATE;
72
+ }
73
+ }
74
+ //#endregion
75
+ //#region src/budget/plan-denial-budget.ts
76
+ function planDenialBudget(state, verdict, retryKey, budget) {
77
+ if (verdict === null) return {
78
+ verdict: null,
79
+ state,
80
+ escalation: false
81
+ };
82
+ if (verdict.kind === "allow") return {
83
+ verdict,
84
+ state: {
85
+ ...state,
86
+ consecutive: 0,
87
+ lastDenied: null
88
+ },
89
+ escalation: false
90
+ };
91
+ if (state.consecutive >= budget.consecutive || state.session >= budget.perSession) return {
92
+ verdict: null,
93
+ state: EMPTY_DENIAL_STATE,
94
+ escalation: true
95
+ };
96
+ const next = {
97
+ consecutive: state.consecutive + 1,
98
+ session: state.session + 1,
99
+ lastDenied: {
100
+ retryKey,
101
+ rule: verdict.rule,
102
+ reason: verdict.reason
103
+ }
104
+ };
105
+ const left = Math.min(budget.consecutive - next.consecutive, budget.perSession - next.session);
106
+ return {
107
+ verdict: {
108
+ ...verdict,
109
+ reason: `${verdict.reason} ${formatDenialsLeft(left)}`
110
+ },
111
+ state: next,
112
+ escalation: false
113
+ };
114
+ }
115
+ function formatDenialsLeft(left) {
116
+ if (left > 0) return `Denials left before auto-mode asks the user: ${left}.`;
117
+ return "This is the last denial before auto-mode asks the user. Stop now, without finishing the rest of the task, and tell the user what consent you need to continue.";
118
+ }
119
+ //#endregion
120
+ //#region src/state/resolve-state-dir.ts
121
+ function resolveStateDir() {
122
+ const stateHome = process.env["XDG_STATE_HOME"];
123
+ const base = stateHome === void 0 || stateHome === "" ? join(homedir(), ".local", "state") : stateHome;
124
+ return join(base, "auto-mode");
125
+ }
126
+ //#endregion
127
+ //#region src/budget/resolve-denial-state-path.ts
128
+ function resolveDenialStatePath(request) {
129
+ const agentID = request.decisionContext?.agentID ?? "";
130
+ const key = createHash("sha256").update(`${request.sessionID}\0${agentID}`).digest("hex").slice(0, 32);
131
+ return join(resolveStateDir(), "denials", `${key}.json`);
132
+ }
133
+ //#endregion
134
+ //#region src/budget/write-denial-state.ts
135
+ async function writeDenialState(path, state) {
136
+ const staged = `${path}.${randomUUID()}.tmp`;
137
+ await mkdir(dirname(path), {
138
+ recursive: true,
139
+ mode: 448
140
+ });
141
+ await writeFile(staged, `${JSON.stringify(state)}\n`, { mode: 384 });
142
+ await rename(staged, path);
143
+ }
144
+ //#endregion
9
145
  //#region src/config/build-jev-only-config.ts
10
146
  function buildJevOnlyConfig(config) {
11
147
  if (config.provider.protocol !== "system-one") return null;
@@ -18,20 +154,51 @@ function buildJevOnlyConfig(config) {
18
154
  };
19
155
  }
20
156
  //#endregion
157
+ //#region src/config/write-migrated-config.ts
158
+ async function writeMigratedConfig(path) {
159
+ let raw;
160
+ try {
161
+ raw = await readFile(path, "utf8");
162
+ } catch (error) {
163
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return `${path} does not exist; nothing to migrate`;
164
+ throw new Error(`${path} is unreadable`, { cause: error });
165
+ }
166
+ let json;
167
+ try {
168
+ json = JSON.parse(raw);
169
+ } catch {
170
+ throw new Error(`${path} is not valid JSON`);
171
+ }
172
+ const normalized = normalizeConfigFile(json, path);
173
+ if (!normalized.isLegacy) return `${path} already uses the current shape`;
174
+ const backup = `${path}.bak`;
175
+ const staged = `${path}.migrating`;
176
+ try {
177
+ await copyFile(path, backup, constants.COPYFILE_EXCL);
178
+ } catch (error) {
179
+ if (error instanceof Error && "code" in error && error.code === "EEXIST") throw new Error(`${backup} already exists; move it aside and run the migration again`, { cause: error });
180
+ throw error;
181
+ }
182
+ await writeFile(staged, `${JSON.stringify(normalized.file, null, 2)}\n`, { mode: 384 });
183
+ await rename(staged, path);
184
+ return `rewrote ${path} in the current shape; the old file is at ${backup}`;
185
+ }
186
+ //#endregion
21
187
  //#region src/diagnostics/write-action-diagnostic.ts
22
188
  async function writeActionDiagnostic(payload, entry) {
23
- const stateHome = process.env["XDG_STATE_HOME"];
24
- const stateDir = stateHome === void 0 || stateHome === "" ? join(homedir(), ".local", "state") : stateHome;
25
- const path = process.env["AUTO_MODE_DIAGNOSTICS_PATH"] ?? join(stateDir, "auto-mode", "actions.jsonl");
189
+ const path = process.env["AUTO_MODE_DIAGNOSTICS_PATH"] ?? join(resolveStateDir(), "actions.jsonl");
26
190
  if (path === "") return;
27
191
  const record = {
28
- schemaVersion: 2,
192
+ schemaVersion: 3,
29
193
  time: (/* @__PURE__ */ new Date()).toISOString(),
30
194
  invocationID: entry.invocationID,
31
195
  sessionHash: toHash(payload.sessionID),
32
196
  actionHash: payload.toolUseID === void 0 ? null : toHash(payload.toolUseID),
33
197
  status: entry.status,
34
198
  verdict: entry.verdict ?? null,
199
+ decidingStage: entry.decidingStage ?? null,
200
+ denials: entry.denials ?? null,
201
+ escalation: entry.escalation ?? false,
35
202
  diagnostics: entry.diagnostics ?? null
36
203
  };
37
204
  try {
@@ -54,6 +221,7 @@ const USAGE = `auto-mode — a permission classifier for the auto-mode Claude Co
54
221
  Usage:
55
222
  auto-mode run Read an action request on stdin, write a verdict on stdout
56
223
  auto-mode print-prompt Print the system prompt the classifier receives
224
+ auto-mode config migrate Rewrite the config file from the old keys into the current shape
57
225
 
58
226
  Options:
59
227
  --classifier <path> Use this framework file instead of the shipped one
@@ -92,6 +260,7 @@ async function run(explain, localOnly, jevOnly, deadline) {
92
260
  });
93
261
  return printNote(true, "configuration unreadable; no verdict");
94
262
  }
263
+ printWarnings(loaded.warnings);
95
264
  const config = jevOnly ? buildJevOnlyConfig(loaded) : loaded;
96
265
  if (config === null) {
97
266
  await writeActionDiagnostic(request, {
@@ -119,27 +288,56 @@ async function run(explain, localOnly, jevOnly, deadline) {
119
288
  process.on("SIGINT", stopEvaluation);
120
289
  }
121
290
  try {
122
- const outcome = await classifyAction(request, config, {
291
+ const statePath = resolveDenialStatePath(request);
292
+ const retryKey = buildRetryKey(request);
293
+ const before = await loadDenialState(statePath);
294
+ const retry = findRetryDeny(before, retryKey);
295
+ const outcome = retry === null ? await classifyAction(request, config, {
123
296
  localOnly,
124
297
  deadlineAt,
125
298
  signal: controller.signal
126
- });
127
- if (outcome.verdict !== null) writeVerdict(outcome.verdict);
299
+ }) : {
300
+ verdict: retry,
301
+ decidingStage: "retry",
302
+ note: "retry of the action just denied; denied again",
303
+ status: "deny"
304
+ };
305
+ const latest = retry === null ? await loadDenialState(statePath) : before;
306
+ const plan = planDenialBudget(latest, outcome.verdict, retryKey, config.denialBudget ?? DEFAULT_DENIAL_BUDGET);
307
+ if (plan.state !== latest) await tryWriteDenialState(statePath, plan.state);
308
+ if (plan.verdict !== null) writeVerdict(plan.verdict);
128
309
  await writeActionDiagnostic(request, {
129
310
  invocationID,
130
311
  status: outcome.status,
131
- verdict: outcome.verdict?.kind ?? "defer",
312
+ verdict: plan.verdict?.kind ?? "defer",
313
+ decidingStage: plan.escalation ? "budget" : outcome.decidingStage,
314
+ denials: {
315
+ consecutive: plan.state.consecutive,
316
+ session: plan.state.session
317
+ },
318
+ escalation: plan.escalation,
132
319
  ...outcome.diagnostics === void 0 ? {} : { diagnostics: outcome.diagnostics }
133
320
  });
134
- return printNote(explain || outcome.unavailable === true, outcome.note);
321
+ const note = plan.escalation ? "denial budget exhausted; the user decides this action" : outcome.note;
322
+ return printNote(explain || outcome.unavailable === true, note);
135
323
  } finally {
136
324
  process.off("SIGTERM", stopEvaluation);
137
325
  process.off("SIGINT", stopEvaluation);
138
326
  }
139
327
  }
328
+ async function tryWriteDenialState(path, state) {
329
+ try {
330
+ await writeDenialState(path, state);
331
+ } catch {
332
+ process.stderr.write("auto-mode: denial counts unavailable\n");
333
+ }
334
+ }
140
335
  function writeVerdict(verdict) {
141
336
  process.stdout.write(renderVerdict(verdict));
142
337
  }
338
+ function printWarnings(warnings = []) {
339
+ for (const warning of warnings) process.stderr.write(`auto-mode: ${warning}\n`);
340
+ }
143
341
  function printNote(explain, message) {
144
342
  if (explain) process.stderr.write(`auto-mode: ${message}\n`);
145
343
  return 0;
@@ -174,6 +372,7 @@ async function main(argv) {
174
372
  }
175
373
  if (command === "print-prompt") {
176
374
  const config = await loadConfig();
375
+ printWarnings(config.warnings);
177
376
  const framework = config.provider.protocol === "system-one" ? "decision.md" : "classifier.md";
178
377
  const prompt = await loadPolicy({
179
378
  classifierPath: args.values.classifier ?? config.classifierPath,
@@ -182,9 +381,21 @@ async function main(argv) {
182
381
  process.stdout.write(`${prompt}\n`);
183
382
  return 0;
184
383
  }
384
+ if (command === "config" && args.positionals[1] === "migrate") return runConfigMigration();
185
385
  process.stderr.write(`auto-mode: unknown command '${command}'\n\n${USAGE}`);
186
386
  return 2;
187
387
  }
388
+ async function runConfigMigration() {
389
+ const path = resolveConfigPath();
390
+ try {
391
+ const result = await writeMigratedConfig(path);
392
+ process.stdout.write(`${result}\n`);
393
+ return 0;
394
+ } catch (error) {
395
+ process.stderr.write(`auto-mode: ${error instanceof Error ? error.message : "config migration failed"}\n`);
396
+ return 1;
397
+ }
398
+ }
188
399
  process.exitCode = await main(process.argv.slice(2));
189
400
  //#endregion
190
401
  export {};
package/dist/index.d.ts CHANGED
@@ -1,9 +1,18 @@
1
+ //#region src/budget/types.d.ts
2
+ interface DenialBudget {
3
+ readonly consecutive: number;
4
+ readonly perSession: number;
5
+ }
6
+ //#endregion
1
7
  //#region src/config/types.d.ts
2
8
  interface EvaluationOptions {
3
9
  readonly deadlineAt?: number | undefined;
4
10
  readonly signal?: Readonly<AbortSignal> | undefined;
5
11
  }
6
12
  //#endregion
13
+ //#region src/config/presets.d.ts
14
+ export declare const PRESETS: Readonly<Record<string, ProviderConfig>>;
15
+ //#endregion
7
16
  //#region src/config/config.d.ts
8
17
  interface ProviderConfig {
9
18
  readonly protocol?: 'messages' | 'system-one';
@@ -15,16 +24,25 @@ interface ProviderConfig {
15
24
  readonly maxTokens: number;
16
25
  readonly timeoutMs: number;
17
26
  }
27
+ interface ScopeSource {
28
+ readonly kind: ScopeSourceKind;
29
+ readonly paths?: readonly string[];
30
+ }
31
+ type ScopeSourceKind = (typeof SCOPE_SOURCE_KINDS)[number];
32
+ declare const SCOPE_SOURCE_KINDS: readonly ['cwd', 'session', 'globs', 'atc'];
18
33
  interface Config {
19
34
  readonly provider: ProviderConfig;
35
+ readonly judge?: ProviderConfig | null;
36
+ readonly scopeSources?: Readonly<Record<string, ScopeSource>>;
20
37
  readonly classifierPath?: string | undefined;
21
38
  readonly rulesPath?: string | undefined;
22
39
  readonly onFailure: 'defer' | 'deny';
23
40
  readonly claudeSettingsPath?: string | null | undefined;
24
41
  readonly minConfidence?: number | undefined;
42
+ readonly denialBudget?: DenialBudget;
43
+ readonly warnings?: readonly string[];
25
44
  }
26
45
  export declare const DEFAULT_CONFIG: Config;
27
- export declare const PRESETS: Readonly<Record<string, ProviderConfig>>;
28
46
  export declare function resolveConfigPath(): string;
29
47
  export declare function loadConfig(path?: string): Promise<Config>;
30
48
  export declare function resolveApiKey(provider: ProviderConfig, options?: EvaluationOptions): Promise<string | null>;
@@ -97,8 +115,10 @@ interface DecisionDiagnostics {
97
115
  }
98
116
  //#endregion
99
117
  //#region src/classify-action.d.ts
118
+ type DecidingStage = 'local' | 'jev' | 'messages' | 'retry';
100
119
  interface ActionOutcome {
101
120
  readonly verdict: Verdict | null;
121
+ readonly decidingStage: DecidingStage;
102
122
  readonly note: string;
103
123
  readonly status: 'allow' | 'skipped' | 'failure' | DecisionDiagnostics['status'];
104
124
  readonly unavailable?: boolean;
@@ -143,4 +163,4 @@ type LocalVerdict = {
143
163
  };
144
164
  export declare function classifyLocally(payload: ActionRequest): LocalVerdict;
145
165
  //#endregion
146
- export type { ActionOutcome, ActionRequest, ClassifyOptions, Config, DecisionContext, LocalVerdict, ModelOutcome, PolicyPaths, ProviderConfig, Verdict };
166
+ export type { ActionOutcome, ActionRequest, ClassifyOptions, Config, DecisionContext, LocalVerdict, ModelOutcome, PolicyPaths, ProviderConfig, ScopeSource, Verdict };
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-9pnm4JBS.js";
1
+ import { a as classifyWithModel, c as DEFAULT_CONFIG, d as resolveApiKey, f as resolveConfigPath, i as classifyLocally, m as PRESETS, n as parseActionRequest, o as parseModelVerdict, r as classifyAction, s as loadPolicy, t as renderVerdict, u as loadConfig } from "./render-verdict-DePMDXKz.js";
2
2
  export { DEFAULT_CONFIG, PRESETS, classifyAction, classifyLocally, classifyWithModel, loadConfig, loadPolicy, parseActionRequest, parseModelVerdict, renderVerdict, resolveApiKey, resolveConfigPath };
@@ -252,7 +252,7 @@ function safeExtend(schema, shape) {
252
252
  if (!isPlainObject(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
253
253
  return clone(schema, mergeDefs(schema._zod.def, { shape: extended(schema, shape) }));
254
254
  }
255
- function merge$1(a, b) {
255
+ function merge(a, b) {
256
256
  if (!b?._zod?.def) throw new Error("Invalid input to merge: expected an object schema. To merge a plain shape, use `.extend()`.");
257
257
  if (a._zod.def.checks?.length) throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");
258
258
  const newShape = {};
@@ -5218,7 +5218,7 @@ const ZodObject = /*@__PURE__*/ $constructor("ZodObject", (inst, def) => {
5218
5218
  return safeExtend(this, incoming);
5219
5219
  },
5220
5220
  merge(other) {
5221
- return merge$1(this, other);
5221
+ return merge(this, other);
5222
5222
  },
5223
5223
  pick(mask) {
5224
5224
  return pick(this, mask);
@@ -5569,6 +5569,171 @@ async function loadClaudeRules(path) {
5569
5569
  hard_deny: (rules.autoMode?.hard_deny ?? []).filter((entry) => entry !== "$defaults")
5570
5570
  };
5571
5571
  }
5572
+ const PRESETS = {
5573
+ jev: {
5574
+ protocol: "system-one",
5575
+ baseURL: "https://api.typesafe.ai",
5576
+ model: "jev-1.13.0",
5577
+ apiKeyEnv: "TYPESAFE_API_KEY",
5578
+ reasoning: false,
5579
+ maxTokens: 3e3,
5580
+ timeoutMs: 5e3
5581
+ },
5582
+ spark: {
5583
+ baseURL: "https://api.meta.ai",
5584
+ model: "muse-spark-1.3-contributor",
5585
+ apiKeyEnv: "META_API_KEY",
5586
+ reasoning: true,
5587
+ maxTokens: 3e3,
5588
+ timeoutMs: 45e3
5589
+ },
5590
+ claude: {
5591
+ baseURL: "https://api.anthropic.com",
5592
+ model: "claude-haiku-4-5-20251001",
5593
+ apiKeyEnv: "ANTHROPIC_API_KEY",
5594
+ reasoning: true,
5595
+ maxTokens: 3e3,
5596
+ timeoutMs: 45e3
5597
+ },
5598
+ glm: {
5599
+ baseURL: "https://api.z.ai/api/anthropic",
5600
+ model: "glm-5.3-flash",
5601
+ apiKeyEnv: "ZAI_API_KEY",
5602
+ reasoning: true,
5603
+ maxTokens: 3e3,
5604
+ timeoutMs: 6e4
5605
+ }
5606
+ };
5607
+ const MESSAGES_DEFAULTS = {
5608
+ protocol: "messages",
5609
+ baseURL: "https://api.anthropic.com",
5610
+ reasoning: true,
5611
+ maxTokens: 3e3,
5612
+ timeoutMs: 45e3
5613
+ };
5614
+ function findPreset(id) {
5615
+ return Object.hasOwn(PRESETS, id) ? PRESETS[id] : void 0;
5616
+ }
5617
+ //#endregion
5618
+ //#region src/config/normalize-config-file.ts
5619
+ function normalizeConfigFile(json, path) {
5620
+ if (!isRecord(json)) return {
5621
+ file: json,
5622
+ isLegacy: false,
5623
+ warnings: []
5624
+ };
5625
+ const legacyKeys = Object.keys(json).filter((key) => LEGACY_KEYS.has(key));
5626
+ if (legacyKeys.length === 0) return {
5627
+ file: json,
5628
+ isLegacy: false,
5629
+ warnings: []
5630
+ };
5631
+ const currentKeys = Object.keys(json).filter((key) => !LEGACY_KEYS.has(key));
5632
+ if (currentKeys.length > 0) throw new Error(`${path} mixes the old keys (${legacyKeys.join(", ")}) with the new ones (${currentKeys.join(", ")}); run \`auto-mode config migrate\` on a copy without the new keys`);
5633
+ const parsed = legacyConfigSchema.safeParse(json);
5634
+ if (!parsed.success) throw new Error(`${path} is not a valid config: ${prettifyError(parsed.error)}`);
5635
+ const legacy = parsed.data;
5636
+ const warnings = [`${path} uses the old config keys (${legacyKeys.join(", ")}); run \`auto-mode config migrate\` to rewrite it`, ...legacy.transcriptEntries === void 0 ? [] : [`${path} sets transcriptEntries, which has no effect; migration drops it`]];
5637
+ return {
5638
+ file: buildFileFromLegacy(legacy, path),
5639
+ isLegacy: true,
5640
+ warnings
5641
+ };
5642
+ }
5643
+ function isRecord(value) {
5644
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5645
+ }
5646
+ const LEGACY_KEYS = /* @__PURE__ */ new Set([
5647
+ "preset",
5648
+ "provider",
5649
+ "classifierPath",
5650
+ "rulesPath",
5651
+ "transcriptEntries",
5652
+ "onFailure",
5653
+ "claudeSettingsPath",
5654
+ "minConfidence"
5655
+ ]);
5656
+ const text$1 = string().min(1);
5657
+ const positive$1 = number().positive();
5658
+ const legacyConfigSchema = strictObject({
5659
+ preset: text$1.optional(),
5660
+ provider: strictObject({
5661
+ protocol: _enum(["messages", "system-one"]).optional(),
5662
+ baseURL: text$1.optional(),
5663
+ model: text$1.optional(),
5664
+ apiKeyEnv: text$1.optional(),
5665
+ apiKeyCommand: text$1.optional(),
5666
+ reasoning: boolean().optional(),
5667
+ maxTokens: positive$1.optional(),
5668
+ timeoutMs: positive$1.optional()
5669
+ }).optional(),
5670
+ classifierPath: text$1.optional(),
5671
+ rulesPath: text$1.optional(),
5672
+ transcriptEntries: number().nonnegative().optional(),
5673
+ onFailure: _enum(["defer", "deny"]).optional(),
5674
+ claudeSettingsPath: text$1.nullable().optional(),
5675
+ minConfidence: number().min(.5).max(1).optional()
5676
+ });
5677
+ function buildFileFromLegacy(legacy, path) {
5678
+ const hasClassifier = legacy.preset !== void 0 || legacy.provider !== void 0;
5679
+ const decision = {
5680
+ ...hasClassifier ? { classifier: getLegacyClassifierID(legacy) } : {},
5681
+ ...legacy.minConfidence === void 0 ? {} : { minConfidence: legacy.minConfidence },
5682
+ ...legacy.onFailure === void 0 ? {} : { onFailure: legacy.onFailure }
5683
+ };
5684
+ const policy = {
5685
+ ...legacy.rulesPath === void 0 ? {} : { rulesPath: legacy.rulesPath },
5686
+ ...legacy.classifierPath === void 0 ? {} : { frameworkPath: legacy.classifierPath },
5687
+ ...legacy.claudeSettingsPath === void 0 ? {} : { claudeSettingsPath: legacy.claudeSettingsPath }
5688
+ };
5689
+ return {
5690
+ ...hasClassifier ? { classifiers: { [getLegacyClassifierID(legacy)]: buildClassifierEntry(getLegacyClassifierID(legacy), buildLegacyProvider(legacy, path)) } } : {},
5691
+ ...Object.keys(decision).length === 0 ? {} : { decision },
5692
+ ...Object.keys(policy).length === 0 ? {} : { policy }
5693
+ };
5694
+ }
5695
+ function getLegacyClassifierID(legacy) {
5696
+ if (legacy.preset !== void 0) return legacy.preset;
5697
+ return legacy.provider !== void 0 && legacy.provider.protocol !== "system-one" ? "spark" : "jev";
5698
+ }
5699
+ function buildLegacyProvider(legacy, path) {
5700
+ const preset = findPreset(getLegacyClassifierID(legacy));
5701
+ if (preset === void 0) throw new Error(`${path} names an unknown preset '${legacy.preset}'; known presets are ${Object.keys(PRESETS).join(", ")}`);
5702
+ const override = legacy.provider ?? {};
5703
+ return {
5704
+ protocol: override.protocol ?? preset.protocol ?? "messages",
5705
+ baseURL: override.baseURL ?? preset.baseURL,
5706
+ model: override.model ?? preset.model,
5707
+ apiKeyEnv: override.apiKeyEnv ?? preset.apiKeyEnv,
5708
+ apiKeyCommand: override.apiKeyCommand ?? preset.apiKeyCommand,
5709
+ reasoning: override.reasoning ?? preset.reasoning,
5710
+ maxTokens: override.maxTokens ?? preset.maxTokens,
5711
+ timeoutMs: override.timeoutMs ?? preset.timeoutMs
5712
+ };
5713
+ }
5714
+ const PROVIDER_FIELDS = [
5715
+ "baseURL",
5716
+ "model",
5717
+ "apiKeyEnv",
5718
+ "apiKeyCommand",
5719
+ "reasoning",
5720
+ "maxTokens",
5721
+ "timeoutMs"
5722
+ ];
5723
+ function buildClassifierEntry(id, provider) {
5724
+ const kind = pickEntryKind(id, provider);
5725
+ const base = kind === "messages" ? MESSAGES_DEFAULTS : findPreset(kind) ?? {};
5726
+ const fields = Object.fromEntries(PROVIDER_FIELDS.filter((field) => provider[field] !== void 0 && provider[field] !== base[field]).map((field) => [field, provider[field]]));
5727
+ return {
5728
+ ...kind === id ? {} : { kind },
5729
+ ...fields
5730
+ };
5731
+ }
5732
+ function pickEntryKind(id, provider) {
5733
+ const preset = findPreset(id);
5734
+ if (preset !== void 0 && (preset.protocol ?? "messages") === provider.protocol) return id;
5735
+ return provider.protocol === "system-one" ? "jev" : "messages";
5736
+ }
5572
5737
  //#endregion
5573
5738
  //#region src/config/read-api-key-from-command.ts
5574
5739
  function readApiKeyFromCommand(command, options = {}) {
@@ -5624,73 +5789,29 @@ function readApiKeyFromCommand(command, options = {}) {
5624
5789
  }
5625
5790
  //#endregion
5626
5791
  //#region src/config/config.ts
5792
+ const SCOPE_SOURCE_KINDS = [
5793
+ "cwd",
5794
+ "session",
5795
+ "globs",
5796
+ "atc"
5797
+ ];
5798
+ const DEFAULT_PROVIDER = findPreset("jev");
5799
+ if (DEFAULT_PROVIDER === void 0) throw new Error("the jev preset is missing");
5800
+ const DEFAULT_DENIAL_BUDGET = {
5801
+ consecutive: 3,
5802
+ perSession: 20
5803
+ };
5627
5804
  const DEFAULT_CONFIG = {
5628
- provider: {
5629
- protocol: "system-one",
5630
- baseURL: "https://api.typesafe.ai",
5631
- model: "jev-1.13.0",
5632
- apiKeyEnv: "TYPESAFE_API_KEY",
5633
- reasoning: false,
5634
- maxTokens: 3e3,
5635
- timeoutMs: 5e3
5636
- },
5805
+ provider: DEFAULT_PROVIDER,
5637
5806
  onFailure: "defer",
5638
- minConfidence: .8
5639
- };
5640
- const SPARK_PROVIDER = {
5641
- baseURL: "https://api.meta.ai",
5642
- model: "muse-spark-1.3-contributor",
5643
- apiKeyEnv: "META_API_KEY",
5644
- reasoning: true,
5645
- maxTokens: 3e3,
5646
- timeoutMs: 45e3
5647
- };
5648
- const PRESETS = {
5649
- jev: DEFAULT_CONFIG.provider,
5650
- spark: SPARK_PROVIDER,
5651
- claude: {
5652
- baseURL: "https://api.anthropic.com",
5653
- model: "claude-haiku-4-5-20251001",
5654
- apiKeyEnv: "ANTHROPIC_API_KEY",
5655
- reasoning: true,
5656
- maxTokens: 3e3,
5657
- timeoutMs: 45e3
5658
- },
5659
- glm: {
5660
- baseURL: "https://api.z.ai/api/anthropic",
5661
- model: "glm-5.3-flash",
5662
- apiKeyEnv: "ZAI_API_KEY",
5663
- reasoning: true,
5664
- maxTokens: 3e3,
5665
- timeoutMs: 6e4
5666
- }
5807
+ minConfidence: .8,
5808
+ denialBudget: DEFAULT_DENIAL_BUDGET
5667
5809
  };
5668
5810
  function resolveConfigPath() {
5669
5811
  const xdg = process.env["XDG_CONFIG_HOME"];
5670
5812
  const base = xdg !== void 0 && xdg !== "" ? xdg : join(homedir(), ".config");
5671
5813
  return join(base, "auto-mode", "config.json");
5672
5814
  }
5673
- const text = string().min(1);
5674
- const positive = number().positive();
5675
- const configFileSchema = strictObject({
5676
- preset: text.optional(),
5677
- provider: strictObject({
5678
- protocol: _enum(["messages", "system-one"]).optional(),
5679
- baseURL: text.optional(),
5680
- model: text.optional(),
5681
- apiKeyEnv: text.optional(),
5682
- apiKeyCommand: text.optional(),
5683
- reasoning: boolean().optional(),
5684
- maxTokens: positive.optional(),
5685
- timeoutMs: positive.optional()
5686
- }).optional(),
5687
- classifierPath: text.optional(),
5688
- rulesPath: text.optional(),
5689
- transcriptEntries: number().nonnegative().optional(),
5690
- onFailure: _enum(["defer", "deny"]).optional(),
5691
- claudeSettingsPath: text.nullable().optional(),
5692
- minConfidence: number().min(.5).max(1).optional()
5693
- });
5694
5815
  async function loadConfig(path = resolveConfigPath()) {
5695
5816
  let raw;
5696
5817
  try {
@@ -5705,33 +5826,150 @@ async function loadConfig(path = resolveConfigPath()) {
5705
5826
  } catch {
5706
5827
  throw new Error(`${path} is not valid JSON`);
5707
5828
  }
5829
+ const normalized = normalizeConfigFile(json, path);
5830
+ return buildConfig(normalized.file, path, normalized.warnings);
5831
+ }
5832
+ const text = string().min(1);
5833
+ const positive = number().positive();
5834
+ const denialBudgetSchema = strictObject({
5835
+ consecutive: number().int().positive().optional(),
5836
+ perSession: number().int().positive().optional()
5837
+ });
5838
+ const configFileSchema = strictObject({
5839
+ classifiers: record(text, unknown()).optional(),
5840
+ scopeSources: record(text, unknown()).optional(),
5841
+ decision: strictObject({
5842
+ classifier: text.optional(),
5843
+ judge: text.nullable().optional(),
5844
+ minConfidence: number().min(.5).max(1).optional(),
5845
+ onFailure: _enum(["defer", "deny"]).optional(),
5846
+ denialBudget: denialBudgetSchema.optional()
5847
+ }).optional(),
5848
+ policy: strictObject({
5849
+ rulesPath: text.nullable().optional(),
5850
+ frameworkPath: text.nullable().optional(),
5851
+ claudeSettingsPath: text.nullable().optional()
5852
+ }).optional()
5853
+ });
5854
+ function buildConfig(json, path, legacyWarnings) {
5708
5855
  const parsed = configFileSchema.safeParse(json);
5709
5856
  if (!parsed.success) throw new Error(`${path} is not a valid config: ${prettifyError(parsed.error)}`);
5710
- return merge(parsed.data, path);
5711
- }
5712
- function merge(file, path) {
5713
- const implicitProvider = file.provider !== void 0 && file.provider.protocol !== "system-one" ? SPARK_PROVIDER : DEFAULT_CONFIG.provider;
5714
- const preset = file.preset === void 0 ? implicitProvider : PRESETS[file.preset];
5715
- if (preset === void 0) throw new Error(`${path} names an unknown preset '${file.preset}'; known presets are ${Object.keys(PRESETS).join(", ")}`);
5716
- const override = file.provider ?? {};
5857
+ const file = parsed.data;
5858
+ const warnings = [...legacyWarnings];
5859
+ const classifiers = buildRegistry(file.classifiers ?? {}, (id, entry) => parseClassifierEntry(id, entry));
5860
+ const scopeSources = buildRegistry(file.scopeSources ?? {}, (id, entry) => parseScopeSourceEntry(id, entry));
5861
+ warnings.push(...classifiers.dropped.map((line) => `${path}: classifiers.${line}`), ...scopeSources.dropped.map((line) => `${path}: scopeSources.${line}`));
5862
+ const decision = file.decision ?? {};
5863
+ const policy = file.policy ?? {};
5864
+ const judgeID = decision.judge ?? null;
5717
5865
  return {
5718
- provider: {
5719
- protocol: override.protocol ?? preset.protocol ?? "messages",
5720
- baseURL: override.baseURL ?? preset.baseURL,
5721
- model: override.model ?? preset.model,
5722
- apiKeyEnv: override.apiKeyEnv ?? preset.apiKeyEnv,
5723
- apiKeyCommand: override.apiKeyCommand ?? preset.apiKeyCommand,
5724
- reasoning: override.reasoning ?? preset.reasoning,
5725
- maxTokens: override.maxTokens ?? preset.maxTokens,
5726
- timeoutMs: override.timeoutMs ?? preset.timeoutMs
5866
+ provider: resolveRole("classifier", decision.classifier ?? "jev", classifiers.entries, classifiers.droppedIDs, path),
5867
+ judge: judgeID === null ? null : resolveRole("judge", judgeID, classifiers.entries, classifiers.droppedIDs, path),
5868
+ scopeSources: scopeSources.entries,
5869
+ classifierPath: expandHomePath(policy.frameworkPath ?? void 0),
5870
+ rulesPath: expandHomePath(policy.rulesPath ?? void 0),
5871
+ onFailure: decision.onFailure ?? DEFAULT_CONFIG.onFailure,
5872
+ claudeSettingsPath: policy.claudeSettingsPath === null ? null : expandHomePath(policy.claudeSettingsPath),
5873
+ minConfidence: decision.minConfidence ?? DEFAULT_CONFIG.minConfidence,
5874
+ denialBudget: {
5875
+ consecutive: decision.denialBudget?.consecutive ?? DEFAULT_DENIAL_BUDGET.consecutive,
5876
+ perSession: decision.denialBudget?.perSession ?? DEFAULT_DENIAL_BUDGET.perSession
5727
5877
  },
5728
- classifierPath: file.classifierPath,
5729
- rulesPath: file.rulesPath,
5730
- onFailure: file.onFailure ?? DEFAULT_CONFIG.onFailure,
5731
- claudeSettingsPath: file.claudeSettingsPath,
5732
- minConfidence: file.minConfidence ?? DEFAULT_CONFIG.minConfidence
5878
+ warnings
5733
5879
  };
5734
5880
  }
5881
+ function buildRegistry(raw, parseEntry) {
5882
+ const entries = /* @__PURE__ */ new Map();
5883
+ const dropped = [];
5884
+ const droppedIDs = [];
5885
+ for (const [id, entry] of Object.entries(raw)) {
5886
+ const result = parseEntry(id, entry);
5887
+ if (typeof result === "string") {
5888
+ dropped.push(`${id} dropped: ${result}`);
5889
+ droppedIDs.push(id);
5890
+ } else entries.set(id, result);
5891
+ }
5892
+ return {
5893
+ entries: Object.fromEntries(entries),
5894
+ dropped,
5895
+ droppedIDs
5896
+ };
5897
+ }
5898
+ const CLASSIFIER_KINDS = [...Object.keys(PRESETS), "messages"];
5899
+ const classifierEntrySchema = strictObject({
5900
+ kind: text.optional(),
5901
+ baseURL: text.optional(),
5902
+ model: text.optional(),
5903
+ apiKeyEnv: text.optional(),
5904
+ apiKeyCommand: text.optional(),
5905
+ reasoning: boolean().optional(),
5906
+ maxTokens: positive.optional(),
5907
+ timeoutMs: positive.optional()
5908
+ });
5909
+ const CREDENTIAL_KEYS = /* @__PURE__ */ new Set([
5910
+ "apiKey",
5911
+ "key",
5912
+ "token",
5913
+ "secret"
5914
+ ]);
5915
+ function parseClassifierEntry(id, entry) {
5916
+ if (typeof entry === "object" && entry !== null) {
5917
+ const credential = Object.keys(entry).find((key) => CREDENTIAL_KEYS.has(key));
5918
+ if (credential !== void 0) return `holds a literal ${credential}; name the key with apiKeyEnv or apiKeyCommand`;
5919
+ }
5920
+ const parsed = classifierEntrySchema.safeParse(entry);
5921
+ if (!parsed.success) return formatFirstIssue(parsed.error.issues);
5922
+ const kind = parsed.data.kind ?? id;
5923
+ if (!CLASSIFIER_KINDS.includes(kind)) return `unknown kind '${kind}'; known kinds are ${CLASSIFIER_KINDS.join(", ")}`;
5924
+ const base = kind === "messages" ? MESSAGES_DEFAULTS : findPreset(kind);
5925
+ const model = parsed.data.model ?? base?.model;
5926
+ if (base === void 0 || model === void 0) return `kind '${kind}' needs a model`;
5927
+ return {
5928
+ protocol: base.protocol ?? "messages",
5929
+ baseURL: parsed.data.baseURL ?? base.baseURL,
5930
+ model,
5931
+ apiKeyEnv: parsed.data.apiKeyEnv ?? base.apiKeyEnv,
5932
+ apiKeyCommand: parsed.data.apiKeyCommand ?? base.apiKeyCommand,
5933
+ reasoning: parsed.data.reasoning ?? base.reasoning,
5934
+ maxTokens: parsed.data.maxTokens ?? base.maxTokens,
5935
+ timeoutMs: parsed.data.timeoutMs ?? base.timeoutMs
5936
+ };
5937
+ }
5938
+ const scopeSourceEntrySchema = strictObject({
5939
+ kind: text.optional(),
5940
+ paths: array(text).min(1).optional()
5941
+ });
5942
+ function parseScopeSourceEntry(id, entry) {
5943
+ const parsed = scopeSourceEntrySchema.safeParse(entry);
5944
+ if (!parsed.success) return formatFirstIssue(parsed.error.issues);
5945
+ const kind = SCOPE_SOURCE_KINDS.find((known) => known === (parsed.data.kind ?? id));
5946
+ if (kind === void 0) return `unknown kind '${parsed.data.kind ?? id}'; known kinds are ${SCOPE_SOURCE_KINDS.join(", ")}`;
5947
+ if (kind === "globs") return parsed.data.paths === void 0 ? "kind 'globs' needs paths" : {
5948
+ kind,
5949
+ paths: parsed.data.paths
5950
+ };
5951
+ return parsed.data.paths === void 0 ? { kind } : `kind '${kind}' takes no paths`;
5952
+ }
5953
+ function formatFirstIssue(issues) {
5954
+ const [issue] = issues;
5955
+ if (issue === void 0) return "invalid entry";
5956
+ return issue.path.length === 0 ? issue.message : `${issue.path.map(String).join(".")}: ${issue.message}`;
5957
+ }
5958
+ function resolveRole(role, id, entries, droppedIDs, path) {
5959
+ const entry = Object.hasOwn(entries, id) ? entries[id] : void 0;
5960
+ if (entry !== void 0) return entry;
5961
+ if (droppedIDs.includes(id)) throw new Error(`${path}: decision.${role} names '${id}', whose entry was dropped`);
5962
+ if (findPreset(id) === void 0) throw new Error(`${path}: decision.${role} names '${id}', which is neither a classifiers entry nor a built-in kind (${Object.keys(PRESETS).join(", ")})`);
5963
+ return parseBuiltIn(id);
5964
+ }
5965
+ function parseBuiltIn(id) {
5966
+ const resolved = parseClassifierEntry(id, {});
5967
+ if (typeof resolved === "string") throw new TypeError(`built-in kind ${id} does not resolve: ${resolved}`);
5968
+ return resolved;
5969
+ }
5970
+ function expandHomePath(path) {
5971
+ return path?.startsWith("~/") === true ? join(homedir(), path.slice(2)) : path;
5972
+ }
5735
5973
  function resolveApiKey(provider, options = {}) {
5736
5974
  const fromEnv = provider.apiKeyEnv === void 0 ? void 0 : process.env[provider.apiKeyEnv];
5737
5975
  if (fromEnv !== void 0 && fromEnv !== "") return Promise.resolve(fromEnv);
@@ -6980,6 +7218,7 @@ async function classifyAction(request, config, options = {}) {
6980
7218
  const guidance = config.onFailure === "deny" ? await tryReadDenialGuidance() : null;
6981
7219
  return {
6982
7220
  verdict: config.onFailure === "deny" ? buildGuidedDeny("Classifier Unavailable", "Claude settings unreadable.", guidance) : null,
7221
+ decidingStage: "jev",
6983
7222
  note: "Claude settings unreadable; classifier unavailable",
6984
7223
  status: "failure",
6985
7224
  unavailable: true
@@ -6988,17 +7227,20 @@ async function classifyAction(request, config, options = {}) {
6988
7227
  const local = configured !== null && (configured.hard_deny.length > 0 || configured.soft_deny.length > 0) ? { kind: "escalate" } : classifyLocally(request);
6989
7228
  if (local.kind === "allow") return {
6990
7229
  verdict: { kind: "allow" },
7230
+ decidingStage: "local",
6991
7231
  note: `allowed by ${local.exception} (local)`,
6992
7232
  status: "allow"
6993
7233
  };
6994
7234
  if (options.localOnly === true) return {
6995
7235
  verdict: null,
7236
+ decidingStage: "local",
6996
7237
  note: `${request.toolName} needs the model tier, which this run skipped`,
6997
7238
  status: "skipped"
6998
7239
  };
6999
7240
  const [outcome, guidance] = await Promise.all([classifyWithModel(request, config, options), tryReadDenialGuidance()]);
7000
7241
  return {
7001
7242
  ...outcome,
7243
+ decidingStage: config.provider.protocol === "system-one" ? "jev" : "messages",
7002
7244
  verdict: outcome.verdict?.kind === "deny" ? buildGuidedDeny(outcome.verdict.rule, outcome.verdict.reason, guidance) : outcome.verdict,
7003
7245
  status: outcome.diagnostics?.status ?? outcome.verdict?.kind ?? "failure"
7004
7246
  };
@@ -7076,4 +7318,4 @@ function renderVerdict(verdict) {
7076
7318
  })).exhaustive();
7077
7319
  }
7078
7320
  //#endregion
7079
- export { classifyWithModel as a, DEFAULT_CONFIG as c, resolveApiKey as d, resolveConfigPath as f, classifyLocally as i, PRESETS as l, parseActionRequest as n, parseModelVerdict as o, classifyAction as r, loadPolicy as s, renderVerdict as t, loadConfig as u };
7321
+ export { string as _, classifyWithModel as a, DEFAULT_CONFIG as c, resolveApiKey as d, resolveConfigPath as f, strictObject as g, number as h, classifyLocally as i, DEFAULT_DENIAL_BUDGET as l, PRESETS as m, parseActionRequest as n, parseModelVerdict as o, normalizeConfigFile as p, classifyAction as r, loadPolicy as s, renderVerdict as t, loadConfig as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-mode",
3
- "version": "0.6.0",
3
+ "version": "0.8.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",