@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
@@ -10,6 +10,7 @@ const base_command_1 = require("../../base-command");
10
10
  const guide_pointer_1 = require("../../lib/guide-pointer");
11
11
  const web_url_1 = require("../../lib/web-url");
12
12
  const prompt_1 = require("../../lib/prompt");
13
+ const commitment_attention_copy_1 = require("../../lib/commitment-attention-copy");
13
14
  const triggers_1 = require("../../lib/triggers");
14
15
  const commitment_product_1 = require("../../lib/commitment-product");
15
16
  const commitments_1 = require("../../lib/commitments");
@@ -153,8 +154,27 @@ function createCadenceFromFlags({ eventDriven, cadence: cadenceInput, cadenceMod
153
154
  * it. `{code, reason, fix}` mirrors the MCP surface's refusal; `guide` points
154
155
  * at the section that explains the code when one does (resolved offline).
155
156
  */
157
+ /** Everything that blocks an activation, when the server listed more than the first gate. */
158
+ function activationBlockers(body) {
159
+ const diagnostic = body?.diagnostic;
160
+ if (!Array.isArray(diagnostic?.blockers))
161
+ return [];
162
+ const blockers = [];
163
+ for (const item of diagnostic.blockers) {
164
+ const gate = item?.gate;
165
+ const reason = item?.reason;
166
+ if (typeof gate === 'string' && gate && typeof reason === 'string' && reason) {
167
+ blockers.push({ gate, reason });
168
+ }
169
+ }
170
+ return blockers;
171
+ }
156
172
  function commitmentActivationRefusal(err, { id, bin }) {
157
173
  const refusal = (0, base_command_1.apiRefusal)(err);
174
+ // `resume` answers with every blocking gate, but only the first is in the message. Fixing it
175
+ // revealed the next one on the following try (Always-On, then the runtime), so the refusal says
176
+ // all of them when there is more than one (dogfood df-261031-commitment-authoring, F12).
177
+ const blockers = activationBlockers(refusal.body);
158
178
  const errorField = refusal.body?.error;
159
179
  const code = refusal.code ||
160
180
  (typeof errorField === 'string' && /^[A-Za-z][A-Za-z0-9_]*$/.test(errorField)
@@ -162,11 +182,17 @@ function commitmentActivationRefusal(err, { id, bin }) {
162
182
  : undefined) ||
163
183
  'COMMITMENT_ACTIVATION_REFUSED';
164
184
  const guide = (0, guide_pointer_1.agentGuidePointerFor)(code);
185
+ // The blocking gate's own remedy beats the generic "fix the gate the reason names": the reason is
186
+ // a token, and the sentence that says what to run is the hint beside it (OSK-21126).
187
+ const hint = (0, base_command_1.commitmentActivationHint)(refusal.body);
165
188
  return {
166
189
  code,
167
190
  reason: refusal.message || (err instanceof Error ? err.message : 'Activation was refused.'),
168
- fix: (0, data_provider_1.describeCommitmentActivationFix)(code === 'COMMITMENT_ACTIVATION_REFUSED' ? undefined : code, { turnOn: `run \`${bin} commitments resume ${id}\`` }),
191
+ fix: hint
192
+ ? `${hint} Then run \`${bin} commitments resume ${id}\`.`
193
+ : (0, data_provider_1.describeCommitmentActivationFix)(code === 'COMMITMENT_ACTIVATION_REFUSED' ? undefined : code, { turnOn: `run \`${bin} commitments resume ${id}\`` }),
169
194
  ...(guide ? { guide } : {}),
195
+ ...(blockers.length > 1 ? { blockers } : {}),
170
196
  };
171
197
  }
172
198
  /**
@@ -210,6 +236,7 @@ cat > commitment.json <<'JSON'
210
236
  }],
211
237
  "policy": {
212
238
  "maxUserNudgesPerDay": 0,
239
+ "quietHours": { "enabled": true, "start": "22:00", "end": "07:00", "timezone": "Asia/Shanghai" },
213
240
  "executionBudget": { "maxRunsPerDay": 0, "maxObservationRunsPerDay": 0, "maxCostUsdPerDay": 0 }
214
241
  }
215
242
  }
@@ -217,11 +244,27 @@ JSON
217
244
  <%= config.bin %> commitments preflight --from-json commitment.json
218
245
  <%= config.bin %> commitments create --from-json commitment.json --json`,
219
246
  '<%= config.bin %> commitments create --agent <agent-id> --title "Keep CI healthy" --outcome "Failed checks are repaired" --watch github --github-event check_run_failed --event-driven --delivery draft-pr --repo owner/repo --base main --status draft',
247
+ `# Govern a Commitment with a Compass. There is no flag: "compassId" goes in the --from-json body.
248
+ # It must be an active Compass of the same Agent (a draft may name an archived one).
249
+ # The binding cannot be changed later: bind a successor Commitment to another Compass instead.
250
+ cat > commitment.json <<'JSON'
251
+ {
252
+ "agentId": "<agent-id>",
253
+ "compassId": "<compass-id>",
254
+ "title": "Keep p99 under 300 ms",
255
+ "status": "draft",
256
+ "target": { "outcome": "p99 API latency stays under 300 ms" }
257
+ }
258
+ JSON
259
+ <%= config.bin %> commitments create --from-json commitment.json --json
260
+ <%= config.bin %> compass commitments <compass-id> --agent <agent-id> # lists it once it is bound`,
220
261
  ];
221
262
  static flags = {
222
- json: core_1.Flags.boolean({ description: 'Output the created commitment as JSON' }),
263
+ json: core_1.Flags.boolean({
264
+ description: 'Output the created commitment as JSON: the commitment itself, not wrapped (commitments show --json puts it under .commitment)',
265
+ }),
223
266
  'from-json': core_1.Flags.string({
224
- description: 'JSON file (or "-" for stdin) for fields not exposed as flags. See examples below for daily times, named agent_run keys and zero budgets.',
267
+ description: `JSON file (or "-" for stdin) for fields not exposed as flags. See examples below for daily times, named agent_run keys, zero budgets and "compassId" (binds a Compass; fixed at creation). ${commitment_attention_copy_1.QUIET_HOURS_HELP} ${commitment_attention_copy_1.EVENT_SOURCES_HELP}`,
225
268
  }),
226
269
  agent: core_1.Flags.string({ description: 'Agent that owns and watches this commitment (required)' }),
227
270
  kind: core_1.Flags.string({
@@ -321,7 +364,7 @@ JSON
321
364
  options: [...commitments_1.COMMITMENT_EXECUTION_ISOLATION_MODES],
322
365
  hidden: true,
323
366
  }),
324
- 'max-nudges': core_1.Flags.integer({ description: 'Max user nudges per day (0..20)' }),
367
+ 'max-nudges': core_1.Flags.string({ description: commitment_attention_copy_1.MAX_NUDGES_FLAG_HELP }),
325
368
  'max-concurrent-runs': core_1.Flags.string({
326
369
  description: 'Owner concurrency policy: "unlimited" (default) or 1..100. Finite values require --concurrency-reason and cannot be activated by an autonomous Agent session.',
327
370
  }),
@@ -337,7 +380,11 @@ JSON
337
380
  // `createBodySchema` is a z.strictObject, so unwrap a `plan --json`
338
381
  // envelope and drop any key it does not declare (a contract dumped from
339
382
  // `list --json` carries id/version/state/… and would otherwise 400).
340
- payload = (0, commitments_1.normalizeCreateBody)(await (0, prompt_1.readJsonBody)(flags['from-json']));
383
+ const rawBody = await (0, prompt_1.readJsonBody)(flags['from-json']);
384
+ payload = (0, commitments_1.normalizeCreateBody)(rawBody);
385
+ const ignoredWarning = (0, commitments_1.ignoredCreateKeysWarning)((0, commitments_1.ignoredCreateKeys)(rawBody));
386
+ if (ignoredWarning)
387
+ this.warn(ignoredWarning);
341
388
  if (flags.agent)
342
389
  payload.agentId = flags.agent;
343
390
  if (flags.kind)
@@ -430,8 +477,9 @@ JSON
430
477
  (0, commitment_product_1.normalizeTaskReviewPolicy)(declaredDelivery.reviewPolicy);
431
478
  if (flags.isolation)
432
479
  policy.executionIsolation = flags.isolation;
433
- if (flags['max-nudges'] !== undefined)
434
- policy.maxUserNudgesPerDay = flags['max-nudges'];
480
+ const maxNudges = (0, commitment_attention_copy_1.parseMaxNudgesFlag)(flags['max-nudges']);
481
+ if (maxNudges !== undefined)
482
+ policy.maxUserNudgesPerDay = maxNudges;
435
483
  const concurrencyPatch = (0, commitments_1.commitmentConcurrencyEnvelopePatch)({
436
484
  rawValue: flags['max-concurrent-runs'],
437
485
  reason: flags['concurrency-reason'],
@@ -446,6 +494,9 @@ JSON
446
494
  }
447
495
  if (Object.keys(policy).length)
448
496
  payload.policy = policy;
497
+ const quietHoursProblem = (0, commitment_attention_copy_1.quietHoursInputProblem)(policy);
498
+ if (quietHoursProblem)
499
+ throw new Error(quietHoursProblem);
449
500
  if (payload.createdVia === undefined) {
450
501
  payload.createdVia = process.env.OVERSKY_AUTONOMOUS_SESSION === '1' ? 'agent_cli' : 'cli';
451
502
  }
@@ -533,6 +584,12 @@ JSON
533
584
  if (activation && !activation.activated) {
534
585
  const { refusal } = activation;
535
586
  this.warn(`Activation refused (${refusal.code}): ${refusal.reason}`);
587
+ if (refusal.blockers) {
588
+ this.warn(`All ${refusal.blockers.length} things blocking it (it stays a draft until each is fixed):`);
589
+ for (const blocker of refusal.blockers)
590
+ this.warn(` - ${blocker.gate}: ${blocker.reason}`);
591
+ this.warn(`Details: ${this.config.bin} commitments preflight ${String(commitment?.id)}`);
592
+ }
536
593
  if (refusal.fix)
537
594
  this.warn(`Fix: ${refusal.fix}`);
538
595
  if (refusal.guide)
@@ -86,7 +86,9 @@ function spaceLoopLine(value) {
86
86
  if (status === 'admitted') {
87
87
  detail = loop.principalVersion ? ` (principal v${loop.principalVersion})` : '';
88
88
  }
89
- return `Space loop: ${status}${loop.spaceId ? ` · ${loop.spaceId}` : ''}${detail}`;
89
+ // Said in words first, with the code kept in parentheses for anyone who searches by it.
90
+ const said = status !== 'admitted' && loop.detail ? ` — ${loop.detail}` : '';
91
+ return `Space loop: ${status}${loop.spaceId ? ` · ${loop.spaceId}` : ''}${said}${detail}`;
90
92
  }
91
93
  class CommitmentsDoctor extends base_command_1.BaseCommand {
92
94
  static description = 'Summarize commitment readiness and the next operator action';
@@ -1,5 +1,15 @@
1
1
  import { BaseCommand } from '../../base-command';
2
- export declare function effectivePolicyLines(policy: Record<string, any>): string[];
2
+ /**
3
+ * `2026-11-02 00:00` for an instant, in a time zone. Built from parts, never from a locale: `02/11/2026`
4
+ * is the 2nd of November to one reader and the 11th of February to the next, and the budget reset is
5
+ * exactly the date a person reads this line for.
6
+ */
7
+ export declare function isoWallClock(instant: unknown, timeZone?: string): string | undefined;
8
+ /**
9
+ * `stored` is the Commitment's own policy, read beside the effective one: the no-reply back-off
10
+ * is not part of the effective-policy answer, so without it that line is left out, not guessed.
11
+ */
12
+ export declare function effectivePolicyLines(policy: Record<string, any>, stored?: Record<string, any> | null): string[];
3
13
  /** Show the policy actually governing a Commitment without changing it. */
4
14
  export default class CommitmentsEffectivePolicy extends BaseCommand {
5
15
  static description: string;
@@ -1,23 +1,62 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isoWallClock = isoWallClock;
3
4
  exports.effectivePolicyLines = effectivePolicyLines;
4
5
  const core_1 = require("@oclif/core");
5
6
  const base_command_1 = require("../../base-command");
6
7
  const commitments_1 = require("../../lib/commitments");
7
8
  const commitment_product_1 = require("../../lib/commitment-product");
9
+ const commitment_attention_copy_1 = require("../../lib/commitment-attention-copy");
10
+ const northstars_1 = require("../../lib/northstars");
8
11
  /** Which limit binds the daily action runs — the fix lives on that object. */
9
12
  const RUN_LIMIT_BINDING_LABELS = {
10
13
  northstar_allocation: 'Northstar allocation',
11
14
  workspace_admin: 'workspace limit',
12
15
  owner: 'owner limit',
13
16
  };
17
+ /**
18
+ * `2026-11-02 00:00` for an instant, in a time zone. Built from parts, never from a locale: `02/11/2026`
19
+ * is the 2nd of November to one reader and the 11th of February to the next, and the budget reset is
20
+ * exactly the date a person reads this line for.
21
+ */
22
+ function isoWallClock(instant, timeZone = 'UTC') {
23
+ const date = new Date(String(instant ?? ''));
24
+ if (Number.isNaN(date.getTime()))
25
+ return undefined;
26
+ try {
27
+ const parts = new Intl.DateTimeFormat('en-US', {
28
+ timeZone,
29
+ year: 'numeric',
30
+ month: '2-digit',
31
+ day: '2-digit',
32
+ hour: '2-digit',
33
+ minute: '2-digit',
34
+ hourCycle: 'h23',
35
+ }).formatToParts(date);
36
+ const part = (type) => parts.find((p) => p.type === type)?.value;
37
+ return `${part('year')}-${part('month')}-${part('day')} ${part('hour')}:${part('minute')}`;
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
14
43
  /** The admission day is UTC and said so; the shared Northstar pools are named apart. */
15
44
  function budgetDayLines(policy) {
16
45
  const day = policy.budgetDay;
17
46
  const shared = policy.portfolio?.sharedBudget;
18
47
  const lines = [];
19
48
  if (day) {
20
- lines.push(`Daily budgets reset at ${day.resetsAt ?? '00:00 UTC'}${day.localResetAt ? ` (${day.localResetAt} ${day.localTimeZone})` : ''}`);
49
+ // The server's `localResetAt` is day-first (`02/11/2026, 00:00`); the next reset is rebuilt from the
50
+ // ISO instant it also sends, so the date reads the same to everyone.
51
+ const next = isoWallClock(day.nextResetAt);
52
+ const local = day.localTimeZone && day.localTimeZone !== 'UTC'
53
+ ? isoWallClock(day.nextResetAt, day.localTimeZone)
54
+ : undefined;
55
+ const when = [
56
+ next ? `next ${next} UTC` : undefined,
57
+ local ? `${local} ${day.localTimeZone}` : undefined,
58
+ ].filter(Boolean);
59
+ lines.push(`Daily budgets reset at ${day.resetsAt ?? '00:00 UTC'}${when.length ? ` (${when.join('; ')})` : ''}`);
21
60
  }
22
61
  if (shared?.status === 'unavailable') {
23
62
  lines.push('Northstar shared run pools: unavailable (could not be read)');
@@ -33,10 +72,23 @@ function budgetDayLines(policy) {
33
72
  ? 'no ceiling'
34
73
  : `${value}/day`;
35
74
  lines.push(`Northstar shared pools: action runs ${pool(shared.actionRunsPerDay, shared.actionRunsPerDayState)} · observation runs ${pool(shared.observationRunsPerDay, shared.observationRunsPerDayState)}`);
75
+ // A pool below the number of linked active commitments is still handed out at the floor of
76
+ // 1/day each: the same sentence `northstars governance show` and `review-now` print (OSK-21334).
77
+ const floorNote = (0, northstars_1.runBudgetFloorNote)(policy.portfolio?.runBudgetFloor);
78
+ if (floorNote)
79
+ lines.push(`! ${floorNote}`);
36
80
  }
37
81
  return lines;
38
82
  }
39
- function effectivePolicyLines(policy) {
83
+ /** An effective-policy `{ mode, value }` limit, back to the stored `null` = unlimited. */
84
+ function storedLimit(limit) {
85
+ return limit?.mode === 'limited' ? limit.value : null;
86
+ }
87
+ /**
88
+ * `stored` is the Commitment's own policy, read beside the effective one: the no-reply back-off
89
+ * is not part of the effective-policy answer, so without it that line is left out, not guessed.
90
+ */
91
+ function effectivePolicyLines(policy, stored) {
40
92
  const governance = policy.governance || {};
41
93
  const owner = policy.owner || {};
42
94
  const lines = [];
@@ -61,7 +113,16 @@ function effectivePolicyLines(policy) {
61
113
  lines.push(`Owner action-run limit: ${runLimit.mode === 'unbounded' ? 'Unlimited' : `${runLimit.value}/day`}`);
62
114
  }
63
115
  const effectiveRuns = policy.effective?.actionRunsPerDay;
64
- if (effectiveRuns) {
116
+ // A portfolio pool of 0 refuses every action run, so "Unlimited" on the line above it is false and
117
+ // the pool line below it ("action runs 0/day") contradicts it: say what the pool means here.
118
+ const sharedPool = policy.portfolio?.sharedBudget;
119
+ const poolRefusesAll = sharedPool?.status === 'available' &&
120
+ sharedPool.actionRunsPerDay === 0 &&
121
+ sharedPool.actionRunsPerDayState !== 'malformed';
122
+ if (poolRefusesAll) {
123
+ lines.push('Action runs: none (portfolio pool is 0/day)');
124
+ }
125
+ else if (effectiveRuns) {
65
126
  const bindingLabel = RUN_LIMIT_BINDING_LABELS[effectiveRuns.binding];
66
127
  const binding = bindingLabel ? ` (binding: ${bindingLabel})` : '';
67
128
  lines.push(`Effective action-run limit: ${effectiveRuns.mode === 'unbounded' ? 'Unlimited' : `${effectiveRuns.value}/day`}${binding}`);
@@ -72,6 +133,21 @@ function effectivePolicyLines(policy) {
72
133
  }
73
134
  if (policy.observation?.sensing)
74
135
  lines.push("Sensing runs: the Agent's own permissions");
136
+ // How much it may interrupt the owner (OSK-21120). The JSON has carried the cap and the gap as
137
+ // `owner.userNudgesPerDay` / `minUserNudgeIntervalMs`; the text dropped both.
138
+ if (owner.userNudgesPerDay) {
139
+ lines.push(...(0, commitment_attention_copy_1.nudgeSettingLines)({
140
+ maxUserNudgesPerDay: storedLimit(owner.userNudgesPerDay),
141
+ minUserNudgeIntervalMs: storedLimit(owner.minUserNudgeIntervalMs),
142
+ noReply: stored?.attention?.noReply,
143
+ }, { withNoReply: Boolean(stored) }));
144
+ }
145
+ // Which event sources may wake it (OSK-21113). The effective-policy answer has never carried
146
+ // `allowedEventSources`, so it is read from the Commitment's own policy; unread, the line is
147
+ // left out, because "all allowed" would be a claim.
148
+ if (stored) {
149
+ lines.push(`Event sources: ${(0, commitment_attention_copy_1.describeEventSources)(stored.executionBudget?.allowedEventSources)}`);
150
+ }
75
151
  lines.push(...budgetDayLines(policy));
76
152
  return lines;
77
153
  }
@@ -104,8 +180,18 @@ class CommitmentsEffectivePolicy extends base_command_1.BaseCommand {
104
180
  this.log(JSON.stringify(policy, null, 2));
105
181
  return;
106
182
  }
183
+ // The Commitment's own policy, for the settings the effective answer does not carry. A failed
184
+ // read costs those lines, never the command.
185
+ let stored = null;
186
+ try {
187
+ stored =
188
+ (await commitments_1.commitmentApi.get(args.id))?.commitment?.policy ?? null;
189
+ }
190
+ catch {
191
+ stored = null;
192
+ }
107
193
  this.log('Effective policy:');
108
- for (const line of effectivePolicyLines(policy))
194
+ for (const line of effectivePolicyLines(policy, stored))
109
195
  this.log(` ${line}`);
110
196
  if (flags.verbose) {
111
197
  this.log('\nRaw policy diagnostics:');
@@ -24,13 +24,22 @@ class CommitmentsExplain extends base_command_1.BaseCommand {
24
24
  this.requireAuth();
25
25
  const { args, flags } = await this.parse(CommitmentsExplain);
26
26
  try {
27
- const explanation = await commitments_1.commitmentApi.explain(args.commitmentId);
27
+ // The explanation names neither the bound Compass nor its status, so the text view reads the
28
+ // Commitment beside it (the read `show` renders). `--json` stays the explanation as the
29
+ // server sent it, with no extra request. A failed read must not fail the explanation
30
+ // (OSK-21332): it becomes a "not shown" line instead.
31
+ const [explanation, read] = await Promise.all([
32
+ commitments_1.commitmentApi.explain(args.commitmentId),
33
+ flags.json ? null : commitments_1.commitmentApi.get(args.commitmentId).catch(() => null),
34
+ ]);
28
35
  if (flags.json) {
29
36
  this.log(JSON.stringify(explanation, null, 2));
30
37
  return;
31
38
  }
32
39
  for (const line of (0, commitments_1.formatCommitmentExplanation)(explanation))
33
40
  this.log(line);
41
+ for (const line of (0, commitments_1.explainCompassLines)(args.commitmentId, read?.commitment))
42
+ this.log(line);
34
43
  }
35
44
  catch (err) {
36
45
  this.handleApiError(err);
@@ -55,20 +55,20 @@ class CommitmentsHypotheses extends base_command_1.BaseCommand {
55
55
  this.handleApiError(err);
56
56
  }
57
57
  if (flags.json) {
58
+ // One JSON document and nothing else: has_more / next_cursor are in it.
58
59
  this.log(JSON.stringify(response, null, 2));
60
+ return;
59
61
  }
60
- else {
61
- // A filtered-out list is not an empty ledger — say which filter did it
62
- // (OSK-10953).
63
- const filters = [
64
- flags.status ? `--status ${flags.status}` : null,
65
- flags['investigation-key'] ? `--investigation-key ${flags['investigation-key']}` : null,
66
- ].filter(Boolean);
67
- const emptyMessage = filters.length
68
- ? `No hypotheses match ${filters.join(' ')}.`
69
- : 'No hypotheses recorded.';
70
- (0, commitments_1.renderCommitmentHypotheses)(response, (line) => this.log(line), { emptyMessage });
71
- }
62
+ // A filtered-out list is not an empty ledger — say which filter did it
63
+ // (OSK-10953).
64
+ const filters = [
65
+ flags.status ? `--status ${flags.status}` : null,
66
+ flags['investigation-key'] ? `--investigation-key ${flags['investigation-key']}` : null,
67
+ ].filter(Boolean);
68
+ const emptyMessage = filters.length
69
+ ? `No hypotheses match ${filters.join(' ')}.`
70
+ : 'No hypotheses recorded.';
71
+ (0, commitments_1.renderCommitmentHypotheses)(response, (line) => this.log(line), { emptyMessage });
72
72
  (0, commitments_1.renderListPageHint)(response, (line) => this.log(line));
73
73
  }
74
74
  }
@@ -71,6 +71,11 @@ function formatNextTick(projection) {
71
71
  // is entirely in that: a reader who thinks a check just ran will misread
72
72
  // every number on the commitment afterwards.
73
73
  'Projection only — no check was recorded, no budget spent, no action routed.',
74
+ // And what it does not MODEL (OSK-21104): it decides from the evidence alone. A real Check also
75
+ // applies the repeat-evidence window, earlier Runs and owed retries, today's Run budget, the
76
+ // dispatch breaker and the attention contract, so it can stop sooner than this, or run a
77
+ // retry this does not show. Without the sentence a projected `enqueue_agent_turn` reads as a promise.
78
+ "It decides from the evidence alone: a real check also applies the repeat-evidence window, earlier runs and owed retries, today's run budget, the dispatch breaker and the attention contract, so it can stop sooner (or run a retry) than shown here.",
74
79
  '',
75
80
  `Posture: ${decision.posture ?? '-'}`,
76
81
  `Health: ${decision.health ?? '-'}`,
@@ -152,25 +152,26 @@ class CommitmentsObservations extends base_command_1.BaseCommand {
152
152
  this.handleApiError(err);
153
153
  }
154
154
  if (flags.json) {
155
+ // One JSON document and nothing else: has_more / next_cursor are in it
156
+ // (OSK-21118).
155
157
  this.printJsonResult(response);
158
+ return;
156
159
  }
157
- else {
158
- // "The commitment has none" and "your filter matched none" are different
159
- // facts; a filter that empties the list must not claim the first
160
- // (OSK-10953).
161
- const filters = [
162
- flags.latest ? '--latest' : null,
163
- flags['declared-only'] ? '--declared-only' : null,
164
- flags['changed-only'] ? '--changed-only' : null,
165
- flags.since ? `--since ${flags.since}` : null,
166
- ].filter(Boolean);
167
- const emptyMessage = filters.length
168
- ? `No observations match ${filters.join(' ')}.`
169
- : 'No observations recorded.';
170
- if (response?.truncated)
171
- this.log(`TRUNCATED: ${response.omitted} current keys omitted; increase --limit (max 200).`);
172
- (0, commitments_1.renderCommitmentObservations)(response, (line) => this.log(line), { emptyMessage });
173
- }
160
+ // "The commitment has none" and "your filter matched none" are different
161
+ // facts; a filter that empties the list must not claim the first
162
+ // (OSK-10953).
163
+ const filters = [
164
+ flags.latest ? '--latest' : null,
165
+ flags['declared-only'] ? '--declared-only' : null,
166
+ flags['changed-only'] ? '--changed-only' : null,
167
+ flags.since ? `--since ${flags.since}` : null,
168
+ ].filter(Boolean);
169
+ const emptyMessage = filters.length
170
+ ? `No observations match ${filters.join(' ')}.`
171
+ : 'No observations recorded.';
172
+ if (response?.truncated)
173
+ this.log(`TRUNCATED: ${response.omitted} current keys omitted; increase --limit (max 200).`);
174
+ (0, commitments_1.renderCommitmentObservations)(response, (line) => this.log(line), { emptyMessage });
174
175
  (0, commitments_1.renderListPageHint)(response, (line) => this.log(line));
175
176
  }
176
177
  }
@@ -41,7 +41,8 @@ class CommitmentsPackList extends base_command_1.BaseCommand {
41
41
  (0, format_1.renderTable)(rows.map((pack) => ({
42
42
  id: String(pack.id),
43
43
  version: String(pack.version),
44
- title: String(pack.title || ''),
44
+ // The catalogue names a Pack `label`; reading only `title` printed a blank column for all eight.
45
+ title: String(pack.label || pack.title || ''),
45
46
  })), [
46
47
  // No maxWidth: a clipped pack id cannot be pasted into `pack plan --pack` (OSK-6175).
47
48
  { key: 'id', header: 'PACK ID' },
@@ -1,4 +1,10 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
+ import { type CommitmentPackPlanResponse } from '../../../lib/commitments';
3
+ /**
4
+ * Lines for what the plan says will stop the loop from ever being activated. `bind` would still
5
+ * write a draft, so a reader who sees only "Nothing was created" must also see this.
6
+ */
7
+ export declare function formatReadiness(readiness: CommitmentPackPlanResponse['readiness']): string[];
2
8
  /**
3
9
  * Draft what a Pack WOULD create, without creating it.
4
10
  *
@@ -1,8 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatReadiness = formatReadiness;
3
4
  const core_1 = require("@oclif/core");
4
5
  const base_command_1 = require("../../../base-command");
5
6
  const commitments_1 = require("../../../lib/commitments");
7
+ /**
8
+ * Lines for what the plan says will stop the loop from ever being activated. `bind` would still
9
+ * write a draft, so a reader who sees only "Nothing was created" must also see this.
10
+ */
11
+ function formatReadiness(readiness) {
12
+ if (!readiness?.length)
13
+ return [];
14
+ const lines = [];
15
+ for (const item of readiness) {
16
+ lines.push(`Not ready: ${item.label} — ${item.reason}`);
17
+ if (item.hint)
18
+ lines.push(` ${item.hint}`);
19
+ }
20
+ lines.push(' `bind` would still create the draft, but `pack activate` will be refused until this is fixed.');
21
+ return lines;
22
+ }
6
23
  /**
7
24
  * Draft what a Pack WOULD create, without creating it.
8
25
  *
@@ -87,6 +104,8 @@ class CommitmentsPackPlan extends base_command_1.BaseCommand {
87
104
  const holder = placement.holder === 'space_lead' ? ' (the Space lead)' : '';
88
105
  this.log(`Space loop: ${placement.spaceId}, held by ${placement.agentId}${holder}`);
89
106
  }
107
+ for (const line of formatReadiness(result.readiness))
108
+ this.log(line);
90
109
  this.log('\nNothing was created. Pass this digest to `skrr commitments pack bind --plan-digest`.');
91
110
  }
92
111
  }
@@ -18,6 +18,20 @@ import { BaseCommand } from '../../base-command';
18
18
  * the command body so it can be unit-tested without the oclif harness.
19
19
  */
20
20
  export declare function formatDroppedFields(droppedFields: string[] | undefined): string[];
21
+ /**
22
+ * The zone a proposal's daily times will be read in, said before the person applies it (OSK-21336).
23
+ * The planner is not told a timezone unless `--timezone` is passed, and the proposal is printed as raw JSON,
24
+ * so "every day at 9am" came back as `times: ["09:00"]` with no zone; applied as it is, a daily cadence with
25
+ * no zone is read in UTC, while the web wizard writes the browser's zone for the same proposal and says so.
26
+ * Nothing for a proposal with no daily times.
27
+ */
28
+ export declare function planTimezoneLines(proposal: {
29
+ cadence?: {
30
+ mode?: unknown;
31
+ times?: unknown;
32
+ timezone?: unknown;
33
+ };
34
+ } | null | undefined, requestedTimezone?: string): string[];
21
35
  export default class CommitmentsPlan extends BaseCommand {
22
36
  static description: string;
23
37
  static examples: string[];
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatDroppedFields = formatDroppedFields;
4
+ exports.planTimezoneLines = planTimezoneLines;
4
5
  const core_1 = require("@oclif/core");
5
6
  const base_command_1 = require("../../base-command");
6
7
  const commitments_1 = require("../../lib/commitments");
@@ -36,6 +37,35 @@ function formatDroppedFields(droppedFields) {
36
37
  lines.push(` - ${field}`);
37
38
  return lines;
38
39
  }
40
+ /**
41
+ * The zone a proposal's daily times will be read in, said before the person applies it (OSK-21336).
42
+ * The planner is not told a timezone unless `--timezone` is passed, and the proposal is printed as raw JSON,
43
+ * so "every day at 9am" came back as `times: ["09:00"]` with no zone; applied as it is, a daily cadence with
44
+ * no zone is read in UTC, while the web wizard writes the browser's zone for the same proposal and says so.
45
+ * Nothing for a proposal with no daily times.
46
+ */
47
+ function planTimezoneLines(proposal, requestedTimezone) {
48
+ const cadence = proposal?.cadence;
49
+ if (!cadence || cadence.mode !== 'daily' || !Array.isArray(cadence.times) || !cadence.times.length) {
50
+ return [];
51
+ }
52
+ const times = cadence.times.filter((time) => typeof time === 'string');
53
+ if (!times.length)
54
+ return [];
55
+ const label = `daily time${times.length === 1 ? '' : 's'} ${times.join(', ')}`;
56
+ if (typeof cadence.timezone === 'string' && cadence.timezone) {
57
+ return ['', `The ${label} ${times.length === 1 ? 'is' : 'are'} read in ${cadence.timezone}.`];
58
+ }
59
+ return [
60
+ '',
61
+ `Note: the ${label} ${times.length === 1 ? 'carries' : 'carry'} no timezone, so applying this as it is reads ` +
62
+ `${times.length === 1 ? 'it' : 'them'} in UTC (the web wizard would use your browser's zone). ` +
63
+ (requestedTimezone
64
+ ? `You planned with --timezone ${requestedTimezone}, but the proposal did not keep it: add "timezone": ` +
65
+ `"${requestedTimezone}" to its cadence.`
66
+ : 'Add "timezone": "<IANA zone>" to its cadence, or plan again with --timezone <IANA zone>.'),
67
+ ];
68
+ }
39
69
  class CommitmentsPlan extends base_command_1.BaseCommand {
40
70
  static description = 'Draft a commitment contract from a sentence (read-only proposal — nothing is created)';
41
71
  static examples = [
@@ -115,6 +145,8 @@ class CommitmentsPlan extends base_command_1.BaseCommand {
115
145
  this.log('No proposal produced.');
116
146
  return;
117
147
  }
148
+ for (const line of planTimezoneLines(response.proposal, flags.timezone))
149
+ this.log(line);
118
150
  this.log('');
119
151
  this.log('Proposed contract:');
120
152
  this.log(JSON.stringify(response.proposal, null, 2));
@@ -35,7 +35,26 @@ export declare function nextRunsLines(report: {
35
35
  verdict?: string;
36
36
  nextRuns?: string[];
37
37
  }): string[];
38
- export declare function preflightGateLines(gate: PreflightGate, verbose?: boolean): string[];
38
+ /**
39
+ * The first line of a preflight. `Ready to run` is a statement about activation (no blocking gate),
40
+ * and it sat directly above a spend-gate warning that the daily Run budget is 0, i.e. that no agent
41
+ * turn can ever start (OSK-21224). The verdict stays `ready` on purpose: a zero ACTION budget does
42
+ * not stop the sensing lane, so refusing activation would be wrong for a Commitment that only
43
+ * observes. The headline carries the caveat instead of contradicting the gate below it.
44
+ */
45
+ export declare function preflightHeadline(report: Pick<CommitmentPreflightReport, 'verdict' | 'gates'>, persisted: boolean): string;
46
+ export declare function preflightGateLines(gate: PreflightGate, verbose?: boolean, refusesActivation?: boolean): string[];
47
+ /**
48
+ * The `Attention:` footer, and under it the settings behind the verdict (OSK-21120).
49
+ *
50
+ * "delivery allowed now" is the same words for a cap of 1, a mute lifted and no limit at all, so
51
+ * the cap, the gap and the no-reply rule are said beside it. `policy` is the stored policy the
52
+ * report was computed from, or the unsaved one under `--from-json`; without it nothing is said
53
+ * about the settings, rather than a guess. The raw reason code (`policy_nudge_muted`) is printed only
54
+ * with `--verbose`: beside the sentence it said the same thing twice, in a vocabulary nobody types
55
+ * (OSK-21124).
56
+ */
57
+ export declare function attentionFooterLines(attention: CommitmentPreflightReport['attention'] | undefined, policy?: Record<string, any> | null, verbose?: boolean): string[];
39
58
  export default class CommitmentsPreflight extends BaseCommand {
40
59
  static description: string;
41
60
  static examples: string[];