@paleo/alcode 0.5.3 → 0.7.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
@@ -12,6 +12,8 @@ Coding runs can be very long: the caller always runs `alcode` as a background ta
12
12
 
13
13
  If `alcode` is terminated, its signal handlers seal the session file (`status: failed`, `exitReason: terminated`) so it never stays frozen at `running`, then send `SIGTERM` to the coding-agent child, giving it a short grace to tear down its own subprocesses before a `SIGKILL` backstop guarantees no orphan is left behind. Only a `SIGKILL` of `alcode` itself (uncatchable) can leave a stale `running` status.
14
14
 
15
+ When the coding agent's own session on the host is missing or expired, `alcode` detects the `authentication_failed` signal in its stream, seals the session file with `exitReason: auth_required`, and exits `2` (distinct from `1`) with a one-line stderr message.
16
+
15
17
  ## Usage
16
18
 
17
19
  ```bash
package/dist/cli.js CHANGED
@@ -5,6 +5,9 @@ import { renderGuide } from "./guide.js";
5
5
  import { assertPlansGate, listSessionRecords, resolveSessionFilePath, writeInitialSessionFile, } from "./session-file.js";
6
6
  import { buildPrompt, PROTOCOLS } from "./prompt.js";
7
7
  import { runClaude } from "./run-claude.js";
8
+ // Distinct from 1 (ordinary run failure) so a script can branch on an auth failure that needs an
9
+ // operator re-login rather than a retry.
10
+ const EXIT_AUTH_REQUIRED = 2;
8
11
  export async function main(options) {
9
12
  const argv = options?.argv ?? process.argv;
10
13
  const stdout = options?.stdout ?? process.stdout;
@@ -73,6 +76,11 @@ async function runSession(parsed, ctx) {
73
76
  if (parsed.isNew && result.sessionId) {
74
77
  stdout.write(`\nSession ID: ${result.sessionId}\n`);
75
78
  }
79
+ if (result.authRequired) {
80
+ stderr.write("alcode: coding agent not authenticated — an administrator must re-login on the host " +
81
+ "(`claude`, then `/login`).\n");
82
+ return EXIT_AUTH_REQUIRED;
83
+ }
76
84
  return result.status === "succeeded" ? 0 : 1;
77
85
  }
78
86
  // Fail-fast launch guards, run against the (healed) session records before anything is written.
@@ -15,6 +15,7 @@ export interface RunOutput {
15
15
  }
16
16
  export interface RunResult {
17
17
  status: "succeeded" | "failed";
18
+ authRequired: boolean;
18
19
  sessionId: string | null;
19
20
  result: string;
20
21
  }
@@ -26,6 +27,7 @@ export interface StreamState {
26
27
  sessionId?: string;
27
28
  result?: string;
28
29
  isError: boolean;
30
+ authFailed: boolean;
29
31
  }
30
32
  export declare function createStreamState(): StreamState;
31
33
  export declare function parseEventLine(line: string): unknown;
@@ -11,15 +11,25 @@ export async function runClaude(config, out) {
11
11
  const state = createStreamState();
12
12
  const outcome = await spawnClaude(config, state, out);
13
13
  const failed = state.isError || outcome.exitCode !== 0 || state.result === undefined;
14
- const result = state.result ?? failureMessage(outcome);
14
+ // An auth signal only matters when the run actually failed: claude retries a 401, so a signal it
15
+ // recovered from (run still succeeded) is not a problem to report.
16
+ const authRequired = failed && state.authFailed;
17
+ const result = authRequired
18
+ ? authFailureMessage(state.result)
19
+ : (state.result ?? failureMessage(outcome));
15
20
  applyCompletion(config.sessionFilePath, {
16
21
  status: failed ? "failed" : "succeeded",
17
22
  endedAt: new Date().toISOString(),
18
- exitReason: failed ? "error" : "completed",
23
+ exitReason: authRequired ? "auth_required" : failed ? "error" : "completed",
19
24
  sessionId: state.sessionId ?? null,
20
25
  result,
21
26
  });
22
- return { status: failed ? "failed" : "succeeded", sessionId: state.sessionId ?? null, result };
27
+ return {
28
+ status: failed ? "failed" : "succeeded",
29
+ authRequired,
30
+ sessionId: state.sessionId ?? null,
31
+ result,
32
+ };
23
33
  }
24
34
  function spawnClaude(config, state, out) {
25
35
  const child = spawn("claude", buildClaudeArgs(config), {
@@ -167,7 +177,7 @@ function emitEvent(sessionFilePath, line, state, out) {
167
177
  out.write(`${rendered}\n`);
168
178
  }
169
179
  export function createStreamState() {
170
- return { isError: false };
180
+ return { isError: false, authFailed: false };
171
181
  }
172
182
  export function parseEventLine(line) {
173
183
  try {
@@ -183,6 +193,7 @@ export function renderEvent(event, state) {
183
193
  if (!isRecord(event))
184
194
  return;
185
195
  captureSessionId(event, state);
196
+ captureAuthFailure(event, state);
186
197
  switch (event.type) {
187
198
  case "system":
188
199
  return event.subtype === "init" ? `[init] session ${asString(event.session_id)}` : undefined;
@@ -204,6 +215,10 @@ function captureSessionId(event, state) {
204
215
  if (id)
205
216
  state.sessionId = id;
206
217
  }
218
+ function captureAuthFailure(event, state) {
219
+ if (event.error === "authentication_failed")
220
+ state.authFailed = true;
221
+ }
207
222
  function captureResult(event, state) {
208
223
  state.isError = event.is_error === true;
209
224
  const result = asString(event.result);
@@ -248,6 +263,15 @@ function failureMessage(outcome) {
248
263
  const stderr = outcome.stderr.trim();
249
264
  return stderr || `claude exited with code ${outcome.exitCode ?? "unknown"}`;
250
265
  }
266
+ // The remedy is an operator action on the host, not a retry — so the result block names it and
267
+ // keeps claude's own reason line (e.g. "OAuth session expired…") beneath it.
268
+ function authFailureMessage(claudeReason) {
269
+ const base = "Coding agent not authenticated (authentication_failed): the host session is missing, expired, " +
270
+ "or rejected. An administrator must re-login on the host (run `claude`, then `/login`) before " +
271
+ "alcode can run again.";
272
+ const detail = claudeReason?.trim();
273
+ return detail ? `${base}\n\n${detail}` : base;
274
+ }
251
275
  // --- Shared helpers ---
252
276
  function isRecord(value) {
253
277
  return typeof value === "object" && value !== null;
@@ -19,7 +19,7 @@ export declare function writeInitialSessionFile(sessionFilePath: string, frontma
19
19
  export interface CompletionUpdate {
20
20
  status: "succeeded" | "failed";
21
21
  endedAt: string;
22
- exitReason: "completed" | "error" | "terminated";
22
+ exitReason: "completed" | "error" | "terminated" | "auth_required";
23
23
  sessionId: string | null;
24
24
  result: string;
25
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paleo/alcode",
3
- "version": "0.5.3",
3
+ "version": "0.7.0",
4
4
  "license": "CC0-1.0",
5
5
  "author": "Thomas MUR",
6
6
  "description": "Run a coding agent through AlignFirst protocols.",
@@ -48,11 +48,20 @@ alcode --new --protocol aad --ticket AB-123 --message "Task description"
48
48
 
49
49
  Answer questions as in the spec flow. The agent implements and writes a summary file, which carries a suggested commit message.
50
50
 
51
+ ## Review workflow
52
+
53
+ Two fresh sessions: one reviews, one fixes.
54
+
55
+ 1. **Review** — `alcode --new --protocol review --ticket AB-123`. The agent reviews the current branch against the base branch and writes a review file; its path is in the run's result. The base defaults to the repository's default branch; override it via `--message "Base branch: \`develop\`"`.
56
+ 2. **Fix** (optional) — `alcode --new --protocol aad --ticket AB-123 --message "Here is a code review: \`.plans/AB-123/B1-review.md\`. What should we fix?"` (adapt the path). The agent proposes fixes; decide together what to fix, as in any AAD session, then it implements and writes a summary file.
57
+
58
+ Skip the fix step when the review is informational.
59
+
51
60
  ## Other protocols
52
61
 
53
62
  - **description** — `alcode --new --protocol description --ticket AB-123`. Writes a PR/MR description for committed work. No discussion.
54
63
  - **read** — `alcode --new --protocol read --ticket AB-123 [--message "..."]`. Loads the ticket's spec and summary files into context; with a message, answers it against that context.
55
- - **review** — `alcode --new --protocol review --ticket AB-123`. Reviews the current branch against the base and writes a review file.
64
+ - **review** — see the review workflow above.
56
65
  - **merge** — `alcode --new --protocol merge --ticket AB-123`. Resolves conflicts and summarizes tricky resolutions. Pass the incoming branch via `--message` to start the merge.
57
66
 
58
67
  ## Answering agent questions
@@ -14,6 +14,8 @@ Running under OpenClaw? Read `alcode --openclaw-guide` instead — the same manu
14
14
 
15
15
  Read the run's session file (the path `alcode` prints on its first line, under `_alcode/`): its frontmatter carries `status` (`succeeded`/`failed`) and the `sessionId`, and the `---- Result ----` block holds the outcome. Report it to the user where the work was requested. If the run failed, say so plainly and propose the next step.
16
16
 
17
+ An `exitReason` of `auth_required` in the frontmatter (alcode also exits `2`) means the coding agent is not authenticated on the host: an administrator must re-login there before any run can succeed. Report that and do not retry.
18
+
17
19
  Do **not** re-verify the repo, re-run the agent, or inspect `git` — the session file is authoritative. Keep session files in place; they are the durable audit trail.
18
20
 
19
21
  {{CLI_REFERENCE}}
@@ -45,4 +45,6 @@ Any heartbeat received while an `alcode` run is **still pending** (running, or f
45
45
 
46
46
  If the session file says the run failed, report that plainly and propose the next step; don't silently retry. When a session turns bad, keep everything in place — session files, directories, and records are the durable audit trail; never delete them; just start a new session.
47
47
 
48
+ If the frontmatter's `exitReason` is `auth_required`, the coding agent itself is not authenticated on the host (its session is missing or expired). No run can succeed until an administrator re-logs it in on the host. Tell the user exactly that, and do not retry — a retry hits the same wall.
49
+
48
50
  {{CLI_REFERENCE}}