auto-mode 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { C as resolveXDGDir, D as string, E as strictObject, S as CLI_LIMITS, T as number, _ as findGitEntry, a as EMPTY_SESSION_SCOPE, b as readHostEnvironment, c as loadPolicy, d as writeActionDiagnostic, f as toRepositorySlug, g as findCheckout, h as isUnresolvedWord, i as loadSessionScope, l as resolveShippedPolicyDir, m as resolveShellPath, n as classifyAction, o as splitGitCommand, p as collectShellCommands, r as resolveSessionScopePath, s as M, t as parseActionRequest, u as loadCheckoutFacts, v as DEFAULT_SCOPE_SOURCES, w as updateJSONFile, x as runCapped, y as loadConfig } from "./parse-action-request-BfkmQxEC.js";
3
- import { randomUUID } from "node:crypto";
2
+ import { C as resolveXDGDir, S as CLI_LIMITS, _ as findGitEntry, a as EMPTY_SESSION_SCOPE, b as readHostEnvironment, c as loadPolicy, d as writeActionDiagnostic, f as toRepositorySlug, g as findCheckout, h as isUnresolvedWord, i as loadSessionScope, l as resolveShippedPolicyDir, m as resolveShellPath, n as classifyAction, o as splitGitCommand, p as collectShellCommands, r as resolveSessionScopePath, s as M, t as parseActionRequest, u as loadCheckoutFacts, v as DEFAULT_SCOPE_SOURCES, w as updateJSONFile, x as runCapped, y as loadConfig } from "./parse-action-request-DtReuNOc.js";
3
+ import { a as literal, d as string, i as discriminatedUnion, r as boolean, s as number, u as strictObject } from "./schemas-Dc2424NY.js";
4
+ import { createHash, randomUUID } from "node:crypto";
4
5
  import { chmod, mkdir, open, readFile, realpath, stat } from "node:fs/promises";
5
6
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
6
- import { constants, existsSync } from "node:fs";
7
+ import { setTimeout } from "node:timers/promises";
8
+ import { spawn } from "node:child_process";
9
+ import { constants, existsSync, statSync } from "node:fs";
7
10
  import { text } from "node:stream/consumers";
8
11
  import { parseArgs } from "node:util";
12
+ import { connect } from "node:net";
9
13
  //#region src/capture/resolve-capture-dir.ts
10
14
  function resolveCaptureDir(capture, host) {
11
15
  return capture.dir ?? join(resolveXDGDir(host, "state"), "captures");
@@ -73,7 +77,6 @@ async function writeOwnerFile(path, text, flag) {
73
77
  //#endregion
74
78
  //#region src/config/build-jev-only-config.ts
75
79
  function buildJevOnlyConfig(config) {
76
- if (config.provider.protocol !== "system-one") return null;
77
80
  const judge = config.judge === void 0 || config.judge === null ? config.judge : {
78
81
  ...config.judge,
79
82
  timeoutMs: Math.min(config.judge.timeoutMs, CLI_LIMITS.judgeMs)
@@ -95,6 +98,117 @@ function resolveShippedModDir(startDir = import.meta.dirname) {
95
98
  return modDir;
96
99
  }
97
100
  //#endregion
101
+ //#region src/process/start-detached-process.ts
102
+ function startDetachedProcess(file, args, env) {
103
+ const child = spawn(file, args, {
104
+ detached: true,
105
+ stdio: "ignore",
106
+ env
107
+ });
108
+ child.on("error", () => {});
109
+ child.unref();
110
+ }
111
+ //#endregion
112
+ //#region src/judge/judge-server-response-schema.ts
113
+ const judgeServerResponseSchema = discriminatedUnion("kind", [strictObject({
114
+ kind: literal("reply"),
115
+ text: string(),
116
+ model: string(),
117
+ safetyStops: number().nullable(),
118
+ fallback: boolean(),
119
+ warm: boolean().nullable()
120
+ }), strictObject({
121
+ kind: literal("failure"),
122
+ aborted: boolean(),
123
+ message: string()
124
+ })]);
125
+ //#endregion
126
+ //#region src/judge/make-judge-server-session.ts
127
+ function makeJudgeServerSession(target) {
128
+ return {
129
+ review: async (model, request, signal) => {
130
+ const answer = await sendLine(await tryConnectOrStartServer(target, signal), `${JSON.stringify({
131
+ model,
132
+ timeoutMs: target.timeoutMs,
133
+ ...request
134
+ })}\n`, signal);
135
+ const response = judgeServerResponseSchema.parse(JSON.parse(answer));
136
+ if (response.kind === "failure") throw response.aborted ? new DOMException(response.message, "AbortError") : new Error(response.message);
137
+ const { kind: _kind, ...reply } = response;
138
+ return reply;
139
+ },
140
+ stop: () => {}
141
+ };
142
+ }
143
+ const START_WAIT_MS = 1e4;
144
+ const START_POLL_MS = 25;
145
+ async function tryConnectOrStartServer(target, signal) {
146
+ const existing = await tryConnect(target.socketPath);
147
+ if (existing !== null) return existing;
148
+ const [file, ...args] = target.command;
149
+ startDetachedProcess(file, [
150
+ ...args,
151
+ "judge-server",
152
+ target.socketPath
153
+ ], target.env);
154
+ const deadline = Date.now() + START_WAIT_MS;
155
+ while (Date.now() < deadline) {
156
+ await setTimeout(START_POLL_MS, void 0, { signal });
157
+ const socket = await tryConnect(target.socketPath);
158
+ if (socket !== null) return socket;
159
+ }
160
+ throw new Error("the judge server did not start");
161
+ }
162
+ function tryConnect(socketPath) {
163
+ return new Promise((resolve) => {
164
+ const socket = connect(socketPath);
165
+ socket.once("connect", () => {
166
+ socket.off("error", onError);
167
+ resolve(socket);
168
+ });
169
+ const onError = () => {
170
+ resolve(null);
171
+ };
172
+ socket.once("error", onError);
173
+ });
174
+ }
175
+ function sendLine(socket, line, signal) {
176
+ return new Promise((resolve, reject) => {
177
+ const chunks = [];
178
+ const onAbort = () => {
179
+ socket.destroy();
180
+ reject(new DOMException("Judge review aborted", "AbortError"));
181
+ };
182
+ if (signal.aborted) {
183
+ onAbort();
184
+ return;
185
+ }
186
+ signal.addEventListener("abort", onAbort, { once: true });
187
+ socket.on("data", (chunk) => {
188
+ chunks.push(chunk);
189
+ });
190
+ socket.once("error", (error) => {
191
+ signal.removeEventListener("abort", onAbort);
192
+ reject(error);
193
+ });
194
+ socket.once("end", () => {
195
+ signal.removeEventListener("abort", onAbort);
196
+ socket.destroy();
197
+ resolve(Buffer.concat(chunks).toString("utf8").trim());
198
+ });
199
+ socket.write(line);
200
+ });
201
+ }
202
+ //#endregion
203
+ //#region src/judge/resolve-judge-socket-path.ts
204
+ function resolveJudgeSocketPath(host, sessionID, entry) {
205
+ const runtime = host.env["XDG_RUNTIME_DIR"];
206
+ const dir = runtime === void 0 || runtime === "" ? join(resolveXDGDir(host, "state"), "judge") : join(runtime, "auto-mode");
207
+ const build = statSync(entry, { throwIfNoEntry: false });
208
+ const name = createHash("sha256").update(`${sessionID}\0${entry}\0${String(build?.mtimeMs ?? 0)}`).digest("hex").slice(0, 24);
209
+ return join(dir, `judge-${name}.sock`);
210
+ }
211
+ //#endregion
98
212
  //#region src/scope/read-pull-request.ts
99
213
  const LOOKUP_TIMEOUT_MS = 5e3;
100
214
  const MAX_LOOKUP_OUTPUT_BYTES = 65536;
@@ -419,6 +533,7 @@ Usage:
419
533
  auto-mode print-prompt Print the system prompt the classifier receives
420
534
  auto-mode record Read a finished Bash call on stdin, add what it created to the session's scope
421
535
  auto-mode mod-path Print the directory of the Claude Code mod this package ships
536
+ auto-mode judge-server <socket> Serve one agent session's judge reviews on a unix socket
422
537
 
423
538
  Options:
424
539
  --classifier <path> Use this framework file instead of the shipped one
@@ -449,14 +564,14 @@ async function runCLI(argv, io) {
449
564
  if (command === "print-prompt") {
450
565
  const config = await loadConfig(void 0, io.host);
451
566
  printWarnings(io.stderr, config.warnings);
452
- const framework = config.provider.protocol === "system-one" ? "decision.md" : "classifier.md";
453
567
  const prompt = await loadPolicy({
454
568
  classifierPath: args.values.classifier ?? config.classifierPath,
455
569
  rulesPath: args.values.rules ?? config.rulesPath
456
- }, framework);
570
+ }, "decision.md");
457
571
  io.stdout.write(`${prompt}\n`);
458
572
  return 0;
459
573
  }
574
+ if (command === "judge-server") return runJudgeServer(io, args.positionals[1]);
460
575
  if (command === "mod-path") try {
461
576
  io.stdout.write(`${resolveShippedModDir()}\n`);
462
577
  return 0;
@@ -467,6 +582,31 @@ async function runCLI(argv, io) {
467
582
  io.stderr.write(`auto-mode: unknown command '${command}'\n\n${USAGE}`);
468
583
  return 2;
469
584
  }
585
+ function buildJudgeServerSession(io, config, sessionID) {
586
+ const judge = config.judge ?? null;
587
+ if (io.judgeServerCommand === void 0 || judge?.protocol !== "claude-code") return;
588
+ const command = io.judgeServerCommand;
589
+ const entry = command[1] ?? command[0];
590
+ return makeJudgeServerSession({
591
+ socketPath: resolveJudgeSocketPath(io.host, sessionID, entry),
592
+ command: io.judgeServerCommand,
593
+ env: io.host.env,
594
+ timeoutMs: judge.timeoutMs
595
+ });
596
+ }
597
+ async function runJudgeServer(io, socketPath) {
598
+ if (socketPath === void 0) {
599
+ io.stderr.write(`auto-mode: judge-server needs a socket path\n\n${USAGE}`);
600
+ return 2;
601
+ }
602
+ await (await (await import("./start-judge-server-Drj5c8js.js")).startJudgeServer({
603
+ socketPath,
604
+ env: io.host.env,
605
+ idleMs: JUDGE_IDLE_MS
606
+ }))?.closed;
607
+ return 0;
608
+ }
609
+ const JUDGE_IDLE_MS = 9e5;
470
610
  function tryParseCLIArgs(argv) {
471
611
  try {
472
612
  return parseArgs({
@@ -509,10 +649,6 @@ async function run(io, explain, localOnly, jevOnly, deadline) {
509
649
  }
510
650
  printWarnings(io.stderr, loaded.warnings);
511
651
  const config = jevOnly ? buildJevOnlyConfig(loaded) : loaded;
512
- if (config === null) {
513
- await writeActionFailure(request, host, io.stderr);
514
- return printNote(io.stderr, true, "Jev-only evaluation requires system-one; no verdict");
515
- }
516
652
  const deadlineAt = deadline === void 0 ? void 0 : Number(deadline);
517
653
  if (deadlineAt !== void 0 && (!jevOnly || !Number.isSafeInteger(deadlineAt) || deadlineAt <= 0)) {
518
654
  await writeActionFailure(request, host, io.stderr);
@@ -530,7 +666,8 @@ async function run(io, explain, localOnly, jevOnly, deadline) {
530
666
  host,
531
667
  stderr: io.stderr,
532
668
  budget: true,
533
- actionLog: true
669
+ actionLog: true,
670
+ judgeSession: buildJudgeServerSession(io, config, request.sessionID)
534
671
  });
535
672
  if (outcome.verdict !== null) printVerdict(io.stdout, outcome.verdict);
536
673
  if (config.capture?.enabled === true) {
@@ -594,7 +731,8 @@ process.exitCode = await runCLI(process.argv.slice(2), {
594
731
  process.off("SIGTERM", onStop);
595
732
  process.off("SIGINT", onStop);
596
733
  };
597
- }
734
+ },
735
+ judgeServerCommand: [process.execPath, import.meta.filename]
598
736
  });
599
737
  //#endregion
600
738
  export {};
package/dist/index.d.ts CHANGED
@@ -4,10 +4,27 @@ interface DenialBudget {
4
4
  readonly perSession: number;
5
5
  }
6
6
  //#endregion
7
+ //#region src/messages/anthropic-client.d.ts
8
+ interface ModelRequest {
9
+ readonly system: string;
10
+ readonly user: string;
11
+ }
12
+ //#endregion
7
13
  //#region src/judge/types.d.ts
8
14
  type OverturnBasis = 'consent' | 'misread';
9
15
  type JudgeOverturns = 'consent' | 'any';
10
16
  type JudgeStatus = 'confirmed' | 'overturned' | 'held' | 'failed';
17
+ interface JudgeMessageReply {
18
+ readonly text: string;
19
+ readonly model: string;
20
+ readonly safetyStops: number | null;
21
+ readonly fallback: boolean;
22
+ readonly warm: boolean | null;
23
+ }
24
+ interface JudgeSession {
25
+ readonly review: (model: string, request: Readonly<ModelRequest>, signal: Readonly<AbortSignal>) => Promise<JudgeMessageReply>;
26
+ readonly stop: () => void;
27
+ }
11
28
  type JudgeFailure = 'credential' | 'policy' | 'request' | 'timeout' | 'unreadable';
12
29
  interface JudgeDiagnostics {
13
30
  readonly status: JudgeStatus;
@@ -16,13 +33,14 @@ interface JudgeDiagnostics {
16
33
  readonly model: string;
17
34
  readonly answeringModel: string | null;
18
35
  readonly safetyStops: number | null;
36
+ readonly warm: boolean | null;
19
37
  readonly rule: string;
20
38
  readonly tier: 'hard' | 'soft';
21
39
  readonly elapsedMs: number;
22
40
  }
23
41
  //#endregion
24
42
  //#region src/types.d.ts
25
- type DecidingStage = 'local' | 'containment' | 'bypass' | 'jev' | 'judge' | 'messages' | 'retry' | 'budget';
43
+ type DecidingStage = 'local' | 'containment' | 'bypass' | 'jev' | 'judge' | 'retry' | 'budget';
26
44
  interface DecisionRule {
27
45
  readonly name: string;
28
46
  readonly tier: 'hard' | 'soft';
@@ -216,6 +234,7 @@ interface ClassifyOptions extends EvaluationOptions {
216
234
  readonly actionLog?: boolean;
217
235
  readonly stderr?: Readonly<OutputStream> | undefined;
218
236
  readonly findCheckout?: CheckoutFinder | undefined;
237
+ readonly judgeSession?: JudgeSession | undefined;
219
238
  }
220
239
  export declare function classifyAction(request: ActionRequest, config: Config, options?: ClassifyOptions): Promise<ActionOutcome>;
221
240
  //#endregion
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as classifyAction, t as parseActionRequest, y as loadConfig } from "./parse-action-request-BfkmQxEC.js";
1
+ import { n as classifyAction, t as parseActionRequest, y as loadConfig } from "./parse-action-request-DtReuNOc.js";
2
2
  export { classifyAction, loadConfig, parseActionRequest };