@skrr-ai/cli 0.1.126 → 0.1.128

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 (123) hide show
  1. package/dist/base-command.d.ts +23 -0
  2. package/dist/base-command.js +72 -0
  3. package/dist/commands/agents/chat.js +7 -0
  4. package/dist/commands/agents/goals/list.d.ts +2 -2
  5. package/dist/commands/agents/goals/list.js +2 -2
  6. package/dist/commands/commitments/action-history.js +1 -1
  7. package/dist/commands/commitments/cases.js +3 -3
  8. package/dist/commands/commitments/check-now.d.ts +10 -2
  9. package/dist/commands/commitments/check-now.js +53 -3
  10. package/dist/commands/commitments/create.d.ts +6 -14
  11. package/dist/commands/commitments/create.js +61 -6
  12. package/dist/commands/commitments/doctor.js +3 -1
  13. package/dist/commands/commitments/effective-policy.d.ts +11 -1
  14. package/dist/commands/commitments/effective-policy.js +84 -4
  15. package/dist/commands/commitments/hypotheses.js +12 -12
  16. package/dist/commands/commitments/next-tick.js +5 -0
  17. package/dist/commands/commitments/observations.js +18 -17
  18. package/dist/commands/commitments/pack/list.js +2 -1
  19. package/dist/commands/commitments/pack/plan.d.ts +6 -0
  20. package/dist/commands/commitments/pack/plan.js +19 -0
  21. package/dist/commands/commitments/preflight.d.ts +19 -0
  22. package/dist/commands/commitments/preflight.js +108 -10
  23. package/dist/commands/commitments/trigger-status.js +8 -0
  24. package/dist/commands/commitments/update.d.ts +1 -0
  25. package/dist/commands/commitments/update.js +45 -4
  26. package/dist/commands/commitments/wake-capabilities.js +13 -0
  27. package/dist/commands/compass/archive.js +4 -0
  28. package/dist/commands/compass/commitments.js +15 -1
  29. package/dist/commands/compass/create.js +11 -3
  30. package/dist/commands/compass/effective-policy.d.ts +1 -0
  31. package/dist/commands/compass/effective-policy.js +8 -7
  32. package/dist/commands/compass/revert.d.ts +8 -0
  33. package/dist/commands/compass/revert.js +25 -1
  34. package/dist/commands/compass/revisions.d.ts +2 -0
  35. package/dist/commands/compass/revisions.js +18 -2
  36. package/dist/commands/compass/tactic.d.ts +5 -3
  37. package/dist/commands/compass/tactic.js +8 -7
  38. package/dist/commands/compass/update.js +19 -3
  39. package/dist/commands/followups/act.js +1 -1
  40. package/dist/commands/followups/cancel.js +13 -0
  41. package/dist/commands/followups/observability.js +20 -1
  42. package/dist/commands/followups/plans/create.js +12 -1
  43. package/dist/commands/followups/show.js +13 -0
  44. package/dist/commands/followups/watch.js +1 -1
  45. package/dist/commands/goals/create.d.ts +1 -0
  46. package/dist/commands/goals/create.js +4 -1
  47. package/dist/commands/goals/planner-config.d.ts +11 -0
  48. package/dist/commands/goals/planner-config.js +16 -0
  49. package/dist/commands/goals/revisions.js +3 -19
  50. package/dist/commands/machines/update.d.ts +5 -3
  51. package/dist/commands/machines/update.js +70 -9
  52. package/dist/commands/northstars/create.js +3 -1
  53. package/dist/commands/northstars/governance/configure.d.ts +4 -0
  54. package/dist/commands/northstars/governance/configure.js +75 -9
  55. package/dist/commands/northstars/intents/decide.d.ts +6 -0
  56. package/dist/commands/northstars/intents/decide.js +69 -15
  57. package/dist/commands/northstars/intents/list.d.ts +2 -1
  58. package/dist/commands/northstars/intents/list.js +15 -5
  59. package/dist/commands/northstars/intents.d.ts +26 -0
  60. package/dist/commands/northstars/intents.js +49 -0
  61. package/dist/commands/northstars/list.d.ts +1 -0
  62. package/dist/commands/northstars/list.js +24 -3
  63. package/dist/commands/northstars/recommendations/list.d.ts +1 -1
  64. package/dist/commands/northstars/recommendations/list.js +3 -3
  65. package/dist/commands/northstars/recommendations.d.ts +19 -0
  66. package/dist/commands/northstars/recommendations.js +42 -0
  67. package/dist/commands/northstars/review-now.d.ts +1 -0
  68. package/dist/commands/northstars/review-now.js +10 -4
  69. package/dist/commands/northstars/reviews.d.ts +1 -0
  70. package/dist/commands/northstars/reviews.js +19 -3
  71. package/dist/commands/northstars/update.js +8 -1
  72. package/dist/commands/proposals/list.js +11 -2
  73. package/dist/commands/proposals/show.js +9 -1
  74. package/dist/commands/spaces/auto-executor/preview.d.ts +14 -1
  75. package/dist/commands/spaces/auto-executor/preview.js +35 -1
  76. package/dist/commands/spaces/auto-executor/run.d.ts +17 -0
  77. package/dist/commands/spaces/auto-executor/run.js +60 -2
  78. package/dist/commands/spaces/files/upload.js +1 -2
  79. package/dist/commands/spaces/index.js +23 -0
  80. package/dist/commands/spaces/revisions.js +3 -2
  81. package/dist/commands/tasks/revisions.js +3 -2
  82. package/dist/commands/triggers/fire-detail.js +2 -0
  83. package/dist/generated/agent-guides.json +69 -69
  84. package/dist/lib/agentic-stream.d.ts +9 -0
  85. package/dist/lib/agentic-stream.js +14 -0
  86. package/dist/lib/command-miss.js +11 -2
  87. package/dist/lib/commitment-attention-copy.d.ts +96 -0
  88. package/dist/lib/commitment-attention-copy.js +209 -0
  89. package/dist/lib/commitment-loop-status.d.ts +6 -0
  90. package/dist/lib/commitment-loop-status.js +16 -6
  91. package/dist/lib/commitments.d.ts +88 -0
  92. package/dist/lib/commitments.js +188 -0
  93. package/dist/lib/compass-body-help.d.ts +20 -0
  94. package/dist/lib/compass-body-help.js +61 -0
  95. package/dist/lib/compass-refusals.d.ts +35 -0
  96. package/dist/lib/compass-refusals.js +50 -0
  97. package/dist/lib/compasses.d.ts +80 -4
  98. package/dist/lib/compasses.js +232 -12
  99. package/dist/lib/followups.d.ts +22 -1
  100. package/dist/lib/followups.js +21 -1
  101. package/dist/lib/format.d.ts +11 -0
  102. package/dist/lib/format.js +29 -0
  103. package/dist/lib/initiatives.d.ts +9 -2
  104. package/dist/lib/initiatives.js +13 -2
  105. package/dist/lib/machines.d.ts +14 -1
  106. package/dist/lib/machines.js +24 -0
  107. package/dist/lib/northstars.d.ts +158 -24
  108. package/dist/lib/northstars.js +538 -26
  109. package/dist/lib/proposal-decision-command.js +21 -1
  110. package/dist/lib/proposals.d.ts +26 -1
  111. package/dist/lib/proposals.js +163 -0
  112. package/dist/lib/rate-limit-retry.d.ts +5 -0
  113. package/dist/lib/rate-limit-retry.js +19 -0
  114. package/dist/lib/revision-actor.d.ts +2 -0
  115. package/dist/lib/revision-actor.js +16 -0
  116. package/dist/lib/roles.d.ts +1 -1
  117. package/dist/lib/space-charter.js +9 -0
  118. package/dist/lib/task-asset-upload.js +1 -2
  119. package/dist/lib/triggers.d.ts +33 -1
  120. package/dist/lib/triggers.js +77 -3
  121. package/dist/node_modules/@skrr-ai/data-provider/index.js +7537 -7474
  122. package/oclif.manifest.json +357 -34
  123. package/package.json +4 -4
@@ -1,23 +1,61 @@
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");
8
10
  /** Which limit binds the daily action runs — the fix lives on that object. */
9
11
  const RUN_LIMIT_BINDING_LABELS = {
10
12
  northstar_allocation: 'Northstar allocation',
11
13
  workspace_admin: 'workspace limit',
12
14
  owner: 'owner limit',
13
15
  };
16
+ /**
17
+ * `2026-11-02 00:00` for an instant, in a time zone. Built from parts, never from a locale: `02/11/2026`
18
+ * is the 2nd of November to one reader and the 11th of February to the next, and the budget reset is
19
+ * exactly the date a person reads this line for.
20
+ */
21
+ function isoWallClock(instant, timeZone = 'UTC') {
22
+ const date = new Date(String(instant ?? ''));
23
+ if (Number.isNaN(date.getTime()))
24
+ return undefined;
25
+ try {
26
+ const parts = new Intl.DateTimeFormat('en-US', {
27
+ timeZone,
28
+ year: 'numeric',
29
+ month: '2-digit',
30
+ day: '2-digit',
31
+ hour: '2-digit',
32
+ minute: '2-digit',
33
+ hourCycle: 'h23',
34
+ }).formatToParts(date);
35
+ const part = (type) => parts.find((p) => p.type === type)?.value;
36
+ return `${part('year')}-${part('month')}-${part('day')} ${part('hour')}:${part('minute')}`;
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ }
14
42
  /** The admission day is UTC and said so; the shared Northstar pools are named apart. */
15
43
  function budgetDayLines(policy) {
16
44
  const day = policy.budgetDay;
17
45
  const shared = policy.portfolio?.sharedBudget;
18
46
  const lines = [];
19
47
  if (day) {
20
- lines.push(`Daily budgets reset at ${day.resetsAt ?? '00:00 UTC'}${day.localResetAt ? ` (${day.localResetAt} ${day.localTimeZone})` : ''}`);
48
+ // The server's `localResetAt` is day-first (`02/11/2026, 00:00`); the next reset is rebuilt from the
49
+ // ISO instant it also sends, so the date reads the same to everyone.
50
+ const next = isoWallClock(day.nextResetAt);
51
+ const local = day.localTimeZone && day.localTimeZone !== 'UTC'
52
+ ? isoWallClock(day.nextResetAt, day.localTimeZone)
53
+ : undefined;
54
+ const when = [
55
+ next ? `next ${next} UTC` : undefined,
56
+ local ? `${local} ${day.localTimeZone}` : undefined,
57
+ ].filter(Boolean);
58
+ lines.push(`Daily budgets reset at ${day.resetsAt ?? '00:00 UTC'}${when.length ? ` (${when.join('; ')})` : ''}`);
21
59
  }
22
60
  if (shared?.status === 'unavailable') {
23
61
  lines.push('Northstar shared run pools: unavailable (could not be read)');
@@ -36,7 +74,15 @@ function budgetDayLines(policy) {
36
74
  }
37
75
  return lines;
38
76
  }
39
- function effectivePolicyLines(policy) {
77
+ /** An effective-policy `{ mode, value }` limit, back to the stored `null` = unlimited. */
78
+ function storedLimit(limit) {
79
+ return limit?.mode === 'limited' ? limit.value : null;
80
+ }
81
+ /**
82
+ * `stored` is the Commitment's own policy, read beside the effective one: the no-reply back-off
83
+ * is not part of the effective-policy answer, so without it that line is left out, not guessed.
84
+ */
85
+ function effectivePolicyLines(policy, stored) {
40
86
  const governance = policy.governance || {};
41
87
  const owner = policy.owner || {};
42
88
  const lines = [];
@@ -61,7 +107,16 @@ function effectivePolicyLines(policy) {
61
107
  lines.push(`Owner action-run limit: ${runLimit.mode === 'unbounded' ? 'Unlimited' : `${runLimit.value}/day`}`);
62
108
  }
63
109
  const effectiveRuns = policy.effective?.actionRunsPerDay;
64
- if (effectiveRuns) {
110
+ // A portfolio pool of 0 refuses every action run, so "Unlimited" on the line above it is false and
111
+ // the pool line below it ("action runs 0/day") contradicts it: say what the pool means here.
112
+ const sharedPool = policy.portfolio?.sharedBudget;
113
+ const poolRefusesAll = sharedPool?.status === 'available' &&
114
+ sharedPool.actionRunsPerDay === 0 &&
115
+ sharedPool.actionRunsPerDayState !== 'malformed';
116
+ if (poolRefusesAll) {
117
+ lines.push('Action runs: none (portfolio pool is 0/day)');
118
+ }
119
+ else if (effectiveRuns) {
65
120
  const bindingLabel = RUN_LIMIT_BINDING_LABELS[effectiveRuns.binding];
66
121
  const binding = bindingLabel ? ` (binding: ${bindingLabel})` : '';
67
122
  lines.push(`Effective action-run limit: ${effectiveRuns.mode === 'unbounded' ? 'Unlimited' : `${effectiveRuns.value}/day`}${binding}`);
@@ -72,6 +127,21 @@ function effectivePolicyLines(policy) {
72
127
  }
73
128
  if (policy.observation?.sensing)
74
129
  lines.push("Sensing runs: the Agent's own permissions");
130
+ // How much it may interrupt the owner (OSK-21120). The JSON has carried the cap and the gap as
131
+ // `owner.userNudgesPerDay` / `minUserNudgeIntervalMs`; the text dropped both.
132
+ if (owner.userNudgesPerDay) {
133
+ lines.push(...(0, commitment_attention_copy_1.nudgeSettingLines)({
134
+ maxUserNudgesPerDay: storedLimit(owner.userNudgesPerDay),
135
+ minUserNudgeIntervalMs: storedLimit(owner.minUserNudgeIntervalMs),
136
+ noReply: stored?.attention?.noReply,
137
+ }, { withNoReply: Boolean(stored) }));
138
+ }
139
+ // Which event sources may wake it (OSK-21113). The effective-policy answer has never carried
140
+ // `allowedEventSources`, so it is read from the Commitment's own policy; unread, the line is
141
+ // left out, because "all allowed" would be a claim.
142
+ if (stored) {
143
+ lines.push(`Event sources: ${(0, commitment_attention_copy_1.describeEventSources)(stored.executionBudget?.allowedEventSources)}`);
144
+ }
75
145
  lines.push(...budgetDayLines(policy));
76
146
  return lines;
77
147
  }
@@ -104,8 +174,18 @@ class CommitmentsEffectivePolicy extends base_command_1.BaseCommand {
104
174
  this.log(JSON.stringify(policy, null, 2));
105
175
  return;
106
176
  }
177
+ // The Commitment's own policy, for the settings the effective answer does not carry. A failed
178
+ // read costs those lines, never the command.
179
+ let stored = null;
180
+ try {
181
+ stored =
182
+ (await commitments_1.commitmentApi.get(args.id))?.commitment?.policy ?? null;
183
+ }
184
+ catch {
185
+ stored = null;
186
+ }
107
187
  this.log('Effective policy:');
108
- for (const line of effectivePolicyLines(policy))
188
+ for (const line of effectivePolicyLines(policy, stored))
109
189
  this.log(` ${line}`);
110
190
  if (flags.verbose) {
111
191
  this.log('\nRaw policy diagnostics:');
@@ -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
  }
@@ -35,7 +35,26 @@ export declare function nextRunsLines(report: {
35
35
  verdict?: string;
36
36
  nextRuns?: string[];
37
37
  }): 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;
38
46
  export declare function preflightGateLines(gate: PreflightGate, verbose?: 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[];
@@ -1,12 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.nextRunsLines = nextRunsLines;
4
+ exports.preflightHeadline = preflightHeadline;
4
5
  exports.preflightGateLines = preflightGateLines;
6
+ exports.attentionFooterLines = attentionFooterLines;
5
7
  const core_1 = require("@oclif/core");
6
8
  const data_provider_1 = require("@skrr-ai/data-provider");
7
9
  const base_command_1 = require("../../base-command");
8
10
  const commitments_1 = require("../../lib/commitments");
9
11
  const prompt_1 = require("../../lib/prompt");
12
+ const commitment_attention_copy_1 = require("../../lib/commitment-attention-copy");
10
13
  const preflight_copy_1 = require("../../lib/preflight-copy");
11
14
  /**
12
15
  * The lines one preflight gate contributes.
@@ -41,6 +44,25 @@ function nextRunsLines(report) {
41
44
  : 'Next runs:';
42
45
  return ['', header, ...report.nextRuns.map((run) => ` ${run}`)];
43
46
  }
47
+ /**
48
+ * The first line of a preflight. `Ready to run` is a statement about activation (no blocking gate),
49
+ * and it sat directly above a spend-gate warning that the daily Run budget is 0, i.e. that no agent
50
+ * turn can ever start (OSK-21224). The verdict stays `ready` on purpose: a zero ACTION budget does
51
+ * not stop the sensing lane, so refusing activation would be wrong for a Commitment that only
52
+ * observes. The headline carries the caveat instead of contradicting the gate below it.
53
+ */
54
+ function preflightHeadline(report, persisted) {
55
+ const label = persisted ? 'Preflight' : 'Pre-create preview (nothing persisted)';
56
+ if (report.verdict === 'blocked')
57
+ return `${label}: Run is blocked`;
58
+ if (report.verdict !== 'ready')
59
+ return `${label}: A capability or configuration check is still needed`;
60
+ const reasons = report.gates?.find((gate) => gate.id === 'budget_bounds')?.detail?.reasons;
61
+ const noActionRuns = Array.isArray(reasons) && reasons.includes('action_runs_zero');
62
+ return noActionRuns
63
+ ? `${label}: Ready to run, but its daily Run budget is 0, so no agent turn will start`
64
+ : `${label}: Ready to run`;
65
+ }
44
66
  function preflightGateLines(gate, verbose = false) {
45
67
  // "Could it contact you now?" is not a Run gate. It answers whether a message
46
68
  // would reach the owner, and a limit the owner set themselves (zero nudges a day,
@@ -50,15 +72,36 @@ function preflightGateLines(gate, verbose = false) {
50
72
  // (OSK-15420). Say what it is in words, and which setting moves it.
51
73
  if (gate.id === 'attention' && gate.verdict === 'block') {
52
74
  const copy = (0, data_provider_1.describeSuppression)(gate.reason);
75
+ // "Held until your quiet hours end." named no time and no zone; the report has both (OSK-21122).
76
+ const quietEnd = gate.reason === 'policy_quiet_hours'
77
+ ? (0, commitment_attention_copy_1.describeQuietHoursEnd)(gate.detail?.quietHours)
78
+ : null;
53
79
  const lines = [
54
80
  ` ${'info'.padEnd(5)} ${gate.label} — ${copy.title.toLowerCase()}${verbose && gate.reason ? ` (${gate.reason})` : ''}`,
55
- ` ${copy.detail}`,
81
+ ` ${quietEnd ?? copy.detail}`,
56
82
  ];
57
- if (copy.settingKey)
83
+ // Whose quiet hours these are, when they are not the Commitment's own (OSK-21151): a reader
84
+ // would otherwise be sent to a field this Commitment does not hold.
85
+ const note = gate.detail?.note;
86
+ if (typeof note === 'string' && note.trim())
87
+ lines.push(` ${note.trim()}`);
88
+ // The policy field is a path into JSON, not something to type: the sentence above says what
89
+ // it means, so the path joins the code behind --verbose (OSK-21124).
90
+ if (verbose && copy.settingKey)
58
91
  lines.push(` Setting: ${copy.settingKey}`);
59
92
  return lines;
60
93
  }
61
- const mark = gate.verdict === 'pass' ? 'ok' : gate.verdict === 'block' ? 'BLOCK' : 'warn';
94
+ // A `block` the report itself marks advisory stops nothing: it is not a Run-admission gate, so it
95
+ // never feeds the verdict or `pack activate`. Printed BLOCK, `pack preflight` read "ready" above
96
+ // two BLOCK lines (dogfooded on a goal_advance draft); the tag has to mean what the report means.
97
+ const advisoryBlock = gate.verdict === 'block' && gate.admission === 'advisory';
98
+ const mark = advisoryBlock
99
+ ? 'info'
100
+ : gate.verdict === 'pass'
101
+ ? 'ok'
102
+ : gate.verdict === 'block'
103
+ ? 'BLOCK'
104
+ : 'warn';
62
105
  // A BLOCKING gate always states its reason. `ok` gates got a helpful `note`
63
106
  // and `warn` gates got a reason token, and the BLOCK gates — the only ones
64
107
  // that stop the run — got the label and nothing: two of them side by side,
@@ -75,7 +118,8 @@ function preflightGateLines(gate, verbose = false) {
75
118
  const hasNote = typeof note === 'string' && Boolean(note.trim());
76
119
  const showReason = verbose || (gate.verdict === 'block' && !hasNote);
77
120
  const lines = [
78
- ` ${mark.padEnd(5)} ${gate.label}${showReason && gate.reason ? ` — ${gate.reason}` : ''}`,
121
+ ` ${mark.padEnd(5)} ${gate.label}${showReason && gate.reason ? ` — ${gate.reason}` : ''}` +
122
+ (advisoryBlock ? ' (advisory: does not stop a run)' : ''),
79
123
  ];
80
124
  if (hasNote) {
81
125
  lines.push(` ${String(note).trim()}`);
@@ -92,6 +136,13 @@ function preflightGateLines(gate, verbose = false) {
92
136
  else if (status === 'invalid') {
93
137
  lines.push(' Active owner rules: unknown (stored rules invalid).');
94
138
  }
139
+ else if (status === 'archived' &&
140
+ typeof count === 'number' &&
141
+ Number.isSafeInteger(count) &&
142
+ count >= 0) {
143
+ // The Compass is archived, not unreadable: its rules are counted, and still enforced.
144
+ lines.push(` Active owner rules: ${count} (on the archived Compass, still enforced).`);
145
+ }
95
146
  else if (status === 'available' &&
96
147
  typeof count === 'number' &&
97
148
  Number.isSafeInteger(count) &&
@@ -116,6 +167,29 @@ function preflightGateLines(gate, verbose = false) {
116
167
  lines.push(` ${sentence}`);
117
168
  return lines;
118
169
  }
170
+ /**
171
+ * The `Attention:` footer, and under it the settings behind the verdict (OSK-21120).
172
+ *
173
+ * "delivery allowed now" is the same words for a cap of 1, a mute lifted and no limit at all, so
174
+ * the cap, the gap and the no-reply rule are said beside it. `policy` is the stored policy the
175
+ * report was computed from, or the unsaved one under `--from-json`; without it nothing is said
176
+ * about the settings, rather than a guess. The raw reason code (`policy_nudge_muted`) is printed only
177
+ * with `--verbose`: beside the sentence it said the same thing twice, in a vocabulary nobody types
178
+ * (OSK-21124).
179
+ */
180
+ function attentionFooterLines(attention, policy, verbose = false) {
181
+ const code = verbose && attention?.suppressedBy ? ` (${attention.suppressedBy})` : '';
182
+ const lines = [
183
+ '',
184
+ `Attention: ${attention?.allowed ? 'delivery allowed now' : `held back — ${(0, data_provider_1.describeSuppression)(attention?.suppressedBy).title.toLowerCase()}${code}`}`,
185
+ ];
186
+ if (policy) {
187
+ for (const line of (0, commitment_attention_copy_1.nudgeSettingLines)((0, commitment_attention_copy_1.nudgeSettingsFromPolicy)(policy)))
188
+ lines.push(` ${line}`);
189
+ lines.push(` Quiet hours: ${(0, commitment_attention_copy_1.describeQuietHours)(policy.quietHours)}`);
190
+ }
191
+ return lines;
192
+ }
119
193
  class CommitmentsPreflight extends base_command_1.BaseCommand {
120
194
  static description = 'Dry-run a commitment: runtime gates, capabilities, attention policy and upcoming runs (runs nothing)';
121
195
  static examples = [
@@ -151,16 +225,37 @@ class CommitmentsPreflight extends base_command_1.BaseCommand {
151
225
  this.error('--next must be between 1 and 10', { exit: 1 });
152
226
  }
153
227
  let report;
228
+ // The policy the verdict was computed from, for the settings printed under it.
229
+ let policy = null;
154
230
  try {
155
- report = args.id
156
- ? await commitments_1.commitmentApi.preflight(args.id, flags.next)
157
- : await commitments_1.commitmentApi.draftPreflight({
158
- commitment: (0, commitments_1.normalizeCreateBody)(await (0, prompt_1.readJsonBody)(flags['from-json'])),
231
+ if (args.id) {
232
+ report = await commitments_1.commitmentApi.preflight(args.id, flags.next);
233
+ try {
234
+ policy =
235
+ (await commitments_1.commitmentApi.get(args.id))?.commitment?.policy ?? null;
236
+ }
237
+ catch {
238
+ policy = null;
239
+ }
240
+ }
241
+ else {
242
+ const rawBody = await (0, prompt_1.readJsonBody)(flags['from-json']);
243
+ const draft = (0, commitments_1.normalizeCreateBody)(rawBody);
244
+ const ignoredWarning = (0, commitments_1.ignoredCreateKeysWarning)((0, commitments_1.ignoredCreateKeys)(rawBody));
245
+ if (ignoredWarning)
246
+ this.warn(ignoredWarning);
247
+ policy = draft.policy ?? {};
248
+ const quietHoursProblem = (0, commitment_attention_copy_1.quietHoursInputProblem)(policy);
249
+ if (quietHoursProblem)
250
+ throw new Error(quietHoursProblem);
251
+ report = await commitments_1.commitmentApi.draftPreflight({
252
+ commitment: draft,
159
253
  ...(flags['event-fixture']
160
254
  ? { eventFixture: await (0, prompt_1.readJsonBody)(flags['event-fixture']) }
161
255
  : {}),
162
256
  ...(flags.next !== undefined ? { nextRunCount: flags.next } : {}),
163
257
  });
258
+ }
164
259
  }
165
260
  catch (err) {
166
261
  this.handleApiError(err);
@@ -170,7 +265,9 @@ class CommitmentsPreflight extends base_command_1.BaseCommand {
170
265
  this.log(JSON.stringify(report, null, 2));
171
266
  return;
172
267
  }
173
- this.log(`${args.id ? 'Preflight' : 'Pre-create preview (nothing persisted)'}: ${report.verdict === 'ready' ? 'Ready to run' : report.verdict === 'blocked' ? 'Run is blocked' : 'A capability or configuration check is still needed'}`);
268
+ this.log(preflightHeadline(report, Boolean(args.id)));
269
+ for (const line of (0, commitments_1.activationRefusalLines)(report.activation))
270
+ this.log(line);
174
271
  // Whose permissions the run will use (OSK-11975), before the gates, so an
175
272
  // Execute Commitment on a plan-mode Agent is the first thing read
176
273
  // (OSK-12065).
@@ -211,7 +308,8 @@ class CommitmentsPreflight extends base_command_1.BaseCommand {
211
308
  (endpoint.reason ? ` (${endpoint.reason})` : '') +
212
309
  (!endpoint.available && endpoint.detail ? ` — ${endpoint.detail}` : ''));
213
310
  }
214
- this.log(`\nAttention: ${report.attention?.allowed ? 'delivery allowed now' : `held back — ${(0, data_provider_1.describeSuppression)(report.attention?.suppressedBy).title.toLowerCase()}${report.attention?.suppressedBy ? ` (${report.attention.suppressedBy})` : ''}`}`);
311
+ for (const line of attentionFooterLines(report.attention, policy, flags.verbose))
312
+ this.log(line);
215
313
  if (report.verdict === 'blocked')
216
314
  process.exitCode = 1;
217
315
  }
@@ -82,6 +82,14 @@ function formatTriggerStatus(status) {
82
82
  '(fired or disabled — cannot wake this commitment)');
83
83
  }
84
84
  }
85
+ // The plan can be converged and every row enabled while nothing can wake (Always-On off). `convergence`
86
+ // is a fact about the rows; this is the other fact, from the same projection `commitments status` reads.
87
+ const wake = status.wake;
88
+ if (wake?.held) {
89
+ lines.push(`Wakes: HELD — ${wake.reason ?? 'blocked'}`);
90
+ if (wake.detail)
91
+ lines.push(` ${String(wake.detail).replace(/\s+/g, ' ').trim()}`);
92
+ }
85
93
  const bindings = Array.isArray(status.bindings) ? status.bindings : [];
86
94
  lines.push('');
87
95
  if (bindings.length === 0) {
@@ -49,6 +49,7 @@ export default class CommitmentsUpdate extends BaseCommand {
49
49
  'cadence-mode': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
50
50
  timezone: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
51
51
  isolation: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
52
+ 'max-nudges': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
52
53
  'max-concurrent-runs': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
53
54
  'concurrency-reason': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
54
55
  };