@skrr-ai/cli 0.1.21 → 0.1.23

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 (245) hide show
  1. package/README.md +83 -73
  2. package/bin/dev-fallback.js +221 -0
  3. package/bin/run.js +43 -0
  4. package/dist/base-command.d.ts +15 -0
  5. package/dist/base-command.js +71 -10
  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/create.d.ts +1 -0
  43. package/dist/commands/commitments/create.js +19 -5
  44. package/dist/commands/commitments/preflight.js +18 -4
  45. package/dist/commands/commitments/receipt-metrics.d.ts +39 -0
  46. package/dist/commands/commitments/receipt-metrics.js +78 -0
  47. package/dist/commands/convos/search.js +5 -5
  48. package/dist/commands/harnesses/leases/list.d.ts +3 -0
  49. package/dist/commands/harnesses/leases/list.js +59 -12
  50. package/dist/commands/harnesses/leases/show.js +36 -0
  51. package/dist/commands/harnesses/list.d.ts +18 -0
  52. package/dist/commands/harnesses/list.js +30 -0
  53. package/dist/commands/harnesses/products.js +48 -3
  54. package/dist/commands/harnesses/quota.d.ts +16 -0
  55. package/dist/commands/harnesses/quota.js +26 -1
  56. package/dist/commands/harnesses/usage.js +17 -0
  57. package/dist/commands/inbox/index.d.ts +2 -1
  58. package/dist/commands/inbox/index.js +27 -4
  59. package/dist/commands/login.js +4 -1
  60. package/dist/commands/machines/list.d.ts +1 -0
  61. package/dist/commands/machines/list.js +33 -5
  62. package/dist/commands/memory/approvals/issue.d.ts +18 -0
  63. package/dist/commands/memory/approvals/issue.js +63 -0
  64. package/dist/commands/memory/commit.js +5 -2
  65. package/dist/commands/memory/context.d.ts +3 -2
  66. package/dist/commands/memory/context.js +9 -11
  67. package/dist/commands/memory/delete.d.ts +1 -1
  68. package/dist/commands/memory/list.d.ts +1 -1
  69. package/dist/commands/memory/list.js +1 -1
  70. package/dist/commands/memory/mounts/resolve.d.ts +10 -0
  71. package/dist/commands/memory/mounts/resolve.js +27 -0
  72. package/dist/commands/memory/proposals/commit.d.ts +18 -0
  73. package/dist/commands/memory/proposals/commit.js +32 -0
  74. package/dist/commands/memory/proposals/create.d.ts +18 -0
  75. package/dist/commands/memory/proposals/create.js +56 -0
  76. package/dist/commands/memory/proposals/list.d.ts +13 -0
  77. package/dist/commands/memory/proposals/list.js +31 -0
  78. package/dist/commands/memory/proposals/promote.d.ts +22 -0
  79. package/dist/commands/memory/proposals/promote.js +33 -0
  80. package/dist/commands/memory/proposals/reject.d.ts +16 -0
  81. package/dist/commands/memory/proposals/reject.js +28 -0
  82. package/dist/commands/memory/rebuild-index.d.ts +20 -0
  83. package/dist/commands/memory/rebuild-index.js +25 -0
  84. package/dist/commands/memory/save.d.ts +1 -1
  85. package/dist/commands/memory/save.js +1 -1
  86. package/dist/commands/memory/search.d.ts +6 -3
  87. package/dist/commands/memory/search.js +27 -3
  88. package/dist/commands/memory/show.d.ts +1 -1
  89. package/dist/commands/memory/stores/archive.d.ts +14 -0
  90. package/dist/commands/memory/stores/archive.js +31 -0
  91. package/dist/commands/memory/stores/create.d.ts +12 -0
  92. package/dist/commands/memory/stores/create.js +30 -0
  93. package/dist/commands/memory/stores/delete.d.ts +15 -0
  94. package/dist/commands/memory/stores/delete.js +38 -0
  95. package/dist/commands/memory/stores/list.d.ts +10 -0
  96. package/dist/commands/memory/stores/list.js +29 -0
  97. package/dist/commands/memory/stores/show.d.ts +12 -0
  98. package/dist/commands/memory/stores/show.js +14 -0
  99. package/dist/commands/memory/stores/update.d.ts +15 -0
  100. package/dist/commands/memory/stores/update.js +39 -0
  101. package/dist/commands/memory/sync/delete.d.ts +13 -0
  102. package/dist/commands/memory/sync/delete.js +35 -0
  103. package/dist/commands/memory/sync/pull.d.ts +19 -0
  104. package/dist/commands/memory/sync/pull.js +52 -0
  105. package/dist/commands/memory/sync/push.d.ts +18 -0
  106. package/dist/commands/memory/sync/push.js +115 -0
  107. package/dist/commands/memory/sync/resolve.d.ts +23 -0
  108. package/dist/commands/memory/sync/resolve.js +27 -0
  109. package/dist/commands/memory/sync/status.d.ts +9 -0
  110. package/dist/commands/memory/sync/status.js +22 -0
  111. package/dist/commands/memory/topics/archive.d.ts +20 -0
  112. package/dist/commands/memory/topics/archive.js +29 -0
  113. package/dist/commands/memory/topics/create.d.ts +23 -0
  114. package/dist/commands/memory/topics/create.js +46 -0
  115. package/dist/commands/memory/topics/delete.d.ts +22 -0
  116. package/dist/commands/memory/topics/delete.js +30 -0
  117. package/dist/commands/memory/topics/forget.d.ts +22 -0
  118. package/dist/commands/memory/topics/forget.js +29 -0
  119. package/dist/commands/memory/topics/list.d.ts +13 -0
  120. package/dist/commands/memory/topics/list.js +22 -0
  121. package/dist/commands/memory/topics/move.d.ts +22 -0
  122. package/dist/commands/memory/topics/move.js +26 -0
  123. package/dist/commands/memory/topics/show.d.ts +17 -0
  124. package/dist/commands/memory/topics/show.js +21 -0
  125. package/dist/commands/memory/topics/update.d.ts +23 -0
  126. package/dist/commands/memory/topics/update.js +27 -0
  127. package/dist/commands/memory/tree.d.ts +5 -2
  128. package/dist/commands/memory/tree.js +20 -2
  129. package/dist/commands/memory/update.d.ts +1 -1
  130. package/dist/commands/memory/versions/list.d.ts +16 -0
  131. package/dist/commands/memory/versions/list.js +22 -0
  132. package/dist/commands/memory/versions/redact.d.ts +21 -0
  133. package/dist/commands/memory/versions/redact.js +22 -0
  134. package/dist/commands/memory/versions/restore.d.ts +20 -0
  135. package/dist/commands/memory/versions/restore.js +16 -0
  136. package/dist/commands/memory/versions/show.d.ts +15 -0
  137. package/dist/commands/memory/versions/show.js +17 -0
  138. package/dist/commands/messages/list.js +25 -8
  139. package/dist/commands/messages/show.js +10 -0
  140. package/dist/commands/store/install.js +5 -1
  141. package/dist/commands/store/releases.js +1 -1
  142. package/dist/commands/store/update.js +5 -1
  143. package/dist/commands/subscriptions/cancel.js +5 -1
  144. package/dist/commands/subscriptions/health.js +5 -1
  145. package/dist/commands/subscriptions/status.js +5 -1
  146. package/dist/commands/subscriptions/subscribe.js +5 -1
  147. package/dist/commands/tasks/activity.d.ts +2 -3
  148. package/dist/commands/tasks/activity.js +15 -5
  149. package/dist/commands/tasks/complete.d.ts +18 -150
  150. package/dist/commands/tasks/complete.js +161 -600
  151. package/dist/commands/tasks/create.d.ts +5 -0
  152. package/dist/commands/tasks/create.js +107 -4
  153. package/dist/commands/tasks/deliverable/add.d.ts +8 -0
  154. package/dist/commands/tasks/deliverable/add.js +41 -8
  155. package/dist/commands/tasks/deliverable/confirm.d.ts +5 -5
  156. package/dist/commands/tasks/deliverable/confirm.js +9 -18
  157. package/dist/commands/tasks/deliverable/list.d.ts +12 -0
  158. package/dist/commands/tasks/deliverable/list.js +37 -0
  159. package/dist/commands/tasks/events/append.d.ts +2 -0
  160. package/dist/commands/tasks/events/append.js +11 -4
  161. package/dist/commands/tasks/events/list.d.ts +1 -0
  162. package/dist/commands/tasks/events/list.js +1 -0
  163. package/dist/commands/tasks/events/tail.d.ts +1 -0
  164. package/dist/commands/tasks/events/tail.js +1 -0
  165. package/dist/commands/tasks/expectations/assess.d.ts +21 -0
  166. package/dist/commands/tasks/expectations/assess.js +77 -0
  167. package/dist/commands/tasks/expectations.js +14 -18
  168. package/dist/commands/tasks/judge.d.ts +1 -0
  169. package/dist/commands/tasks/judge.js +2 -1
  170. package/dist/commands/tasks/output.d.ts +2 -11
  171. package/dist/commands/tasks/output.js +64 -145
  172. package/dist/commands/tasks/report.d.ts +3 -3
  173. package/dist/commands/tasks/report.js +64 -41
  174. package/dist/commands/tasks/resource/add.d.ts +23 -0
  175. package/dist/commands/tasks/resource/add.js +76 -0
  176. package/dist/commands/tasks/resource/list.d.ts +13 -0
  177. package/dist/commands/tasks/resource/list.js +41 -0
  178. package/dist/commands/tasks/result/show.d.ts +13 -0
  179. package/dist/commands/tasks/result/show.js +46 -0
  180. package/dist/commands/tasks/result/submit.d.ts +26 -0
  181. package/dist/commands/tasks/result/submit.js +134 -0
  182. package/dist/commands/tasks/resume/save.d.ts +16 -0
  183. package/dist/commands/tasks/resume/save.js +59 -0
  184. package/dist/commands/tasks/resume/show.d.ts +13 -0
  185. package/dist/commands/tasks/resume/show.js +35 -0
  186. package/dist/commands/tasks/review.d.ts +17 -0
  187. package/dist/commands/tasks/review.js +54 -0
  188. package/dist/commands/tasks/runs.js +5 -0
  189. package/dist/commands/tasks/timeline.d.ts +2 -2
  190. package/dist/commands/tasks/timeline.js +10 -23
  191. package/dist/commands/tasks/updates/add.d.ts +22 -0
  192. package/dist/commands/tasks/updates/add.js +101 -0
  193. package/dist/commands/tasks/updates/list.d.ts +15 -0
  194. package/dist/commands/tasks/updates/list.js +57 -0
  195. package/dist/commands/tasks/waive.d.ts +1 -0
  196. package/dist/commands/tasks/waive.js +2 -1
  197. package/dist/lib/agentic-stream.d.ts +257 -0
  198. package/dist/lib/agentic-stream.js +490 -33
  199. package/dist/lib/balance.d.ts +8 -0
  200. package/dist/lib/commitment-product.d.ts +10 -1
  201. package/dist/lib/commitment-product.js +66 -2
  202. package/dist/lib/commitments.d.ts +4 -1
  203. package/dist/lib/commitments.js +32 -1
  204. package/dist/lib/conversation-search.d.ts +3 -0
  205. package/dist/lib/conversation-search.js +75 -0
  206. package/dist/lib/execution-target.d.ts +21 -0
  207. package/dist/lib/execution-target.js +22 -6
  208. package/dist/lib/harnesses.d.ts +82 -6
  209. package/dist/lib/harnesses.js +30 -2
  210. package/dist/lib/hosted-machines.js +9 -0
  211. package/dist/lib/inbox.d.ts +2 -1
  212. package/dist/lib/machines.d.ts +17 -0
  213. package/dist/lib/machines.js +29 -0
  214. package/dist/lib/managed-agent-keys.d.ts +104 -0
  215. package/dist/lib/managed-agent-keys.js +50 -0
  216. package/dist/lib/memory-scope.d.ts +4 -3
  217. package/dist/lib/memory-scope.js +9 -9
  218. package/dist/lib/memory-sync.d.ts +114 -0
  219. package/dist/lib/memory-sync.js +730 -0
  220. package/dist/lib/message-provenance.d.ts +42 -0
  221. package/dist/lib/message-provenance.js +89 -0
  222. package/dist/lib/scoped-memory-command.d.ts +68 -0
  223. package/dist/lib/scoped-memory-command.js +183 -0
  224. package/dist/lib/session-task-endpoints.d.ts +1 -1
  225. package/dist/lib/session-task-endpoints.js +7 -0
  226. package/dist/lib/sky-code-broker.d.ts +47 -0
  227. package/dist/lib/sky-code-broker.js +50 -0
  228. package/dist/lib/sky-code-doctor.js +10 -1
  229. package/dist/lib/task-artifact-flags.d.ts +2 -0
  230. package/dist/lib/task-artifact-flags.js +7 -0
  231. package/dist/lib/task-execution.d.ts +4 -5
  232. package/dist/lib/task-execution.js +4 -5
  233. package/dist/lib/task-extras.d.ts +1 -5
  234. package/dist/lib/task-extras.js +1 -5
  235. package/dist/lib/task-transcript.d.ts +5 -7
  236. package/dist/lib/task-transcript.js +8 -16
  237. package/dist/lib/tasks.d.ts +7 -21
  238. package/dist/lib/tasks.js +17 -20
  239. package/dist/lib/triggers.d.ts +39 -1
  240. package/dist/lib/triggers.js +70 -2
  241. package/dist/lib/usage-discovery.d.ts +17 -1
  242. package/dist/lib/usage-discovery.js +17 -1
  243. package/dist/node_modules/@skrr-ai/data-provider/index.js +3733 -3618
  244. package/oclif.manifest.json +25726 -19886
  245. package/package.json +26 -5
@@ -1,294 +1,97 @@
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
- }
207
- }
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
- };
46
+ function describePendingReview({ statusType, taskId, resultId, bin, }) {
47
+ if (statusType !== 'review')
48
+ return null;
49
+ return ('The Result was submitted, but the Task is not done yet. An independent reviewer must ' +
50
+ `accept this exact Result with: ${bin} tasks review ${taskId} --result-id ${resultId || '<result-id>'} --decision accepted`);
219
51
  }
220
52
  class TasksComplete extends base_command_1.BaseCommand {
221
- static description = 'Post a canonical TASK_OUTPUT comment and finish a task';
53
+ static description = 'Submit one versioned Result and advance the Task workflow';
222
54
  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',
55
+ '<%= config.bin %> tasks complete <task> --summary "Implemented and verified the fix" --deliverable <resource-link-id>',
56
+ '<%= config.bin %> tasks complete <task> --summary "Do not ship" --conclusion-type release_recommendation --conclusion-json \'{"posture":"no_ship"}\'',
57
+ '<%= config.bin %> tasks complete <task> --status blocked --summary "Waiting for production credentials" --remaining "A workspace owner must grant access"',
229
58
  ];
230
59
  static args = {
231
60
  id: core_1.Args.string({
232
- description: 'Task ID or identifier; defaults to OVERSKY_SESSION_TASK_ID in task runtimes',
61
+ description: 'Task id or ref; defaults to the bound runtime Task',
233
62
  required: false,
234
63
  ignoreStdin: true,
235
64
  }),
236
65
  };
237
66
  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',
67
+ task: core_1.Flags.string({ description: 'Task id; overrides runtime context' }),
68
+ status: core_1.Flags.string({ options: ['completed', 'failed', 'blocked'] }),
69
+ summary: core_1.Flags.string({ description: 'The Result in one readable paragraph' }),
70
+ body: core_1.Flags.string({ description: 'Long-form Result body' }),
71
+ 'conclusion-type': core_1.Flags.string({
72
+ description: 'Domain conclusion type, such as release_recommendation',
252
73
  }),
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'],
74
+ 'conclusion-json': core_1.Flags.string({ description: 'JSON value for the domain conclusion' }),
75
+ 'structured-schema': core_1.Flags.string({ description: 'Schema name for --structured-json' }),
76
+ 'structured-json': core_1.Flags.string({
77
+ description: 'Task-type-specific structured Result JSON object',
267
78
  }),
268
79
  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
80
  multiple: true,
81
+ description: 'Exact Task Resource link id produced by `tasks deliverable add`',
284
82
  }),
285
- next: core_1.Flags.string({
286
- description: 'Follow-up or next action for the durable task report (repeatable)',
287
- multiple: true,
83
+ evidence: core_1.Flags.string({ multiple: true, description: 'Evidence as <kind>:<id-or-uri>' }),
84
+ remaining: core_1.Flags.string({ multiple: true, description: 'Remaining work item' }),
85
+ 'source-run': core_1.Flags.string({ multiple: true, description: 'Contributing Task Run id' }),
86
+ 'run-id': core_1.Flags.string({ description: 'Expected current Task Run id' }),
87
+ 'spec-version': core_1.Flags.integer({
88
+ description: 'Task specification version this Result answers',
288
89
  }),
289
- 'no-move': core_1.Flags.boolean({
290
- description: 'Only post the TASK_OUTPUT comment; do not move task status',
90
+ 'idempotency-key': core_1.Flags.string({ description: 'Stable retry key for this exact closeout' }),
91
+ 'from-json': core_1.Flags.string({
92
+ description: 'JSON file containing the same canonical Result fields; "-" for stdin',
291
93
  }),
94
+ json: core_1.Flags.boolean({ description: 'Output JSON' }),
292
95
  };
293
96
  async run() {
294
97
  this.requireAuth();
@@ -298,51 +101,26 @@ class TasksComplete extends base_command_1.BaseCommand {
298
101
  try {
299
102
  jsonBody = await (0, prompt_1.readJsonBody)(flags['from-json']);
300
103
  }
301
- catch (err) {
104
+ catch (error) {
302
105
  this.failWithCliError({
303
- message: err.message,
106
+ message: error.message,
304
107
  code: 'INVALID_JSON_INPUT',
305
108
  exit: 1,
306
109
  retryable: false,
307
110
  });
308
111
  }
309
112
  }
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;
113
+ let identity;
336
114
  try {
337
- taskIdentity = (0, task_resolver_1.resolveTaskIdentity)({
115
+ identity = (0, task_resolver_1.resolveTaskIdentity)({
338
116
  positionalId: args.id,
339
117
  flagTask: flags.task,
340
118
  jsonBody,
341
119
  jsonKeys: ['taskId', 'task', 'id'],
342
120
  });
343
121
  }
344
- catch (err) {
345
- const failure = (0, task_resolver_1.taskResolutionFailure)(err);
122
+ catch (error) {
123
+ const failure = (0, task_resolver_1.taskResolutionFailure)(error);
346
124
  this.failWithCliError({
347
125
  message: failure.message,
348
126
  code: failure.code,
@@ -351,348 +129,131 @@ class TasksComplete extends base_command_1.BaseCommand {
351
129
  details: { suggestion: failure.suggestion, context: failure.details },
352
130
  });
353
131
  }
354
- let status;
355
- let summary;
356
- let deliverables;
357
- let verdict;
358
- let report;
132
+ let conclusionValue;
133
+ let structuredValue;
359
134
  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
- }
135
+ conclusionValue = parseJson(flags['conclusion-json'], '--conclusion-json');
136
+ structuredValue = parseJson(flags['structured-json'], '--structured-json');
383
137
  }
384
- catch (err) {
138
+ catch (error) {
385
139
  this.failWithCliError({
386
- message: err.message,
387
- code: 'INVALID_TASK_OUTPUT',
140
+ message: error.message,
141
+ code: 'INVALID_RESULT_JSON',
388
142
  exit: 1,
389
143
  retryable: false,
390
144
  });
391
145
  }
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',
146
+ if (Boolean(flags['conclusion-type']) !== Boolean(flags['conclusion-json'])) {
147
+ this.error('--conclusion-type and --conclusion-json must be supplied together', { exit: 1 });
148
+ }
149
+ if (Boolean(flags['structured-schema']) !== Boolean(flags['structured-json'])) {
150
+ this.error('--structured-schema and --structured-json must be supplied together', {
396
151
  exit: 1,
397
- retryable: false,
398
152
  });
399
153
  }
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) {
154
+ if (structuredValue &&
155
+ (typeof structuredValue !== 'object' || Array.isArray(structuredValue))) {
156
+ this.error('--structured-json must contain a JSON object', { exit: 1 });
157
+ }
158
+ const input = (0, prompt_1.mergeDefined)(jsonBody, {
159
+ status: flags.status,
160
+ summary: flags.summary,
161
+ body: flags.body,
162
+ conclusion: flags['conclusion-type']
163
+ ? { type: flags['conclusion-type'], value: conclusionValue }
164
+ : undefined,
165
+ structuredContent: flags['structured-schema']
166
+ ? {
167
+ schema: flags['structured-schema'],
168
+ value: structuredValue,
169
+ }
170
+ : undefined,
171
+ resourceLinkIds: flags.deliverable,
172
+ evidenceRefs: flags.evidence?.map(parseReference),
173
+ remainingWork: flags.remaining?.map((summary) => ({ summary })),
174
+ sourceRunIds: flags['source-run'],
175
+ expectedRunId: flags['run-id'],
176
+ expectedSpecVersion: flags['spec-version'],
177
+ idempotencyKey: flags['idempotency-key'],
178
+ });
179
+ let status;
180
+ let summary;
181
+ try {
182
+ const candidate = optionalString(input.status) || 'completed';
183
+ if (!['completed', 'failed', 'blocked'].includes(candidate)) {
184
+ throw new Error('status must be completed, failed, or blocked.');
185
+ }
186
+ status = candidate;
187
+ summary = stringValue(input.summary, 'summary');
188
+ }
189
+ catch (error) {
406
190
  this.failWithCliError({
407
- message: (0, data_provider_1.describeTaskOutputOverflow)(budget),
408
- code: 'TASK_OUTPUT_TOO_LONG',
191
+ message: error.message,
192
+ code: 'INVALID_TASK_RESULT',
409
193
  exit: 1,
410
194
  retryable: false,
411
- details: {
412
- limit: budget.limit,
413
- length: budget.length,
414
- overBy: budget.overBy,
415
- fieldLengths: budget.fieldLengths,
416
- },
417
195
  });
418
196
  }
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 = {
197
+ const payload = {
198
+ status,
429
199
  summary,
430
- deliverables,
431
- verdict,
432
- ...(report ? { report } : {}),
433
- ...(deliveryReceipt ? { deliveryReceipt } : {}),
434
- noMove: !!flags['no-move'],
200
+ body: optionalString(input.body),
201
+ conclusion: input.conclusion,
202
+ structuredContent: input.structuredContent,
203
+ resourceLinkIds: Array.isArray(input.resourceLinkIds)
204
+ ? stringArray(input.resourceLinkIds)
205
+ : [],
206
+ evidenceRefs: Array.isArray(input.evidenceRefs) ? input.evidenceRefs : [],
207
+ remainingWork: Array.isArray(input.remainingWork) ? input.remainingWork : [],
208
+ contributors: Array.isArray(input.contributors)
209
+ ? input.contributors
210
+ : [],
211
+ sourceRunIds: stringArray(input.sourceRunIds),
212
+ expectedRunId: optionalString(input.expectedRunId),
213
+ expectedSpecVersion: typeof input.expectedSpecVersion === 'number' ? input.expectedSpecVersion : undefined,
435
214
  };
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;
215
+ const idempotencyKey = optionalString(input.idempotencyKey) ||
216
+ (0, outbox_1.idempotencyKeyFor)('task-complete', identity.id, JSON.stringify(payload));
217
+ const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('completion');
218
+ let taskId = identity.id;
219
+ if (!sessionPath) {
472
220
  try {
473
- canonicalBody = (await (0, task_transcript_1.fetchLatestTaskOutputComment)(completionTaskId))?.body ?? null;
221
+ const resolved = await this.resolveTaskReference(identity.id);
222
+ taskId = String(resolved);
474
223
  }
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
- });
224
+ catch (error) {
225
+ this.handleApiError(error);
515
226
  }
516
227
  }
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
228
  let result;
543
229
  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
- },
230
+ result = await (0, api_fetch_1.apiFetch)(sessionPath || (0, task_extras_1.taskCompletePath)(taskId), {
231
+ method: 'POST',
232
+ body: { ...payload, idempotencyKey },
233
+ headers: { 'X-Idempotency-Key': idempotencyKey },
570
234
  });
571
235
  }
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
- });
236
+ catch (error) {
237
+ this.handleApiError(error);
590
238
  }
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
- };
239
+ const url = (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, { id: taskId });
615
240
  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));
241
+ this.log(JSON.stringify({ ...result, url }, null, 2));
620
242
  return;
621
243
  }
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.
244
+ this.log(`Submitted Result v${result.result.version}; Task is ${result.task.statusType || result.task.status || 'updated'}.`);
245
+ const pending = describePendingReview({
246
+ statusType: result.task.statusType,
247
+ taskId,
248
+ resultId: result.result.id,
249
+ bin: this.config.bin,
250
+ });
251
+ if (pending)
252
+ this.log(pending);
253
+ for (const advisory of result.advisories || [])
640
254
  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));
255
+ if (url)
256
+ this.log(`Task: ${url}#task-result`);
648
257
  }
649
258
  }
650
259
  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;