ai-foreman 1.0.9 → 1.0.10
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 +2 -1
- package/dist/adapters/claude.js +14 -4
- package/dist/adapters/codex.js +4 -0
- package/dist/cli/runtimeAuthPrompt.d.ts +2 -0
- package/dist/cli/runtimeAuthPrompt.js +27 -0
- package/dist/cli/start.js +5 -2
- package/dist/cli/tickets.js +2 -0
- package/dist/runtimeAuth.d.ts +23 -0
- package/dist/runtimeAuth.js +116 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -145,12 +145,14 @@ Claude Code:
|
|
|
145
145
|
- Default agent.
|
|
146
146
|
- Uses your existing Claude Code credentials.
|
|
147
147
|
- Runs through the Claude Agent SDK.
|
|
148
|
+
- `start` and `tickets populate` check Claude auth before launching the builder and prompt you to repair it if needed.
|
|
148
149
|
|
|
149
150
|
Codex:
|
|
150
151
|
|
|
151
152
|
- Use `--agent codex`.
|
|
152
153
|
- Shells out to `codex exec`.
|
|
153
154
|
- Requires the `codex` CLI on your `PATH`.
|
|
155
|
+
- `start` and `tickets populate --agent codex` check Codex auth before launching the builder and prompt you to repair it if needed.
|
|
154
156
|
|
|
155
157
|
Task file:
|
|
156
158
|
|
|
@@ -522,4 +524,3 @@ Package output:
|
|
|
522
524
|
- **`@rafi-ai/cli`** — CLI for `rafi create` and `rafi compile`
|
|
523
525
|
|
|
524
526
|
|
|
525
|
-
|
package/dist/adapters/claude.js
CHANGED
|
@@ -11,6 +11,7 @@ export async function requireClaudeSDK() {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
import { AsyncQueue } from "../util/asyncQueue.js";
|
|
14
|
+
import { normalizeRuntimeErrorText } from "../runtimeAuth.js";
|
|
14
15
|
/**
|
|
15
16
|
* Pure function: build the `options` object passed to `query()`.
|
|
16
17
|
* Extracted so tests can assert on the shape without making a live SDK call.
|
|
@@ -50,8 +51,14 @@ export class ClaudeAdapter {
|
|
|
50
51
|
pending;
|
|
51
52
|
closed = false;
|
|
52
53
|
static async create(opts) {
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
try {
|
|
55
|
+
const { query } = await requireClaudeSDK();
|
|
56
|
+
return new ClaudeAdapter(opts, query);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
60
|
+
throw new Error(normalizeRuntimeErrorText("claude", message, null, "adapter startup"), { cause: err });
|
|
61
|
+
}
|
|
55
62
|
}
|
|
56
63
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
64
|
constructor(opts, query) {
|
|
@@ -83,7 +90,8 @@ export class ClaudeAdapter {
|
|
|
83
90
|
(err instanceof Error &&
|
|
84
91
|
(err.name === "AbortError" || err.message.includes("aborted")));
|
|
85
92
|
if (!isShutdownAbort) {
|
|
86
|
-
const
|
|
93
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
94
|
+
const message = normalizeRuntimeErrorText("claude", rawMessage, null, "builder stream");
|
|
87
95
|
this.eventQueue.push({ kind: "error", message });
|
|
88
96
|
this.pending?.reject(new Error(message));
|
|
89
97
|
this.pending = undefined;
|
|
@@ -118,7 +126,9 @@ export class ClaudeAdapter {
|
|
|
118
126
|
? msg.errors.join("; ")
|
|
119
127
|
: "";
|
|
120
128
|
const result = {
|
|
121
|
-
text
|
|
129
|
+
text: msg.is_error
|
|
130
|
+
? normalizeRuntimeErrorText("claude", text, null, "builder turn")
|
|
131
|
+
: text,
|
|
122
132
|
isError: msg.is_error,
|
|
123
133
|
numTurns: msg.num_turns,
|
|
124
134
|
costUsd: msg.total_cost_usd,
|
package/dist/adapters/codex.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { AsyncQueue } from "../util/asyncQueue.js";
|
|
4
|
+
import { normalizeRuntimeErrorText } from "../runtimeAuth.js";
|
|
4
5
|
/**
|
|
5
6
|
* Pure function: parse one JSONL event from `codex exec --json` output into
|
|
6
7
|
* structured actions. Exported for unit testing; production code calls this
|
|
@@ -122,6 +123,9 @@ export class CodexAdapter {
|
|
|
122
123
|
if (!text && code !== 0) {
|
|
123
124
|
text = Buffer.concat(stderrChunks).toString().trim();
|
|
124
125
|
}
|
|
126
|
+
if (code !== 0) {
|
|
127
|
+
text = normalizeRuntimeErrorText("codex", text, code, "builder turn");
|
|
128
|
+
}
|
|
125
129
|
const result = {
|
|
126
130
|
text,
|
|
127
131
|
isError: code !== 0,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { select, isCancel, log } from "@clack/prompts";
|
|
2
|
+
import { checkRuntimeReady, RuntimeAuthError, runtimeCommandLabel, } from "../runtimeAuth.js";
|
|
3
|
+
export async function ensureRuntimeReadyForCommand(projectDir, runtime, label) {
|
|
4
|
+
while (true) {
|
|
5
|
+
try {
|
|
6
|
+
checkRuntimeReady(projectDir, runtime);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
const failure = err instanceof RuntimeAuthError
|
|
11
|
+
? err
|
|
12
|
+
: new RuntimeAuthError({ runtime, context: label, cause: err });
|
|
13
|
+
log.error(failure.message);
|
|
14
|
+
const choice = await select({
|
|
15
|
+
message: `${runtimeCommandLabel(runtime)} is not ready for ${label}. Fix authentication, then retry the check.`,
|
|
16
|
+
options: [
|
|
17
|
+
{ value: "retry", label: "Retry check" },
|
|
18
|
+
{ value: "cancel", label: "Cancel" },
|
|
19
|
+
],
|
|
20
|
+
});
|
|
21
|
+
if (isCancel(choice) || choice === "cancel") {
|
|
22
|
+
console.log("foreman: cancelled");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
package/dist/cli/start.js
CHANGED
|
@@ -10,6 +10,7 @@ import { CodexAdapter } from "../adapters/codex.js";
|
|
|
10
10
|
import { Foreman, createPermissionHandler } from "../foreman.js";
|
|
11
11
|
import { printEvents } from "./events.js";
|
|
12
12
|
import { loadRoleBundle } from "../roles.js";
|
|
13
|
+
import { ensureRuntimeReadyForCommand } from "./runtimeAuthPrompt.js";
|
|
13
14
|
function fail(message) {
|
|
14
15
|
console.error(`foreman: ${message}`);
|
|
15
16
|
process.exit(1);
|
|
@@ -94,6 +95,8 @@ export function buildStartCommand() {
|
|
|
94
95
|
const logPath = join(cwd, ".foreman", `${stamp}.jsonl`);
|
|
95
96
|
const log = new Log(logPath);
|
|
96
97
|
const policy = new PermissionPolicy(config.permissions, cwd);
|
|
98
|
+
const agent = opts.agent;
|
|
99
|
+
await ensureRuntimeReadyForCommand(cwd, agent, "start");
|
|
97
100
|
const roleBundle = loadRoleBundle("builder", { projectDir: cwd });
|
|
98
101
|
const adapterOpts = {
|
|
99
102
|
cwd,
|
|
@@ -105,7 +108,7 @@ export function buildStartCommand() {
|
|
|
105
108
|
systemPromptAppend: roleBundle.system || undefined,
|
|
106
109
|
skills: roleBundle.skills.length > 0 ? roleBundle.skills : undefined,
|
|
107
110
|
};
|
|
108
|
-
const builder =
|
|
111
|
+
const builder = agent === "codex"
|
|
109
112
|
? new CodexAdapter(adapterOpts)
|
|
110
113
|
: await ClaudeAdapter.create(adapterOpts);
|
|
111
114
|
const qaEnabled = opts.qa !== false && config.qa.enabled !== false;
|
|
@@ -116,7 +119,7 @@ export function buildStartCommand() {
|
|
|
116
119
|
opts.fast ? "fast" : null,
|
|
117
120
|
qaEnabled ? null : "qa=off",
|
|
118
121
|
].filter(Boolean).join(" ");
|
|
119
|
-
console.log(`foreman: driving a ${
|
|
122
|
+
console.log(`foreman: driving a ${agent} builder through ${steps} step(s)${modifiers ? ` [${modifiers}]` : ""}`);
|
|
120
123
|
console.log(`foreman: project ${cwd}`);
|
|
121
124
|
if (trackerRelPath)
|
|
122
125
|
console.log(`foreman: tracker ${trackerRelPath}`);
|
package/dist/cli/tickets.js
CHANGED
|
@@ -13,6 +13,7 @@ import { cmdInit, cmdUpdate, cmdComplete, cmdBlock, cmdUnblock, cmdCancel, cmdDi
|
|
|
13
13
|
import { isTicketsInitialized } from "../tickets/config.js";
|
|
14
14
|
import { importFromMarkdown } from "../tickets/importer.js";
|
|
15
15
|
import { formatValidationIssues } from "../tickets/validate.js";
|
|
16
|
+
import { ensureRuntimeReadyForCommand } from "./runtimeAuthPrompt.js";
|
|
16
17
|
function fail(msg) {
|
|
17
18
|
console.error(`foreman tickets: ${msg}`);
|
|
18
19
|
process.exit(1);
|
|
@@ -148,6 +149,7 @@ export function buildTicketsCommand() {
|
|
|
148
149
|
const logPath = makeLogPath(dir, "tickets-populate");
|
|
149
150
|
const log = new Log(logPath);
|
|
150
151
|
const policy = new PermissionPolicy(config.permissions, dir);
|
|
152
|
+
await ensureRuntimeReadyForCommand(dir, agent, "tickets populate");
|
|
151
153
|
const adapterOpts = {
|
|
152
154
|
cwd: dir,
|
|
153
155
|
model: opts.model,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type AgentRuntime = "claude" | "codex";
|
|
2
|
+
export interface RuntimeAuthErrorOptions {
|
|
3
|
+
runtime: AgentRuntime;
|
|
4
|
+
context: string;
|
|
5
|
+
exitCode?: number | null;
|
|
6
|
+
stdout?: string;
|
|
7
|
+
stderr?: string;
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
}
|
|
10
|
+
export declare class RuntimeAuthError extends Error {
|
|
11
|
+
readonly runtime: AgentRuntime;
|
|
12
|
+
readonly exitCode?: number | null;
|
|
13
|
+
readonly stdout: string;
|
|
14
|
+
readonly stderr: string;
|
|
15
|
+
readonly authLikely: boolean;
|
|
16
|
+
constructor(opts: RuntimeAuthErrorOptions);
|
|
17
|
+
}
|
|
18
|
+
export declare function checkRuntimeReady(projectDir: string, runtime: AgentRuntime): void;
|
|
19
|
+
export declare function normalizeRuntimeErrorText(runtime: AgentRuntime, text: string, exitCode?: number | null, context?: string): string;
|
|
20
|
+
export declare function isRuntimeAuthFailure(output: string): boolean;
|
|
21
|
+
export declare function runtimeCommandLabel(runtime: AgentRuntime): string;
|
|
22
|
+
export declare function runtimeRepairCommands(runtime: AgentRuntime): string;
|
|
23
|
+
export declare function formatRuntimeAuthFailure(opts: RuntimeAuthErrorOptions): string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
export class RuntimeAuthError extends Error {
|
|
3
|
+
runtime;
|
|
4
|
+
exitCode;
|
|
5
|
+
stdout;
|
|
6
|
+
stderr;
|
|
7
|
+
authLikely;
|
|
8
|
+
constructor(opts) {
|
|
9
|
+
super(formatRuntimeAuthFailure(opts), { cause: opts.cause });
|
|
10
|
+
this.name = "RuntimeAuthError";
|
|
11
|
+
this.runtime = opts.runtime;
|
|
12
|
+
this.exitCode = opts.exitCode;
|
|
13
|
+
this.stdout = opts.stdout ?? "";
|
|
14
|
+
this.stderr = opts.stderr ?? "";
|
|
15
|
+
this.authLikely = isRuntimeAuthFailure(`${this.stderr}\n${this.stdout}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function checkRuntimeReady(projectDir, runtime) {
|
|
19
|
+
try {
|
|
20
|
+
if (runtime === "claude") {
|
|
21
|
+
execFileSync("claude", ["-p", "Return exactly OK"], {
|
|
22
|
+
cwd: projectDir,
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
execFileSync("codex", ["exec", "-C", projectDir, "Return exactly OK"], {
|
|
29
|
+
cwd: projectDir,
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const failure = err;
|
|
37
|
+
throw new RuntimeAuthError({
|
|
38
|
+
runtime,
|
|
39
|
+
context: "readiness check",
|
|
40
|
+
exitCode: failure.status,
|
|
41
|
+
stdout: outputToString(failure.stdout),
|
|
42
|
+
stderr: outputToString(failure.stderr),
|
|
43
|
+
cause: err,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function normalizeRuntimeErrorText(runtime, text, exitCode, context = "builder turn") {
|
|
48
|
+
if (!isRuntimeAuthFailure(text))
|
|
49
|
+
return text;
|
|
50
|
+
return formatRuntimeAuthFailure({
|
|
51
|
+
runtime,
|
|
52
|
+
context,
|
|
53
|
+
exitCode,
|
|
54
|
+
stderr: text,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export function isRuntimeAuthFailure(output) {
|
|
58
|
+
return [
|
|
59
|
+
/\b401\b/i,
|
|
60
|
+
/invalid authentication credentials/i,
|
|
61
|
+
/not logged in/i,
|
|
62
|
+
/login required/i,
|
|
63
|
+
/unauthenticated/i,
|
|
64
|
+
/unauthorized/i,
|
|
65
|
+
/expired[\w\s-]*token/i,
|
|
66
|
+
/token[\w\s-]*expired/i,
|
|
67
|
+
/session expired/i,
|
|
68
|
+
/authentication.*expired/i,
|
|
69
|
+
].some((pattern) => pattern.test(output));
|
|
70
|
+
}
|
|
71
|
+
export function runtimeCommandLabel(runtime) {
|
|
72
|
+
return runtime === "claude" ? "claude -p" : "codex exec";
|
|
73
|
+
}
|
|
74
|
+
export function runtimeRepairCommands(runtime) {
|
|
75
|
+
if (runtime === "claude") {
|
|
76
|
+
return [
|
|
77
|
+
"claude auth logout",
|
|
78
|
+
"claude auth login --claudeai",
|
|
79
|
+
'claude -p "Return exactly OK"',
|
|
80
|
+
"",
|
|
81
|
+
"Claude subscription users may also need:",
|
|
82
|
+
"claude setup-token",
|
|
83
|
+
].join("\n");
|
|
84
|
+
}
|
|
85
|
+
return [
|
|
86
|
+
"codex login",
|
|
87
|
+
'codex exec "Return exactly OK"',
|
|
88
|
+
].join("\n");
|
|
89
|
+
}
|
|
90
|
+
export function formatRuntimeAuthFailure(opts) {
|
|
91
|
+
const output = [opts.stderr, opts.stdout].filter(Boolean).join("\n").trim();
|
|
92
|
+
const exit = opts.exitCode === undefined || opts.exitCode === null ? "unknown" : String(opts.exitCode);
|
|
93
|
+
const authLine = isRuntimeAuthFailure(output)
|
|
94
|
+
? "The runtime output looks like an authentication failure."
|
|
95
|
+
: "This often means the selected agent runtime is missing or not authenticated.";
|
|
96
|
+
const details = output ? `\n\nRuntime output:\n${truncateRuntimeOutput(output)}` : "";
|
|
97
|
+
return (`${runtimeCommandLabel(opts.runtime)} failed during ${opts.context} (exit code ${exit}).\n\n` +
|
|
98
|
+
`${authLine}\n\n` +
|
|
99
|
+
"Repair and verify:\n" +
|
|
100
|
+
indent(runtimeRepairCommands(opts.runtime)) +
|
|
101
|
+
details);
|
|
102
|
+
}
|
|
103
|
+
function outputToString(value) {
|
|
104
|
+
if (!value)
|
|
105
|
+
return "";
|
|
106
|
+
return Buffer.isBuffer(value) ? value.toString("utf8") : value;
|
|
107
|
+
}
|
|
108
|
+
function truncateRuntimeOutput(output) {
|
|
109
|
+
const max = 2000;
|
|
110
|
+
if (output.length <= max)
|
|
111
|
+
return output;
|
|
112
|
+
return `${output.slice(0, max).trimEnd()}\n... truncated ...`;
|
|
113
|
+
}
|
|
114
|
+
function indent(value) {
|
|
115
|
+
return value.split("\n").map((line) => ` ${line}`).join("\n");
|
|
116
|
+
}
|
package/package.json
CHANGED