@skrr-ai/cli 0.1.128 → 0.1.129

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 (79) hide show
  1. package/dist/base-command.d.ts +12 -0
  2. package/dist/base-command.js +35 -0
  3. package/dist/commands/agenda/trigger/list.d.ts +2 -1
  4. package/dist/commands/agenda/trigger/list.js +55 -6
  5. package/dist/commands/agents/create.js +3 -1
  6. package/dist/commands/browser/persistent-profile.d.ts +22 -0
  7. package/dist/commands/browser/persistent-profile.js +46 -0
  8. package/dist/commands/commitments/create.js +3 -1
  9. package/dist/commands/commitments/effective-policy.js +6 -0
  10. package/dist/commands/commitments/explain.js +10 -1
  11. package/dist/commands/commitments/plan.d.ts +14 -0
  12. package/dist/commands/commitments/plan.js +32 -0
  13. package/dist/commands/commitments/preflight.d.ts +1 -1
  14. package/dist/commands/commitments/preflight.js +12 -4
  15. package/dist/commands/commitments/show.d.ts +9 -0
  16. package/dist/commands/commitments/show.js +14 -1
  17. package/dist/commands/compass/revisions.d.ts +6 -2
  18. package/dist/commands/compass/revisions.js +13 -5
  19. package/dist/commands/compass/tactic.d.ts +5 -0
  20. package/dist/commands/compass/tactic.js +14 -0
  21. package/dist/commands/followups/plans/create.js +1 -1
  22. package/dist/commands/harnesses/list.js +1 -1
  23. package/dist/commands/northstars/governance/configure.js +1 -1
  24. package/dist/commands/northstars/intents/decide.js +2 -3
  25. package/dist/commands/northstars/review-now.js +5 -0
  26. package/dist/commands/northstars/reviews.js +15 -3
  27. package/dist/commands/spaces/auto-executor/runs.d.ts +9 -1
  28. package/dist/commands/spaces/auto-executor/runs.js +18 -1
  29. package/dist/commands/spaces/heartbeats/create.js +7 -5
  30. package/dist/commands/spaces/heartbeats/update.js +5 -3
  31. package/dist/commands/store/artifacts/releases.d.ts +20 -0
  32. package/dist/commands/store/artifacts/releases.js +85 -0
  33. package/dist/commands/store/library.d.ts +14 -0
  34. package/dist/commands/store/library.js +71 -0
  35. package/dist/commands/tasks/list.d.ts +48 -0
  36. package/dist/commands/tasks/list.js +141 -9
  37. package/dist/commands/tasks/remind.js +23 -11
  38. package/dist/commands/tasks/retry-schedule.js +6 -3
  39. package/dist/commands/tasks/schedule.js +29 -7
  40. package/dist/commands/tasks/show.js +4 -0
  41. package/dist/lib/agent-trigger-listing.d.ts +26 -0
  42. package/dist/lib/agent-trigger-listing.js +63 -0
  43. package/dist/lib/agentic-stream.js +138 -41
  44. package/dist/lib/commitment-ledgers.js +3 -1
  45. package/dist/lib/commitments.d.ts +24 -0
  46. package/dist/lib/commitments.js +64 -4
  47. package/dist/lib/compasses.d.ts +2 -0
  48. package/dist/lib/iso-instant.d.ts +52 -0
  49. package/dist/lib/iso-instant.js +162 -0
  50. package/dist/lib/northstars.d.ts +43 -0
  51. package/dist/lib/northstars.js +122 -6
  52. package/dist/lib/proposals.js +6 -2
  53. package/dist/lib/store-artifact-releases.d.ts +39 -0
  54. package/dist/lib/store-artifact-releases.js +12 -0
  55. package/dist/lib/store-library.d.ts +37 -0
  56. package/dist/lib/store-library.js +12 -0
  57. package/dist/lib/task-calendar.d.ts +19 -0
  58. package/dist/lib/task-calendar.js +72 -0
  59. package/dist/lib/task-recurrence-outcome.js +3 -1
  60. package/dist/lib/task-recurrence-summary.d.ts +15 -0
  61. package/dist/lib/task-recurrence-summary.js +228 -0
  62. package/dist/lib/task-reminder.d.ts +28 -0
  63. package/dist/lib/task-reminder.js +50 -0
  64. package/dist/lib/triggers.d.ts +13 -2
  65. package/dist/lib/triggers.js +22 -12
  66. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +11 -1
  67. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.d.ts +123 -0
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.js +160 -0
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -0
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +17 -0
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +11 -1
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.d.ts +123 -0
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.js +173 -0
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -0
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -0
  76. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  77. package/dist/node_modules/@skrr-ai/data-provider/index.js +6755 -6672
  78. package/oclif.manifest.json +263 -29
  79. package/package.json +1 -1
@@ -1,11 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveListWorkspace = resolveListWorkspace;
3
4
  exports.agentSessionOpenDefault = agentSessionOpenDefault;
4
5
  exports.emptyTasksMessage = emptyTasksMessage;
5
6
  exports.externalKeyPrefixWarnings = externalKeyPrefixWarnings;
6
7
  exports.nextActionText = nextActionText;
7
8
  exports.nextColumnWidth = nextColumnWidth;
8
9
  exports.taskRef = taskRef;
10
+ exports.calendarJsonRow = calendarJsonRow;
11
+ exports.calendarJsonResponse = calendarJsonResponse;
9
12
  exports.taskExecution = taskExecution;
10
13
  exports.taskPageSummary = taskPageSummary;
11
14
  exports.taskExecutionText = taskExecutionText;
@@ -18,7 +21,9 @@ const web_url_1 = require("../../lib/web-url");
18
21
  const format_1 = require("../../lib/format");
19
22
  const list_output_1 = require("../../lib/list-output");
20
23
  const tasks_1 = require("../../lib/tasks");
24
+ const workspaces_1 = require("../../lib/workspaces");
21
25
  const task_economics_1 = require("../../lib/task-economics");
26
+ const task_calendar_1 = require("../../lib/task-calendar");
22
27
  /**
23
28
  * Names of `tasks list` flags that narrow the result set. Used to distinguish
24
29
  * "no tasks anywhere" from "no tasks matching your filters" — the first case
@@ -63,6 +68,55 @@ const FILTER_FLAGS = new Set([
63
68
  'q',
64
69
  'view',
65
70
  ]);
71
+ /**
72
+ * A plain `tasks list` named no workspace, and the server answers that with a list across EVERY
73
+ * workspace the person belongs to. Its access filter has branches that belong to no single
74
+ * workspace, so no index can serve it in order: on production it timed out after about 6 s while
75
+ * the same list naming a workspace answered in about a second. `skrr workspaces list` already says
76
+ * an unselected session "resolves to your default workspace"; a list now reads it too.
77
+ *
78
+ * --all-workspaces the old, wider list, asked for by name (`workspaceScope=all`)
79
+ * a selected workspace, `--workspace`, or `skrr workspaces use` untouched: the choice stands
80
+ * an autonomous Agent session untouched: a workspace-less Agent session stays personal
81
+ * `--space`, `--calendar` untouched: a Space names its own scope
82
+ *
83
+ * Returns nothing when there is nothing to apply, and `source: 'default'` when the caller's
84
+ * default workspace should be used. `lookupDefault` is injected so the rule is tested without the
85
+ * network; a lookup that fails or finds no default changes nothing.
86
+ */
87
+ async function resolveListWorkspace(flags, env, lookupDefault) {
88
+ if (flags['all-workspaces'])
89
+ return { source: 'all' };
90
+ if (env.OVERSKY_WORKSPACE_ID?.trim())
91
+ return undefined;
92
+ if (env.OVERSKY_AUTONOMOUS_SESSION === '1')
93
+ return undefined;
94
+ if (flags.space || flags.calendar)
95
+ return undefined;
96
+ let found = null;
97
+ try {
98
+ found = await lookupDefault();
99
+ }
100
+ catch {
101
+ return undefined;
102
+ }
103
+ if (!found?.id)
104
+ return undefined;
105
+ return {
106
+ source: 'default',
107
+ workspaceId: found.id,
108
+ ...(found.name ? { workspaceName: found.name } : {}),
109
+ workspaceCount: found.count,
110
+ };
111
+ }
112
+ /** The default workspace the signed-in person's `skrr workspaces list` reports. */
113
+ async function lookupDefaultWorkspace() {
114
+ const listing = await (0, workspaces_1.listWorkspaceListing)();
115
+ if (!listing.defaultWorkspaceId)
116
+ return null;
117
+ const row = listing.rows.find((entry) => entry.id === listing.defaultWorkspaceId);
118
+ return { id: listing.defaultWorkspaceId, ...(row?.name ? { name: row.name } : {}), count: listing.rows.length };
119
+ }
66
120
  /**
67
121
  * E12: inside an agent session an unfiltered `tasks list --json` returned the
68
122
  * first page of EVERYTHING, which in practice was cancelled test tasks. When the
@@ -234,6 +288,43 @@ function nextColumnWidth(rows) {
234
288
  function taskRef(t) {
235
289
  return String(t.identifier ?? (t.taskNumber ? `#${t.taskNumber}` : '-'));
236
290
  }
291
+ /**
292
+ * A calendar row in `--json`: the Task it places, by the names `tasks show` resolves.
293
+ *
294
+ * The server's occurrence `id` is `<taskId>:<kind>:<instant>`, a key for the occurrence, and the
295
+ * list shaping every other `tasks list` row gets (`ref` and `url` read off the row itself, and
296
+ * `nextAction` off its actionability) turned it into an `id`, a `url` and a `ref` that named
297
+ * nothing: `tasks show` refused the first two and the third was "-" (OSK-21586, OSK-21587). So the
298
+ * row's `id`, `ref` and `url` are the Task's, and the occurrence key is `occurrenceId`. A missing
299
+ * answer is `null`, never a placeholder.
300
+ */
301
+ function calendarJsonRow(baseURL, row) {
302
+ const task = (row.task ?? {});
303
+ const taskId = (row.taskId ?? task.id);
304
+ const next = nextActionText(task);
305
+ return {
306
+ ...row,
307
+ id: taskId ?? row.id,
308
+ occurrenceId: row.id,
309
+ ref: task.identifier ?? null,
310
+ nextAction: next === '-' ? null : next,
311
+ url: taskId ? (0, web_url_1.taskUrlFrom)(baseURL, { id: taskId, spaceId: task.spaceId }) : null,
312
+ };
313
+ }
314
+ /** The calendar response with its rows made addressable; everything else stays as the server sent it. */
315
+ function calendarJsonResponse(baseURL, response) {
316
+ if (!response || typeof response !== 'object')
317
+ return response;
318
+ const calendar = response;
319
+ if (!Array.isArray(calendar.data))
320
+ return response;
321
+ return {
322
+ ...calendar,
323
+ data: calendar.data.map((row) => row && typeof row === 'object'
324
+ ? calendarJsonRow(baseURL, row)
325
+ : row),
326
+ };
327
+ }
237
328
  /** Evidence about managed execution only; unmanaged work is outside this projection. */
238
329
  function taskExecution(t) {
239
330
  const actionability = t.actionability;
@@ -351,6 +442,7 @@ function buildListParams(flags, assigneeUserId) {
351
442
  ...(flag('explicit-only') ? { explicitOnly: true } : {}),
352
443
  ...(flag('subscribed') ? { subscribed: true } : {}),
353
444
  ...(flag('include-snoozed') ? { includeSnoozed: true } : {}),
445
+ ...(flag('all-workspaces') ? { workspaceScope: 'all' } : {}),
354
446
  ...(flag('external-key')
355
447
  ? { externalKey: flag('external-key') }
356
448
  : {}),
@@ -430,6 +522,7 @@ class TasksList extends base_command_1.BaseCommand {
430
522
  '<%= config.bin %> tasks list --dri me',
431
523
  '<%= config.bin %> tasks list --view <task-view-id>',
432
524
  '<%= config.bin %> tasks list --calendar 2026-08-01..2026-08-31 --json',
525
+ '<%= config.bin %> tasks list --calendar 2026-12-06T16:00:00Z..2026-12-07T15:59:59Z',
433
526
  '<%= config.bin %> tasks list --open --compact --json',
434
527
  '<%= config.bin %> tasks list --space <space-id> --fields id,ref,title,status --json',
435
528
  '<%= config.bin %> tasks list --open --ndjson | jq -r .ref',
@@ -543,6 +636,10 @@ class TasksList extends base_command_1.BaseCommand {
543
636
  'include-snoozed': core_1.Flags.boolean({
544
637
  description: 'Include tasks you have personally snoozed (hidden by default)',
545
638
  }),
639
+ 'all-workspaces': core_1.Flags.boolean({
640
+ description: 'List across every workspace you belong to. Without it, a list with no workspace selected ' +
641
+ 'reads your default workspace; a list across all of them is slow and may time out.',
642
+ }),
546
643
  'external-key': core_1.Flags.string({ description: 'Filter by external key' }),
547
644
  'external-key-prefix': core_1.Flags.string({
548
645
  description: 'Filter to tasks whose external key starts with this literal prefix, e.g. every task of one tree written under --external-key-prefix. Combine with --space; see also `tasks create-tree --verify`.',
@@ -581,7 +678,11 @@ class TasksList extends base_command_1.BaseCommand {
581
678
  description: 'Exit 2 when --status names a value that is not a built-in state id and matches nothing (instead of an empty success).',
582
679
  }),
583
680
  calendar: core_1.Flags.string({
584
- description: 'Expand the human task calendar for an inclusive range (YYYY-MM-DD..YYYY-MM-DD or ISO instants). Includes due dates and agent schedule occurrences.',
681
+ description: 'Expand the human task calendar for an inclusive range, <from>..<to>, at most 93 days. ' +
682
+ 'A date-only value (YYYY-MM-DD) is a whole UTC day, 00:00:00.000Z to 23:59:59.999Z, not your local day: ' +
683
+ 'for a local day pass ISO instants, e.g. 2026-12-06T16:00:00Z..2026-12-07T15:59:59Z. ' +
684
+ 'Rows print in UTC, and the human output names the resolved window. ' +
685
+ 'Includes due dates and agent schedule occurrences; a result cut short by a limit says which limit.',
585
686
  }),
586
687
  };
587
688
  async run() {
@@ -633,6 +734,16 @@ class TasksList extends base_command_1.BaseCommand {
633
734
  const filterApplied = agentSessionOpenDefault(flags);
634
735
  if (filterApplied)
635
736
  flags.open = true;
737
+ // Which workspace this list reads. A default is applied the way `--workspace` is: the
738
+ // request header follows OVERSKY_WORKSPACE_ID, set for this process only.
739
+ const workspaceScope = await resolveListWorkspace(flags, process.env, lookupDefaultWorkspace);
740
+ if (workspaceScope?.source === 'default') {
741
+ process.env.OVERSKY_WORKSPACE_ID = workspaceScope.workspaceId;
742
+ if (!flags.json && !flags.ndjson && workspaceScope.workspaceCount > 1) {
743
+ this.logToStderr(`Workspace: ${workspaceScope.workspaceName ? `"${workspaceScope.workspaceName}"` : workspaceScope.workspaceId} ` +
744
+ `(your default). Every workspace you belong to: ${this.config.bin} tasks list --all-workspaces`);
745
+ }
746
+ }
636
747
  let response;
637
748
  try {
638
749
  response = calendarRange
@@ -687,19 +798,24 @@ class TasksList extends base_command_1.BaseCommand {
687
798
  // `identifier` and `actionability` — under `--json` an agent scanning 43
688
799
  // keys could not find either (OSK-10692). Alias them to the names the
689
800
  // table prints so the machine view answers the same questions.
690
- const enriched = list && Array.isArray(list.data)
801
+ const enriched = list && Array.isArray(list.data) && !calendarRange
691
802
  ? {
692
803
  ...list,
693
- ...(!calendarRange ? { summary: taskPageSummary(list.data, Boolean(list.has_more ?? list.next_cursor)) } : {}),
804
+ summary: taskPageSummary(list.data, Boolean(list.has_more ?? list.next_cursor)),
694
805
  data: list.data.map((t) => ({
695
806
  ...t,
696
807
  ref: taskRef(t),
697
808
  nextAction: nextActionText(t),
698
- ...(!calendarRange ? { execution: taskExecution(t) } : {}),
809
+ execution: taskExecution(t),
699
810
  })),
700
811
  }
701
812
  : response;
702
- const withUrls = (0, web_url_1.withDataUrls)(this.cliConfig.baseURL, enriched, web_url_1.taskUrlFrom);
813
+ // A calendar row is an occurrence, not a Task: it gets the Task's id, ref and url and the
814
+ // occurrence key as `occurrenceId`, instead of the Task list's treatment, which rebuilt the
815
+ // url and the ref from the occurrence's own id (OSK-21586).
816
+ const withUrls = calendarRange
817
+ ? calendarJsonResponse(this.cliConfig.baseURL, response)
818
+ : (0, web_url_1.withDataUrls)(this.cliConfig.baseURL, enriched, web_url_1.taskUrlFrom);
703
819
  // The calendar response is occurrences, not tasks: leave its shape alone.
704
820
  const out = calendarRange
705
821
  ? withUrls
@@ -723,14 +839,27 @@ class TasksList extends base_command_1.BaseCommand {
723
839
  }
724
840
  : {}),
725
841
  });
726
- this.printList(out, Boolean(flags.ndjson));
842
+ // Say which workspace was read, as data, when the CLI chose it (or the caller asked for all):
843
+ // an agent reading JSON cannot see the stderr note.
844
+ this.printList(workspaceScope && out && typeof out === 'object' && !Array.isArray(out)
845
+ ? { ...out, workspace_scope: workspaceScope }
846
+ : out, Boolean(flags.ndjson));
727
847
  return;
728
848
  }
729
849
  if (calendarRange) {
730
850
  const calendar = response;
731
851
  const occurrences = calendar.data ?? [];
852
+ const notices = (0, task_calendar_1.calendarNoticeLines)(calendar);
853
+ // The resolved window goes to stderr: it is about the read, not a row, so a piped table
854
+ // stays a table. A date-only range is a whole UTC day (OSK-21529).
855
+ this.logToStderr((0, task_calendar_1.calendarWindowLine)(calendar));
732
856
  if (occurrences.length === 0) {
733
- this.log('No calendar occurrences.');
857
+ // "No calendar occurrences." is only true of a complete read (OSK-21382).
858
+ this.log(notices.length === 0
859
+ ? 'No calendar occurrences.'
860
+ : 'No calendar occurrences were listed, but the read was cut short.');
861
+ for (const line of notices)
862
+ this.log(line);
734
863
  return;
735
864
  }
736
865
  (0, format_1.renderTable)(occurrences.map((item) => ({
@@ -746,8 +875,11 @@ class TasksList extends base_command_1.BaseCommand {
746
875
  { key: 'title', header: 'TITLE', maxWidth: 50 },
747
876
  { key: 'status', header: 'STATUS' },
748
877
  ], (line) => this.log(line));
749
- if (calendar.truncated)
750
- this.log('\nNote: calendar occurrence expansion reached its safety cap.');
878
+ if (notices.length > 0) {
879
+ this.log('');
880
+ for (const line of notices)
881
+ this.log(line);
882
+ }
751
883
  return;
752
884
  }
753
885
  const r = response;
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
+ const iso_instant_1 = require("../../lib/iso-instant");
7
+ const task_reminder_1 = require("../../lib/task-reminder");
6
8
  /** Set a one-time human reminder without changing the task's visibility or state. */
7
9
  class TasksRemind extends base_command_1.BaseCommand {
8
10
  static description = 'Set a one-time inbox reminder on a task. The task stays visible and unchanged.';
@@ -18,24 +20,27 @@ class TasksRemind extends base_command_1.BaseCommand {
18
20
  }),
19
21
  };
20
22
  static flags = {
21
- at: core_1.Flags.string({ description: 'Reminder time as an ISO-8601 UTC instant', required: true }),
23
+ at: core_1.Flags.string({
24
+ description: 'Reminder time as an ISO-8601 UTC instant ending in Z (e.g. 2026-08-25T16:00:00Z); a time with no Z or a numeric offset such as +08:00 is refused',
25
+ required: true,
26
+ }),
22
27
  json: core_1.Flags.boolean({ description: 'Output the TriggerDefinition as JSON' }),
23
28
  };
24
29
  async run() {
25
30
  this.requireAuth();
26
31
  const { args, flags } = await this.parse(TasksRemind);
32
+ // Before any lookup: a bad time costs no round trip and creates nothing. Never `new Date(flags.at)`,
33
+ // which reads a zoneless time in THIS machine's zone and rolls 2027-02-30 into March (OSK-21504/21506).
34
+ const parsed = (0, iso_instant_1.parseUtcInstant)(flags.at, '--at');
35
+ if (!parsed.ok)
36
+ this.error(parsed.message, { exit: 2 });
37
+ const at = parsed.date;
27
38
  const taskRef = await this.resolveEntityRef('task', args.taskId);
28
- const at = new Date(flags.at);
29
- if (Number.isNaN(at.getTime()) || !/^\d{4}-\d{2}-\d{2}T/.test(flags.at)) {
30
- this.error('--at must be an ISO-8601 instant, for example 2026-08-25T16:00:00Z.', {
31
- exit: 2,
32
- });
33
- }
34
39
  let task;
35
40
  let reminder;
36
41
  try {
37
42
  task = (await data_provider_1.dataService.getTask(taskRef, { enrich: false }));
38
- reminder = await data_provider_1.request.post('/api/triggers', {
43
+ reminder = (await data_provider_1.request.post('/api/triggers', {
39
44
  scope: {
40
45
  kind: 'entity_reminder',
41
46
  refs: {
@@ -47,8 +52,8 @@ class TasksRemind extends base_command_1.BaseCommand {
47
52
  kind: 'once',
48
53
  label: `Reminder: ${task.title || task.identifier || task.id}`.slice(0, 120),
49
54
  execution: { mode: 'notify_user', promptTemplate: '' },
50
- once: { runAt: at.toISOString() },
51
- });
55
+ once: { runAt: parsed.iso },
56
+ }));
52
57
  }
53
58
  catch (err) {
54
59
  this.handleApiError(err);
@@ -58,7 +63,14 @@ class TasksRemind extends base_command_1.BaseCommand {
58
63
  this.log(JSON.stringify(reminder, null, 2));
59
64
  return;
60
65
  }
61
- this.log(`Reminder set for ${task.title || task.identifier || task.id} at ${at.toLocaleString()}.`);
66
+ for (const line of (0, task_reminder_1.reminderReceiptLines)({
67
+ title: task.title || task.identifier || task.id,
68
+ trigger: reminder,
69
+ requested: at,
70
+ bin: this.config.bin,
71
+ })) {
72
+ this.log(line);
73
+ }
62
74
  }
63
75
  }
64
76
  exports.default = TasksRemind;
@@ -5,10 +5,12 @@ 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 TasksRetrySchedule extends base_command_1.BaseCommand {
8
- static description = 'Re-arm a stale/failed schedule. Optionally pin a new first-fire time';
8
+ static description = 'Re-arm a stale/failed schedule. After a failed or skipped start, pass --at with a new first-fire time: ' +
9
+ 'the failed fire cleared the scheduledFor, so there is nothing left to retry against. ' +
10
+ 'The Task must have an assigned agent, as the start will fail the same way otherwise.';
9
11
  static examples = [
10
- '<%= config.bin %> tasks retry-schedule <task-id>',
11
12
  '<%= config.bin %> tasks retry-schedule <task-id> --at 2026-06-01T15:30:00Z',
13
+ '<%= config.bin %> tasks retry-schedule <task-id>',
12
14
  ];
13
15
  static args = {
14
16
  id: core_1.Args.string({ description: 'Task ID', required: true, ignoreStdin: true }),
@@ -16,7 +18,8 @@ class TasksRetrySchedule extends base_command_1.BaseCommand {
16
18
  static flags = {
17
19
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
18
20
  at: core_1.Flags.string({
19
- description: 'Optional new first-fire time as ISO-8601 UTC. Omit to retry against the current scheduledFor.',
21
+ description: 'New first-fire time as ISO-8601 UTC. Required after a failed or skipped start (the failed fire cleared scheduledFor). ' +
22
+ 'Omit it only to re-sync a schedule whose scheduledFor is still in the future.',
20
23
  }),
21
24
  };
22
25
  async run() {
@@ -5,6 +5,8 @@ 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
  const prompt_1 = require("../../lib/prompt");
8
+ const iso_instant_1 = require("../../lib/iso-instant");
9
+ const task_recurrence_summary_1 = require("../../lib/task-recurrence-summary");
8
10
  class TasksSchedule extends base_command_1.BaseCommand {
9
11
  static description = 'Schedule a task to fire at a future time (optionally recurring)';
10
12
  static examples = [
@@ -21,10 +23,11 @@ class TasksSchedule extends base_command_1.BaseCommand {
21
23
  description: 'First fire time as ISO-8601 UTC ending in Z (e.g. 2026-06-01T15:30:00Z); numeric offsets such as +00:00 are not accepted',
22
24
  }),
23
25
  cron: core_1.Flags.string({
24
- description: 'Recurrence cron pattern (UTC unless --timezone is set)',
26
+ description: 'Recurrence cron pattern: minute hour day-of-month month day-of-week (UTC unless --timezone is set). ' +
27
+ 'When both a day of the month and a weekday are set, it fires when EITHER matches',
25
28
  }),
26
29
  timezone: core_1.Flags.string({
27
- description: 'IANA timezone for the cron pattern (e.g. America/Los_Angeles)',
30
+ description: 'IANA zone name for the cron pattern, e.g. Asia/Shanghai or America/Los_Angeles (not an offset such as UTC+8, and not "local")',
28
31
  dependsOn: ['cron'],
29
32
  }),
30
33
  'max-runs': core_1.Flags.integer({
@@ -32,7 +35,7 @@ class TasksSchedule extends base_command_1.BaseCommand {
32
35
  dependsOn: ['cron'],
33
36
  }),
34
37
  'end-date': core_1.Flags.string({
35
- description: 'Stop recurrence after this ISO-8601 date',
38
+ description: 'Last day of the recurrence: YYYY-MM-DD (through the end of that day in --timezone, UTC if omitted) or an ISO-8601 instant with a zone (2026-12-31T17:00:00Z); must be after the first fire',
36
39
  dependsOn: ['cron'],
37
40
  }),
38
41
  'from-json': core_1.Flags.string({
@@ -52,8 +55,10 @@ class TasksSchedule extends base_command_1.BaseCommand {
52
55
  }
53
56
  const recurrence = flags.cron
54
57
  ? (0, prompt_1.mergeDefined)({}, {
55
- pattern: flags.cron,
56
- timezone: flags.timezone,
58
+ // Stored as typed otherwise: a padded pattern came back padded from `show --json`, and a blank
59
+ // `--timezone ""` printed a recurrence with no zone at all (OSK-21531).
60
+ pattern: flags.cron.trim(),
61
+ timezone: flags.timezone?.trim() || undefined,
57
62
  maxRuns: flags['max-runs'],
58
63
  endDate: flags['end-date'],
59
64
  })
@@ -65,6 +70,11 @@ class TasksSchedule extends base_command_1.BaseCommand {
65
70
  if (!payload.scheduledFor || typeof payload.scheduledFor !== 'string') {
66
71
  this.error('A first-fire time is required. Pass --at <ISO-8601 UTC ending in Z> or include scheduledFor in --from-json using the same format.', { exit: 1 });
67
72
  }
73
+ // Said here, in the user's words: the server's zod issue names the REST field (`scheduledFor`) and is the same
74
+ // text for a missing Z, a numeric offset and a day that does not exist (OSK-21507).
75
+ const instant = (0, iso_instant_1.parseUtcInstant)(payload.scheduledFor, flags.at !== undefined ? '--at' : 'scheduledFor');
76
+ if (!instant.ok)
77
+ this.error(instant.message, { exit: 2 });
68
78
  let result;
69
79
  try {
70
80
  result = await data_provider_1.dataService.scheduleTask({
@@ -83,8 +93,20 @@ class TasksSchedule extends base_command_1.BaseCommand {
83
93
  const ref = t.identifier ?? (t.taskNumber ? `#${t.taskNumber}` : '');
84
94
  this.log(`Scheduled task ${t.id ?? t._id ?? taskRef}${ref ? ` (${ref})` : ''} for ${payload.scheduledFor}`);
85
95
  if (t.recurrence?.pattern) {
86
- const tz = t.recurrence.timezone ? ` (${t.recurrence.timezone})` : '';
87
- this.log(`Recurrence: ${t.recurrence.pattern}${tz}`);
96
+ // Always name the zone: the pattern is read in UTC unless one was set, and a rule with no zone beside it
97
+ // is exactly what could not be told apart from a local-time one.
98
+ const tz = t.recurrence.timezone?.trim() || 'UTC';
99
+ this.log(`Recurrence: ${t.recurrence.pattern} (${tz})`);
100
+ const reading = (0, task_recurrence_summary_1.describeCronPattern)(t.recurrence.pattern);
101
+ if (reading) {
102
+ this.log(` ${reading.text}`);
103
+ if (reading.dayOfMonthOrWeekday) {
104
+ this.log(' With both a day of the month and a weekday, it fires when EITHER matches, not only when both do.');
105
+ }
106
+ }
107
+ // The instant the server resolved `--end-date` to, so a calendar date is never a guess.
108
+ if (t.recurrence.endDate)
109
+ this.log(`Ends: ${t.recurrence.endDate}`);
88
110
  }
89
111
  }
90
112
  }
@@ -23,6 +23,7 @@ const principal_display_1 = require("../../lib/principal-display");
23
23
  const task_economics_line_1 = require("../../lib/task-economics-line");
24
24
  const format_1 = require("../../lib/format");
25
25
  const task_recurrence_outcome_1 = require("../../lib/task-recurrence-outcome");
26
+ const task_recurrence_summary_1 = require("../../lib/task-recurrence-summary");
26
27
  const record_output_1 = require("../../lib/record-output");
27
28
  /**
28
29
  * Where to look next — as COMMANDS, not as facts about the task.
@@ -441,6 +442,9 @@ class TasksShow extends base_command_1.BaseCommand {
441
442
  this.log(line);
442
443
  for (const line of recurrenceArmFailureLines(t, this.config.bin))
443
444
  this.log(line);
445
+ // That the Task recurs, by what rule and in which zone (OSK-21531): only `--json` carried it.
446
+ for (const line of (0, task_recurrence_summary_1.recurrenceSummaryLines)(t))
447
+ this.log(line);
444
448
  /*
445
449
  * Closing is the actor's call — a Task may be `done` with work still
446
450
  * outstanding, and `resolution.closure` is where the server recorded what
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `skrr agenda trigger list <agent>` and the triggers it did not show (OSK-21454).
3
+ *
4
+ * `/api/agents/:id/triggers` is the legacy per-agent facade and lists `scope: 'agent'` rows only. A
5
+ * passive agent (`agents create --initial-always-on off`) still owns system-managed Commitment
6
+ * triggers - the daily self-reflection check is `commitment-check:obj_agent_reflection_*`, scope
7
+ * `commitment_watcher` - so the one command whose name says "list this agent's triggers" answered
8
+ * "No triggers." while `triggers list --ref-entity agent:<id>` and the web Upcoming page listed it.
9
+ *
10
+ * The universal list, filtered by the agent ref, is the truth; this reads it and returns the rows the
11
+ * facade left out, marked so a person can tell the platform's from their own.
12
+ */
13
+ export type TriggerRow = Record<string, unknown>;
14
+ /** Created and run by the platform for a Commitment, not authored by the person. */
15
+ export declare function isSystemManaged(row: TriggerRow): boolean;
16
+ /**
17
+ * The universal-list rows the per-agent facade did not return, each tagged `systemManaged`.
18
+ * Rows already listed are not repeated; a row with no id cannot be matched and is kept.
19
+ */
20
+ export declare function triggersMissingFromAgentList(listed: TriggerRow[], universal: TriggerRow[]): TriggerRow[];
21
+ /** The platform marked this managed trigger suspended (Always-On is off for the agent). */
22
+ export declare function isSuspended(row: TriggerRow): boolean;
23
+ /** What the SOURCE column says: the platform, the scope it belongs to, a turn capture, or nothing. */
24
+ export declare function sourceLabel(row: TriggerRow, unconfirmedTurnCapture: boolean): string;
25
+ /** The legend printed under a table that includes platform-managed rows. */
26
+ export declare function systemManagedLegend(agentId: string, bin?: string): string[];
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /**
3
+ * `skrr agenda trigger list <agent>` and the triggers it did not show (OSK-21454).
4
+ *
5
+ * `/api/agents/:id/triggers` is the legacy per-agent facade and lists `scope: 'agent'` rows only. A
6
+ * passive agent (`agents create --initial-always-on off`) still owns system-managed Commitment
7
+ * triggers - the daily self-reflection check is `commitment-check:obj_agent_reflection_*`, scope
8
+ * `commitment_watcher` - so the one command whose name says "list this agent's triggers" answered
9
+ * "No triggers." while `triggers list --ref-entity agent:<id>` and the web Upcoming page listed it.
10
+ *
11
+ * The universal list, filtered by the agent ref, is the truth; this reads it and returns the rows the
12
+ * facade left out, marked so a person can tell the platform's from their own.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.isSystemManaged = isSystemManaged;
16
+ exports.triggersMissingFromAgentList = triggersMissingFromAgentList;
17
+ exports.isSuspended = isSuspended;
18
+ exports.sourceLabel = sourceLabel;
19
+ exports.systemManagedLegend = systemManagedLegend;
20
+ function idOf(row) {
21
+ return typeof row.id === 'string' ? row.id : '';
22
+ }
23
+ function scopeKindOf(row) {
24
+ const scope = row.scope;
25
+ return typeof scope?.kind === 'string' ? scope.kind : '';
26
+ }
27
+ /** Created and run by the platform for a Commitment, not authored by the person. */
28
+ function isSystemManaged(row) {
29
+ return row.producerOwned === true || /^commitment-(check|reflect):/.test(idOf(row));
30
+ }
31
+ /**
32
+ * The universal-list rows the per-agent facade did not return, each tagged `systemManaged`.
33
+ * Rows already listed are not repeated; a row with no id cannot be matched and is kept.
34
+ */
35
+ function triggersMissingFromAgentList(listed, universal) {
36
+ const known = new Set(listed.map(idOf).filter(Boolean));
37
+ return universal
38
+ .filter((row) => !idOf(row) || !known.has(idOf(row)))
39
+ .map((row) => ({ ...row, systemManaged: isSystemManaged(row) }));
40
+ }
41
+ /** The platform marked this managed trigger suspended (Always-On is off for the agent). */
42
+ function isSuspended(row) {
43
+ const management = row.management;
44
+ return management?.suspended === true;
45
+ }
46
+ /** What the SOURCE column says: the platform, the scope it belongs to, a turn capture, or nothing. */
47
+ function sourceLabel(row, unconfirmedTurnCapture) {
48
+ if (row.systemManaged === true)
49
+ return isSuspended(row) ? 'system, suspended' : 'system';
50
+ if (unconfirmedTurnCapture)
51
+ return 'turn*';
52
+ if (row.systemManaged === false)
53
+ return scopeKindOf(row) || '-';
54
+ return '-';
55
+ }
56
+ /** The legend printed under a table that includes platform-managed rows. */
57
+ function systemManagedLegend(agentId, bin = 'skrr') {
58
+ return [
59
+ 'system = created and run by the platform for a Commitment, not by you (for example the daily self-reflection check every agent is seeded with, passive or not).',
60
+ ' "suspended" = the platform is holding it because Always-On is off for this agent.',
61
+ ` Find the Commitment behind one with: ${bin} commitments list --agent ${agentId} --include-system`,
62
+ ];
63
+ }