@skrr-ai/cli 0.1.36 → 0.1.37

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 (152) hide show
  1. package/README.md +17 -4
  2. package/dist/commands/agents/actions/create.d.ts +5 -0
  3. package/dist/commands/agents/actions/create.js +26 -0
  4. package/dist/commands/agents/actions/update.d.ts +5 -0
  5. package/dist/commands/agents/actions/update.js +24 -0
  6. package/dist/commands/agents/api-actions/add.d.ts +41 -0
  7. package/dist/commands/agents/api-actions/add.js +202 -0
  8. package/dist/commands/agents/api-actions/index.d.ts +28 -0
  9. package/dist/commands/agents/api-actions/index.js +77 -0
  10. package/dist/commands/agents/api-actions/remove.d.ts +14 -0
  11. package/dist/commands/agents/api-actions/remove.js +36 -0
  12. package/dist/commands/agents/avatar/default/apply.d.ts +25 -0
  13. package/dist/commands/agents/avatar/default/apply.js +56 -0
  14. package/dist/commands/agents/avatar/default/index.d.ts +18 -0
  15. package/dist/commands/agents/avatar/default/index.js +44 -0
  16. package/dist/commands/agents/avatar/default/set.d.ts +23 -0
  17. package/dist/commands/agents/avatar/default/set.js +95 -0
  18. package/dist/commands/agents/avatar/library/delete.d.ts +20 -0
  19. package/dist/commands/agents/avatar/library/delete.js +47 -0
  20. package/dist/commands/agents/avatar/library/index.d.ts +16 -0
  21. package/dist/commands/agents/avatar/library/index.js +56 -0
  22. package/dist/commands/agents/avatar/presets.d.ts +19 -0
  23. package/dist/commands/agents/avatar/presets.js +56 -0
  24. package/dist/commands/agents/categories/create.d.ts +14 -0
  25. package/dist/commands/agents/categories/create.js +46 -0
  26. package/dist/commands/agents/categories/delete.d.ts +13 -0
  27. package/dist/commands/agents/categories/delete.js +33 -0
  28. package/dist/commands/agents/categories/index.d.ts +20 -0
  29. package/dist/commands/agents/categories/index.js +82 -0
  30. package/dist/commands/agents/categories/update.d.ts +22 -0
  31. package/dist/commands/agents/categories/update.js +59 -0
  32. package/dist/commands/agents/create.d.ts +10 -0
  33. package/dist/commands/agents/create.js +57 -3
  34. package/dist/commands/agents/ensure-default.d.ts +27 -0
  35. package/dist/commands/agents/ensure-default.js +65 -0
  36. package/dist/commands/agents/mute.d.ts +16 -0
  37. package/dist/commands/agents/mute.js +38 -0
  38. package/dist/commands/agents/pin.d.ts +16 -0
  39. package/dist/commands/agents/pin.js +38 -0
  40. package/dist/commands/agents/tools/auth.d.ts +19 -0
  41. package/dist/commands/agents/tools/auth.js +41 -0
  42. package/dist/commands/agents/tools/calls.d.ts +18 -0
  43. package/dist/commands/agents/tools/calls.js +54 -0
  44. package/dist/commands/agents/tools/index.d.ts +20 -0
  45. package/dist/commands/agents/tools/index.js +77 -0
  46. package/dist/commands/agents/unmute.d.ts +16 -0
  47. package/dist/commands/agents/unmute.js +38 -0
  48. package/dist/commands/agents/unpin.d.ts +16 -0
  49. package/dist/commands/agents/unpin.js +38 -0
  50. package/dist/commands/agents/update.d.ts +12 -0
  51. package/dist/commands/agents/update.js +64 -3
  52. package/dist/commands/followups/act.d.ts +22 -0
  53. package/dist/commands/followups/act.js +102 -0
  54. package/dist/commands/followups/cancel.d.ts +17 -0
  55. package/dist/commands/followups/cancel.js +40 -0
  56. package/dist/commands/followups/checkin.d.ts +27 -0
  57. package/dist/commands/followups/checkin.js +107 -0
  58. package/dist/commands/followups/list.d.ts +24 -0
  59. package/dist/commands/followups/list.js +90 -0
  60. package/dist/commands/followups/remind.d.ts +26 -0
  61. package/dist/commands/followups/remind.js +106 -0
  62. package/dist/commands/followups/reschedule.d.ts +15 -0
  63. package/dist/commands/followups/reschedule.js +48 -0
  64. package/dist/commands/followups/resolve.d.ts +23 -0
  65. package/dist/commands/followups/resolve.js +54 -0
  66. package/dist/commands/followups/show.d.ts +12 -0
  67. package/dist/commands/followups/show.js +45 -0
  68. package/dist/commands/followups/watch.d.ts +51 -0
  69. package/dist/commands/followups/watch.js +207 -0
  70. package/dist/commands/goals/labels/add.js +19 -2
  71. package/dist/commands/goals/labels/remove.js +22 -2
  72. package/dist/commands/labels/list.d.ts +24 -1
  73. package/dist/commands/labels/list.js +62 -19
  74. package/dist/commands/memory/governance/index.d.ts +21 -0
  75. package/dist/commands/memory/governance/index.js +64 -0
  76. package/dist/commands/memory/governance/set.d.ts +24 -0
  77. package/dist/commands/memory/governance/set.js +95 -0
  78. package/dist/commands/memory/provenance.d.ts +18 -0
  79. package/dist/commands/memory/provenance.js +66 -0
  80. package/dist/commands/spaces/create.d.ts +3 -0
  81. package/dist/commands/spaces/create.js +32 -2
  82. package/dist/commands/spaces/labels/add.js +19 -2
  83. package/dist/commands/spaces/labels/remove.js +22 -2
  84. package/dist/commands/spaces/show.js +2 -0
  85. package/dist/commands/spaces/update.d.ts +5 -0
  86. package/dist/commands/spaces/update.js +41 -4
  87. package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +11 -0
  88. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +45 -0
  89. package/dist/commands/tasks/complete.d.ts +1 -0
  90. package/dist/commands/tasks/complete.js +8 -1
  91. package/dist/commands/tasks/create.d.ts +3 -0
  92. package/dist/commands/tasks/create.js +34 -2
  93. package/dist/commands/tasks/expectations/assess.d.ts +11 -0
  94. package/dist/commands/tasks/expectations/assess.js +85 -14
  95. package/dist/commands/tasks/expectations.js +19 -2
  96. package/dist/commands/tasks/labels/attach.js +20 -3
  97. package/dist/commands/tasks/labels/detach.js +21 -3
  98. package/dist/commands/tasks/lifecycle/attach.d.ts +14 -0
  99. package/dist/commands/tasks/lifecycle/attach.js +47 -0
  100. package/dist/commands/tasks/lifecycle/definitions.d.ts +11 -0
  101. package/dist/commands/tasks/lifecycle/definitions.js +40 -0
  102. package/dist/commands/tasks/lifecycle/detach.d.ts +12 -0
  103. package/dist/commands/tasks/lifecycle/detach.js +32 -0
  104. package/dist/commands/tasks/lifecycle/history.d.ts +13 -0
  105. package/dist/commands/tasks/lifecycle/history.js +38 -0
  106. package/dist/commands/tasks/lifecycle/migrate.d.ts +12 -0
  107. package/dist/commands/tasks/lifecycle/migrate.js +29 -0
  108. package/dist/commands/tasks/lifecycle/reconcile.d.ts +11 -0
  109. package/dist/commands/tasks/lifecycle/reconcile.js +27 -0
  110. package/dist/commands/tasks/lifecycle/reopen.d.ts +13 -0
  111. package/dist/commands/tasks/lifecycle/reopen.js +34 -0
  112. package/dist/commands/tasks/lifecycle/show.d.ts +11 -0
  113. package/dist/commands/tasks/lifecycle/show.js +27 -0
  114. package/dist/commands/tasks/lifecycle/start.d.ts +12 -0
  115. package/dist/commands/tasks/lifecycle/start.js +30 -0
  116. package/dist/commands/tasks/lifecycle/transition.d.ts +15 -0
  117. package/dist/commands/tasks/lifecycle/transition.js +78 -0
  118. package/dist/commands/tasks/move.d.ts +1 -0
  119. package/dist/commands/tasks/move.js +31 -0
  120. package/dist/commands/tasks/result/submit.js +11 -1
  121. package/dist/commands/tasks/review-queue.d.ts +29 -0
  122. package/dist/commands/tasks/review-queue.js +165 -0
  123. package/dist/commands/tasks/show.js +42 -0
  124. package/dist/commands/tasks/update.d.ts +1 -0
  125. package/dist/commands/tasks/update.js +47 -0
  126. package/dist/lib/agent-actions.d.ts +36 -10
  127. package/dist/lib/agent-actions.js +63 -9
  128. package/dist/lib/agent-config.d.ts +11 -0
  129. package/dist/lib/agent-config.js +85 -3
  130. package/dist/lib/agent-pin-mute.d.ts +24 -0
  131. package/dist/lib/agent-pin-mute.js +17 -0
  132. package/dist/lib/agent-visual-refs.d.ts +6 -0
  133. package/dist/lib/agent-visual-refs.js +142 -0
  134. package/dist/lib/first-party-harness-agent.js +7 -0
  135. package/dist/lib/followups.d.ts +107 -0
  136. package/dist/lib/followups.js +102 -0
  137. package/dist/lib/label-ref.d.ts +95 -0
  138. package/dist/lib/label-ref.js +163 -0
  139. package/dist/lib/label-scope.d.ts +2 -2
  140. package/dist/lib/label-scope.js +3 -3
  141. package/dist/lib/task-closure.d.ts +32 -0
  142. package/dist/lib/task-closure.js +106 -0
  143. package/dist/lib/task-lifecycle-output.d.ts +14 -0
  144. package/dist/lib/task-lifecycle-output.js +101 -0
  145. package/dist/lib/tasks.d.ts +3 -0
  146. package/dist/lib/tasks.js +31 -0
  147. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +4 -0
  148. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +4 -0
  149. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  150. package/dist/node_modules/@skrr-ai/data-provider/index.js +5079 -4546
  151. package/oclif.manifest.json +20216 -16002
  152. package/package.json +16 -4
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../base-command");
6
+ const task_closure_1 = require("../../lib/task-closure");
7
+ const web_url_1 = require("../../lib/web-url");
8
+ /**
9
+ * Everything in one Space that is waiting on a judgement, in one pass.
10
+ *
11
+ * WHY THIS EXISTS
12
+ * ---------------
13
+ * An agent fans out. One session routinely produces nine Tasks carrying twenty
14
+ * or more Expectations, and every surface for settling them was built for one
15
+ * Task at a time — so the cost of CLOSING the work grew linearly with how well
16
+ * the work was DECOMPOSED, which punishes exactly the decomposition the product
17
+ * wants. Finding what was owed meant knowing that `tasks expectations` exists
18
+ * and running it per Task, having first worked out which Tasks to run it on.
19
+ *
20
+ * This is a read. It changes nothing, it names every outstanding judgement in
21
+ * the Space with the evidence already bound to it, and it prints the exact
22
+ * command that settles each one — so a reviewer can work down a list instead of
23
+ * reconstructing it.
24
+ */
25
+ class TasksReviewQueue extends base_command_1.BaseCommand {
26
+ static description = 'Every Expectation in a Space still awaiting a judgement, with its evidence and the command that settles it';
27
+ static examples = [
28
+ '<%= config.bin %> tasks review-queue --space <space-id>',
29
+ '<%= config.bin %> tasks review-queue --space <space-id> --json',
30
+ '<%= config.bin %> tasks review-queue --space <space-id> --actionable',
31
+ ];
32
+ static flags = {
33
+ space: core_1.Flags.string({ description: 'Space id', required: true }),
34
+ actionable: core_1.Flags.boolean({
35
+ description: 'Only Tasks where a Result is already in — the ones a reviewer can act on',
36
+ }),
37
+ limit: core_1.Flags.integer({ description: 'Maximum Tasks to inspect (default 100)', default: 100 }),
38
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
39
+ };
40
+ async run() {
41
+ this.requireAuth();
42
+ const { flags } = await this.parse(TasksReviewQueue);
43
+ /**
44
+ * `response.data`, read from the TYPE rather than guessed.
45
+ *
46
+ * The first version of this read `response.tasks`, which does not exist —
47
+ * and the failure was silent in the worst possible direction: an empty array
48
+ * printed "Nothing is awaiting a judgement" for a Space with twenty-three
49
+ * unassessed Expectations. A reviewer would have believed they were done.
50
+ *
51
+ * `COMMAND_INDEX.md` warns about exactly this ("read the wrong key and you
52
+ * get an empty result rather than an error, which looks exactly like 'no
53
+ * rows matched'"). So the shape is taken from `TaskListResponse` and a
54
+ * missing envelope is an ERROR, never an empty queue: this command's whole
55
+ * value is telling someone there is nothing left, and it may only say that
56
+ * when it actually looked.
57
+ */
58
+ /*
59
+ * Follow the cursor — a truncated list is the same silent false negative
60
+ * as a misread envelope, one page later. This command's whole value is
61
+ * "there is nothing left," and a tail it never saw breaks that claim.
62
+ * `--limit` caps how many Tasks get inspected, not how many pages get read.
63
+ */
64
+ if (flags.limit < 1) {
65
+ // Inspecting zero Tasks and then saying "nothing is owed" is the exact
66
+ // unlooked-for empty answer this command refuses to give.
67
+ this.error('--limit must be at least 1.', { exit: 1 });
68
+ }
69
+ const tasks = [];
70
+ try {
71
+ let cursor;
72
+ for (;;) {
73
+ const response = await data_provider_1.dataService.listTasks({
74
+ spaceId: flags.space,
75
+ limit: Math.min(100, flags.limit - tasks.length),
76
+ ...(cursor ? { cursor } : {}),
77
+ });
78
+ if (!response || !Array.isArray(response.data)) {
79
+ this.error('The task list came back in a shape this command does not recognise, so it cannot ' +
80
+ 'say whether anything is owed. Refusing to report an empty queue.', { exit: 1 });
81
+ }
82
+ tasks.push(...response.data);
83
+ if (!response.has_more || !response.next_cursor || tasks.length >= flags.limit)
84
+ break;
85
+ cursor = response.next_cursor;
86
+ }
87
+ }
88
+ catch (error) {
89
+ this.handleApiError(error);
90
+ return;
91
+ }
92
+ /**
93
+ * Acceptance is read per Task, and a Task whose acceptance cannot be read is
94
+ * REPORTED rather than dropped. A queue that silently omitted a row would be
95
+ * worse than no queue: the reviewer would believe they had seen everything.
96
+ */
97
+ 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' });
117
+ }
118
+ }
119
+ const visible = flags.actionable ? rows.filter((row) => row.hasResult || row.error) : rows;
120
+ if (flags.json) {
121
+ this.log(JSON.stringify({ space: flags.space, tasks: visible }, null, 2));
122
+ return;
123
+ }
124
+ if (visible.length === 0) {
125
+ this.log(`Nothing in ${flags.space} is awaiting a judgement.`);
126
+ return;
127
+ }
128
+ let total = 0;
129
+ for (const row of visible) {
130
+ this.log('');
131
+ this.log(`${row.identifier} ${row.title}`);
132
+ if (row.error) {
133
+ this.log(` could not read its Expectations: ${row.error}`);
134
+ continue;
135
+ }
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.
139
+ if (!row.hasResult) {
140
+ this.log(' no Result submitted yet — settling these will not close it');
141
+ }
142
+ for (const expectation of row.awaiting) {
143
+ 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}`);
148
+ if (expectation.evidenceLevel) {
149
+ this.log(` evidence: ${expectation.evidenceLevel}${expectation.requiredEvidence ? ` (asked for ${expectation.requiredEvidence})` : ''}`);
150
+ }
151
+ }
152
+ const url = (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, { id: row.taskId });
153
+ if (url)
154
+ this.log(` ${url}`);
155
+ }
156
+ this.log('');
157
+ this.log(`${total} Expectation(s) awaiting a judgement across ${visible.length} Task(s).`);
158
+ this.log('');
159
+ this.log('Settle one: skrr tasks expectations assess <task-id> <expectation-id> \\');
160
+ this.log(' --decision met --reason "..."');
161
+ this.log('Settle a Task: skrr tasks expectations assess <task-id> --all \\');
162
+ this.log(' --decision met --reason "..."');
163
+ }
164
+ }
165
+ exports.default = TasksReviewQueue;
@@ -173,6 +173,20 @@ class TasksShow extends base_command_1.BaseCommand {
173
173
  // question and this is the answer to it.
174
174
  for (const line of taskFailureLines(t, this.config.bin))
175
175
  this.log(line);
176
+ /*
177
+ * Closing is the actor's call — a Task may be `done` with work still
178
+ * outstanding, and `resolution.closure` is where the server recorded what
179
+ * it stood on at close. Show it so a bare "done" never reads as "clean."
180
+ */
181
+ 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'}`);
189
+ }
176
190
  this.log(`Executor: ${t.executionMode ?? 'legacy / inferred'}`);
177
191
  this.log(`Priority: ${t.priority ?? '-'}`);
178
192
  this.log(`Parent: ${t.parentId ?? '-'}`);
@@ -291,6 +305,34 @@ class TasksShow extends base_command_1.BaseCommand {
291
305
  }
292
306
  }
293
307
  const ref = String(t.id ?? t._id ?? taskIdentity.id);
308
+ /**
309
+ * Where this Task stands, on the surface people already open.
310
+ *
311
+ * `tasks expectations <id>` answers this well and is reachable only if you
312
+ * already know it exists — its one pointer lives in the `--expect` help of
313
+ * `tasks create`, which you read while AUTHORING and not while closing. So
314
+ * an agent that submitted a Result had no cheap way to learn the Task was
315
+ * still owed, and reasonably concluded from the CLI's own output that it was
316
+ * not. Say it here, where the question is actually asked.
317
+ *
318
+ * Read, not recomputed, and never fatal: a Task page must still render when
319
+ * the acceptance read fails, and it must not invent a state it could not
320
+ * fetch.
321
+ */
322
+ try {
323
+ const acceptance = await data_provider_1.dataService.getTaskAcceptance(ref);
324
+ const gating = acceptance.expectations.filter((e) => e.gating);
325
+ if (gating.length > 0) {
326
+ const settled = gating.filter((e) => e.met).length;
327
+ this.log('');
328
+ this.log(`Expectations: ${settled} of ${gating.length} settled · overall: ${acceptance.state}` +
329
+ (settled < gating.length ? ` — skrr tasks expectations ${ref}` : ''));
330
+ }
331
+ }
332
+ catch {
333
+ // Silent: a Task this caller can VIEW but whose acceptance they cannot
334
+ // read is a permissions shape, not something to shout about mid-page.
335
+ }
294
336
  this.log('');
295
337
  this.log(`What the agent produced: skrr tasks output ${ref}`);
296
338
  if (t.conversationId) {
@@ -10,6 +10,7 @@ export default class TasksUpdate extends BaseCommand {
10
10
  task: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
11
  'from-json': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
12
  title: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
14
  expect: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
14
15
  description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
15
16
  'description-format': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -42,6 +42,9 @@ class TasksUpdate extends base_command_1.BaseCommand {
42
42
  description: 'Path to a JSON file (or "-" for stdin) to use as the patch body; may include taskId',
43
43
  }),
44
44
  title: core_1.Flags.string({ description: 'New title' }),
45
+ yes: core_1.Flags.boolean({
46
+ description: 'Skip the confirmation when changing Expectations revokes assessments already recorded',
47
+ }),
45
48
  expect: core_1.Flags.string({
46
49
  multiple: true,
47
50
  description: tasks_1.EXPECT_FLAG_HELP +
@@ -227,6 +230,50 @@ class TasksUpdate extends base_command_1.BaseCommand {
227
230
  }
228
231
  }
229
232
  }
233
+ /**
234
+ * Say what the edit costs BEFORE making it.
235
+ *
236
+ * `expectations` is a TaskSpecFence field, and assessments are matched by
237
+ * exact `specVersion` — so rewriting the list bumps the version and every
238
+ * judgement already recorded against the old one stops counting. Assess
239
+ * five, fix a typo in the sixth, and the five are gone.
240
+ *
241
+ * Disclosure, not a block: the edit is legitimate and must stay possible.
242
+ * The point is only that its price should not be discovered afterwards, by
243
+ * a Task that will not close. `--yes` states the intent in the command line
244
+ * where it can be reviewed, which is also what a non-TTY caller needs.
245
+ */
246
+ /*
247
+ * The trigger is the PAYLOAD, not the flag that produced it.
248
+ *
249
+ * The first version guarded only `--expect`, so `--from-json
250
+ * '{"expectations": [...]}'` — exactly what an agent batching edits uses —
251
+ * rewrote the list and revoked the assessments with no warning at all.
252
+ * Same write, same price: the disclosure belongs on the effective change.
253
+ */
254
+ if (expectationsPatch !== undefined || body.expectations !== undefined) {
255
+ let settledCount = 0;
256
+ try {
257
+ const acceptance = await data_provider_1.dataService.getTaskAcceptance(taskIdentity.id);
258
+ settledCount = acceptance.expectations.filter((e) => e.assessmentId).length;
259
+ }
260
+ catch {
261
+ // An unreadable acceptance is not a reason to refuse the edit. Warn
262
+ // without a number rather than inventing one or blocking the write.
263
+ settledCount = -1;
264
+ }
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} ` +
269
+ 'will stop counting and must be recorded again.');
270
+ const ok = await this.confirm('Continue? [y/N] ');
271
+ if (!ok) {
272
+ this.log('No change made.');
273
+ return;
274
+ }
275
+ }
276
+ }
230
277
  const payload = (0, prompt_1.mergeDefined)((0, task_resolver_1.stripTaskIdentityFields)(body), {
231
278
  title: flags.title,
232
279
  expectations: expectationsPatch === null ? [] : (expectationsPatch ?? undefined),
@@ -1,4 +1,4 @@
1
- import type { Agent, AgentAction, BuiltinActionConfig, BuiltinActionType, BuiltinAgentAction, EmbeddedActionRef, PromptAgentAction } from '@skrr-ai/data-provider';
1
+ import type { Agent, AgentAction, AgentActionOutcome, AgentActionScope, BuiltinActionConfig, BuiltinActionType, BuiltinAgentAction, EmbeddedActionRef, PromptAgentAction } from '@skrr-ai/data-provider';
2
2
  export declare const BUILTIN_ACTION_TYPES: readonly ["slack_send_message", "slack_tool", "group_chat_message", "space_create_task", "space_post_update", "task_create", "task_mark_done", "task_assign", "goal_create", "goal_mark_done", "smart_schedule_plan", "smart_schedule_delta_watch"];
3
3
  export declare const ACTION_TARGET_TYPES: readonly ["group-chats", "spaces", "tasks", "goals", "slack-channels", "slack-users"];
4
4
  export type ActionTargetType = (typeof ACTION_TARGET_TYPES)[number];
@@ -7,28 +7,54 @@ export declare function fetchAgentForActionEdit(agentId: string): Promise<Agent>
7
7
  export declare function getAgentActions(agent: Agent): AgentAction[];
8
8
  export declare function findAgentAction(agent: Agent, actionId: string): AgentAction | undefined;
9
9
  export declare function updateAgentActions(agentId: string, actions: AgentAction[]): Promise<Agent>;
10
- export declare function makePromptAction(input: {
10
+ /**
11
+ * Fields every action shape carries regardless of kind.
12
+ *
13
+ * `scope` and `outcome` are listed HERE rather than threaded through each
14
+ * factory's own parameter list, because the previous version enumerated the
15
+ * fields it copied and simply omitted `scope` — so a space-scoped action was
16
+ * unauthorable from the CLI even via `--from-json`, and the omission was
17
+ * invisible (the row wrote fine, it was just offered everywhere).
18
+ */
19
+ export type AgentActionCommonInput = {
11
20
  title: string;
12
21
  description?: string;
13
- prompt: string;
14
22
  order: number;
15
23
  enabled: boolean;
16
24
  id?: string;
17
25
  sourceListingId?: string;
26
+ scope?: AgentActionScope;
27
+ outcome?: AgentActionOutcome;
28
+ };
29
+ export declare function makePromptAction(input: AgentActionCommonInput & {
30
+ prompt: string;
18
31
  /** Structured built-in actions embedded in the prompt — the chip source of truth. */
19
32
  embeddedActions?: EmbeddedActionRef[];
20
33
  }): PromptAgentAction;
21
- export declare function makeBuiltinAction(input: {
22
- title: string;
23
- description?: string;
34
+ export declare function makeBuiltinAction(input: AgentActionCommonInput & {
24
35
  builtinType: BuiltinActionType;
25
36
  config: BuiltinActionConfig | Record<string, unknown>;
26
37
  prompt?: string;
27
- order: number;
28
- enabled: boolean;
29
- id?: string;
30
- sourceListingId?: string;
31
38
  }): BuiltinAgentAction;
39
+ export declare const AGENT_ACTION_SCOPE_CHOICES: readonly ["general", "space", "label"];
40
+ export declare const AGENT_ACTION_OUTCOME_CHOICES: readonly ["ephemeral", "durable"];
41
+ export type AgentActionScopeFlags = {
42
+ scope?: string;
43
+ space?: string;
44
+ 'space-title'?: string;
45
+ label?: string[];
46
+ };
47
+ /**
48
+ * Turn the scope flags into a stored scope, or `undefined` when the caller said
49
+ * nothing about scope (so an update leaves the existing one alone).
50
+ *
51
+ * `--space <id>` and `--label <id>` imply their kind, because typing
52
+ * `--scope space --space s1` twice is the kind of ceremony people skip and then
53
+ * wonder why the action is everywhere. An explicit `--scope general` clears it.
54
+ */
55
+ export declare function resolveAgentActionScopeFlags(flags: AgentActionScopeFlags): AgentActionScope | undefined;
56
+ /** Human-readable one-liner for a scope, used by list/show renderers. */
57
+ export declare function describeAgentActionScope(scope?: AgentActionScope | null): string;
32
58
  export declare function nextActionOrder(actions: AgentAction[]): number;
33
59
  export declare function actionKind(action: AgentAction): 'prompt' | 'builtin';
34
60
  export declare function renderActions(actions: AgentAction[], log: (line: string) => void): void;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ACTION_TARGET_TYPES = exports.BUILTIN_ACTION_TYPES = void 0;
3
+ exports.AGENT_ACTION_OUTCOME_CHOICES = exports.AGENT_ACTION_SCOPE_CHOICES = exports.ACTION_TARGET_TYPES = exports.BUILTIN_ACTION_TYPES = void 0;
4
4
  exports.parseJsonObjectFlag = parseJsonObjectFlag;
5
5
  exports.fetchAgentForActionEdit = fetchAgentForActionEdit;
6
6
  exports.getAgentActions = getAgentActions;
@@ -8,6 +8,8 @@ exports.findAgentAction = findAgentAction;
8
8
  exports.updateAgentActions = updateAgentActions;
9
9
  exports.makePromptAction = makePromptAction;
10
10
  exports.makeBuiltinAction = makeBuiltinAction;
11
+ exports.resolveAgentActionScopeFlags = resolveAgentActionScopeFlags;
12
+ exports.describeAgentActionScope = describeAgentActionScope;
11
13
  exports.nextActionOrder = nextActionOrder;
12
14
  exports.actionKind = actionKind;
13
15
  exports.renderActions = renderActions;
@@ -70,34 +72,77 @@ async function updateAgentActions(agentId, actions) {
70
72
  data: { agentActions: actions },
71
73
  });
72
74
  }
73
- function makePromptAction(input) {
75
+ function commonActionFields(input) {
74
76
  return {
75
77
  id: input.id ?? (0, node_crypto_1.randomUUID)(),
76
78
  title: input.title,
77
79
  description: input.description,
78
80
  order: input.order,
79
81
  enabled: input.enabled,
82
+ ...(input.scope ? { scope: input.scope } : {}),
83
+ ...(input.outcome ? { outcome: input.outcome } : {}),
84
+ ...(input.sourceListingId ? { sourceListingId: input.sourceListingId } : {}),
85
+ };
86
+ }
87
+ function makePromptAction(input) {
88
+ return {
89
+ ...commonActionFields(input),
80
90
  prompt: input.prompt,
81
91
  ...(input.embeddedActions && input.embeddedActions.length > 0
82
92
  ? { embeddedActions: input.embeddedActions }
83
93
  : {}),
84
- ...(input.sourceListingId ? { sourceListingId: input.sourceListingId } : {}),
85
94
  };
86
95
  }
87
96
  function makeBuiltinAction(input) {
88
97
  return {
89
- id: input.id ?? (0, node_crypto_1.randomUUID)(),
98
+ ...commonActionFields(input),
90
99
  kind: 'builtin',
91
- title: input.title,
92
- description: input.description,
93
- order: input.order,
94
- enabled: input.enabled,
95
100
  builtinType: input.builtinType,
96
101
  config: input.config,
97
102
  ...(input.prompt ? { prompt: input.prompt } : {}),
98
- ...(input.sourceListingId ? { sourceListingId: input.sourceListingId } : {}),
99
103
  };
100
104
  }
105
+ /* ── scope + outcome flags (shared by `create` and `update`) ─────────────── */
106
+ exports.AGENT_ACTION_SCOPE_CHOICES = ['general', 'space', 'label'];
107
+ exports.AGENT_ACTION_OUTCOME_CHOICES = ['ephemeral', 'durable'];
108
+ /**
109
+ * Turn the scope flags into a stored scope, or `undefined` when the caller said
110
+ * nothing about scope (so an update leaves the existing one alone).
111
+ *
112
+ * `--space <id>` and `--label <id>` imply their kind, because typing
113
+ * `--scope space --space s1` twice is the kind of ceremony people skip and then
114
+ * wonder why the action is everywhere. An explicit `--scope general` clears it.
115
+ */
116
+ function resolveAgentActionScopeFlags(flags) {
117
+ const labelIds = (flags.label ?? []).map((value) => value.trim()).filter(Boolean);
118
+ const spaceId = flags.space?.trim();
119
+ const declared = flags.scope?.trim();
120
+ const kind = declared ?? (spaceId ? 'space' : labelIds.length ? 'label' : undefined);
121
+ if (!kind)
122
+ return undefined;
123
+ if (kind === 'general')
124
+ return { kind: 'general' };
125
+ if (kind === 'space') {
126
+ if (!spaceId)
127
+ throw new Error('--scope space requires --space <space-id>.');
128
+ const spaceTitle = flags['space-title']?.trim();
129
+ return { kind: 'space', spaceId, ...(spaceTitle ? { spaceTitle } : {}) };
130
+ }
131
+ if (kind === 'label') {
132
+ if (!labelIds.length)
133
+ throw new Error('--scope label requires at least one --label <label-id>.');
134
+ return { kind: 'label', labelIds };
135
+ }
136
+ throw new Error(`--scope must be one of ${exports.AGENT_ACTION_SCOPE_CHOICES.join(', ')}.`);
137
+ }
138
+ /** Human-readable one-liner for a scope, used by list/show renderers. */
139
+ function describeAgentActionScope(scope) {
140
+ if (scope?.kind === 'space')
141
+ return `space:${scope.spaceId}`;
142
+ if (scope?.kind === 'label')
143
+ return `label:${scope.labelIds.join('|')}`;
144
+ return 'general';
145
+ }
101
146
  function nextActionOrder(actions) {
102
147
  if (actions.length === 0)
103
148
  return 0;
@@ -122,12 +167,19 @@ function renderActions(actions, log) {
122
167
  target: action.kind === 'builtin'
123
168
  ? action.builtinType
124
169
  : `${(action.prompt || '').trim().length} chars`,
170
+ // Where an action is offered and what it leaves behind decide whether
171
+ // a person ever sees it fire. A listing that omits both makes a
172
+ // mis-scoped action indistinguishable from a working one.
173
+ scope: describeAgentActionScope(action.scope),
174
+ outcome: action.outcome ?? 'ephemeral',
125
175
  order: String(action.order),
126
176
  })), [
127
177
  { key: 'id', header: 'ID', verbatim: true },
128
178
  { key: 'title', header: 'Title', maxWidth: 30 },
129
179
  { key: 'kind', header: 'Kind' },
130
180
  { key: 'target', header: 'Target', maxWidth: 24 },
181
+ { key: 'scope', header: 'Scope', maxWidth: 24 },
182
+ { key: 'outcome', header: 'Outcome' },
131
183
  { key: 'enabled', header: 'On' },
132
184
  { key: 'order', header: 'Order' },
133
185
  ], log);
@@ -137,6 +189,8 @@ function renderAction(action, log) {
137
189
  log(`Title: ${action.title}`);
138
190
  log(`Kind: ${actionKind(action)}`);
139
191
  log(`Enabled: ${action.enabled === false ? 'no' : 'yes'}`);
192
+ log(`Scope: ${describeAgentActionScope(action.scope)}`);
193
+ log(`Outcome: ${action.outcome ?? 'ephemeral'}`);
140
194
  log(`Order: ${action.order}`);
141
195
  if (action.description) {
142
196
  log(`Description: ${action.description}`);
@@ -1,2 +1,13 @@
1
+ export declare const COMMITMENT_RESPONSIBILITY_LEVELS: readonly ["reasoning_only", "full_ownership"];
2
+ export declare const REASONING_TOOL_ESCAPES: readonly ["bash", "browser"];
3
+ export declare const RELEASE_INSTALL_CHANNELS: readonly ["latest", "manual"];
4
+ /**
5
+ * `09:00-18:00` or `09:00-18:00@America/Los_Angeles`, or a JSON object.
6
+ *
7
+ * The stored shape is `{start, end, timezone}` with `start`/`end` REQUIRED, so
8
+ * the shorthand always supplies both and never a half-open window the schema
9
+ * would reject at write time with a message about a subdocument.
10
+ */
11
+ export declare function parseWorkingHoursFlag(value: string): Record<string, unknown>;
1
12
  /** Apply the structured agent fields exposed by the web config editor. */
2
13
  export declare function mergeAgentConfigFlags(body: Record<string, unknown>, flags: Record<string, unknown>): Record<string, unknown>;
@@ -1,7 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RELEASE_INSTALL_CHANNELS = exports.REASONING_TOOL_ESCAPES = exports.COMMITMENT_RESPONSIBILITY_LEVELS = void 0;
4
+ exports.parseWorkingHoursFlag = parseWorkingHoursFlag;
3
5
  exports.mergeAgentConfigFlags = mergeAgentConfigFlags;
4
6
  const prompt_1 = require("./prompt");
7
+ const agent_visual_refs_1 = require("./agent-visual-refs");
8
+ exports.COMMITMENT_RESPONSIBILITY_LEVELS = ['reasoning_only', 'full_ownership'];
9
+ exports.REASONING_TOOL_ESCAPES = ['bash', 'browser'];
10
+ exports.RELEASE_INSTALL_CHANNELS = ['latest', 'manual'];
11
+ /**
12
+ * `09:00-18:00` or `09:00-18:00@America/Los_Angeles`, or a JSON object.
13
+ *
14
+ * The stored shape is `{start, end, timezone}` with `start`/`end` REQUIRED, so
15
+ * the shorthand always supplies both and never a half-open window the schema
16
+ * would reject at write time with a message about a subdocument.
17
+ */
18
+ function parseWorkingHoursFlag(value) {
19
+ const trimmed = value.trim();
20
+ if (trimmed.startsWith('{'))
21
+ return JSON.parse(trimmed);
22
+ const [window, timezone] = trimmed.split('@');
23
+ const match = /^([0-2]\d:[0-5]\d)-([0-2]\d:[0-5]\d)$/.exec(window ?? '');
24
+ if (!match) {
25
+ throw new Error(`--todo-working-hours: cannot read "${value}". ` +
26
+ 'Write HH:MM-HH:MM, optionally @<IANA timezone> — for example ' +
27
+ '09:00-18:00@America/Los_Angeles. A JSON object is also accepted.');
28
+ }
29
+ return timezone
30
+ ? { start: match[1], end: match[2], timezone }
31
+ : { start: match[1], end: match[2] };
32
+ }
5
33
  /** Apply the structured agent fields exposed by the web config editor. */
6
34
  function mergeAgentConfigFlags(body, flags) {
7
35
  const json = {
@@ -22,10 +50,21 @@ function mergeAgentConfigFlags(body, flags) {
22
50
  if (value !== undefined)
23
51
  parsed[field] = JSON.parse(String(value));
24
52
  }
53
+ /**
54
+ * `avatar` and `sceneBackground` are DISCRIMINATED UNIONS server-side, not
55
+ * strings — see `agent-visual-refs.ts`. `--scene-background` used to sit in
56
+ * the pass-through map below, which made it a flag with no value that could
57
+ * pass validation. Both go through the shared parser.
58
+ */
59
+ if (flags['scene-background'] !== undefined) {
60
+ parsed.sceneBackground = (0, agent_visual_refs_1.parseSceneBackgroundFlag)(String(flags['scene-background']));
61
+ }
62
+ if (flags.avatar !== undefined) {
63
+ parsed.avatar = (0, agent_visual_refs_1.parseAvatarRefFlag)(String(flags.avatar));
64
+ }
25
65
  const direct = {
26
66
  'network-mode': 'network_mode',
27
67
  'character-3d-id': 'character3DId',
28
- 'scene-background': 'sceneBackground',
29
68
  'tone-mode': 'toneMode',
30
69
  'tone-id': 'toneId',
31
70
  'tone-notes': 'toneNotes',
@@ -46,8 +85,51 @@ function mergeAgentConfigFlags(body, flags) {
46
85
  if (flags[flag] !== undefined)
47
86
  parsed[field] = flags[flag];
48
87
  }
49
- if (flags['recursion-limit'] !== undefined)
50
- parsed.recursion_limit = flags['recursion-limit'];
88
+ const integers = {
89
+ 'recursion-limit': 'recursion_limit',
90
+ 'max-turns': 'maxTurns',
91
+ };
92
+ for (const [flag, field] of Object.entries(integers)) {
93
+ if (flags[flag] !== undefined)
94
+ parsed[field] = flags[flag];
95
+ }
96
+ /* Fields the web config editor has always written and no flag reached. */
97
+ if (flags['conversation-starter'] !== undefined) {
98
+ parsed.conversation_starters = flags['conversation-starter'];
99
+ }
100
+ if (flags['todo-working-hours'] !== undefined) {
101
+ parsed.todoWorkingHours = parseWorkingHoursFlag(String(flags['todo-working-hours']));
102
+ }
103
+ if (flags['reasoning-tool-escape'] !== undefined) {
104
+ parsed.reasoningToolEscapes = flags['reasoning-tool-escape'];
105
+ }
106
+ if (flags['commitment-responsibility'] !== undefined) {
107
+ parsed.commitmentResponsibilityDefault = flags['commitment-responsibility'];
108
+ }
109
+ if (flags['commitment-responsibility-ceiling'] !== undefined) {
110
+ parsed.commitmentResponsibilityCeiling = flags['commitment-responsibility-ceiling'];
111
+ }
112
+ if (flags['tool-resources'] !== undefined) {
113
+ parsed.tool_resources = JSON.parse(String(flags['tool-resources']));
114
+ }
115
+ if (flags['project-id'] !== undefined)
116
+ parsed.projectIds = flags['project-id'];
117
+ if (flags['remove-project-id'] !== undefined) {
118
+ parsed.removeProjectIds = flags['remove-project-id'];
119
+ }
120
+ // Two flags, one stored object. Sent whole so a caller who sets only the
121
+ // channel does not silently clear `allowAutoMinorUpdates` — the server
122
+ // replaces the subdocument rather than merging into it.
123
+ if (flags['install-channel'] !== undefined || flags['auto-minor-updates'] !== undefined) {
124
+ const releasePolicy = {};
125
+ if (flags['install-channel'] !== undefined) {
126
+ releasePolicy.defaultInstallChannel = flags['install-channel'];
127
+ }
128
+ if (flags['auto-minor-updates'] !== undefined) {
129
+ releasePolicy.allowAutoMinorUpdates = flags['auto-minor-updates'];
130
+ }
131
+ parsed.releasePolicy = releasePolicy;
132
+ }
51
133
  // Cross-Space Task execution ceiling. `0` means unlimited, matching the
52
134
  // Space limits' convention so a user learns one spelling of "no cap".
53
135
  if (flags['max-concurrent-task-executions'] !== undefined) {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `pin` / `unpin` / `mute` / `unmute` on top of a TOGGLE endpoint.
3
+ *
4
+ * `PATCH /api/user/agent-preferences/:agentId/pin` flips the bit; it does not
5
+ * take a desired state. A CLI that exposed the flip directly would make
6
+ * `skrr agents pin X` mean "pin it, unless it already was, in which case unpin
7
+ * it" — fine for a button a human clicks while looking at the result, wrong for
8
+ * a command in a script, where the outcome would depend on state the script
9
+ * never read.
10
+ *
11
+ * So the desired state is read first and the toggle is sent only when it
12
+ * differs. That makes each command idempotent and its name true. The read is not
13
+ * a lock: two concurrent invocations can still race, which is a real limit of a
14
+ * toggle endpoint and not something the CLI can fix from out here — the
15
+ * returned state is always what the server says, never what was asked for.
16
+ */
17
+ export type AgentFlag = 'pin' | 'mute';
18
+ export interface FlagChange {
19
+ /** The value the server reports AFTER this call. */
20
+ value: boolean;
21
+ /** False when the flag already held the desired value and nothing was sent. */
22
+ changed: boolean;
23
+ }
24
+ export declare function setAgentFlag(agentId: string, flag: AgentFlag, desired: boolean): Promise<FlagChange>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setAgentFlag = setAgentFlag;
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ async function setAgentFlag(agentId, flag, desired) {
6
+ const current = await data_provider_1.dataService.getAgentPreferences(agentId);
7
+ const held = flag === 'pin' ? current.preferences?.isPinned : current.preferences?.isMuted;
8
+ if (Boolean(held) === desired) {
9
+ return { value: desired, changed: false };
10
+ }
11
+ if (flag === 'pin') {
12
+ const result = await data_provider_1.dataService.toggleAgentPin(agentId);
13
+ return { value: result.isPinned, changed: true };
14
+ }
15
+ const result = await data_provider_1.dataService.toggleAgentMute(agentId);
16
+ return { value: result.isMuted, changed: true };
17
+ }