@skrr-ai/cli 0.1.37 → 0.1.39

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 (177) hide show
  1. package/bin/dev-fallback.js +175 -4
  2. package/dist/base-command.js +46 -2
  3. package/dist/commands/agents/actions/create.js +18 -7
  4. package/dist/commands/agents/actions/delete.d.ts +1 -0
  5. package/dist/commands/agents/actions/delete.js +7 -0
  6. package/dist/commands/agents/actions/update.js +5 -3
  7. package/dist/commands/agents/api-actions/add.js +5 -2
  8. package/dist/commands/agents/api-actions/index.d.ts +11 -0
  9. package/dist/commands/agents/api-actions/index.js +27 -1
  10. package/dist/commands/agents/avatar/default/index.d.ts +11 -0
  11. package/dist/commands/agents/avatar/default/index.js +17 -0
  12. package/dist/commands/agents/avatar/default/set.d.ts +2 -1
  13. package/dist/commands/agents/avatar/default/set.js +32 -7
  14. package/dist/commands/agents/avatar/upload.js +7 -1
  15. package/dist/commands/agents/categories/delete.js +8 -0
  16. package/dist/commands/agents/categories/index.d.ts +21 -5
  17. package/dist/commands/agents/categories/index.js +68 -15
  18. package/dist/commands/agents/chat.d.ts +21 -0
  19. package/dist/commands/agents/chat.js +94 -7
  20. package/dist/commands/agents/create.js +11 -4
  21. package/dist/commands/agents/list.d.ts +15 -0
  22. package/dist/commands/agents/list.js +31 -1
  23. package/dist/commands/agents/scene-background/upload.d.ts +30 -0
  24. package/dist/commands/agents/scene-background/upload.js +97 -0
  25. package/dist/commands/agents/show.js +13 -0
  26. package/dist/commands/agents/tools/calls.d.ts +23 -5
  27. package/dist/commands/agents/tools/calls.js +81 -18
  28. package/dist/commands/agents/update.js +15 -5
  29. package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
  30. package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
  31. package/dist/commands/convos/messages.d.ts +13 -0
  32. package/dist/commands/convos/messages.js +40 -16
  33. package/dist/commands/daemon/index.js +8 -1
  34. package/dist/commands/daemon/logs.d.ts +26 -0
  35. package/dist/commands/daemon/logs.js +56 -0
  36. package/dist/commands/daemon/uninstall.js +1 -1
  37. package/dist/commands/followups/act.js +3 -2
  38. package/dist/commands/followups/cancel.d.ts +6 -3
  39. package/dist/commands/followups/cancel.js +16 -6
  40. package/dist/commands/followups/observability.d.ts +23 -0
  41. package/dist/commands/followups/observability.js +114 -0
  42. package/dist/commands/followups/reschedule.js +1 -1
  43. package/dist/commands/followups/resolve.js +3 -3
  44. package/dist/commands/followups/show.js +2 -1
  45. package/dist/commands/followups/watch.js +9 -5
  46. package/dist/commands/harnesses/forget.js +28 -0
  47. package/dist/commands/harnesses/install.d.ts +41 -0
  48. package/dist/commands/harnesses/install.js +165 -0
  49. package/dist/commands/harnesses/installers.d.ts +45 -0
  50. package/dist/commands/harnesses/installers.js +155 -0
  51. package/dist/commands/harnesses/list.d.ts +82 -1
  52. package/dist/commands/harnesses/list.js +104 -12
  53. package/dist/commands/harnesses/models.js +10 -1
  54. package/dist/commands/harnesses/show.d.ts +15 -0
  55. package/dist/commands/harnesses/show.js +44 -3
  56. package/dist/commands/harnesses/update.js +1 -1
  57. package/dist/commands/harnesses/usage.d.ts +25 -0
  58. package/dist/commands/harnesses/usage.js +24 -2
  59. package/dist/commands/instructions/status.js +13 -1
  60. package/dist/commands/machines/dedicated/catalog.js +4 -15
  61. package/dist/commands/machines/dedicated/create.d.ts +8 -0
  62. package/dist/commands/machines/dedicated/create.js +63 -2
  63. package/dist/commands/machines/dedicated/destroy.js +19 -5
  64. package/dist/commands/machines/dedicated/exec.js +3 -1
  65. package/dist/commands/machines/dedicated/list.js +9 -6
  66. package/dist/commands/machines/dedicated/price-book.d.ts +17 -0
  67. package/dist/commands/machines/dedicated/price-book.js +35 -0
  68. package/dist/commands/machines/hosted/list.js +12 -0
  69. package/dist/commands/machines/list.js +11 -0
  70. package/dist/commands/machines/show.d.ts +1 -0
  71. package/dist/commands/machines/show.js +40 -11
  72. package/dist/commands/profiles/list.d.ts +23 -0
  73. package/dist/commands/profiles/list.js +96 -0
  74. package/dist/commands/search/skills.d.ts +11 -1
  75. package/dist/commands/search/skills.js +26 -8
  76. package/dist/commands/skills/details.d.ts +2 -0
  77. package/dist/commands/skills/details.js +24 -1
  78. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  79. package/dist/commands/skills/import-as-actions.js +175 -0
  80. package/dist/commands/skills/install.d.ts +2 -0
  81. package/dist/commands/skills/install.js +24 -1
  82. package/dist/commands/skills/invocable.js +6 -2
  83. package/dist/commands/skills/local.d.ts +40 -0
  84. package/dist/commands/skills/local.js +160 -0
  85. package/dist/commands/skills/search.d.ts +11 -1
  86. package/dist/commands/skills/search.js +29 -8
  87. package/dist/commands/spaces/actions/add.js +23 -5
  88. package/dist/commands/spaces/actions/available.js +3 -0
  89. package/dist/commands/spaces/actions/list.d.ts +5 -0
  90. package/dist/commands/spaces/actions/list.js +49 -3
  91. package/dist/commands/spaces/actions/run.d.ts +28 -0
  92. package/dist/commands/spaces/actions/run.js +146 -0
  93. package/dist/commands/spaces/actions/update.js +27 -5
  94. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  95. package/dist/commands/spaces/label-summaries.js +64 -0
  96. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  97. package/dist/commands/spaces/task-rollups.js +15 -4
  98. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
  99. package/dist/commands/tasks/actions/attach.js +32 -7
  100. package/dist/commands/tasks/complete.js +13 -0
  101. package/dist/commands/tasks/create.js +1 -1
  102. package/dist/commands/tasks/events/append.js +29 -6
  103. package/dist/commands/tasks/expectations/assess.d.ts +1 -0
  104. package/dist/commands/tasks/expectations/assess.js +43 -5
  105. package/dist/commands/tasks/expectations.js +77 -16
  106. package/dist/commands/tasks/follow-up.js +2 -2
  107. package/dist/commands/tasks/lifecycle/definitions.js +21 -2
  108. package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
  109. package/dist/commands/tasks/lifecycle/detach.js +43 -3
  110. package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
  111. package/dist/commands/tasks/lifecycle/migrate.js +42 -3
  112. package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
  113. package/dist/commands/tasks/lifecycle/reopen.js +40 -3
  114. package/dist/commands/tasks/lifecycle/transition.js +33 -2
  115. package/dist/commands/tasks/list.js +16 -9
  116. package/dist/commands/tasks/move.js +11 -13
  117. package/dist/commands/tasks/review-queue.d.ts +15 -0
  118. package/dist/commands/tasks/review-queue.js +180 -31
  119. package/dist/commands/tasks/show.d.ts +11 -0
  120. package/dist/commands/tasks/show.js +105 -12
  121. package/dist/commands/tasks/trust.js +7 -2
  122. package/dist/commands/tasks/update.js +46 -10
  123. package/dist/commands/tasks/updates/add.js +1 -1
  124. package/dist/commands/token/list.d.ts +22 -0
  125. package/dist/commands/token/list.js +68 -0
  126. package/dist/commands/token/revoke.d.ts +23 -0
  127. package/dist/commands/token/revoke.js +53 -0
  128. package/dist/lib/agent-actions.d.ts +57 -2
  129. package/dist/lib/agent-actions.js +113 -21
  130. package/dist/lib/agent-pin-mute.d.ts +10 -5
  131. package/dist/lib/agent-pin-mute.js +17 -4
  132. package/dist/lib/agent-visual-refs.d.ts +27 -0
  133. package/dist/lib/agent-visual-refs.js +127 -2
  134. package/dist/lib/agentic-stream.d.ts +6 -0
  135. package/dist/lib/agentic-stream.js +34 -3
  136. package/dist/lib/assignee-resolver.d.ts +1 -1
  137. package/dist/lib/assignee-resolver.js +8 -0
  138. package/dist/lib/bulk-task-targets.d.ts +12 -0
  139. package/dist/lib/bulk-task-targets.js +34 -1
  140. package/dist/lib/cli-installers.d.ts +130 -0
  141. package/dist/lib/cli-installers.js +145 -0
  142. package/dist/lib/command-miss.js +55 -7
  143. package/dist/lib/dedicated-lease-command.d.ts +16 -1
  144. package/dist/lib/dedicated-lease-command.js +26 -3
  145. package/dist/lib/dedicated-machines.d.ts +135 -1
  146. package/dist/lib/dedicated-machines.js +307 -3
  147. package/dist/lib/failed-lookup.d.ts +3 -0
  148. package/dist/lib/failed-lookup.js +40 -0
  149. package/dist/lib/first-party-harness-agent.js +9 -2
  150. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  151. package/dist/lib/first-party-harness-doctor.js +126 -27
  152. package/dist/lib/followups.d.ts +14 -6
  153. package/dist/lib/followups.js +31 -6
  154. package/dist/lib/harnesses.d.ts +8 -0
  155. package/dist/lib/instruction-input.d.ts +19 -1
  156. package/dist/lib/instruction-input.js +52 -1
  157. package/dist/lib/label-scope.d.ts +15 -0
  158. package/dist/lib/label-scope.js +25 -5
  159. package/dist/lib/local-skills.d.ts +98 -0
  160. package/dist/lib/local-skills.js +195 -0
  161. package/dist/lib/machines.d.ts +10 -10
  162. package/dist/lib/machines.js +23 -1
  163. package/dist/lib/search-query.d.ts +19 -0
  164. package/dist/lib/search-query.js +35 -0
  165. package/dist/lib/space-custom-actions.d.ts +72 -0
  166. package/dist/lib/space-custom-actions.js +117 -0
  167. package/dist/lib/space-resolver.d.ts +1 -21
  168. package/dist/lib/space-resolver.js +19 -10
  169. package/dist/lib/task-closure.d.ts +3 -2
  170. package/dist/lib/task-closure.js +18 -20
  171. package/dist/lib/task-lifecycle-output.js +117 -9
  172. package/dist/lib/task-ref-resolver.d.ts +1 -17
  173. package/dist/lib/task-ref-resolver.js +18 -8
  174. package/dist/lib/tasks.js +9 -3
  175. package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
  176. package/oclif.manifest.json +15505 -14460
  177. package/package.json +9 -3
@@ -35,6 +35,9 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
35
35
  description: 'Only Tasks where a Result is already in — the ones a reviewer can act on',
36
36
  }),
37
37
  limit: core_1.Flags.integer({ description: 'Maximum Tasks to inspect (default 100)', default: 100 }),
38
+ 'include-closed': core_1.Flags.boolean({
39
+ description: 'Also list Tasks already closed — for auditing what was closed over',
40
+ }),
38
41
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
39
42
  };
40
43
  async run() {
@@ -66,7 +69,27 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
66
69
  // unlooked-for empty answer this command refuses to give.
67
70
  this.error('--limit must be at least 1.', { exit: 1 });
68
71
  }
72
+ /*
73
+ * oclif's `required: true` asserts the flag was SUPPLIED, not that it says
74
+ * anything. `--space ""` — an unset shell variable, which is how it happens
75
+ * — satisfied it, and the empty string was then dropped from the query
76
+ * string as falsy, so the command silently widened from one Space to every
77
+ * Space the caller can see and reported that as the answer.
78
+ */
79
+ if (!flags.space.trim()) {
80
+ this.error('--space needs a Space id; it was given an empty value.', { exit: 1 });
81
+ }
69
82
  const tasks = [];
83
+ /*
84
+ * Whether the scan actually reached the end.
85
+ *
86
+ * The loop already followed the cursor, and then stopped at `--limit` with
87
+ * `has_more` still true and threw that fact away — so a Space with 95 owed
88
+ * Expectations reported on the first 11 and printed the same sentence it
89
+ * prints for a Space that owes nothing. The comment above states the
90
+ * principle this had to satisfy; nothing recorded whether it had.
91
+ */
92
+ let truncated = false;
70
93
  try {
71
94
  let cursor;
72
95
  for (;;) {
@@ -80,8 +103,12 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
80
103
  'say whether anything is owed. Refusing to report an empty queue.', { exit: 1 });
81
104
  }
82
105
  tasks.push(...response.data);
83
- if (!response.has_more || !response.next_cursor || tasks.length >= flags.limit)
106
+ if (!response.has_more || !response.next_cursor)
107
+ break;
108
+ if (tasks.length >= flags.limit) {
109
+ truncated = true;
84
110
  break;
111
+ }
85
112
  cursor = response.next_cursor;
86
113
  }
87
114
  }
@@ -95,34 +122,101 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
95
122
  * worse than no queue: the reviewer would believe they had seen everything.
96
123
  */
97
124
  const rows = [];
98
- for (const task of tasks) {
99
- const taskId = String(task.id ?? task._id ?? '');
100
- if (!taskId)
101
- continue;
102
- const base = {
103
- taskId,
104
- identifier: String(task.identifier ?? taskId),
105
- title: String(task.title ?? '(untitled)'),
106
- status: String(task.status ?? '-'),
107
- hasResult: Boolean(task.currentResultId),
108
- };
109
- try {
110
- const acceptance = await data_provider_1.dataService.getTaskAcceptance(taskId);
111
- const awaiting = acceptance.expectations.filter((e) => e.gating && !e.met);
112
- if (awaiting.length > 0)
113
- rows.push({ ...base, awaiting });
114
- }
115
- catch (error) {
116
- rows.push({ ...base, awaiting: [], error: error?.message || 'unreadable' });
125
+ /*
126
+ * A finished Task is not a worklist item.
127
+ *
128
+ * Closing is the closer's call now, so a Task can reach `done` over
129
+ * Expectations nobody judged — and those rows stayed in the queue forever,
130
+ * captioned in the present tense ("settling these will not close it") about
131
+ * something already closed. Every forced close permanently added noise to
132
+ * the list a reviewer is supposed to work down. `--include-closed` keeps the
133
+ * old view for anyone auditing what was closed over.
134
+ */
135
+ const CLOSED_STATUS_TYPES = new Set(['completed', 'cancelled']);
136
+ const candidates = tasks.filter((task) => flags['include-closed'] || !CLOSED_STATUS_TYPES.has(String(task.statusType ?? '')));
137
+ const skippedClosed = tasks.length - candidates.length;
138
+ /*
139
+ * Acceptance is one request per Task and there is no batch endpoint, so the
140
+ * serial loop this replaces took 61 seconds on 100 Tasks and printed nothing
141
+ * at all until it finished. Bounded rather than `Promise.all`: 100
142
+ * simultaneous authorized reads hit the same cascade the `/cards` endpoint
143
+ * exists to avoid. Results are written by INDEX so output order stays the
144
+ * list's order regardless of which request returns first.
145
+ */
146
+ const ACCEPTANCE_CONCURRENCY = 6;
147
+ const settled = new Array(candidates.length).fill(null);
148
+ let next = 0;
149
+ const worker = async () => {
150
+ for (;;) {
151
+ const index = next;
152
+ next += 1;
153
+ if (index >= candidates.length)
154
+ return;
155
+ const task = candidates[index];
156
+ const taskId = String(task.id ?? task._id ?? '');
157
+ if (!taskId)
158
+ continue;
159
+ const base = {
160
+ taskId,
161
+ identifier: String(task.identifier ?? taskId),
162
+ title: String(task.title ?? '(untitled)'),
163
+ status: String(task.status ?? '-'),
164
+ hasResult: Boolean(task.currentResultId),
165
+ };
166
+ try {
167
+ const acceptance = await data_provider_1.dataService.getTaskAcceptance(taskId);
168
+ const awaiting = acceptance.expectations.filter((e) => e.gating && !e.met);
169
+ if (awaiting.length > 0)
170
+ settled[index] = { ...base, awaiting };
171
+ }
172
+ catch (error) {
173
+ settled[index] = {
174
+ ...base,
175
+ awaiting: [],
176
+ error: error?.message || 'unreadable',
177
+ };
178
+ }
117
179
  }
118
- }
180
+ };
181
+ await Promise.all(Array.from({ length: Math.min(ACCEPTANCE_CONCURRENCY, candidates.length) }, worker));
182
+ rows.push(...settled.filter((row) => row !== null));
119
183
  const visible = flags.actionable ? rows.filter((row) => row.hasResult || row.error) : rows;
120
184
  if (flags.json) {
121
- this.log(JSON.stringify({ space: flags.space, tasks: visible }, null, 2));
185
+ this.log(JSON.stringify({
186
+ space: flags.space,
187
+ inspected: candidates.length,
188
+ truncated,
189
+ ...(skippedClosed > 0 ? { skippedClosed } : {}),
190
+ owedTasks: rows.length,
191
+ tasks: visible,
192
+ }, null, 2));
122
193
  return;
123
194
  }
124
- if (visible.length === 0) {
195
+ /*
196
+ * "Nothing is owed" may only be said by a scan that reached the end.
197
+ *
198
+ * One sentence used to cover four different states — a clean queue, a
199
+ * truncated scan, a filter that emptied the list, and a Space that does not
200
+ * exist — and in three of them it was false. Each now says its own thing,
201
+ * and the two that cannot support the claim refuse to make it, in the same
202
+ * shape as the misread-envelope guard above.
203
+ */
204
+ if (truncated && rows.length === 0) {
205
+ this.error(`Inspected only the first ${candidates.length} Task(s) of a longer list in ${flags.space}, ` +
206
+ 'so it cannot say whether anything is owed. Refusing to report an empty queue. ' +
207
+ 'Re-run with a larger --limit.', { exit: 1 });
208
+ }
209
+ if (rows.length === 0) {
210
+ await this.assertSpaceExists(flags.space);
125
211
  this.log(`Nothing in ${flags.space} is awaiting a judgement.`);
212
+ if (skippedClosed > 0) {
213
+ this.log(` (${skippedClosed} closed Task(s) skipped — see them with --include-closed.)`);
214
+ }
215
+ return;
216
+ }
217
+ if (visible.length === 0) {
218
+ this.log(`${rows.length} Task(s) in ${flags.space} owe judgements, but none has a Result yet — ` +
219
+ 're-run without --actionable to see them.');
126
220
  return;
127
221
  }
128
222
  let total = 0;
@@ -133,18 +227,37 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
133
227
  this.log(` could not read its Expectations: ${row.error}`);
134
228
  continue;
135
229
  }
136
- // A Task with no Result yet is listed but marked: its Expectations are
137
- // owed, and settling them still leaves it unclosable. Saying so here stops
138
- // a reviewer working through a list that cannot finish.
230
+ /*
231
+ * State the fact, not a gate. This read "no Result submitted yet — settling
232
+ * these will not close it", which implied that a Result is what stands
233
+ * between these judgements and a close. Since `56a6ded703` it is not: a
234
+ * Task closes without one and the absence is recorded on
235
+ * `resolution.closure`. What a reviewer needs to know is only that there is
236
+ * no Result to judge the work against yet.
237
+ */
139
238
  if (!row.hasResult) {
140
- this.log(' no Result submitted yet — settling these will not close it');
239
+ this.log(' no Result submitted yet — nothing to judge the work against');
141
240
  }
142
241
  for (const expectation of row.awaiting) {
143
242
  total += 1;
144
- const settledBy = expectation.assessedByKind
145
- ? ` [last judged by ${(0, task_closure_1.describeAssessor)(expectation.assessedByKind)}]`
146
- : '';
147
- this.log(` ${expectation.id} ${expectation.sentence || expectation.text || ''}${settledBy}`);
243
+ this.log(` ${expectation.id} ${expectation.sentence || expectation.text || ''}`);
244
+ /*
245
+ * The judgement itself, not just the KIND of judge.
246
+ *
247
+ * `[last judged by person]` named no person, no decision, no date and no
248
+ * reason — so a reviewer working down this list could see that someone
249
+ * had looked and not what they concluded, which is the one thing they
250
+ * need before looking again. The acceptance projection carries all of it
251
+ * now; this is where it was being dropped.
252
+ */
253
+ if (expectation.assessedByKind) {
254
+ const decision = expectation.decision || 'judged';
255
+ const when = expectation.assessedAt
256
+ ? ` on ${String(expectation.assessedAt).slice(0, 10)}`
257
+ : '';
258
+ const why = expectation.reason ? `: ${expectation.reason}` : '';
259
+ this.log(` ${decision} by ${(0, task_closure_1.describeAssessor)(expectation.assessedByKind)}${when}${why}`);
260
+ }
148
261
  if (expectation.evidenceLevel) {
149
262
  this.log(` evidence: ${expectation.evidenceLevel}${expectation.requiredEvidence ? ` (asked for ${expectation.requiredEvidence})` : ''}`);
150
263
  }
@@ -155,11 +268,47 @@ class TasksReviewQueue extends base_command_1.BaseCommand {
155
268
  }
156
269
  this.log('');
157
270
  this.log(`${total} Expectation(s) awaiting a judgement across ${visible.length} Task(s).`);
271
+ if (truncated) {
272
+ this.log(`Only the first ${candidates.length} Task(s) were inspected and more remain — ` +
273
+ 're-run with a larger --limit to see the rest.');
274
+ }
275
+ if (skippedClosed > 0) {
276
+ this.log(`${skippedClosed} closed Task(s) skipped — see them with --include-closed.`);
277
+ }
158
278
  this.log('');
159
279
  this.log('Settle one: skrr tasks expectations assess <task-id> <expectation-id> \\');
160
280
  this.log(' --decision met --reason "..."');
161
281
  this.log('Settle a Task: skrr tasks expectations assess <task-id> --all \\');
162
282
  this.log(' --decision met --reason "..."');
163
283
  }
284
+ /**
285
+ * An empty queue must not be how you learn a Space id is wrong.
286
+ *
287
+ * A UUID-shaped ref is accepted without a lookup (`base-command` short-circuits
288
+ * on the SHAPE, which is a cheap and correct optimisation nearly everywhere),
289
+ * and the list route deliberately never 404s — an unknown Space falls back to
290
+ * the ACL enumeration and returns an honest empty page. Those two reasonable
291
+ * decisions compose into "Nothing is awaiting a judgement", exit 0, for a
292
+ * Space that does not exist. A typo read as done.
293
+ *
294
+ * So the existence check is paid for ONLY on the path that is about to make
295
+ * the completeness claim — never on the path that found work.
296
+ */
297
+ async assertSpaceExists(spaceId) {
298
+ try {
299
+ await data_provider_1.dataService.getSpace(spaceId);
300
+ }
301
+ catch (error) {
302
+ // The status rides the error itself here, the way `base-command` reads it
303
+ // (`e.status === 404`) — not on a nested `response`.
304
+ const status = error?.status;
305
+ if (status === 404 || status === 403) {
306
+ this.error(`No Space ${spaceId} is visible to you, so there is nothing to report on. ` +
307
+ 'Check the id with `skrr spaces list`.', { exit: 2 });
308
+ }
309
+ // Any other failure is about this read, not about the Space. Let the
310
+ // empty answer stand rather than turning a blip into a false "not found".
311
+ }
312
+ }
164
313
  }
165
314
  exports.default = TasksReviewQueue;
@@ -1,4 +1,15 @@
1
1
  import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * Where to look next — as COMMANDS, not as facts about the task.
4
+ *
5
+ * These used to print as `What the agent produced: skrr tasks output <ref>`,
6
+ * in the same `Label: value` grammar as every line above them, so the line read
7
+ * as a statement of what the agent had produced. It also printed on tasks no
8
+ * agent had ever run, asserting output that did not exist (OSK-9669). They are
9
+ * now a visibly separate list, and only once a run, a conversation or a Result
10
+ * exists for them to show.
11
+ */
12
+ export declare function taskPointerLines(t: Record<string, unknown>, ref: string, bin?: string): string[];
2
13
  /**
3
14
  * The failure rows of `tasks show`, when there are any. (S3 dogfood finding)
4
15
  *
@@ -1,14 +1,43 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.taskPointerLines = taskPointerLines;
3
4
  exports.taskFailureLines = taskFailureLines;
4
5
  const core_1 = require("@oclif/core");
5
6
  const data_provider_1 = require("@skrr-ai/data-provider");
6
7
  const api_fetch_1 = require("../../lib/api-fetch");
7
8
  const session_task_endpoints_1 = require("../../lib/session-task-endpoints");
8
9
  const base_command_1 = require("../../base-command");
10
+ const task_closure_1 = require("../../lib/task-closure");
9
11
  const task_resolver_1 = require("../../lib/task-resolver");
10
12
  const web_url_1 = require("../../lib/web-url");
11
13
  const html_text_1 = require("../../lib/html-text");
14
+ /**
15
+ * Where to look next — as COMMANDS, not as facts about the task.
16
+ *
17
+ * These used to print as `What the agent produced: skrr tasks output <ref>`,
18
+ * in the same `Label: value` grammar as every line above them, so the line read
19
+ * as a statement of what the agent had produced. It also printed on tasks no
20
+ * agent had ever run, asserting output that did not exist (OSK-9669). They are
21
+ * now a visibly separate list, and only once a run, a conversation or a Result
22
+ * exists for them to show.
23
+ */
24
+ function taskPointerLines(t, ref, bin = 'skrr') {
25
+ const hasSomethingToShow = Boolean(t.conversationId || t.lastFinalizedRunId || t.checkoutRunId || t.currentResultId);
26
+ if (!hasSomethingToShow)
27
+ return [];
28
+ const pointers = [
29
+ [`${bin} tasks output ${ref}`, 'what the agent produced'],
30
+ ];
31
+ if (t.conversationId) {
32
+ pointers.push([`${bin} tasks transcript ${ref}`, 'the full agent transcript']);
33
+ }
34
+ const width = Math.max(...pointers.map(([command]) => command.length));
35
+ return [
36
+ '',
37
+ 'See also:',
38
+ ...pointers.map(([command, what]) => ` ${command.padEnd(width)} ${what}`),
39
+ ];
40
+ }
12
41
  /**
13
42
  * The failure rows of `tasks show`, when there are any. (S3 dogfood finding)
14
43
  *
@@ -179,13 +208,80 @@ class TasksShow extends base_command_1.BaseCommand {
179
208
  * it stood on at close. Show it so a bare "done" never reads as "clean."
180
209
  */
181
210
  const resolution = t.resolution;
182
- if (resolution?.closure && resolution.closure.closable === false) {
183
- const parts = [];
184
- const unmet = resolution.closure.expectations?.unmet?.length ?? 0;
185
- if (unmet > 0)
186
- parts.push(`${unmet} Expectation${unmet === 1 ? '' : 's'} still unmet`);
187
- parts.push(...(resolution.closure.blockers || []).filter((c) => c !== 'TASK_EXPECTATIONS_NOT_MET'));
188
- this.log(`Resolution: ${resolution.kind ?? '-'} — closed over ${parts.join('; ') || 'outstanding items'}`);
211
+ /*
212
+ * Same renderer as the `tasks move` receipt, so the two cannot disagree
213
+ * about a Task they are both describing.
214
+ *
215
+ * This copy showed an owner's ordinary close as an override:
216
+ * `TASK_RESULT_REVIEW_REQUIRED` lands in `blockers` whenever the policy is
217
+ * unsatisfied — including when the owner, who is not bound by the Review
218
+ * they declared, closes it themselves — and this rendered it identically to
219
+ * a genuine forced close. So the only read surface for "was this gate
220
+ * overridden?" could not answer the question.
221
+ */
222
+ if (resolution?.closure) {
223
+ const sentences = (0, task_closure_1.closureSnapshotLines)(resolution.closure);
224
+ if (sentences.length > 0) {
225
+ this.log(`Resolution: ${resolution.kind ?? '-'} — ${sentences.join(' ')}`);
226
+ }
227
+ }
228
+ /*
229
+ * A Result orphaned by a spec edit is not the same as no Result.
230
+ *
231
+ * Editing a `description` or an Expectation advances `specVersion`, which
232
+ * unsets `currentResultId` by design — the old answer replied to a question
233
+ * that has changed. But every CLI surface then reported the Task as though
234
+ * nothing had ever been submitted, and the prior Result is still there. The
235
+ * web already says this correctly ("No Result answers the current Task spec
236
+ * vN", plus the previous one); the CLI said nothing at all.
237
+ *
238
+ * The extra request is paid only in the ambiguous case — when there is no
239
+ * current Result — and `tasks show` is a detail view, so one call to
240
+ * distinguish "nobody answered" from "the answer is out of date" is worth
241
+ * it. A failed lookup degrades to silence rather than to a claim.
242
+ */
243
+ const taskSpecVersion = Number(t.specVersion) || 1;
244
+ if (t.currentResultId) {
245
+ this.log(`Result: ${String(t.currentResultId)} (answers spec v${taskSpecVersion})`);
246
+ /*
247
+ * The Review on that Result, with its REASON.
248
+ *
249
+ * Closing over a `changes_requested` Review is allowed and recorded, and
250
+ * the closure line says "a reviewer requested changes" — the verdict. What
251
+ * they asked for lived only on the Review row, which no text surface
252
+ * printed, so the objection survived as a fact and lost its content. One
253
+ * read, on a detail view, only when there is a Result to have been
254
+ * reviewed; a failed read says nothing rather than implying no Review.
255
+ */
256
+ try {
257
+ const history = await data_provider_1.dataService.listTaskResults(taskIdentity.id);
258
+ const review = (history?.reviews || []).find((row) => row.resultId === String(t.currentResultId));
259
+ if (review) {
260
+ const who = (0, task_closure_1.describeAssessor)(review.reviewedBy?.kind);
261
+ const when = review.reviewedAt ? ` on ${String(review.reviewedAt).slice(0, 10)}` : '';
262
+ const why = review.rationale ? `: ${review.rationale}` : '';
263
+ this.log(`Review: ${review.decision} by ${who}${when}${why}`);
264
+ }
265
+ }
266
+ catch {
267
+ // Silence, not "no Review": this read failing is not evidence of absence.
268
+ }
269
+ }
270
+ else {
271
+ let priorLine = 'none submitted';
272
+ try {
273
+ const history = await data_provider_1.dataService.listTaskResults(taskIdentity.id);
274
+ const prior = history?.results?.[0];
275
+ if (prior) {
276
+ priorLine =
277
+ `none answers spec v${taskSpecVersion} — previous v${prior.version} ` +
278
+ `answered spec v${prior.specVersion ?? '?'}`;
279
+ }
280
+ }
281
+ catch {
282
+ priorLine = 'none answers the current spec (earlier Results could not be read)';
283
+ }
284
+ this.log(`Result: ${priorLine}`);
189
285
  }
190
286
  this.log(`Executor: ${t.executionMode ?? 'legacy / inferred'}`);
191
287
  this.log(`Priority: ${t.priority ?? '-'}`);
@@ -333,11 +429,8 @@ class TasksShow extends base_command_1.BaseCommand {
333
429
  // Silent: a Task this caller can VIEW but whose acceptance they cannot
334
430
  // read is a permissions shape, not something to shout about mid-page.
335
431
  }
336
- this.log('');
337
- this.log(`What the agent produced: skrr tasks output ${ref}`);
338
- if (t.conversationId) {
339
- this.log(`Full agent transcript: skrr tasks transcript ${ref}`);
340
- }
432
+ for (const line of taskPointerLines(t, ref, this.config.bin))
433
+ this.log(line);
341
434
  }
342
435
  }
343
436
  exports.default = TasksShow;
@@ -41,8 +41,13 @@ class TasksTrust extends base_command_1.BaseCommand {
41
41
  level: core_1.Flags.string({
42
42
  options: [...data_provider_1.DELIVERABLE_AUTHORITIES],
43
43
  required: true,
44
- description: 'How well evidenced the answer must be: self_reported (the doer says so), ' +
45
- 'human_confirmed (someone else looked), observation_backed (the platform saw it).',
44
+ description:
45
+ // `observation_backed` is a RETIRED rung name for this domain — the flag
46
+ // rejects it, so the only two values the help explained were one real one
47
+ // and one the command refuses. The four the enum actually accepts:
48
+ 'How well evidenced the answer must be: self_reported (the doer says so), ' +
49
+ 'human_confirmed (a person confirmed it), system_observed (the platform saw it), ' +
50
+ 'external_receipt (a provider receipt proves it).',
46
51
  }),
47
52
  reason: core_1.Flags.string({
48
53
  required: true,
@@ -251,26 +251,62 @@ class TasksUpdate extends base_command_1.BaseCommand {
251
251
  * rewrote the list and revoked the assessments with no warning at all.
252
252
  * Same write, same price: the disclosure belongs on the effective change.
253
253
  */
254
- if (expectationsPatch !== undefined || body.expectations !== undefined) {
255
- let settledCount = 0;
254
+ /*
255
+ * The DESCRIPTION is a spec field too.
256
+ *
257
+ * `TaskSpecFence.TASK_SPEC_FIELDS` is `['description', 'expectations']`, and
258
+ * both advance `specVersion` — which revokes every assessment, because they
259
+ * are matched by exact version. The disclosure was wired to the Expectations
260
+ * payload alone, so fixing a typo in the prose voided a reviewer's sign-off
261
+ * with no warning and no receipt. Same write, same price, same disclosure.
262
+ */
263
+ const changesSpec = expectationsPatch !== undefined ||
264
+ body.expectations !== undefined ||
265
+ flags.description !== undefined ||
266
+ body.description !== undefined;
267
+ if (changesSpec) {
268
+ let settled = [];
269
+ let unreadable = false;
256
270
  try {
257
271
  const acceptance = await data_provider_1.dataService.getTaskAcceptance(taskIdentity.id);
258
- settledCount = acceptance.expectations.filter((e) => e.assessmentId).length;
272
+ settled = acceptance.expectations.filter((e) => e.assessmentId);
259
273
  }
260
274
  catch {
261
275
  // An unreadable acceptance is not a reason to refuse the edit. Warn
262
276
  // without a number rather than inventing one or blocking the write.
263
- settledCount = -1;
277
+ unreadable = true;
264
278
  }
265
- if (settledCount !== 0 && !flags.yes) {
266
- const noun = settledCount === 1 ? 'assessment' : 'assessments';
267
- const count = settledCount < 0 ? 'Any existing' : `${settledCount} existing`;
268
- this.log(`Changing Expectations advances the Task specification, so ${count} ${noun} ` +
279
+ if ((unreadable || settled.length > 0) && !flags.yes) {
280
+ const noun = settled.length === 1 ? 'assessment' : 'assessments';
281
+ const count = unreadable ? 'Any existing' : `${settled.length} existing`;
282
+ const field = expectationsPatch !== undefined || body.expectations !== undefined
283
+ ? 'Changing Expectations'
284
+ : 'Changing the description';
285
+ this.log(`${field} advances the Task specification, so ${count} ${noun} ` +
269
286
  'will stop counting and must be recorded again.');
287
+ /*
288
+ * NAME them. The count alone made the warning unactionable — and it is
289
+ * task-WIDE, so Expectations you are not touching lose their judgements
290
+ * too, which a bare number actively hides.
291
+ */
292
+ for (const row of settled) {
293
+ this.log(` ${row.id} ${row.sentence || row.text || ''}`);
294
+ }
295
+ if (settled.length > 0) {
296
+ this.log('Revocation covers every Expectation on this Task, including ones this edit ' +
297
+ 'does not touch.');
298
+ }
270
299
  const ok = await this.confirm('Continue? [y/N] ');
271
300
  if (!ok) {
272
- this.log('No change made.');
273
- return;
301
+ /*
302
+ * Non-zero, because the edit did NOT happen.
303
+ *
304
+ * This printed "No change made." and exited 0, so `skrr tasks update
305
+ * … && echo applied` printed `applied`. The guard exists to protect
306
+ * the non-interactive path, and the non-interactive path is precisely
307
+ * the one that cannot see a message — it can only read the exit code.
308
+ */
309
+ this.error('No change made — re-run with `--yes` to accept the revocation.', { exit: 2 });
274
310
  }
275
311
  }
276
312
  }
@@ -42,7 +42,7 @@ class TasksUpdatesAdd extends base_command_1.BaseCommand {
42
42
  'fields are required even when empty. ' +
43
43
  'progress {completedSinceLast:[],nextActions:[],currentFocus?} · ' +
44
44
  'finding {finding,evidenceRefs:[],impact?,severity?} · ' +
45
- 'decision {decision,rationale,alternativesConsidered?} · ' +
45
+ 'decision {decision,rationale,alternativesConsidered?:[]} · ' +
46
46
  'blocker {cause,requiredAction,nextOwner} · ' +
47
47
  'verification {claim,result:passed|failed|inconclusive,evidenceRefs:[]}. ' +
48
48
  'nextOwner is a principal object like {"kind":"human","userId":"..."}, not a name. ' +
@@ -0,0 +1,22 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * `skrr token list`
4
+ *
5
+ * The other half of `token mint`. Minting binds a long-lived refresh session to
6
+ * a machine and records the `--label` with it — which reads as a promise that
7
+ * the credential can be found again — and there was no way to see it: `token`
8
+ * had only `mint`, and `list-tokens` shows CI tokens, a different credential
9
+ * type, so `revoke-token` had nothing to aim at. The only remedy was
10
+ * `logout --all-devices`, which signs every machine out.
11
+ *
12
+ * `GET /api/auth/sessions/refresh` has served exactly this list all along; it
13
+ * was simply not reachable from the CLI.
14
+ */
15
+ export default class TokenList extends BaseCommand {
16
+ static description: string;
17
+ static examples: string[];
18
+ static flags: {
19
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
20
+ };
21
+ run(): Promise<void>;
22
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../base-command");
5
+ const api_fetch_1 = require("../../lib/api-fetch");
6
+ const format_1 = require("../../lib/format");
7
+ /**
8
+ * `skrr token list`
9
+ *
10
+ * The other half of `token mint`. Minting binds a long-lived refresh session to
11
+ * a machine and records the `--label` with it — which reads as a promise that
12
+ * the credential can be found again — and there was no way to see it: `token`
13
+ * had only `mint`, and `list-tokens` shows CI tokens, a different credential
14
+ * type, so `revoke-token` had nothing to aim at. The only remedy was
15
+ * `logout --all-devices`, which signs every machine out.
16
+ *
17
+ * `GET /api/auth/sessions/refresh` has served exactly this list all along; it
18
+ * was simply not reachable from the CLI.
19
+ */
20
+ class TokenList extends base_command_1.BaseCommand {
21
+ static description = 'List the active sessions a minted token belongs to — the other half of `token mint`';
22
+ static examples = [
23
+ '<%= config.bin %> token list',
24
+ '<%= config.bin %> token list --json',
25
+ ];
26
+ static flags = { json: core_1.Flags.boolean({ description: 'Output JSON' }) };
27
+ async run() {
28
+ this.requireAuth();
29
+ const { flags } = await this.parse(TokenList);
30
+ let data = null;
31
+ try {
32
+ data = await (0, api_fetch_1.apiFetch)('/api/auth/sessions/refresh', {
33
+ method: 'GET',
34
+ credential: this.resolvedCredential,
35
+ });
36
+ }
37
+ catch (err) {
38
+ this.handleApiError(err);
39
+ }
40
+ const sessions = data?.sessions || [];
41
+ if (flags.json)
42
+ return this.log(JSON.stringify({ sessions, count: sessions.length }, null, 2));
43
+ if (!sessions.length)
44
+ return this.log('No active sessions.');
45
+ (0, format_1.renderTable)(sessions.map((session) => ({
46
+ id: session.id,
47
+ kind: session.kind,
48
+ // The operator's own name for the credential wins over a derived
49
+ // device string — it is what they would search by.
50
+ device: session.label || session.device || '-',
51
+ ip: session.ip || '-',
52
+ lastActive: session.lastActiveAt || session.createdAt || '-',
53
+ // The session you are typing in. Revoking it signs THIS machine out,
54
+ // which is the one outcome nobody means when they revoke a credential.
55
+ current: session.current ? 'yes' : '',
56
+ })), [
57
+ { key: 'id', header: 'ID' },
58
+ { key: 'kind', header: 'KIND' },
59
+ { key: 'device', header: 'DEVICE', maxWidth: 34 },
60
+ { key: 'ip', header: 'IP' },
61
+ { key: 'lastActive', header: 'LAST ACTIVE' },
62
+ { key: 'current', header: 'CURRENT' },
63
+ ], (line) => this.log(line));
64
+ this.log('');
65
+ this.log('Retire one with: skrr token revoke <id>');
66
+ }
67
+ }
68
+ exports.default = TokenList;
@@ -0,0 +1,23 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * `skrr token revoke <id>`
4
+ *
5
+ * Retire ONE session — the credential `token mint` handed to one machine —
6
+ * without signing every other machine out. `logout --all-devices` was the only
7
+ * way to do this, which is a blunt instrument for "that one CI box is
8
+ * decommissioned".
9
+ *
10
+ * Owner-gated server-side: a row that is not the caller's returns
11
+ * `{ revoked: false }` rather than an error, so ids cannot be enumerated.
12
+ */
13
+ export default class TokenRevoke extends BaseCommand {
14
+ static description: string;
15
+ static examples: string[];
16
+ static args: {
17
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
18
+ };
19
+ static flags: {
20
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ };
22
+ run(): Promise<void>;
23
+ }