@skrr-ai/cli 0.1.127 → 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 (165) hide show
  1. package/dist/base-command.d.ts +35 -0
  2. package/dist/base-command.js +107 -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/chat.js +7 -0
  6. package/dist/commands/agents/create.js +3 -1
  7. package/dist/commands/browser/persistent-profile.d.ts +22 -0
  8. package/dist/commands/browser/persistent-profile.js +46 -0
  9. package/dist/commands/commitments/action-history.js +1 -1
  10. package/dist/commands/commitments/cases.js +3 -3
  11. package/dist/commands/commitments/check-now.d.ts +10 -2
  12. package/dist/commands/commitments/check-now.js +53 -3
  13. package/dist/commands/commitments/create.d.ts +6 -14
  14. package/dist/commands/commitments/create.js +64 -7
  15. package/dist/commands/commitments/doctor.js +3 -1
  16. package/dist/commands/commitments/effective-policy.d.ts +11 -1
  17. package/dist/commands/commitments/effective-policy.js +90 -4
  18. package/dist/commands/commitments/explain.js +10 -1
  19. package/dist/commands/commitments/hypotheses.js +12 -12
  20. package/dist/commands/commitments/next-tick.js +5 -0
  21. package/dist/commands/commitments/observations.js +18 -17
  22. package/dist/commands/commitments/pack/list.js +2 -1
  23. package/dist/commands/commitments/pack/plan.d.ts +6 -0
  24. package/dist/commands/commitments/pack/plan.js +19 -0
  25. package/dist/commands/commitments/plan.d.ts +14 -0
  26. package/dist/commands/commitments/plan.js +32 -0
  27. package/dist/commands/commitments/preflight.d.ts +20 -1
  28. package/dist/commands/commitments/preflight.js +118 -12
  29. package/dist/commands/commitments/show.d.ts +9 -0
  30. package/dist/commands/commitments/show.js +14 -1
  31. package/dist/commands/commitments/trigger-status.js +8 -0
  32. package/dist/commands/commitments/update.d.ts +1 -0
  33. package/dist/commands/commitments/update.js +45 -4
  34. package/dist/commands/commitments/wake-capabilities.js +13 -0
  35. package/dist/commands/compass/archive.js +4 -0
  36. package/dist/commands/compass/commitments.js +15 -1
  37. package/dist/commands/compass/create.js +11 -3
  38. package/dist/commands/compass/effective-policy.d.ts +1 -0
  39. package/dist/commands/compass/effective-policy.js +8 -7
  40. package/dist/commands/compass/revert.d.ts +8 -0
  41. package/dist/commands/compass/revert.js +25 -1
  42. package/dist/commands/compass/revisions.d.ts +6 -0
  43. package/dist/commands/compass/revisions.js +26 -2
  44. package/dist/commands/compass/tactic.d.ts +10 -3
  45. package/dist/commands/compass/tactic.js +22 -7
  46. package/dist/commands/compass/update.js +19 -3
  47. package/dist/commands/followups/act.js +1 -1
  48. package/dist/commands/followups/cancel.js +13 -0
  49. package/dist/commands/followups/observability.js +20 -1
  50. package/dist/commands/followups/plans/create.js +12 -1
  51. package/dist/commands/followups/show.js +13 -0
  52. package/dist/commands/followups/watch.js +1 -1
  53. package/dist/commands/goals/planner-config.d.ts +11 -0
  54. package/dist/commands/goals/planner-config.js +16 -0
  55. package/dist/commands/harnesses/list.js +1 -1
  56. package/dist/commands/machines/update.d.ts +5 -3
  57. package/dist/commands/machines/update.js +70 -9
  58. package/dist/commands/northstars/create.js +3 -1
  59. package/dist/commands/northstars/governance/configure.d.ts +4 -0
  60. package/dist/commands/northstars/governance/configure.js +76 -10
  61. package/dist/commands/northstars/intents/decide.d.ts +6 -0
  62. package/dist/commands/northstars/intents/decide.js +68 -15
  63. package/dist/commands/northstars/intents/list.d.ts +2 -1
  64. package/dist/commands/northstars/intents/list.js +15 -5
  65. package/dist/commands/northstars/intents.d.ts +26 -0
  66. package/dist/commands/northstars/intents.js +49 -0
  67. package/dist/commands/northstars/list.d.ts +1 -0
  68. package/dist/commands/northstars/list.js +24 -3
  69. package/dist/commands/northstars/recommendations/list.d.ts +1 -1
  70. package/dist/commands/northstars/recommendations/list.js +3 -3
  71. package/dist/commands/northstars/recommendations.d.ts +19 -0
  72. package/dist/commands/northstars/recommendations.js +42 -0
  73. package/dist/commands/northstars/review-now.d.ts +1 -0
  74. package/dist/commands/northstars/review-now.js +15 -4
  75. package/dist/commands/northstars/reviews.d.ts +1 -0
  76. package/dist/commands/northstars/reviews.js +34 -6
  77. package/dist/commands/northstars/update.js +8 -1
  78. package/dist/commands/proposals/list.js +11 -2
  79. package/dist/commands/proposals/show.js +9 -1
  80. package/dist/commands/spaces/auto-executor/preview.d.ts +14 -1
  81. package/dist/commands/spaces/auto-executor/preview.js +35 -1
  82. package/dist/commands/spaces/auto-executor/run.d.ts +17 -0
  83. package/dist/commands/spaces/auto-executor/run.js +60 -2
  84. package/dist/commands/spaces/auto-executor/runs.d.ts +9 -1
  85. package/dist/commands/spaces/auto-executor/runs.js +18 -1
  86. package/dist/commands/spaces/files/upload.js +1 -2
  87. package/dist/commands/spaces/heartbeats/create.js +7 -5
  88. package/dist/commands/spaces/heartbeats/update.js +5 -3
  89. package/dist/commands/spaces/index.js +23 -0
  90. package/dist/commands/store/artifacts/releases.d.ts +20 -0
  91. package/dist/commands/store/artifacts/releases.js +85 -0
  92. package/dist/commands/store/library.d.ts +14 -0
  93. package/dist/commands/store/library.js +71 -0
  94. package/dist/commands/tasks/list.d.ts +48 -0
  95. package/dist/commands/tasks/list.js +141 -9
  96. package/dist/commands/tasks/remind.js +23 -11
  97. package/dist/commands/tasks/retry-schedule.js +6 -3
  98. package/dist/commands/tasks/schedule.js +29 -7
  99. package/dist/commands/tasks/show.js +4 -0
  100. package/dist/generated/agent-guides.json +69 -69
  101. package/dist/lib/agent-trigger-listing.d.ts +26 -0
  102. package/dist/lib/agent-trigger-listing.js +63 -0
  103. package/dist/lib/agentic-stream.d.ts +9 -0
  104. package/dist/lib/agentic-stream.js +152 -41
  105. package/dist/lib/command-miss.js +11 -2
  106. package/dist/lib/commitment-attention-copy.d.ts +96 -0
  107. package/dist/lib/commitment-attention-copy.js +209 -0
  108. package/dist/lib/commitment-ledgers.js +3 -1
  109. package/dist/lib/commitment-loop-status.d.ts +6 -0
  110. package/dist/lib/commitment-loop-status.js +16 -6
  111. package/dist/lib/commitments.d.ts +112 -0
  112. package/dist/lib/commitments.js +250 -2
  113. package/dist/lib/compass-body-help.d.ts +20 -0
  114. package/dist/lib/compass-body-help.js +61 -0
  115. package/dist/lib/compass-refusals.d.ts +35 -0
  116. package/dist/lib/compass-refusals.js +50 -0
  117. package/dist/lib/compasses.d.ts +82 -4
  118. package/dist/lib/compasses.js +232 -12
  119. package/dist/lib/followups.d.ts +22 -1
  120. package/dist/lib/followups.js +21 -1
  121. package/dist/lib/format.d.ts +11 -0
  122. package/dist/lib/format.js +29 -0
  123. package/dist/lib/initiatives.d.ts +9 -2
  124. package/dist/lib/initiatives.js +13 -2
  125. package/dist/lib/iso-instant.d.ts +52 -0
  126. package/dist/lib/iso-instant.js +162 -0
  127. package/dist/lib/machines.d.ts +14 -1
  128. package/dist/lib/machines.js +24 -0
  129. package/dist/lib/northstars.d.ts +201 -24
  130. package/dist/lib/northstars.js +655 -27
  131. package/dist/lib/proposal-decision-command.js +21 -1
  132. package/dist/lib/proposals.d.ts +26 -1
  133. package/dist/lib/proposals.js +167 -0
  134. package/dist/lib/rate-limit-retry.d.ts +5 -0
  135. package/dist/lib/rate-limit-retry.js +19 -0
  136. package/dist/lib/roles.d.ts +1 -1
  137. package/dist/lib/space-charter.js +9 -0
  138. package/dist/lib/store-artifact-releases.d.ts +39 -0
  139. package/dist/lib/store-artifact-releases.js +12 -0
  140. package/dist/lib/store-library.d.ts +37 -0
  141. package/dist/lib/store-library.js +12 -0
  142. package/dist/lib/task-asset-upload.js +1 -2
  143. package/dist/lib/task-calendar.d.ts +19 -0
  144. package/dist/lib/task-calendar.js +72 -0
  145. package/dist/lib/task-recurrence-outcome.js +3 -1
  146. package/dist/lib/task-recurrence-summary.d.ts +15 -0
  147. package/dist/lib/task-recurrence-summary.js +228 -0
  148. package/dist/lib/task-reminder.d.ts +28 -0
  149. package/dist/lib/task-reminder.js +50 -0
  150. package/dist/lib/triggers.d.ts +13 -2
  151. package/dist/lib/triggers.js +31 -13
  152. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +11 -1
  153. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.d.ts +123 -0
  154. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.js +160 -0
  155. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -0
  156. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +17 -0
  157. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +11 -1
  158. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.d.ts +123 -0
  159. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.js +173 -0
  160. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -0
  161. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -0
  162. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  163. package/dist/node_modules/@skrr-ai/data-provider/index.js +6922 -6828
  164. package/oclif.manifest.json +612 -62
  165. package/package.json +4 -4
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describeCronPattern = describeCronPattern;
4
+ exports.recurrenceSummaryLines = recurrenceSummaryLines;
5
+ /**
6
+ * What `skrr tasks show` says about a Task that recurs (OSK-21531).
7
+ *
8
+ * The human view printed "it is scheduled for 2026-12-01" and nothing else: no word that the Task
9
+ * recurs, no pattern, no zone. The rule lived only in `--json` (`recurrence.pattern`, `.timezone`,
10
+ * `.runsCompleted`), so the cron semantics that surprise people - a day of month AND a weekday
11
+ * fire on EITHER, not both - were invisible exactly where someone checks what they just set up.
12
+ *
13
+ * `describeCronPattern` reads the five-field form the scheduler runs. It is deliberately modest: a
14
+ * pattern it cannot read plainly (names it does not know, `L`, `?`, six fields) returns `null` and
15
+ * the caller prints the raw pattern alone rather than a confident wrong sentence.
16
+ */
17
+ const iso_instant_1 = require("./iso-instant");
18
+ const WEEKDAYS = [
19
+ 'Sunday',
20
+ 'Monday',
21
+ 'Tuesday',
22
+ 'Wednesday',
23
+ 'Thursday',
24
+ 'Friday',
25
+ 'Saturday',
26
+ ];
27
+ const MONTHS = [
28
+ 'January',
29
+ 'February',
30
+ 'March',
31
+ 'April',
32
+ 'May',
33
+ 'June',
34
+ 'July',
35
+ 'August',
36
+ 'September',
37
+ 'October',
38
+ 'November',
39
+ 'December',
40
+ ];
41
+ const MINUTE = { min: 0, max: 59 };
42
+ const HOUR = { min: 0, max: 23 };
43
+ const DAY_OF_MONTH = { min: 1, max: 31 };
44
+ const MONTH = {
45
+ min: 1,
46
+ max: 12,
47
+ names: MONTHS.map((m) => m.slice(0, 3).toUpperCase()),
48
+ nameBase: 1,
49
+ };
50
+ const DAY_OF_WEEK = {
51
+ min: 0,
52
+ max: 7,
53
+ names: WEEKDAYS.map((d) => d.slice(0, 3).toUpperCase()),
54
+ nameBase: 0,
55
+ };
56
+ /** The values a field names, or `null` when it is not a form this reader understands. */
57
+ function expandField(raw, domain) {
58
+ const values = new Set();
59
+ for (const part of raw.split(',')) {
60
+ const [rangeText, stepText, extra] = part.split('/');
61
+ if (extra !== undefined || rangeText === undefined || rangeText === '')
62
+ return null;
63
+ const step = stepText === undefined ? 1 : Number(stepText);
64
+ if (!Number.isInteger(step) || step < 1)
65
+ return null;
66
+ const toNumber = (token) => {
67
+ if (/^\d+$/.test(token))
68
+ return Number(token);
69
+ const index = domain.names?.indexOf(token.toUpperCase()) ?? -1;
70
+ return index >= 0 ? index + (domain.nameBase ?? 0) : null;
71
+ };
72
+ let from;
73
+ let to;
74
+ if (rangeText === '*') {
75
+ from = domain.min;
76
+ to = domain === DAY_OF_WEEK ? 6 : domain.max;
77
+ }
78
+ else if (rangeText.includes('-')) {
79
+ const [a, b, more] = rangeText.split('-');
80
+ if (more !== undefined)
81
+ return null;
82
+ from = toNumber(a);
83
+ to = toNumber(b);
84
+ }
85
+ else {
86
+ from = toNumber(rangeText);
87
+ // `5/10` runs from 5 to the top of the field.
88
+ to = stepText === undefined ? from : domain === DAY_OF_WEEK ? 6 : domain.max;
89
+ }
90
+ if (from === null || to === null || from > to)
91
+ return null;
92
+ if (from < domain.min || to > domain.max)
93
+ return null;
94
+ for (let value = from; value <= to; value += step)
95
+ values.add(domain === DAY_OF_WEEK && value === 7 ? 0 : value);
96
+ }
97
+ return [...values].sort((a, b) => a - b);
98
+ }
99
+ function ordinal(n) {
100
+ const tail = n % 100;
101
+ if (tail >= 11 && tail <= 13)
102
+ return `${n}th`;
103
+ return `${n}${{ 1: 'st', 2: 'nd', 3: 'rd' }[n % 10] ?? 'th'}`;
104
+ }
105
+ function joinList(items, word = 'and') {
106
+ if (items.length <= 1)
107
+ return items.join('');
108
+ return `${items.slice(0, -1).join(', ')} ${word} ${items[items.length - 1]}`;
109
+ }
110
+ const pad = (n) => String(n).padStart(2, '0');
111
+ /** Evenly spaced from the field's start, e.g. 0,15,30,45 -> 15. */
112
+ function evenStep(values, size) {
113
+ if (values.length < 2 || values[0] !== 0)
114
+ return null;
115
+ const step = values[1] - values[0];
116
+ if (step < 2 || size % step !== 0 || values.length !== size / step)
117
+ return null;
118
+ return values.every((v, i) => v === i * step) ? step : null;
119
+ }
120
+ function describeTime(minute, hour) {
121
+ const minutes = expandField(minute, MINUTE);
122
+ const hours = expandField(hour, HOUR);
123
+ if (!minutes || !hours)
124
+ return null;
125
+ const everyHour = hours.length === 24;
126
+ if (everyHour && minutes.length === 60)
127
+ return 'every minute';
128
+ if (everyHour) {
129
+ const step = evenStep(minutes, 60);
130
+ if (step)
131
+ return `every ${step} minutes`;
132
+ if (minutes.length === 1)
133
+ return minutes[0] === 0
134
+ ? 'at the start of every hour'
135
+ : `at minute ${minutes[0]} of every hour`;
136
+ return `at minutes ${joinList(minutes.map(String))} of every hour`;
137
+ }
138
+ if (minutes.length === 1 && hours.length <= 6) {
139
+ return `at ${joinList(hours.map((h) => `${pad(h)}:${pad(minutes[0])}`))}`;
140
+ }
141
+ const hourStep = evenStep(hours, 24);
142
+ if (minutes.length === 1 && hourStep) {
143
+ return `every ${hourStep} hours, at minute ${minutes[0]}`;
144
+ }
145
+ return `at minute${minutes.length > 1 ? 's' : ''} ${joinList(minutes.map(String))} past hour${hours.length > 1 ? 's' : ''} ${joinList(hours.map(String))}`;
146
+ }
147
+ function describeWeekdays(values) {
148
+ const key = values.join(',');
149
+ if (key === '1,2,3,4,5')
150
+ return 'Monday to Friday';
151
+ if (key === '0,6')
152
+ return 'Saturday and Sunday';
153
+ return joinList(values.map((d) => WEEKDAYS[d]));
154
+ }
155
+ /**
156
+ * Plain language for a five-field cron pattern, or `null` when this reader cannot say it faithfully.
157
+ */
158
+ function describeCronPattern(pattern) {
159
+ const fields = String(pattern ?? '')
160
+ .trim()
161
+ .split(/\s+/);
162
+ if (fields.length !== 5)
163
+ return null;
164
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
165
+ const time = describeTime(minute, hour);
166
+ const months = month === '*' ? [] : expandField(month, MONTH);
167
+ const monthDays = dayOfMonth === '*' ? [] : expandField(dayOfMonth, DAY_OF_MONTH);
168
+ const weekdays = dayOfWeek === '*' ? [] : expandField(dayOfWeek, DAY_OF_WEEK);
169
+ if (!time || !months || !monthDays || !weekdays)
170
+ return null;
171
+ const dayParts = [];
172
+ if (monthDays.length > 0) {
173
+ dayParts.push(`on the ${joinList(monthDays.map(ordinal))} of the month`);
174
+ }
175
+ if (weekdays.length > 0) {
176
+ dayParts.push(`on ${describeWeekdays(weekdays)}`);
177
+ }
178
+ const dayText = dayParts.length === 0 ? 'every day' : dayParts.join(' OR ');
179
+ const monthText = months.length > 0 ? ` in ${joinList(months.map((m) => MONTHS[m - 1]))}` : '';
180
+ return {
181
+ text: `${time}, ${dayText}${monthText}`,
182
+ dayOfMonthOrWeekday: monthDays.length > 0 && weekdays.length > 0,
183
+ };
184
+ }
185
+ function instantText(value) {
186
+ if (typeof value !== 'string' && !(value instanceof Date))
187
+ return null;
188
+ const date = new Date(value);
189
+ return Number.isNaN(date.getTime())
190
+ ? typeof value === 'string'
191
+ ? value
192
+ : null
193
+ : (0, iso_instant_1.formatUtcInstant)(date);
194
+ }
195
+ /**
196
+ * The block `tasks show` prints for a recurring Task: the rule, its zone, a plain reading, when it
197
+ * next fires, how many runs it has used, and when it ends. Empty for a Task that does not recur.
198
+ */
199
+ function recurrenceSummaryLines(task) {
200
+ const recurrence = task.recurrence;
201
+ const pattern = typeof recurrence?.pattern === 'string' ? recurrence.pattern.trim() : '';
202
+ if (!recurrence || !pattern)
203
+ return [];
204
+ const zone = typeof recurrence.timezone === 'string' && recurrence.timezone.trim()
205
+ ? recurrence.timezone.trim()
206
+ : 'UTC';
207
+ const reading = describeCronPattern(pattern);
208
+ const lines = [`Recurs: ${pattern} (${zone})${reading ? ` - ${reading.text}` : ''}`];
209
+ const facts = [];
210
+ if (recurrence.paused === true)
211
+ facts.push('paused');
212
+ const next = instantText(recurrence.nextRunAt ?? task.scheduledFor);
213
+ if (next)
214
+ facts.push(`next ${next}`);
215
+ const used = Number.isFinite(Number(recurrence.runsCompleted))
216
+ ? Number(recurrence.runsCompleted)
217
+ : 0;
218
+ const cap = Number(recurrence.maxRuns);
219
+ facts.push(Number.isInteger(cap) && cap > 0 ? `${used} of ${cap} runs` : `${used} runs so far, no cap`);
220
+ const ends = instantText(recurrence.endDate);
221
+ if (ends)
222
+ facts.push(`ends ${ends}`);
223
+ lines.push(` ${facts.join(', ')}`);
224
+ if (reading?.dayOfMonthOrWeekday) {
225
+ lines.push(' Cron note: with both a day of the month and a weekday, it fires when EITHER matches, not only when both do.');
226
+ }
227
+ return lines;
228
+ }
@@ -0,0 +1,28 @@
1
+ export type ReminderOutcome = {
2
+ /** The Task already had a live reminder with these words, and that row was returned. */
3
+ reused?: boolean;
4
+ /** ...and it was moved to the requested time. */
5
+ moved?: boolean;
6
+ /** Where it was before it moved. */
7
+ previousRunAt?: string;
8
+ };
9
+ export type ReminderTrigger = {
10
+ id?: string;
11
+ once?: {
12
+ runAt?: string;
13
+ };
14
+ reminderOutcome?: ReminderOutcome;
15
+ };
16
+ /**
17
+ * The lines to print after a successful `POST /api/triggers`.
18
+ *
19
+ * `requested` is the instant the user asked for; the stored `once.runAt` wins when the
20
+ * server returned one, because that is the time that will fire.
21
+ */
22
+ export declare function reminderReceiptLines(input: {
23
+ title: string;
24
+ trigger: ReminderTrigger | null | undefined;
25
+ requested: Date;
26
+ bin?: string;
27
+ timeZone?: string;
28
+ }): string[];
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reminderReceiptLines = reminderReceiptLines;
4
+ /**
5
+ * The receipt `skrr tasks remind` prints (OSK-21505).
6
+ *
7
+ * It used to say "Reminder set for <title> at <toLocaleString()>." for every outcome: a
8
+ * local time with no zone and a doubled full stop, no id to cancel with, and the same
9
+ * words when the server had silently MOVED the Task's one existing reminder ("two
10
+ * identical reminders are one reminder") - so a person who set "2 days before" and then
11
+ * "1 hour before" lost the first and was never told.
12
+ *
13
+ * The server now answers `reminderOutcome` beside the trigger (additive; an older server
14
+ * omits it, and the receipt falls back to the plain "set" wording rather than guessing).
15
+ */
16
+ const iso_instant_1 = require("./iso-instant");
17
+ function readInstant(value) {
18
+ if (typeof value !== 'string' && !(value instanceof Date))
19
+ return null;
20
+ const date = new Date(value);
21
+ return Number.isNaN(date.getTime()) ? null : date;
22
+ }
23
+ /**
24
+ * The lines to print after a successful `POST /api/triggers`.
25
+ *
26
+ * `requested` is the instant the user asked for; the stored `once.runAt` wins when the
27
+ * server returned one, because that is the time that will fire.
28
+ */
29
+ function reminderReceiptLines(input) {
30
+ const { title, trigger, requested, bin = 'skrr', timeZone } = input;
31
+ const stored = readInstant(trigger?.once?.runAt) ?? requested;
32
+ const at = (0, iso_instant_1.describeInstant)(stored, timeZone);
33
+ const outcome = trigger?.reminderOutcome;
34
+ const lines = [];
35
+ if (outcome?.reused && outcome.moved) {
36
+ const previous = readInstant(outcome.previousRunAt);
37
+ lines.push(`Reminder moved for ${title} to ${at}${previous ? ` (was ${(0, iso_instant_1.describeInstant)(previous, timeZone)})` : ''}`);
38
+ lines.push(' A Task keeps one reminder per wording, so the earlier time no longer fires.');
39
+ }
40
+ else if (outcome?.reused) {
41
+ lines.push(`Reminder already set for ${title} at ${at}; nothing changed`);
42
+ }
43
+ else {
44
+ lines.push(`Reminder set for ${title} at ${at}`);
45
+ }
46
+ if (trigger?.id) {
47
+ lines.push(` Reminder id: ${trigger.id} Cancel it with: ${bin} followups cancel ${trigger.id}`);
48
+ }
49
+ return lines;
50
+ }
@@ -1,6 +1,12 @@
1
1
  import { type EmbeddedActionRef, type TriggerIntentUnsupportedItem, type TriggerPreflightReport, type TriggerSensorConfig } from '@skrr-ai/data-provider';
2
2
  export declare const TRIGGER_KINDS: readonly ["schedule", "once", "event", "stochastic", "self_scheduled", "on_demand", "form", "chat_message"];
3
- export declare const TRIGGER_SCOPE_KINDS: readonly ["agent", "group_chat_subscriber", "space_oracle", "space_subscriber", "key_result_watcher", "task_watcher", "goal_watcher", "entity_reminder"];
3
+ /**
4
+ * The scope kinds the trigger endpoints accept. NOT a list of its own: `@skrr-ai/data-provider` owns it
5
+ * (`TRIGGER_API_SCOPE_KINDS`) and the API route reads the same array. The hand-written copy that stood here
6
+ * lacked `commitment_watcher` and `space_cycle`, so `triggers list --scope commitment_watcher` was refused locally
7
+ * for a scope the server serves (OSK-21518). `trigger-scope-kinds-parity.spec.ts` pins it.
8
+ */
9
+ export declare const TRIGGER_SCOPE_KINDS: readonly ["agent", "group_chat_subscriber", "space_oracle", "space_subscriber", "key_result_watcher", "task_watcher", "goal_watcher", "commitment_watcher", "entity_reminder", "space_cycle"];
4
10
  /**
5
11
  * Executor modes this CLI will let you author.
6
12
  *
@@ -508,7 +514,12 @@ export declare function formatDuplicateSubscriptionWarning(trigger: unknown): {
508
514
  * "armed" when it will fire and "inert" until enabled — so a freshly-created
509
515
  * trigger did not plainly confirm it is live.
510
516
  */
511
- export declare function armedStateLabel(enabled: boolean | undefined): string;
517
+ export declare function armedStateLabel(enabled: boolean | undefined, lastResult?: string | null,
518
+ /** Pass for a `once` trigger: how often its single fire ran. A spent one-shot is not waiting to be enabled (OSK-21509). */
519
+ oneShot?: {
520
+ fires?: number;
521
+ failures?: number;
522
+ } | null): string;
512
523
  /**
513
524
  * Human phrasing for an effect trigger's write mode (OSK-4886).
514
525
  *
@@ -66,16 +66,13 @@ exports.TRIGGER_KINDS = [
66
66
  'form',
67
67
  'chat_message',
68
68
  ];
69
- exports.TRIGGER_SCOPE_KINDS = [
70
- 'agent',
71
- 'group_chat_subscriber',
72
- 'space_oracle',
73
- 'space_subscriber',
74
- 'key_result_watcher',
75
- 'task_watcher',
76
- 'goal_watcher',
77
- 'entity_reminder',
78
- ];
69
+ /**
70
+ * The scope kinds the trigger endpoints accept. NOT a list of its own: `@skrr-ai/data-provider` owns it
71
+ * (`TRIGGER_API_SCOPE_KINDS`) and the API route reads the same array. The hand-written copy that stood here
72
+ * lacked `commitment_watcher` and `space_cycle`, so `triggers list --scope commitment_watcher` was refused locally
73
+ * for a scope the server serves (OSK-21518). `trigger-scope-kinds-parity.spec.ts` pins it.
74
+ */
75
+ exports.TRIGGER_SCOPE_KINDS = data_provider_1.TRIGGER_API_SCOPE_KINDS;
79
76
  /**
80
77
  * Executor modes this CLI will let you author.
81
78
  *
@@ -346,12 +343,15 @@ exports.SCOPE_IMPLIED_REF_KEY = {
346
343
  space_oracle: 'spaceId',
347
344
  goal_watcher: 'goalId',
348
345
  group_chat_subscriber: 'groupChatId',
346
+ space_cycle: 'spaceId',
349
347
  };
350
348
  /** Ref keys each multi-ref scope expects, so the error can name them. */
351
349
  const SCOPE_EXPECTED_REF_KEYS = {
352
350
  task_watcher: ['taskId', 'watcherAgentId'],
353
351
  key_result_watcher: ['keyResultId', 'watcherAgentId'],
354
352
  space_subscriber: ['spaceId', 'agentId', 'subscriberUserId'],
353
+ // `commitmentId` is the one the resolver requires; it also reads optional agentId/goalId/spaceId/... refs.
354
+ commitment_watcher: ['commitmentId'],
355
355
  };
356
356
  function parseKeyValueFlags(values, flagName, scopeKind) {
357
357
  const refs = {};
@@ -1241,8 +1241,24 @@ function formatDuplicateSubscriptionWarning(trigger) {
1241
1241
  * "armed" when it will fire and "inert" until enabled — so a freshly-created
1242
1242
  * trigger did not plainly confirm it is live.
1243
1243
  */
1244
- function armedStateLabel(enabled) {
1245
- return enabled === false ? 'no — inert until enabled' : 'yes — will fire';
1244
+ function armedStateLabel(enabled, lastResult,
1245
+ /** Pass for a `once` trigger: how often its single fire ran. A spent one-shot is not waiting to be enabled (OSK-21509). */
1246
+ oneShot) {
1247
+ if (enabled === false && oneShot && (oneShot.fires ?? 0) > 0) {
1248
+ return 'no — already fired (a one-shot trigger is spent once it fires)';
1249
+ }
1250
+ if (enabled === false && oneShot && (oneShot.failures ?? 0) > 0) {
1251
+ return 'no — its one fire already ran and did not succeed (a one-shot trigger is spent; see `skrr triggers audit`)';
1252
+ }
1253
+ if (enabled === false)
1254
+ return 'no — inert until enabled';
1255
+ // "Will fire" is the row's intent. A platform-managed row whose owner is fenced (Always-On off) ticks on
1256
+ // schedule and is skipped every time, and the same screen's `Fires:` line says so; the first line a reader
1257
+ // meets must not say the opposite (dogfood df-261031-commitment-authoring, OSK-21103).
1258
+ const skipped = typeof lastResult === 'string' ? /^skipped:(.+)$/.exec(lastResult) : null;
1259
+ return skipped
1260
+ ? `yes — but its last tick was skipped (${skipped[1]}), so it did not run`
1261
+ : 'yes — will fire';
1246
1262
  }
1247
1263
  /**
1248
1264
  * Human phrasing for an effect trigger's write mode (OSK-4886).
@@ -1340,7 +1356,9 @@ function fireRefusalLines(metadata) {
1340
1356
  function renderTriggerSummary(trigger, log, options = {}) {
1341
1357
  log(`ID: ${trigger.id ?? '-'}`);
1342
1358
  log(`Kind: ${trigger.kind ?? '-'}`);
1343
- log(`Armed: ${armedStateLabel(trigger.enabled)}`);
1359
+ log(`Armed: ${armedStateLabel(trigger.enabled, trigger.audit?.lastResult, trigger.kind === 'once'
1360
+ ? { fires: trigger.audit?.fireCount, failures: trigger.audit?.failureCount }
1361
+ : undefined)}`);
1344
1362
  log(`Scope: ${scopeLabel(trigger)}`);
1345
1363
  /*
1346
1364
  * Say when the row is not yours to change.
@@ -69,6 +69,7 @@ const SESSION_TURN_DIFF_LIVE = "session_turn_diff_live_v1";
69
69
  const WORKSPACE_ISOLATION_PROBE = "workspace_isolation_probe_v1";
70
70
  const TOOL_RESPONSE_OUTBOX = "tool_response_outbox_v1";
71
71
  const SESSION_STEER = "session_steer_v1";
72
+ const QUESTION_ACK = "question_ack_v1";
72
73
  const CONNECTION = [
73
74
  toServer("daemon:register", "The daemon introduces itself: identity, version, capabilities, sessions it holds.", {
74
75
  delivery: "acked",
@@ -226,7 +227,16 @@ const SESSIONS = [
226
227
  delivery: "acked",
227
228
  answeredBy: "daemon:claude:question:response"
228
229
  }),
229
- toDaemon("daemon:claude:question:response", "A person's answer to a question.")
230
+ // A daemon that announces `question_ack_v1` says whether it TOOK the answer: the
231
+ // question was still pending and the harness has it ('delivered'), or the daemon had
232
+ // already timed out or been cancelled ('not_pending'). Without the capability the
233
+ // worker settles an answer at the relay handoff, as it always did.
234
+ toDaemon("daemon:claude:question:response", "A person's answer to a question.", {
235
+ delivery: "acked",
236
+ answeredBy: "daemon:claude:question:ack",
237
+ capability: QUESTION_ACK
238
+ }),
239
+ toServer("daemon:claude:question:ack", "Whether this daemon took a person's answer to a question, or had already stopped waiting.", { capability: QUESTION_ACK, payload: { kind: "zod", schema: "daemonQuestionAckSchema" } })
230
240
  ];
231
241
  const TOOLS = [
232
242
  toDaemon("daemon:tool:request", "Run one tool call on this machine.", {
@@ -0,0 +1,123 @@
1
+ /**
2
+ * executorStopReceipt.ts — a daemon-signed proof that a scheduled session stopped.
3
+ *
4
+ * OSK-20777, Gate 1 (docs/architecture/scheduled-publication-custody-enrollment-2026-11-01.md).
5
+ * Today the executor's terminal proof is the daemon reporting about itself over its own socket: the
6
+ * dispatch tuple is checked for consistency but nothing is signed, nothing covers descendant
7
+ * processes, and nothing says what was erased. A receipt is that report, signed with the device key
8
+ * the server already pins on the daemon's row, so a settlement that depends on it depends on the
9
+ * machine and not on whoever reached the socket.
10
+ *
11
+ * No new cryptography. A receipt is a DPoP-shaped device proof (`deviceKey.ts`) under its OWN header
12
+ * `typ`, so the two can never stand in for each other: a request proof fails here with `bad_typ`, and a
13
+ * receipt fails the request verifier the same way. The proof's `ath` commits to the SHA-256 of the
14
+ * canonical receipt body, its `htu` names the dispatch, and the pin decides which key may sign it.
15
+ *
16
+ * Verification is deliberately strict and returns a reason, never a boolean:
17
+ * - a pin is REQUIRED (a receipt with no key to compare against has no authority);
18
+ * - the algorithm admitted is the algorithm of the pin, never a list, so a receipt cannot choose a
19
+ * weaker algorithm than the device enrolled with;
20
+ * - the body is a closed shape with bounded fields;
21
+ * - every field the caller expects (the dispatch the server issued) must match exactly;
22
+ * - a descendant that survived is `not_quiescent`, not a valid receipt with a footnote;
23
+ * - erasure is claimed only for the one scope the owner allowed, `session_temp`.
24
+ *
25
+ * Freshness is the caller's decision (`maxAgeSeconds`). A receipt can legitimately arrive hours after it
26
+ * was signed, replayed from the daemon's response WAL after a reconnect, so it has no short window of its
27
+ * own. Replay protection is the dispatch binding: the server accepts one receipt per `dispatchId`.
28
+ */
29
+ import { type DeviceKeyAlg, type DeviceKeySigner, type DevicePublicJwk, type VerifyFailureReason } from './deviceKey.js';
30
+ /** Header `typ` of an executor stop receipt. Distinct from `DEVICE_PROOF_TYP` on purpose. */
31
+ export declare const EXECUTOR_STOP_RECEIPT_TYP = "skrr-executor-stop-receipt+jwt";
32
+ /** What the daemon may claim to have erased. One scope, decided by the owner (2026-11-01). */
33
+ export declare const EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES: readonly ["session_temp"];
34
+ export type ExecutorStopReceiptErasureScope = (typeof EXECUTOR_STOP_RECEIPT_ERASURE_SCOPES)[number];
35
+ /** Default ceiling on a receipt's age, for a caller that does not decide: one day. */
36
+ export declare const EXECUTOR_STOP_RECEIPT_DEFAULT_MAX_AGE_SECONDS: number;
37
+ export type ExecutorStopReceiptStatus = 'completed' | 'failed' | 'cancelled';
38
+ export interface ExecutorStopReceiptBody {
39
+ version: 1;
40
+ daemonId: string;
41
+ /** The scheduled Run (or Source) the dispatch belongs to. */
42
+ sourceId: string;
43
+ sessionId: string;
44
+ requestId: string;
45
+ dispatchId: string;
46
+ status: ExecutorStopReceiptStatus;
47
+ /** ISO-8601, when the session's process tree was last observed stopped. */
48
+ stoppedAt: string;
49
+ /** Counted after the stop, not assumed. `observed === stopped + survivors`. */
50
+ descendants: {
51
+ observed: number;
52
+ stopped: number;
53
+ survivors: number;
54
+ };
55
+ /** `performed: false` carries no scope: nothing was erased and nothing is claimed. */
56
+ erasure: {
57
+ performed: false;
58
+ } | {
59
+ performed: true;
60
+ scope: ExecutorStopReceiptErasureScope;
61
+ artifacts: number;
62
+ bytes: number;
63
+ };
64
+ }
65
+ export interface ExecutorStopReceipt {
66
+ body: ExecutorStopReceiptBody;
67
+ /** The device-key proof (JWS compact) that commits to `body`. */
68
+ proof: string;
69
+ }
70
+ export type ExecutorStopReceiptFailureReason = VerifyFailureReason | 'bad_receipt' | 'no_pin' | 'binding_mismatch' | 'not_quiescent';
71
+ export type VerifyExecutorStopReceiptResult = {
72
+ valid: true;
73
+ body: ExecutorStopReceiptBody;
74
+ alg: DeviceKeyAlg;
75
+ thumbprint: string;
76
+ signedAtSeconds: number;
77
+ } | {
78
+ valid: false;
79
+ reason: ExecutorStopReceiptFailureReason;
80
+ };
81
+ declare const BINDING_KEYS: readonly ["daemonId", "sourceId", "sessionId", "requestId", "dispatchId"];
82
+ /**
83
+ * Stable JSON: object keys sorted at every level, no whitespace. The signer and the verifier must
84
+ * produce the same bytes from the same body, whatever order a transport reordered its keys into.
85
+ */
86
+ export declare function canonicalReceiptJson(value: unknown): string;
87
+ /** The URI a receipt's proof names. Reserved `.invalid` host: it can never be a real request target. */
88
+ export declare function executorStopReceiptHtu(dispatchId: string): string;
89
+ /** Shape check only. Returns the body as typed, or null when it is not exactly a v1 receipt body. */
90
+ export declare function parseExecutorStopReceiptBody(value: unknown): ExecutorStopReceiptBody | null;
91
+ export interface SignExecutorStopReceiptOptions {
92
+ /** Test hooks; production leaves both unset. */
93
+ iat?: number;
94
+ jti?: string;
95
+ }
96
+ /**
97
+ * Sign a receipt with any `DeviceKeySigner` (a software key, or a hardware handle). Throws on a body
98
+ * that is not exactly a v1 receipt body, so a daemon bug cannot produce a signed receipt the server
99
+ * would only reject later.
100
+ */
101
+ export declare function signExecutorStopReceipt(signer: DeviceKeySigner, body: ExecutorStopReceiptBody, options?: SignExecutorStopReceiptOptions): Promise<ExecutorStopReceipt>;
102
+ export interface VerifyExecutorStopReceiptOptions {
103
+ /** The key pinned on the daemon's row. REQUIRED: without it nothing authorizes the signature. */
104
+ pinnedPublicJwk: DevicePublicJwk | undefined;
105
+ /**
106
+ * What the server issued for this dispatch. Every key given must equal the receipt's, so a receipt for
107
+ * another daemon, Run, session, request or dispatch is `binding_mismatch`.
108
+ */
109
+ expected?: Partial<Pick<ExecutorStopReceiptBody, (typeof BINDING_KEYS)[number]>>;
110
+ /** Ceiling on the receipt's age in seconds. Defaults to one day. */
111
+ maxAgeSeconds?: number;
112
+ /** Tolerated clock skew in seconds. Defaults to the proof verifier's. */
113
+ clockSkewSeconds?: number;
114
+ /** Testing hook. */
115
+ now?: number;
116
+ }
117
+ /**
118
+ * Verify a receipt against the pinned key. Order matters: shape, then signature and pin, then the
119
+ * binding to the dispatch, then what the body claims. A forged receipt is reported as the forgery it
120
+ * is (`sig_invalid`, `pin_mismatch`, `ath_mismatch`), not as a quiescence failure.
121
+ */
122
+ export declare function verifyExecutorStopReceipt(receipt: unknown, options: VerifyExecutorStopReceiptOptions): VerifyExecutorStopReceiptResult;
123
+ export {};