agent-dealer 1.3.0 → 1.3.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.
Files changed (44) hide show
  1. package/bundle/server/dist/adapters/git-worktree.js +38 -1
  2. package/bundle/server/dist/adapters/github.js +50 -0
  3. package/bundle/server/dist/adapters/github.test.js +89 -1
  4. package/bundle/server/dist/coordinator/admission.test.js +115 -0
  5. package/bundle/server/dist/coordinator/auto-merge.js +46 -9
  6. package/bundle/server/dist/coordinator/commands.js +89 -9
  7. package/bundle/server/dist/coordinator/commands.test.js +228 -2
  8. package/bundle/server/dist/coordinator/developer-effect.js +36 -0
  9. package/bundle/server/dist/coordinator/operator-verification.integration.test.js +102 -1
  10. package/bundle/server/dist/coordinator/product-scope-contract.test.js +249 -0
  11. package/bundle/server/dist/coordinator/prompts.js +57 -2
  12. package/bundle/server/dist/coordinator/prompts.test.js +236 -2
  13. package/bundle/server/dist/coordinator/reviewer-browser-probe.js +1083 -0
  14. package/bundle/server/dist/coordinator/reviewer-browser-probe.test.js +780 -0
  15. package/bundle/server/dist/coordinator/reviewer-effect.js +30 -1
  16. package/bundle/server/dist/coordinator/reviewer-effect.test.js +54 -0
  17. package/bundle/server/dist/coordinator/reviewer-visual-evidence.js +594 -0
  18. package/bundle/server/dist/coordinator/reviewer-visual-evidence.test.js +496 -0
  19. package/bundle/server/dist/coordinator/source-reload.js +44 -9
  20. package/bundle/server/dist/coordinator/visual-qa-collection.test.js +619 -0
  21. package/bundle/server/dist/coordinator/visual-qa.js +630 -0
  22. package/bundle/server/dist/coordinator/visual-qa.test.js +493 -0
  23. package/bundle/server/dist/coordinator/worker-loop.js +1 -1
  24. package/bundle/server/dist/paths.js +24 -0
  25. package/bundle/server/dist/repository/admission-settings.js +5 -5
  26. package/bundle/server/dist/repository/admission-settings.test.js +71 -15
  27. package/bundle/server/dist/repository/issues.js +8 -1
  28. package/bundle/server/dist/repository/issues.test.js +34 -0
  29. package/bundle/server/dist/routes/issues-execution-contract.test.js +293 -0
  30. package/bundle/server/dist/routes/issues-source-attachments.test.js +277 -0
  31. package/bundle/server/dist/routes/issues.js +23 -8
  32. package/bundle/server/dist/routes/issues.test.js +80 -0
  33. package/bundle/server/dist/routes/queue.js +1 -1
  34. package/bundle/server/dist/routes/queue.test.js +55 -20
  35. package/bundle/server/dist/runners/process-registry.js +1 -1
  36. package/bundle/server/package.json +2 -2
  37. package/bundle/server/static-ui/assets/index-DQ5ivcyg.js +64 -0
  38. package/bundle/server/static-ui/index.html +1 -1
  39. package/bundle/shared/dist/queue-entries.d.ts +5 -4
  40. package/bundle/shared/dist/queue-entries.js +5 -4
  41. package/bundle/shared/package.json +1 -1
  42. package/package.json +1 -1
  43. package/templates/prod.env.example +5 -2
  44. package/bundle/server/static-ui/assets/index-Bm74Vq_X.js +0 -64
@@ -28,10 +28,12 @@ import { parseProfileSnapshot, roleCeiling } from "@agent-dealer/shared";
28
28
  import { getTaskSnapshot } from "./commands.js";
29
29
  import { extractOperatorCriteria } from "./operator-criteria.js";
30
30
  import { buildReviewerPrompt, formatDiffForPrompt, TOTAL_DIFF_LIMIT } from "./prompts.js";
31
+ import { cleanupStagedVisualArtifacts, resolveReviewerVisualEvidence, toReviewerVisualEvidenceInput, } from "./reviewer-visual-evidence.js";
32
+ import { readLatestVisualQa } from "./visual-qa.js";
31
33
  import { guidanceForNextSession } from "./guidance.js";
32
34
  import { realReviewerSpawn, reviewerSessionLogPath } from "./spawn.js";
33
35
  import { parseReviewerResult, normalizeReviewerResult, INCOMPLETE_REVIEW_FINGERPRINT, ReviewerResult as ReviewerResultSchema, } from "./reviewer-result.js";
34
- import { createRoleWorktree, safeRemoveWorktree, isWorktreeClean, mergeBase, fetchRef, diffShas, } from "../adapters/git-worktree.js";
36
+ import { createRoleWorktree, safeRemoveWorktree, isWorktreeClean, listChangedFiles, mergeBase, fetchRef, diffShas, } from "../adapters/git-worktree.js";
35
37
  import { ensureIssueRepoCheckout, roleWorktreePathForResolution, resolveCheckoutBaseBranch, } from "../adapters/managed-repo.js";
36
38
  import { prepareWorkerDeckConnection, releaseWorkerDeckConnection } from "../adapters/agent-deck-bind.js";
37
39
  import { realGithubAdapter } from "../adapters/github.js";
@@ -104,6 +106,14 @@ const EVENT_FOR_VERDICT = {
104
106
  };
105
107
  /** Best-effort: the worker never writes, so a non-clean checkout here is unexpected, not valuable work. */
106
108
  async function bestEffortRemove(repo, worktreePath) {
109
+ try {
110
+ // NOT-384: staged visual evidence goes first, so no residue survives the
111
+ // attempt even when the worktree removal below fails.
112
+ cleanupStagedVisualArtifacts(worktreePath);
113
+ }
114
+ catch {
115
+ // same courtesy as below — staging cleanup must never fail the attempt
116
+ }
107
117
  try {
108
118
  await safeRemoveWorktree({ repo, path: worktreePath, role: "reviewer" });
109
119
  }
@@ -335,12 +345,29 @@ export async function runReviewerEffect(ctx, deps = defaultDeps) {
335
345
  const baseSha = await mergeBase({ repo: worktreePath, base: `origin/${baseBranch}`, head: headSha });
336
346
  const diff = await diffShas({ worktreePath, baseSha, headSha });
337
347
  const { truncated: diffTruncated, omittedPaths } = formatDiffForPrompt(diff);
348
+ // NOT-384: SHA-bound CI visual evidence for this head (never throws —
349
+ // every failure mode resolves to `missing` with a reason, and a missing
350
+ // or failed capture never counts as a visual pass).
351
+ const changedFiles = await listChangedFiles({ worktreePath, baseSha, headSha }).catch(() => null);
352
+ const visualEvidence = toReviewerVisualEvidenceInput(await resolveReviewerVisualEvidence({
353
+ issueId: issue.id,
354
+ workerSessionId: sessionId,
355
+ worktreePath,
356
+ baseSha,
357
+ headSha,
358
+ changedFiles,
359
+ github: deps.github,
360
+ fetcher: deps.visualEvidenceFetcher ?? null,
361
+ }));
338
362
  const openFindings = listFindingsForIssue(issue.id).filter((f) => f.status === "open" || f.status === "recurring");
339
363
  const guidance = guidanceForNextSession(issue.id, sessionId);
340
364
  // NOT-314: the frozen snapshot's operator criteria — the reviewer must not
341
365
  // treat missing operator evidence as a defect (Dealer gates the merge), but
342
366
  // the probe and doc must exist. Rendered only when non-empty.
343
367
  const operatorCriteria = extractOperatorCriteria(taskSnapshot.acceptanceCriteria);
368
+ // NOT-381: the latest coordinator-validated visual receipt (or loud
369
+ // rejection), SHA-checked against the pinned head inside the prompt section.
370
+ const visualQaRecord = readLatestVisualQa(issue.id);
344
371
  const prompt = buildReviewerPrompt({
345
372
  taskSnapshot,
346
373
  round: workItem.round,
@@ -354,6 +381,8 @@ export async function runReviewerEffect(ctx, deps = defaultDeps) {
354
381
  deckId: snapshot?.deckId ?? null,
355
382
  guidance: guidance.length ? guidance : undefined,
356
383
  operatorCriteria: operatorCriteria.length > 0 ? operatorCriteria : undefined,
384
+ visualEvidence,
385
+ visualQa: visualQaRecord ? { record: visualQaRecord, pinnedHeadSha: headSha } : undefined,
357
386
  });
358
387
  // NOT-83 review finding — see developer-effect.ts's identical check for the full
359
388
  // rationale: re-verify this item is still leased right before the real spawn, since an
@@ -728,3 +728,57 @@ test("NOT-225 regression: a non-zero reviewer exit keeps its session_failed beha
728
728
  const crashReason = outcome.reason ?? "";
729
729
  assert.ok(!crashReason.includes("could not start"), "a spawned-then-crashed session is not a setup failure");
730
730
  });
731
+ test("NOT-384: available visual evidence stages CI artifacts under the reviewer worktree and cites them", async () => {
732
+ const { isWorktreeClean } = await import("../adapters/git-worktree.js");
733
+ const { reviewerVisualStagingDir } = await import("./reviewer-visual-evidence.js");
734
+ const issueId = await makeIssue();
735
+ const github = fakeGithub();
736
+ const uiSpawn = async (input) => {
737
+ fs.mkdirSync(path.join(input.cwd, "apps", "web", "src"), { recursive: true });
738
+ fs.writeFileSync(path.join(input.cwd, "apps", "web", "src", "app.tsx"), "export const App = () => null;\n");
739
+ git(input.cwd, "add", ".");
740
+ git(input.cwd, "-c", "user.email=agent@test", "-c", "user.name=Agent", "commit", "-q", "-m", "ui change");
741
+ return { exitCode: 0, transcript: "Implementation conclusion: changed the web app.", logPath: "/dev/null", timedOut: false };
742
+ };
743
+ registerEffectHandler("developer", (ctx) => runDeveloperEffect(ctx, { deckCallTool: okDeckCallTool, spawn: uiSpawn, github }));
744
+ startWorkflow(issueId);
745
+ await pump(1);
746
+ assert.equal(getIssue(issueId).status, "reviewing", "test setup: UI change did not reach reviewing");
747
+ let seenPrompt = "";
748
+ let seenCwd = "";
749
+ let cleanDuringSpawn = null;
750
+ const fetcher = {
751
+ async findRunForHead({ headSha }) {
752
+ return { databaseId: 4242, headSha, conclusion: "success", status: "completed" };
753
+ },
754
+ async downloadArtifact({ name, destDir }) {
755
+ fs.mkdirSync(destDir, { recursive: true });
756
+ fs.writeFileSync(path.join(destDir, "issues-home-1440x900.png"), `png-${name}`);
757
+ fs.writeFileSync(path.join(destDir, "issues-home-390x800.png"), `png-${name}`);
758
+ if (name === "ui-diff")
759
+ fs.writeFileSync(path.join(destDir, "SUMMARY.md"), "## UI diff\n\nhead: `h`\n");
760
+ },
761
+ };
762
+ const capturingSpawn = async (input) => {
763
+ seenPrompt = input.prompt;
764
+ seenCwd = input.cwd;
765
+ cleanDuringSpawn = await isWorktreeClean(input.cwd);
766
+ return verdictSpawn({ verdict: "approved" })(input);
767
+ };
768
+ registerEffectHandler("reviewer", (ctx) => runReviewerEffect(ctx, { deckCallTool: okDeckCallTool, spawn: capturingSpawn, github, visualEvidenceFetcher: fetcher }));
769
+ await pump(1);
770
+ const issue = getIssue(issueId);
771
+ assert.equal(issue.status, "final_review");
772
+ const stagingDir = reviewerVisualStagingDir(seenCwd);
773
+ assert.ok(stagingDir.startsWith(seenCwd + path.sep), "staging dir is inside the reviewer worktree");
774
+ assert.ok(seenPrompt.includes(stagingDir), "reviewer input must name the staged directory");
775
+ assert.ok(seenPrompt.includes(issue.headSha), "reviewer input must cite the head SHA");
776
+ assert.ok(seenPrompt.includes("## UI diff"), "reviewer input must embed the diff summary");
777
+ assert.match(seenPrompt, /1440x900 and 390x800/);
778
+ assert.equal(cleanDuringSpawn, true, "the worktree reports clean with staged evidence present");
779
+ const visual = listArtifactsForIssue(issueId).find((a) => a.kind === "visual_evidence");
780
+ assert.ok(visual, "expected a recorded visual_evidence artifact");
781
+ assert.equal(JSON.parse(visual.contentJson).state, "available");
782
+ assert.equal(fs.existsSync(stagingDir), false, "the staging directory is gone after the attempt");
783
+ assert.equal(fs.existsSync(seenCwd), false, "the reviewer worktree is removed after the attempt");
784
+ });