agent-dealer 0.2.0 → 1.0.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/bundle/server/dist/adapters/agent-deck-bind.js +443 -0
- package/bundle/server/dist/adapters/agent-deck-bind.test.js +454 -0
- package/bundle/server/dist/adapters/agent-deck.js +38 -50
- package/bundle/server/dist/adapters/agent-deck.test.js +63 -0
- package/bundle/server/dist/adapters/agent-health.js +230 -33
- package/bundle/server/dist/adapters/agent-health.test.js +237 -0
- package/bundle/server/dist/adapters/authority-lifecycle.js +289 -0
- package/bundle/server/dist/adapters/codex-scoped-config.js +103 -0
- package/bundle/server/dist/adapters/execution-authority.js +103 -0
- package/bundle/server/dist/adapters/execution-authority.test.js +166 -0
- package/bundle/server/dist/adapters/external.js +0 -1
- package/bundle/server/dist/adapters/git-worktree.js +514 -0
- package/bundle/server/dist/adapters/git-worktree.test.js +502 -0
- package/bundle/server/dist/adapters/github.js +170 -0
- package/bundle/server/dist/adapters/github.test.js +205 -0
- package/bundle/server/dist/adapters/linear-inbox.js +199 -30
- package/bundle/server/dist/adapters/linear-inbox.test.js +35 -0
- package/bundle/server/dist/adapters/linear-sync.js +3 -101
- package/bundle/server/dist/adapters/managed-repo.js +168 -0
- package/bundle/server/dist/adapters/managed-repo.test.js +63 -0
- package/bundle/server/dist/adapters/outbound-delivery.js +83 -0
- package/bundle/server/dist/adapters/outbound-delivery.test.js +53 -0
- package/bundle/server/dist/adapters/reflect-authority.js +43 -0
- package/bundle/server/dist/cli-env.js +26 -0
- package/bundle/server/dist/cli-env.test.js +14 -1
- package/bundle/server/dist/coordinator/abort.integration.test.js +74 -0
- package/bundle/server/dist/coordinator/admission.js +330 -0
- package/bundle/server/dist/coordinator/admission.test.js +338 -0
- package/bundle/server/dist/coordinator/args.js +128 -0
- package/bundle/server/dist/coordinator/args.test.js +132 -0
- package/bundle/server/dist/coordinator/authority-recovery.integration.test.js +661 -0
- package/bundle/server/dist/coordinator/auto-merge.integration.test.js +363 -0
- package/bundle/server/dist/coordinator/auto-merge.js +286 -0
- package/bundle/server/dist/coordinator/auto-merge.timeout.test.js +14 -0
- package/bundle/server/dist/coordinator/branch-progress.js +81 -0
- package/bundle/server/dist/coordinator/clock-jump.js +98 -0
- package/bundle/server/dist/coordinator/commands.js +1088 -0
- package/bundle/server/dist/coordinator/commands.test.js +554 -0
- package/bundle/server/dist/coordinator/deck-outage-config.js +32 -0
- package/bundle/server/dist/coordinator/deck-outage.integration.test.js +200 -0
- package/bundle/server/dist/coordinator/dependencies.js +242 -0
- package/bundle/server/dist/coordinator/dependency-readiness.integration.test.js +535 -0
- package/bundle/server/dist/coordinator/developer-effect.js +959 -0
- package/bundle/server/dist/coordinator/developer-effect.test.js +1143 -0
- package/bundle/server/dist/coordinator/dispatcher.js +58 -0
- package/bundle/server/dist/coordinator/dispatcher.test.js +81 -0
- package/bundle/server/dist/coordinator/effect-registry.js +17 -0
- package/bundle/server/dist/coordinator/execution-env.integration.test.js +122 -0
- package/bundle/server/dist/coordinator/failure-reason.js +273 -0
- package/bundle/server/dist/coordinator/failure-reason.test.js +234 -0
- package/bundle/server/dist/coordinator/guidance.js +33 -0
- package/bundle/server/dist/coordinator/guidance.test.js +110 -0
- package/bundle/server/dist/coordinator/human-resolution.js +88 -0
- package/bundle/server/dist/coordinator/human-resolution.test.js +72 -0
- package/bundle/server/dist/coordinator/intent-forecast.js +13 -0
- package/bundle/server/dist/coordinator/intent-forecast.test.js +34 -0
- package/bundle/server/dist/coordinator/latest-failure.js +100 -0
- package/bundle/server/dist/coordinator/latest-failure.test.js +125 -0
- package/bundle/server/dist/coordinator/metrics.js +35 -0
- package/bundle/server/dist/coordinator/metrics.test.js +52 -0
- package/bundle/server/dist/coordinator/permissions.js +127 -0
- package/bundle/server/dist/coordinator/permissions.test.js +184 -0
- package/bundle/server/dist/coordinator/presumed-dead-budget.integration.test.js +146 -0
- package/bundle/server/dist/coordinator/process-liveness.js +191 -0
- package/bundle/server/dist/coordinator/profile-snapshot.js +20 -0
- package/bundle/server/dist/coordinator/profile-snapshot.test.js +118 -0
- package/bundle/server/dist/coordinator/projection.js +147 -0
- package/bundle/server/dist/coordinator/projection.test.js +121 -0
- package/bundle/server/dist/coordinator/prompts.js +164 -0
- package/bundle/server/dist/coordinator/prompts.test.js +177 -0
- package/bundle/server/dist/coordinator/queue-by-default.integration.test.js +271 -0
- package/bundle/server/dist/coordinator/reclaim-republish.integration.test.js +372 -0
- package/bundle/server/dist/coordinator/recovery.js +466 -0
- package/bundle/server/dist/coordinator/recovery.test.js +164 -0
- package/bundle/server/dist/coordinator/reflect-trigger.js +218 -0
- package/bundle/server/dist/coordinator/reflect-trigger.test.js +324 -0
- package/bundle/server/dist/coordinator/repair-cycle.integration.test.js +234 -0
- package/bundle/server/dist/coordinator/restart-recovery.integration.test.js +363 -0
- package/bundle/server/dist/coordinator/reviewer-effect.js +507 -0
- package/bundle/server/dist/coordinator/reviewer-effect.test.js +612 -0
- package/bundle/server/dist/coordinator/reviewer-result.js +38 -0
- package/bundle/server/dist/coordinator/reviewer-result.test.js +45 -0
- package/bundle/server/dist/coordinator/routing.js +160 -0
- package/bundle/server/dist/coordinator/routing.test.js +336 -0
- package/bundle/server/dist/coordinator/session-lifecycle.integration.test.js +127 -0
- package/bundle/server/dist/coordinator/session-lifecycle.js +280 -0
- package/bundle/server/dist/coordinator/session-lifecycle.test.js +121 -0
- package/bundle/server/dist/coordinator/session-progress.js +351 -0
- package/bundle/server/dist/coordinator/session-progress.test.js +190 -0
- package/bundle/server/dist/coordinator/session-timeouts.js +33 -0
- package/bundle/server/dist/coordinator/sleep-recovery.integration.test.js +233 -0
- package/bundle/server/dist/coordinator/spawn.js +49 -0
- package/bundle/server/dist/coordinator/sync-issue-base-branch.js +18 -0
- package/bundle/server/dist/coordinator/usage-cap-config.js +10 -0
- package/bundle/server/dist/coordinator/usage-cap-defer.js +189 -0
- package/bundle/server/dist/coordinator/usage-cap.integration.test.js +193 -0
- package/bundle/server/dist/coordinator/usage.js +40 -0
- package/bundle/server/dist/coordinator/usage.test.js +69 -0
- package/bundle/server/dist/coordinator/verification-receipt.js +333 -0
- package/bundle/server/dist/coordinator/verification-receipt.test.js +382 -0
- package/bundle/server/dist/coordinator/worker-loop.js +363 -0
- package/bundle/server/dist/coordinator/worker-loop.test.js +327 -0
- package/bundle/server/dist/coordinator/workflow-instances.test.js +45 -0
- package/bundle/server/dist/coordinator/worktree-owner-liveness.js +82 -0
- package/bundle/server/dist/coordinator/worktree-owner-liveness.test.js +199 -0
- package/bundle/server/dist/db/artifacts-issue-columns.test.js +18 -0
- package/bundle/server/dist/db/authority-attempts-stale-at-migration.test.js +65 -0
- package/bundle/server/dist/db/idempotency-index-migration.test.js +57 -0
- package/bundle/server/dist/db/index.js +359 -7
- package/bundle/server/dist/db/linear-inbox-unlock.test.js +67 -0
- package/bundle/server/dist/db/migrate-to-issues.js +824 -0
- package/bundle/server/dist/db/migrate-to-issues.test.js +842 -0
- package/bundle/server/dist/db/schema.sql +311 -1
- package/bundle/server/dist/db/schema.test.js +66 -0
- package/bundle/server/dist/db/worker-session-pid-migration.test.js +112 -0
- package/bundle/server/dist/dev-review-cli-happy-path.integration.test.js +338 -0
- package/bundle/server/dist/direct-start-liveness.integration.test.js +350 -0
- package/bundle/server/dist/index.js +58 -5
- package/bundle/server/dist/paths.js +40 -4
- package/bundle/server/dist/paths.test.js +42 -0
- package/bundle/server/dist/pid-marker-claim-child.js +18 -0
- package/bundle/server/dist/pid-marker-hold-arbiter-child.js +24 -0
- package/bundle/server/dist/pid-marker.js +141 -0
- package/bundle/server/dist/pid-marker.test.js +208 -0
- package/bundle/server/dist/queue/approve-deliver.js +128 -28
- package/bundle/server/dist/queue/reflect-schedule.js +25 -0
- package/bundle/server/dist/queue/result-qa.js +1 -1
- package/bundle/server/dist/queue/send-gate.test.js +172 -36
- package/bundle/server/dist/repository/agents.js +55 -18
- package/bundle/server/dist/repository/artifacts-for-issue.js +42 -0
- package/bundle/server/dist/repository/artifacts-for-issue.test.js +48 -0
- package/bundle/server/dist/repository/artifacts.js +47 -0
- package/bundle/server/dist/repository/artifacts.test.js +48 -0
- package/bundle/server/dist/repository/authority-attempts.js +189 -0
- package/bundle/server/dist/repository/findings.js +70 -0
- package/bundle/server/dist/repository/findings.test.js +68 -0
- package/bundle/server/dist/repository/human-actions.js +142 -0
- package/bundle/server/dist/repository/human-actions.test.js +123 -0
- package/bundle/server/dist/repository/intake-settings.js +5 -59
- package/bundle/server/dist/repository/issues.js +305 -0
- package/bundle/server/dist/repository/issues.test.js +69 -0
- package/bundle/server/dist/repository/outbound-drafts.js +28 -0
- package/bundle/server/dist/repository/queue-entries.js +162 -0
- package/bundle/server/dist/repository/review-publications.js +153 -0
- package/bundle/server/dist/repository/review-publications.test.js +78 -0
- package/bundle/server/dist/repository/runs-external-id.test.js +5 -3
- package/bundle/server/dist/repository/runs.js +4 -211
- package/bundle/server/dist/repository/runtime-availability.js +54 -0
- package/bundle/server/dist/repository/usage-events.js +60 -0
- package/bundle/server/dist/repository/usage-events.test.js +56 -0
- package/bundle/server/dist/repository/work-items.js +305 -0
- package/bundle/server/dist/repository/work-items.test.js +142 -0
- package/bundle/server/dist/repository/worker-sessions.js +219 -0
- package/bundle/server/dist/repository/worker-sessions.test.js +60 -0
- package/bundle/server/dist/repository/workflow-events.js +193 -0
- package/bundle/server/dist/repository/workflow-events.test.js +89 -0
- package/bundle/server/dist/resolve-tsx-bin.js +19 -0
- package/bundle/server/dist/routes/human-actions.js +68 -0
- package/bundle/server/dist/routes/human-actions.test.js +284 -0
- package/bundle/server/dist/routes/index.js +29 -496
- package/bundle/server/dist/routes/issues.js +240 -0
- package/bundle/server/dist/routes/issues.test.js +451 -0
- package/bundle/server/dist/routes/queue.js +41 -0
- package/bundle/server/dist/routes/queue.test.js +78 -0
- package/bundle/server/dist/runners/claude-args.js +18 -30
- package/bundle/server/dist/runners/claude.js +19 -163
- package/bundle/server/dist/runners/claude.test.js +15 -70
- package/bundle/server/dist/runners/codex-args.js +59 -0
- package/bundle/server/dist/runners/codex-args.test.js +72 -0
- package/bundle/server/dist/runners/codex-jsonl.js +150 -0
- package/bundle/server/dist/runners/codex-jsonl.test.js +36 -0
- package/bundle/server/dist/runners/codex-resume.test.js +19 -0
- package/bundle/server/dist/runners/codex.js +46 -0
- package/bundle/server/dist/runners/models-codex.test.js +14 -0
- package/bundle/server/dist/runners/models.js +53 -1
- package/bundle/server/dist/runners/persist.js +4 -1
- package/bundle/server/dist/runners/process-registry.js +11 -0
- package/bundle/server/dist/runners/prompts.js +13 -280
- package/bundle/server/dist/runners/prompts.test.js +22 -153
- package/bundle/server/dist/runners/qa.js +17 -1
- package/bundle/server/dist/runners/reflect.js +4 -16
- package/bundle/server/dist/runners/spawn-cli.js +197 -0
- package/bundle/server/dist/runners/spawn-cli.test.js +130 -0
- package/bundle/server/dist/runners/stream-json.js +0 -141
- package/bundle/server/dist/runners/usage-cap.js +242 -0
- package/bundle/server/dist/runners/usage-cap.test.js +97 -0
- package/bundle/server/dist/server-liveness.js +37 -0
- package/bundle/server/dist/server-liveness.test.js +82 -0
- package/bundle/server/dist/static-ui.test.js +44 -0
- package/bundle/server/package.json +4 -2
- package/bundle/server/static-ui/assets/index-BTtPGYpY.js +60 -0
- package/bundle/server/static-ui/assets/index-BxTx4b1d.css +1 -0
- package/bundle/server/static-ui/index.html +2 -2
- package/bundle/shared/dist/agents.d.ts +262 -133
- package/bundle/shared/dist/agents.js +85 -19
- package/bundle/shared/dist/cursor-auth-health.d.ts +12 -0
- package/bundle/shared/dist/cursor-auth-health.js +36 -0
- package/bundle/shared/dist/cursor-auth-health.test.d.ts +1 -0
- package/bundle/shared/dist/cursor-auth-health.test.js +40 -0
- package/bundle/shared/dist/findings.d.ts +46 -0
- package/bundle/shared/dist/findings.js +17 -0
- package/bundle/shared/dist/findings.test.d.ts +1 -0
- package/bundle/shared/dist/findings.test.js +20 -0
- package/bundle/shared/dist/github-repo.d.ts +28 -0
- package/bundle/shared/dist/github-repo.js +98 -0
- package/bundle/shared/dist/github-repo.test.d.ts +1 -0
- package/bundle/shared/dist/github-repo.test.js +32 -0
- package/bundle/shared/dist/human-actions.d.ts +66 -0
- package/bundle/shared/dist/human-actions.js +51 -0
- package/bundle/shared/dist/human-actions.test.d.ts +1 -0
- package/bundle/shared/dist/human-actions.test.js +57 -0
- package/bundle/shared/dist/index.d.ts +567 -757
- package/bundle/shared/dist/index.js +22 -36
- package/bundle/shared/dist/issues.d.ts +385 -0
- package/bundle/shared/dist/issues.js +169 -0
- package/bundle/shared/dist/issues.test.d.ts +1 -0
- package/bundle/shared/dist/issues.test.js +72 -0
- package/bundle/shared/dist/outbound-draft.d.ts +6 -0
- package/bundle/shared/dist/outbound-draft.js +4 -0
- package/bundle/shared/dist/plan-triage.d.ts +6 -6
- package/bundle/shared/dist/profile-snapshot.d.ts +154 -0
- package/bundle/shared/dist/profile-snapshot.js +145 -0
- package/bundle/shared/dist/profile-snapshot.test.d.ts +1 -0
- package/bundle/shared/dist/profile-snapshot.test.js +67 -0
- package/bundle/shared/dist/queue-entries.d.ts +89 -0
- package/bundle/shared/dist/queue-entries.js +19 -0
- package/bundle/shared/dist/result-qa.d.ts +2 -2
- package/bundle/shared/dist/runtime-auth-health.d.ts +66 -0
- package/bundle/shared/dist/runtime-auth-health.js +186 -0
- package/bundle/shared/dist/runtime-auth-health.test.d.ts +1 -0
- package/bundle/shared/dist/runtime-auth-health.test.js +200 -0
- package/bundle/shared/dist/runtime-availability.d.ts +51 -0
- package/bundle/shared/dist/runtime-availability.js +13 -0
- package/bundle/shared/dist/runtime.d.ts +1 -1
- package/bundle/shared/dist/runtime.js +1 -1
- package/bundle/shared/dist/usage-events.d.ts +36 -0
- package/bundle/shared/dist/usage-events.js +13 -0
- package/bundle/shared/dist/worker-sessions.d.ts +140 -0
- package/bundle/shared/dist/worker-sessions.js +70 -0
- package/bundle/shared/dist/worker-sessions.test.d.ts +1 -0
- package/bundle/shared/dist/worker-sessions.test.js +39 -0
- package/bundle/shared/dist/workflow.d.ts +78 -0
- package/bundle/shared/dist/workflow.js +59 -0
- package/bundle/shared/dist/workflow.test.d.ts +1 -0
- package/bundle/shared/dist/workflow.test.js +61 -0
- package/bundle/shared/package.json +1 -1
- package/dist/action.d.ts +1 -0
- package/dist/action.js +50 -0
- package/dist/action.test.d.ts +1 -0
- package/dist/action.test.js +70 -0
- package/dist/agent.d.ts +1 -0
- package/dist/agent.js +17 -0
- package/dist/agent.test.d.ts +1 -0
- package/dist/agent.test.js +19 -0
- package/dist/cli-check.d.ts +7 -0
- package/dist/cli-check.js +40 -0
- package/dist/doctor.js +59 -1
- package/dist/http.d.ts +5 -0
- package/dist/http.js +19 -0
- package/dist/index.js +28 -0
- package/dist/issue.d.ts +35 -0
- package/dist/issue.js +143 -0
- package/dist/issue.test.d.ts +1 -0
- package/dist/issue.test.js +240 -0
- package/dist/lifecycle.contract.test.d.ts +1 -0
- package/dist/lifecycle.contract.test.js +135 -0
- package/dist/queue.d.ts +11 -0
- package/dist/queue.js +60 -0
- package/dist/queue.test.d.ts +1 -0
- package/dist/queue.test.js +46 -0
- package/dist/test-fetch-stub.d.ts +6 -0
- package/dist/test-fetch-stub.js +31 -0
- package/package.json +2 -1
- package/templates/prod.env.example +1 -1
- package/bundle/server/static-ui/assets/index-B_7S7xJb.js +0 -79
- package/bundle/server/static-ui/assets/index-QYv3zmE6.css +0 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { test, before, beforeEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import Fastify from "fastify";
|
|
7
|
+
process.env.AGENT_DEALER_HOME = fs.mkdtempSync(path.join(os.tmpdir(), "dealer-action-routes-"));
|
|
8
|
+
const { migrate, getDb } = await import("../db/index.js");
|
|
9
|
+
const { BUILTIN_AGENT_CLAUDE_ID, BUILTIN_AGENT_CURSOR_ID } = await import("@agent-dealer/shared");
|
|
10
|
+
const { createIssue, transitionIssue, getIssue } = await import("../repository/issues.js");
|
|
11
|
+
const { createHumanAction, getHumanAction, listHumanActionsForIssue } = await import("../repository/human-actions.js");
|
|
12
|
+
const { registerHumanActionRoutes } = await import("./human-actions.js");
|
|
13
|
+
const { startWorkflow, applyCompletion } = await import("../coordinator/commands.js");
|
|
14
|
+
const { ReviewerResult } = await import("../coordinator/reviewer-result.js");
|
|
15
|
+
const { claimWorkItem } = await import("../repository/work-items.js");
|
|
16
|
+
const { listArtifactsForIssue } = await import("../repository/artifacts-for-issue.js");
|
|
17
|
+
const { createRun, getRun, transitionRun, addArtifact, updateRunFields } = await import("../repository/runs.js");
|
|
18
|
+
const { pendingSendCount, getPendingOutboundDraft } = await import("../repository/outbound-drafts.js");
|
|
19
|
+
const { setMergePrForTests, clearFinalizeInflightForTests } = await import("../coordinator/auto-merge.js");
|
|
20
|
+
before(() => {
|
|
21
|
+
migrate();
|
|
22
|
+
});
|
|
23
|
+
// claimWorkItem is global FIFO, not issue-scoped — a leftover queued item from an earlier
|
|
24
|
+
// test would otherwise be claimed instead of the issue this test just started.
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
getDb().exec("DELETE FROM work_items");
|
|
27
|
+
clearFinalizeInflightForTests();
|
|
28
|
+
// final_review:complete now undrafts+merges — never hit real `gh` from route tests.
|
|
29
|
+
setMergePrForTests(async () => ({ ok: true }));
|
|
30
|
+
});
|
|
31
|
+
async function buildApp() {
|
|
32
|
+
const app = Fastify();
|
|
33
|
+
await registerHumanActionRoutes(app);
|
|
34
|
+
return app;
|
|
35
|
+
}
|
|
36
|
+
function seedIssueAwaitingFinalReview() {
|
|
37
|
+
const issue = createIssue({ title: "Awaiting review", repo: "acme/app", baseBranch: "main", developerAgentId: BUILTIN_AGENT_CLAUDE_ID, reviewerAgentId: BUILTIN_AGENT_CURSOR_ID, maxReviewRounds: 3, maxInfraAttempts: 3, source: "manual" });
|
|
38
|
+
transitionIssue(issue.id, "developing");
|
|
39
|
+
transitionIssue(issue.id, "reviewing");
|
|
40
|
+
transitionIssue(issue.id, "final_review", { currentOwner: "human" });
|
|
41
|
+
const action = createHumanAction({ issueId: issue.id, actionType: "final_review", reason: "Reviewer approved", question: "Accept?" });
|
|
42
|
+
return { issue, action };
|
|
43
|
+
}
|
|
44
|
+
/** Drives a real issue through startWorkflow → clean developer handoff → reviewer
|
|
45
|
+
* approval, so the resulting final_review human action carries a real workflow instance
|
|
46
|
+
* (unlike seedIssueAwaitingFinalReview's hand-crafted transitions) — needed to exercise
|
|
47
|
+
* resolveHumanActionAndAdvance's actual transactional path through the route. */
|
|
48
|
+
async function seedRealIssueAwaitingFinalReview() {
|
|
49
|
+
const issue = createIssue({ title: "Real workflow", repo: "acme/app", baseBranch: "main", developerAgentId: BUILTIN_AGENT_CLAUDE_ID, reviewerAgentId: BUILTIN_AGENT_CURSOR_ID, acceptanceCriteria: "Works", maxReviewRounds: 3, maxInfraAttempts: 3, source: "manual" });
|
|
50
|
+
const start = startWorkflow(issue.id);
|
|
51
|
+
assert.equal(start.ok, true);
|
|
52
|
+
const devItem = claimWorkItem(`route-test-${issue.id}-dev`, { leaseMs: 60_000 });
|
|
53
|
+
await applyCompletion(devItem.id, devItem.leaseToken, {
|
|
54
|
+
kind: "clean_handoff",
|
|
55
|
+
branch: `issue-${issue.id}`,
|
|
56
|
+
headSha: "a".repeat(40),
|
|
57
|
+
baseSha: "b".repeat(40),
|
|
58
|
+
prNumber: 1,
|
|
59
|
+
prUrl: "https://gh/pr/1"
|
|
60
|
+
});
|
|
61
|
+
const reviewItem = claimWorkItem(`route-test-${issue.id}-rev`, { leaseMs: 60_000 });
|
|
62
|
+
await applyCompletion(reviewItem.id, reviewItem.leaseToken, {
|
|
63
|
+
kind: "verdict",
|
|
64
|
+
result: ReviewerResult.parse({
|
|
65
|
+
verdict: "approved",
|
|
66
|
+
baseSha: "b".repeat(40),
|
|
67
|
+
headSha: "a".repeat(40),
|
|
68
|
+
acceptanceCriteriaAssessment: "met",
|
|
69
|
+
evidenceAssessment: "fine",
|
|
70
|
+
findings: [],
|
|
71
|
+
risks: []
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
assert.ok(getIssue(issue.id));
|
|
75
|
+
const humanAction = listHumanActionsForIssue(issue.id).find((a) => a.actionType === "final_review");
|
|
76
|
+
assert.ok(humanAction, "expected a final_review human action");
|
|
77
|
+
return { issueId: issue.id, actionId: humanAction.id };
|
|
78
|
+
}
|
|
79
|
+
// NOT-58: the global queue is read-only. Typed resolution + continuation land in NOT-64.
|
|
80
|
+
test("GET /api/human-actions lists only open actions", async () => {
|
|
81
|
+
const app = await buildApp();
|
|
82
|
+
const { action } = seedIssueAwaitingFinalReview();
|
|
83
|
+
const res = await app.inject({ method: "GET", url: "/api/human-actions" });
|
|
84
|
+
const list = res.json();
|
|
85
|
+
assert.ok(list.some((a) => a.id === action.id && a.status === "open"));
|
|
86
|
+
await app.close();
|
|
87
|
+
});
|
|
88
|
+
test("POST resolve requires resolvedBy and choice", async () => {
|
|
89
|
+
const app = await buildApp();
|
|
90
|
+
const { action } = seedIssueAwaitingFinalReview();
|
|
91
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/${action.id}/resolve`, payload: {} });
|
|
92
|
+
assert.equal(res.statusCode, 400);
|
|
93
|
+
await app.close();
|
|
94
|
+
});
|
|
95
|
+
test("POST resolve 400s (not 500) on non-string resolvedBy/choice values", async () => {
|
|
96
|
+
const app = await buildApp();
|
|
97
|
+
const { action } = seedIssueAwaitingFinalReview();
|
|
98
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/${action.id}/resolve`, payload: { resolvedBy: 123, choice: "complete" } });
|
|
99
|
+
assert.equal(res.statusCode, 400);
|
|
100
|
+
const res2 = await app.inject({ method: "POST", url: `/api/human-actions/${action.id}/resolve`, payload: { resolvedBy: "yusuke", choice: ["complete"] } });
|
|
101
|
+
assert.equal(res2.statusCode, 400);
|
|
102
|
+
await app.close();
|
|
103
|
+
});
|
|
104
|
+
test("POST resolve 404s for an unknown action", async () => {
|
|
105
|
+
const app = await buildApp();
|
|
106
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/does-not-exist/resolve`, payload: { resolvedBy: "yusuke", choice: "complete" } });
|
|
107
|
+
assert.equal(res.statusCode, 404);
|
|
108
|
+
await app.close();
|
|
109
|
+
});
|
|
110
|
+
test("POST resolve 400s on a choice not valid for the action type", async () => {
|
|
111
|
+
const app = await buildApp();
|
|
112
|
+
const { action } = seedIssueAwaitingFinalReview();
|
|
113
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/${action.id}/resolve`, payload: { resolvedBy: "yusuke", choice: "retry" } });
|
|
114
|
+
assert.equal(res.statusCode, 400);
|
|
115
|
+
await app.close();
|
|
116
|
+
});
|
|
117
|
+
test("POST resolve 409s on an already-resolved action", async () => {
|
|
118
|
+
const app = await buildApp();
|
|
119
|
+
const { actionId } = await seedRealIssueAwaitingFinalReview();
|
|
120
|
+
const first = await app.inject({ method: "POST", url: `/api/human-actions/${actionId}/resolve`, payload: { resolvedBy: "yusuke", choice: "close" } });
|
|
121
|
+
assert.equal(first.statusCode, 200);
|
|
122
|
+
const second = await app.inject({ method: "POST", url: `/api/human-actions/${actionId}/resolve`, payload: { resolvedBy: "yusuke", choice: "close" } });
|
|
123
|
+
assert.equal(second.statusCode, 409);
|
|
124
|
+
await app.close();
|
|
125
|
+
});
|
|
126
|
+
test("POST resolve final_review:repair sends the issue back to a fresh developer round, no reflect artifact", async () => {
|
|
127
|
+
const app = await buildApp();
|
|
128
|
+
const { issueId, actionId } = await seedRealIssueAwaitingFinalReview();
|
|
129
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/${actionId}/resolve`, payload: { resolvedBy: "yusuke", choice: "repair" } });
|
|
130
|
+
assert.equal(res.statusCode, 200);
|
|
131
|
+
const body = res.json();
|
|
132
|
+
assert.equal(body.issueStatus, "repairing");
|
|
133
|
+
assert.ok(body.nextWorkItemId);
|
|
134
|
+
assert.equal(body.instanceCompleted, false);
|
|
135
|
+
assert.ok(!listArtifactsForIssue(issueId).some((a) => a.kind === "reflect_status" || a.kind === "playbook_patch"));
|
|
136
|
+
await app.close();
|
|
137
|
+
});
|
|
138
|
+
test("POST resolve final_review:complete finishes the workflow and attempts reflect (skips: no deck configured)", async () => {
|
|
139
|
+
const app = await buildApp();
|
|
140
|
+
const { issueId, actionId } = await seedRealIssueAwaitingFinalReview();
|
|
141
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/${actionId}/resolve`, payload: { resolvedBy: "yusuke", choice: "complete" } });
|
|
142
|
+
assert.equal(res.statusCode, 200);
|
|
143
|
+
const body = res.json();
|
|
144
|
+
assert.equal(body.issueStatus, "done");
|
|
145
|
+
assert.equal(body.instanceCompleted, true);
|
|
146
|
+
const action = getHumanAction(actionId);
|
|
147
|
+
assert.equal(action.status, "resolved");
|
|
148
|
+
// The seeded issue uses the built-in developer agent, which has no deck configured —
|
|
149
|
+
// triggerIssueReflect skips silently (no artifact) rather than throwing.
|
|
150
|
+
assert.ok(!listArtifactsForIssue(issueId).some((a) => a.kind === "playbook_patch"));
|
|
151
|
+
await app.close();
|
|
152
|
+
});
|
|
153
|
+
// NOT-94: a reflection_interaction_required action is raised against an issue that is
|
|
154
|
+
// already `done` with no active workflow instance — resolveHumanActionAndAdvance's normal
|
|
155
|
+
// path 409s on "no active workflow for this action" for any type but
|
|
156
|
+
// final_review/attempts_exhausted/product_scope_decision, so the route must dispatch this
|
|
157
|
+
// action type to resolveReflectionInteractionAction instead, never through that machine.
|
|
158
|
+
test("POST resolve on a reflection_interaction_required action bypasses the workflow state machine (issue already done, no active instance)", async () => {
|
|
159
|
+
const app = await buildApp();
|
|
160
|
+
const { issueId, actionId } = await seedRealIssueAwaitingFinalReview();
|
|
161
|
+
const complete = await app.inject({ method: "POST", url: `/api/human-actions/${actionId}/resolve`, payload: { resolvedBy: "yusuke", choice: "complete" } });
|
|
162
|
+
assert.equal(complete.statusCode, 200);
|
|
163
|
+
assert.equal(getIssue(issueId).status, "done");
|
|
164
|
+
const reflectAction = createHumanAction({
|
|
165
|
+
issueId,
|
|
166
|
+
actionType: "reflection_interaction_required",
|
|
167
|
+
reason: "Approve the coordinator enrollment.",
|
|
168
|
+
question: "Approve the coordinator enrollment. Retry the reflection, or dismiss?",
|
|
169
|
+
responseOptions: [
|
|
170
|
+
{ choice: "retry", label: "Retry reflection" },
|
|
171
|
+
{ choice: "dismiss", label: "Dismiss" },
|
|
172
|
+
],
|
|
173
|
+
requestId: "req_route_test"
|
|
174
|
+
});
|
|
175
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/${reflectAction.id}/resolve`, payload: { resolvedBy: "yusuke", choice: "dismiss" } });
|
|
176
|
+
assert.equal(res.statusCode, 200);
|
|
177
|
+
const body = res.json();
|
|
178
|
+
assert.equal(body.issueStatus, "done");
|
|
179
|
+
assert.equal(body.nextWorkItemId, null);
|
|
180
|
+
assert.equal(body.instanceCompleted, false);
|
|
181
|
+
assert.equal(body.restarted, false);
|
|
182
|
+
assert.equal(getHumanAction(reflectAction.id).status, "resolved");
|
|
183
|
+
await app.close();
|
|
184
|
+
});
|
|
185
|
+
/** Seeds a communication Run in review with a pending Slack draft and an open, Run-scoped
|
|
186
|
+
* outbound_delivery_interaction_required action against it (NOT-95) — mirrors an
|
|
187
|
+
* INTERACTION_REQUIRED park without needing a real Agent Deck to raise one. */
|
|
188
|
+
function seedRunAwaitingDeliveryDecision() {
|
|
189
|
+
const run = createRun({
|
|
190
|
+
title: "Send gate action-route test",
|
|
191
|
+
taskCategory: "communication",
|
|
192
|
+
status: "plan_pending",
|
|
193
|
+
agentId: BUILTIN_AGENT_CLAUDE_ID,
|
|
194
|
+
repo: "acme/app"
|
|
195
|
+
});
|
|
196
|
+
updateRunFields(run.id, { deck_id: "6e825b59-13de-4ddd-ab7e-55ab5a1c279a" });
|
|
197
|
+
transitionRun(run.id, "plan_approved");
|
|
198
|
+
transitionRun(run.id, "running");
|
|
199
|
+
transitionRun(run.id, "review");
|
|
200
|
+
addArtifact(run.id, "slack_draft", {
|
|
201
|
+
draft: {
|
|
202
|
+
actionType: "slack_message",
|
|
203
|
+
summary: { target: "#test", body: "Hello action-route test" },
|
|
204
|
+
toolCall: { serviceName: "svc-1", toolName: "chat_postMessage", arguments: { channel: "C1", text: "Hello action-route test" } }
|
|
205
|
+
},
|
|
206
|
+
status: "pending"
|
|
207
|
+
}, "agent");
|
|
208
|
+
const action = createHumanAction({
|
|
209
|
+
runId: run.id,
|
|
210
|
+
actionType: "outbound_delivery_interaction_required",
|
|
211
|
+
reason: "Agent Deck requires a control-plane decision before this draft can be delivered.",
|
|
212
|
+
question: "Retry the send or reject the draft?",
|
|
213
|
+
responseOptions: [
|
|
214
|
+
{ choice: "retry_send", label: "Retry send" },
|
|
215
|
+
{ choice: "reject", label: "Reject draft" },
|
|
216
|
+
],
|
|
217
|
+
requestId: "req_delivery_route_test"
|
|
218
|
+
});
|
|
219
|
+
return { run, action };
|
|
220
|
+
}
|
|
221
|
+
test("POST resolve outbound_delivery_interaction_required:reject rejects the draft with no provider call, run ends done", async () => {
|
|
222
|
+
const app = await buildApp();
|
|
223
|
+
const { run, action } = seedRunAwaitingDeliveryDecision();
|
|
224
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/${action.id}/resolve`, payload: { resolvedBy: "yusuke", choice: "reject" } });
|
|
225
|
+
assert.equal(res.statusCode, 200);
|
|
226
|
+
const body = res.json();
|
|
227
|
+
assert.equal(body.runStatus, "done");
|
|
228
|
+
assert.equal(body.delivered, false);
|
|
229
|
+
assert.equal(pendingSendCount(run.id), 0);
|
|
230
|
+
assert.equal(getRun(run.id).status, "done");
|
|
231
|
+
assert.equal(getHumanAction(action.id).status, "resolved");
|
|
232
|
+
await app.close();
|
|
233
|
+
});
|
|
234
|
+
test("POST resolve outbound_delivery_interaction_required:retry_send re-attempts delivery (no deck configured, typed infra failure, draft stays pending, action stays open)", async () => {
|
|
235
|
+
const app = await buildApp();
|
|
236
|
+
const { run, action } = seedRunAwaitingDeliveryDecision();
|
|
237
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/${action.id}/resolve`, payload: { resolvedBy: "yusuke", choice: "retry_send" } });
|
|
238
|
+
// No Agent Deck enrollment configured in this test environment, so the mint call fails —
|
|
239
|
+
// an ordinary bounded-retry failure, not a crash. The action is left open (not resolved)
|
|
240
|
+
// so the operator doesn't lose the queue item on a failed retry — there is no per-run
|
|
241
|
+
// detail page to navigate back to on the legacy Run model.
|
|
242
|
+
assert.equal(res.statusCode, 502);
|
|
243
|
+
assert.equal(getRun(run.id).status, "review");
|
|
244
|
+
assert.equal(pendingSendCount(run.id), 1);
|
|
245
|
+
assert.equal(getPendingOutboundDraft(run.id)?.content.status, "pending");
|
|
246
|
+
assert.equal(getHumanAction(action.id).status, "open");
|
|
247
|
+
await app.close();
|
|
248
|
+
});
|
|
249
|
+
test("POST resolve outbound_delivery_interaction_required:retry_send can be retried again after a failed attempt (action still open)", async () => {
|
|
250
|
+
const app = await buildApp();
|
|
251
|
+
const { action } = seedRunAwaitingDeliveryDecision();
|
|
252
|
+
const first = await app.inject({ method: "POST", url: `/api/human-actions/${action.id}/resolve`, payload: { resolvedBy: "yusuke", choice: "retry_send" } });
|
|
253
|
+
assert.equal(first.statusCode, 502);
|
|
254
|
+
// Same still-open action id — a real second attempt, not a 409/404 on an already-resolved one.
|
|
255
|
+
const second = await app.inject({ method: "POST", url: `/api/human-actions/${action.id}/resolve`, payload: { resolvedBy: "yusuke", choice: "retry_send" } });
|
|
256
|
+
assert.equal(second.statusCode, 502);
|
|
257
|
+
assert.equal(getHumanAction(action.id).status, "open");
|
|
258
|
+
await app.close();
|
|
259
|
+
});
|
|
260
|
+
test("POST resolve 400s on a choice not valid for outbound_delivery_interaction_required", async () => {
|
|
261
|
+
const app = await buildApp();
|
|
262
|
+
const { action } = seedRunAwaitingDeliveryDecision();
|
|
263
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/${action.id}/resolve`, payload: { resolvedBy: "yusuke", choice: "complete" } });
|
|
264
|
+
assert.equal(res.statusCode, 400);
|
|
265
|
+
await app.close();
|
|
266
|
+
});
|
|
267
|
+
test("POST resolve 400s on a choice not valid for reflection_interaction_required", async () => {
|
|
268
|
+
const app = await buildApp();
|
|
269
|
+
const { issueId, actionId } = await seedRealIssueAwaitingFinalReview();
|
|
270
|
+
await app.inject({ method: "POST", url: `/api/human-actions/${actionId}/resolve`, payload: { resolvedBy: "yusuke", choice: "complete" } });
|
|
271
|
+
const reflectAction = createHumanAction({
|
|
272
|
+
issueId,
|
|
273
|
+
actionType: "reflection_interaction_required",
|
|
274
|
+
reason: "Approve the coordinator enrollment.",
|
|
275
|
+
question: "Retry the reflection, or dismiss?",
|
|
276
|
+
responseOptions: [
|
|
277
|
+
{ choice: "retry", label: "Retry reflection" },
|
|
278
|
+
{ choice: "dismiss", label: "Dismiss" },
|
|
279
|
+
]
|
|
280
|
+
});
|
|
281
|
+
const res = await app.inject({ method: "POST", url: `/api/human-actions/${reflectAction.id}/resolve`, payload: { resolvedBy: "yusuke", choice: "close" } });
|
|
282
|
+
assert.equal(res.statusCode, 400);
|
|
283
|
+
await app.close();
|
|
284
|
+
});
|