@skrr-ai/cli 0.1.109 → 0.1.110

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 (63) hide show
  1. package/dist/base-command.d.ts +15 -0
  2. package/dist/base-command.js +64 -17
  3. package/dist/commands/agents/triggers/create.js +1 -1
  4. package/dist/commands/agents/triggers/update.js +1 -1
  5. package/dist/commands/agents/update.d.ts +11 -0
  6. package/dist/commands/agents/update.js +58 -2
  7. package/dist/commands/daemon/install.js +17 -1
  8. package/dist/commands/daemon/login.js +14 -0
  9. package/dist/commands/harnesses/prune.d.ts +26 -0
  10. package/dist/commands/harnesses/prune.js +172 -0
  11. package/dist/commands/login.js +11 -0
  12. package/dist/commands/spaces/workflows/start.js +3 -1
  13. package/dist/commands/spaces/workflows/validate.js +12 -2
  14. package/dist/commands/tasks/activity.js +11 -1
  15. package/dist/commands/tasks/list.js +6 -2
  16. package/dist/commands/tasks/rearm-recurrence.js +22 -3
  17. package/dist/commands/tasks/show.d.ts +8 -0
  18. package/dist/commands/tasks/show.js +64 -32
  19. package/dist/commands/tasks/unschedule.js +5 -1
  20. package/dist/commands/tasks/update.js +11 -1
  21. package/dist/commands/triggers/audit.js +12 -3
  22. package/dist/commands/triggers/create.js +1 -1
  23. package/dist/commands/triggers/fire-detail.d.ts +7 -0
  24. package/dist/commands/triggers/fire-detail.js +30 -12
  25. package/dist/commands/triggers/fire.js +13 -3
  26. package/dist/commands/triggers/parse-intent.js +1 -1
  27. package/dist/commands/triggers/update.js +1 -1
  28. package/dist/commands/workspaces/grants.js +1 -4
  29. package/dist/commands/workspaces/members.js +6 -6
  30. package/dist/lib/commitment-product.js +5 -2
  31. package/dist/lib/commitments.d.ts +2 -1
  32. package/dist/lib/commitments.js +40 -40
  33. package/dist/lib/daemon-machine-identity-copy.d.ts +14 -0
  34. package/dist/lib/daemon-machine-identity-copy.js +27 -0
  35. package/dist/lib/daemon-machine-identity.d.ts +27 -0
  36. package/dist/lib/daemon-machine-identity.js +61 -0
  37. package/dist/lib/daemonHandoff.d.ts +19 -1
  38. package/dist/lib/daemonHandoff.js +59 -0
  39. package/dist/lib/harnesses.d.ts +71 -0
  40. package/dist/lib/harnesses.js +70 -0
  41. package/dist/lib/list-output.d.ts +1 -1
  42. package/dist/lib/list-output.js +2 -0
  43. package/dist/lib/profile-machine-identity-guard.js +11 -1
  44. package/dist/lib/task-economics.d.ts +10 -0
  45. package/dist/lib/task-economics.js +24 -2
  46. package/dist/lib/triggers.d.ts +11 -0
  47. package/dist/lib/triggers.js +43 -1
  48. package/dist/lib/workflow-run-view.d.ts +4 -0
  49. package/dist/lib/workflow-run-view.js +5 -0
  50. package/dist/lib/workspaces.d.ts +24 -7
  51. package/dist/lib/workspaces.js +14 -2
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +5 -3
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +60 -0
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +130 -6
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +60 -0
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +128 -6
  60. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  61. package/dist/node_modules/@skrr-ai/data-provider/index.js +4003 -3968
  62. package/oclif.manifest.json +99 -9
  63. package/package.json +10 -1
@@ -30,12 +30,22 @@ class SpacesWorkflowsValidate extends base_command_1.BaseCommand {
30
30
  this.handleApiError(error);
31
31
  return;
32
32
  }
33
+ // The one rule (OSK-16493): an error-severity issue means invalid, whatever `valid` says. A
34
+ // server that predates the shared gate answered `valid: true` beside such an issue, and the
35
+ // text view then called the draft ready to publish and hid the issue.
36
+ const blocking = (0, data_provider_1.blockingWorkflowIssues)(result.issues ?? []);
37
+ const valid = Boolean(result.valid) && blocking.length === 0;
33
38
  if (flags.json) {
34
39
  this.log(JSON.stringify((0, space_output_1.withSpacePerspectiveUrl)(this.cliConfig.baseURL, args.spaceId, 'workflows', result), null, 2));
40
+ // One answer, two renderings: an invalid draft fails the command in either mode, so a
41
+ // script reading --json can trust the exit status exactly as a person reading text can
42
+ // (OSK-16473).
43
+ if (!valid)
44
+ this.exit(1);
35
45
  return;
36
46
  }
37
47
  const advisories = (result.issues ?? []).filter((issue) => issue.severity !== 'error');
38
- if (result.valid) {
48
+ if (valid) {
39
49
  this.log('Workflow draft is valid and ready to publish.');
40
50
  if (advisories.length) {
41
51
  this.log(`${advisories.length} warning${advisories.length === 1 ? '' : 's'} (publishing is allowed):`);
@@ -44,7 +54,7 @@ class SpacesWorkflowsValidate extends base_command_1.BaseCommand {
44
54
  }
45
55
  return;
46
56
  }
47
- this.log(`Workflow draft is invalid${result.code ? ` (${result.code})` : ''}: ${result.message ?? 'Unknown validation error'}`);
57
+ this.log(`Workflow draft is invalid${result.code ? ` (${result.code})` : ''}: ${result.message ?? blocking[0]?.message ?? 'Unknown validation error'}`);
48
58
  // Every problem, not only the first one publish would stop at.
49
59
  const all = result.issues ?? [];
50
60
  if (all.length) {
@@ -8,6 +8,16 @@ const base_command_1 = require("../../base-command");
8
8
  const format_1 = require("../../lib/format");
9
9
  const task_economics_1 = require("../../lib/task-economics");
10
10
  const task_legacy_1 = require("../../lib/task-legacy");
11
+ /**
12
+ * A system-written embedded entry names no person (OSK-16475): it carries
13
+ * `details.actorKind: 'system'` instead of a `userId`, so the actor is the service.
14
+ */
15
+ function systemEntryActor(details) {
16
+ const value = details;
17
+ if (value?.actorKind !== 'system')
18
+ return undefined;
19
+ return `system:${value.actorService ?? '-'}`;
20
+ }
11
21
  function activityRow(entry) {
12
22
  const actor = entry.actor;
13
23
  const actorText = actor
@@ -18,7 +28,7 @@ function activityRow(entry) {
18
28
  : actor.kind === 'integration'
19
29
  ? `integration:${actor.provider}`
20
30
  : `system:${actor.service}`
21
- : (entry.userId ?? entry.actorId);
31
+ : (entry.userId ?? entry.actorId ?? systemEntryActor(entry.details));
22
32
  const type = entry.action ?? entry.event ?? entry.type;
23
33
  return {
24
34
  time: (0, format_1.localTime)(entry.occurredAt ?? entry.timestamp ?? entry.createdAt),
@@ -588,7 +588,9 @@ class TasksList extends base_command_1.BaseCommand {
588
588
  Array.isArray(response.data)) {
589
589
  response = {
590
590
  ...response,
591
- data: (0, data_provider_1.rankEconomicItems)(response.data),
591
+ // Each row carries the `economicsRank` it was sorted by, so `--json` shows the same SCORE
592
+ // as the table, computed once in the shared ranking (OSK-16476).
593
+ data: (0, data_provider_1.annotateEconomicRank)(response.data),
592
594
  rank_applied: { key: 'economics', scope: 'fetched_page' },
593
595
  };
594
596
  }
@@ -693,7 +695,9 @@ class TasksList extends base_command_1.BaseCommand {
693
695
  : 'Unknown',
694
696
  effort: economics?.effortHours !== undefined ? `${economics.effortHours}h` : 'Unknown',
695
697
  risk: economics?.risk ?? 'Unknown',
696
- score: (0, task_economics_1.economicsScoreText)(economics),
698
+ score: t.economicsRank
699
+ ? (0, task_economics_1.scoreText)(t.economicsRank.score)
700
+ : (0, task_economics_1.economicsScoreText)(economics),
697
701
  basis: economics?.basis && Object.prototype.hasOwnProperty.call(basis, economics.basis)
698
702
  ? basis[economics.basis]
699
703
  : 'Unknown',
@@ -60,9 +60,28 @@ class TasksRearmRecurrence extends base_command_1.BaseCommand {
60
60
  const from = finished.identifier || finished.id || taskRef;
61
61
  const next = result.successor.identifier || result.successor.id;
62
62
  const when = result.successor.scheduledFor ? ` for ${result.successor.scheduledFor}` : '';
63
- this.log(result.alreadyArmed
64
- ? `The series from ${from} was already armed: next occurrence ${next}${when}. The failure marker is cleared.`
65
- : `Re-armed the series from ${from}: next occurrence ${next} is scheduled${when}.`);
63
+ // The server's sentence IS the answer: it is derived with the JSON fields, so the two can
64
+ // never disagree (round 3 of OSK-15321 found them disagreeing). The fallback below is only
65
+ // for an older server that sends no `message`.
66
+ if (typeof result.message === 'string' && result.message) {
67
+ this.log(result.message);
68
+ return;
69
+ }
70
+ // Never call a successor with no schedule "armed": it will not fire (#4262 verification).
71
+ let line;
72
+ if (!result.successor.scheduledFor) {
73
+ line = `The next occurrence ${next} of the series from ${from} exists but has no schedule, so nothing will fire it on its own; start it by hand or give it a time with \`${this.config.bin} tasks schedule ${next} --at <ISO time>\`.`;
74
+ }
75
+ else if (result.rescheduled) {
76
+ line = `The next occurrence ${next} existed but had no schedule; it is now scheduled${when}. The failure marker is cleared.`;
77
+ }
78
+ else if (result.alreadyArmed) {
79
+ line = `The series from ${from} was already armed: next occurrence ${next}${when}. The failure marker is cleared.`;
80
+ }
81
+ else {
82
+ line = `Re-armed the series from ${from}: next occurrence ${next} is scheduled${when}.`;
83
+ }
84
+ this.log(line);
66
85
  }
67
86
  }
68
87
  exports.default = TasksRearmRecurrence;
@@ -69,6 +69,14 @@ export declare function labelsLine(labelIds?: string[], nameById?: Map<string, s
69
69
  * reassigning the task changes only the implied kind.
70
70
  */
71
71
  export declare function taskExecutorLabel(t: Record<string, unknown>, bin?: string): string;
72
+ /**
73
+ * The `Resolution:` lines for any resolved Task: how it ended, the reason the
74
+ * closer recorded, who closed it, and what the close stood on. The reason used to
75
+ * be JSON-only, so text showed "Task workflow type is cancelled" for every
76
+ * cancel, and a Task its Workflow step cancelled could not be told from one a
77
+ * person cancelled by hand (OSK-16475). Silent for an open Task.
78
+ */
79
+ export declare function resolutionLines(t: Record<string, unknown>, names?: Map<string, string>): string[];
72
80
  /**
73
81
  * The `Recurrence:` line for a series whose next occurrence was never armed.
74
82
  * Silent otherwise: a healthy series has nothing to say here.
@@ -6,6 +6,7 @@ exports.completionLines = completionLines;
6
6
  exports.reviewPolicyLine = reviewPolicyLine;
7
7
  exports.labelsLine = labelsLine;
8
8
  exports.taskExecutorLabel = taskExecutorLabel;
9
+ exports.resolutionLines = resolutionLines;
9
10
  exports.recurrenceArmFailureLines = recurrenceArmFailureLines;
10
11
  exports.taskFailureLines = taskFailureLines;
11
12
  const core_1 = require("@oclif/core");
@@ -151,6 +152,43 @@ function taskExecutorLabel(t, bin = 'skrr') {
151
152
  return '-';
152
153
  }
153
154
  }
155
+ function resolverLabel(by, names) {
156
+ if (!by || typeof by !== 'object' || !by.kind)
157
+ return null;
158
+ const kind = String(by.kind);
159
+ if (kind === 'human')
160
+ return `person ${(0, principal_display_1.displayPrincipal)(by.userId, names)}`;
161
+ if (kind === 'agent')
162
+ return `agent ${(0, principal_display_1.displayPrincipal)(by.agentId, names)}`;
163
+ if (kind === 'system')
164
+ return `system${by.service ? ` (${String(by.service)})` : ''}`;
165
+ if (kind === 'integration')
166
+ return `integration${by.provider ? ` (${String(by.provider)})` : ''}`;
167
+ return kind;
168
+ }
169
+ /**
170
+ * The `Resolution:` lines for any resolved Task: how it ended, the reason the
171
+ * closer recorded, who closed it, and what the close stood on. The reason used to
172
+ * be JSON-only, so text showed "Task workflow type is cancelled" for every
173
+ * cancel, and a Task its Workflow step cancelled could not be told from one a
174
+ * person cancelled by hand (OSK-16475). Silent for an open Task.
175
+ */
176
+ function resolutionLines(t, names) {
177
+ const resolution = t.resolution;
178
+ if (!resolution || typeof resolution !== 'object' || !resolution.kind)
179
+ return [];
180
+ const reason = typeof resolution.reason === 'string' ? resolution.reason.trim() : '';
181
+ const closure = resolution.closure
182
+ ? (0, task_closure_1.closureSnapshotLines)(resolution.closure)
183
+ : [];
184
+ const lines = [`Resolution: ${String(resolution.kind)}${reason ? ` — ${reason}` : ''}`];
185
+ if (closure.length > 0)
186
+ lines.push(` ${closure.join(' ')}`);
187
+ const by = resolverLabel(resolution.resolvedBy, names);
188
+ if (by)
189
+ lines.push(`Resolved by: ${by}`);
190
+ return lines;
191
+ }
154
192
  /**
155
193
  * The `Recurrence:` line for a series whose next occurrence was never armed.
156
194
  * Silent otherwise: a healthy series has nothing to say here.
@@ -163,10 +201,14 @@ function recurrenceArmFailureLines(t, bin = 'skrr') {
163
201
  const ref = (typeof t.identifier === 'string' && t.identifier) ||
164
202
  (typeof t.id === 'string' && t.id) ||
165
203
  '<task-id>';
204
+ // A finished occurrence is re-armed (`tasks schedule` refuses it, OSK-15338); a LIVE series the
205
+ // scheduler stopped on an unrunnable pattern is rescheduled with a valid one, which clears it.
206
+ const finished = ['completed', 'cancelled'].includes(String(t.statusType ?? ''));
166
207
  return [
167
208
  `Recurrence: ${(0, data_provider_1.describeRecurrenceArmFailure)(code)} (${code})`,
168
- // Not `tasks schedule`: that refuses the finished occurrence carrying this failure (OSK-15338).
169
- ` ${(0, task_recurrence_outcome_1.recurrenceRearmHint)(code, ref, bin)}`,
209
+ finished || t.statusType === undefined
210
+ ? ` ${(0, task_recurrence_outcome_1.recurrenceRearmHint)(code, ref, bin)}`
211
+ : ` Reschedule it with \`${bin} tasks schedule ${ref} --at <ISO time> --cron "<minute hour day month weekday>"\`.`,
170
212
  ];
171
213
  }
172
214
  /**
@@ -352,6 +394,21 @@ class TasksShow extends base_command_1.BaseCommand {
352
394
  this.log(line);
353
395
  const agents = Array.isArray(t.assigneeAgentIds) ? t.assigneeAgentIds : [];
354
396
  const users = Array.isArray(t.assigneeUserIds) ? t.assigneeUserIds : [];
397
+ // Principals print as names, not ObjectIds — same rule as labels: a
398
+ // roster that cannot be read prints ids rather than failing the command.
399
+ const resolvedBy = t.resolution?.resolvedBy;
400
+ const principalIds = [
401
+ t.author,
402
+ t.dri?.resolved?.id,
403
+ ...(Array.isArray(t.assigneeAgentIds) ? t.assigneeAgentIds : []),
404
+ ...(Array.isArray(t.assigneeUserIds) ? t.assigneeUserIds : []),
405
+ t.economics?.confirmedBy,
406
+ resolvedBy?.userId,
407
+ resolvedBy?.agentId,
408
+ ];
409
+ const principalNames = await (0, principal_display_1.resolvePrincipalNames)(principalIds, {
410
+ spaceId: t.spaceId ?? null,
411
+ });
355
412
  this.log(`ID: ${t.id ?? t._id ?? '-'}`);
356
413
  this.log(`Ref: ${t.identifier ?? (t.taskNumber ? `#${t.taskNumber}` : '-')}`);
357
414
  this.log(`Title: ${t.title ?? '-'}`);
@@ -368,25 +425,12 @@ class TasksShow extends base_command_1.BaseCommand {
368
425
  * Closing is the actor's call — a Task may be `done` with work still
369
426
  * outstanding, and `resolution.closure` is where the server recorded what
370
427
  * it stood on at close. Show it so a bare "done" never reads as "clean."
428
+ * The closure sentences share the `tasks move` receipt's renderer, so the
429
+ * two cannot disagree; the reason and the closer are printed for every
430
+ * resolved Task (OSK-16475).
371
431
  */
372
- const resolution = t.resolution;
373
- /*
374
- * Same renderer as the `tasks move` receipt, so the two cannot disagree
375
- * about a Task they are both describing.
376
- *
377
- * This copy showed an owner's ordinary close as an override:
378
- * `TASK_RESULT_REVIEW_REQUIRED` lands in `blockers` whenever the policy is
379
- * unsatisfied — including when the owner, who is not bound by the Review
380
- * they declared, closes it themselves — and this rendered it identically to
381
- * a genuine forced close. So the only read surface for "was this gate
382
- * overridden?" could not answer the question.
383
- */
384
- if (resolution?.closure) {
385
- const sentences = (0, task_closure_1.closureSnapshotLines)(resolution.closure);
386
- if (sentences.length > 0) {
387
- this.log(`Resolution: ${resolution.kind ?? '-'} — ${sentences.join(' ')}`);
388
- }
389
- }
432
+ for (const line of resolutionLines(t, principalNames))
433
+ this.log(line);
390
434
  /*
391
435
  * A Result orphaned by a spec edit is not the same as no Result.
392
436
  *
@@ -519,18 +563,6 @@ class TasksShow extends base_command_1.BaseCommand {
519
563
  }
520
564
  }
521
565
  this.log(`Labels: ${labelsLine(labelIds, labelNames)}`);
522
- // Principals print as names, not ObjectIds — same rule as labels above: a
523
- // roster that cannot be read prints ids rather than failing the command.
524
- const principalIds = [
525
- t.author,
526
- t.dri?.resolved?.id,
527
- ...(Array.isArray(t.assigneeAgentIds) ? t.assigneeAgentIds : []),
528
- ...(Array.isArray(t.assigneeUserIds) ? t.assigneeUserIds : []),
529
- t.economics?.confirmedBy,
530
- ];
531
- const principalNames = await (0, principal_display_1.resolvePrincipalNames)(principalIds, {
532
- spaceId: t.spaceId ?? null,
533
- });
534
566
  this.log(`Author: ${t.authorDisplayName || (0, principal_display_1.displayPrincipal)(t.author, principalNames)}`);
535
567
  const dri = t.dri;
536
568
  if (dri?.resolved) {
@@ -5,7 +5,11 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const web_url_1 = require("../../lib/web-url");
7
7
  class TasksUnschedule extends base_command_1.BaseCommand {
8
- static description = 'Clear the schedule on a task (no future fires)';
8
+ // Decision (OSK-15321, round 3): unschedule ENDS the series at this occurrence; it clears the
9
+ // time and the recurrence. Stopping without ending is `pause-schedule` (keeps the series,
10
+ // `resume-schedule` continues it). A series ended here by mistake is restored, settings and
11
+ // all, with `rearm-recurrence <the previous occurrence>`.
12
+ static description = 'Clear the schedule on a task and end its series here (no future fires). To stop without ending the series use pause-schedule; to restore an ended series run rearm-recurrence on the previous occurrence';
9
13
  static examples = [
10
14
  '<%= config.bin %> tasks unschedule <task-id>',
11
15
  '<%= config.bin %> tasks unschedule <task-id> --json',
@@ -543,7 +543,17 @@ class TasksUpdate extends base_command_1.BaseCommand {
543
543
  }
544
544
  const t = updated;
545
545
  const ref = t.identifier ?? (t.taskNumber ? `#${t.taskNumber}` : '');
546
- this.log(`Updated task ${t.id ?? t._id ?? taskRef}${ref ? ` (${ref})` : ''} — ${t.title ?? '(no title)'}`);
546
+ const label = `${t.id ?? t._id ?? taskRef}${ref ? ` (${ref})` : ''}`;
547
+ // The server's own answer about the estimate (OSK-16476): a no-op says so, never "Updated".
548
+ const unchanged = (0, task_economics_1.estimateUnchangedLine)(payload.economics, t);
549
+ const onlyEstimate = payloadKeys.every((key) => key === 'economics' || payload[key] === undefined);
550
+ if (unchanged && onlyEstimate) {
551
+ this.log(`Task ${label}: ${unchanged}`);
552
+ return;
553
+ }
554
+ this.log(`Updated task ${label} — ${t.title ?? '(no title)'}`);
555
+ if (unchanged)
556
+ this.log(unchanged);
547
557
  // `--yes` asserts the revocation; it does not erase that it happened.
548
558
  // Report the cost after the write too, or the evidence is silently gone.
549
559
  if (changesSpec && flags.yes && (settledSpecAssessments > 0 || acceptanceUnreadable)) {
@@ -25,17 +25,25 @@ class TriggersAudit extends base_command_1.BaseCommand {
25
25
  agent: core_1.Flags.string({ description: 'Agent ID filter for owner-wide audit history' }),
26
26
  scope: core_1.Flags.string({ description: 'Scope kind filter for owner-wide audit history' }),
27
27
  outcome: core_1.Flags.string({
28
- description: 'Outcome filter for owner-wide audit history (succeeded, failed, skipped, incomplete, capped, ...); an unknown value is refused with the full list',
28
+ description: 'Outcome filter (succeeded, failed, skipped, incomplete, capped, ...); an unknown value is refused with the full list',
29
29
  }),
30
30
  source: core_1.Flags.string({
31
- description: 'Source filter for owner-wide audit history (schedule, once, event, manual, webhook, replay, ...); an unknown value is refused with the full list',
31
+ description: 'Source filter (schedule, once, event, manual, webhook, replay, ...); an unknown value is refused with the full list',
32
32
  }),
33
33
  };
34
34
  async run() {
35
35
  this.requireAuth();
36
36
  const { args, flags } = await this.parse(TriggersAudit);
37
37
  const triggerId = args.id ?? flags.trigger;
38
- const path = triggerId
38
+ // The per-trigger route takes no filters. A filter passed with a trigger id used to be
39
+ // dropped without a word, so `--outcome failed` listed succeeded rows and exited 0 (OSK-16474).
40
+ // A filter now always reaches the filtered history, narrowed to that trigger; that history
41
+ // covers triggers you own, which is said out loud rather than discovered.
42
+ const filtered = [flags.outcome, flags.source, flags.scope, flags.agent].some((value) => value !== undefined);
43
+ if (triggerId && filtered && !flags.json) {
44
+ this.warn('Filters read the fire history of triggers you own: a trigger shared with you but owned by someone else lists no rows here (omit the filters to read its own audit).');
45
+ }
46
+ const path = triggerId && !filtered
39
47
  ? (0, triggers_1.withQuery)(`/api/triggers/${encodeURIComponent(triggerId)}/audit`, {
40
48
  limit: flags.limit,
41
49
  before: flags.before,
@@ -47,6 +55,7 @@ class TriggersAudit extends base_command_1.BaseCommand {
47
55
  agentId: flags.agent,
48
56
  outcome: flags.outcome,
49
57
  source: flags.source,
58
+ ...(triggerId ? { triggerId } : {}),
50
59
  });
51
60
  let response;
52
61
  try {
@@ -99,7 +99,7 @@ class TriggersCreate extends base_command_1.BaseCommand {
99
99
  description: 'ISO-8601 datetime after which a schedule stops firing',
100
100
  }),
101
101
  'stale-catchup-ms': core_1.Flags.integer({
102
- description: 'How late a missed occurrence may still run, in ms (a positive number). After downtime, an occurrence more than this overdue is skipped rather than run late, and the schedule moves to its next future slot. Unset: 3600000 (1 hour).',
102
+ description: 'How late a missed occurrence may still run, in ms, from 1000 (1 second) to 31622400000 (366 days). After downtime, an occurrence more than this overdue is skipped rather than run late, and the schedule moves to its next future slot. Unset: 3600000 (1 hour).',
103
103
  }),
104
104
  at: core_1.Flags.string({
105
105
  description: 'ISO-8601 datetime for once triggers. --next-run-at is also accepted with --kind once for backwards compatibility.',
@@ -1,4 +1,11 @@
1
1
  import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * The text rendering of one fire. `reason` is the typed code and `errorMessage` the sentence a
4
+ * person reads; the view printed whichever came first, so a fire that resolved nothing showed
5
+ * only `Reason: no_waiting_step` and its sentence ("No step in this Space was waiting on that
6
+ * event key.") was JSON-only (OSK-16474). Both are shown now when both exist.
7
+ */
8
+ export declare function describeFireAuditDetail(detail: Record<string, unknown>, fireId: string, triggerId: string): string[];
2
9
  export default class TriggersFireDetail extends BaseCommand {
3
10
  static description: string;
4
11
  static examples: string[];
@@ -1,8 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describeFireAuditDetail = describeFireAuditDetail;
3
4
  const core_1 = require("@oclif/core");
4
5
  const base_command_1 = require("../../base-command");
5
6
  const triggers_1 = require("../../lib/triggers");
7
+ /**
8
+ * The text rendering of one fire. `reason` is the typed code and `errorMessage` the sentence a
9
+ * person reads; the view printed whichever came first, so a fire that resolved nothing showed
10
+ * only `Reason: no_waiting_step` and its sentence ("No step in this Space was waiting on that
11
+ * event key.") was JSON-only (OSK-16474). Both are shown now when both exist.
12
+ */
13
+ function describeFireAuditDetail(detail, fireId, triggerId) {
14
+ const text = (value) => (typeof value === 'string' && value.trim() ? value.trim() : '');
15
+ const lines = [
16
+ `Fire: ${String(detail._id ?? fireId)}`,
17
+ `Trigger: ${String(detail.triggerId ?? triggerId)}`,
18
+ `Fired at: ${String(detail.firedAt ?? '-')}`,
19
+ `Outcome: ${String(detail.outcome ?? '-')}`,
20
+ `Source: ${String(detail.source ?? '-')}`,
21
+ ];
22
+ const reason = text(detail.reason);
23
+ const message = text(detail.errorMessage);
24
+ if (reason)
25
+ lines.push(`Reason: ${reason}`);
26
+ if (message && message !== reason)
27
+ lines.push(`${reason ? 'Detail:' : 'Reason:'} ${message}`);
28
+ if (detail.durationMs !== undefined)
29
+ lines.push(`Duration: ${String(detail.durationMs)}ms`);
30
+ if (detail.conversationId)
31
+ lines.push(`Conversation: ${String(detail.conversationId)}`);
32
+ return lines;
33
+ }
6
34
  class TriggersFireDetail extends base_command_1.BaseCommand {
7
35
  static description = 'Show full detail for one trigger fire record';
8
36
  static examples = [
@@ -34,18 +62,8 @@ class TriggersFireDetail extends base_command_1.BaseCommand {
34
62
  this.log(JSON.stringify(detail, null, 2));
35
63
  return;
36
64
  }
37
- this.log(`Fire: ${detail._id ?? args.fireId}`);
38
- this.log(`Trigger: ${detail.triggerId ?? args.id}`);
39
- this.log(`Fired at: ${detail.firedAt ?? '-'}`);
40
- this.log(`Outcome: ${detail.outcome ?? '-'}`);
41
- this.log(`Source: ${detail.source ?? '-'}`);
42
- if (detail.reason || detail.errorMessage) {
43
- this.log(`Reason: ${detail.reason ?? detail.errorMessage}`);
44
- }
45
- if (detail.durationMs !== undefined)
46
- this.log(`Duration: ${detail.durationMs}ms`);
47
- if (detail.conversationId)
48
- this.log(`Conversation: ${detail.conversationId}`);
65
+ for (const line of describeFireAuditDetail(detail, args.fireId, args.id))
66
+ this.log(line);
49
67
  }
50
68
  }
51
69
  exports.default = TriggersFireDetail;
@@ -46,9 +46,17 @@ class TriggersFire extends base_command_1.BaseCommand {
46
46
  }
47
47
  const accepted = (0, triggers_1.fireWasAccepted)(result);
48
48
  if (flags.json) {
49
- this.log(JSON.stringify(result, null, 2));
50
- if (flags.strict && !accepted)
49
+ // `ok` and the exit status are one answer: a strict refusal prints `ok: false` and why,
50
+ // never the server's transport-level `ok: true` beside exit 1 (OSK-16494).
51
+ const refused = flags.strict && !accepted;
52
+ this.log(JSON.stringify(refused
53
+ ? { ...result, ok: false, strictRefusal: (0, triggers_1.describeStrictFireRefusal)(result) }
54
+ : result, null, 2));
55
+ if (refused) {
56
+ // stdout stays the one JSON document; the reason goes to stderr.
57
+ process.stderr.write(`${(0, triggers_1.describeStrictFireRefusal)(result)}\n`);
51
58
  this.exit(1);
59
+ }
52
60
  return;
53
61
  }
54
62
  this.log(`Fire requested: outcome=${String(result.outcome ?? '-')} reason=${String(result.reason ?? '-')}`);
@@ -57,8 +65,10 @@ class TriggersFire extends base_command_1.BaseCommand {
57
65
  const note = (0, triggers_1.describeScheduledFire)(result);
58
66
  if (note)
59
67
  this.log(note);
60
- if (flags.strict && !accepted)
68
+ if (flags.strict && !accepted) {
69
+ process.stderr.write(`${(0, triggers_1.describeStrictFireRefusal)(result)}\n`);
61
70
  this.exit(1);
71
+ }
62
72
  }
63
73
  }
64
74
  exports.default = TriggersFire;
@@ -26,7 +26,7 @@ class TriggersParseIntent extends base_command_1.BaseCommand {
26
26
  multiple: true,
27
27
  }),
28
28
  'history-json': core_1.Flags.string({
29
- description: 'Parser history JSON array of {role:"user"|"assistant",content:string} objects. To answer a clarifying question, pass the `history` a previous --json run returned and add your reply with --message',
29
+ description: 'Parser history: an inline JSON array of {role:"user"|"assistant",content:string} objects, or @file / a path to a file holding it. To answer a clarifying question, pass the `history` a previous --json run returned and add your reply with --message',
30
30
  }),
31
31
  timezone: core_1.Flags.string({ description: 'IANA timezone used for relative times' }),
32
32
  now: core_1.Flags.string({ description: 'Override current ISO timestamp used for relative times' }),
@@ -56,7 +56,7 @@ class TriggersUpdate extends base_command_1.BaseCommand {
56
56
  description: 'ISO-8601 datetime after which a schedule stops firing',
57
57
  }),
58
58
  'stale-catchup-ms': core_1.Flags.integer({
59
- description: 'How late a missed occurrence may still run, in ms (a positive number). After downtime, an occurrence more than this overdue is skipped rather than run late, and the schedule moves to its next future slot. Unset: 3600000 (1 hour).',
59
+ description: 'How late a missed occurrence may still run, in ms, from 1000 (1 second) to 31622400000 (366 days). After downtime, an occurrence more than this overdue is skipped rather than run late, and the schedule moves to its next future slot. Unset: 3600000 (1 hour).',
60
60
  }),
61
61
  at: core_1.Flags.string({ description: 'ISO-8601 datetime for once triggers' }),
62
62
  entity: core_1.Flags.string({
@@ -39,10 +39,7 @@ async function memberNames(workspaceId) {
39
39
  try {
40
40
  const members = await (0, workspaces_1.listWorkspaceMembers)(workspaceId);
41
41
  return new Map(members
42
- .map((member) => [
43
- String(member.userId),
44
- member.user?.name || member.user?.username || member.user?.email || '',
45
- ])
42
+ .map((member) => [member.userId, member.name ?? member.email ?? ''])
46
43
  .filter(([, name]) => name));
47
44
  }
48
45
  catch {
@@ -36,7 +36,7 @@ class WorkspacesMembers extends base_command_1.BaseCommand {
36
36
  return;
37
37
  }
38
38
  if (flags.json) {
39
- this.log(JSON.stringify(members, null, 2));
39
+ this.printJsonResult(members);
40
40
  return;
41
41
  }
42
42
  if (members.length === 0) {
@@ -44,11 +44,11 @@ class WorkspacesMembers extends base_command_1.BaseCommand {
44
44
  return;
45
45
  }
46
46
  (0, format_1.renderTable)(members.map((member) => ({
47
- userId: String(member.userId),
48
- name: member.user?.name || member.user?.username || '—',
49
- email: member.user?.email || '—',
50
- role: member.role || '—',
51
- joined: member.joinedAt || '—',
47
+ userId: member.userId,
48
+ name: member.name ?? '—',
49
+ email: member.email ?? '—',
50
+ role: member.role ?? '—',
51
+ joined: member.joinedAt ?? '—',
52
52
  })), [
53
53
  { key: 'userId', header: 'USER ID', verbatim: true },
54
54
  { key: 'name', header: 'NAME', maxWidth: 28 },
@@ -62,9 +62,12 @@ function renderExecutionReceipt(receipt, log, verbose = false) {
62
62
  // restates what happened (a typed blocker's summary, which servers before
63
63
  // OSK-16429 sent as the reason) is narrative, not something to do: the actor
64
64
  // is printed without it and the narrative stays with the blocker.
65
+ // A bare reason code (servers before OSK-16451 sent `runtime_connection_unverifiable`
66
+ // there) is not an action either: the actor is printed alone.
65
67
  const narrative = [receipt.summary, receipt.blocker?.summary].filter(Boolean);
66
- const action = receipt.nextActor?.reason && !narrative.includes(receipt.nextActor.reason)
67
- ? receipt.nextActor.reason
68
+ const reason = receipt.nextActor?.reason;
69
+ const action = reason && !narrative.includes(reason) && !/^[A-Za-z0-9_.:-]+$/.test(reason.trim())
70
+ ? reason
68
71
  : '';
69
72
  if (receipt.nextActor)
70
73
  log(`Next: ${receipt.nextActor.label || receipt.nextActor.kind}${action ? ` — ${action}` : ''}`);
@@ -832,8 +832,9 @@ export declare function describeOperation(operation: string | null | undefined,
832
832
  * under "Fix:" read as a remedy nobody wrote (OSK-16230). That narrative is
833
833
  * labelled `Blocker:` instead, and no remedy is made up for it.
834
834
  */
835
+ type OperationFixLabel = 'Fix' | 'Blocker' | 'Last run';
835
836
  export declare function describeOperationFix(commitment: CommitmentView, operation: string | null | undefined): Array<{
836
- label: 'Fix' | 'Blocker';
837
+ label: OperationFixLabel;
837
838
  text: string;
838
839
  }>;
839
840
  /**