@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
@@ -16,6 +16,21 @@ export interface CompassInvariantTarget {
16
16
  cadence?: Record<string, unknown>;
17
17
  escalation?: Record<string, unknown>;
18
18
  }
19
+ /** What a tactic write did, as the API reports it (`POST /api/compasses/:id/tactics`). */
20
+ export interface CompassTacticResult {
21
+ outcome: 'appended' | 'updated' | 'unchanged';
22
+ id: string;
23
+ description: string;
24
+ /** The invariant target the tactic lives on. */
25
+ targetInvariantId: string | null;
26
+ /** Tactics on that target after the write. */
27
+ count: number;
28
+ /** Tactics that rolled off the end to stay within the cap. */
29
+ dropped: Array<{
30
+ id: string;
31
+ description: string;
32
+ }>;
33
+ }
19
34
  export interface Compass {
20
35
  id: string;
21
36
  agentId: string;
@@ -41,14 +56,20 @@ export interface Compass {
41
56
  id: string | null;
42
57
  issues: string[];
43
58
  }>;
59
+ /** `compass revert` only: the enforced rules before and after, and which ids were removed. */
60
+ rulesChanged?: {
61
+ from: number;
62
+ to: number;
63
+ removed?: string[];
64
+ };
65
+ /** `compass revert` only: true when the version restored held the current content, so nothing was written. */
66
+ unchanged?: boolean;
44
67
  invariantTargets?: CompassInvariantTarget[];
45
68
  autonomyDefaults?: Record<string, unknown>;
46
69
  communicationDefaults?: Record<string, unknown>;
47
- learnedTactics?: Array<{
48
- id?: string;
49
- description?: string;
50
- }>;
51
70
  commitments?: Array<Record<string, unknown>>;
71
+ /** What `compass tactic` did; present only on that response. */
72
+ tactic?: CompassTacticResult;
52
73
  updatedBy?: string;
53
74
  createdAt?: string;
54
75
  updatedAt?: string;
@@ -65,6 +86,12 @@ export interface CompassRevision {
65
86
  createdAt?: string;
66
87
  previousConfig?: Record<string, unknown>;
67
88
  nextConfig?: Record<string, unknown>;
89
+ metadata?: {
90
+ autoRevert?: {
91
+ fromVersion?: number;
92
+ toVersion?: number;
93
+ };
94
+ } & Record<string, unknown>;
68
95
  }
69
96
  export interface CompassTraceEvent {
70
97
  id: string;
@@ -100,9 +127,15 @@ export declare const compassApi: {
100
127
  ok: boolean;
101
128
  id: string;
102
129
  status: string;
130
+ /** True when the compass was archived already and nothing was written. */
131
+ alreadyArchived?: boolean;
103
132
  }>;
104
133
  revisions: (id: string, query: Parameters<typeof withQuery>[1]) => Promise<{
105
134
  data: CompassRevision[];
135
+ /** True when the ledger holds rows older than this page. */
136
+ has_more?: boolean;
137
+ /** How many revisions the ledger holds in all; absent from a server that predates it. */
138
+ total?: number;
106
139
  }>;
107
140
  trace: (id: string, query: Parameters<typeof withQuery>[1]) => Promise<CompassTrace>;
108
141
  revert: (id: string, body: Record<string, unknown>) => Promise<Compass>;
@@ -112,6 +145,51 @@ export declare const compassApi: {
112
145
  }>;
113
146
  };
114
147
  export declare function renderCompassList(rows: Compass[], log: (line: string) => void): void;
148
+ /**
149
+ * One setting per line (`key: value`, nested objects as dotted keys, lists joined with `;`), so a
150
+ * settings object reads as labelled lines rather than one JSON blob (OSK-21150).
151
+ */
152
+ export declare function describeSettings(value: unknown, prefix?: string): string[];
153
+ /** A quiet-hours window in words: the hours and the zone, `off`, or `not set`. */
154
+ export declare function describeQuietHours(window: unknown): string;
155
+ /**
156
+ * The mode, and the word the author wrote when it differs from it. The server stores `auto` as
157
+ * `auto_run_limited`; saying so beats showing a value nobody typed as if they had (OSK-21150).
158
+ */
159
+ export declare function describeMode(mode: unknown, authoredMode: unknown): string;
115
160
  export declare function renderCompass(compass: Compass, log: (line: string) => void): void;
161
+ /**
162
+ * How many Commitments are bound to a Compass, and whether it governs them. A Commitment stays
163
+ * bound to an archived Compass but runs on defaults: the platform reads only an ACTIVE Compass's
164
+ * direction. "Governed by this compass: 1" printed the same words for both, on the Compass side,
165
+ * while the Commitment's own preflight said it was running on defaults (OSK-21242).
166
+ */
167
+ export declare function describeBoundCommitments(count: number, status: string | undefined): string;
168
+ /**
169
+ * What `compass tactic` says back. Three outcomes, each in its own words: it used to print
170
+ * "Appended" for all of them, with a count that was always 0, and name nothing that rolled off.
171
+ * A server that does not report `tactic` is not guessed at.
172
+ */
173
+ export declare function renderTacticResult(compass: Compass, log: (line: string) => void): void;
174
+ /**
175
+ * `compass effective-policy` in words (OSK-21150): the mode beside what was written when they
176
+ * differ, who chose it, the quiet hours that apply (or why none do), the communication defaults, and
177
+ * where each field came from. It used to print each of those as one JSON line, with a null mode and
178
+ * "derived" unexplained. `--json` is unchanged.
179
+ */
180
+ export declare function renderEffectivePolicy(policy: Record<string, unknown>, log: (line: string) => void): void;
116
181
  export declare function renderRevisionList(rows: CompassRevision[], log: (line: string) => void): void;
117
182
  export declare function renderCompassTrace(trace: CompassTrace, log: (line: string) => void): void;
183
+ /**
184
+ * The ledger's name for a row. A revert is stored as `auto_revert`, which reads as something the
185
+ * platform did on its own, so a person's `compass revert` is shown as `revert`, with the versions
186
+ * it moved between: `from v6 → v2` is the version it was at and the version whose content it took.
187
+ * Display only: the stored enum is unchanged.
188
+ */
189
+ export declare function revisionTypeLabel(row: CompassRevision): string;
190
+ /**
191
+ * What `compass revert` says after it ran. Three things the old one-liner left out: whether
192
+ * anything was written, that the status is whatever it was (a revert never changes lifecycle), and
193
+ * which enforced rules the restored version no longer carries.
194
+ */
195
+ export declare function renderRevertResult(compass: Compass, toVersion: number, agentId: string, log: (line: string) => void): void;
@@ -2,9 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.compassApi = exports.COMPASS_STATUSES = void 0;
4
4
  exports.renderCompassList = renderCompassList;
5
+ exports.describeSettings = describeSettings;
6
+ exports.describeQuietHours = describeQuietHours;
7
+ exports.describeMode = describeMode;
5
8
  exports.renderCompass = renderCompass;
9
+ exports.describeBoundCommitments = describeBoundCommitments;
10
+ exports.renderTacticResult = renderTacticResult;
11
+ exports.renderEffectivePolicy = renderEffectivePolicy;
6
12
  exports.renderRevisionList = renderRevisionList;
7
13
  exports.renderCompassTrace = renderCompassTrace;
14
+ exports.revisionTypeLabel = revisionTypeLabel;
15
+ exports.renderRevertResult = renderRevertResult;
8
16
  const data_provider_1 = require("@skrr-ai/data-provider");
9
17
  /**
10
18
  * compasses.ts — request-based client for `/api/compasses`.
@@ -79,6 +87,76 @@ function renderCompassList(rows, log) {
79
87
  { key: 'updated', header: 'UPDATED' },
80
88
  ], log);
81
89
  }
90
+ function scalar(value) {
91
+ if (value === null || value === undefined)
92
+ return 'none';
93
+ return typeof value === 'object' ? JSON.stringify(value) : String(value);
94
+ }
95
+ /**
96
+ * One setting per line (`key: value`, nested objects as dotted keys, lists joined with `;`), so a
97
+ * settings object reads as labelled lines rather than one JSON blob (OSK-21150).
98
+ */
99
+ function describeSettings(value, prefix = '') {
100
+ if (!value || typeof value !== 'object' || Array.isArray(value))
101
+ return [];
102
+ const lines = [];
103
+ for (const [key, entry] of Object.entries(value)) {
104
+ if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
105
+ lines.push(...describeSettings(entry, `${prefix}${key}.`));
106
+ }
107
+ else if (Array.isArray(entry)) {
108
+ lines.push(`${prefix}${key}: ${entry.map(scalar).join('; ') || 'none'}`);
109
+ }
110
+ else {
111
+ lines.push(`${prefix}${key}: ${scalar(entry)}`);
112
+ }
113
+ }
114
+ return lines;
115
+ }
116
+ /** A quiet-hours window in words: the hours and the zone, `off`, or `not set`. */
117
+ function describeQuietHours(window) {
118
+ if (!window || typeof window !== 'object' || Array.isArray(window))
119
+ return 'not set';
120
+ const w = window;
121
+ if (w.enabled !== true)
122
+ return 'off';
123
+ const zone = typeof w.timezone === 'string' && w.timezone.trim() ? w.timezone.trim() : 'UTC (no zone set)';
124
+ return `${scalar(w.start ?? '?')} to ${scalar(w.end ?? '?')} ${zone}`;
125
+ }
126
+ /**
127
+ * The mode, and the word the author wrote when it differs from it. The server stores `auto` as
128
+ * `auto_run_limited`; saying so beats showing a value nobody typed as if they had (OSK-21150).
129
+ */
130
+ function describeMode(mode, authoredMode) {
131
+ const normalized = typeof mode === 'string' && mode ? mode : null;
132
+ const authored = typeof authoredMode === 'string' && authoredMode ? authoredMode : null;
133
+ if (!normalized) {
134
+ return authored
135
+ ? `not set (${JSON.stringify(authored)} is not a mode the platform knows)`
136
+ : 'not set (the platform default applies)';
137
+ }
138
+ return authored && authored !== normalized
139
+ ? `${normalized} (normalized; you wrote ${JSON.stringify(authored)})`
140
+ : normalized;
141
+ }
142
+ const SOURCE_WORDS = {
143
+ authored: 'the author',
144
+ derived: 'nobody (a default the platform derived)',
145
+ };
146
+ function labelled(label, lines, log, width = 12) {
147
+ lines.forEach((line, index) => log(`${index === 0 ? label.padEnd(width) : ' '.repeat(width)}${line}`));
148
+ }
149
+ /** The stored autonomy defaults, as the lines `show` prints (no JSON). */
150
+ function autonomyLines(autonomy) {
151
+ const { mode, authoredMode, modeSource, quietHours, ...rest } = autonomy;
152
+ const source = typeof modeSource === 'string' ? (SOURCE_WORDS[modeSource] ?? modeSource) : undefined;
153
+ const out = [];
154
+ const modeLine = `${describeMode(mode, authoredMode)}${source ? `; set by ${source}` : ''}`;
155
+ out.push(['Autonomy', [modeLine, ...describeSettings(rest)]]);
156
+ if (quietHours !== undefined)
157
+ out.push(['Quiet hours', [describeQuietHours(quietHours)]]);
158
+ return out;
159
+ }
82
160
  function renderCompass(compass, log) {
83
161
  log(`${compass.title ?? '(untitled compass)'} (${compass.id})`);
84
162
  log(`Agent ${compass.agentId}`);
@@ -86,12 +164,13 @@ function renderCompass(compass, log) {
86
164
  if (compass.domain)
87
165
  log(`Domain ${compass.domain}`);
88
166
  if (compass.scope)
89
- log(`Scope ${JSON.stringify(compass.scope)}`);
167
+ labelled('Scope', describeSettings(compass.scope), log);
90
168
  if (compass.autonomyDefaults) {
91
- log(`Autonomy ${JSON.stringify(compass.autonomyDefaults)}`);
169
+ for (const [label, lines] of autonomyLines(compass.autonomyDefaults))
170
+ labelled(label, lines, log);
92
171
  }
93
172
  if (compass.communicationDefaults) {
94
- log(`Comms ${JSON.stringify(compass.communicationDefaults)}`);
173
+ labelled('Comms', describeSettings(compass.communicationDefaults), log);
95
174
  }
96
175
  const rules = compass.rules ?? [];
97
176
  if (rules.length) {
@@ -126,6 +205,13 @@ function renderCompass(compass, log) {
126
205
  if (target.successMetrics?.length) {
127
206
  log(` measured by: ${target.successMetrics.join('; ')}`);
128
207
  }
208
+ // Tactics live on a target, not on the compass; they used to be printed nowhere.
209
+ if (target.learnedTactics?.length) {
210
+ log(` learned tactics (${target.learnedTactics.length}):`);
211
+ for (const tactic of target.learnedTactics) {
212
+ log(` • ${tactic.id ?? '—'}: ${tactic.description ?? '—'}`);
213
+ }
214
+ }
129
215
  // The agent's prompt carries a bounded number of each list. Say which
130
216
  // were cut, so the list above is not read as what the agent sees.
131
217
  for (const overflow of (0, data_provider_1.describeCompassOverflow)(target)) {
@@ -133,17 +219,103 @@ function renderCompass(compass, log) {
133
219
  }
134
220
  }
135
221
  }
136
- const tactics = compass.learnedTactics ?? [];
137
- if (tactics.length) {
138
- log('');
139
- log(`Learned tactics (${tactics.length}):`);
140
- for (const tactic of tactics)
141
- log(` • ${tactic.description ?? '—'}`);
142
- }
143
222
  const commitments = compass.commitments ?? [];
144
223
  if (commitments.length) {
145
224
  log('');
146
- log(`Commitments governed by this compass: ${commitments.length}`);
225
+ log(describeBoundCommitments(commitments.length, compass.status));
226
+ }
227
+ }
228
+ /**
229
+ * How many Commitments are bound to a Compass, and whether it governs them. A Commitment stays
230
+ * bound to an archived Compass but runs on defaults: the platform reads only an ACTIVE Compass's
231
+ * direction. "Governed by this compass: 1" printed the same words for both, on the Compass side,
232
+ * while the Commitment's own preflight said it was running on defaults (OSK-21242).
233
+ */
234
+ function describeBoundCommitments(count, status) {
235
+ return status === 'archived'
236
+ ? `Commitments bound to this compass: ${count} (not governed while the compass is archived)`
237
+ : `Commitments governed by this compass: ${count}`;
238
+ }
239
+ /**
240
+ * What `compass tactic` says back. Three outcomes, each in its own words: it used to print
241
+ * "Appended" for all of them, with a count that was always 0, and name nothing that rolled off.
242
+ * A server that does not report `tactic` is not guessed at.
243
+ */
244
+ function renderTacticResult(compass, log) {
245
+ const tactic = compass.tactic;
246
+ if (!tactic) {
247
+ log(`Recorded. ${compass.id} is now v${compass.version}. This server did not report what changed; ` +
248
+ 'run `compass show` to see the learned tactics.');
249
+ return;
250
+ }
251
+ const where = tactic.targetInvariantId ? `invariant ${tactic.targetInvariantId}` : 'its target';
252
+ const carries = `${compass.id} now carries ${tactic.count} learned tactic(s) on ${where}`;
253
+ if (tactic.outcome === 'unchanged') {
254
+ log(`No change — tactic already recorded. ${tactic.id} already reads that way on ${where}; ` +
255
+ `${compass.id} stays at v${compass.version}.`);
256
+ }
257
+ else if (tactic.outcome === 'updated') {
258
+ log(`Updated. ${tactic.id} on ${where} has the new text. ${carries} (v${compass.version}).`);
259
+ }
260
+ else {
261
+ log(`Appended. ${tactic.id} filed under ${where}. ${carries} (v${compass.version}).`);
262
+ }
263
+ for (const dropped of tactic.dropped ?? []) {
264
+ log(`Rolled off to stay within the cap: ${dropped.id} — "${dropped.description}"`);
265
+ }
266
+ }
267
+ const record = (value) => value && typeof value === 'object' && !Array.isArray(value)
268
+ ? value
269
+ : {};
270
+ /**
271
+ * `compass effective-policy` in words (OSK-21150): the mode beside what was written when they
272
+ * differ, who chose it, the quiet hours that apply (or why none do), the communication defaults, and
273
+ * where each field came from. It used to print each of those as one JSON line, with a null mode and
274
+ * "derived" unexplained. `--json` is unchanged.
275
+ */
276
+ function renderEffectivePolicy(policy, log) {
277
+ log(`Compass ${scalar(policy.compassId)}, revision ${scalar(policy.compassVersion)}: ${scalar(policy.status)}`);
278
+ log(`Applies to runtime: ${policy.appliesToRuntime === true ? 'yes' : 'no'}`);
279
+ const autonomy = record(policy.autonomy);
280
+ log('Autonomy');
281
+ const source = typeof autonomy.modeSource === 'string' ? autonomy.modeSource : undefined;
282
+ labelled(' Mode', [describeMode(autonomy.mode, autonomy.authoredMode)], log, 16);
283
+ if (source)
284
+ labelled(' Mode set by', [SOURCE_WORDS[source] ?? source], log, 16);
285
+ const applied = autonomy.quietHours;
286
+ const configured = autonomy.configuredQuietHours;
287
+ const issues = Array.isArray(autonomy.quietHoursIssues) ? autonomy.quietHoursIssues : [];
288
+ labelled(' Quiet hours', [
289
+ applied
290
+ ? describeQuietHours(applied)
291
+ : configured
292
+ ? `not applied (stored: ${describeQuietHours(configured)})`
293
+ : 'not set',
294
+ ], log, 16);
295
+ for (const issue of issues) {
296
+ const message = record(issue).message;
297
+ if (typeof message === 'string')
298
+ log(` ! ${message} Quiet hours do not apply until this is fixed.`);
299
+ }
300
+ const communication = describeSettings(policy.communication);
301
+ log('Communication');
302
+ if (communication.length === 0)
303
+ log(' nothing set');
304
+ for (const line of communication)
305
+ log(` ${line}`);
306
+ log(`Authority: ${scalar(policy.authority ?? 'unknown')}`);
307
+ const bySource = new Map();
308
+ for (const [path, entry] of Object.entries(record(policy.fieldProvenance))) {
309
+ const from = scalar(record(entry).source ?? 'unknown');
310
+ bySource.set(from, [...(bySource.get(from) ?? []), path]);
311
+ }
312
+ if (bySource.size === 0) {
313
+ log('Field provenance: none (no defaults are set)');
314
+ }
315
+ else {
316
+ log('Field provenance:');
317
+ for (const [from, paths] of bySource)
318
+ log(` ${from}: ${paths.join(', ')}`);
147
319
  }
148
320
  }
149
321
  function renderRevisionList(rows, log) {
@@ -153,7 +325,7 @@ function renderRevisionList(rows, log) {
153
325
  }
154
326
  (0, format_1.renderTable)(rows.map((row) => ({
155
327
  revision: `v${row.revision}`,
156
- type: row.revisionType ?? '—',
328
+ type: revisionTypeLabel(row),
157
329
  when: shortDate(row.createdAt),
158
330
  actor: row.actorUserId ?? '—',
159
331
  reason: row.reason ?? '—',
@@ -194,3 +366,51 @@ function renderCompassTrace(trace, log) {
194
366
  if (trace.has_more)
195
367
  log('More events exist; request a larger --limit (up to 500).');
196
368
  }
369
+ /**
370
+ * The ledger's name for a row. A revert is stored as `auto_revert`, which reads as something the
371
+ * platform did on its own, so a person's `compass revert` is shown as `revert`, with the versions
372
+ * it moved between: `from v6 → v2` is the version it was at and the version whose content it took.
373
+ * Display only: the stored enum is unchanged.
374
+ */
375
+ function revisionTypeLabel(row) {
376
+ // Reviving an archived compass has no ledger type of its own (the stored enum has no member for it),
377
+ // so it was recorded and shown as a plain `update`, indistinguishable from an edit. Each revision
378
+ // carries the status before and after (`status` is one of the captured config fields), so the
379
+ // lifecycle act is read from that; display only, the stored type is unchanged (OSK-21244).
380
+ if (row.revisionType === 'update' &&
381
+ row.previousConfig?.status === 'archived' &&
382
+ row.nextConfig?.status === 'active') {
383
+ return 'reactivate';
384
+ }
385
+ if (row.revisionType !== 'auto_revert')
386
+ return row.revisionType ?? '—';
387
+ const { fromVersion, toVersion } = row.metadata?.autoRevert ?? {};
388
+ return fromVersion !== undefined && toVersion !== undefined
389
+ ? `revert from v${fromVersion} → v${toVersion}`
390
+ : 'revert';
391
+ }
392
+ /**
393
+ * What `compass revert` says after it ran. Three things the old one-liner left out: whether
394
+ * anything was written, that the status is whatever it was (a revert never changes lifecycle), and
395
+ * which enforced rules the restored version no longer carries.
396
+ */
397
+ function renderRevertResult(compass, toVersion, agentId, log) {
398
+ if (compass.unchanged) {
399
+ log(`Already at v${toVersion}'s content — nothing written. ${compass.id} stays at v${compass.version}.`);
400
+ log(`Status stays ${compass.status}.`);
401
+ return;
402
+ }
403
+ log(`Restored v${toVersion} as v${compass.version} — the intermediate versions stay in the ledger.`);
404
+ log(compass.status === 'archived'
405
+ ? `Status stays archived: a revert restores the configuration, never the lifecycle. ` +
406
+ `Reactivate it with \`compass update ${compass.id} --agent ${agentId} --status active\`.`
407
+ : `Status stays ${compass.status}.`);
408
+ const changed = compass.rulesChanged;
409
+ if (changed && (changed.to < changed.from || (changed.removed ?? []).length > 0)) {
410
+ const removed = changed.removed ?? [];
411
+ log(`Warning: rules went from ${changed.from} to ${changed.to}` +
412
+ `${removed.length ? ` (removed: ${removed.join(', ')})` : ''}. ` +
413
+ `A removed active rule is no longer enforced; ` +
414
+ `\`compass revert --to-version ${compass.version - 1}\` brings it back.`);
415
+ }
416
+ }
@@ -69,10 +69,18 @@ export interface FollowUpRow {
69
69
  /** Who settled it: `trigger_fire` for the runtime, otherwise the actor. */
70
70
  terminalBy?: string | null;
71
71
  escalatedInitiativeId?: string | null;
72
- /** The gate the fire passes: exempt_silent, exempt_user_directed, exempt_system, user_directed_guarded, autonomy_ladder, approval_ladder. */
72
+ /** The gate the fire passes: exempt_silent, exempt_user_directed, exempt_system, exempt_notification (a passive reminder), user_directed_guarded, autonomy_ladder, approval_ladder. */
73
73
  gate?: string | null;
74
74
  /** Set while its fire waits on an offline machine; it escalates after 60 minutes. */
75
75
  parkedAt?: string | null;
76
+ /** The SLA backstop of a paired event/poll watch: it escalates the watch when the wait outlives its deadline. */
77
+ slaFallback?: boolean;
78
+ /** Set while a fire was moved (quiet hours, working hours, an unavailable machine) instead of run. */
79
+ held?: {
80
+ until: string;
81
+ reason?: string | null;
82
+ note?: string | null;
83
+ } | null;
76
84
  capsule?: unknown;
77
85
  nextRunAt?: string | null;
78
86
  deadlineAt?: string | null;
@@ -100,6 +108,15 @@ export interface FollowUpCreateResponse {
100
108
  }
101
109
  /** One line telling a person the follow-up was recorded with weaker provenance than claimed. */
102
110
  export declare function producerDowngradeNotice(response: FollowUpCreateResponse): string | undefined;
111
+ /**
112
+ * What a reused answer means for the person who asked, for the kinds whose lane a repeat
113
+ * declaration cannot move (an act, a watch): it armed nothing and changed neither the note nor
114
+ * the time they typed, and the row that holds the lane keeps ITS time, which may be weeks away.
115
+ * `Already armed … was not duplicated` alone let `act --in 5m` read as scheduled for five minutes
116
+ * while the existing act waited thirty days. Say what is armed and how to move it, as the
117
+ * check-in answer does (OSK-15411).
118
+ */
119
+ export declare function reusedWithoutChangeMessage(row: FollowUpCreateResponse['followup'], what: string): string;
103
120
  export interface FollowUpObservability {
104
121
  generatedAt?: string;
105
122
  agents?: Array<{
@@ -123,6 +140,10 @@ export interface FollowUpObservability {
123
140
  pool: string | null;
124
141
  used: number;
125
142
  limit: number | null;
143
+ /** Creations the platform dropped on purpose today (an Agent's own check-in); null when unreadable. */
144
+ droppedToday?: number | null;
145
+ /** Creations refused with FOLLOWUP_BUDGET_EXCEEDED today; null when unreadable. */
146
+ refusedToday?: number | null;
126
147
  } | null;
127
148
  silenceRate: number | null;
128
149
  silentFires: number | null;
@@ -5,6 +5,7 @@ exports.whenFlagProblem = whenFlagProblem;
5
5
  exports.settledLine = settledLine;
6
6
  exports.recipeWatchBoundsRefusal = recipeWatchBoundsRefusal;
7
7
  exports.producerDowngradeNotice = producerDowngradeNotice;
8
+ exports.reusedWithoutChangeMessage = reusedWithoutChangeMessage;
8
9
  exports.resolveFollowUpAgent = resolveFollowUpAgent;
9
10
  exports.parseSubjectRef = parseSubjectRef;
10
11
  exports.followupSummaryLine = followupSummaryLine;
@@ -135,6 +136,25 @@ function producerDowngradeNotice(response) {
135
136
  return undefined;
136
137
  return `Note: recorded as ${downgraded.recorded}, not ${downgraded.requested}. ${downgraded.note}`;
137
138
  }
139
+ /**
140
+ * What a reused answer means for the person who asked, for the kinds whose lane a repeat
141
+ * declaration cannot move (an act, a watch): it armed nothing and changed neither the note nor
142
+ * the time they typed, and the row that holds the lane keeps ITS time, which may be weeks away.
143
+ * `Already armed … was not duplicated` alone let `act --in 5m` read as scheduled for five minutes
144
+ * while the existing act waited thirty days. Say what is armed and how to move it, as the
145
+ * check-in answer does (OSK-15411).
146
+ */
147
+ function reusedWithoutChangeMessage(row, what) {
148
+ const lines = [
149
+ `Already armed: ${row?.id} — ${what} exists and was not duplicated.`,
150
+ 'Nothing in this request was applied: it did not change the note or the time you gave.',
151
+ ];
152
+ if (row) {
153
+ lines.push(`Armed: ${row.why ? `"${row.why}"` : '(no note)'}, ${row.nextRunAt ? `next ${row.nextRunAt}` : 'waiting on its trigger'}.`);
154
+ }
155
+ lines.push(`To change the time: skrr followups reschedule ${row?.id ?? '<id>'} --at <instant> or --in <offset>.`);
156
+ return lines.join('\n');
157
+ }
138
158
  /**
139
159
  * A trigger subject becomes a durable concurrency key, so it cannot be prose.
140
160
  * Keep this grammar aligned with TriggerEngine's subject validation and the
@@ -213,5 +233,5 @@ function followupSummaryLine(row) {
213
233
  const subject = row.subject ? `${row.subject.type}:${row.subject.externalKey}` : '—';
214
234
  const kindForm = row.form ? `${row.kind}/${row.form}` : (row.kind ?? '—');
215
235
  const when = row.state === 'settled' ? `settled:${row.terminal ?? '?'}` : `next:${fmtWhen(row.nextRunAt)}`;
216
- return `${row.id} [${row.state ?? '?'}] ${kindForm} ${subject} ${when}`;
236
+ return `${row.id} [${row.state ?? '?'}] ${kindForm}${row.slaFallback ? ' (SLA fallback)' : ''} ${subject} ${when}`;
217
237
  }
@@ -22,6 +22,17 @@ export declare function pad(s: string, width: number): string;
22
22
  * surrogate pair is never split down the middle into two replacement glyphs.
23
23
  */
24
24
  export declare function truncate(s: string, max: number): string;
25
+ /**
26
+ * `truncate`, but never in the middle of a word.
27
+ *
28
+ * For a prose cell (a reason, a title), where `truncate` cuts a sentence at the
29
+ * column and leaves a half word that reads as a typo. The cut moves back to the
30
+ * last word boundary that fits, and loose punctuation is dropped before the
31
+ * ellipsis. A single token longer than the budget has no boundary to move to, so
32
+ * it is cut hard: a clipped word is better than an empty cell. A boundary so
33
+ * early it would throw away most of the budget is not taken for the same reason.
34
+ */
35
+ export declare function truncateAtWord(s: string, max: number): string;
25
36
  /**
26
37
  * Render an arbitrary API value as a single table cell.
27
38
  *
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.displayWidth = displayWidth;
4
4
  exports.pad = pad;
5
5
  exports.truncate = truncate;
6
+ exports.truncateAtWord = truncateAtWord;
6
7
  exports.formatCell = formatCell;
7
8
  exports.collapseWhitespace = collapseWhitespace;
8
9
  exports.renderTable = renderTable;
@@ -105,6 +106,34 @@ function truncate(s, max) {
105
106
  }
106
107
  return `${out}…`;
107
108
  }
109
+ /**
110
+ * `truncate`, but never in the middle of a word.
111
+ *
112
+ * For a prose cell (a reason, a title), where `truncate` cuts a sentence at the
113
+ * column and leaves a half word that reads as a typo. The cut moves back to the
114
+ * last word boundary that fits, and loose punctuation is dropped before the
115
+ * ellipsis. A single token longer than the budget has no boundary to move to, so
116
+ * it is cut hard: a clipped word is better than an empty cell. A boundary so
117
+ * early it would throw away most of the budget is not taken for the same reason.
118
+ */
119
+ function truncateAtWord(s, max) {
120
+ if (displayWidth(s) <= max)
121
+ return s;
122
+ if (max <= 1)
123
+ return '…';
124
+ const hard = truncate(s, max);
125
+ const kept = hard.slice(0, -1); // the longest prefix that leaves room for the ellipsis
126
+ const rest = s.slice(kept.length);
127
+ const trim = (value) => value.replace(/[\s,;:(\-—]+$/u, '');
128
+ // The cut already fell on a word boundary.
129
+ if (/^\s/u.test(rest))
130
+ return `${trim(kept)}…`;
131
+ const boundary = kept.search(/\s\S*$/u);
132
+ if (boundary > 0 && displayWidth(kept.slice(0, boundary)) >= max * 0.4) {
133
+ return `${trim(kept.slice(0, boundary))}…`;
134
+ }
135
+ return hard;
136
+ }
108
137
  /**
109
138
  * Render an arbitrary API value as a single table cell.
110
139
  *
@@ -2,8 +2,15 @@ import { withQuery } from './triggers';
2
2
  /** The decisions `PATCH /api/initiatives/:id/action` accepts. */
3
3
  export declare const INITIATIVE_ACTIONS: readonly ["approve", "edit_then_approve", "always_allow_similar", "dismiss", "never_suggest_again", "mute_source", "not_now", "snooze", "reopen", "resolve", "wait", "follow_up", "undo", "grant_autonomy", "only_when_urgent"];
4
4
  export type InitiativeAction = (typeof INITIATIVE_ACTIONS)[number];
5
- /** States an Initiative moves through before and after a decision. */
6
- export declare const INITIATIVE_STATES: readonly ["detected", "proposed", "approved", "executing", "waiting", "completed", "dismissed", "failed"];
5
+ /**
6
+ * States an Initiative moves through before and after a decision: exactly the stored
7
+ * vocabulary (`AGENT_INITIATIVE_STATES`, `@skrr-ai/data-schemas`; a spec holds the two
8
+ * equal). This list used to say `completed`, a state nothing stores and the server refuses
9
+ * (`unsupported initiative state(s): completed`), and omitted `resolved`, so a person could
10
+ * not list the cards that had been approved and finished, nor the snoozed, expired,
11
+ * reopened or followed-up ones.
12
+ */
13
+ export declare const INITIATIVE_STATES: readonly ["detected", "proposed", "approved", "executing", "waiting", "followed_up", "reopened", "resolved", "dismissed", "snoozed", "expired", "failed"];
7
14
  export type InitiativeView = {
8
15
  id?: string;
9
16
  title?: string;
@@ -39,15 +39,26 @@ exports.INITIATIVE_ACTIONS = [
39
39
  'grant_autonomy',
40
40
  'only_when_urgent',
41
41
  ];
42
- /** States an Initiative moves through before and after a decision. */
42
+ /**
43
+ * States an Initiative moves through before and after a decision: exactly the stored
44
+ * vocabulary (`AGENT_INITIATIVE_STATES`, `@skrr-ai/data-schemas`; a spec holds the two
45
+ * equal). This list used to say `completed`, a state nothing stores and the server refuses
46
+ * (`unsupported initiative state(s): completed`), and omitted `resolved`, so a person could
47
+ * not list the cards that had been approved and finished, nor the snoozed, expired,
48
+ * reopened or followed-up ones.
49
+ */
43
50
  exports.INITIATIVE_STATES = [
44
51
  'detected',
45
52
  'proposed',
46
53
  'approved',
47
54
  'executing',
48
55
  'waiting',
49
- 'completed',
56
+ 'followed_up',
57
+ 'reopened',
58
+ 'resolved',
50
59
  'dismissed',
60
+ 'snoozed',
61
+ 'expired',
51
62
  'failed',
52
63
  ];
53
64
  exports.initiativeApi = {