agent-dealer 1.2.7 → 1.2.9
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/bundle/server/dist/adapters/agent-deck-bind.js +16 -6
- package/bundle/server/dist/adapters/agent-deck-bind.test.js +74 -0
- package/bundle/server/dist/adapters/agent-health.js +14 -3
- package/bundle/server/dist/adapters/github.js +84 -5
- package/bundle/server/dist/adapters/github.test.js +83 -1
- package/bundle/server/dist/adapters/muse-capability.js +443 -24
- package/bundle/server/dist/adapters/muse-capability.test.js +469 -25
- package/bundle/server/dist/adapters/muse-visual-qa.js +114 -0
- package/bundle/server/dist/adapters/muse-visual-qa.test.js +68 -0
- package/bundle/server/dist/adapters/worktree-deps.js +96 -0
- package/bundle/server/dist/adapters/worktree-deps.test.js +144 -0
- package/bundle/server/dist/capacity/muse-probe.js +56 -9
- package/bundle/server/dist/capacity/muse-probe.test.js +188 -1
- package/bundle/server/dist/coordinator/admission.js +14 -1
- package/bundle/server/dist/coordinator/admission.test.js +197 -4
- package/bundle/server/dist/coordinator/auto-merge.integration.test.js +276 -0
- package/bundle/server/dist/coordinator/auto-merge.js +244 -4
- package/bundle/server/dist/coordinator/checks-wait-config.js +19 -0
- package/bundle/server/dist/coordinator/commands.js +284 -23
- package/bundle/server/dist/coordinator/commands.test.js +47 -0
- package/bundle/server/dist/coordinator/deck-outage.integration.test.js +4 -2
- package/bundle/server/dist/coordinator/developer-effect.js +224 -4
- package/bundle/server/dist/coordinator/developer-effect.test.js +288 -3
- package/bundle/server/dist/coordinator/execution-report.js +6 -0
- package/bundle/server/dist/coordinator/execution-report.test.js +7 -0
- package/bundle/server/dist/coordinator/failure-cause.js +64 -1
- package/bundle/server/dist/coordinator/failure-cause.test.js +92 -1
- package/bundle/server/dist/coordinator/failure-reason.js +7 -0
- package/bundle/server/dist/coordinator/failure-reason.test.js +14 -0
- package/bundle/server/dist/coordinator/human-resolution.js +36 -1
- package/bundle/server/dist/coordinator/human-resolution.test.js +33 -0
- package/bundle/server/dist/coordinator/merge-conflict-sync.js +647 -0
- package/bundle/server/dist/coordinator/merge-conflict-sync.test.js +121 -0
- package/bundle/server/dist/coordinator/muse-developer.integration.test.js +9 -4
- package/bundle/server/dist/coordinator/muse-spawn.js +122 -29
- package/bundle/server/dist/coordinator/muse-spawn.test.js +210 -0
- package/bundle/server/dist/coordinator/operator-criteria.js +168 -0
- package/bundle/server/dist/coordinator/operator-criteria.test.js +78 -0
- package/bundle/server/dist/coordinator/operator-verification.integration.test.js +347 -0
- package/bundle/server/dist/coordinator/playbook-feedback.js +693 -0
- package/bundle/server/dist/coordinator/playbook-feedback.test.js +702 -0
- package/bundle/server/dist/coordinator/projection.js +10 -4
- package/bundle/server/dist/coordinator/projection.test.js +13 -0
- package/bundle/server/dist/coordinator/prompts-execution-contract.test.js +107 -0
- package/bundle/server/dist/coordinator/prompts.js +211 -2
- package/bundle/server/dist/coordinator/prompts.test.js +231 -0
- package/bundle/server/dist/coordinator/recovery.js +57 -3
- package/bundle/server/dist/coordinator/recovery.test.js +67 -8
- package/bundle/server/dist/coordinator/reflect-trigger.js +33 -169
- package/bundle/server/dist/coordinator/reflect-trigger.test.js +149 -200
- package/bundle/server/dist/coordinator/repair-cycle.integration.test.js +1 -0
- package/bundle/server/dist/coordinator/reviewer-effect.js +16 -1
- package/bundle/server/dist/coordinator/reviewer-result.js +6 -0
- package/bundle/server/dist/coordinator/routing.js +45 -6
- package/bundle/server/dist/coordinator/routing.test.js +76 -15
- package/bundle/server/dist/coordinator/session-timeouts.js +30 -0
- package/bundle/server/dist/coordinator/usage-cap-defer.js +61 -0
- package/bundle/server/dist/coordinator/usage-cap.integration.test.js +1 -1
- package/bundle/server/dist/coordinator/worker-loop.js +18 -4
- package/bundle/server/dist/db/ci-attempts-migration.test.js +70 -0
- package/bundle/server/dist/db/index.js +18 -0
- package/bundle/server/dist/db/schema.sql +5 -0
- package/bundle/server/dist/docs-execution-analysis.test.js +1 -0
- package/bundle/server/dist/repository/artifacts-for-issue.js +3 -3
- package/bundle/server/dist/repository/human-actions.js +14 -0
- package/bundle/server/dist/repository/issues.js +67 -6
- package/bundle/server/dist/repository/work-items.js +7 -0
- package/bundle/server/dist/repository/worker-sessions.js +51 -2
- package/bundle/server/dist/routes/human-actions.js +10 -8
- package/bundle/server/dist/routes/issues-execution-contract.test.js +321 -0
- package/bundle/server/dist/routes/issues.js +19 -2
- package/bundle/server/dist/runners/muse-code-args.js +25 -0
- package/bundle/server/dist/runners/muse-code-args.test.js +30 -0
- package/bundle/server/dist/runners/muse-code-jsonl.js +106 -11
- package/bundle/server/dist/runners/muse-config-core.js +18 -1
- package/bundle/server/dist/runners/muse-config.test.js +37 -0
- package/bundle/server/dist/runners/muse-serve-session.js +5 -0
- package/bundle/server/dist/runners/spawn-cli.js +116 -0
- package/bundle/server/dist/runners/spawn-cli.test.js +124 -0
- package/bundle/server/package.json +2 -2
- package/bundle/server/static-ui/assets/index-CU6fNntg.js +64 -0
- package/bundle/server/static-ui/assets/{index-DyAJNyfV.css → index-K_YcYkQU.css} +1 -1
- package/bundle/server/static-ui/index.html +2 -2
- package/bundle/shared/dist/execution-analysis.d.ts +22 -22
- package/bundle/shared/dist/execution-contract.d.ts +117 -0
- package/bundle/shared/dist/execution-contract.js +307 -0
- package/bundle/shared/dist/execution-contract.test.d.ts +2 -0
- package/bundle/shared/dist/execution-contract.test.js +499 -0
- package/bundle/shared/dist/execution-report.d.ts +9 -9
- package/bundle/shared/dist/execution-report.js +2 -0
- package/bundle/shared/dist/failure-cause.d.ts +4 -4
- package/bundle/shared/dist/failure-cause.js +2 -0
- package/bundle/shared/dist/human-actions.d.ts +4 -4
- package/bundle/shared/dist/human-actions.js +17 -0
- package/bundle/shared/dist/index.d.ts +85 -84
- package/bundle/shared/dist/index.js +3 -0
- package/bundle/shared/dist/issues.d.ts +176 -16
- package/bundle/shared/dist/issues.js +22 -0
- package/bundle/shared/dist/issues.test.js +3 -0
- package/bundle/shared/dist/outbound-draft.d.ts +12 -12
- package/bundle/shared/dist/worker-sessions.d.ts +10 -0
- package/bundle/shared/dist/worker-sessions.js +8 -0
- package/bundle/shared/dist/worker-sessions.test.js +1 -0
- package/bundle/shared/dist/workflow.d.ts +4 -4
- package/bundle/shared/dist/workflow.js +16 -0
- package/bundle/shared/dist/workflow.test.js +3 -0
- package/bundle/shared/package.json +1 -1
- package/dist/action.js +4 -1
- package/dist/action.test.js +42 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/bundle/server/static-ui/assets/index-yLyxRd-7.js +0 -64
|
@@ -18,16 +18,22 @@ import os from "node:os";
|
|
|
18
18
|
import path from "node:path";
|
|
19
19
|
import { parsePhaseBudget, parseProfileSnapshot, roleCeiling } from "@agent-dealer/shared";
|
|
20
20
|
import { getTaskSnapshot } from "./commands.js";
|
|
21
|
+
import { extractOperatorCriteria } from "./operator-criteria.js";
|
|
21
22
|
import { buildDeveloperPrompt } from "./prompts.js";
|
|
22
23
|
import { guidanceForNextSession } from "./guidance.js";
|
|
23
24
|
import { realDeveloperSpawn, developerSessionLogPath } from "./spawn.js";
|
|
25
|
+
import { ensureWorktreeDeps, worktreeDepsTimeoutMs, } from "../adapters/worktree-deps.js";
|
|
24
26
|
import { DEFAULT_BASE_FETCH_TIMEOUT_MS, fastForwardLocalBranchToSha, resolveDeveloperWorktree, safeRemoveWorktree, isWorktreeClean, commitsAhead, pushBranch, mergeBase, branchExists, pushBranchRef, revParseHead, revParseRef, fetchRef, salvageDirtyWorktree, dirtyWorktreeRecoveryCommands, withRepoLock, leftoverFingerprint, } from "../adapters/git-worktree.js";
|
|
25
27
|
import { recordIssueBaseSha } from "../repository/issues.js";
|
|
26
28
|
import { ensureIssueRepoCheckout, roleWorktreePathForResolution, resolveCheckoutBaseBranch, } from "../adapters/managed-repo.js";
|
|
27
29
|
import { baseRefCandidates, inspectBranchProgress } from "./branch-progress.js";
|
|
28
30
|
import { prepareWorkerDeckConnection, releaseWorkerDeckConnection, verifyWorkerDeckConnection } from "../adapters/agent-deck-bind.js";
|
|
29
|
-
import { realGithubAdapter,
|
|
30
|
-
import {
|
|
31
|
+
import { realGithubAdapter, pollPrChecksDetailed } from "../adapters/github.js";
|
|
32
|
+
import { checksWaitCeilingExceeded, checksWaitCeilingMs } from "./checks-wait-config.js";
|
|
33
|
+
import { museCapabilitySafetyNetAfterSession } from "../adapters/muse-capability.js";
|
|
34
|
+
import { getOrAssignSessionCorrelationId, getWorkerSession, mergeSessionMetadata, patchRunningSession, recordSessionProcess, setSessionInputSha } from "../repository/worker-sessions.js";
|
|
35
|
+
import { museIdleMinutes, museStallMetadata } from "./muse-spawn.js";
|
|
36
|
+
import { museIdleTimeoutMs } from "./session-timeouts.js";
|
|
31
37
|
import { COORDINATOR_PROCESS_OWNER, readProcessStartTime } from "./process-liveness.js";
|
|
32
38
|
import { checkDeveloperWorktreeOwnerLiveness } from "./worktree-owner-liveness.js";
|
|
33
39
|
import { developerSessionTimeoutMs } from "./session-timeouts.js";
|
|
@@ -87,6 +93,23 @@ async function crashOrTimeoutOutcome(opts) {
|
|
|
87
93
|
...(opts.logPath ? { logPath: opts.logPath } : {}),
|
|
88
94
|
};
|
|
89
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* NOT-307: idle-kill description for the timeout reason, or null for anything else
|
|
98
|
+
* (wall-clock timeout, crash, success). Minutes prefer the measured last-activity
|
|
99
|
+
* time and fall back to the configured bound; the last tool name rides along so the
|
|
100
|
+
* recorded reason — and the `muse_no_progress` classifier — names the stall point.
|
|
101
|
+
*/
|
|
102
|
+
function idleCrashInfoFor(spawned) {
|
|
103
|
+
if (!spawned.idleTimedOut)
|
|
104
|
+
return null;
|
|
105
|
+
return {
|
|
106
|
+
minutes: museIdleMinutes({
|
|
107
|
+
lastActivityAt: spawned.muse?.lastActivityAt ?? null,
|
|
108
|
+
idleTimeoutMs: museIdleTimeoutMs(),
|
|
109
|
+
}),
|
|
110
|
+
lastToolName: spawned.muse?.lastToolName ?? null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
90
113
|
export const developerEffectConfig = {
|
|
91
114
|
get sessionTimeoutMs() {
|
|
92
115
|
return developerSessionTimeoutMs();
|
|
@@ -97,10 +120,18 @@ export const developerEffectConfig = {
|
|
|
97
120
|
get checksPollIntervalMs() {
|
|
98
121
|
return num("CHECKS_POLL_INTERVAL_MS", 15_000);
|
|
99
122
|
},
|
|
123
|
+
/** NOT-311: overall CI-wait ceiling measured from the first poll, across deferrals. */
|
|
124
|
+
get checksWaitCeilingMs() {
|
|
125
|
+
return checksWaitCeilingMs();
|
|
126
|
+
},
|
|
100
127
|
/** NOT-197: bound for the pre-branch `git fetch origin <base>` on a fresh issue branch. */
|
|
101
128
|
get baseFetchTimeoutMs() {
|
|
102
129
|
return num("BASE_FETCH_TIMEOUT_MS", DEFAULT_BASE_FETCH_TIMEOUT_MS);
|
|
103
130
|
},
|
|
131
|
+
/** NOT-315: bound for `npm ci` in the fresh worktree (coordinator side, with network). */
|
|
132
|
+
get worktreeDepsTimeoutMs() {
|
|
133
|
+
return worktreeDepsTimeoutMs();
|
|
134
|
+
},
|
|
104
135
|
/** NOT-110: bounded window to let a lagging `gh pr view` catch up to a just-pushed HEAD. */
|
|
105
136
|
get headReconcileTimeoutMs() {
|
|
106
137
|
return num("HEAD_RECONCILE_TIMEOUT_MS", 30_000);
|
|
@@ -270,6 +301,69 @@ async function checksFailedOutcome(opts) {
|
|
|
270
301
|
});
|
|
271
302
|
return details ? { kind: "checks_failed", details } : { kind: "checks_failed" };
|
|
272
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* NOT-311: the `checksWaitStartedAt` persisted by a prior `checks_pending` deferral,
|
|
306
|
+
* anchoring the overall wait ceiling across re-polls. Null on a first wait (or an
|
|
307
|
+
* unparseable payload) — the caller then anchors at its own poll start.
|
|
308
|
+
*/
|
|
309
|
+
function checksWaitStartedAtFromPayload(payloadJson) {
|
|
310
|
+
if (!payloadJson)
|
|
311
|
+
return null;
|
|
312
|
+
try {
|
|
313
|
+
const v = JSON.parse(payloadJson).checksWaitStartedAt;
|
|
314
|
+
return typeof v === "string" && v ? v : null;
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* NOT-311: decide what a finished checks poll means. Returns a `checks_pending`
|
|
322
|
+
* outcome when CI is still queued/running at the timeout (or reports no checks while
|
|
323
|
+
* the base branch has a `pull_request` workflow — "not yet registered", never "no
|
|
324
|
+
* CI"), and null when the caller stays on today's path (success, failure, an aborted
|
|
325
|
+
* wait, or `none` with no workflow configured). Also yields the `checks.completed`
|
|
326
|
+
* milestone reason for the wait cases.
|
|
327
|
+
*
|
|
328
|
+
* A missing/unknown workflow answer (no adapter method, unreadable base ref) waits
|
|
329
|
+
* fail-closed: sending an unverified PR to review is the outcome this exists to
|
|
330
|
+
* prevent, and the wait ceiling still bounds it.
|
|
331
|
+
*/
|
|
332
|
+
async function resolveChecksWait(opts) {
|
|
333
|
+
const waitStartedAt = checksWaitStartedAtFromPayload(opts.payloadJson) ?? opts.pollStartedAt;
|
|
334
|
+
const base = {
|
|
335
|
+
kind: "checks_pending",
|
|
336
|
+
branch: opts.branch,
|
|
337
|
+
prNumber: opts.prNumber,
|
|
338
|
+
headSha: opts.headSha,
|
|
339
|
+
waitStartedAt,
|
|
340
|
+
};
|
|
341
|
+
const noChecks = opts.poll.result === "none" || (opts.poll.result === "timeout" && opts.poll.lastSnapshot === "none");
|
|
342
|
+
if (noChecks) {
|
|
343
|
+
let hasWorkflow = null;
|
|
344
|
+
try {
|
|
345
|
+
hasWorkflow = (await opts.github.hasPullRequestWorkflow?.({ cwd: opts.cwd, baseRef: opts.baseRef })) ?? null;
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
hasWorkflow = null;
|
|
349
|
+
}
|
|
350
|
+
if (hasWorkflow === false) {
|
|
351
|
+
return { pending: null, milestoneReason: "none_no_workflow" };
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
pending: { ...base, reason: "Waiting for CI (queued) - no attempt spent" },
|
|
355
|
+
milestoneReason: "none_with_workflow",
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
if (opts.poll.result === "timeout" && opts.poll.lastSnapshot === "pending" && !opts.poll.aborted && !opts.signal?.aborted) {
|
|
359
|
+
const milestoneReason = checksWaitCeilingExceeded(waitStartedAt) ? "pending_ceiling" : undefined;
|
|
360
|
+
return {
|
|
361
|
+
pending: { ...base, reason: "Waiting for CI (running) - no attempt spent" },
|
|
362
|
+
...(milestoneReason ? { milestoneReason } : {}),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return { pending: null };
|
|
366
|
+
}
|
|
273
367
|
/**
|
|
274
368
|
* Infra retry that skips the agent: publish whatever the branch already carries. Uses the
|
|
275
369
|
* issue repo as `gh` cwd and `origin/<branch>` as the verified head (any worktree was
|
|
@@ -508,17 +602,35 @@ async function runPublishOnlyHandoff(ctx, deps, branchName) {
|
|
|
508
602
|
prNumber: prView.number,
|
|
509
603
|
headSha: prView.headRefOid,
|
|
510
604
|
});
|
|
511
|
-
const
|
|
605
|
+
const pollStartedAt = new Date().toISOString();
|
|
606
|
+
const poll = await pollPrChecksDetailed(deps.github, {
|
|
512
607
|
cwd,
|
|
513
608
|
timeoutMs: developerEffectConfig.checksPollTimeoutMs,
|
|
514
609
|
intervalMs: developerEffectConfig.checksPollIntervalMs,
|
|
515
610
|
signal: ctx.signal,
|
|
516
611
|
number: prView.number,
|
|
517
612
|
});
|
|
613
|
+
// NOT-311: pending/queued CI at the timeout (or no checks yet with a
|
|
614
|
+
// `pull_request` workflow on the base) waits with backoff instead of spending
|
|
615
|
+
// an infra attempt — resolved here so the milestone carries the wait reason.
|
|
616
|
+
const wait = await resolveChecksWait({
|
|
617
|
+
github: deps.github,
|
|
618
|
+
cwd,
|
|
619
|
+
prNumber: prView.number,
|
|
620
|
+
baseRef: prView.baseRefName,
|
|
621
|
+
headSha: prView.headRefOid,
|
|
622
|
+
branch: branchName,
|
|
623
|
+
poll,
|
|
624
|
+
signal: ctx.signal,
|
|
625
|
+
payloadJson: workItem.payloadJson,
|
|
626
|
+
pollStartedAt,
|
|
627
|
+
});
|
|
628
|
+
const checks = poll.result;
|
|
518
629
|
milestone("checks.completed", `Developer · checks ${checks}`, {
|
|
519
630
|
snapshot: checks,
|
|
520
631
|
prNumber: prView.number,
|
|
521
632
|
headSha: prView.headRefOid,
|
|
633
|
+
...(wait.milestoneReason ? { reason: wait.milestoneReason } : {}),
|
|
522
634
|
});
|
|
523
635
|
const postPollView = await deps.github.viewPr({ cwd, branch: branchName });
|
|
524
636
|
if (!postPollView) {
|
|
@@ -555,6 +667,10 @@ async function runPublishOnlyHandoff(ctx, deps, branchName) {
|
|
|
555
667
|
author: "system",
|
|
556
668
|
content: { snapshot: checks, prNumber: prView.number, headSha: prView.headRefOid },
|
|
557
669
|
});
|
|
670
|
+
// NOT-311: still-waiting CI defers with backoff (no attempt spent) instead of
|
|
671
|
+
// timing out the session. An aborted wait keeps today's `timed_out`.
|
|
672
|
+
if (wait.pending)
|
|
673
|
+
return wait.pending;
|
|
558
674
|
if (checks === "timeout")
|
|
559
675
|
return { kind: "timed_out" };
|
|
560
676
|
await fetchRef(cwd, prView.baseRefName);
|
|
@@ -601,6 +717,9 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
601
717
|
}
|
|
602
718
|
const session = getWorkerSession(sessionId);
|
|
603
719
|
const snapshot = parseProfileSnapshot(session?.profileSnapshotJson);
|
|
720
|
+
// NOT-305: opaque per-session Deck correlation UUID, persisted before spawn and
|
|
721
|
+
// carried as observability metadata in every runtime launch config below.
|
|
722
|
+
const deckCorrelationId = session?.deckCorrelationId ?? getOrAssignSessionCorrelationId(sessionId);
|
|
604
723
|
let taskSnapshot = getTaskSnapshot(issue);
|
|
605
724
|
const runtime = snapshot?.runtime ?? "claude_code";
|
|
606
725
|
const round = workItem.round;
|
|
@@ -750,6 +869,18 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
750
869
|
recordIssueBaseSha(issue.id, resolved.baseSha);
|
|
751
870
|
issue.baseSha = resolved.baseSha;
|
|
752
871
|
}
|
|
872
|
+
// NOT-315: provision dependencies here, outside the sandbox, with network — the
|
|
873
|
+
// builder sandbox has none, so an in-session install can never work. A throw
|
|
874
|
+
// lands in the worktree-setup adapter_failure below: visible and
|
|
875
|
+
// infra-retryable, never a silent hour-long stall. The session is not spawned.
|
|
876
|
+
const depsResult = await ensureWorktreeDeps(worktreePath, {
|
|
877
|
+
runner: deps.depsRunner,
|
|
878
|
+
timeoutMs: developerEffectConfig.worktreeDepsTimeoutMs,
|
|
879
|
+
});
|
|
880
|
+
milestone("worktree.deps_ready", `Developer · dependencies ready (round ${round})`, {
|
|
881
|
+
...(depsResult.ran ? { ran: true } : { ran: false, reason: depsResult.reason }),
|
|
882
|
+
durationMs: depsResult.durationMs,
|
|
883
|
+
});
|
|
753
884
|
}
|
|
754
885
|
catch (err) {
|
|
755
886
|
return { kind: "adapter_failure", reason: `worktree setup failed: ${String(err)}` };
|
|
@@ -807,6 +938,7 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
807
938
|
worktreePath,
|
|
808
939
|
runtime,
|
|
809
940
|
policy,
|
|
941
|
+
correlationId: deckCorrelationId,
|
|
810
942
|
verifyCallTool: deps.deckCallTool,
|
|
811
943
|
});
|
|
812
944
|
if (!prepared.ok) {
|
|
@@ -862,6 +994,30 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
862
994
|
const scopeDecisionNote = typeof payload.scopeDecisionNote === "string" && payload.scopeDecisionNote.trim()
|
|
863
995
|
? payload.scopeDecisionNote.trim()
|
|
864
996
|
: undefined;
|
|
997
|
+
// NOT-314: same one-shot ride for an operator_verification repair note.
|
|
998
|
+
const operatorRepairNote = typeof payload.operatorRepairNote === "string" && payload.operatorRepairNote.trim()
|
|
999
|
+
? payload.operatorRepairNote.trim()
|
|
1000
|
+
: undefined;
|
|
1001
|
+
// NOT-314: the frozen snapshot's operator criteria, parsed here so the
|
|
1002
|
+
// worker never re-derives them — the section renders only when non-empty.
|
|
1003
|
+
const operatorCriteria = extractOperatorCriteria(taskSnapshot.acceptanceCriteria);
|
|
1004
|
+
const hasOperatorCriteria = operatorCriteria.length > 0 ? operatorCriteria : undefined;
|
|
1005
|
+
// NOT-310: the conflict directive rides only the work item queued by the merge
|
|
1006
|
+
// path — exactly this round reads it; nothing is re-read from the DB. Shaped
|
|
1007
|
+
// defensively: a malformed directive renders nothing rather than breaking the
|
|
1008
|
+
// prompt (conflictRepairSection also guards on empty base/branch).
|
|
1009
|
+
const conflictRepair = payload.conflictRepair &&
|
|
1010
|
+
typeof payload.conflictRepair === "object" &&
|
|
1011
|
+
typeof payload.conflictRepair.baseBranch === "string" &&
|
|
1012
|
+
typeof payload.conflictRepair.branch === "string"
|
|
1013
|
+
? {
|
|
1014
|
+
baseBranch: payload.conflictRepair.baseBranch,
|
|
1015
|
+
branch: payload.conflictRepair.branch,
|
|
1016
|
+
files: Array.isArray(payload.conflictRepair.files)
|
|
1017
|
+
? payload.conflictRepair.files.filter((f) => typeof f === "string")
|
|
1018
|
+
: [],
|
|
1019
|
+
}
|
|
1020
|
+
: undefined;
|
|
865
1021
|
let priorConclusion;
|
|
866
1022
|
let priorVerificationReceipt;
|
|
867
1023
|
if (retryReason) {
|
|
@@ -897,6 +1053,9 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
897
1053
|
museDeveloper: isMuse,
|
|
898
1054
|
guidance: guidance.length ? guidance : undefined,
|
|
899
1055
|
scopeDecisionNote,
|
|
1056
|
+
conflictRepair,
|
|
1057
|
+
operatorCriteria: hasOperatorCriteria,
|
|
1058
|
+
operatorRepairNote,
|
|
900
1059
|
});
|
|
901
1060
|
// NOT-83 review finding: the session is marked `running` (worker-loop.ts) before this
|
|
902
1061
|
// handler ever runs, so an abort landing during worktree setup/deck-bind above already
|
|
@@ -998,6 +1157,9 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
998
1157
|
// NOT-278: frozen profile deck → the Muse exec attempt (deck headers + pre-spawn
|
|
999
1158
|
// verify). Other runtimes carry their deck via mcpConfigPath/mcpEnv and ignore this.
|
|
1000
1159
|
deckId: snapshot?.deckId ?? null,
|
|
1160
|
+
// NOT-305: opaque per-session Deck correlation UUID → the Muse settings
|
|
1161
|
+
// `x-agent-deck-correlation-id` observability header.
|
|
1162
|
+
deckCorrelationId,
|
|
1001
1163
|
prompt,
|
|
1002
1164
|
cwd: worktreePath,
|
|
1003
1165
|
timeoutMs: developerEffectConfig.sessionTimeoutMs,
|
|
@@ -1069,6 +1231,18 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
1069
1231
|
// here on (usage, receipt, push, PR verify, checks poll) is post-spawn and must
|
|
1070
1232
|
// not be mislabeled as a session that could not start.
|
|
1071
1233
|
sessionStarted = true;
|
|
1234
|
+
// NOT-307: stall evidence for every Muse session (timed out or not) — merged
|
|
1235
|
+
// into metadata_json while the row is still running, so the classifier and the
|
|
1236
|
+
// operator can read last-activity evidence whatever the outcome. Best-effort:
|
|
1237
|
+
// evidence must never fail the attempt it describes.
|
|
1238
|
+
if (spawned.muse) {
|
|
1239
|
+
try {
|
|
1240
|
+
mergeSessionMetadata(sessionId, museStallMetadata(spawned.muse));
|
|
1241
|
+
}
|
|
1242
|
+
catch {
|
|
1243
|
+
// ignore
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1072
1246
|
// NOT-169: exactly one agent.completed per spawned process, at child exit and before
|
|
1073
1247
|
// any receipt mining, usage extraction, or validation. Raw outcome only — no
|
|
1074
1248
|
// failure classification.
|
|
@@ -1094,6 +1268,26 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
1094
1268
|
// ignore
|
|
1095
1269
|
}
|
|
1096
1270
|
}
|
|
1271
|
+
// NOT-308 safety net: a Muse session that left the worktree dirty without a
|
|
1272
|
+
// single shell tool call is what a shell-less build looks like from the outside —
|
|
1273
|
+
// when the running version is not yet the confirmed baseline, re-verify it in the
|
|
1274
|
+
// background immediately (bypassing the error backoff) and escalate a `missing`
|
|
1275
|
+
// verdict on this issue. Fire-and-forget: never throws, never changes this outcome.
|
|
1276
|
+
// Dirtiness is read here, before salvage/commit handling below can clean the tree.
|
|
1277
|
+
if (runtime === "muse_code") {
|
|
1278
|
+
try {
|
|
1279
|
+
const dirtyAtSessionEnd = !(await isWorktreeClean(worktreePath).catch(() => true));
|
|
1280
|
+
museCapabilitySafetyNetAfterSession({
|
|
1281
|
+
issueId: issue.id,
|
|
1282
|
+
runtime,
|
|
1283
|
+
logPath: spawned.logPath,
|
|
1284
|
+
dirty: dirtyAtSessionEnd,
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
catch {
|
|
1288
|
+
// Observational only — the session outcome stands on its own.
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1097
1291
|
// NOT-130: record suite evidence even when the session later fails/times out — an
|
|
1098
1292
|
// interrupted-but-verified tip must carry the receipt into the retry prompt.
|
|
1099
1293
|
const verificationReceipt = await persistVerificationReceiptIfAny({
|
|
@@ -1287,6 +1481,7 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
1287
1481
|
timedOut: Boolean(spawned.timedOut),
|
|
1288
1482
|
logPath: spawned.logPath,
|
|
1289
1483
|
runtime,
|
|
1484
|
+
idle: idleCrashInfoFor(spawned),
|
|
1290
1485
|
});
|
|
1291
1486
|
const salvageNote = `Salvaged uncommitted work as ${salvaged.message} (${salvaged.commitSha.slice(0, 7)}).`;
|
|
1292
1487
|
const crashOutcome = await crashOrTimeoutOutcome({
|
|
@@ -1341,6 +1536,7 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
1341
1536
|
timedOut: Boolean(spawned.timedOut),
|
|
1342
1537
|
logPath: spawned.logPath,
|
|
1343
1538
|
runtime,
|
|
1539
|
+
idle: idleCrashInfoFor(spawned),
|
|
1344
1540
|
}),
|
|
1345
1541
|
worktreePath,
|
|
1346
1542
|
logPath: spawned.logPath,
|
|
@@ -1536,7 +1732,8 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
1536
1732
|
prNumber: prView.number,
|
|
1537
1733
|
headSha: prView.headRefOid,
|
|
1538
1734
|
});
|
|
1539
|
-
const
|
|
1735
|
+
const pollStartedAt = new Date().toISOString();
|
|
1736
|
+
const poll = await pollPrChecksDetailed(deps.github, {
|
|
1540
1737
|
cwd: worktreePath,
|
|
1541
1738
|
timeoutMs: developerEffectConfig.checksPollTimeoutMs,
|
|
1542
1739
|
intervalMs: developerEffectConfig.checksPollIntervalMs,
|
|
@@ -1546,10 +1743,27 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
1546
1743
|
// back to a bare `gh pr view` either).
|
|
1547
1744
|
number: prView.number,
|
|
1548
1745
|
});
|
|
1746
|
+
// NOT-311: pending/queued CI at the timeout (or no checks yet with a
|
|
1747
|
+
// `pull_request` workflow on the base) waits with backoff instead of spending
|
|
1748
|
+
// an infra attempt — resolved here so the milestone carries the wait reason.
|
|
1749
|
+
const wait = await resolveChecksWait({
|
|
1750
|
+
github: deps.github,
|
|
1751
|
+
cwd: worktreePath,
|
|
1752
|
+
prNumber: prView.number,
|
|
1753
|
+
baseRef: prView.baseRefName,
|
|
1754
|
+
headSha: prView.headRefOid,
|
|
1755
|
+
branch: branchName,
|
|
1756
|
+
poll,
|
|
1757
|
+
signal: ctx.signal,
|
|
1758
|
+
payloadJson: workItem.payloadJson,
|
|
1759
|
+
pollStartedAt,
|
|
1760
|
+
});
|
|
1761
|
+
const checks = poll.result;
|
|
1549
1762
|
milestone("checks.completed", `Developer · checks ${checks}`, {
|
|
1550
1763
|
snapshot: checks,
|
|
1551
1764
|
prNumber: prView.number,
|
|
1552
1765
|
headSha: prView.headRefOid,
|
|
1766
|
+
...(wait.milestoneReason ? { reason: wait.milestoneReason } : {}),
|
|
1553
1767
|
});
|
|
1554
1768
|
// The poll can run for up to checksPollTimeoutMs (default 10 minutes) — re-fetch and
|
|
1555
1769
|
// re-validate identity against the SAME localHead before trusting either the checks
|
|
@@ -1597,6 +1811,12 @@ export async function runDeveloperEffect(ctx, deps = defaultDeps) {
|
|
|
1597
1811
|
author: "system",
|
|
1598
1812
|
content: { snapshot: checks, prNumber: prView.number, headSha: prView.headRefOid },
|
|
1599
1813
|
});
|
|
1814
|
+
// NOT-311: still-waiting CI defers with backoff (no attempt spent) instead of
|
|
1815
|
+
// timing out the session. An aborted wait keeps today's `timed_out`.
|
|
1816
|
+
if (wait.pending) {
|
|
1817
|
+
await bestEffortRemove(repoPath, worktreePath);
|
|
1818
|
+
return wait.pending;
|
|
1819
|
+
}
|
|
1600
1820
|
if (checks === "timeout") {
|
|
1601
1821
|
await bestEffortRemove(repoPath, worktreePath);
|
|
1602
1822
|
return { kind: "timed_out" };
|