@tokenfactory/acc-runner 0.44.1 → 0.44.3
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 +20 -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 +46 -0
- package/dist/cost-pricing.d.ts.map +1 -1
- package/dist/cost-pricing.js +58 -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 +33 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +284 -44
- 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/keychain.d.ts +17 -0
- package/dist/keychain.d.ts.map +1 -1
- package/dist/keychain.js +30 -0
- 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 +59 -2
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +87 -7
- 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/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 +30 -5
- package/dist/program.js.map +1 -1
- package/dist/provider-auth.d.ts +8 -0
- package/dist/provider-auth.d.ts.map +1 -1
- package/dist/provider-auth.js +10 -0
- package/dist/provider-auth.js.map +1 -1
- 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/reviewer.d.ts +90 -2
- package/dist/runtime/reviewer.d.ts.map +1 -1
- package/dist/runtime/reviewer.js +547 -46
- 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/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/task-runner.d.ts +126 -5
- package/dist/task-runner.d.ts.map +1 -1
- package/dist/task-runner.js +1219 -127
- 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 +501 -89
- package/dist/watch.js.map +1 -1
- package/package.json +2 -2
package/dist/doctor.js
CHANGED
|
@@ -8,18 +8,19 @@ 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";
|
|
23
24
|
import { INSTANCE_ENV, INSTANCE_NAME_RE, instanceKeychainAccount, resolveInstance, } from "./instance.js";
|
|
24
25
|
import { checkVersion } from "./version-check.js";
|
|
25
26
|
import { PROTOCOL_VERSION } from "./types.js";
|
|
@@ -33,6 +34,10 @@ import { getClaudeVersion, npmLatestVersion, readTaskEnv, } from "./runtime/vers
|
|
|
33
34
|
import { compareSemver as compareSemverVer } from "./version-check.js";
|
|
34
35
|
import { latestSelfUpdateAttempt } from "./runtime/self-upgrade.js";
|
|
35
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";
|
|
36
41
|
export const LAUNCHD_LABEL = "com.tokenfactory.acc-runner";
|
|
37
42
|
export function launchdPlistPath() {
|
|
38
43
|
return path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
@@ -76,7 +81,7 @@ function maskKey(s) {
|
|
|
76
81
|
function formatConfig(cfg, bootstrap) {
|
|
77
82
|
const src = configSources(bootstrap);
|
|
78
83
|
const lines = Object.keys(CONFIG_LABELS).map((k) => {
|
|
79
|
-
//
|
|
84
|
+
// worktreeRepoPath is optional at the config layer
|
|
80
85
|
// (undefined means "use repoPath"). Render "<not set>" rather than
|
|
81
86
|
// the literal string "undefined" so the doctor output is clean.
|
|
82
87
|
const raw = cfg[k];
|
|
@@ -131,9 +136,26 @@ function formatRuntime() {
|
|
|
131
136
|
];
|
|
132
137
|
return [chalk.bold("Runtime:"), ...lines].join("\n");
|
|
133
138
|
}
|
|
134
|
-
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
|
+
}
|
|
135
157
|
try {
|
|
136
|
-
const { stdout } = await execa(
|
|
158
|
+
const { stdout } = await execa(command, args, { env });
|
|
137
159
|
const first = stdout.split(/\r?\n/)[0]?.trim() ?? bin;
|
|
138
160
|
return { ok: true, detail: first };
|
|
139
161
|
}
|
|
@@ -150,8 +172,8 @@ async function checkNode() {
|
|
|
150
172
|
fix: "Install Node 20.6+ (https://nodejs.org).",
|
|
151
173
|
};
|
|
152
174
|
}
|
|
153
|
-
async function checkClaude() {
|
|
154
|
-
const r = await checkBinary("claude", ["--version"]);
|
|
175
|
+
export async function checkClaude(resolveOptions = {}) {
|
|
176
|
+
const r = await checkBinary("claude", ["--version"], resolveOptions);
|
|
155
177
|
return {
|
|
156
178
|
name: "claude CLI in PATH",
|
|
157
179
|
ok: r.ok,
|
|
@@ -178,9 +200,7 @@ async function checkAnthropicKey() {
|
|
|
178
200
|
return {
|
|
179
201
|
name,
|
|
180
202
|
ok: true,
|
|
181
|
-
detail: hint
|
|
182
|
-
? `${describeServingAuthKind(kind)} — ${hint}`
|
|
183
|
-
: describeServingAuthKind(kind),
|
|
203
|
+
detail: hint ? `${describeServingAuthKind(kind)} — ${hint}` : describeServingAuthKind(kind),
|
|
184
204
|
};
|
|
185
205
|
}
|
|
186
206
|
return {
|
|
@@ -219,16 +239,23 @@ async function checkAnthropicKey() {
|
|
|
219
239
|
* Secrets are never printed (the probe detail redacts key material). The probe
|
|
220
240
|
* is injectable so the doctor test exercises the rendering without live gh/auth.
|
|
221
241
|
*/
|
|
222
|
-
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";
|
|
223
245
|
const name = "chat lane credentials";
|
|
224
|
-
const result = await probe();
|
|
246
|
+
const result = await (probe ?? (() => engine.credentialProbe()))();
|
|
225
247
|
if (!result.ok) {
|
|
248
|
+
const login = selectedEngineId === "codex"
|
|
249
|
+
? "codex login"
|
|
250
|
+
: selectedEngineId === "gemini"
|
|
251
|
+
? "gemini"
|
|
252
|
+
: "claude login";
|
|
226
253
|
return {
|
|
227
254
|
name,
|
|
228
255
|
ok: false,
|
|
229
256
|
warn: true,
|
|
230
|
-
detail: `chat lane unavailable — ${result.detail}`,
|
|
231
|
-
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.`,
|
|
232
259
|
};
|
|
233
260
|
}
|
|
234
261
|
const draftOnly = result.tier === "draft-only";
|
|
@@ -238,10 +265,61 @@ export async function checkChatAuth(probe = () => claudeCodeChatEngine.credentia
|
|
|
238
265
|
warn: draftOnly ? true : undefined,
|
|
239
266
|
detail: `tier=${result.tier} (${result.channel}); ${result.detail}`,
|
|
240
267
|
...(draftOnly
|
|
241
|
-
? {
|
|
268
|
+
? {
|
|
269
|
+
fix: "authenticate `gh` (run `gh auth login`) to lift the chat lane from draft-only to full (repo-mutating) turns.",
|
|
270
|
+
}
|
|
242
271
|
: {}),
|
|
243
272
|
};
|
|
244
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
|
+
}
|
|
245
323
|
/**
|
|
246
324
|
* M4 (ENGINE-CODEX): per-engine validation for the codex engine — binary in
|
|
247
325
|
* PATH + a usable auth channel. Skipped-green when the `engines.codex_enabled`
|
|
@@ -253,8 +331,28 @@ export async function checkChatAuth(probe = () => claudeCodeChatEngine.credentia
|
|
|
253
331
|
*/
|
|
254
332
|
export async function checkCodexEngine() {
|
|
255
333
|
const name = "codex engine";
|
|
256
|
-
|
|
257
|
-
|
|
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
|
+
};
|
|
258
356
|
}
|
|
259
357
|
const bin = await checkBinary("codex", ["--version"]);
|
|
260
358
|
if (!bin.ok) {
|
|
@@ -277,25 +375,77 @@ export async function checkCodexEngine() {
|
|
|
277
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.",
|
|
278
376
|
};
|
|
279
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
|
+
}
|
|
280
394
|
return {
|
|
281
395
|
name,
|
|
282
396
|
ok: true,
|
|
283
|
-
detail:
|
|
397
|
+
detail: `registered for dispatch; ${bin.detail}; ${describeCodexAuthMode(mode)}; OPENAI_API_KEY ${keyPresent ? "present" : "absent (subscription)"}`,
|
|
284
398
|
};
|
|
285
399
|
}
|
|
286
400
|
export async function detectCapableEngines(deps = {}) {
|
|
287
401
|
const claudeProbe = deps.claudeProbe ?? (() => claudeCodeEngine.healthProbe());
|
|
288
402
|
const codexEnabled = deps.codexEnabled ?? codexEngineEnabled;
|
|
289
403
|
const codexProbe = deps.codexProbe ?? checkCodexEngine;
|
|
404
|
+
const logRejected = deps.logRejected ?? ((line) => process.stderr.write(`${line}\n`));
|
|
290
405
|
const engines = [];
|
|
291
|
-
|
|
406
|
+
const claude = await claudeProbe();
|
|
407
|
+
if (claude.ok) {
|
|
292
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
|
+
}
|
|
293
414
|
if (codexEnabled()) {
|
|
294
|
-
|
|
415
|
+
const codex = await codexProbe();
|
|
416
|
+
if (codex.ok) {
|
|
295
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
|
+
}
|
|
296
423
|
}
|
|
297
424
|
return engines;
|
|
298
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
|
+
}
|
|
299
449
|
async function checkGh() {
|
|
300
450
|
const ghOk = await checkBinary("gh", ["--version"]);
|
|
301
451
|
if (!ghOk.ok) {
|
|
@@ -399,11 +549,22 @@ export async function checkInstance() {
|
|
|
399
549
|
}
|
|
400
550
|
catch (err) {
|
|
401
551
|
const detail = err.message.split("\n")[0];
|
|
402
|
-
return {
|
|
552
|
+
return {
|
|
553
|
+
name,
|
|
554
|
+
ok: false,
|
|
555
|
+
detail,
|
|
556
|
+
fix: `Fix ${INSTANCE_ENV} / --instance to match ${INSTANCE_NAME_RE.source}.`,
|
|
557
|
+
};
|
|
403
558
|
}
|
|
404
559
|
const slot = resolveTokenMode() === "env" ? "n/a (token mode: env)" : `"${instanceKeychainAccount()}"`;
|
|
405
|
-
const runner = await getTokenProvider()
|
|
406
|
-
|
|
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
|
+
};
|
|
407
568
|
}
|
|
408
569
|
async function checkKeychain() {
|
|
409
570
|
// v0.12.0 (T-52-7): env-token runners (containers) have no OS keychain;
|
|
@@ -493,9 +654,7 @@ export async function checkEnvToken() {
|
|
|
493
654
|
// confirm spare credentials are wired before relying on failover. A single-
|
|
494
655
|
// credential runner reports "no fleet (single credential)".
|
|
495
656
|
const fleetSize = provider.fleetSize();
|
|
496
|
-
const fleet = fleetSize > 1
|
|
497
|
-
? `fleet of ${fleetSize} (failover armed)`
|
|
498
|
-
: "no fleet (single credential)";
|
|
657
|
+
const fleet = fleetSize > 1 ? `fleet of ${fleetSize} (failover armed)` : "no fleet (single credential)";
|
|
499
658
|
const expMs = Date.parse(session.access_expires_at);
|
|
500
659
|
if (Number.isFinite(expMs) && expMs <= Date.now()) {
|
|
501
660
|
return {
|
|
@@ -511,6 +670,19 @@ export async function checkEnvToken() {
|
|
|
511
670
|
detail: `runner ${session.runner_id} (id: ${idSource}), ${refresh}, ${fleet}, expires ${session.access_expires_at}`,
|
|
512
671
|
};
|
|
513
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
|
+
}
|
|
514
686
|
/**
|
|
515
687
|
* RD-1 (BOOTSTRAP-CONFIG): best-effort load of the persisted keychain / env
|
|
516
688
|
* session so the doctor can show WHERE the Supabase config resolved from and
|
|
@@ -640,7 +812,10 @@ async function checkOrphanWorktrees(cfg) {
|
|
|
640
812
|
if (orphans.length === 0) {
|
|
641
813
|
return { name: "no orphan worktrees", ok: true, detail: "clean" };
|
|
642
814
|
}
|
|
643
|
-
const first = orphans
|
|
815
|
+
const first = orphans
|
|
816
|
+
.slice(0, 3)
|
|
817
|
+
.map((o) => o.taskId)
|
|
818
|
+
.join(", ");
|
|
644
819
|
const more = orphans.length > 3 ? ` (+${orphans.length - 3} more)` : "";
|
|
645
820
|
return {
|
|
646
821
|
name: "no orphan worktrees",
|
|
@@ -706,7 +881,7 @@ async function checkQuarantine() {
|
|
|
706
881
|
name: "runner quarantine",
|
|
707
882
|
ok: false,
|
|
708
883
|
detail: `QUARANTINED (${q.cause}): ${q.detail} — task: ${q.taskId}, at: ${q.classifiedAt}${consec}`,
|
|
709
|
-
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.`,
|
|
710
885
|
};
|
|
711
886
|
}
|
|
712
887
|
catch (err) {
|
|
@@ -752,7 +927,10 @@ async function checkStaleLocks() {
|
|
|
752
927
|
if (stale.length === 0) {
|
|
753
928
|
return { name: "no stale task locks", ok: true, detail: "clean" };
|
|
754
929
|
}
|
|
755
|
-
const first = stale
|
|
930
|
+
const first = stale
|
|
931
|
+
.slice(0, 3)
|
|
932
|
+
.map((l) => l.taskId)
|
|
933
|
+
.join(", ");
|
|
756
934
|
const more = stale.length > 3 ? ` (+${stale.length - 3} more)` : "";
|
|
757
935
|
return {
|
|
758
936
|
name: "no stale task locks",
|
|
@@ -863,7 +1041,9 @@ export async function checkSelfUpdateOn426(cfg) {
|
|
|
863
1041
|
const enabled = cfg?.selfUpdateOn426 === true;
|
|
864
1042
|
const attempt = await latestSelfUpdateAttempt().catch(() => null);
|
|
865
1043
|
const last = attempt
|
|
866
|
-
? `; 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}`
|
|
867
1047
|
: "";
|
|
868
1048
|
return {
|
|
869
1049
|
name: "self-update on 426",
|
|
@@ -936,9 +1116,7 @@ async function checkRegistration() {
|
|
|
936
1116
|
name: "runner registered",
|
|
937
1117
|
ok: false,
|
|
938
1118
|
detail: provider.mode === "env" ? "no session in env" : "no session in keychain",
|
|
939
|
-
fix: provider.mode === "env"
|
|
940
|
-
? "Set ACC_RUNNER_ACCESS_TOKEN."
|
|
941
|
-
: "Run `acc-runner login`.",
|
|
1119
|
+
fix: provider.mode === "env" ? "Set ACC_RUNNER_ACCESS_TOKEN." : "Run `acc-runner login`.",
|
|
942
1120
|
};
|
|
943
1121
|
}
|
|
944
1122
|
try {
|
|
@@ -974,7 +1152,11 @@ async function checkRegistration() {
|
|
|
974
1152
|
fix: "Run `acc-runner logout` then `acc-runner login` again.",
|
|
975
1153
|
};
|
|
976
1154
|
}
|
|
977
|
-
return {
|
|
1155
|
+
return {
|
|
1156
|
+
name: "runner registered",
|
|
1157
|
+
ok: true,
|
|
1158
|
+
detail: `${session.runner_id} (status=${data.status})`,
|
|
1159
|
+
};
|
|
978
1160
|
}
|
|
979
1161
|
catch (err) {
|
|
980
1162
|
return {
|
|
@@ -1011,7 +1193,11 @@ export async function checkConcurrency(cfg) {
|
|
|
1011
1193
|
envExplicit,
|
|
1012
1194
|
});
|
|
1013
1195
|
if (!cfg) {
|
|
1014
|
-
return {
|
|
1196
|
+
return {
|
|
1197
|
+
name,
|
|
1198
|
+
ok: true,
|
|
1199
|
+
detail: `effective ${localEff.concurrency} (source=${localEff.source}), reserve ${localEff.reservedReviewSlots} — config unavailable (skipped)`,
|
|
1200
|
+
};
|
|
1015
1201
|
}
|
|
1016
1202
|
const provider = getTokenProvider();
|
|
1017
1203
|
let session;
|
|
@@ -1022,7 +1208,11 @@ export async function checkConcurrency(cfg) {
|
|
|
1022
1208
|
session = null;
|
|
1023
1209
|
}
|
|
1024
1210
|
if (!session) {
|
|
1025
|
-
return {
|
|
1211
|
+
return {
|
|
1212
|
+
name,
|
|
1213
|
+
ok: true,
|
|
1214
|
+
detail: `effective ${localEff.concurrency} (source=${localEff.source}), reserve ${localEff.reservedReviewSlots} — not registered (skipped)`,
|
|
1215
|
+
};
|
|
1026
1216
|
}
|
|
1027
1217
|
let row = null;
|
|
1028
1218
|
try {
|
|
@@ -1034,12 +1224,20 @@ export async function checkConcurrency(cfg) {
|
|
|
1034
1224
|
.eq("id", session.runner_id)
|
|
1035
1225
|
.maybeSingle();
|
|
1036
1226
|
if (error) {
|
|
1037
|
-
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
|
+
};
|
|
1038
1232
|
}
|
|
1039
1233
|
row = (data ?? null);
|
|
1040
1234
|
}
|
|
1041
1235
|
catch (err) {
|
|
1042
|
-
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
|
+
};
|
|
1043
1241
|
}
|
|
1044
1242
|
const eff = resolveEffectiveConcurrency({
|
|
1045
1243
|
serverConcurrency: row?.desired_concurrency ?? null,
|
|
@@ -1117,12 +1315,20 @@ export async function checkRepoAssignments(cfg) {
|
|
|
1117
1315
|
.eq("active", true)
|
|
1118
1316
|
.eq("enforced", true);
|
|
1119
1317
|
if (error) {
|
|
1120
|
-
return {
|
|
1318
|
+
return {
|
|
1319
|
+
name,
|
|
1320
|
+
ok: true,
|
|
1321
|
+
detail: `no visible assignments (skipped): ${error.message.split("\n")[0]}`,
|
|
1322
|
+
};
|
|
1121
1323
|
}
|
|
1122
1324
|
rows = (data ?? []);
|
|
1123
1325
|
}
|
|
1124
1326
|
catch (err) {
|
|
1125
|
-
return {
|
|
1327
|
+
return {
|
|
1328
|
+
name,
|
|
1329
|
+
ok: true,
|
|
1330
|
+
detail: `assignment read failed (skipped): ${err.message.split("\n")[0]}`,
|
|
1331
|
+
};
|
|
1126
1332
|
}
|
|
1127
1333
|
if (rows.length === 0) {
|
|
1128
1334
|
return { name, ok: true, detail: "no enforced DB assignments visible" };
|
|
@@ -1242,6 +1448,38 @@ function format(outcome, strict = false) {
|
|
|
1242
1448
|
const fix = !outcome.ok && outcome.fix ? chalk.gray(`\n → ${outcome.fix}`) : "";
|
|
1243
1449
|
return `${tag} ${outcome.name.padEnd(28, " ")} ${chalk.gray(outcome.detail)}${fix}`;
|
|
1244
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
|
+
}
|
|
1245
1483
|
export async function doctorCommand(options = {}) {
|
|
1246
1484
|
const rawCfg = loadConfigOrNull();
|
|
1247
1485
|
// RD-1: overlay the persisted device-code bootstrap so the displayed config
|
|
@@ -1253,13 +1491,16 @@ export async function doctorCommand(options = {}) {
|
|
|
1253
1491
|
checkClaude,
|
|
1254
1492
|
checkAnthropicKey,
|
|
1255
1493
|
checkChatAuth,
|
|
1494
|
+
() => checkMcpServer(cfg),
|
|
1256
1495
|
checkCodexEngine,
|
|
1496
|
+
checkEngineDetection,
|
|
1257
1497
|
checkGh,
|
|
1258
1498
|
checkGit,
|
|
1259
1499
|
() => checkGitAuth(cfg),
|
|
1260
1500
|
checkKeychain,
|
|
1261
1501
|
checkInstance,
|
|
1262
1502
|
checkEnvToken,
|
|
1503
|
+
checkCredentialIdentity,
|
|
1263
1504
|
() => checkBootstrapConfig(bootstrap),
|
|
1264
1505
|
...(cfg ? [() => checkServer(cfg.publicUrl)] : []),
|
|
1265
1506
|
...(cfg ? [() => checkProtocolVersion(cfg.publicUrl)] : []),
|
|
@@ -1278,6 +1519,7 @@ export async function doctorCommand(options = {}) {
|
|
|
1278
1519
|
checkClaudeDrift,
|
|
1279
1520
|
checkCaffeinate,
|
|
1280
1521
|
checkLaunchd,
|
|
1522
|
+
checkRunningBuild,
|
|
1281
1523
|
];
|
|
1282
1524
|
const strict = options.strict === true;
|
|
1283
1525
|
if (cfg && !options.checks) {
|
|
@@ -1322,9 +1564,7 @@ export async function doctorCommand(options = {}) {
|
|
|
1322
1564
|
console.log(chalk.green(`✓ All checks passed${suffix}.`));
|
|
1323
1565
|
}
|
|
1324
1566
|
else {
|
|
1325
|
-
const warnNote = warned > 0 && !strict
|
|
1326
|
-
? `, ${warned} advisory warning${warned === 1 ? "" : "s"}`
|
|
1327
|
-
: "";
|
|
1567
|
+
const warnNote = warned > 0 && !strict ? `, ${warned} advisory warning${warned === 1 ? "" : "s"}` : "";
|
|
1328
1568
|
console.log(chalk.red(`✗ ${failed} check${failed === 1 ? "" : "s"} failed${warnNote}.`));
|
|
1329
1569
|
}
|
|
1330
1570
|
}
|