@tokenfactory/acc-runner 0.42.0 → 0.44.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 +52 -3
- package/dist/companion-run.d.ts.map +1 -1
- package/dist/companion-run.js +20 -0
- package/dist/companion-run.js.map +1 -1
- package/dist/config.d.ts +72 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +117 -3
- package/dist/config.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +13 -3
- package/dist/doctor.js.map +1 -1
- package/dist/engines/account-probe.d.ts +24 -0
- package/dist/engines/account-probe.d.ts.map +1 -1
- package/dist/engines/account-probe.js +18 -1
- package/dist/engines/account-probe.js.map +1 -1
- package/dist/engines/registry.d.ts +17 -6
- package/dist/engines/registry.d.ts.map +1 -1
- package/dist/engines/registry.js +38 -13
- package/dist/engines/registry.js.map +1 -1
- package/dist/git-auth/github-token-source.d.ts +54 -0
- package/dist/git-auth/github-token-source.d.ts.map +1 -0
- package/dist/git-auth/github-token-source.js +44 -0
- package/dist/git-auth/github-token-source.js.map +1 -0
- package/dist/git-auth/index.d.ts +4 -0
- package/dist/git-auth/index.d.ts.map +1 -1
- package/dist/git-auth/index.js +4 -0
- package/dist/git-auth/index.js.map +1 -1
- package/dist/git-auth/installation-token.d.ts +95 -0
- package/dist/git-auth/installation-token.d.ts.map +1 -0
- package/dist/git-auth/installation-token.js +192 -0
- package/dist/git-auth/installation-token.js.map +1 -0
- package/dist/local-repo/command.d.ts +28 -0
- package/dist/local-repo/command.d.ts.map +1 -0
- package/dist/local-repo/command.js +178 -0
- package/dist/local-repo/command.js.map +1 -0
- package/dist/local-repo/index.d.ts +119 -0
- package/dist/local-repo/index.d.ts.map +1 -0
- package/dist/local-repo/index.js +141 -0
- package/dist/local-repo/index.js.map +1 -0
- package/dist/machine-capabilities/index.d.ts +21 -0
- package/dist/machine-capabilities/index.d.ts.map +1 -0
- package/dist/machine-capabilities/index.js +21 -0
- package/dist/machine-capabilities/index.js.map +1 -0
- package/dist/machine-capabilities/probe.d.ts +61 -0
- package/dist/machine-capabilities/probe.d.ts.map +1 -0
- package/dist/machine-capabilities/probe.js +243 -0
- package/dist/machine-capabilities/probe.js.map +1 -0
- package/dist/machine-capabilities/redact.d.ts +42 -0
- package/dist/machine-capabilities/redact.d.ts.map +1 -0
- package/dist/machine-capabilities/redact.js +160 -0
- package/dist/machine-capabilities/redact.js.map +1 -0
- package/dist/machine-capabilities/report.d.ts +107 -0
- package/dist/machine-capabilities/report.d.ts.map +1 -0
- package/dist/machine-capabilities/report.js +211 -0
- package/dist/machine-capabilities/report.js.map +1 -0
- package/dist/machine-capabilities/types.d.ts +115 -0
- package/dist/machine-capabilities/types.d.ts.map +1 -0
- package/dist/machine-capabilities/types.js +11 -0
- package/dist/machine-capabilities/types.js.map +1 -0
- package/dist/mcp-spawn.d.ts +19 -0
- package/dist/mcp-spawn.d.ts.map +1 -1
- package/dist/mcp-spawn.js +52 -26
- package/dist/mcp-spawn.js.map +1 -1
- package/dist/migration-seq-guard.d.ts +78 -0
- package/dist/migration-seq-guard.d.ts.map +1 -0
- package/dist/migration-seq-guard.js +304 -0
- package/dist/migration-seq-guard.js.map +1 -0
- package/dist/node-version.d.ts +132 -0
- package/dist/node-version.d.ts.map +1 -0
- package/dist/node-version.js +318 -0
- package/dist/node-version.js.map +1 -0
- package/dist/prepr-gate.d.ts +120 -0
- package/dist/prepr-gate.d.ts.map +1 -1
- package/dist/prepr-gate.js +202 -3
- package/dist/prepr-gate.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +34 -0
- package/dist/program.js.map +1 -1
- package/dist/provider-auth.d.ts +51 -1
- package/dist/provider-auth.d.ts.map +1 -1
- package/dist/provider-auth.js +83 -2
- package/dist/provider-auth.js.map +1 -1
- package/dist/runtime/review-chunking.d.ts +101 -0
- package/dist/runtime/review-chunking.d.ts.map +1 -0
- package/dist/runtime/review-chunking.js +192 -0
- package/dist/runtime/review-chunking.js.map +1 -0
- package/dist/runtime/review-diff.d.ts +30 -0
- package/dist/runtime/review-diff.d.ts.map +1 -0
- package/dist/runtime/review-diff.js +59 -0
- package/dist/runtime/review-diff.js.map +1 -0
- package/dist/runtime/reviewer.d.ts +40 -19
- package/dist/runtime/reviewer.d.ts.map +1 -1
- package/dist/runtime/reviewer.js +283 -68
- package/dist/runtime/reviewer.js.map +1 -1
- package/dist/serve-scope/command.d.ts +12 -0
- package/dist/serve-scope/command.d.ts.map +1 -0
- package/dist/serve-scope/command.js +89 -0
- package/dist/serve-scope/command.js.map +1 -0
- package/dist/serve-scope/index.d.ts +130 -0
- package/dist/serve-scope/index.d.ts.map +1 -0
- package/dist/serve-scope/index.js +148 -0
- package/dist/serve-scope/index.js.map +1 -0
- package/dist/task-runner.d.ts +22 -2
- package/dist/task-runner.d.ts.map +1 -1
- package/dist/task-runner.js +142 -25
- package/dist/task-runner.js.map +1 -1
- package/dist/watch.d.ts +21 -0
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +109 -1
- package/dist/watch.js.map +1 -1
- package/package.json +1 -1
package/dist/task-runner.js
CHANGED
|
@@ -20,7 +20,7 @@ import path from "node:path";
|
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
22
|
import { execa } from "execa";
|
|
23
|
-
import { canServeRepo, loadProfile as defaultLoadProfile, resolveBaseBranch } from "./config.js";
|
|
23
|
+
import { canServeRepo, loadProfile as defaultLoadProfile, localRepoPathFor, resolveBaseBranch } from "./config.js";
|
|
24
24
|
import { normalizeUsage, priceUsdCents, toCliAlias, } from "./cost-pricing.js";
|
|
25
25
|
import { extractResetTime, isCapacityExhausted, isGitProvisionContention, } from "./failure-classifier.js";
|
|
26
26
|
import { getEngine } from "./engines/registry.js";
|
|
@@ -46,18 +46,25 @@ import { parseConflictMeta, resolveConflict, MAX_CONFLICT_ATTEMPTS, } from "./ru
|
|
|
46
46
|
import { parseReworkMeta, renderReworkPrompt, reconcileReworkBranch as defaultReconcileReworkBranch, fetchPrHeadRef as defaultFetchPrHeadRef, } from "./runtime/rework.js";
|
|
47
47
|
import { assertResolutionIntegrity, isConflictResolutionTask, summariseFailures, } from "./runtime/resolution-integrity.js";
|
|
48
48
|
import { runPreprGate as defaultRunPreprGate, } from "./prepr-gate.js";
|
|
49
|
+
import { revalidateMigrationSeq as defaultRevalidateMigrationSeq, } from "./migration-seq-guard.js";
|
|
49
50
|
import { commitGuardSkipRequested, gitConfigHooksEnv, installCommitGuardSync, } from "./commit-guard.js";
|
|
50
51
|
import { readLiveAllowedGlobs } from "./locks/index.js";
|
|
51
52
|
const LOG_BATCH_BYTES = 4 * 1024;
|
|
52
|
-
// M2 (ENGINE-EXTRACT): the coding engine this runner drives. Wave 1
|
|
53
|
-
//
|
|
54
|
-
// and health-probe behavior
|
|
55
|
-
// now owned by src/engines/claude-code.ts.
|
|
56
|
-
|
|
53
|
+
// M2 (ENGINE-EXTRACT): the coding engine this runner drives. Wave 1 resolved
|
|
54
|
+
// this to a single module-level `claude-code` constant — spawn, argv,
|
|
55
|
+
// result-parse, failure-class, and health-probe behavior were byte-identical
|
|
56
|
+
// to the pre-refactor inline logic now owned by src/engines/claude-code.ts.
|
|
57
|
+
//
|
|
58
|
+
// NS MM-2 (BATCH-ENGINE-DISPATCH): a single module-level engine can no longer
|
|
59
|
+
// be right — a fleet with codex/gemini flagged on runs different tasks on
|
|
60
|
+
// different engines. Every call site below now resolves `getEngine(task.engine)`
|
|
61
|
+
// per task (default param omitted ⇒ `getEngine()` ⇒ claude-code, so a task with
|
|
62
|
+
// no engine stamped — or a runner that never sees a non-default id — is
|
|
63
|
+
// byte-identical to Wave 1).
|
|
57
64
|
// v0.6.0 REG-296: argv builder for `claude --print`, re-exported from the
|
|
58
65
|
// claude-code engine so the historical import surface is preserved.
|
|
59
66
|
export { buildClaudeArgs } from "./engines/claude-code.js";
|
|
60
|
-
function defaultSpawnClaude(cwd, modelId) {
|
|
67
|
+
function defaultSpawnClaude(cwd, modelId, engineId) {
|
|
61
68
|
// RR-4: install the commit-time mechanical guard into this
|
|
62
69
|
// worktree and point the agent's git at it via an ephemeral core.hooksPath env
|
|
63
70
|
// (never a config-file write). The agent then hits an actionable in-session
|
|
@@ -76,7 +83,7 @@ function defaultSpawnClaude(cwd, modelId) {
|
|
|
76
83
|
baseEnv = undefined;
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
|
-
return
|
|
86
|
+
return getEngine(engineId).spawn({ cwd, modelId, baseEnv });
|
|
80
87
|
}
|
|
81
88
|
async function defaultCheckoutBase(repoPath, baseBranch) {
|
|
82
89
|
// Plain `git checkout <branch>` (not `-B`) so the integration
|
|
@@ -306,8 +313,8 @@ export async function defaultReclaimStaleConflictLocks(supabase, conflicts, nowM
|
|
|
306
313
|
}
|
|
307
314
|
return reclaimed;
|
|
308
315
|
}
|
|
309
|
-
async function defaultHealthProbe() {
|
|
310
|
-
return
|
|
316
|
+
async function defaultHealthProbe(engineId) {
|
|
317
|
+
return getEngine(engineId).healthProbe();
|
|
311
318
|
}
|
|
312
319
|
const DEFAULT_CLAUDE_UNAVAILABLE_KNOBS = {
|
|
313
320
|
windowMs: 15 * 60_000,
|
|
@@ -413,9 +420,14 @@ async function streamToEvents(stream, supabase, taskId, streamName) {
|
|
|
413
420
|
* both `--output-format=json` (extracts .result) and plain text. Falls
|
|
414
421
|
* back to the entire stdout (capped) if no `## Summary` heading is
|
|
415
422
|
* present — that way the PR body still tells the reviewer what happened.
|
|
423
|
+
*
|
|
424
|
+
* NS MM-2: `engineId` is optional and appended last so every pre-existing
|
|
425
|
+
* caller (api/github/webhook.ts, the packages/acc-runner test suite) keeps
|
|
426
|
+
* compiling unchanged and keeps resolving `claude-code` — only the in-file
|
|
427
|
+
* per-task call site now forwards `task.engine`.
|
|
416
428
|
*/
|
|
417
|
-
export function extractReportFromOutput(stdout) {
|
|
418
|
-
const parsed =
|
|
429
|
+
export function extractReportFromOutput(stdout, engineId) {
|
|
430
|
+
const parsed = getEngine(engineId).parseResult(stdout);
|
|
419
431
|
const text = parsed?.result ?? stdout;
|
|
420
432
|
const idx = text.indexOf("## Summary");
|
|
421
433
|
if (idx === -1) {
|
|
@@ -505,9 +517,13 @@ async function emitFirstCommit(supabase, git, taskId, workdir, baseRef, branch)
|
|
|
505
517
|
* Build a cost event from claude's stdout. Always returns a payload
|
|
506
518
|
* (zero-cost when usage isn't parseable) so the cap-tracking surface
|
|
507
519
|
* stays consistent: one cost_events row per attempt.
|
|
520
|
+
*
|
|
521
|
+
* NS MM-2: `engineId` is optional and appended last for the same reason as
|
|
522
|
+
* `extractReportFromOutput` — every pre-existing caller keeps resolving
|
|
523
|
+
* `claude-code` unchanged.
|
|
508
524
|
*/
|
|
509
|
-
export function buildCostEvent(taskId, stdout, fallbackModel, runnerId) {
|
|
510
|
-
const parsed =
|
|
525
|
+
export function buildCostEvent(taskId, stdout, fallbackModel, runnerId, engineId) {
|
|
526
|
+
const parsed = getEngine(engineId).parseResult(stdout);
|
|
511
527
|
const model = parsed?.model ?? fallbackModel ?? "unknown";
|
|
512
528
|
const usage = normalizeUsage(parsed?.usage);
|
|
513
529
|
return {
|
|
@@ -713,7 +729,11 @@ async function runConflictResolutionPath(taskId, task, deps, postState_, appendE
|
|
|
713
729
|
export function runTask(taskId, deps) {
|
|
714
730
|
const git = deps.git ?? defaultGit;
|
|
715
731
|
const gh = deps.gh ?? defaultGh;
|
|
716
|
-
|
|
732
|
+
// NS MM-2: `spawnClaude`'s default needs the FETCHED task's `engine` column,
|
|
733
|
+
// which isn't known this early (claim/transition haven't run yet) — its
|
|
734
|
+
// binding moves below, right after `const { task } = result;`, so the
|
|
735
|
+
// default resolves `getEngine(task.engine)` instead of the fixed default.
|
|
736
|
+
// `deps.spawnClaude` (test override) is unaffected either way.
|
|
717
737
|
const checkoutBase = deps.checkoutBase ?? defaultCheckoutBase;
|
|
718
738
|
const acquireLock = deps.acquireLock ?? defaultAcquireTaskLock;
|
|
719
739
|
const prepareWorktree = deps.prepareWorktree ?? defaultPrepareTaskWorktree;
|
|
@@ -723,10 +743,13 @@ export function runTask(taskId, deps) {
|
|
|
723
743
|
((args) => defaultPostRunnerStateMessage(deps.supabase, args));
|
|
724
744
|
const loadProfile = deps.loadProfile ?? defaultLoadProfile;
|
|
725
745
|
const loadPromptPreludeFn = deps.loadPromptPrelude ?? loadPromptPrelude;
|
|
726
|
-
|
|
746
|
+
// NS MM-2: same reason as `spawnClaude` above — `healthProbe`'s default
|
|
747
|
+
// binds below, after the task (and its `engine`) is fetched.
|
|
727
748
|
const runPreprGate = deps.runPreprGate ??
|
|
728
749
|
((workdir, baseRef) => defaultRunPreprGate(workdir, { baseRef }));
|
|
729
750
|
const reconcileRework = deps.reconcileReworkBranch ?? defaultReconcileReworkBranch;
|
|
751
|
+
const revalidateSeq = deps.revalidateMigrationSeq ??
|
|
752
|
+
((workdir, opts) => defaultRevalidateMigrationSeq(workdir, opts));
|
|
730
753
|
// FU-9: default seeds the worktree from the warm parent clone (see
|
|
731
754
|
// ensureWorktreeNodeModules); logs via the runner's stderr so a doctor can see it.
|
|
732
755
|
const ensureWorktreeDeps = deps.ensureWorktreeDeps ??
|
|
@@ -999,7 +1022,21 @@ export function runTask(taskId, deps) {
|
|
|
999
1022
|
return { taskId, status: "failed", phase: "fetch", error: msg };
|
|
1000
1023
|
}
|
|
1001
1024
|
const result = fetched.data;
|
|
1002
|
-
const
|
|
1025
|
+
const task = result.task;
|
|
1026
|
+
// NS MM-2 (BATCH-ENGINE-DISPATCH): resolve this task's build engine ONCE,
|
|
1027
|
+
// right after the task itself is known — the router (NS MM-1) stamps
|
|
1028
|
+
// `engine` alongside `model_id` on every dispatch path, defaulting to
|
|
1029
|
+
// 'claude-code' on the DB column. `getEngine(null)` (an unstamped/
|
|
1030
|
+
// pre-MM-1 row) resolves the same default Wave 1 always used, so this is
|
|
1031
|
+
// byte-identical for the all-claude-code fleet. Every spawn/parse/classify
|
|
1032
|
+
// call below drives THIS engine instead of a fixed module-level default.
|
|
1033
|
+
const taskEngineId = task.engine ?? null;
|
|
1034
|
+
const taskEngine = getEngine(taskEngineId);
|
|
1035
|
+
// `spawnClaude`/`healthProbe` bind here (not in the sync prologue above)
|
|
1036
|
+
// because their defaults need `taskEngineId`, which isn't known until now.
|
|
1037
|
+
const spawnClaude = deps.spawnClaude ??
|
|
1038
|
+
((cwd, modelId) => defaultSpawnClaude(cwd, modelId, taskEngineId));
|
|
1039
|
+
const healthProbe = deps.healthProbe ?? (() => defaultHealthProbe(taskEngineId));
|
|
1003
1040
|
// LOCK-LIFECYCLE-WIRE: re-read the task's CURRENT file
|
|
1004
1041
|
// scope from the live row so an operator who narrowed a QUEUED task between
|
|
1005
1042
|
// dispatch and claim has that narrowing honoured for the agent's scope
|
|
@@ -1139,13 +1176,27 @@ export function runTask(taskId, deps) {
|
|
|
1139
1176
|
// guarded clone step below, inside the provision-mutex. The repo_path_hint
|
|
1140
1177
|
// operator pin always wins; with the flag off this is byte-identical to
|
|
1141
1178
|
// the historic `task.repo_path_hint ?? cfg.repoPath` resolution.
|
|
1179
|
+
//
|
|
1180
|
+
// LOCAL-REPO-CLI: a local repo (`local.<frag>/<name>`, migration 0318) has no
|
|
1181
|
+
// remote to clone from — its only copy is the working directory the operator
|
|
1182
|
+
// pointed `acc repo add-local` at, recorded in this machine's config.json.
|
|
1183
|
+
// So its path comes from cfg.localRepos and, critically, the clone cache is
|
|
1184
|
+
// suppressed: cloning a `local.…` slug would try to reach a GitHub repo that
|
|
1185
|
+
// does not exist. An explicit repo_path_hint still wins (an operator pin is
|
|
1186
|
+
// always the last word), and a task for a repo this machine has no local
|
|
1187
|
+
// entry for resolves exactly as before.
|
|
1142
1188
|
const repoPathHint = task.repo_path_hint?.trim();
|
|
1143
|
-
const
|
|
1189
|
+
const localRepoPath = repoPathHint
|
|
1190
|
+
? null
|
|
1191
|
+
: localRepoPathFor(deps.cfg, task.repo);
|
|
1192
|
+
const useCloneCache = !repoPathHint && !localRepoPath && !!deps.cfg.cloneOnDemand && !!task.repo?.trim();
|
|
1144
1193
|
const repoPath = repoPathHint
|
|
1145
1194
|
? expandHomePath(repoPathHint)
|
|
1146
|
-
:
|
|
1147
|
-
?
|
|
1148
|
-
:
|
|
1195
|
+
: localRepoPath
|
|
1196
|
+
? expandHomePath(localRepoPath)
|
|
1197
|
+
: useCloneCache
|
|
1198
|
+
? clonePathForRepo(task.repo.trim())
|
|
1199
|
+
: deps.cfg.repoPath;
|
|
1149
1200
|
const targetRepo = task.repo?.trim() || deps.cfg.targetRepo;
|
|
1150
1201
|
// REG-301: branch base comes from task → cfg → main. cfg.integrationBranch
|
|
1151
1202
|
// already defaults to "acc/integration" so the third fallback only
|
|
@@ -1318,6 +1369,62 @@ export function runTask(taskId, deps) {
|
|
|
1318
1369
|
error: `conflict-resolution integrity guardrail rejected resolution (${summary})`,
|
|
1319
1370
|
};
|
|
1320
1371
|
};
|
|
1372
|
+
/**
|
|
1373
|
+
* MIG-SEQ-GUARD: last thing before `git push`, re-validate this branch's
|
|
1374
|
+
* NEW migration seq against a FRESHLY-FETCHED integration head. The seq was
|
|
1375
|
+
* chosen at worktree-fork time; by push time a sibling migration may have
|
|
1376
|
+
* merged and taken it, and until now that only surfaced as a migration-lint
|
|
1377
|
+
* failure on the PR — a whole run spent on a rename the runner can do
|
|
1378
|
+
* itself. When the number is gone the migration is renumbered to the next
|
|
1379
|
+
* free contiguous seq (filename + self-record + verify messages + every
|
|
1380
|
+
* test that reads it by path) and committed, so the push carries the fix.
|
|
1381
|
+
*
|
|
1382
|
+
* Never blocks: the guard is fail-open by construction and this returns
|
|
1383
|
+
* void. A renumber emits `migration.seq_renumbered` so the move is visible
|
|
1384
|
+
* on the History tab rather than being a silent rewrite.
|
|
1385
|
+
*/
|
|
1386
|
+
const runMigrationSeqGuard = async (rework) => {
|
|
1387
|
+
let result;
|
|
1388
|
+
try {
|
|
1389
|
+
result = await revalidateSeq(workdir, {
|
|
1390
|
+
integrationBranch,
|
|
1391
|
+
baseRef: resolvedBaseRef,
|
|
1392
|
+
remote: "origin",
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
catch (err) {
|
|
1396
|
+
// The guard already fails open internally; this catches a stub/dep that
|
|
1397
|
+
// throws so a seq check can never be the reason a push does not happen.
|
|
1398
|
+
process.stderr.write(`[acc-runner] migration seq guard crashed (push proceeds): ` +
|
|
1399
|
+
`${err.message?.slice(0, 200)}\n`);
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
if (!result.renumbered || !result.move) {
|
|
1403
|
+
// Quiet on the no-op path — the overwhelming majority of pushes touch
|
|
1404
|
+
// no migration at all, and an event per push would be pure noise.
|
|
1405
|
+
if (result.skipped && result.skipped !== "no-new-migrations") {
|
|
1406
|
+
process.stderr.write(`[acc-runner] migration seq guard: ${result.detail}\n`);
|
|
1407
|
+
}
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
await appendEvent(deps.supabase, taskId, "log", {
|
|
1411
|
+
phase: "push",
|
|
1412
|
+
stream: "stdout",
|
|
1413
|
+
event: "migration.seq_renumbered",
|
|
1414
|
+
branch,
|
|
1415
|
+
rework,
|
|
1416
|
+
integration_branch: integrationBranch,
|
|
1417
|
+
from_seq: result.move.fromSeq,
|
|
1418
|
+
to_seq: result.move.toSeq,
|
|
1419
|
+
file: result.move.file,
|
|
1420
|
+
new_file: result.move.newFile,
|
|
1421
|
+
remote_max_seq: result.remoteMaxSeq ?? null,
|
|
1422
|
+
message: result.detail,
|
|
1423
|
+
renumber_output: result.move.output,
|
|
1424
|
+
runner_id: deps.session.runnerId,
|
|
1425
|
+
});
|
|
1426
|
+
process.stderr.write(`[acc-runner] migration seq guard: ${result.detail}\n`);
|
|
1427
|
+
};
|
|
1321
1428
|
try {
|
|
1322
1429
|
// POOL-P1b: clone-on-demand source provisioning. When the flag is on and
|
|
1323
1430
|
// repoPath resolved to the per-repo cache path above, clone the task's
|
|
@@ -1715,7 +1822,7 @@ export function runTask(taskId, deps) {
|
|
|
1715
1822
|
// cancellation, retry, and non-zero exit alike. Best-effort: a
|
|
1716
1823
|
// failed POST logs to stderr but never bubbles past the runner.
|
|
1717
1824
|
{
|
|
1718
|
-
const event = buildCostEvent(taskId, capturedStdout, result.model?.id, result.runner?.id);
|
|
1825
|
+
const event = buildCostEvent(taskId, capturedStdout, result.model?.id, result.runner?.id, taskEngineId);
|
|
1719
1826
|
try {
|
|
1720
1827
|
await postCostEvent(event);
|
|
1721
1828
|
}
|
|
@@ -1754,7 +1861,7 @@ export function runTask(taskId, deps) {
|
|
|
1754
1861
|
// claude_unavailable only, so this exit-0 capacity case never retries.
|
|
1755
1862
|
const classifyIfFailed = (a) => {
|
|
1756
1863
|
if (a.exitCode !== 0) {
|
|
1757
|
-
return
|
|
1864
|
+
return taskEngine.classifyFailure(a.exitCode, a.stderr, a.stdout, a.durationMs);
|
|
1758
1865
|
}
|
|
1759
1866
|
if (isCapacityExhausted(a.stdout, a.stderr)) {
|
|
1760
1867
|
return {
|
|
@@ -1809,7 +1916,7 @@ export function runTask(taskId, deps) {
|
|
|
1809
1916
|
// because v0.63's health probe can upgrade a claude_unavailable exit
|
|
1810
1917
|
// to env_broken when the probe proves the host is genuinely broken.
|
|
1811
1918
|
let cls = classified ??
|
|
1812
|
-
|
|
1919
|
+
taskEngine.classifyFailure(attempt.exitCode, attempt.stderr, attempt.stdout, attempt.durationMs);
|
|
1813
1920
|
// FH-1a: capacity exhaustion mid-task is NOT a task failure — but it
|
|
1814
1921
|
// must not ABANDON the in-flight task either. v0.56 left the task
|
|
1815
1922
|
// 'running' with its signal loop stopped (the outer finally) so the
|
|
@@ -2166,6 +2273,11 @@ export function runTask(taskId, deps) {
|
|
|
2166
2273
|
return { taskId, status: "failed", phase: "push", error: msg };
|
|
2167
2274
|
}
|
|
2168
2275
|
}
|
|
2276
|
+
// MIG-SEQ-GUARD: after the no-advance check (which compares heads) and
|
|
2277
|
+
// immediately before the push, so the freshest possible integration head
|
|
2278
|
+
// decides the seq. A renumber adds a commit, which is also what makes the
|
|
2279
|
+
// advance check above meaningful on a rework that only moved a number.
|
|
2280
|
+
await runMigrationSeqGuard(true);
|
|
2169
2281
|
try {
|
|
2170
2282
|
await git.push(workdir, branch);
|
|
2171
2283
|
}
|
|
@@ -2325,6 +2437,11 @@ export function runTask(taskId, deps) {
|
|
|
2325
2437
|
runner_id: deps.session.runnerId,
|
|
2326
2438
|
});
|
|
2327
2439
|
}
|
|
2440
|
+
// MIG-SEQ-GUARD: the LAST thing before the push. It deliberately runs
|
|
2441
|
+
// after the pre-PR gate rather than inside it — the gate's own MIGFIX-01
|
|
2442
|
+
// renumber reads the fork-time worktree snapshot, and only a fresh fetch
|
|
2443
|
+
// here can see a sibling that merged while the gate was running.
|
|
2444
|
+
await runMigrationSeqGuard(false);
|
|
2328
2445
|
// 5. Push + open PR. Both run from the worktree so the operator's
|
|
2329
2446
|
// shared clone never has the task branch checked out.
|
|
2330
2447
|
let prUrl = "";
|
|
@@ -2380,7 +2497,7 @@ export function runTask(taskId, deps) {
|
|
|
2380
2497
|
branch,
|
|
2381
2498
|
});
|
|
2382
2499
|
try {
|
|
2383
|
-
const report = extractReportFromOutput(capturedStdout);
|
|
2500
|
+
const report = extractReportFromOutput(capturedStdout, taskEngineId);
|
|
2384
2501
|
// V96 (R4): generate the PR-body "Files changed" table FROM the actual
|
|
2385
2502
|
// pushed diff at PR-open so the body can never contradict the diff (a
|
|
2386
2503
|
// hand-listed file that drifted from reality). Best-effort — if the
|