@tokenfactory/acc-runner 0.44.0 → 0.44.2
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 +43 -1
- package/dist/bin-resolve.d.ts +39 -3
- package/dist/bin-resolve.d.ts.map +1 -1
- package/dist/bin-resolve.js +176 -12
- package/dist/bin-resolve.js.map +1 -1
- package/dist/build-fingerprint.d.ts +66 -0
- package/dist/build-fingerprint.d.ts.map +1 -0
- package/dist/build-fingerprint.js +70 -0
- package/dist/build-fingerprint.js.map +1 -0
- package/dist/build-info.generated.d.ts +3 -0
- package/dist/build-info.generated.d.ts.map +1 -0
- package/dist/build-info.generated.js +3 -0
- package/dist/build-info.generated.js.map +1 -0
- package/dist/capacity/resume-controller.d.ts +5 -3
- package/dist/capacity/resume-controller.d.ts.map +1 -1
- package/dist/capacity/resume-controller.js +9 -0
- package/dist/capacity/resume-controller.js.map +1 -1
- package/dist/chat-delegation-guard.d.ts +23 -0
- package/dist/chat-delegation-guard.d.ts.map +1 -0
- package/dist/chat-delegation-guard.js +62 -0
- package/dist/chat-delegation-guard.js.map +1 -0
- package/dist/chat-lane.d.ts +17 -6
- package/dist/chat-lane.d.ts.map +1 -1
- package/dist/chat-lane.js +29 -8
- package/dist/chat-lane.js.map +1 -1
- package/dist/companion-identity.d.ts +37 -0
- package/dist/companion-identity.d.ts.map +1 -1
- package/dist/companion-identity.js +98 -29
- package/dist/companion-identity.js.map +1 -1
- package/dist/companion-run.d.ts +1 -50
- package/dist/companion-run.d.ts.map +1 -1
- package/dist/companion-run.js +48 -105
- package/dist/companion-run.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -3
- package/dist/config.js.map +1 -1
- package/dist/conformance.d.ts +49 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +121 -0
- package/dist/conformance.js.map +1 -0
- package/dist/cost-pricing.d.ts +91 -0
- package/dist/cost-pricing.d.ts.map +1 -1
- package/dist/cost-pricing.js +115 -1
- package/dist/cost-pricing.js.map +1 -1
- package/dist/credential-identity.d.ts +70 -0
- package/dist/credential-identity.d.ts.map +1 -0
- package/dist/credential-identity.js +128 -0
- package/dist/credential-identity.js.map +1 -0
- package/dist/doctor.d.ts +37 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +300 -41
- package/dist/doctor.js.map +1 -1
- package/dist/engines/chat-stream.d.ts +71 -1
- package/dist/engines/chat-stream.d.ts.map +1 -1
- package/dist/engines/chat-stream.js +126 -8
- package/dist/engines/chat-stream.js.map +1 -1
- package/dist/engines/claude-code-chat.d.ts +11 -1
- package/dist/engines/claude-code-chat.d.ts.map +1 -1
- package/dist/engines/claude-code-chat.js +69 -5
- package/dist/engines/claude-code-chat.js.map +1 -1
- package/dist/engines/claude-code.d.ts.map +1 -1
- package/dist/engines/claude-code.js +30 -6
- package/dist/engines/claude-code.js.map +1 -1
- package/dist/engines/codex.d.ts.map +1 -1
- package/dist/engines/codex.js +18 -5
- package/dist/engines/codex.js.map +1 -1
- package/dist/engines/registry.d.ts +26 -0
- package/dist/engines/registry.d.ts.map +1 -1
- package/dist/engines/registry.js +37 -0
- package/dist/engines/registry.js.map +1 -1
- package/dist/engines/types.d.ts +5 -0
- package/dist/engines/types.d.ts.map +1 -1
- package/dist/failure-classifier.d.ts +1 -1
- package/dist/failure-classifier.d.ts.map +1 -1
- package/dist/failure-classifier.js +65 -12
- package/dist/failure-classifier.js.map +1 -1
- package/dist/gh.d.ts +15 -0
- package/dist/gh.d.ts.map +1 -1
- package/dist/gh.js +18 -0
- package/dist/gh.js.map +1 -1
- package/dist/git.d.ts +6 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +27 -1
- package/dist/git.js.map +1 -1
- package/dist/instance.d.ts +30 -0
- package/dist/instance.d.ts.map +1 -0
- package/dist/instance.js +51 -0
- package/dist/instance.js.map +1 -0
- package/dist/keychain.d.ts +20 -7
- package/dist/keychain.d.ts.map +1 -1
- package/dist/keychain.js +40 -4
- package/dist/keychain.js.map +1 -1
- package/dist/loc-advisory.d.ts +70 -0
- package/dist/loc-advisory.d.ts.map +1 -0
- package/dist/loc-advisory.js +196 -0
- package/dist/loc-advisory.js.map +1 -0
- package/dist/locks/claim-lock-policy.d.ts +67 -0
- package/dist/locks/claim-lock-policy.d.ts.map +1 -0
- package/dist/locks/claim-lock-policy.js +132 -0
- package/dist/locks/claim-lock-policy.js.map +1 -0
- package/dist/login.d.ts +63 -2
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +93 -8
- package/dist/login.js.map +1 -1
- package/dist/mcp-spawn.d.ts +54 -0
- package/dist/mcp-spawn.d.ts.map +1 -1
- package/dist/mcp-spawn.js +88 -4
- package/dist/mcp-spawn.js.map +1 -1
- package/dist/memory-context.d.ts +106 -0
- package/dist/memory-context.d.ts.map +1 -0
- package/dist/memory-context.js +220 -0
- package/dist/memory-context.js.map +1 -0
- package/dist/memory-curation.d.ts +172 -0
- package/dist/memory-curation.d.ts.map +1 -0
- package/dist/memory-curation.js +277 -0
- package/dist/memory-curation.js.map +1 -0
- package/dist/migration-seq-guard.d.ts +28 -1
- package/dist/migration-seq-guard.d.ts.map +1 -1
- package/dist/migration-seq-guard.js +111 -6
- package/dist/migration-seq-guard.js.map +1 -1
- package/dist/output-diagnostics.d.ts +15 -0
- package/dist/output-diagnostics.d.ts.map +1 -0
- package/dist/output-diagnostics.js +95 -0
- package/dist/output-diagnostics.js.map +1 -0
- package/dist/pr-open-guard.d.ts +67 -0
- package/dist/pr-open-guard.d.ts.map +1 -0
- package/dist/pr-open-guard.js +79 -0
- package/dist/pr-open-guard.js.map +1 -0
- package/dist/prepr-gate.d.ts +229 -1
- package/dist/prepr-gate.d.ts.map +1 -1
- package/dist/prepr-gate.js +406 -3
- package/dist/prepr-gate.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +43 -5
- package/dist/program.js.map +1 -1
- package/dist/provider-auth.d.ts +27 -0
- package/dist/provider-auth.d.ts.map +1 -1
- package/dist/provider-auth.js +33 -0
- package/dist/provider-auth.js.map +1 -1
- package/dist/runtime/cache-dir.d.ts.map +1 -1
- package/dist/runtime/cache-dir.js +8 -3
- package/dist/runtime/cache-dir.js.map +1 -1
- package/dist/runtime/decision-provenance.d.ts +63 -0
- package/dist/runtime/decision-provenance.d.ts.map +1 -0
- package/dist/runtime/decision-provenance.js +107 -0
- package/dist/runtime/decision-provenance.js.map +1 -0
- package/dist/runtime/prompt-substitute.d.ts +100 -0
- package/dist/runtime/prompt-substitute.d.ts.map +1 -0
- package/dist/runtime/prompt-substitute.js +124 -0
- package/dist/runtime/prompt-substitute.js.map +1 -0
- package/dist/runtime/quarantine.d.ts +11 -0
- package/dist/runtime/quarantine.d.ts.map +1 -1
- package/dist/runtime/quarantine.js +16 -1
- package/dist/runtime/quarantine.js.map +1 -1
- package/dist/runtime/review-cost-cap.d.ts +167 -0
- package/dist/runtime/review-cost-cap.d.ts.map +1 -0
- package/dist/runtime/review-cost-cap.js +182 -0
- package/dist/runtime/review-cost-cap.js.map +1 -0
- package/dist/runtime/reviewer-abstain.d.ts +101 -0
- package/dist/runtime/reviewer-abstain.d.ts.map +1 -0
- package/dist/runtime/reviewer-abstain.js +138 -0
- package/dist/runtime/reviewer-abstain.js.map +1 -0
- package/dist/runtime/reviewer.d.ts +165 -2
- package/dist/runtime/reviewer.d.ts.map +1 -1
- package/dist/runtime/reviewer.js +752 -111
- package/dist/runtime/reviewer.js.map +1 -1
- package/dist/runtime/running-build.d.ts +24 -0
- package/dist/runtime/running-build.d.ts.map +1 -0
- package/dist/runtime/running-build.js +62 -0
- package/dist/runtime/running-build.js.map +1 -0
- package/dist/runtime/singleton.d.ts.map +1 -1
- package/dist/runtime/singleton.js +9 -1
- package/dist/runtime/singleton.js.map +1 -1
- package/dist/runtime/worktree.d.ts +45 -0
- package/dist/runtime/worktree.d.ts.map +1 -1
- package/dist/runtime/worktree.js +174 -5
- package/dist/runtime/worktree.js.map +1 -1
- package/dist/serving-auth/index.d.ts +79 -0
- package/dist/serving-auth/index.d.ts.map +1 -0
- package/dist/serving-auth/index.js +169 -0
- package/dist/serving-auth/index.js.map +1 -0
- package/dist/task-runner.d.ts +134 -5
- package/dist/task-runner.d.ts.map +1 -1
- package/dist/task-runner.js +1255 -129
- package/dist/task-runner.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/watch-chat/wire.d.ts +80 -0
- package/dist/watch-chat/wire.d.ts.map +1 -1
- package/dist/watch-chat/wire.js +203 -43
- package/dist/watch-chat/wire.js.map +1 -1
- package/dist/watch.d.ts +70 -16
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +548 -108
- package/dist/watch.js.map +1 -1
- package/package.json +4 -3
package/dist/doctor.js
CHANGED
|
@@ -8,18 +8,20 @@ import os from "node:os";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { execa } from "execa";
|
|
10
10
|
import chalk from "chalk";
|
|
11
|
-
import { buildEnv } from "./bin-resolve.js";
|
|
11
|
+
import { buildEnv, executableSearchPaths, resolveExecutable, unresolvedExecutableDetail, } from "./bin-resolve.js";
|
|
12
12
|
import { authPrecedenceHint, servingAuthKind, describeServingAuthKind, anthropicApiKey, forwardsAmbientKey, } from "./provider-auth.js";
|
|
13
|
-
import { openaiApiKey, openaiApiKeyFromKeychain, describeCodexAuthMode
|
|
13
|
+
import { openaiApiKey, openaiApiKeyFromKeychain, describeCodexAuthMode } from "./provider-auth.js";
|
|
14
14
|
import { codexEngineEnabled, resolveCodexSpawnMode } from "./engines/codex.js";
|
|
15
|
+
import { engineRegistrationState, getConfiguredChatEngine } from "./engines/registry.js";
|
|
15
16
|
import { claudeCodeEngine } from "./engines/claude-code.js";
|
|
16
|
-
import { claudeCodeChatEngine } from "./engines/claude-code-chat.js";
|
|
17
17
|
import { applyBootstrapConfig, CONCURRENCY_ENV, concurrencyEnvExplicit, configSources, loadConfigOrNull, resolveConcurrencyLimit, resolveEffectiveConcurrency, resolveReservedReviewSlots, RESERVED_REVIEW_SLOT_ENV, supabaseConfigSource, } from "./config.js";
|
|
18
18
|
import { getTokenProvider, resolveTokenMode } from "./token-provider.js";
|
|
19
19
|
import { cacheDir } from "./runtime/cache-dir.js";
|
|
20
20
|
import { cloneCacheRoot, listCachedClones } from "./runtime/clone-cache.js";
|
|
21
21
|
import { createRunnerClient } from "./supabase.js";
|
|
22
22
|
import { machineIdentity } from "./login.js";
|
|
23
|
+
import { safeText } from "./machine-capabilities/redact.js";
|
|
24
|
+
import { INSTANCE_ENV, INSTANCE_NAME_RE, instanceKeychainAccount, resolveInstance, } from "./instance.js";
|
|
23
25
|
import { checkVersion } from "./version-check.js";
|
|
24
26
|
import { PROTOCOL_VERSION } from "./types.js";
|
|
25
27
|
import { locksDir, listStaleLocks } from "./runtime/locks.js";
|
|
@@ -32,6 +34,10 @@ import { getClaudeVersion, npmLatestVersion, readTaskEnv, } from "./runtime/vers
|
|
|
32
34
|
import { compareSemver as compareSemverVer } from "./version-check.js";
|
|
33
35
|
import { latestSelfUpdateAttempt } from "./runtime/self-upgrade.js";
|
|
34
36
|
import { PACKAGE_VERSION } from "./pkg-version.js";
|
|
37
|
+
import { resolveCredentialIdentity } from "./credential-identity.js";
|
|
38
|
+
import { readRunningBuild } from "./runtime/running-build.js";
|
|
39
|
+
import { runningBuildStatus } from "./build-fingerprint.js";
|
|
40
|
+
import { probeMcpServer } from "./mcp-spawn.js";
|
|
35
41
|
export const LAUNCHD_LABEL = "com.tokenfactory.acc-runner";
|
|
36
42
|
export function launchdPlistPath() {
|
|
37
43
|
return path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
@@ -75,7 +81,7 @@ function maskKey(s) {
|
|
|
75
81
|
function formatConfig(cfg, bootstrap) {
|
|
76
82
|
const src = configSources(bootstrap);
|
|
77
83
|
const lines = Object.keys(CONFIG_LABELS).map((k) => {
|
|
78
|
-
//
|
|
84
|
+
// worktreeRepoPath is optional at the config layer
|
|
79
85
|
// (undefined means "use repoPath"). Render "<not set>" rather than
|
|
80
86
|
// the literal string "undefined" so the doctor output is clean.
|
|
81
87
|
const raw = cfg[k];
|
|
@@ -130,9 +136,26 @@ function formatRuntime() {
|
|
|
130
136
|
];
|
|
131
137
|
return [chalk.bold("Runtime:"), ...lines].join("\n");
|
|
132
138
|
}
|
|
133
|
-
async function checkBinary(bin, args) {
|
|
139
|
+
async function checkBinary(bin, args, resolveOptions = {}) {
|
|
140
|
+
const platform = resolveOptions.platform ?? process.platform;
|
|
141
|
+
const env = resolveOptions.env ?? buildEnv();
|
|
142
|
+
// AC4: no behaviour change on darwin/linux. Only Windows resolves a concrete
|
|
143
|
+
// shim up front (execa cannot find a PATHEXT `.cmd`/`.exe` by bare name); POSIX
|
|
144
|
+
// hands execa the bare name, keeping its own PATH resolution + ENOENT failure.
|
|
145
|
+
let command = bin;
|
|
146
|
+
if (platform === "win32") {
|
|
147
|
+
const options = { ...resolveOptions, env, platform };
|
|
148
|
+
const resolved = resolveExecutable(bin, options);
|
|
149
|
+
if (!resolved) {
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
detail: unresolvedExecutableDetail(bin, executableSearchPaths(bin, options)),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
command = resolved.command;
|
|
156
|
+
}
|
|
134
157
|
try {
|
|
135
|
-
const { stdout } = await execa(
|
|
158
|
+
const { stdout } = await execa(command, args, { env });
|
|
136
159
|
const first = stdout.split(/\r?\n/)[0]?.trim() ?? bin;
|
|
137
160
|
return { ok: true, detail: first };
|
|
138
161
|
}
|
|
@@ -149,8 +172,8 @@ async function checkNode() {
|
|
|
149
172
|
fix: "Install Node 20.6+ (https://nodejs.org).",
|
|
150
173
|
};
|
|
151
174
|
}
|
|
152
|
-
async function checkClaude() {
|
|
153
|
-
const r = await checkBinary("claude", ["--version"]);
|
|
175
|
+
export async function checkClaude(resolveOptions = {}) {
|
|
176
|
+
const r = await checkBinary("claude", ["--version"], resolveOptions);
|
|
154
177
|
return {
|
|
155
178
|
name: "claude CLI in PATH",
|
|
156
179
|
ok: r.ok,
|
|
@@ -177,9 +200,7 @@ async function checkAnthropicKey() {
|
|
|
177
200
|
return {
|
|
178
201
|
name,
|
|
179
202
|
ok: true,
|
|
180
|
-
detail: hint
|
|
181
|
-
? `${describeServingAuthKind(kind)} — ${hint}`
|
|
182
|
-
: describeServingAuthKind(kind),
|
|
203
|
+
detail: hint ? `${describeServingAuthKind(kind)} — ${hint}` : describeServingAuthKind(kind),
|
|
183
204
|
};
|
|
184
205
|
}
|
|
185
206
|
return {
|
|
@@ -218,16 +239,23 @@ async function checkAnthropicKey() {
|
|
|
218
239
|
* Secrets are never printed (the probe detail redacts key material). The probe
|
|
219
240
|
* is injectable so the doctor test exercises the rendering without live gh/auth.
|
|
220
241
|
*/
|
|
221
|
-
export async function checkChatAuth(probe
|
|
242
|
+
export async function checkChatAuth(probe, engineId) {
|
|
243
|
+
const engine = !probe || !engineId ? getConfiguredChatEngine() : null;
|
|
244
|
+
const selectedEngineId = engineId ?? engine?.id ?? "claude-code";
|
|
222
245
|
const name = "chat lane credentials";
|
|
223
|
-
const result = await probe();
|
|
246
|
+
const result = await (probe ?? (() => engine.credentialProbe()))();
|
|
224
247
|
if (!result.ok) {
|
|
248
|
+
const login = selectedEngineId === "codex"
|
|
249
|
+
? "codex login"
|
|
250
|
+
: selectedEngineId === "gemini"
|
|
251
|
+
? "gemini"
|
|
252
|
+
: "claude login";
|
|
225
253
|
return {
|
|
226
254
|
name,
|
|
227
255
|
ok: false,
|
|
228
256
|
warn: true,
|
|
229
|
-
detail: `chat lane unavailable — ${result.detail}`,
|
|
230
|
-
fix:
|
|
257
|
+
detail: `chat lane unavailable for ${selectedEngineId} — ${result.detail}`,
|
|
258
|
+
fix: `run \`${login}\` under the runner's OS user so ${selectedEngineId} can serve chat from that subscription; no API key is required.`,
|
|
231
259
|
};
|
|
232
260
|
}
|
|
233
261
|
const draftOnly = result.tier === "draft-only";
|
|
@@ -237,10 +265,61 @@ export async function checkChatAuth(probe = () => claudeCodeChatEngine.credentia
|
|
|
237
265
|
warn: draftOnly ? true : undefined,
|
|
238
266
|
detail: `tier=${result.tier} (${result.channel}); ${result.detail}`,
|
|
239
267
|
...(draftOnly
|
|
240
|
-
? {
|
|
268
|
+
? {
|
|
269
|
+
fix: "authenticate `gh` (run `gh auth login`) to lift the chat lane from draft-only to full (repo-mutating) turns.",
|
|
270
|
+
}
|
|
241
271
|
: {}),
|
|
242
272
|
};
|
|
243
273
|
}
|
|
274
|
+
/** Prove the installed/fallback MCP command can initialize and list its catalog. */
|
|
275
|
+
export async function checkMcpServer(cfg, deps = {}) {
|
|
276
|
+
const name = "ACC MCP server";
|
|
277
|
+
if (!cfg)
|
|
278
|
+
return { name, ok: true, detail: "config unavailable (skipped)" };
|
|
279
|
+
let session;
|
|
280
|
+
try {
|
|
281
|
+
session = await (deps.loadSession ?? (() => getTokenProvider().load()))();
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
return {
|
|
285
|
+
name,
|
|
286
|
+
ok: false,
|
|
287
|
+
detail: `runner session unavailable: ${err.message.split("\n")[0]}`,
|
|
288
|
+
fix: "Re-run `acc login`, then restart the runner.",
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (!session) {
|
|
292
|
+
return {
|
|
293
|
+
name,
|
|
294
|
+
ok: false,
|
|
295
|
+
detail: "runner session unavailable",
|
|
296
|
+
fix: "Run `acc login`, then restart the runner.",
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const probe = deps.probe ?? ((args) => probeMcpServer(args));
|
|
300
|
+
const result = await probe({
|
|
301
|
+
cwd: cfg.repoPath,
|
|
302
|
+
taskId: "",
|
|
303
|
+
runnerId: session.runner_id,
|
|
304
|
+
accessToken: session.access_token,
|
|
305
|
+
publicUrl: cfg.publicUrl,
|
|
306
|
+
supabaseUrl: cfg.supabaseUrl,
|
|
307
|
+
supabaseAnonKey: cfg.supabaseAnonKey,
|
|
308
|
+
});
|
|
309
|
+
if (!result.ok) {
|
|
310
|
+
return {
|
|
311
|
+
name,
|
|
312
|
+
ok: false,
|
|
313
|
+
detail: `${result.command} healthcheck failed: ${result.detail}`,
|
|
314
|
+
fix: "Install the current `@tokenfactory/acc-mcp-server`, then restart every `acc-runner@*.service` instance.",
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
name,
|
|
319
|
+
ok: true,
|
|
320
|
+
detail: `${result.detail} (${result.command})`,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
244
323
|
/**
|
|
245
324
|
* M4 (ENGINE-CODEX): per-engine validation for the codex engine — binary in
|
|
246
325
|
* PATH + a usable auth channel. Skipped-green when the `engines.codex_enabled`
|
|
@@ -252,8 +331,28 @@ export async function checkChatAuth(probe = () => claudeCodeChatEngine.credentia
|
|
|
252
331
|
*/
|
|
253
332
|
export async function checkCodexEngine() {
|
|
254
333
|
const name = "codex engine";
|
|
255
|
-
|
|
256
|
-
|
|
334
|
+
// M5 (CODEX-DISPATCH-WIRE-1): gate on the ACTUAL dispatch registry, not just
|
|
335
|
+
// the raw flag. `engineRegistrationState` is the same resolution
|
|
336
|
+
// `getEngine("codex")` performs on dispatch, so doctor's verdict matches what
|
|
337
|
+
// a codex-pinned task actually gets — binary + login are necessary but not
|
|
338
|
+
// sufficient; the engine must be REGISTERED for the runner to serve it.
|
|
339
|
+
const regState = engineRegistrationState("codex");
|
|
340
|
+
if (regState === "flag_disabled") {
|
|
341
|
+
return {
|
|
342
|
+
name,
|
|
343
|
+
ok: true,
|
|
344
|
+
detail: "disabled (ACC_ENGINE_CODEX_ENABLED off — not registered for dispatch)",
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (regState === "unknown") {
|
|
348
|
+
// codex is a KNOWN batch engine, so this is only reachable if the registry
|
|
349
|
+
// build dropped it entirely — fail loudly rather than skip green.
|
|
350
|
+
return {
|
|
351
|
+
name,
|
|
352
|
+
ok: false,
|
|
353
|
+
detail: "codex is not present in this runner's engine registry build",
|
|
354
|
+
fix: "Rebuild/reinstall @tokenfactory/acc-runner; the codex engine is missing from the registry.",
|
|
355
|
+
};
|
|
257
356
|
}
|
|
258
357
|
const bin = await checkBinary("codex", ["--version"]);
|
|
259
358
|
if (!bin.ok) {
|
|
@@ -276,25 +375,77 @@ export async function checkCodexEngine() {
|
|
|
276
375
|
fix: "Set OPENAI_API_KEY (env or keychain) for the api-key channel, or use ACC_CODEX_AUTH_PREFERENCE=subscription_first with a `codex login` session.",
|
|
277
376
|
};
|
|
278
377
|
}
|
|
378
|
+
if (mode === "subscription") {
|
|
379
|
+
const auth = await checkBinary("codex", ["login", "status"]);
|
|
380
|
+
if (!auth.ok) {
|
|
381
|
+
return {
|
|
382
|
+
name,
|
|
383
|
+
ok: false,
|
|
384
|
+
detail: `${bin.detail}; subscription login unusable: ${auth.detail}`,
|
|
385
|
+
fix: "Run `codex login`, then re-run `acc doctor`.",
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
name,
|
|
390
|
+
ok: true,
|
|
391
|
+
detail: `registered for dispatch; ${bin.detail}; ${describeCodexAuthMode(mode)}; ${auth.detail}`,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
279
394
|
return {
|
|
280
395
|
name,
|
|
281
396
|
ok: true,
|
|
282
|
-
detail:
|
|
397
|
+
detail: `registered for dispatch; ${bin.detail}; ${describeCodexAuthMode(mode)}; OPENAI_API_KEY ${keyPresent ? "present" : "absent (subscription)"}`,
|
|
283
398
|
};
|
|
284
399
|
}
|
|
285
400
|
export async function detectCapableEngines(deps = {}) {
|
|
286
401
|
const claudeProbe = deps.claudeProbe ?? (() => claudeCodeEngine.healthProbe());
|
|
287
402
|
const codexEnabled = deps.codexEnabled ?? codexEngineEnabled;
|
|
288
403
|
const codexProbe = deps.codexProbe ?? checkCodexEngine;
|
|
404
|
+
const logRejected = deps.logRejected ?? ((line) => process.stderr.write(`${line}\n`));
|
|
289
405
|
const engines = [];
|
|
290
|
-
|
|
406
|
+
const claude = await claudeProbe();
|
|
407
|
+
if (claude.ok) {
|
|
291
408
|
engines.push(claudeCodeEngine.id);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
const detail = safeText(claude.detail, 300) ?? "probe failed";
|
|
412
|
+
logRejected(`[acc-runner] engine rejected: ${claudeCodeEngine.id}: ${detail}`);
|
|
413
|
+
}
|
|
292
414
|
if (codexEnabled()) {
|
|
293
|
-
|
|
415
|
+
const codex = await codexProbe();
|
|
416
|
+
if (codex.ok) {
|
|
294
417
|
engines.push("codex");
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
const detail = safeText(codex.detail, 300) ?? "probe failed";
|
|
421
|
+
logRejected(`[acc-runner] engine rejected: codex: ${detail}`);
|
|
422
|
+
}
|
|
295
423
|
}
|
|
296
424
|
return engines;
|
|
297
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* CAPS-ENGINES-STALE-KEYCHAIN-1 (AC5): surface WHY an enabled engine is or isn't
|
|
428
|
+
* advertised, on doctor's DEFAULT reporting path — so an operator sees the same
|
|
429
|
+
* rejection reason detectCapableEngines uses, without reading source. Runs that
|
|
430
|
+
* exact detection and folds every rejection line into the row's detail. `detect`
|
|
431
|
+
* is injectable so the reason is testable without the real binaries.
|
|
432
|
+
*/
|
|
433
|
+
export async function checkEngineDetection(detect = detectCapableEngines) {
|
|
434
|
+
const name = "engine detection";
|
|
435
|
+
const rejected = [];
|
|
436
|
+
const advertised = await detect({ logRejected: (line) => rejected.push(line) });
|
|
437
|
+
const adv = advertised.length ? advertised.join(", ") : "(default engine only)";
|
|
438
|
+
if (rejected.length === 0) {
|
|
439
|
+
return { name, ok: true, detail: `advertised: ${adv}` };
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
name,
|
|
443
|
+
ok: false,
|
|
444
|
+
warn: true,
|
|
445
|
+
detail: `advertised: ${adv}; rejected — ${rejected.join(" ; ")}`,
|
|
446
|
+
fix: "An enabled engine failed its probe (reason above); fix its auth/binary or disable it.",
|
|
447
|
+
};
|
|
448
|
+
}
|
|
298
449
|
async function checkGh() {
|
|
299
450
|
const ghOk = await checkBinary("gh", ["--version"]);
|
|
300
451
|
if (!ghOk.ok) {
|
|
@@ -387,6 +538,34 @@ export async function checkGitAuth(cfg, probeFactory = (repoPath) => makeGitAuth
|
|
|
387
538
|
fix: "Network/remote hiccup — re-run `doctor`; if it persists, check connectivity to GitHub.",
|
|
388
539
|
};
|
|
389
540
|
}
|
|
541
|
+
/** NAMED-INSTANCES-1: names what diverges per instance — the name, its
|
|
542
|
+
* keychain slot, and the runner id that slot resolved to. Always OK: unnamed
|
|
543
|
+
* is normal, and a missing session already fails `runner registered`. */
|
|
544
|
+
export async function checkInstance() {
|
|
545
|
+
const name = "runner instance";
|
|
546
|
+
let instance;
|
|
547
|
+
try {
|
|
548
|
+
instance = resolveInstance();
|
|
549
|
+
}
|
|
550
|
+
catch (err) {
|
|
551
|
+
const detail = err.message.split("\n")[0];
|
|
552
|
+
return {
|
|
553
|
+
name,
|
|
554
|
+
ok: false,
|
|
555
|
+
detail,
|
|
556
|
+
fix: `Fix ${INSTANCE_ENV} / --instance to match ${INSTANCE_NAME_RE.source}.`,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
const slot = resolveTokenMode() === "env" ? "n/a (token mode: env)" : `"${instanceKeychainAccount()}"`;
|
|
560
|
+
const runner = await getTokenProvider()
|
|
561
|
+
.load()
|
|
562
|
+
.then((s) => s?.runner_id ?? "no session", () => "unreadable");
|
|
563
|
+
return {
|
|
564
|
+
name,
|
|
565
|
+
ok: true,
|
|
566
|
+
detail: `${instance ?? "default (unnamed)"}, keychain slot ${slot}, runner ${runner}`,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
390
569
|
async function checkKeychain() {
|
|
391
570
|
// v0.12.0 (T-52-7): env-token runners (containers) have no OS keychain;
|
|
392
571
|
// the check is inapplicable rather than broken.
|
|
@@ -475,9 +654,7 @@ export async function checkEnvToken() {
|
|
|
475
654
|
// confirm spare credentials are wired before relying on failover. A single-
|
|
476
655
|
// credential runner reports "no fleet (single credential)".
|
|
477
656
|
const fleetSize = provider.fleetSize();
|
|
478
|
-
const fleet = fleetSize > 1
|
|
479
|
-
? `fleet of ${fleetSize} (failover armed)`
|
|
480
|
-
: "no fleet (single credential)";
|
|
657
|
+
const fleet = fleetSize > 1 ? `fleet of ${fleetSize} (failover armed)` : "no fleet (single credential)";
|
|
481
658
|
const expMs = Date.parse(session.access_expires_at);
|
|
482
659
|
if (Number.isFinite(expMs) && expMs <= Date.now()) {
|
|
483
660
|
return {
|
|
@@ -493,6 +670,19 @@ export async function checkEnvToken() {
|
|
|
493
670
|
detail: `runner ${session.runner_id} (id: ${idSource}), ${refresh}, ${fleet}, expires ${session.access_expires_at}`,
|
|
494
671
|
};
|
|
495
672
|
}
|
|
673
|
+
export async function checkCredentialIdentity() {
|
|
674
|
+
// Resolve the SERVING model-credential account (BYOK key → null email;
|
|
675
|
+
// config-file or OS-keychain login → the account it carries), NOT the ACC
|
|
676
|
+
// runner session. Only the safe email/org/source labels are surfaced — never
|
|
677
|
+
// the underlying token material.
|
|
678
|
+
const id = await resolveCredentialIdentity();
|
|
679
|
+
return {
|
|
680
|
+
name: "model credential identity",
|
|
681
|
+
ok: true,
|
|
682
|
+
detail: `email=${id.account_email ?? "unknown"}, org=${id.account_org ?? "unknown"}, ` +
|
|
683
|
+
`source=${id.credential_source}`,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
496
686
|
/**
|
|
497
687
|
* RD-1 (BOOTSTRAP-CONFIG): best-effort load of the persisted keychain / env
|
|
498
688
|
* session so the doctor can show WHERE the Supabase config resolved from and
|
|
@@ -622,7 +812,10 @@ async function checkOrphanWorktrees(cfg) {
|
|
|
622
812
|
if (orphans.length === 0) {
|
|
623
813
|
return { name: "no orphan worktrees", ok: true, detail: "clean" };
|
|
624
814
|
}
|
|
625
|
-
const first = orphans
|
|
815
|
+
const first = orphans
|
|
816
|
+
.slice(0, 3)
|
|
817
|
+
.map((o) => o.taskId)
|
|
818
|
+
.join(", ");
|
|
626
819
|
const more = orphans.length > 3 ? ` (+${orphans.length - 3} more)` : "";
|
|
627
820
|
return {
|
|
628
821
|
name: "no orphan worktrees",
|
|
@@ -688,7 +881,7 @@ async function checkQuarantine() {
|
|
|
688
881
|
name: "runner quarantine",
|
|
689
882
|
ok: false,
|
|
690
883
|
detail: `QUARANTINED (${q.cause}): ${q.detail} — task: ${q.taskId}, at: ${q.classifiedAt}${consec}`,
|
|
691
|
-
fix: `Resolve the underlying ${q.cause} issue then
|
|
884
|
+
fix: `Resolve the underlying ${q.cause} issue, then run \`acc-runner quarantine clear\` for this named instance (sentinel: ${quarantineFile()}); repeat for every instance on the host. Its running watcher resumes on the next recovery tick without a restart.`,
|
|
692
885
|
};
|
|
693
886
|
}
|
|
694
887
|
catch (err) {
|
|
@@ -734,7 +927,10 @@ async function checkStaleLocks() {
|
|
|
734
927
|
if (stale.length === 0) {
|
|
735
928
|
return { name: "no stale task locks", ok: true, detail: "clean" };
|
|
736
929
|
}
|
|
737
|
-
const first = stale
|
|
930
|
+
const first = stale
|
|
931
|
+
.slice(0, 3)
|
|
932
|
+
.map((l) => l.taskId)
|
|
933
|
+
.join(", ");
|
|
738
934
|
const more = stale.length > 3 ? ` (+${stale.length - 3} more)` : "";
|
|
739
935
|
return {
|
|
740
936
|
name: "no stale task locks",
|
|
@@ -845,7 +1041,9 @@ export async function checkSelfUpdateOn426(cfg) {
|
|
|
845
1041
|
const enabled = cfg?.selfUpdateOn426 === true;
|
|
846
1042
|
const attempt = await latestSelfUpdateAttempt().catch(() => null);
|
|
847
1043
|
const last = attempt
|
|
848
|
-
? `; last attempt v${attempt.target} ${attempt.record.ok
|
|
1044
|
+
? `; last attempt v${attempt.target} ${attempt.record.ok
|
|
1045
|
+
? "ok"
|
|
1046
|
+
: `failed${attempt.record.error ? ` (${attempt.record.error})` : ""}`} at ${attempt.record.attemptedAt}`
|
|
849
1047
|
: "";
|
|
850
1048
|
return {
|
|
851
1049
|
name: "self-update on 426",
|
|
@@ -918,9 +1116,7 @@ async function checkRegistration() {
|
|
|
918
1116
|
name: "runner registered",
|
|
919
1117
|
ok: false,
|
|
920
1118
|
detail: provider.mode === "env" ? "no session in env" : "no session in keychain",
|
|
921
|
-
fix: provider.mode === "env"
|
|
922
|
-
? "Set ACC_RUNNER_ACCESS_TOKEN."
|
|
923
|
-
: "Run `acc-runner login`.",
|
|
1119
|
+
fix: provider.mode === "env" ? "Set ACC_RUNNER_ACCESS_TOKEN." : "Run `acc-runner login`.",
|
|
924
1120
|
};
|
|
925
1121
|
}
|
|
926
1122
|
try {
|
|
@@ -956,7 +1152,11 @@ async function checkRegistration() {
|
|
|
956
1152
|
fix: "Run `acc-runner logout` then `acc-runner login` again.",
|
|
957
1153
|
};
|
|
958
1154
|
}
|
|
959
|
-
return {
|
|
1155
|
+
return {
|
|
1156
|
+
name: "runner registered",
|
|
1157
|
+
ok: true,
|
|
1158
|
+
detail: `${session.runner_id} (status=${data.status})`,
|
|
1159
|
+
};
|
|
960
1160
|
}
|
|
961
1161
|
catch (err) {
|
|
962
1162
|
return {
|
|
@@ -993,7 +1193,11 @@ export async function checkConcurrency(cfg) {
|
|
|
993
1193
|
envExplicit,
|
|
994
1194
|
});
|
|
995
1195
|
if (!cfg) {
|
|
996
|
-
return {
|
|
1196
|
+
return {
|
|
1197
|
+
name,
|
|
1198
|
+
ok: true,
|
|
1199
|
+
detail: `effective ${localEff.concurrency} (source=${localEff.source}), reserve ${localEff.reservedReviewSlots} — config unavailable (skipped)`,
|
|
1200
|
+
};
|
|
997
1201
|
}
|
|
998
1202
|
const provider = getTokenProvider();
|
|
999
1203
|
let session;
|
|
@@ -1004,7 +1208,11 @@ export async function checkConcurrency(cfg) {
|
|
|
1004
1208
|
session = null;
|
|
1005
1209
|
}
|
|
1006
1210
|
if (!session) {
|
|
1007
|
-
return {
|
|
1211
|
+
return {
|
|
1212
|
+
name,
|
|
1213
|
+
ok: true,
|
|
1214
|
+
detail: `effective ${localEff.concurrency} (source=${localEff.source}), reserve ${localEff.reservedReviewSlots} — not registered (skipped)`,
|
|
1215
|
+
};
|
|
1008
1216
|
}
|
|
1009
1217
|
let row = null;
|
|
1010
1218
|
try {
|
|
@@ -1016,12 +1224,20 @@ export async function checkConcurrency(cfg) {
|
|
|
1016
1224
|
.eq("id", session.runner_id)
|
|
1017
1225
|
.maybeSingle();
|
|
1018
1226
|
if (error) {
|
|
1019
|
-
return {
|
|
1227
|
+
return {
|
|
1228
|
+
name,
|
|
1229
|
+
ok: true,
|
|
1230
|
+
detail: `effective ${localEff.concurrency} (source=${localEff.source}) — server row unreadable (skipped): ${error.message.split("\n")[0]}`,
|
|
1231
|
+
};
|
|
1020
1232
|
}
|
|
1021
1233
|
row = (data ?? null);
|
|
1022
1234
|
}
|
|
1023
1235
|
catch (err) {
|
|
1024
|
-
return {
|
|
1236
|
+
return {
|
|
1237
|
+
name,
|
|
1238
|
+
ok: true,
|
|
1239
|
+
detail: `effective ${localEff.concurrency} (source=${localEff.source}) — server read failed (skipped): ${err.message.split("\n")[0]}`,
|
|
1240
|
+
};
|
|
1025
1241
|
}
|
|
1026
1242
|
const eff = resolveEffectiveConcurrency({
|
|
1027
1243
|
serverConcurrency: row?.desired_concurrency ?? null,
|
|
@@ -1099,12 +1315,20 @@ export async function checkRepoAssignments(cfg) {
|
|
|
1099
1315
|
.eq("active", true)
|
|
1100
1316
|
.eq("enforced", true);
|
|
1101
1317
|
if (error) {
|
|
1102
|
-
return {
|
|
1318
|
+
return {
|
|
1319
|
+
name,
|
|
1320
|
+
ok: true,
|
|
1321
|
+
detail: `no visible assignments (skipped): ${error.message.split("\n")[0]}`,
|
|
1322
|
+
};
|
|
1103
1323
|
}
|
|
1104
1324
|
rows = (data ?? []);
|
|
1105
1325
|
}
|
|
1106
1326
|
catch (err) {
|
|
1107
|
-
return {
|
|
1327
|
+
return {
|
|
1328
|
+
name,
|
|
1329
|
+
ok: true,
|
|
1330
|
+
detail: `assignment read failed (skipped): ${err.message.split("\n")[0]}`,
|
|
1331
|
+
};
|
|
1108
1332
|
}
|
|
1109
1333
|
if (rows.length === 0) {
|
|
1110
1334
|
return { name, ok: true, detail: "no enforced DB assignments visible" };
|
|
@@ -1224,6 +1448,38 @@ function format(outcome, strict = false) {
|
|
|
1224
1448
|
const fix = !outcome.ok && outcome.fix ? chalk.gray(`\n → ${outcome.fix}`) : "";
|
|
1225
1449
|
return `${tag} ${outcome.name.padEnd(28, " ")} ${chalk.gray(outcome.detail)}${fix}`;
|
|
1226
1450
|
}
|
|
1451
|
+
/**
|
|
1452
|
+
* BUILD-FINGERPRINT-RUNNING-1 (AC4): compare the build the long-lived
|
|
1453
|
+
* `watch`/companion process recorded at boot against the build THIS `acc doctor`
|
|
1454
|
+
* process loaded (=== the installed on-disk build). Answers the operator's real
|
|
1455
|
+
* question — "did the launchd process pick up the fix I just installed?" —
|
|
1456
|
+
* WITHOUT needing any symbol name from the diff, the check the incident had to
|
|
1457
|
+
* invent by grepping dist. A stale process is advisory (WARN), not a hard
|
|
1458
|
+
* environment failure; `--strict` promotes it like every other WARN. No record
|
|
1459
|
+
* (nothing long-lived has booted since the feature shipped) is not a problem.
|
|
1460
|
+
*/
|
|
1461
|
+
export async function checkRunningBuild(read = () => readRunningBuild()) {
|
|
1462
|
+
const name = "Running build";
|
|
1463
|
+
const status = runningBuildStatus(await read());
|
|
1464
|
+
if (status.noRunningRecord) {
|
|
1465
|
+
return {
|
|
1466
|
+
name,
|
|
1467
|
+
ok: true,
|
|
1468
|
+
detail: "no running record — start the runner (`watch`/companion) to record it",
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
if (status.restart_required) {
|
|
1472
|
+
return {
|
|
1473
|
+
name,
|
|
1474
|
+
ok: false,
|
|
1475
|
+
warn: true,
|
|
1476
|
+
detail: `STALE — process is running ${status.running?.dist_sha256} (${status.running?.commit_sha}) ` +
|
|
1477
|
+
`but the installed build is ${status.installed.dist_sha256} (${status.installed.commit_sha})`,
|
|
1478
|
+
fix: "Restart the runner (`watch`/companion) process to load the installed build.",
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
return { name, ok: true, detail: "up to date (running the installed build)" };
|
|
1482
|
+
}
|
|
1227
1483
|
export async function doctorCommand(options = {}) {
|
|
1228
1484
|
const rawCfg = loadConfigOrNull();
|
|
1229
1485
|
// RD-1: overlay the persisted device-code bootstrap so the displayed config
|
|
@@ -1235,12 +1491,16 @@ export async function doctorCommand(options = {}) {
|
|
|
1235
1491
|
checkClaude,
|
|
1236
1492
|
checkAnthropicKey,
|
|
1237
1493
|
checkChatAuth,
|
|
1494
|
+
() => checkMcpServer(cfg),
|
|
1238
1495
|
checkCodexEngine,
|
|
1496
|
+
checkEngineDetection,
|
|
1239
1497
|
checkGh,
|
|
1240
1498
|
checkGit,
|
|
1241
1499
|
() => checkGitAuth(cfg),
|
|
1242
1500
|
checkKeychain,
|
|
1501
|
+
checkInstance,
|
|
1243
1502
|
checkEnvToken,
|
|
1503
|
+
checkCredentialIdentity,
|
|
1244
1504
|
() => checkBootstrapConfig(bootstrap),
|
|
1245
1505
|
...(cfg ? [() => checkServer(cfg.publicUrl)] : []),
|
|
1246
1506
|
...(cfg ? [() => checkProtocolVersion(cfg.publicUrl)] : []),
|
|
@@ -1259,6 +1519,7 @@ export async function doctorCommand(options = {}) {
|
|
|
1259
1519
|
checkClaudeDrift,
|
|
1260
1520
|
checkCaffeinate,
|
|
1261
1521
|
checkLaunchd,
|
|
1522
|
+
checkRunningBuild,
|
|
1262
1523
|
];
|
|
1263
1524
|
const strict = options.strict === true;
|
|
1264
1525
|
if (cfg && !options.checks) {
|
|
@@ -1303,9 +1564,7 @@ export async function doctorCommand(options = {}) {
|
|
|
1303
1564
|
console.log(chalk.green(`✓ All checks passed${suffix}.`));
|
|
1304
1565
|
}
|
|
1305
1566
|
else {
|
|
1306
|
-
const warnNote = warned > 0 && !strict
|
|
1307
|
-
? `, ${warned} advisory warning${warned === 1 ? "" : "s"}`
|
|
1308
|
-
: "";
|
|
1567
|
+
const warnNote = warned > 0 && !strict ? `, ${warned} advisory warning${warned === 1 ? "" : "s"}` : "";
|
|
1309
1568
|
console.log(chalk.red(`✗ ${failed} check${failed === 1 ? "" : "s"} failed${warnNote}.`));
|
|
1310
1569
|
}
|
|
1311
1570
|
}
|