@agilno-tech/rivet 0.1.0-alpha.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.
Files changed (283) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +59 -0
  4. package/bin/cli.js +42 -0
  5. package/dist/agents/backend-django-agent/SKILL.md +35 -0
  6. package/dist/agents/backend-nestjs-agent/SKILL.md +36 -0
  7. package/dist/agents/boss-agent/SKILL.md +28 -0
  8. package/dist/agents/bounded-worker/SKILL.md +28 -0
  9. package/dist/agents/data-performance-agent/SKILL.md +32 -0
  10. package/dist/agents/delivery-ticketing-agent/SKILL.md +36 -0
  11. package/dist/agents/devops-agent/SKILL.md +34 -0
  12. package/dist/agents/engineering-manager/SKILL.md +28 -0
  13. package/dist/agents/frontend-nextjs-agent/SKILL.md +34 -0
  14. package/dist/agents/frontend-web-agent/SKILL.md +31 -0
  15. package/dist/agents/incident-response-agent/SKILL.md +42 -0
  16. package/dist/agents/jest-agent/SKILL.md +77 -0
  17. package/dist/agents/mobile-agent/SKILL.md +32 -0
  18. package/dist/agents/playwright-agent/SKILL.md +54 -0
  19. package/dist/agents/product-design-manager/SKILL.md +27 -0
  20. package/dist/agents/qa-agent/SKILL.md +29 -0
  21. package/dist/agents/quality-manager/SKILL.md +28 -0
  22. package/dist/agents/vitest-agent/SKILL.md +77 -0
  23. package/dist/governance/pre-push-rules.md +75 -0
  24. package/dist/governance/prompt-hygiene.md +22 -0
  25. package/dist/governance/review-checklist.md +24 -0
  26. package/dist/governance/safety-and-data.md +27 -0
  27. package/dist/governance/usage-rules.md +21 -0
  28. package/dist/mandatory/address-pr-feedback/SKILL.md +196 -0
  29. package/dist/mandatory/agentic-goal/SKILL.md +42 -0
  30. package/dist/mandatory/agentic-status/SKILL.md +74 -0
  31. package/dist/mandatory/apply-design/SKILL.md +228 -0
  32. package/dist/mandatory/check-ac/SKILL.md +124 -0
  33. package/dist/mandatory/create-pr-and-commit/SKILL.md +304 -0
  34. package/dist/mandatory/design/SKILL.md +312 -0
  35. package/dist/mandatory/feature-workflow/SKILL.md +87 -0
  36. package/dist/mandatory/hotfix/SKILL.md +195 -0
  37. package/dist/mandatory/pre-push/SKILL.md +272 -0
  38. package/dist/mandatory/project-context/SKILL.md +180 -0
  39. package/dist/mandatory/release-docs/SKILL.md +191 -0
  40. package/dist/mandatory/review-pr/SKILL.md +323 -0
  41. package/dist/mandatory/security-review/SKILL.md +157 -0
  42. package/dist/skills/api-contract/SKILL.md +77 -0
  43. package/dist/skills/backend-django/SKILL.md +27 -0
  44. package/dist/skills/backend-nestjs/SKILL.md +65 -0
  45. package/dist/skills/bug-ticket-creation/SKILL.md +43 -0
  46. package/dist/skills/cloudwatch-troubleshooting/SKILL.md +73 -0
  47. package/dist/skills/database-migration/SKILL.md +97 -0
  48. package/dist/skills/debugging/SKILL.md +32 -0
  49. package/dist/skills/devops-infra/SKILL.md +26 -0
  50. package/dist/skills/documentation/SKILL.md +24 -0
  51. package/dist/skills/frontend-nextjs/SKILL.md +27 -0
  52. package/dist/skills/incident-postmortem/SKILL.md +38 -0
  53. package/dist/skills/kubernetes-troubleshooting/SKILL.md +71 -0
  54. package/dist/skills/mobile-react-native/SKILL.md +37 -0
  55. package/dist/skills/postgres-analytics/SKILL.md +29 -0
  56. package/dist/skills/product-jira-ticketing/SKILL.md +28 -0
  57. package/dist/skills/qa-bug-analysis/SKILL.md +27 -0
  58. package/dist/skills/refactoring/SKILL.md +32 -0
  59. package/dist/skills/sprint-planning/SKILL.md +32 -0
  60. package/dist/skills/testing-quality/SKILL.md +62 -0
  61. package/dist/v2/protocols/agent-orchestration.md +49 -0
  62. package/dist/v2/protocols/delivery.md +45 -0
  63. package/dist/v2/protocols/design-authority.md +29 -0
  64. package/dist/v2/protocols/goal-graph.md +44 -0
  65. package/dist/v2/protocols/pattern-first-development.md +29 -0
  66. package/dist/v2/protocols/qa-evidence.md +29 -0
  67. package/dist/v2/protocols/security.md +29 -0
  68. package/dist/v2/schemas/event.schema.json +86 -0
  69. package/dist/v2/schemas/evidence.schema.json +94 -0
  70. package/dist/v2/schemas/feature-decomposition.schema.json +48 -0
  71. package/dist/v2/schemas/feature-plan.schema.json +95 -0
  72. package/dist/v2/schemas/goal-graph.schema.json +61 -0
  73. package/dist/v2/schemas/integration.schema.json +119 -0
  74. package/dist/v2/schemas/orchestration.schema.json +69 -0
  75. package/dist/v2/schemas/project.schema.json +209 -0
  76. package/dist/v2/schemas/providers.schema.json +142 -0
  77. package/dist/v2/schemas/quality.schema.json +50 -0
  78. package/dist/v2/schemas/work-action.schema.json +44 -0
  79. package/dist/v2/schemas/work-request.schema.json +253 -0
  80. package/dist/v2/templates/evidence/qa-bundle.json +194 -0
  81. package/dist/v2/templates/github-actions/rivet-deploy.yml +98 -0
  82. package/dist/v2/templates/harness/SKILL.md +90 -0
  83. package/dist/v2/templates/project/.rivet/orchestration.yaml +42 -0
  84. package/dist/v2/templates/project/.rivet/project.yaml +14 -0
  85. package/dist/v2/templates/project/.rivet/providers.yaml +8 -0
  86. package/dist/v2/templates/project/.rivet/quality.yaml +16 -0
  87. package/package.json +58 -0
  88. package/protocols/agent-orchestration.md +49 -0
  89. package/protocols/delivery.md +45 -0
  90. package/protocols/design-authority.md +29 -0
  91. package/protocols/goal-graph.md +44 -0
  92. package/protocols/pattern-first-development.md +29 -0
  93. package/protocols/qa-evidence.md +29 -0
  94. package/protocols/security.md +29 -0
  95. package/schemas/event.schema.json +86 -0
  96. package/schemas/evidence.schema.json +94 -0
  97. package/schemas/feature-decomposition.schema.json +48 -0
  98. package/schemas/feature-plan.schema.json +95 -0
  99. package/schemas/goal-graph.schema.json +61 -0
  100. package/schemas/integration.schema.json +119 -0
  101. package/schemas/orchestration.schema.json +69 -0
  102. package/schemas/project.schema.json +209 -0
  103. package/schemas/providers.schema.json +142 -0
  104. package/schemas/quality.schema.json +50 -0
  105. package/schemas/work-action.schema.json +44 -0
  106. package/schemas/work-request.schema.json +253 -0
  107. package/src/adapters/confluence.js +138 -0
  108. package/src/adapters/contract.js +657 -0
  109. package/src/adapters/factory.js +122 -0
  110. package/src/adapters/figma.js +178 -0
  111. package/src/adapters/fixtures.js +183 -0
  112. package/src/adapters/github.js +314 -0
  113. package/src/adapters/http.js +704 -0
  114. package/src/adapters/jira.js +195 -0
  115. package/src/adapters/linear.js +171 -0
  116. package/src/adapters/node-transport.js +59 -0
  117. package/src/cli/integration-setup-prompt.js +32 -0
  118. package/src/cli/interrupt.js +25 -0
  119. package/src/cli/main.js +596 -0
  120. package/src/cli/output.js +174 -0
  121. package/src/cli/parse-args.js +246 -0
  122. package/src/cli/project-discovery.js +70 -0
  123. package/src/cli/task-confirmation.js +38 -0
  124. package/src/cli/task-presentation.js +22 -0
  125. package/src/clients/claude.js +214 -0
  126. package/src/clients/codex.js +180 -0
  127. package/src/clients/compatibility.js +23 -0
  128. package/src/clients/contract.js +224 -0
  129. package/src/clients/fake.js +170 -0
  130. package/src/clients/process-runner.js +657 -0
  131. package/src/clients/result-contract.js +78 -0
  132. package/src/commands/delivery-publish.js +80 -0
  133. package/src/commands/delivery-remote.js +230 -0
  134. package/src/commands/delivery-review-update.js +39 -0
  135. package/src/commands/delivery-tracker.js +94 -0
  136. package/src/commands/delivery-transition.js +79 -0
  137. package/src/commands/delivery.js +223 -0
  138. package/src/commands/dependency-approval.js +21 -0
  139. package/src/commands/doctor.js +191 -0
  140. package/src/commands/evidence.js +44 -0
  141. package/src/commands/feature.js +212 -0
  142. package/src/commands/goals.js +231 -0
  143. package/src/commands/human-run.js +208 -0
  144. package/src/commands/human-task.js +278 -0
  145. package/src/commands/init.js +848 -0
  146. package/src/commands/install.js +814 -0
  147. package/src/commands/integration-setup.js +65 -0
  148. package/src/commands/integrations.js +54 -0
  149. package/src/commands/models.js +158 -0
  150. package/src/commands/orchestrate.js +61 -0
  151. package/src/commands/preflight.js +125 -0
  152. package/src/commands/protocols.js +228 -0
  153. package/src/commands/repositories.js +77 -0
  154. package/src/commands/setup-remote.js +41 -0
  155. package/src/commands/setup.js +214 -0
  156. package/src/commands/status.js +224 -0
  157. package/src/commands/support.js +44 -0
  158. package/src/commands/task-completion.js +104 -0
  159. package/src/commands/uninstall.js +234 -0
  160. package/src/commands/verify.js +171 -0
  161. package/src/commands/work.js +220 -0
  162. package/src/commands/worktrees.js +52 -0
  163. package/src/config/command-readiness.js +218 -0
  164. package/src/config/commands.js +206 -0
  165. package/src/config/defaults.js +55 -0
  166. package/src/config/load.js +230 -0
  167. package/src/config/validate.js +560 -0
  168. package/src/delivery/bitbucket-review.js +123 -0
  169. package/src/delivery/branch-publication.js +23 -0
  170. package/src/delivery/contract.js +379 -0
  171. package/src/delivery/github-deployment.js +132 -0
  172. package/src/delivery/github-review.js +5 -0
  173. package/src/delivery/github.js +382 -0
  174. package/src/delivery/gitlab-review.js +5 -0
  175. package/src/delivery/gitlab.js +395 -0
  176. package/src/delivery/prepare.js +90 -0
  177. package/src/delivery/publication-process.js +33 -0
  178. package/src/delivery/publication-transport.js +112 -0
  179. package/src/delivery/review-request.js +184 -0
  180. package/src/delivery/review-update.js +74 -0
  181. package/src/delivery/service.js +400 -0
  182. package/src/delivery/store.js +76 -0
  183. package/src/delivery/tracker-target.js +51 -0
  184. package/src/delivery/tracker-transition.js +158 -0
  185. package/src/delivery/tracker.js +164 -0
  186. package/src/discovery/git.js +85 -0
  187. package/src/discovery/portable.js +108 -0
  188. package/src/discovery/project.js +345 -0
  189. package/src/discovery/tools.js +145 -0
  190. package/src/evaluations/approval.js +20 -0
  191. package/src/evaluations/cost-policy.js +20 -0
  192. package/src/evaluations/harness-attempt.js +113 -0
  193. package/src/evaluations/live-runner.js +55 -0
  194. package/src/evaluations/profile.js +28 -0
  195. package/src/evaluations/report.js +11 -0
  196. package/src/evaluations/text-attempt.js +18 -0
  197. package/src/evidence/checksum.js +87 -0
  198. package/src/evidence/collect.js +727 -0
  199. package/src/evidence/validate.js +354 -0
  200. package/src/feature/accepted-integration.js +46 -0
  201. package/src/feature/actions.js +37 -0
  202. package/src/feature/client-profile.js +56 -0
  203. package/src/feature/decomposition-contract.js +58 -0
  204. package/src/feature/host-execution.js +633 -0
  205. package/src/feature/host-lock-recovery.js +96 -0
  206. package/src/feature/host-run-lock.js +32 -0
  207. package/src/feature/local-approval.js +183 -0
  208. package/src/feature/plan-contract.js +215 -0
  209. package/src/feature/planner.js +253 -0
  210. package/src/feature/run-store.js +241 -0
  211. package/src/feature/runtime-bridge.js +840 -0
  212. package/src/feature/verification-report.js +168 -0
  213. package/src/feature/workflow.js +375 -0
  214. package/src/git/client.js +509 -0
  215. package/src/git/integration-worktree.js +153 -0
  216. package/src/git/reconcile.js +222 -0
  217. package/src/git/reservations.js +450 -0
  218. package/src/git/worktrees.js +478 -0
  219. package/src/graph/completion.js +189 -0
  220. package/src/graph/fixtures.js +116 -0
  221. package/src/graph/reducer.js +124 -0
  222. package/src/graph/scheduler.js +252 -0
  223. package/src/graph/validate.js +106 -0
  224. package/src/install/managed.js +579 -0
  225. package/src/install/project-reference.js +33 -0
  226. package/src/install/project-runtime.js +142 -0
  227. package/src/install/runtime-integrity.cjs +141 -0
  228. package/src/integrations/capabilities.js +69 -0
  229. package/src/integrations/host-observation.js +26 -0
  230. package/src/integrations/registry.js +56 -0
  231. package/src/models/delegate.js +102 -0
  232. package/src/models/profiles.js +53 -0
  233. package/src/models/protocols.js +208 -0
  234. package/src/models/registry.js +83 -0
  235. package/src/models/transport.js +59 -0
  236. package/src/policy/approvals.js +251 -0
  237. package/src/policy/authority.js +282 -0
  238. package/src/policy/budget.js +149 -0
  239. package/src/policy/command-bootstrap.js +219 -0
  240. package/src/policy/commands.js +803 -0
  241. package/src/prompts/launch-contract.js +43 -0
  242. package/src/prompts/planning-contract.js +92 -0
  243. package/src/protocols/presentation.js +53 -0
  244. package/src/protocols/project.js +289 -0
  245. package/src/quality/runner.js +497 -0
  246. package/src/quality/traceability.js +215 -0
  247. package/src/repositories/identity.js +59 -0
  248. package/src/repositories/index.js +2 -0
  249. package/src/repositories/provider.js +210 -0
  250. package/src/runtime/application.js +353 -0
  251. package/src/runtime/dependency-directory.js +33 -0
  252. package/src/runtime/harness-discovery.js +121 -0
  253. package/src/runtime/heartbeat.js +47 -0
  254. package/src/runtime/instance-store.js +112 -0
  255. package/src/runtime/orchestrator.js +1133 -0
  256. package/src/runtime/portable-dependencies.js +94 -0
  257. package/src/runtime/recovery.js +190 -0
  258. package/src/runtime/retry.js +54 -0
  259. package/src/runtime/supervisor.js +166 -0
  260. package/src/runtime/worktree-bootstrap.js +169 -0
  261. package/src/state/event-store.js +333 -0
  262. package/src/state/lock.js +266 -0
  263. package/src/state/paths.js +274 -0
  264. package/src/state/redact.js +106 -0
  265. package/src/state/snapshot-store.js +310 -0
  266. package/src/status/public/app.js +96 -0
  267. package/src/status/public/index.html +37 -0
  268. package/src/status/public/styles.css +50 -0
  269. package/src/status/server.js +323 -0
  270. package/src/status/view-model.js +356 -0
  271. package/src/support/bundle.js +195 -0
  272. package/src/support/failure-report.js +96 -0
  273. package/src/work-request/contract.js +250 -0
  274. package/src/work-request/host.js +78 -0
  275. package/src/work-request/local.js +163 -0
  276. package/src/work-request/tracker.js +105 -0
  277. package/templates/evidence/qa-bundle.json +194 -0
  278. package/templates/github-actions/rivet-deploy.yml +98 -0
  279. package/templates/harness/SKILL.md +90 -0
  280. package/templates/project/.rivet/orchestration.yaml +42 -0
  281. package/templates/project/.rivet/project.yaml +14 -0
  282. package/templates/project/.rivet/providers.yaml +8 -0
  283. package/templates/project/.rivet/quality.yaml +16 -0
@@ -0,0 +1,208 @@
1
+ import { taskState } from '../cli/task-presentation.js';
2
+ import { finishTask } from './task-completion.js';
3
+ import { createHash } from 'node:crypto';
4
+ import { loadProjectConfig } from '../config/load.js';
5
+ import { resolveConfiguredProject } from '../cli/project-discovery.js';
6
+ import { withTerminalInterruption } from '../cli/interrupt.js';
7
+ import { CliError, EXIT_CODES, observeOutputErrors } from '../cli/output.js';
8
+ import { invokeFeature } from './feature.js';
9
+ import { confirmIsolatedDependencyInstall } from './dependency-approval.js';
10
+ import {defaultIntegrationSetupPrompt} from '../cli/integration-setup-prompt.js';
11
+
12
+ const KINDS = new Set(['claude', 'codex']);
13
+
14
+ function fail(message, code = 'INVALID_INPUT') { throw new CliError(message, code); }
15
+
16
+ function visible(value) {
17
+ return String(value).replace(/[\u0000-\u001f\u007f\u009b]/g, character => {
18
+ if (character === '\n') return '\n';
19
+ if (character === '\t') return '\t';
20
+ return `\\u${character.codePointAt(0).toString(16).padStart(4, '0')}`;
21
+ });
22
+ }
23
+
24
+ export function normalizedTask(value) {
25
+ if (typeof value !== 'string' || value.length < 1 || Buffer.byteLength(value, 'utf8') > 4_000
26
+ || !value.trim() || /[\u0000\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(value)
27
+ || value.normalize('NFKC') !== value) fail('Task text is invalid or too long.');
28
+ const task = value.trim();
29
+ if (/^#\s+\S/m.test(task) && /^#{2,6}\s+Acceptance Criteria\s*$/im.test(task)) return task;
30
+ if (/^[A-Z][A-Z0-9]*-[1-9][0-9]*$/.test(task)) {
31
+ fail('A ticket ID alone does not include its acceptance criteria. Use the ticket intake command or describe the task.');
32
+ }
33
+ const criterion = task.replace(/\s+/g, ' ');
34
+ return `# User request\n\n${task}\n\n## Acceptance Criteria\n- ${criterion}\n`;
35
+ }
36
+
37
+ function commandSteps(config, id) {
38
+ const command = config.project.commands[id];
39
+ if (!command) return ['not configured'];
40
+ const steps = Array.isArray(command) ? [{ cwd: '.', argv: command }] : command.steps;
41
+ return steps.map(step => `${step.cwd}: ${step.argv.map(argument=>/^[A-Za-z0-9_./:=+-]+$/.test(argument)?argument:JSON.stringify(argument)).join(' ')}`);
42
+ }
43
+
44
+ function reviewLines(project, task, harness, proposal, details = false) {
45
+ const request = proposal.workRequest;
46
+ const plan = proposal.featurePlan;
47
+ if (!request || !plan || !Array.isArray(plan.nodes) || !Array.isArray(request.acceptanceCriteria)
48
+ || typeof proposal.proposalDigest !== 'string' || !Number.isSafeInteger(proposal.version)) {
49
+ fail('Rivet returned an invalid proposal.', 'INTERNAL_ERROR');
50
+ }
51
+ const lines = [
52
+ 'Review this exact Rivet plan before activation:',
53
+ ...(details ? [`Summary: ${proposal.summary ?? 'Ready for review.'}`] : []),
54
+ `Project: ${project.root}`,
55
+ `Harness: ${harness.kind} (${harness.version})`,
56
+ ...(details ? [`Executable: ${harness.executable}`] : []),
57
+ `Base commit: ${plan.baselineCommit}`,
58
+ `Providers: ${(plan.providerRefs ?? []).join(', ') || 'none'}`,
59
+ ...(plan.clientProfile ? [`Client limits: ${JSON.stringify(plan.clientProfile)}`] : []),
60
+ 'Request:', task,
61
+ 'Acceptance criteria:', ...request.acceptanceCriteria.map((item,index) => ` ${index+1}. ${item}`),
62
+ 'Work and scope:',
63
+ ];
64
+ const labels = new Map();
65
+ let workerNumber = 0;
66
+ for (const node of plan.nodes) labels.set(node.id,node.role==='worker' ? `Work item ${++workerNumber}` : node.approvalGate==='activation' ? 'Plan approval' : node.approvalGate==='final-delivery' ? 'Final review' : 'Coordination');
67
+ for (const node of plan.nodes) {
68
+ lines.push(details ? ` ${node.role} (${node.id}): ${node.objective}` : ` ${labels.get(node.id)}${node.role==='worker' ? `: ${node.objective}` : ''}`);
69
+ for (const path of node.ownedPaths ?? []) lines.push(` path: ${path}`);
70
+ if (node.dependencies?.length) lines.push(` after: ${node.dependencies.map(id=>details?id:labels.get(id)??id).join(', ')}`);
71
+ if (node.acceptanceCriteria?.length) lines.push(` accepts: ${node.acceptanceCriteria.map(criterion=>!details && request.acceptanceCriteria.includes(criterion) ? `criterion ${request.acceptanceCriteria.indexOf(criterion)+1}` : criterion).join('; ')}`);
72
+ if (node.authorityScopes?.length) lines.push(` authority: ${node.authorityScopes.join(', ')}`);
73
+ if (node.commandIds?.length) lines.push(` commands: ${node.commandIds.join(', ')}`);
74
+ if (node.requiredEvidenceTypes?.length) lines.push(` evidence: ${node.requiredEvidenceTypes.join(', ')}`);
75
+ if (node.role === 'worker') {
76
+ lines.push(` executor: ${node.execution?.client ?? plan.client ?? harness.kind}`);
77
+ const profile = node.execution ? node.execution.clientProfile : plan.clientProfile;
78
+ if (profile) lines.push(` executor limits: ${JSON.stringify(profile)}`);
79
+ }
80
+ if (node.budget) lines.push(details ? ` budget: ${JSON.stringify(node.budget)}` : ` limits: ${Object.entries(node.budget).map(([key,value])=>key==='timeMinutes'?`${value} min`:key==='tokenLimit'?`${value} tokens`:key==='costUsd'?`$${value}`:key==='taskLimit'?`${value} tasks`:`${key}: ${JSON.stringify(value)}`).join('; ')}`);
81
+ if (node.approvalGate) lines.push(` human gate: ${node.approvalGate}`);
82
+ }
83
+ lines.push('Activation and final review share the Boss allowance; those repeated limits are not added together.');
84
+ if (!details) lines.push('Use --details when starting or resuming to include full graph identifiers.');
85
+ lines.push('Required checks:');
86
+ for (const gate of project.config.quality.commandGates.filter(item => item.required)) {
87
+ lines.push(` ${gate.command}:`);
88
+ for (const step of commandSteps(project.config, gate.command)) lines.push(` ${step}`);
89
+ }
90
+ for (const ref of request.contextRefs ?? []) lines.push(`Context/protocol: ${ref}`);
91
+ lines.push('Stop conditions: a blocked worker, interrupted run, failed required check, or final human approval gate.');
92
+ return lines.map(visible);
93
+ }
94
+
95
+ function resultCode(result) {
96
+ if (result.status === 'awaiting-final-approval') return EXIT_CODES.SUCCESS;
97
+ if (result.status === 'blocked') {
98
+ return /quality gate|configured check/i.test(result.summary ?? '')
99
+ ? EXIT_CODES.FAILED_GATE : EXIT_CODES.REPOSITORY_CONFLICT;
100
+ }
101
+ return EXIT_CODES.REPOSITORY_CONFLICT;
102
+ }
103
+
104
+ export async function humanRunCommand(parsed, dependencies) {
105
+ if (parsed.command !== 'run' || parsed.subcommand !== null || parsed.operands.length !== 1
106
+ || Object.keys(parsed.flags).some(key => !['project', 'harness', 'details'].includes(key))) {
107
+ fail('Use rivet run "task" [--harness=claude|codex] [--project=<path>].');
108
+ }
109
+ if (dependencies.terminalIsInteractive?.() !== true) {
110
+ fail('rivet run needs an interactive terminal for plan review and approval. Use feature/work JSON commands for automation.');
111
+ }
112
+ return withTerminalInterruption(signal => runInteractive(parsed, dependencies, signal));
113
+ }
114
+
115
+ async function runInteractive(parsed, dependencies, signal) {
116
+ const project = await resolveConfiguredProject(dependencies.cwd(), parsed.flags.project, { env: dependencies.env });
117
+ const harnesses = dependencies.harnesses;
118
+ if (!harnesses || typeof harnesses.discover !== 'function' || typeof harnesses.select !== 'function') {
119
+ fail('Harness discovery is unavailable.', 'MISSING_CONFIGURATION');
120
+ }
121
+ const requested = parsed.flags.harness;
122
+ if (requested !== undefined && !KINDS.has(requested)) fail('Use --harness=claude or --harness=codex.');
123
+ const discovered = await harnesses.discover(project.root, { signal });
124
+ const eligible = discovered.filter(item => item.executable);
125
+ let choice;
126
+ if (requested) {
127
+ choice = eligible.find(item => item.kind === requested);
128
+ if (!choice) {
129
+ const reason = discovered.find(item => item.kind === requested)?.reason;
130
+ const interpreterHelp = reason === 'interpreter-required'
131
+ ? ` This CLI is a script; set RIVET_${requested.toUpperCase()}_INTERPRETER to its canonical native interpreter path.` : '';
132
+ fail(`The ${requested} adapter is unavailable (${visible(reason ?? 'probe failed')}). Install a CLI with the required options and authenticate it.${interpreterHelp} Check ${requested} ${requested === 'codex' ? 'exec --help' : '--help'}. You can use the Rivet skill in your coding harness meanwhile.`, 'PROVIDER_UNAVAILABLE');
133
+ }
134
+ } else if (eligible.length === 1) choice = eligible[0];
135
+ else if (eligible.length === 0) {
136
+ const scripts = discovered.filter(item => item.reason === 'interpreter-required').map(item => `RIVET_${item.kind.toUpperCase()}_INTERPRETER`);
137
+ const interpreterHelp = scripts.length ? ` Script CLIs need a canonical native interpreter path in ${scripts.join(' or ')}.` : '';
138
+ fail(`No compatible CLI was found. ${discovered.map(item => `${item.kind}: ${visible(item.reason)}`).join('; ')}.${interpreterHelp} Check claude --help or codex exec --help, or use the Rivet skill in your coding harness.`, 'PROVIDER_UNAVAILABLE');
139
+ } else {
140
+ const kind=await (dependencies.harnessChoicePrompt??defaultIntegrationSetupPrompt)({type:'select',signal,message:'Choose an installed harness for this task:',choices:eligible.map(item=>({value:item.kind,label:`${item.kind} (${visible(item.version)})`}))});
141
+ if(kind===null || kind===undefined) {dependencies.output.log('Harness selection cancelled. No model was called.');return EXIT_CODES.SUCCESS;}
142
+ choice=eligible.find(item=>item.kind===kind);
143
+ if(!choice)fail('Select an available harness, or use --harness=claude or --harness=codex.');
144
+ }
145
+ signal.throwIfAborted();
146
+ let selected;
147
+ try { selected = await harnesses.select(choice.kind, project.root, { signal }); }
148
+ catch { fail(`The ${choice.kind} adapter changed during discovery. Check the installed CLI and retry.`, 'PROVIDER_UNAVAILABLE'); }
149
+ const task = normalizedTask(parsed.operands[0]);
150
+ const feature = dependencies.feature;
151
+ if (!feature || ['propose', 'start', 'watch', 'status'].some(name => typeof feature[name] !== 'function')) {
152
+ fail('Feature workflow is unavailable.', 'MISSING_CONFIGURATION');
153
+ }
154
+ const proposal = await invokeFeature(feature, 'propose', {
155
+ project: project.root, source: { kind: 'inline', value: task }, client: choice.kind,
156
+ }, { signal });
157
+ if (proposal.status !== 'proposed') {
158
+ const existing = await invokeFeature(feature, 'status', { project: project.root, runId: proposal.runId });
159
+ dependencies.output.log(`This exact request already has a ${visible(existing.status)} run. Use rivet task status to inspect it.`);
160
+ return existing.status === 'awaiting-final-approval' ? EXIT_CODES.SUCCESS : EXIT_CODES.REPOSITORY_CONFLICT;
161
+ }
162
+ for (const kind of new Set(proposal.featurePlan.nodes.filter(node => node.role === 'worker')
163
+ .map(node => node.execution?.client).filter(Boolean))) {
164
+ if (!KINDS.has(kind)) fail('The worker harness is unsupported.', 'PROVIDER_UNAVAILABLE');
165
+ try { await harnesses.select(kind, project.root, { signal }); }
166
+ catch { fail(`The selected worker harness ${kind} is unavailable or incompatible. Install and authenticate it before activation.`, 'PROVIDER_UNAVAILABLE'); }
167
+ }
168
+ return activateProposal(project, parsed.operands[0], selected, proposal, dependencies, signal, {details:parsed.flags.details});
169
+ }
170
+
171
+ export async function activateProposal(project, task, selected, proposal, dependencies, signal, {details = false} = {}) {
172
+ const reviewedConfig = createHash('sha256').update(JSON.stringify(project.config)).digest('hex');
173
+ const feature = dependencies.feature;
174
+ if (!feature || typeof feature.start !== 'function' || typeof feature.watch !== 'function'
175
+ || typeof dependencies.confirmFeatureActivation !== 'function') fail('Feature workflow is unavailable.', 'MISSING_CONFIGURATION');
176
+ let outputFailed = false;
177
+ const unobserve = observeOutputErrors(dependencies.output, () => { outputFailed = true; });
178
+ let approved = false;
179
+ try {
180
+ try {
181
+ for (const line of reviewLines(project, task, selected, proposal, details)) dependencies.output.log(line);
182
+ } catch { fail('Could not display the complete plan. The task was not activated.', 'REPOSITORY_CONFLICT'); }
183
+ if (outputFailed) fail('Could not display the complete plan. The task was not activated.', 'REPOSITORY_CONFLICT');
184
+ if (signal.aborted) fail('Task interrupted before activation.', 'REPOSITORY_CONFLICT');
185
+ try { approved = await dependencies.confirmFeatureActivation(proposal, { signal }) === true; } catch { /* EOF is a decline. */ }
186
+ if (outputFailed) fail('Plan output failed before approval. The task was not activated.', 'REPOSITORY_CONFLICT');
187
+ } finally { unobserve?.(); }
188
+ if (!approved) {
189
+ dependencies.output.log('Plan not activated. The proposal remains available for review.');
190
+ dependencies.output.log(`Review and start it later (from the project shown above): rivet task start --run=${proposal.runId}`);
191
+ return EXIT_CODES.SUCCESS;
192
+ }
193
+ if (signal.aborted) fail('Task interrupted before activation.', 'REPOSITORY_CONFLICT');
194
+ const currentConfig = createHash('sha256').update(JSON.stringify(await loadProjectConfig(project.root))).digest('hex');
195
+ if (currentConfig !== reviewedConfig) fail('Project configuration changed during review. Review the saved proposal again with rivet task start.', 'REPOSITORY_CONFLICT');
196
+ const started = await invokeFeature(feature, 'start', {
197
+ project: project.root, runId: proposal.runId,
198
+ expectedVersion: proposal.version, proposalDigest: proposal.proposalDigest,
199
+ });
200
+ const result = await invokeFeature(feature, 'watch', {
201
+ project: project.root, runId: proposal.runId, expectedVersion: started.version,
202
+ }, { signal, confirmDependencyInstall: confirmIsolatedDependencyInstall(dependencies, signal) });
203
+ dependencies.output.log(`Rivet task: ${taskState(result.status)}.`);
204
+ if (result.summary) dependencies.output.log(visible(result.summary));
205
+ dependencies.output.log('Use rivet task status to inspect the checkout and verification evidence.');
206
+ if (result.status === 'awaiting-final-approval') return finishTask(project, {...proposal,status:result.status}, dependencies, {signal});
207
+ return resultCode(result);
208
+ }
@@ -0,0 +1,278 @@
1
+ import { taskState } from '../cli/task-presentation.js';
2
+ import { finishTask } from './task-completion.js';
3
+ import { resolveConfiguredProject } from '../cli/project-discovery.js';
4
+ import { withTerminalInterruption } from '../cli/interrupt.js';
5
+ import { CliError, EXIT_CODES } from '../cli/output.js';
6
+ import { loadProjectConfig } from '../config/load.js';
7
+ import { createFeatureRunStore } from '../feature/run-store.js';
8
+ import { createGitClient } from '../git/client.js';
9
+ import { createReservationStore } from '../git/reservations.js';
10
+ import { bootstrapWorktreeDependencies } from '../runtime/worktree-bootstrap.js';
11
+ import { listExistingFeatureRunPaths, resolveExistingStatePaths } from '../state/paths.js';
12
+ import { activateProposal } from './human-run.js';
13
+ import { invokeFeature } from './feature.js';
14
+ import { confirmIsolatedDependencyInstall } from './dependency-approval.js';
15
+
16
+ const RUN_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
17
+ const ACTIVE = new Set(['proposed', 'approved', 'running', 'blocked', 'awaiting-final-approval']);
18
+
19
+ function fail(message, code = 'INVALID_INPUT') { throw new CliError(message, code); }
20
+
21
+ function visible(value) {
22
+ return String(value).replace(/[\u0000-\u001f\u007f\u009b]/g, character =>
23
+ `\\u${character.codePointAt(0).toString(16).padStart(4, '0')}`);
24
+ }
25
+
26
+ function diagnostic(value) {
27
+ return String(value).replace(/[\u0000-\u001f\u007f\u009b]/g, character =>
28
+ character === '\n' || character === '\t'
29
+ ? character : `\\u${character.codePointAt(0).toString(16).padStart(4, '0')}`);
30
+ }
31
+
32
+ async function runs(project) {
33
+ try {
34
+ const paths = await listExistingFeatureRunPaths(project);
35
+ const result = [];
36
+ for (const path of paths) {
37
+ const record = await createFeatureRunStore(path).readOnly();
38
+ if (record === null) fail('A private run is incomplete. Inspect its state before selecting another run.', 'REPOSITORY_CONFLICT');
39
+ result.push(record);
40
+ }
41
+ return result;
42
+ } catch (error) {
43
+ if (error instanceof CliError) throw error;
44
+ fail('Private run discovery failed or found unsafe state. Inspect it before continuing.', 'REPOSITORY_CONFLICT');
45
+ }
46
+ }
47
+
48
+ function summary(record) {
49
+ const criterion = record.workRequest.acceptanceCriteria[0] ?? record.workRequest.title;
50
+ return `${record.runId}: ${record.status} (${record.featurePlan.client}) ${visible(criterion.slice(0, 100))}`;
51
+ }
52
+
53
+ async function selectedRun(project, wanted, operation, dependencies) {
54
+ const {output} = dependencies;
55
+ const records = await runs(project);
56
+ if (wanted !== undefined) {
57
+ if (typeof wanted !== 'string' || !RUN_ID.test(wanted)) fail('Run selector is invalid.');
58
+ const exact = records.find(record => record.runId === wanted);
59
+ if (!exact) fail('No task with that run ID exists in this project.', 'REPOSITORY_CONFLICT');
60
+ return exact;
61
+ }
62
+ const eligible = records.filter(record => ACTIVE.has(record.status)
63
+ && (operation !== 'recover' || record.featurePlan.client === 'host' || record.status === 'awaiting-final-approval'));
64
+ if (eligible.length === 0) fail(operation === 'recover' ? 'No host task or terminal task at final review is available for lock recovery.'
65
+ : 'No active task exists in this project. Start one with rivet run "task".', 'REPOSITORY_CONFLICT');
66
+ if (eligible.length > 1) {
67
+ if (dependencies.terminalIsInteractive?.() === true) {
68
+ const choice = await withTerminalInterruption(async signal => {
69
+ const selected = await dependencies.taskSelectionPrompt({type:'select',signal,message:'Which task would you like to continue?',choices:eligible.map(record=>({value:record.runId,label:`${visible(record.workRequest.acceptanceCriteria[0] ?? record.workRequest.title)} (${taskState(record.status)})`}))});
70
+ signal.throwIfAborted();
71
+ return selected;
72
+ });
73
+ if (choice === null || choice === undefined) return null;
74
+ if (!eligible.some(record=>record.runId===choice)) fail('Select one of the listed tasks.');
75
+ const current = (await runs(project)).find(record=>record.runId===choice);
76
+ if (!current) fail('The selected task is no longer available. Run rivet task status.', 'REPOSITORY_CONFLICT');
77
+ return current;
78
+ }
79
+ output.log('Several active tasks exist:');
80
+ for (const record of eligible) output.log(` ${summary(record)}`);
81
+ fail('Choose one with --run=<id>, or run interactively to choose from a numbered list.');
82
+ }
83
+ return eligible[0];
84
+ }
85
+
86
+ async function dependencyCommand(project, record, dependencies) {
87
+ if (dependencies.terminalIsInteractive?.() !== true || typeof dependencies.confirmDependencyInstall !== 'function') {
88
+ fail('Run rivet task deps in an interactive terminal to approve dependency installation.');
89
+ }
90
+ if (typeof dependencies.work?.status !== 'function'
91
+ || typeof dependencies.resolveCommandExecutable !== 'function') fail('Dependency setup is unavailable.', 'MISSING_CONFIGURATION');
92
+ const status = await invokeFeature(dependencies.work, 'status', { project: project.root, runId: record.runId });
93
+ let target = null;
94
+ if (record.featurePlan.client === 'host' && record.status === 'running') {
95
+ const statePaths = await resolveExistingStatePaths(project.root, record.runId);
96
+ const workerIds = new Set(record.featurePlan.nodes.filter(node => node.role === 'worker').map(node => node.id));
97
+ const nodes = (status.runtime?.nodes ?? []).filter(node => node.status === 'running' && workerIds.has(node.id));
98
+ if (statePaths && nodes.length === 1) {
99
+ const reservations = await createReservationStore(statePaths).list();
100
+ const matches = reservations.reservations.filter(item => item.status === 'active' && item.nodeId === nodes[0].id);
101
+ if (matches.length === 1) target = {
102
+ path: matches[0].worktreePath,
103
+ commit: matches[0].baseSha,
104
+ branch: matches[0].branch,
105
+ kind: 'worker',
106
+ };
107
+ }
108
+ }
109
+ if (!target && status.checkout?.status === 'clean') target = {
110
+ path: status.checkout.path,
111
+ commit: status.checkout.acceptedCommit,
112
+ branch: status.checkout.branch,
113
+ kind: 'integration',
114
+ };
115
+ if (!['running', 'blocked'].includes(record.status) || !target) {
116
+ fail('This task has no eligible isolated checkout yet. Continue the task, then retry dependency setup.', 'REPOSITORY_CONFLICT');
117
+ }
118
+ const config = await loadProjectConfig(project.root);
119
+ if(!config.project.dependencies && !['npm','pnpm','yarn','bun'].includes(config.project.stack.packageManager)) {
120
+ fail('No dependency installation plan is configured. Define inputs, provides, and steps in project.dependencies in .rivet/project.yaml, review and commit the policy, then start a new task. For the current task, prepare its isolated environment using the project’s documented procedure. No installation was attempted.','INVALID_INPUT');
121
+ }
122
+ let gitClient;
123
+ try { gitClient = await createGitClient({ gitExecutable: await dependencies.resolveCommandExecutable('git') }); }
124
+ catch { fail('A supported Git executable is required for dependency setup.', 'PROVIDER_UNAVAILABLE'); }
125
+ return withTerminalInterruption(async signal => {
126
+ let result;
127
+ try {
128
+ result = await bootstrapWorktreeDependencies({
129
+ projectRoot: project.root,
130
+ worktreePath: target.path,
131
+ expectedCommit: target.commit,
132
+ expectedBranch: target.branch,
133
+ ...(config.project.dependencies ? {dependencies:config.project.dependencies} : {manager: config.project.stack.packageManager}),
134
+ }, {
135
+ gitClient,
136
+ resolveCommandExecutable: dependencies.resolveCommandExecutable,
137
+ confirm: confirmIsolatedDependencyInstall(dependencies, signal),
138
+ signal,
139
+ });
140
+ } catch (error) {
141
+ fail(error?.safeMessage ?? 'Dependency setup failed safely.', 'FAILED_GATE');
142
+ }
143
+ if (result.status === 'declined') {
144
+ dependencies.output.log('Dependency setup was declined.');
145
+ return EXIT_CODES.SUCCESS;
146
+ }
147
+ if (result.status !== 'ready') {
148
+ dependencies.output.error(`Dependency setup failed (${visible(result.result.status)}).`);
149
+ if (result.result.stdout) dependencies.output.error(`stdout:\n${diagnostic(result.result.stdout)}`);
150
+ if (result.result.stderr) dependencies.output.error(`stderr:\n${diagnostic(result.result.stderr)}`);
151
+ return EXIT_CODES.FAILED_GATE;
152
+ }
153
+ dependencies.output.log(record.featurePlan.client === 'host'
154
+ ? target.kind === 'worker'
155
+ ? 'Dependencies are ready in the Worker checkout. Continue in the owning harness.'
156
+ : 'Dependencies are ready in the isolated checkout. Continue in the owning harness and retry work verify.'
157
+ : 'Dependencies are ready in the isolated checkout. Use rivet task resume to retry verification.');
158
+ return EXIT_CODES.SUCCESS;
159
+ });
160
+ }
161
+
162
+ function renderWorkerCheckouts(status, output) {
163
+ for (const worker of status.workerCheckouts ?? []) {
164
+ output.log(`Worker checkout: ${visible(worker.nodeId)} (${visible(worker.status)}; ${visible(worker.reservationStatus)}${worker.leaseExpired ? '; lease expired' : ''})`);
165
+ output.log(` Path: ${visible(worker.path)}`);
166
+ output.log(` Branch: ${visible(worker.expectedBranch)}; observed: ${visible(worker.observedBranch ?? 'unknown')}`);
167
+ for (const path of worker.dirtyPaths) output.log(` Uncommitted: ${visible(path)}`);
168
+ for (const path of worker.branchLocations) output.log(` Branch location: ${visible(path)}`);
169
+ output.log(` Next: ${visible(worker.nextAction)}`);
170
+ }
171
+ }
172
+
173
+
174
+ export async function humanTaskCommand(parsed, dependencies) {
175
+ if (parsed.command !== 'task' || !['status', 'start', 'approve', 'resume', 'deps', 'recover'].includes(parsed.subcommand)
176
+ || parsed.operands.length !== 0 || Object.keys(parsed.flags).some(key => !['project', 'run', 'details'].includes(key))) {
177
+ fail('Use rivet task status|start|approve|resume|deps|recover [--project=<path>] [--run=<id>].');
178
+ }
179
+ const project = await resolveConfiguredProject(dependencies.cwd(), parsed.flags.project, { env: dependencies.env });
180
+ const record = await selectedRun(project.root, parsed.flags.run, parsed.subcommand, dependencies);
181
+ if (!record) {dependencies.output.log('Task selection cancelled. No task action was taken.');return EXIT_CODES.SUCCESS;}
182
+ if (parsed.subcommand === 'approve') return finishTask(project, record, dependencies);
183
+ if (parsed.subcommand === 'recover') {
184
+ if (record.featurePlan.client !== 'host' && !['awaiting-final-approval','completed'].includes(record.status)) fail('Lock recovery is only available for host tasks or terminal tasks at final review; it never restarts a worker.', 'REPOSITORY_CONFLICT');
185
+ if (typeof dependencies.work?.recover !== 'function') fail('Host recovery is unavailable.', 'MISSING_CONFIGURATION');
186
+ const result = await invokeFeature(dependencies.work, 'recover', { project: project.root, runId: record.runId });
187
+ dependencies.output.log(`Recovery: ${visible(result.status)}.`);
188
+ dependencies.output.log(`Recovered locks: ${result.recoveredLocks.map(visible).join(', ') || 'none'}.`);
189
+ if (result.blockedLock) dependencies.output.error(`Blocked lock: ${visible(result.blockedLock)}.`);
190
+ dependencies.output.log(visible(result.nextAction));
191
+ return result.status === 'recovered' ? EXIT_CODES.SUCCESS : EXIT_CODES.REPOSITORY_CONFLICT;
192
+ }
193
+ if (parsed.subcommand === 'deps') return dependencyCommand(project, record, dependencies);
194
+ if (parsed.subcommand === 'status') {
195
+ if (typeof dependencies.work?.status !== 'function') fail('Task status is unavailable.', 'MISSING_CONFIGURATION');
196
+ const status = await invokeFeature(dependencies.work, 'status', { project: project.root, runId: record.runId });
197
+ dependencies.output.log(`Run: ${record.runId}`);
198
+ dependencies.output.log(`Project: ${visible(project.root)} (run the next command from this project)`);
199
+ dependencies.output.log(`Task: ${visible(record.workRequest.acceptanceCriteria[0] ?? record.workRequest.title)}`);
200
+ dependencies.output.log(`State: ${taskState(record.status)}${parsed.flags.details ? ` (${visible(record.status)})` : ''}`);
201
+ if (record.featurePlan.nodes.some(node => node.execution)) {
202
+ dependencies.output.log(`Planning harness: ${record.featurePlan.client}`);
203
+ dependencies.output.log(`Worker harnesses: ${[...new Set(record.featurePlan.nodes.filter(node => node.role === 'worker').map(node => node.execution?.client ?? record.featurePlan.client))].join(', ')}`);
204
+ } else dependencies.output.log(`Harness: ${record.featurePlan.client}`);
205
+ dependencies.output.log(`Next: ${record.status === 'completed' ? 'Task completed. Inspect its recorded evidence and any separate delivery status.' : record.status === 'proposed' && record.featurePlan.client !== 'host' ? `rivet task start --run=${record.runId}` : record.status === 'awaiting-final-approval' && status.deliveryReady ? `rivet task approve --run=${record.runId}` : visible(status.nextAction)}`);
206
+ if (status.verification) {
207
+ dependencies.output.log(`Verification: ${status.verification.status} at ${status.verification.commitSha}`);
208
+ for (const path of status.verification.changedPaths) dependencies.output.log(`Changed: ${visible(path)}`);
209
+ for (const check of status.verification.checks) {
210
+ dependencies.output.log(`Check: ${visible(check.id)} ${check.status} (${visible(check.cwd)})`);
211
+ if (check.status === 'failed') {
212
+ dependencies.output.log(` Exit: ${check.exitCode ?? 'unknown'}; execution: ${visible(check.executionStatus)}`);
213
+ if (check.output?.stdout) dependencies.output.log(` stdout:\n${diagnostic(check.output.stdout)}`);
214
+ if (check.output?.stderr) dependencies.output.log(` stderr:\n${diagnostic(check.output.stderr)}`);
215
+ if (check.output?.truncated?.combined || check.output?.truncated?.stdout || check.output?.truncated?.stderr) {
216
+ dependencies.output.log(' Output was truncated.');
217
+ }
218
+ if (check.output?.redacted) dependencies.output.log(' Output was redacted.');
219
+ if (check.output?.suppressed) dependencies.output.log(' Output was suppressed.');
220
+ }
221
+ }
222
+ if (status.verification.failure) dependencies.output.log(`Failure: ${visible(status.verification.failure)}`);
223
+ }
224
+ renderWorkerCheckouts(status, dependencies.output);
225
+ if (status.checkout) dependencies.output.log(`Integration checkout: ${visible(status.checkout.path)} (${status.checkout.status})`);
226
+ return EXIT_CODES.SUCCESS;
227
+ }
228
+ if (parsed.subcommand === 'start' || (parsed.subcommand === 'resume' && record.status === 'proposed' && record.featurePlan.client !== 'host')) {
229
+ if (record.status !== 'proposed') fail('This task is already activated. Use rivet task status to see its next step.', 'REPOSITORY_CONFLICT');
230
+ if (record.featurePlan.client === 'host') fail('Continue proposal approval through the coding harness that owns this task.', 'REPOSITORY_CONFLICT');
231
+ if (dependencies.terminalIsInteractive?.() !== true) fail('Run rivet task start in an interactive terminal to review and approve the saved plan.');
232
+ if (typeof dependencies.harnesses?.select !== 'function') fail('Harness discovery is unavailable.', 'MISSING_CONFIGURATION');
233
+ return withTerminalInterruption(async signal => {
234
+ let selected;
235
+ for (const client of new Set([record.featurePlan.client, ...record.featurePlan.nodes.filter(node => node.role === 'worker').map(node => node.execution?.client ?? record.featurePlan.client)])) {
236
+ try {
237
+ const available = await dependencies.harnesses.select(client, project.root, {signal});
238
+ if (client === record.featurePlan.client) selected = available;
239
+ } catch { fail(`The saved harness ${client} is unavailable or incompatible. Restore it before starting.`, 'PROVIDER_UNAVAILABLE'); }
240
+ }
241
+ return activateProposal(project, record.workRequest.description, selected, record, dependencies, signal, {details:parsed.flags.details});
242
+ });
243
+ }
244
+ if (record.status === 'awaiting-final-approval') return finishTask(project, record, dependencies);
245
+ if (record.status === 'completed') {dependencies.output.log('This task is completed. Inspect its recorded evidence and any separate delivery status.');return EXIT_CODES.SUCCESS;}
246
+ if (record.featurePlan.client === 'host') {
247
+ const status = await invokeFeature(dependencies.work, 'status', { project: project.root, runId: record.runId });
248
+ renderWorkerCheckouts(status, dependencies.output);
249
+ dependencies.output.log(`Run: ${record.runId}`);
250
+ dependencies.output.log(`Host task: ${visible(status.run.status)}. ${visible(status.nextAction)}`);
251
+ dependencies.output.log('Continue in the coding harness that owns this task; Rivet will not launch a second worker.');
252
+ return EXIT_CODES.SUCCESS;
253
+ }
254
+ if (record.status === 'running') {
255
+ fail('This spawned run is already marked running. Rivet cannot prove its worker stopped, so it will not launch a duplicate. Inspect its process and state.', 'REPOSITORY_CONFLICT');
256
+ }
257
+ if (!['approved', 'blocked'].includes(record.status)) {
258
+ fail('This task cannot be resumed from its current state.', 'REPOSITORY_CONFLICT');
259
+ }
260
+ if (typeof dependencies.feature?.resume !== 'function') fail('Feature resume is unavailable.', 'MISSING_CONFIGURATION');
261
+ if (typeof dependencies.harnesses?.select !== 'function') fail('Harness discovery is unavailable.', 'MISSING_CONFIGURATION');
262
+ return withTerminalInterruption(async signal => {
263
+ const clients = new Set(record.featurePlan.nodes.filter(node => node.role === 'worker')
264
+ .map(node => node.execution?.client ?? record.featurePlan.client));
265
+ for (const client of clients) {
266
+ try { await dependencies.harnesses.select(client, project.root, { signal }); }
267
+ catch { fail(`The saved worker harness ${client} is unavailable or incompatible. Restore it before resuming; Rivet will not switch models.`, 'PROVIDER_UNAVAILABLE'); }
268
+ }
269
+ const result = await invokeFeature(dependencies.feature, 'resume', {
270
+ project: project.root, runId: record.runId, expectedVersion: record.version,
271
+ }, { signal, confirmDependencyInstall: confirmIsolatedDependencyInstall(dependencies, signal) });
272
+ dependencies.output.log(`Task: ${taskState(result.status)}.`);
273
+ if (result.summary) dependencies.output.log(visible(result.summary));
274
+ if (result.status === 'awaiting-final-approval') return finishTask(project, {...record,status:result.status}, dependencies, {signal});
275
+ return result.status === 'awaiting-final-approval' ? EXIT_CODES.SUCCESS
276
+ : result.status === 'blocked' ? EXIT_CODES.FAILED_GATE : EXIT_CODES.REPOSITORY_CONFLICT;
277
+ });
278
+ }