@skrr-ai/cli 0.1.22 → 0.1.24

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 (281) hide show
  1. package/README.md +83 -73
  2. package/bin/dev-fallback.js +221 -0
  3. package/bin/run.js +96 -0
  4. package/dist/base-command.d.ts +15 -0
  5. package/dist/base-command.js +145 -11
  6. package/dist/commands/agents/attach-harness.d.ts +37 -0
  7. package/dist/commands/agents/attach-harness.js +102 -3
  8. package/dist/commands/agents/chat.d.ts +69 -1
  9. package/dist/commands/agents/chat.js +202 -22
  10. package/dist/commands/agents/create.d.ts +38 -0
  11. package/dist/commands/agents/create.js +95 -2
  12. package/dist/commands/agents/schedule-trace.js +11 -0
  13. package/dist/commands/agents/show.js +29 -1
  14. package/dist/commands/api-keys/create.d.ts +24 -0
  15. package/dist/commands/api-keys/create.js +80 -0
  16. package/dist/commands/api-keys/list.d.ts +11 -0
  17. package/dist/commands/api-keys/list.js +42 -0
  18. package/dist/commands/api-keys/revoke.d.ts +14 -0
  19. package/dist/commands/api-keys/revoke.js +33 -0
  20. package/dist/commands/api-keys/rotate.d.ts +19 -0
  21. package/dist/commands/api-keys/rotate.js +42 -0
  22. package/dist/commands/balance/index.d.ts +18 -0
  23. package/dist/commands/balance/index.js +29 -0
  24. package/dist/commands/balance/show.d.ts +25 -0
  25. package/dist/commands/balance/show.js +60 -0
  26. package/dist/commands/balance/statement.d.ts +19 -0
  27. package/dist/commands/balance/statement.js +52 -0
  28. package/dist/commands/browser/doctor.d.ts +11 -0
  29. package/dist/commands/browser/doctor.js +11 -0
  30. package/dist/commands/code/jobs/approve.d.ts +25 -0
  31. package/dist/commands/code/jobs/approve.js +52 -0
  32. package/dist/commands/code/jobs/cancel.d.ts +12 -0
  33. package/dist/commands/code/jobs/cancel.js +39 -0
  34. package/dist/commands/code/jobs/index.d.ts +19 -0
  35. package/dist/commands/code/jobs/index.js +38 -0
  36. package/dist/commands/code/jobs/list.d.ts +12 -0
  37. package/dist/commands/code/jobs/list.js +87 -0
  38. package/dist/commands/code/jobs/run.d.ts +32 -0
  39. package/dist/commands/code/jobs/run.js +151 -0
  40. package/dist/commands/code/jobs/show.d.ts +12 -0
  41. package/dist/commands/code/jobs/show.js +86 -0
  42. package/dist/commands/commitments/preflight.js +18 -4
  43. package/dist/commands/commitments/receipt-metrics.d.ts +39 -0
  44. package/dist/commands/commitments/receipt-metrics.js +78 -0
  45. package/dist/commands/convos/search.js +5 -5
  46. package/dist/commands/harnesses/leases/list.d.ts +3 -0
  47. package/dist/commands/harnesses/leases/list.js +59 -12
  48. package/dist/commands/harnesses/leases/show.js +36 -0
  49. package/dist/commands/harnesses/list.d.ts +18 -0
  50. package/dist/commands/harnesses/list.js +30 -0
  51. package/dist/commands/harnesses/products.js +49 -3
  52. package/dist/commands/harnesses/quota.d.ts +16 -0
  53. package/dist/commands/harnesses/quota.js +26 -1
  54. package/dist/commands/harnesses/usage.js +17 -0
  55. package/dist/commands/inbox/index.d.ts +2 -1
  56. package/dist/commands/inbox/index.js +27 -4
  57. package/dist/commands/login.js +4 -1
  58. package/dist/commands/machines/dedicated/create.d.ts +18 -0
  59. package/dist/commands/machines/dedicated/create.js +90 -0
  60. package/dist/commands/machines/dedicated/destroy.d.ts +14 -0
  61. package/dist/commands/machines/dedicated/destroy.js +56 -0
  62. package/dist/commands/machines/dedicated/grow.d.ts +13 -0
  63. package/dist/commands/machines/dedicated/grow.js +46 -0
  64. package/dist/commands/machines/dedicated/health-check.d.ts +12 -0
  65. package/dist/commands/machines/dedicated/health-check.js +42 -0
  66. package/dist/commands/machines/dedicated/incident-close.d.ts +12 -0
  67. package/dist/commands/machines/dedicated/incident-close.js +42 -0
  68. package/dist/commands/machines/dedicated/index.d.ts +6 -0
  69. package/dist/commands/machines/dedicated/index.js +39 -0
  70. package/dist/commands/machines/dedicated/list.d.ts +11 -0
  71. package/dist/commands/machines/dedicated/list.js +58 -0
  72. package/dist/commands/machines/dedicated/restart.d.ts +12 -0
  73. package/dist/commands/machines/dedicated/restart.js +42 -0
  74. package/dist/commands/machines/dedicated/show.d.ts +12 -0
  75. package/dist/commands/machines/dedicated/show.js +58 -0
  76. package/dist/commands/machines/dedicated/snapshot.d.ts +12 -0
  77. package/dist/commands/machines/dedicated/snapshot.js +42 -0
  78. package/dist/commands/machines/dedicated/start.d.ts +12 -0
  79. package/dist/commands/machines/dedicated/start.js +42 -0
  80. package/dist/commands/machines/dedicated/stop.d.ts +12 -0
  81. package/dist/commands/machines/dedicated/stop.js +42 -0
  82. package/dist/commands/machines/hosted/index.js +4 -1
  83. package/dist/commands/machines/list.d.ts +1 -0
  84. package/dist/commands/machines/list.js +33 -5
  85. package/dist/commands/memory/approvals/issue.d.ts +18 -0
  86. package/dist/commands/memory/approvals/issue.js +63 -0
  87. package/dist/commands/memory/commit.js +5 -2
  88. package/dist/commands/memory/context.d.ts +3 -2
  89. package/dist/commands/memory/context.js +9 -11
  90. package/dist/commands/memory/delete.d.ts +1 -1
  91. package/dist/commands/memory/list.d.ts +1 -1
  92. package/dist/commands/memory/list.js +1 -1
  93. package/dist/commands/memory/mounts/resolve.d.ts +10 -0
  94. package/dist/commands/memory/mounts/resolve.js +27 -0
  95. package/dist/commands/memory/proposals/commit.d.ts +18 -0
  96. package/dist/commands/memory/proposals/commit.js +32 -0
  97. package/dist/commands/memory/proposals/create.d.ts +18 -0
  98. package/dist/commands/memory/proposals/create.js +56 -0
  99. package/dist/commands/memory/proposals/list.d.ts +13 -0
  100. package/dist/commands/memory/proposals/list.js +31 -0
  101. package/dist/commands/memory/proposals/promote.d.ts +22 -0
  102. package/dist/commands/memory/proposals/promote.js +33 -0
  103. package/dist/commands/memory/proposals/reject.d.ts +16 -0
  104. package/dist/commands/memory/proposals/reject.js +28 -0
  105. package/dist/commands/memory/rebuild-index.d.ts +20 -0
  106. package/dist/commands/memory/rebuild-index.js +25 -0
  107. package/dist/commands/memory/save.d.ts +1 -1
  108. package/dist/commands/memory/save.js +1 -1
  109. package/dist/commands/memory/search.d.ts +6 -3
  110. package/dist/commands/memory/search.js +27 -3
  111. package/dist/commands/memory/show.d.ts +1 -1
  112. package/dist/commands/memory/stores/archive.d.ts +14 -0
  113. package/dist/commands/memory/stores/archive.js +31 -0
  114. package/dist/commands/memory/stores/create.d.ts +12 -0
  115. package/dist/commands/memory/stores/create.js +30 -0
  116. package/dist/commands/memory/stores/delete.d.ts +15 -0
  117. package/dist/commands/memory/stores/delete.js +38 -0
  118. package/dist/commands/memory/stores/list.d.ts +10 -0
  119. package/dist/commands/memory/stores/list.js +29 -0
  120. package/dist/commands/memory/stores/show.d.ts +12 -0
  121. package/dist/commands/memory/stores/show.js +14 -0
  122. package/dist/commands/memory/stores/update.d.ts +15 -0
  123. package/dist/commands/memory/stores/update.js +39 -0
  124. package/dist/commands/memory/sync/delete.d.ts +13 -0
  125. package/dist/commands/memory/sync/delete.js +35 -0
  126. package/dist/commands/memory/sync/pull.d.ts +19 -0
  127. package/dist/commands/memory/sync/pull.js +52 -0
  128. package/dist/commands/memory/sync/push.d.ts +18 -0
  129. package/dist/commands/memory/sync/push.js +115 -0
  130. package/dist/commands/memory/sync/resolve.d.ts +23 -0
  131. package/dist/commands/memory/sync/resolve.js +27 -0
  132. package/dist/commands/memory/sync/status.d.ts +9 -0
  133. package/dist/commands/memory/sync/status.js +22 -0
  134. package/dist/commands/memory/topics/archive.d.ts +20 -0
  135. package/dist/commands/memory/topics/archive.js +29 -0
  136. package/dist/commands/memory/topics/create.d.ts +23 -0
  137. package/dist/commands/memory/topics/create.js +46 -0
  138. package/dist/commands/memory/topics/delete.d.ts +22 -0
  139. package/dist/commands/memory/topics/delete.js +30 -0
  140. package/dist/commands/memory/topics/forget.d.ts +22 -0
  141. package/dist/commands/memory/topics/forget.js +29 -0
  142. package/dist/commands/memory/topics/list.d.ts +13 -0
  143. package/dist/commands/memory/topics/list.js +22 -0
  144. package/dist/commands/memory/topics/move.d.ts +22 -0
  145. package/dist/commands/memory/topics/move.js +26 -0
  146. package/dist/commands/memory/topics/show.d.ts +17 -0
  147. package/dist/commands/memory/topics/show.js +21 -0
  148. package/dist/commands/memory/topics/update.d.ts +23 -0
  149. package/dist/commands/memory/topics/update.js +27 -0
  150. package/dist/commands/memory/tree.d.ts +5 -2
  151. package/dist/commands/memory/tree.js +20 -2
  152. package/dist/commands/memory/update.d.ts +1 -1
  153. package/dist/commands/memory/versions/list.d.ts +16 -0
  154. package/dist/commands/memory/versions/list.js +22 -0
  155. package/dist/commands/memory/versions/redact.d.ts +21 -0
  156. package/dist/commands/memory/versions/redact.js +22 -0
  157. package/dist/commands/memory/versions/restore.d.ts +20 -0
  158. package/dist/commands/memory/versions/restore.js +16 -0
  159. package/dist/commands/memory/versions/show.d.ts +15 -0
  160. package/dist/commands/memory/versions/show.js +17 -0
  161. package/dist/commands/messages/list.js +25 -8
  162. package/dist/commands/messages/show.js +10 -0
  163. package/dist/commands/spaces/update.js +16 -12
  164. package/dist/commands/spaces/work-sync/reconcile.d.ts +38 -0
  165. package/dist/commands/spaces/work-sync/reconcile.js +90 -0
  166. package/dist/commands/spaces/work-sync/telemetry.d.ts +29 -0
  167. package/dist/commands/spaces/work-sync/telemetry.js +96 -0
  168. package/dist/commands/store/install.js +5 -1
  169. package/dist/commands/store/releases.js +1 -1
  170. package/dist/commands/store/update.js +5 -1
  171. package/dist/commands/subscriptions/cancel.js +5 -1
  172. package/dist/commands/subscriptions/health.js +5 -1
  173. package/dist/commands/subscriptions/status.js +5 -1
  174. package/dist/commands/subscriptions/subscribe.js +5 -1
  175. package/dist/commands/tasks/activity.d.ts +2 -3
  176. package/dist/commands/tasks/activity.js +20 -6
  177. package/dist/commands/tasks/complete.d.ts +22 -150
  178. package/dist/commands/tasks/complete.js +174 -599
  179. package/dist/commands/tasks/create.d.ts +6 -0
  180. package/dist/commands/tasks/create.js +114 -4
  181. package/dist/commands/tasks/deliverable/add.d.ts +8 -0
  182. package/dist/commands/tasks/deliverable/add.js +62 -11
  183. package/dist/commands/tasks/deliverable/confirm.d.ts +5 -5
  184. package/dist/commands/tasks/deliverable/confirm.js +9 -18
  185. package/dist/commands/tasks/deliverable/list.d.ts +12 -0
  186. package/dist/commands/tasks/deliverable/list.js +58 -0
  187. package/dist/commands/tasks/events/append.d.ts +2 -0
  188. package/dist/commands/tasks/events/append.js +41 -6
  189. package/dist/commands/tasks/events/list.d.ts +1 -0
  190. package/dist/commands/tasks/events/list.js +1 -0
  191. package/dist/commands/tasks/events/tail.d.ts +1 -0
  192. package/dist/commands/tasks/events/tail.js +1 -0
  193. package/dist/commands/tasks/expectations/assess.d.ts +21 -0
  194. package/dist/commands/tasks/expectations/assess.js +103 -0
  195. package/dist/commands/tasks/expectations.js +121 -25
  196. package/dist/commands/tasks/judge.d.ts +1 -0
  197. package/dist/commands/tasks/judge.js +2 -1
  198. package/dist/commands/tasks/output.d.ts +1 -11
  199. package/dist/commands/tasks/output.js +80 -141
  200. package/dist/commands/tasks/report.d.ts +3 -3
  201. package/dist/commands/tasks/report.js +66 -41
  202. package/dist/commands/tasks/resource/add.d.ts +23 -0
  203. package/dist/commands/tasks/resource/add.js +76 -0
  204. package/dist/commands/tasks/resource/list.d.ts +13 -0
  205. package/dist/commands/tasks/resource/list.js +41 -0
  206. package/dist/commands/tasks/result/show.d.ts +13 -0
  207. package/dist/commands/tasks/result/show.js +64 -0
  208. package/dist/commands/tasks/result/submit.d.ts +26 -0
  209. package/dist/commands/tasks/result/submit.js +134 -0
  210. package/dist/commands/tasks/resume/save.d.ts +16 -0
  211. package/dist/commands/tasks/resume/save.js +69 -0
  212. package/dist/commands/tasks/resume/show.d.ts +13 -0
  213. package/dist/commands/tasks/resume/show.js +35 -0
  214. package/dist/commands/tasks/review.d.ts +17 -0
  215. package/dist/commands/tasks/review.js +54 -0
  216. package/dist/commands/tasks/runs.js +5 -0
  217. package/dist/commands/tasks/self-schedule.js +7 -4
  218. package/dist/commands/tasks/show.js +19 -0
  219. package/dist/commands/tasks/timeline.d.ts +2 -2
  220. package/dist/commands/tasks/timeline.js +19 -24
  221. package/dist/commands/tasks/updates/add.d.ts +22 -0
  222. package/dist/commands/tasks/updates/add.js +122 -0
  223. package/dist/commands/tasks/updates/list.d.ts +15 -0
  224. package/dist/commands/tasks/updates/list.js +57 -0
  225. package/dist/commands/tasks/waive.d.ts +1 -0
  226. package/dist/commands/tasks/waive.js +2 -1
  227. package/dist/lib/agentic-stream.d.ts +257 -0
  228. package/dist/lib/agentic-stream.js +490 -33
  229. package/dist/lib/balance.d.ts +8 -0
  230. package/dist/lib/commitment-product.d.ts +10 -1
  231. package/dist/lib/commitment-product.js +66 -2
  232. package/dist/lib/commitments.d.ts +2 -1
  233. package/dist/lib/commitments.js +1 -0
  234. package/dist/lib/conversation-search.d.ts +3 -0
  235. package/dist/lib/conversation-search.js +75 -0
  236. package/dist/lib/dedicated-machines.d.ts +66 -0
  237. package/dist/lib/dedicated-machines.js +161 -0
  238. package/dist/lib/execution-target.d.ts +21 -0
  239. package/dist/lib/execution-target.js +22 -6
  240. package/dist/lib/harnesses.d.ts +87 -6
  241. package/dist/lib/harnesses.js +32 -1
  242. package/dist/lib/hosted-machines.js +9 -0
  243. package/dist/lib/inbox.d.ts +2 -1
  244. package/dist/lib/machines.d.ts +17 -0
  245. package/dist/lib/machines.js +29 -0
  246. package/dist/lib/managed-agent-keys.d.ts +104 -0
  247. package/dist/lib/managed-agent-keys.js +50 -0
  248. package/dist/lib/memory-scope.d.ts +4 -3
  249. package/dist/lib/memory-scope.js +9 -9
  250. package/dist/lib/memory-sync.d.ts +114 -0
  251. package/dist/lib/memory-sync.js +730 -0
  252. package/dist/lib/message-provenance.d.ts +42 -0
  253. package/dist/lib/message-provenance.js +89 -0
  254. package/dist/lib/scoped-memory-command.d.ts +68 -0
  255. package/dist/lib/scoped-memory-command.js +183 -0
  256. package/dist/lib/session-task-endpoints.d.ts +1 -1
  257. package/dist/lib/session-task-endpoints.js +7 -0
  258. package/dist/lib/sky-code-broker.d.ts +47 -0
  259. package/dist/lib/sky-code-broker.js +50 -0
  260. package/dist/lib/sky-code-doctor.js +10 -1
  261. package/dist/lib/task-artifact-flags.d.ts +2 -0
  262. package/dist/lib/task-artifact-flags.js +7 -0
  263. package/dist/lib/task-execution.d.ts +4 -5
  264. package/dist/lib/task-execution.js +4 -5
  265. package/dist/lib/task-extras.d.ts +1 -5
  266. package/dist/lib/task-extras.js +1 -5
  267. package/dist/lib/task-legacy.d.ts +24 -0
  268. package/dist/lib/task-legacy.js +48 -0
  269. package/dist/lib/task-transcript.d.ts +5 -7
  270. package/dist/lib/task-transcript.js +8 -16
  271. package/dist/lib/tasks.d.ts +39 -21
  272. package/dist/lib/tasks.js +64 -21
  273. package/dist/lib/triggers.d.ts +39 -1
  274. package/dist/lib/triggers.js +70 -2
  275. package/dist/lib/usage-discovery.d.ts +17 -1
  276. package/dist/lib/usage-discovery.js +17 -1
  277. package/dist/lib/work-sync.d.ts +70 -0
  278. package/dist/lib/work-sync.js +29 -0
  279. package/dist/node_modules/@skrr-ai/data-provider/index.js +3760 -3621
  280. package/oclif.manifest.json +14653 -7813
  281. package/package.json +26 -5
@@ -1,294 +1,110 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TaskCompletionMismatchError = void 0;
4
- exports.resolveCanonicalCompletionTaskId = resolveCanonicalCompletionTaskId;
5
- exports.resolveCompletionTarget = resolveCompletionTarget;
6
- exports.decideRecompletion = decideRecompletion;
7
- exports.isCompleteEndpointMissing = isCompleteEndpointMissing;
8
- exports.completeTaskDurably = completeTaskDurably;
3
+ exports.describePendingReview = describePendingReview;
9
4
  const core_1 = require("@oclif/core");
10
- const data_provider_1 = require("@skrr-ai/data-provider");
11
5
  const base_command_1 = require("../../base-command");
12
- const task_extras_1 = require("../../lib/task-extras");
13
6
  const api_fetch_1 = require("../../lib/api-fetch");
14
7
  const outbox_1 = require("../../lib/outbox");
15
- const credential_resolver_1 = require("../../lib/credential-resolver");
16
- const delegated_cli_1 = require("../../lib/delegated-cli");
17
8
  const prompt_1 = require("../../lib/prompt");
18
- const task_report_1 = require("../../lib/task-report");
9
+ const session_task_endpoints_1 = require("../../lib/session-task-endpoints");
19
10
  const task_resolver_1 = require("../../lib/task-resolver");
20
- const task_instruction_offer_1 = require("../../lib/task-instruction-offer");
21
- const task_transcript_1 = require("../../lib/task-transcript");
11
+ const task_extras_1 = require("../../lib/task-extras");
22
12
  const web_url_1 = require("../../lib/web-url");
23
- const tasks_1 = require("../../lib/tasks");
24
- const git_task_1 = require("../../lib/git-task");
25
- /**
26
- * Forward any `--deliverable` value that is really a ledger coordinate.
27
- *
28
- * FAIL-OPEN, and quietly. This is a convenience bridge on the completion path:
29
- * the completion itself is the operation the caller asked for, and a ledger
30
- * write that fails must not take it down. A caller who needed the row and did
31
- * not get it meets the gate's refusal, which names the command that writes it.
32
- *
33
- * `local`, never anything stronger — this is the executor talking about its own
34
- * work, which is exactly what the authority ladder is built to weigh.
35
- */
36
- async function recordCoordinateDeliverables({ taskId, values, warn, }) {
37
- const seen = new Set();
38
- for (const value of values) {
39
- const raw = (value ?? '').trim();
40
- if (!raw || raw === 'none' || seen.has(raw)) {
41
- continue;
42
- }
43
- seen.add(raw);
44
- const parsed = (0, tasks_1.parseDeliverableCoordinate)(raw);
45
- if (!parsed.ok) {
46
- continue;
47
- }
48
- try {
49
- await data_provider_1.dataService.addTaskDeliverable(taskId, parsed.value);
50
- }
51
- catch {
52
- warn(`Recorded "${raw}" in the report but not in the deliverables ledger. ` +
53
- `If the task refuses to close, run \`tasks deliverable add\` for it.`);
54
- }
13
+ function parseJson(value, label) {
14
+ if (!value)
15
+ return undefined;
16
+ try {
17
+ return JSON.parse(value);
18
+ }
19
+ catch {
20
+ throw new Error(`${label} must be valid JSON`);
55
21
  }
56
22
  }
57
- /**
58
- * Mutation endpoints require the task's canonical UUID even though the CLI
59
- * accepts human-readable refs and titles. Resolve the display reference before
60
- * writing anything so a 404 from `POST /complete` cannot be mistaken for an
61
- * old server and fall through to the legacy comment-first path.
62
- */
63
- async function resolveCanonicalCompletionTaskId(taskRef, lookup) {
64
- return (await resolveCompletionTarget(taskRef, lookup)).id;
65
- }
66
- /** Terminal workflow states — a task in one of these has already been closed out. */
67
- const TERMINAL_STATUSES = new Set(['done', 'failed']);
68
- /**
69
- * The canonical id AND the status it is in.
70
- *
71
- * The status comes free: this lookup already happens on the user path, so the
72
- * re-completion guard below costs no extra round trip on the common case. It is
73
- * also the only way to make the success line honest — `tasks complete` printed
74
- * "Moved task X to done" unconditionally, so a task that was ALREADY done got a
75
- * report of a state transition that did not happen.
76
- */
77
- async function resolveCompletionTarget(taskRef, lookup) {
78
- const task = (await lookup(taskRef));
79
- const id = typeof task?.id === 'string' ? task.id.trim() : '';
80
- if (!id) {
81
- throw new Error(`Task ${taskRef} did not resolve to a canonical id.`);
23
+ function parseReference(raw) {
24
+ const split = raw.indexOf(':');
25
+ if (split < 1 || split === raw.length - 1) {
26
+ throw new Error('Evidence must use <kind>:<id-or-uri>.');
82
27
  }
83
- return { id, status: typeof task?.status === 'string' ? task.status : null };
28
+ const kind = raw.slice(0, split);
29
+ const value = raw.slice(split + 1);
30
+ return /^[a-z][a-z0-9+.-]*:/i.test(value) || value.startsWith('/')
31
+ ? { kind, uri: value }
32
+ : { kind, id: value };
84
33
  }
85
- /**
86
- * What a second `tasks complete` on an already-closed task should do.
87
- *
88
- * The repo contract calls this "the only terminal closeout", and a task has one
89
- * canonical outcome — but a second run used to succeed silently, post another
90
- * TASK_OUTPUT block, and leave `tasks output` rendering the throwaway one as
91
- * authoritative. The real summary and its deliverables link were still on the task
92
- * but no longer canonical, which is the worst of the available failures: no
93
- * error, no warning, exit 0, and a record that now says something different.
94
- *
95
- * Refusing outright would break the case that makes retries safe. A completion
96
- * whose response is lost — a flaky connection, a re-run script — SHOULD be able
97
- * to run again, and today it collapses server-side on the shared
98
- * `X-Idempotency-Key`. Turning that into a hard failure would force every
99
- * caller to write its own is-it-already-done check.
100
- *
101
- * So the axis is not "again or not", it is "the SAME again, or something
102
- * different":
103
- *
104
- * - identical report → `noop`, exit 0. Nothing to do, and saying so is more
105
- * honest than re-posting an identical block.
106
- * - differing report → `refuse`. Superseding a canonical outcome is a real
107
- * decision, so it takes `--amend` and says so.
108
- * - not yet terminal → `proceed`, unchanged.
109
- *
110
- * Pure, so the rule is testable without a server.
111
- */
112
- function decideRecompletion(opts) {
113
- const { priorStatus, canonicalBody, incomingBody, amend } = opts;
114
- if (!priorStatus || !TERMINAL_STATUSES.has(priorStatus))
115
- return 'proceed';
116
- if (amend)
117
- return 'proceed';
118
- // No canonical block to disagree with — a task closed by a path that skipped
119
- // it, so writing one is an addition rather than a replacement.
120
- if (canonicalBody === null)
121
- return 'proceed';
122
- return canonicalBody.trim() === incomingBody.trim() ? 'noop' : 'refuse';
34
+ function stringValue(value, field) {
35
+ const text = typeof value === 'string' ? value.trim() : '';
36
+ if (!text)
37
+ throw new Error(`${field} is required.`);
38
+ return text;
123
39
  }
124
- /** A 404 from the /complete endpoint means an old server without the route. */
125
- function isCompleteEndpointMissing(err) {
126
- return err instanceof api_fetch_1.ApiFetchError && err.status === 404;
40
+ function optionalString(value) {
41
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
127
42
  }
128
- /**
129
- * The server finalized a different task than the one we were asked to finalize.
130
- *
131
- * The session-scoped completion endpoint derives its task from the run claim
132
- * and deliberately ignores any client-supplied id — that is what stops a
133
- * compromised agent process finishing a sibling task. The corollary is that the
134
- * id resolved on this side is not necessarily the one that moved, so reporting
135
- * ours would print a success line (and a `--json` `taskId`) for a task that is
136
- * still open.
137
- */
138
- class TaskCompletionMismatchError extends Error {
139
- requestedTaskId;
140
- completedTaskId;
141
- constructor(requestedTaskId, completedTaskId) {
142
- super(`Asked to complete ${requestedTaskId}, but the server finalized ${completedTaskId}. ` +
143
- 'In an autonomous session the server derives the task from the run claim and ignores ' +
144
- `the id passed here, so ${requestedTaskId} has NOT been completed. Re-run without a ` +
145
- "task id to finalize this session's own task.");
146
- this.name = 'TaskCompletionMismatchError';
147
- this.requestedTaskId = requestedTaskId;
148
- this.completedTaskId = completedTaskId;
149
- }
43
+ function stringArray(value) {
44
+ return Array.isArray(value) ? value.map((item) => String(item).trim()).filter(Boolean) : [];
150
45
  }
151
- exports.TaskCompletionMismatchError = TaskCompletionMismatchError;
152
- /**
153
- * Finalize a task durably (OSK-1969).
154
- *
155
- * Prefers the server's atomic POST /:taskId/complete (CAS move first, then
156
- * the TASK_OUTPUT comment no half-finished state if this process dies).
157
- * Falls back to the legacy client-side two-step (comment, then move) when the
158
- * server predates the endpoint (HTTP 404). `noMove` keeps the historical
159
- * comment-only behavior and never touches the endpoint. All transport errors
160
- * other than the endpoint-missing 404 are rethrown for the caller's uniform
161
- * error handling.
162
- */
163
- async function completeTaskDurably(opts) {
164
- const { taskId, status, body, summary, deliverables, verdict, report, deliveryReceipt, noMove, transport, } = opts;
165
- if (noMove) {
166
- const comment = await transport.addComment(taskId, body);
167
- return {
168
- comment,
169
- movedTask: null,
170
- moved: false,
171
- commentFailed: false,
172
- usedAtomicEndpoint: false,
173
- };
174
- }
175
- try {
176
- const response = await transport.postComplete(taskId, {
177
- status,
178
- summary,
179
- deliverables,
180
- verdict,
181
- ...(report ? { report } : {}),
182
- ...(deliveryReceipt ? { deliveryReceipt } : {}),
183
- });
184
- // Reconcile before reporting. On the session-scoped path the server owns
185
- // the task selection, so a divergence here means the task we were asked to
186
- // finish is still open — a silent success would hide that from the agent
187
- // and from anything parsing `--json`.
188
- const finalizedTaskId = response.task?.id;
189
- if (finalizedTaskId && finalizedTaskId !== taskId) {
190
- throw new TaskCompletionMismatchError(taskId, finalizedTaskId);
191
- }
192
- return {
193
- comment: response.comment ?? null,
194
- movedTask: response.task ?? null,
195
- moved: true,
196
- commentFailed: response.commentFailed === true,
197
- ...(response.advisories?.length ? { advisories: response.advisories } : {}),
198
- usedAtomicEndpoint: true,
199
- };
200
- }
201
- catch (err) {
202
- if (err instanceof TaskCompletionMismatchError)
203
- throw err;
204
- if (!isCompleteEndpointMissing(err) || transport.allowLegacyFallback === false) {
205
- throw err;
206
- }
46
+ function describePendingReview({ statusType, taskId, resultId, bin, advisories, }) {
47
+ if (statusType !== 'review')
48
+ return null;
49
+ // Say which thing is actually holding the Task. This used to assert a missing
50
+ // reviewer unconditionally, so a Task held by unmet Expectations was reported
51
+ // as needing reviewand the command it handed over failed every time, both
52
+ // because the block was elsewhere and because a submitter may not accept their
53
+ // own Result (`TASK_REVIEW_SELF_ACCEPTANCE`).
54
+ const expectationAdvisory = (advisories || []).find((row) => row.code === 'task_acceptance_not_met' || /expectation/i.test(row.code || ''));
55
+ if (expectationAdvisory) {
56
+ return ('The Result was submitted, but the Task is not done yet: its Expectations are not all ' +
57
+ `settled. ${expectationAdvisory.message} ` +
58
+ `Settle each with: ${bin} tasks expectations assess ${taskId} <expectation-id> ` +
59
+ '--decision met|waived --reason "..."');
207
60
  }
208
- // Old server legacy non-atomic two-step, byte-identical to the historical
209
- // behavior (comment first, then move).
210
- const comment = await transport.addComment(taskId, body);
211
- const movedTask = await transport.moveTask(taskId, status === 'completed' ? 'done' : 'failed');
212
- return {
213
- comment,
214
- movedTask,
215
- moved: true,
216
- commentFailed: false,
217
- usedAtomicEndpoint: false,
218
- };
61
+ return ('The Result was submitted, but the Task is not done yet. It is waiting on an independent ' +
62
+ `accepted Review someone OTHER than the submitter runs: ${bin} tasks review ${taskId} ` +
63
+ `--result-id ${resultId || '<result-id>'} --decision accepted`);
219
64
  }
220
65
  class TasksComplete extends base_command_1.BaseCommand {
221
- static description = 'Post a canonical TASK_OUTPUT comment and finish a task';
66
+ static description = 'Submit one versioned Result and advance the Task workflow';
222
67
  static examples = [
223
- '<%= config.bin %> tasks complete <task-id> --summary "Shipped the fix." --verdict approved',
224
- '<%= config.bin %> tasks complete --summary "Shipped the fix." --verdict approved',
225
- '<%= config.bin %> tasks complete --task <task-id> --summary "Shipped the fix." --json',
226
- '<%= config.bin %> tasks complete <task-id> --status failed --summary "Could not deploy." --verdict blocked',
227
- '<%= config.bin %> tasks complete <task-id> --from-json task-output.json --json',
228
- '<%= config.bin %> tasks complete <task-id> --summary "Comment only" --no-move',
68
+ '<%= config.bin %> tasks complete <task> --summary "Implemented and verified the fix" --deliverable <resource-link-id>',
69
+ '<%= config.bin %> tasks complete <task> --summary "Do not ship" --conclusion-type release_recommendation --conclusion-json \'{"posture":"no_ship"}\'',
70
+ '<%= config.bin %> tasks complete <task> --status blocked --summary "Waiting for production credentials" --remaining "A workspace owner must grant access"',
229
71
  ];
230
72
  static args = {
231
73
  id: core_1.Args.string({
232
- description: 'Task ID or identifier; defaults to OVERSKY_SESSION_TASK_ID in task runtimes',
74
+ description: 'Task id or ref; defaults to the bound runtime Task',
233
75
  required: false,
234
76
  ignoreStdin: true,
235
77
  }),
236
78
  };
237
79
  static flags = {
238
- json: core_1.Flags.boolean({ description: 'Output as JSON' }),
239
- task: core_1.Flags.string({
240
- description: 'Task ID or identifier; overrides daemon runtime context',
241
- }),
242
- 'from-json': core_1.Flags.string({
243
- description: 'Path to JSON with taskId, status, summary, deliverables, and verdict; "-" for stdin',
244
- }),
245
- amend: core_1.Flags.boolean({
246
- description: 'Supersede the canonical outcome of an already-completed task. Without it, a differing report on a closed task is refused',
247
- }),
248
- status: core_1.Flags.string({
249
- description: 'TASK_OUTPUT status',
250
- options: ['completed', 'failed'],
251
- default: 'completed',
80
+ task: core_1.Flags.string({ description: 'Task id; overrides runtime context' }),
81
+ status: core_1.Flags.string({ options: ['completed', 'failed', 'blocked'] }),
82
+ summary: core_1.Flags.string({ description: 'The Result in one readable paragraph' }),
83
+ body: core_1.Flags.string({ description: 'Long-form Result body' }),
84
+ 'conclusion-type': core_1.Flags.string({
85
+ description: 'Domain conclusion type, such as release_recommendation',
252
86
  }),
253
- summary: core_1.Flags.string({ description: 'Completion summary' }),
254
- deliverables: core_1.Flags.string({
255
- description: 'Deliverables produced, or "none". Repeatable, and interchangeable with --deliverable. ' +
256
- `The whole TASK_OUTPUT block (status + summary + deliverables + verdict) must fit in ${data_provider_1.TASK_OUTPUT_BODY_MAX} ` +
257
- 'characters — link to commits, PRs, or file paths rather than pasting content.',
258
- // Repeatable, even though `--deliverable` already is. The two spellings sit
259
- // one letter apart and both read as "the deliverables go here", so the wrong
260
- // one gets typed — and oclif answered that with "Flag --deliverables can only
261
- // be specified once", which names the flag you used and not the sibling
262
- // that does what you wanted. Dogfooded (OSK-266) by losing a completion
263
- // call to exactly that, then re-running with the two references pre-joined
264
- // into one string, which is the workaround rather than the shape.
265
- multiple: true,
266
- default: ['none'],
87
+ 'conclusion-json': core_1.Flags.string({ description: 'JSON value for the domain conclusion' }),
88
+ 'structured-schema': core_1.Flags.string({ description: 'Schema name for --structured-json' }),
89
+ 'structured-json': core_1.Flags.string({
90
+ description: 'Task-type-specific structured Result JSON object',
267
91
  }),
268
92
  deliverable: core_1.Flags.string({
269
- description: 'A single artifact reference (repeatable); joined with commas. Prefer this ' +
270
- 'over pre-joining into --deliverables.',
271
- multiple: true,
272
- }),
273
- verdict: core_1.Flags.string({ description: 'Final verdict', default: 'approved' }),
274
- outcome: core_1.Flags.string({
275
- description: 'Reader-facing conclusion for the durable task report',
276
- }),
277
- finding: core_1.Flags.string({
278
- description: 'Concrete finding for the durable task report (repeatable)',
279
- multiple: true,
280
- }),
281
- evidence: core_1.Flags.string({
282
- description: 'Supporting rationale or evidence for the durable task report (repeatable)',
283
93
  multiple: true,
94
+ description: 'Exact Task Resource link id produced by `tasks deliverable add`',
284
95
  }),
285
- next: core_1.Flags.string({
286
- description: 'Follow-up or next action for the durable task report (repeatable)',
287
- multiple: true,
96
+ evidence: core_1.Flags.string({ multiple: true, description: 'Evidence as <kind>:<id-or-uri>' }),
97
+ remaining: core_1.Flags.string({ multiple: true, description: 'Remaining work item' }),
98
+ 'source-run': core_1.Flags.string({ multiple: true, description: 'Contributing Task Run id' }),
99
+ 'run-id': core_1.Flags.string({ description: 'Expected current Task Run id' }),
100
+ 'spec-version': core_1.Flags.integer({
101
+ description: 'Task specification version this Result answers',
288
102
  }),
289
- 'no-move': core_1.Flags.boolean({
290
- description: 'Only post the TASK_OUTPUT comment; do not move task status',
103
+ 'idempotency-key': core_1.Flags.string({ description: 'Stable retry key for this exact closeout' }),
104
+ 'from-json': core_1.Flags.string({
105
+ description: 'JSON file containing the same canonical Result fields; "-" for stdin',
291
106
  }),
107
+ json: core_1.Flags.boolean({ description: 'Output JSON' }),
292
108
  };
293
109
  async run() {
294
110
  this.requireAuth();
@@ -298,51 +114,26 @@ class TasksComplete extends base_command_1.BaseCommand {
298
114
  try {
299
115
  jsonBody = await (0, prompt_1.readJsonBody)(flags['from-json']);
300
116
  }
301
- catch (err) {
117
+ catch (error) {
302
118
  this.failWithCliError({
303
- message: err.message,
119
+ message: error.message,
304
120
  code: 'INVALID_JSON_INPUT',
305
121
  exit: 1,
306
122
  retryable: false,
307
123
  });
308
124
  }
309
125
  }
310
- const input = (0, prompt_1.mergeDefined)(jsonBody, {
311
- status: flags.status,
312
- summary: flags.summary,
313
- // Both spellings, pooled, then normalized to the one-line form the server
314
- // stores. Pooling rather than letting one win: someone who passes both has
315
- // named deliverables in each, and silently dropping half of them is worse
316
- // than either flag being unavailable.
317
- deliverables: (0, data_provider_1.normalizeTaskOutputDeliverables)([
318
- ...(flags.deliverable ?? []),
319
- // The literal default is dropped when it is only the default; a user
320
- // who typed `--deliverables none` alongside a real one meant the real one.
321
- ...(flags.deliverables ?? []).filter((value) => value !== 'none'),
322
- ]) || 'none',
323
- verdict: flags.verdict,
324
- });
325
- // `--verdict` is deliberately not enumerated — a reviewer task passes a
326
- // JSON posture object, and `normalizeReviewPosture` accepts synonyms. But
327
- // when it recognises nothing it derives the posture from the criteria
328
- // rather than failing, so a typo is recorded and silently means something
329
- // else. Warn over the set the server actually matches; never refuse.
330
- if (!(0, tasks_1.verdictIsRecognized)(input.verdict)) {
331
- this.warn(`Verdict "${input.verdict}" is not one the server recognises ` +
332
- `(${tasks_1.RECOGNIZED_TASK_VERDICTS.join(', ')}, or a JSON review posture). ` +
333
- 'It will be recorded as written, and a review will fall back to what the criteria imply.');
334
- }
335
- let taskIdentity;
126
+ let identity;
336
127
  try {
337
- taskIdentity = (0, task_resolver_1.resolveTaskIdentity)({
128
+ identity = (0, task_resolver_1.resolveTaskIdentity)({
338
129
  positionalId: args.id,
339
130
  flagTask: flags.task,
340
131
  jsonBody,
341
132
  jsonKeys: ['taskId', 'task', 'id'],
342
133
  });
343
134
  }
344
- catch (err) {
345
- const failure = (0, task_resolver_1.taskResolutionFailure)(err);
135
+ catch (error) {
136
+ const failure = (0, task_resolver_1.taskResolutionFailure)(error);
346
137
  this.failWithCliError({
347
138
  message: failure.message,
348
139
  code: failure.code,
@@ -351,348 +142,132 @@ class TasksComplete extends base_command_1.BaseCommand {
351
142
  details: { suggestion: failure.suggestion, context: failure.details },
352
143
  });
353
144
  }
354
- let status;
355
- let summary;
356
- let deliverables;
357
- let verdict;
358
- let report;
145
+ let conclusionValue;
146
+ let structuredValue;
359
147
  try {
360
- status = normalizeStatus(input.status);
361
- summary = requireOutputField(input.summary, 'summary');
362
- deliverables =
363
- optionalOutputField((0, data_provider_1.normalizeTaskOutputDeliverables)(input.deliverables)) || 'none';
364
- verdict =
365
- optionalOutputField(input.verdict) || (status === 'completed' ? 'approved' : 'failed');
366
- const jsonReport = input.report && typeof input.report === 'object'
367
- ? input.report
368
- : undefined;
369
- const reportInput = {
370
- outcome: flags.outcome ?? jsonReport?.outcome,
371
- findings: flags.finding?.length ? flags.finding : jsonReport?.findings,
372
- evidence: flags.evidence?.length ? flags.evidence : jsonReport?.evidence,
373
- nextActions: flags.next?.length
374
- ? flags.next
375
- : (jsonReport?.nextActions ?? jsonReport?.next),
376
- };
377
- if ((0, task_report_1.hasTaskReportInput)(reportInput)) {
378
- // Summary remains the compact completion acknowledgement. A report's
379
- // outcome is the readable conclusion in the task detail, so default it
380
- // to summary when a caller supplied findings but no duplicate outcome.
381
- report = (0, task_report_1.buildTaskReport)({ ...reportInput, outcome: reportInput.outcome ?? summary });
382
- }
148
+ conclusionValue = parseJson(flags['conclusion-json'], '--conclusion-json');
149
+ structuredValue = parseJson(flags['structured-json'], '--structured-json');
383
150
  }
384
- catch (err) {
151
+ catch (error) {
385
152
  this.failWithCliError({
386
- message: err.message,
387
- code: 'INVALID_TASK_OUTPUT',
153
+ message: error.message,
154
+ code: 'INVALID_RESULT_JSON',
388
155
  exit: 1,
389
156
  retryable: false,
390
157
  });
391
158
  }
392
- if (report && flags['no-move']) {
393
- this.failWithCliError({
394
- message: '--no-move cannot persist a completion report atomically. Run `skrr tasks report` first, or complete the task without --no-move.',
395
- code: 'TASK_REPORT_REQUIRES_COMPLETION',
159
+ if (Boolean(flags['conclusion-type']) !== Boolean(flags['conclusion-json'])) {
160
+ this.error('--conclusion-type and --conclusion-json must be supplied together', { exit: 1 });
161
+ }
162
+ if (Boolean(flags['structured-schema']) !== Boolean(flags['structured-json'])) {
163
+ this.error('--structured-schema and --structured-json must be supplied together', {
396
164
  exit: 1,
397
- retryable: false,
398
165
  });
399
166
  }
400
- // OSK-2941 — refuse locally, before the work of a round trip, using the SAME
401
- // budget the server enforces. Discovering an undocumented cap by HTTP 400
402
- // after a run has already finished is the failure being fixed; a caller must
403
- // be able to see the limit without provoking it.
404
- const budget = (0, data_provider_1.measureTaskOutputBudget)({ status, summary, deliverables, verdict });
405
- if (!budget.fits) {
167
+ if (structuredValue &&
168
+ (typeof structuredValue !== 'object' || Array.isArray(structuredValue))) {
169
+ this.error('--structured-json must contain a JSON object', { exit: 1 });
170
+ }
171
+ const input = (0, prompt_1.mergeDefined)(jsonBody, {
172
+ status: flags.status,
173
+ summary: flags.summary,
174
+ body: flags.body,
175
+ conclusion: flags['conclusion-type']
176
+ ? { type: flags['conclusion-type'], value: conclusionValue }
177
+ : undefined,
178
+ structuredContent: flags['structured-schema']
179
+ ? {
180
+ schema: flags['structured-schema'],
181
+ value: structuredValue,
182
+ }
183
+ : undefined,
184
+ resourceLinkIds: flags.deliverable,
185
+ evidenceRefs: flags.evidence?.map(parseReference),
186
+ remainingWork: flags.remaining?.map((summary) => ({ summary })),
187
+ sourceRunIds: flags['source-run'],
188
+ expectedRunId: flags['run-id'],
189
+ expectedSpecVersion: flags['spec-version'],
190
+ idempotencyKey: flags['idempotency-key'],
191
+ });
192
+ let status;
193
+ let summary;
194
+ try {
195
+ const candidate = optionalString(input.status) || 'completed';
196
+ if (!['completed', 'failed', 'blocked'].includes(candidate)) {
197
+ throw new Error('status must be completed, failed, or blocked.');
198
+ }
199
+ status = candidate;
200
+ summary = stringValue(input.summary, 'summary');
201
+ }
202
+ catch (error) {
406
203
  this.failWithCliError({
407
- message: (0, data_provider_1.describeTaskOutputOverflow)(budget),
408
- code: 'TASK_OUTPUT_TOO_LONG',
204
+ message: error.message,
205
+ code: 'INVALID_TASK_RESULT',
409
206
  exit: 1,
410
207
  retryable: false,
411
- details: {
412
- limit: budget.limit,
413
- length: budget.length,
414
- overBy: budget.overBy,
415
- fieldLengths: budget.fieldLengths,
416
- },
417
208
  });
418
209
  }
419
- const body = formatTaskOutput({ status, summary, deliverables, verdict });
420
- // Artifact text such as a commit SHA is a delivery claim. The local probe
421
- // both verifies remote-default ancestry and anchors a commit that has no
422
- // local branch before the completion request can release this run.
423
- const deliveryReceipt = status === 'completed' && !flags['no-move']
424
- ? ((0, git_task_1.inspectTaskDeliveryArtifacts)({ deliverables, taskRef: taskIdentity.id }) ?? undefined)
425
- : undefined;
426
- // Computed once, OUTSIDE the try, so the original attempt and the queued
427
- // replay carry the same key. Must match the payload the outbox stores.
428
- const completionPayload = {
210
+ const payload = {
211
+ status,
429
212
  summary,
430
- deliverables,
431
- verdict,
432
- ...(report ? { report } : {}),
433
- ...(deliveryReceipt ? { deliveryReceipt } : {}),
434
- noMove: !!flags['no-move'],
213
+ body: optionalString(input.body),
214
+ conclusion: input.conclusion,
215
+ structuredContent: input.structuredContent,
216
+ resourceLinkIds: Array.isArray(input.resourceLinkIds)
217
+ ? stringArray(input.resourceLinkIds)
218
+ : [],
219
+ evidenceRefs: Array.isArray(input.evidenceRefs) ? input.evidenceRefs : [],
220
+ remainingWork: Array.isArray(input.remainingWork) ? input.remainingWork : [],
221
+ contributors: Array.isArray(input.contributors)
222
+ ? input.contributors
223
+ : [],
224
+ sourceRunIds: stringArray(input.sourceRunIds),
225
+ expectedRunId: optionalString(input.expectedRunId),
226
+ expectedSpecVersion: typeof input.expectedSpecVersion === 'number' ? input.expectedSpecVersion : undefined,
435
227
  };
436
- const completionIdempotencyKey = (0, outbox_1.idempotencyKeyFor)('task-complete', taskIdentity.id, JSON.stringify(completionPayload));
437
- const sessionCompletionPath = autonomousSessionCompletionPath();
438
- // Resolved OUTSIDE the write's try/catch. It is a GET, so a failure here is
439
- // not a queueable write — and the re-completion guard has to be able to
440
- // refuse without `queueWriteOrFail` treating the refusal as an outage worth
441
- // replaying later.
442
- //
443
- // The session endpoint derives its task from the trusted run claim and does
444
- // not need (or permit) a broad task lookup. The normal user and
445
- // delegated-daemon path does: task routes read OSK refs, while mutation
446
- // services key on UUIDs. Passing the ref straight through caused the atomic
447
- // endpoint's task-level 404 to look like "endpoint missing"; legacy fallback
448
- // then posted TASK_OUTPUT before its move 404'd.
449
- let completionTaskId = taskIdentity.id;
450
- // Captured at resolution so the guard and the success line can both tell
451
- // "already closed" from "just closed".
452
- let priorStatus = null;
453
- if (!sessionCompletionPath) {
454
- try {
455
- const target = await resolveCompletionTarget(await this.resolveTaskReference(taskIdentity.id), (taskRef) => data_provider_1.dataService.getTask(taskRef, { enrich: false }));
456
- completionTaskId = target.id;
457
- priorStatus = target.status;
458
- }
459
- catch (err) {
460
- this.handleApiError(err);
461
- }
462
- }
463
- // A task that is already closed gets one extra read, and only then — the
464
- // first completion of a task never pays for this.
465
- if (decideRecompletion({
466
- priorStatus,
467
- canonicalBody: '',
468
- incomingBody: body,
469
- amend: flags.amend,
470
- }) !== 'proceed') {
471
- let canonicalBody = null;
228
+ const idempotencyKey = optionalString(input.idempotencyKey) ||
229
+ (0, outbox_1.idempotencyKeyFor)('task-complete', identity.id, JSON.stringify(payload));
230
+ const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('completion');
231
+ let taskId = identity.id;
232
+ if (!sessionPath) {
472
233
  try {
473
- canonicalBody = (await (0, task_transcript_1.fetchLatestTaskOutputComment)(completionTaskId))?.body ?? null;
234
+ const resolved = await this.resolveTaskReference(identity.id);
235
+ taskId = String(resolved);
474
236
  }
475
- catch {
476
- // Unreadable history must not block a completion: falling through to
477
- // `proceed` is exactly today's behaviour, which is the safe direction
478
- // for a guard whose whole job is to protect a record it cannot see.
479
- canonicalBody = null;
480
- }
481
- const decision = decideRecompletion({
482
- priorStatus,
483
- canonicalBody,
484
- incomingBody: body,
485
- amend: flags.amend,
486
- });
487
- if (decision === 'noop') {
488
- const url = (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, { id: completionTaskId });
489
- if (flags.json) {
490
- this.log(JSON.stringify({
491
- taskId: completionTaskId,
492
- status: priorStatus,
493
- alreadyCompleted: true,
494
- changed: false,
495
- url: url ? `${url}#task-outcome` : url,
496
- }, null, 2));
497
- return;
498
- }
499
- this.log(`Already ${priorStatus}; this report is identical to the canonical one — nothing to do.`);
500
- if (url)
501
- this.log(`Outcome: ${url}#task-outcome`);
502
- return;
503
- }
504
- if (decision === 'refuse') {
505
- this.failWithCliError({
506
- message: `Task ${completionTaskId} is already ${priorStatus}, and this report differs from its canonical ` +
507
- `outcome — completing again would silently replace it (deliverables included).\n` +
508
- ` → re-run with \`--amend\` to supersede it deliberately, or read what is there now with ` +
509
- `\`${this.config.bin} tasks output ${completionTaskId}\`.`,
510
- code: 'TASK_ALREADY_COMPLETED',
511
- exit: 1,
512
- retryable: false,
513
- details: { taskId: completionTaskId, status: priorStatus },
514
- });
237
+ catch (error) {
238
+ this.handleApiError(error);
515
239
  }
516
240
  }
517
- // THE THIRD SPELLING, BRIDGED.
518
- //
519
- // `--deliverable` here is PROSE for the TASK_OUTPUT block. The ledger the
520
- // completion gate reads is written by `tasks deliverable add`, and nothing
521
- // connected the two — so an agent that ran
522
- // `tasks complete --deliverable code:abc@origin/main` had, from its own
523
- // point of view, recorded its deliverable, and was then refused by a gate
524
- // whose message told it to record its deliverable.
525
- //
526
- // Three surfaces in one CLI answer to the word (`tasks deliverable add`,
527
- // this flag, and `tasks report --deliverable`), and the project's own
528
- // operating instructions teach the two that do NOT write the ledger. That
529
- // is a documentation problem only if you believe people read the docs
530
- // before the error; the cheaper fix is to make the wrong spelling do the
531
- // right thing when what was typed is unambiguously a coordinate.
532
- //
533
- // Deliberately narrow: only values that PARSE as `<kind>:<identity>` are
534
- // forwarded. A prose reference like `report.pdf` is left exactly as it was —
535
- // guessing a coordinate out of prose is how the ledger fills with rows
536
- // nobody can look up again.
537
- await recordCoordinateDeliverables({
538
- taskId: completionTaskId,
539
- values: [...(flags.deliverable ?? []), ...(flags.deliverables ?? [])],
540
- warn: (message) => this.warn(message),
541
- });
542
241
  let result;
543
242
  try {
544
- result = await completeTaskDurably({
545
- taskId: completionTaskId,
546
- status,
547
- body,
548
- summary,
549
- deliverables,
550
- verdict,
551
- report,
552
- deliveryReceipt,
553
- noMove: !!flags['no-move'],
554
- transport: {
555
- postComplete: (taskId, payload) => (0, api_fetch_1.apiFetch)(
556
- // A managed session supplies its own completion path; otherwise the
557
- // lib owns the URL so the endpoint stays visible to the parity check.
558
- sessionCompletionPath ?? (0, task_extras_1.taskCompletePath)(taskId), {
559
- method: 'POST',
560
- body: payload,
561
- // Same key the outbox will replay with — see `spaces create`.
562
- // Without it on THIS attempt, a completion that lands and loses
563
- // its response would be applied twice on replay.
564
- headers: { 'X-Idempotency-Key': completionIdempotencyKey },
565
- }),
566
- addComment: (taskId, commentBody) => data_provider_1.dataService.addTaskComment({ taskId, data: { body: commentBody } }),
567
- moveTask: (taskId, taskStatus) => data_provider_1.dataService.moveTask({ taskId, status: taskStatus }),
568
- allowLegacyFallback: sessionCompletionPath ? false : undefined,
569
- },
243
+ result = await (0, api_fetch_1.apiFetch)(sessionPath || (0, task_extras_1.taskCompletePath)(taskId), {
244
+ method: 'POST',
245
+ body: { ...payload, idempotencyKey },
246
+ headers: { 'X-Idempotency-Key': idempotencyKey },
570
247
  });
571
248
  }
572
- catch (err) {
573
- // The completion summary/deliverables/verdict are authored content — the
574
- // record of what the agent actually did. Losing that to a 503 costs the
575
- // reasoning, not just a retry, so an OUTAGE queues it durably (OSK-2806)
576
- // while every 4xx still fails immediately through handleApiError.
577
- //
578
- // The key is derived from (taskId, payload) so replaying THIS completion
579
- // collapses onto the original server-side rather than double-completing;
580
- // `requestIdempotency` on POST /api/tasks/:taskId/complete is what makes
581
- // that hold.
582
- this.queueWriteOrFail(err, {
583
- command: 'tasks complete',
584
- summary: `completion of task ${taskIdentity.id}`,
585
- method: 'post',
586
- url: `/api/tasks/${encodeURIComponent(taskIdentity.id)}/complete`,
587
- body: completionPayload,
588
- idempotencyKey: completionIdempotencyKey,
589
- });
249
+ catch (error) {
250
+ this.handleApiError(error);
590
251
  }
591
- const { comment, movedTask } = result;
592
- // Report the task the SERVER finalized, not the one resolved locally.
593
- // `completeTaskDurably` already refuses a divergence, so these agree by the
594
- // time we get here — preferring the server's id keeps that true even if a
595
- // future path stops throwing.
596
- const completedTaskId = movedTask?.id ?? taskIdentity.id;
597
- const payload = {
598
- success: true,
599
- taskId: completedTaskId,
600
- task: taskIdentity,
601
- moved: !flags['no-move'],
602
- moveStatus: movedTask?.status ?? null,
603
- comment,
604
- movedTask,
605
- taskOutput: {
606
- status,
607
- summary,
608
- deliverables,
609
- verdict,
610
- ...(report ? { report } : {}),
611
- body,
612
- },
613
- ...(result.advisories?.length ? { advisories: result.advisories } : {}),
614
- };
252
+ const url = (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, { id: taskId });
615
253
  if (flags.json) {
616
- const url = movedTask
617
- ? (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, movedTask)
618
- : (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, { id: completedTaskId });
619
- this.log(JSON.stringify({ ...payload, url: report && url ? `${url}#task-outcome` : url }, null, 2));
254
+ this.log(JSON.stringify({ ...result, url }, null, 2));
620
255
  return;
621
256
  }
622
- const commentId = comment
623
- ? (comment.id ?? comment._id)
624
- : undefined;
625
- this.log(`Posted TASK_OUTPUT comment ${commentId ?? '?'} on task ${completedTaskId}`);
626
- if (movedTask) {
627
- // Only claim a transition that happened. This printed unconditionally, so
628
- // `tasks complete` on an already-done task reported "Moved task X to
629
- // done" when nothing moved — a state change asserted by the one tool the
630
- // reader is trusting to tell them the state.
631
- const newStatus = movedTask.status;
632
- this.log(priorStatus && priorStatus === newStatus
633
- ? `Task ${completedTaskId} was already ${newStatus}; the outcome was amended.`
634
- : `Moved task ${completedTaskId} to ${newStatus}`);
635
- }
636
- for (const advisory of result.advisories ?? []) {
637
- // One warn per unmet condition. Each already reads as an observation —
638
- // the completion happened — so the CLI relays them rather than
639
- // re-wording them into something that sounds like a failure.
257
+ this.log(`Submitted Result v${result.result.version}; Task is ${result.task.statusType || result.task.status || 'updated'}.`);
258
+ const pending = describePendingReview({
259
+ statusType: result.task.statusType,
260
+ taskId,
261
+ resultId: result.result.id,
262
+ bin: this.config.bin,
263
+ advisories: result.advisories,
264
+ });
265
+ if (pending)
266
+ this.log(pending);
267
+ for (const advisory of result.advisories || [])
640
268
  this.warn(advisory.message);
641
- }
642
- if (report) {
643
- const url = (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, movedTask ?? { id: completedTaskId });
644
- if (url)
645
- this.log(`Outcome: ${url}#task-outcome`);
646
- }
647
- await (0, task_instruction_offer_1.maybeOfferTaskManagementInstructions)((message) => this.log(message));
269
+ if (url)
270
+ this.log(`Task: ${url}#task-result`);
648
271
  }
649
272
  }
650
273
  exports.default = TasksComplete;
651
- function autonomousSessionCompletionPath() {
652
- // A daemon-delegated CLI credential has the regular user command surface;
653
- // use the normal atomic endpoint so completion, comments, and moves all
654
- // share the same full-permission principal.
655
- if ((0, delegated_cli_1.isDelegatedCliSession)())
656
- return null;
657
- if (!(0, credential_resolver_1.resolveTrustedAutonomousCredential)())
658
- return null;
659
- const sessionId = (process.env.OVERSKY_SESSION_ID || '').trim();
660
- if (!sessionId) {
661
- throw new Error('Autonomous task completion requires OVERSKY_SESSION_ID.');
662
- }
663
- return `/api/agentic/sessions/${encodeURIComponent(sessionId)}/task-completion`;
664
- }
665
- function normalizeStatus(value) {
666
- const normalized = optionalString(value) || 'completed';
667
- if (normalized === 'completed' || normalized === 'failed') {
668
- return normalized;
669
- }
670
- throw new Error('status must be "completed" or "failed".');
671
- }
672
- function requireOutputField(value, field) {
673
- const normalized = optionalOutputField(value);
674
- if (!normalized) {
675
- throw new Error(`${field} is required.`);
676
- }
677
- return normalized;
678
- }
679
- function optionalOutputField(value) {
680
- if (value === undefined || value === null)
681
- return undefined;
682
- const normalized = String(value)
683
- .replace(/---TASK_OUTPUT---/gi, 'TASK_OUTPUT')
684
- .replace(/---END_OUTPUT---/gi, 'END_OUTPUT')
685
- .replace(/[\r\n]+/g, ' ')
686
- .replace(/\s+/g, ' ')
687
- .trim();
688
- return normalized || undefined;
689
- }
690
- function optionalString(value) {
691
- if (value === undefined || value === null)
692
- return undefined;
693
- const normalized = String(value).trim();
694
- return normalized || undefined;
695
- }
696
- // Rendering is shared with the API (`@skrr-ai/data-provider`), so the string
697
- // measured by the local preflight is byte-identical to the one the server stores.
698
- const formatTaskOutput = data_provider_1.renderTaskOutputBody;