@skrr-ai/cli 0.1.128 → 0.1.130

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 (103) hide show
  1. package/dist/base-command.d.ts +12 -0
  2. package/dist/base-command.js +65 -0
  3. package/dist/commands/agenda/trigger/create.js +9 -1
  4. package/dist/commands/agenda/trigger/list.d.ts +2 -1
  5. package/dist/commands/agenda/trigger/list.js +55 -6
  6. package/dist/commands/agents/create.js +3 -1
  7. package/dist/commands/agents/reflect.d.ts +6 -0
  8. package/dist/commands/agents/reflect.js +45 -6
  9. package/dist/commands/agents/schedule-overview.d.ts +17 -0
  10. package/dist/commands/agents/schedule-overview.js +33 -9
  11. package/dist/commands/agents/triggers/policy.js +3 -1
  12. package/dist/commands/browser/persistent-profile.d.ts +22 -0
  13. package/dist/commands/browser/persistent-profile.js +46 -0
  14. package/dist/commands/commitments/create.js +3 -1
  15. package/dist/commands/commitments/effective-policy.js +6 -0
  16. package/dist/commands/commitments/explain.js +10 -1
  17. package/dist/commands/commitments/plan.d.ts +14 -0
  18. package/dist/commands/commitments/plan.js +32 -0
  19. package/dist/commands/commitments/preflight.d.ts +1 -1
  20. package/dist/commands/commitments/preflight.js +12 -4
  21. package/dist/commands/commitments/show.d.ts +9 -0
  22. package/dist/commands/commitments/show.js +14 -1
  23. package/dist/commands/compass/revisions.d.ts +6 -2
  24. package/dist/commands/compass/revisions.js +13 -5
  25. package/dist/commands/compass/tactic.d.ts +5 -0
  26. package/dist/commands/compass/tactic.js +14 -0
  27. package/dist/commands/followups/plans/create.js +1 -1
  28. package/dist/commands/harnesses/list.js +1 -1
  29. package/dist/commands/northstars/create.js +6 -1
  30. package/dist/commands/northstars/governance/configure.js +1 -1
  31. package/dist/commands/northstars/intents/decide.js +2 -3
  32. package/dist/commands/northstars/list.js +1 -1
  33. package/dist/commands/northstars/review-now.js +5 -0
  34. package/dist/commands/northstars/reviews.js +15 -3
  35. package/dist/commands/northstars/show.js +1 -1
  36. package/dist/commands/spaces/auto-executor/runs.d.ts +9 -1
  37. package/dist/commands/spaces/auto-executor/runs.js +18 -1
  38. package/dist/commands/spaces/heartbeats/create.js +7 -5
  39. package/dist/commands/spaces/heartbeats/status.js +10 -0
  40. package/dist/commands/spaces/heartbeats/update.js +5 -3
  41. package/dist/commands/store/artifacts/releases.d.ts +20 -0
  42. package/dist/commands/store/artifacts/releases.js +85 -0
  43. package/dist/commands/store/library.d.ts +14 -0
  44. package/dist/commands/store/library.js +71 -0
  45. package/dist/commands/tasks/archive.js +6 -0
  46. package/dist/commands/tasks/list.d.ts +48 -0
  47. package/dist/commands/tasks/list.js +141 -9
  48. package/dist/commands/tasks/pause-schedule.js +1 -1
  49. package/dist/commands/tasks/remind.js +23 -11
  50. package/dist/commands/tasks/resume-schedule.js +4 -1
  51. package/dist/commands/tasks/retry-schedule.js +6 -3
  52. package/dist/commands/tasks/schedule.js +35 -8
  53. package/dist/commands/tasks/self-schedule.d.ts +56 -0
  54. package/dist/commands/tasks/self-schedule.js +179 -21
  55. package/dist/commands/tasks/show.js +4 -0
  56. package/dist/lib/agent-trigger-listing.d.ts +26 -0
  57. package/dist/lib/agent-trigger-listing.js +63 -0
  58. package/dist/lib/agentic-stream.js +138 -41
  59. package/dist/lib/commitment-ledgers.js +3 -1
  60. package/dist/lib/commitments.d.ts +24 -0
  61. package/dist/lib/commitments.js +64 -4
  62. package/dist/lib/compasses.d.ts +2 -0
  63. package/dist/lib/iso-instant.d.ts +52 -0
  64. package/dist/lib/iso-instant.js +162 -0
  65. package/dist/lib/northstars.d.ts +87 -4
  66. package/dist/lib/northstars.js +226 -94
  67. package/dist/lib/proposals.js +6 -2
  68. package/dist/lib/reflection.d.ts +58 -0
  69. package/dist/lib/reflection.js +115 -6
  70. package/dist/lib/store-artifact-releases.d.ts +39 -0
  71. package/dist/lib/store-artifact-releases.js +12 -0
  72. package/dist/lib/store-library.d.ts +37 -0
  73. package/dist/lib/store-library.js +12 -0
  74. package/dist/lib/task-calendar.d.ts +19 -0
  75. package/dist/lib/task-calendar.js +72 -0
  76. package/dist/lib/task-operations.js +2 -0
  77. package/dist/lib/task-recurrence-outcome.js +3 -1
  78. package/dist/lib/task-recurrence-summary.d.ts +15 -0
  79. package/dist/lib/task-recurrence-summary.js +228 -0
  80. package/dist/lib/task-reminder.d.ts +28 -0
  81. package/dist/lib/task-reminder.js +50 -0
  82. package/dist/lib/task-schedule-notes.d.ts +46 -0
  83. package/dist/lib/task-schedule-notes.js +71 -0
  84. package/dist/lib/triggers.d.ts +31 -2
  85. package/dist/lib/triggers.js +38 -18
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +11 -1
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +9 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +22 -0
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.d.ts +123 -0
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.js +160 -0
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +19 -0
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +11 -1
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +9 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +28 -0
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.d.ts +123 -0
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.js +173 -0
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +3 -1
  100. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  101. package/dist/node_modules/@skrr-ai/data-provider/index.js +6772 -6675
  102. package/oclif.manifest.json +282 -40
  103. package/package.json +1 -1
@@ -1,12 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.northstarWideFlag = exports.ALLOCATION_INPUT_FLAGS = exports.NORTHSTAR_LIST_DEFAULT_LIMIT = exports.NORTHSTAR_REASON_WORDS = exports.northstarApi = exports.NORTHSTAR_RECOMMENDATION_STATUSES = exports.NORTHSTAR_PRIORITY_TIERS = exports.NORTHSTAR_STATUSES = exports.NORTHSTAR_REVIEW_STATUSES = exports.NORTHSTAR_MANAGEMENT_INTENT_KINDS = exports.NORTHSTAR_INTENT_VERDICTS = exports.NORTHSTAR_EXECUTION_MODES = exports.NORTHSTAR_APPROVAL_TRIGGERS = exports.NORTHSTAR_ANALYSIS_MODES = void 0;
3
+ exports.northstarWideFlag = exports.CRITICAL_COMMITMENT_APPROVAL_NOTE = exports.ALLOCATION_INPUT_FLAGS = exports.NORTHSTAR_WORKSPACE_CREATE_HELP = exports.NORTHSTAR_WORKSPACE_READ_HELP = exports.NORTHSTAR_WEB_SCOPE_SENTENCE = exports.PERSONAL_NORTHSTAR_WORKSPACE = exports.NORTHSTAR_LIST_DEFAULT_LIMIT = exports.northstarApi = exports.NORTHSTAR_RECOMMENDATION_STATUSES = exports.NORTHSTAR_PRIORITY_TIERS = exports.NORTHSTAR_STATUSES = exports.describeNorthstarReason = exports.NORTHSTAR_REASON_WORDS = exports.NORTHSTAR_REVIEW_STATUSES = exports.NORTHSTAR_MANAGEMENT_INTENT_KINDS = exports.NORTHSTAR_INTENT_VERDICTS = exports.NORTHSTAR_EXECUTION_MODES = exports.NORTHSTAR_APPROVAL_TRIGGERS = exports.NORTHSTAR_ANALYSIS_MODES = void 0;
4
+ exports.intentRisk = intentRisk;
5
+ exports.shortReviewId = shortReviewId;
6
+ exports.lookupReview = lookupReview;
4
7
  exports.humanMs = humanMs;
5
8
  exports.shortDate = shortDate;
6
- exports.describeNorthstarReason = describeNorthstarReason;
7
9
  exports.describeDecisionRefusal = describeDecisionRefusal;
8
10
  exports.describeDecisionOutcome = describeDecisionOutcome;
9
11
  exports.listFooter = listFooter;
12
+ exports.shortWorkspaceId = shortWorkspaceId;
13
+ exports.northstarWorkspaceLabel = northstarWorkspaceLabel;
14
+ exports.northstarWorkspaceLine = northstarWorkspaceLine;
15
+ exports.personalNorthstarNote = personalNorthstarNote;
16
+ exports.createdNorthstarNote = createdNorthstarNote;
10
17
  exports.renderNorthstarList = renderNorthstarList;
11
18
  exports.describeMaxActive = describeMaxActive;
12
19
  exports.renderNorthstar = renderNorthstar;
@@ -15,6 +22,7 @@ exports.reviewerReadiness = reviewerReadiness;
15
22
  exports.readAllocationRefresh = readAllocationRefresh;
16
23
  exports.describeAllocationRefresh = describeAllocationRefresh;
17
24
  exports.describeReviewAllocations = describeReviewAllocations;
25
+ exports.runBudgetFloorNote = runBudgetFloorNote;
18
26
  exports.renderGovernance = renderGovernance;
19
27
  exports.describeAutoBudget = describeAutoBudget;
20
28
  exports.reviewNote = reviewNote;
@@ -51,7 +59,9 @@ Object.defineProperty(exports, "NORTHSTAR_APPROVAL_TRIGGERS", { enumerable: true
51
59
  Object.defineProperty(exports, "NORTHSTAR_EXECUTION_MODES", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_EXECUTION_MODES; } });
52
60
  Object.defineProperty(exports, "NORTHSTAR_INTENT_VERDICTS", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_INTENT_VERDICTS; } });
53
61
  Object.defineProperty(exports, "NORTHSTAR_MANAGEMENT_INTENT_KINDS", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_MANAGEMENT_INTENT_KINDS; } });
62
+ Object.defineProperty(exports, "NORTHSTAR_REASON_WORDS", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_REASON_WORDS; } });
54
63
  Object.defineProperty(exports, "NORTHSTAR_REVIEW_STATUSES", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_REVIEW_STATUSES; } });
64
+ Object.defineProperty(exports, "describeNorthstarReason", { enumerable: true, get: function () { return data_provider_1.describeNorthstarReason; } });
55
65
  const format_1 = require("./format");
56
66
  const triggers_1 = require("./triggers");
57
67
  /**
@@ -67,6 +77,37 @@ exports.NORTHSTAR_STATUSES = [
67
77
  ];
68
78
  exports.NORTHSTAR_PRIORITY_TIERS = ['critical', 'high', 'medium', 'low'];
69
79
  exports.NORTHSTAR_RECOMMENDATION_STATUSES = ['open', 'applied', 'dismissed'];
80
+ /* --------------------------------------------------------------------------
81
+ * Which risk a person sees
82
+ *
83
+ * Two different things carry a `riskLevel` and they are NOT one number. A recommendation
84
+ * (`northstars recommendations`) is rated by the reviewer when it recommends; an INTENT
85
+ * (`northstars intents`, `proposals show`) is rated by the governance policy for the change it
86
+ * would make, and that is the rating the policy gates on, records and sends in the
87
+ * awaiting-approval notice. They differ on purpose: a pause is recommended as `low` (it is the
88
+ * lowest-priority commitment over the cap) and is a `high`-risk change to apply. Printed side by
89
+ * side under one word they read as a contradiction (OSK-21192), so:
90
+ *
91
+ * - every surface that shows an INTENT shows the intent's own rating, labelled `risk`;
92
+ * - a recommendation's rating is labelled `recommendation risk`, wherever it is shown.
93
+ *
94
+ * A rating the server did not send is `unknown`, never `low`: an absent rating is not a safe one.
95
+ * -------------------------------------------------------------------------- */
96
+ function intentRisk(intent) {
97
+ return intent?.riskLevel || 'unknown';
98
+ }
99
+ /**
100
+ * A review id short enough for a table: `nsrev_3727b5959f436d29b69c1832` is `nsrev_3727b595`. The
101
+ * full id stays in `--json`. `reviews <northstar> --review` accepts the short form too
102
+ * (`resolveReviewRef`); an id that does not look like a prefix and a long hex run is left as it is.
103
+ */
104
+ const SHORT_ID_HEX = 8;
105
+ function shortReviewId(id) {
106
+ const match = /^([a-z]+_)([0-9a-f]+)$/i.exec(id);
107
+ return match && match[2].length > SHORT_ID_HEX
108
+ ? `${match[1]}${match[2].slice(0, SHORT_ID_HEX)}`
109
+ : id;
110
+ }
70
111
  const base = (id) => `/api/northstars/${encodeURIComponent(id)}`;
71
112
  exports.northstarApi = {
72
113
  /* --- portfolio CRUD ---------------------------------------------------- */
@@ -92,6 +133,30 @@ exports.northstarApi = {
92
133
  intents: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/intents`, query)),
93
134
  decideIntent: (id, intentId, body) => data_provider_1.request.post(`${base(id)}/intents/${encodeURIComponent(intentId)}/decide`, body),
94
135
  };
136
+ /**
137
+ * One review, from the id a person typed. The full id is asked for first, exactly as before. Only when
138
+ * the server has no review of that exact id is the typed text read as the start of one, among the
139
+ * northstar's newest 100 reviews: that is what the REVIEW column of `intents list` prints
140
+ * (`shortReviewId`), so the table's own output can be pasted back. A prefix of several reviews is
141
+ * reported, not guessed; one of none, or of a review older than the newest 100, raises the server's
142
+ * original "not found". Both ids are in `--json`.
143
+ */
144
+ async function lookupReview(northstarId, typed) {
145
+ try {
146
+ return { review: await exports.northstarApi.review(northstarId, typed) };
147
+ }
148
+ catch (error) {
149
+ if (error?.status !== 404 || !typed.trim())
150
+ throw error;
151
+ const listed = await exports.northstarApi.reviews(northstarId, { limit: 100 });
152
+ const matches = (listed.data ?? []).filter((review) => review.id.startsWith(typed.trim()));
153
+ if (matches.length === 0)
154
+ throw error;
155
+ if (matches.length > 1)
156
+ return { ambiguous: matches.map((review) => review.id) };
157
+ return { review: await exports.northstarApi.review(northstarId, matches[0].id) };
158
+ }
159
+ }
95
160
  /** ms → a short human duration, for a table cell that must stay narrow. */
96
161
  function humanMs(ms) {
97
162
  if (!Number.isFinite(Number(ms)))
@@ -110,84 +175,6 @@ function shortDate(value) {
110
175
  const date = new Date(value);
111
176
  return Number.isNaN(date.getTime()) ? '—' : date.toISOString().replace('T', ' ').slice(0, 16);
112
177
  }
113
- /* --------------------------------------------------------------------------
114
- * Reason codes in words
115
- *
116
- * The server refuses, skips and decides with machine codes
117
- * (`reviewer_agent_not_always_on`, `northstar_version_drift`, ...). A code is
118
- * for a script; a person reading a table or an error needs the sentence. This
119
- * is the ONE place those sentences live, so a command never invents its own
120
- * wording (the old governance line guessed "offline" for a reviewer that was
121
- * simply not Always-On) and `--json` can carry the same text as `reasonText`.
122
- *
123
- * Entries are CLAUSES, lower-case with no trailing full stop, so a caller can
124
- * put them after "agent turn not started:" or inside a longer sentence. A code
125
- * with no entry is returned AS IT IS: an unknown code printed raw is honest,
126
- * a guessed explanation is not.
127
- * ------------------------------------------------------------------------ */
128
- exports.NORTHSTAR_REASON_WORDS = {
129
- // An agent turn that was not reserved for an agent_assisted review.
130
- no_reviewer_agent_configured: 'no reviewer agent is set on this northstar',
131
- northstar_paused: 'the northstar is paused',
132
- northstar_achieved: 'the northstar is marked achieved',
133
- northstar_archived: 'the northstar is archived',
134
- analysis_mode_not_agent_assisted: 'analysis mode is deterministic, so no agent is asked',
135
- managed_agent_missing: 'the reviewer agent does not exist or is not visible to you',
136
- always_on_control_unavailable: "the reviewer agent's Always-On state could not be read just now",
137
- always_on_disabled: 'Always-On is turned off for the reviewer agent',
138
- always_on_suspended: 'Always-On is suspended for the reviewer agent',
139
- always_on_stale_generation: "the reviewer agent's Always-On state changed while this ran",
140
- reviewer_agent_foreign_owner: "the reviewer agent belongs to someone other than this northstar's owner",
141
- reviewer_agent_not_always_on: 'the reviewer agent is not Always-On (it is not offline; turn Always-On on for it)',
142
- run_already_reserved: 'an agent run was already reserved for this review',
143
- envelope_build_failed: 'the review could not be packaged for the agent',
144
- governance_run_reservation_failed: 'reserving the agent turn failed unexpectedly',
145
- run_not_reserved: 'the run queue declined to reserve the agent turn',
146
- enqueue_failed: 'the agent turn could not be queued',
147
- agent_run_reserved: 'an agent turn is reserved; its results land asynchronously',
148
- // Why an intent was recorded, held or refused by policy.
149
- execution_mode_advisory: 'execution mode is advisory: recorded for you to read, nothing is changed',
150
- execution_mode_approval: 'execution mode is approval: it waits for a person',
151
- proposal_only: 'a proposal: the governor never applies it, a person does',
152
- commitment_protected: "the commitment is on this northstar's protected list",
153
- intent_kind_not_allowed: "this kind of intent is not in the northstar's allowed list",
154
- critical_commitment_lifecycle: 'it changes a critical commitment, which always needs a person',
155
- resume_requires_approval: 'resuming always needs a person',
156
- budget_telemetry_unavailable: 'the automatic-change budget could not be read, so it fell back to approval',
157
- critical_demotion: 'it would demote a critical commitment',
158
- tier_change_too_large: 'the tier change is larger than one step',
159
- weight_change_too_large: 'the weight change exceeds the allowed relative change',
160
- bounds_increase_compute: 'it would buy more compute',
161
- bounds_change_exceeds_max_relative_change: 'the bounds change exceeds the allowed relative change',
162
- auto_pause_evidence_incomplete: 'the evidence for an automatic pause is incomplete',
163
- no_auto_rule_for_kind: 'no automatic rule covers this kind of change',
164
- auto_mutation_review_cap_reached: 'this review already made its maximum automatic changes',
165
- auto_mutation_daily_cap_reached: "today's automatic-change cap is used up",
166
- mutation_cooldown_active: 'the commitment was changed automatically too recently (cooldown)',
167
- commitment_not_linked_to_northstar: 'the commitment is no longer linked to this northstar',
168
- commitment_not_found: 'the commitment no longer exists',
169
- approved_by_user: 'approved by a person',
170
- rejected_by_user: 'rejected by a person',
171
- // Why a decision (approve / reject) was refused or did not take effect.
172
- commitment_version_drift: 'the commitment changed after this was proposed',
173
- northstar_version_drift: 'the northstar changed after this was proposed',
174
- superseded: 'a newer review proposed the same thing',
175
- recommendation_dismissed: 'its recommendation was dismissed',
176
- intent_not_found: "no such intent in this northstar's 50 most recent reviews",
177
- review_not_found: 'no such review on this northstar',
178
- northstar_not_found: 'no such northstar',
179
- owner_approval_required: "only the northstar's owner can approve a proposal",
180
- transition_lost: 'another decision landed first',
181
- claim_lost: 'another process is already applying it',
182
- mutation_error: 'the change failed part-way and was left for the repair pass',
183
- proposal_kind_requires_manual_action: 'this kind of proposal is completed by hand',
184
- };
185
- /** A code in words; an unknown code comes back unchanged, an absent one says so. */
186
- function describeNorthstarReason(code) {
187
- if (!code)
188
- return 'no reason was recorded';
189
- return exports.NORTHSTAR_REASON_WORDS[code] ?? code;
190
- }
191
178
  function utcStamp(value) {
192
179
  const stamp = shortDate(typeof value === 'string' ? value : undefined);
193
180
  return stamp === '—' ? undefined : `${stamp} UTC`;
@@ -242,7 +229,7 @@ function describeDecisionRefusal(body, ctx) {
242
229
  const when = utcStamp(body.decidedAt);
243
230
  return {
244
231
  code,
245
- text: `This intent was already invalidated${why ? ` (${describeNorthstarReason(why)})` : ''}${when ? ` at ${when}` : ''} — nothing changed.`,
232
+ text: `This intent was already invalidated${why ? ` (${(0, data_provider_1.describeNorthstarReason)(why)})` : ''}${when ? ` at ${when}` : ''} — nothing changed.`,
246
233
  };
247
234
  }
248
235
  let by = '';
@@ -268,7 +255,7 @@ function describeDecisionRefusal(body, ctx) {
268
255
  text: `No intent ${ctx.intentId} in this northstar's 50 most recent reviews. If it is older, pass --review <review-id>.`,
269
256
  };
270
257
  }
271
- const words = describeNorthstarReason(code);
258
+ const words = (0, data_provider_1.describeNorthstarReason)(code);
272
259
  return { code, text: `${words.charAt(0).toUpperCase()}${words.slice(1)}.` };
273
260
  }
274
261
  /**
@@ -329,7 +316,7 @@ function describeDecisionOutcome(result, ctx) {
329
316
  text = `${what}, so the approval was not applied and the intent was invalidated. ${reviewNow}`;
330
317
  }
331
318
  else if (code) {
332
- text = `Your approval is recorded, but nothing was applied: ${describeNorthstarReason(code)}.`;
319
+ text = `Your approval is recorded, but nothing was applied: ${(0, data_provider_1.describeNorthstarReason)(code)}.`;
333
320
  }
334
321
  else {
335
322
  text = `Your approval is recorded, but the change has not landed yet (verdict: ${verdict}). It is retried automatically; check \`${bin} northstars intents list ${ctx.northstarId}\`.`;
@@ -362,6 +349,93 @@ function listFooter(input) {
362
349
  ? `${head} --limit is already at its maximum (${input.max}), so narrow the list with --status`
363
350
  : `${head} raise --limit (max ${input.max})`;
364
351
  }
352
+ /* --------------------------------------------------------------------------
353
+ * Which workspace a northstar lives in (OSK-21643)
354
+ *
355
+ * The web Northstars list is scoped to the person's ACTIVE workspace and sends `X-Workspace-Id`, so
356
+ * a northstar with no workspace is hidden from it while a workspace is active. The API creates a
357
+ * northstar in the workspace the request names and in none otherwise, which is what a CLI without
358
+ * `--workspace` sends. Nothing on either surface said so, and a northstar made here simply seemed
359
+ * to be missing from the web.
360
+ *
361
+ * Every sentence about it comes from this block, so `list`, `show`, `create`, their `--help` and
362
+ * the docs cannot say different things. The docs quote `personalNorthstarNote` byte for byte and a
363
+ * spec holds them to it.
364
+ * -------------------------------------------------------------------------- */
365
+ /** What a northstar with no workspace is called wherever a workspace is named. */
366
+ exports.PERSONAL_NORTHSTAR_WORKSPACE = 'personal';
367
+ /** Why a personal northstar can look missing on the web. The one sentence the others build on. */
368
+ exports.NORTHSTAR_WEB_SCOPE_SENTENCE = `The ${data_provider_1.BRAND.displayName} web Northstars list shows only the active workspace's Northstars, so a personal one is hidden there while a workspace is active.`;
369
+ /** Help text shared by `list` and `show`: where a northstar's workspace is printed. */
370
+ exports.NORTHSTAR_WORKSPACE_READ_HELP = `A northstar belongs to the workspace it was created in, or is "${exports.PERSONAL_NORTHSTAR_WORKSPACE}" when it has none. ` +
371
+ `${exports.NORTHSTAR_WEB_SCOPE_SENTENCE}`;
372
+ /** Help text for `create`: which workspace it lands in, and how to choose one. */
373
+ exports.NORTHSTAR_WORKSPACE_CREATE_HELP = 'The northstar is created in the workspace the global --workspace <id> flag names, or the one chosen once with ' +
374
+ '`workspaces use`. With neither, or for a workspace you are not a member of, it is personal (no workspace), and ' +
375
+ 'the output says so. ' +
376
+ `${exports.NORTHSTAR_WEB_SCOPE_SENTENCE} An Agent session always uses its own session workspace, and --workspace does not change it.`;
377
+ /** The workspace id a northstar carries, or undefined for a personal one (absent, null or blank). */
378
+ function northstarWorkspaceId(northstar) {
379
+ const id = typeof northstar.workspaceId === 'string' ? northstar.workspaceId.trim() : '';
380
+ return id || undefined;
381
+ }
382
+ /**
383
+ * A workspace id short enough for a table: `038b2251-6a0c-4e2b-9f7d-c1d0f3a8b2aa` is `038b2251…` and
384
+ * `ws_01H8XKPQ7ABCDEF` is `ws_01H8XKPQ…`. Unlike a review id (`shortReviewId`) the short form is NOT
385
+ * accepted back by `--workspace`, which needs the full id, so a shortened value ends in `…` to say
386
+ * it is a prefix. The full id is in `--json` and in `skrr workspaces list`.
387
+ */
388
+ const WORKSPACE_ID_SHORT_CHARS = 8;
389
+ function shortWorkspaceId(id) {
390
+ const prefixed = /^([a-z]+_)(.+)$/i.exec(id);
391
+ const prefix = prefixed?.[1] ?? '';
392
+ const rest = prefixed?.[2] ?? id;
393
+ return rest.length > WORKSPACE_ID_SHORT_CHARS
394
+ ? `${prefix}${rest.slice(0, WORKSPACE_ID_SHORT_CHARS)}…`
395
+ : id;
396
+ }
397
+ /** The workspace a northstar lives in, in one word or id: `personal` when it has none. */
398
+ function northstarWorkspaceLabel(northstar, options = {}) {
399
+ const id = northstarWorkspaceId(northstar);
400
+ if (!id)
401
+ return exports.PERSONAL_NORTHSTAR_WORKSPACE;
402
+ return options.short ? shortWorkspaceId(id) : id;
403
+ }
404
+ /** The line `show` and `create` print: the full workspace id, or `personal`. */
405
+ function northstarWorkspaceLine(northstar) {
406
+ return `Workspace ${northstarWorkspaceLabel(northstar)}`;
407
+ }
408
+ /** Read from the environment `--workspace` and `workspaces use` both write (`withWorkspaceHeader`). */
409
+ function ambientPersonalContext(env = process.env) {
410
+ return {
411
+ autonomous: env.OVERSKY_AUTONOMOUS_SESSION === '1',
412
+ requestedWorkspaceId: env.OVERSKY_WORKSPACE_ID?.trim() || undefined,
413
+ };
414
+ }
415
+ /**
416
+ * What `create` adds when the northstar landed in no workspace. Three causes, three endings:
417
+ *
418
+ * - an Agent session takes its workspace from the session and ignores `--workspace`, so telling
419
+ * it to pass the flag would send it round in a circle;
420
+ * - a workspace WAS requested but the person is not a member of it: the server does not refuse,
421
+ * it creates the northstar personal (`resolveWorkspaceContext`), so "pass --workspace" would
422
+ * be said to someone who just did;
423
+ * - nothing was requested, and the way out is the flag.
424
+ */
425
+ function personalNorthstarNote(context = ambientPersonalContext()) {
426
+ const lead = `Not in a workspace (${exports.PERSONAL_NORTHSTAR_WORKSPACE}). ${exports.NORTHSTAR_WEB_SCOPE_SENTENCE}`;
427
+ if (context.autonomous) {
428
+ return `${lead} This Agent session has no workspace to create it in, and --workspace does not change that.`;
429
+ }
430
+ if (context.requestedWorkspaceId) {
431
+ return `${lead} Workspace ${context.requestedWorkspaceId} was requested, but a northstar is created in a workspace only for a member of it: check \`skrr workspaces list\`.`;
432
+ }
433
+ return `${lead} Pass the global --workspace <id> flag to create one inside a workspace.`;
434
+ }
435
+ /** The note for a northstar `create` just made: nothing when it is in a workspace. */
436
+ function createdNorthstarNote(northstar, context) {
437
+ return northstarWorkspaceId(northstar) ? undefined : personalNorthstarNote(context);
438
+ }
365
439
  function renderNorthstarList(rows, log) {
366
440
  if (rows.length === 0) {
367
441
  log('No northstars.');
@@ -376,6 +450,7 @@ function renderNorthstarList(rows, log) {
376
450
  commitments: String(row.state?.activeCommitmentCount ?? 0),
377
451
  open: String(row.state?.openRecommendationCount ?? 0),
378
452
  reviewed: shortDate(row.state?.lastReviewedAt),
453
+ workspace: northstarWorkspaceLabel(row, { short: true }),
379
454
  })), [
380
455
  { key: 'id', header: 'ID' },
381
456
  { key: 'title', header: 'TITLE', maxWidth: 40 },
@@ -385,6 +460,7 @@ function renderNorthstarList(rows, log) {
385
460
  { key: 'commitments', header: 'ACTIVE' },
386
461
  { key: 'open', header: 'OPEN REC' },
387
462
  { key: 'reviewed', header: 'REVIEWED' },
463
+ { key: 'workspace', header: 'WORKSPACE' },
388
464
  ], log);
389
465
  }
390
466
  /** A budget that was never set says so; `—` is kept for a timestamp that does not exist yet. */
@@ -409,6 +485,7 @@ function renderNorthstar(status, log) {
409
485
  log(`Attention total ${notSet(ns.attentionBudget?.totalWeight)} · max active ${describeMaxActive(ns.attentionBudget?.maxActiveCommitments)}`);
410
486
  const cadence = ns.reviewCadence ?? {};
411
487
  log(`Review ${cadence.mode ?? 'manual'}${cadence.intervalMs ? ` every ${humanMs(cadence.intervalMs)}` : ''} next ${shortDate(cadence.nextReviewAt)} last ${shortDate(ns.state?.lastReviewedAt)}`);
488
+ log(northstarWorkspaceLine(ns));
412
489
  if (ns.goalIds?.length)
413
490
  log(`Goals ${ns.goalIds.join(', ')}`);
414
491
  if (ns.agentIds?.length)
@@ -460,7 +537,9 @@ function renderRecommendationList(rows, log) {
460
537
  { key: 'kind', header: 'KIND' },
461
538
  { key: 'target', header: 'TARGET' },
462
539
  { key: 'status', header: 'STATUS' },
463
- { key: 'risk', header: 'RISK' },
540
+ // The reviewer's rating of the recommendation. The intent made from it is rated separately
541
+ // (`intents list` RISK) and may be higher; see `intentRisk`.
542
+ { key: 'risk', header: 'RECOMMENDATION RISK' },
464
543
  { key: 'reason', header: 'WHY', maxWidth: 46 },
465
544
  ], log);
466
545
  }
@@ -481,15 +560,15 @@ function reviewerReadiness(reviewer) {
481
560
  if (admission.admitted)
482
561
  return ' [ready]';
483
562
  if (admission.reason === 'analysis_mode_not_agent_assisted') {
484
- return ` [not in use: ${describeNorthstarReason(admission.reason)}]`;
563
+ return ` [not in use: ${(0, data_provider_1.describeNorthstarReason)(admission.reason)}]`;
485
564
  }
486
- return ` [not ready: ${describeNorthstarReason(admission.reason)}]`;
565
+ return ` [not ready: ${(0, data_provider_1.describeNorthstarReason)(admission.reason)}]`;
487
566
  }
488
567
  if (reviewer.ready)
489
568
  return ' [ready]';
490
569
  // No reason from an older server is "unknown", never a guess: the old fallback
491
570
  // said "offline" about an agent that was reachable and merely not Always-On.
492
- return ` [not ready: ${reviewer.reason ? describeNorthstarReason(reviewer.reason) : 'reason unknown'}]`;
571
+ return ` [not ready: ${reviewer.reason ? (0, data_provider_1.describeNorthstarReason)(reviewer.reason) : 'reason unknown'}]`;
493
572
  }
494
573
  /** The `governance configure` flags that move what a commitment's allocation is computed from. */
495
574
  exports.ALLOCATION_INPUT_FLAGS = [
@@ -555,6 +634,37 @@ function describeReviewAllocations(result) {
555
634
  }
556
635
  return 'Allocations already up to date: no commitment needed a write';
557
636
  }
637
+ /**
638
+ * The sentence for a portfolio run budget smaller than the number of linked active commitments
639
+ * (OSK-21334): each still gets the floor of 1 run/day, so the ceilings add up to more than the
640
+ * budget. The server derives it once (`portfolioRunBudgetFloor.js`) and carries it, when it
641
+ * applies, on the `governance show` read, the `review-now` result and the effective policy's
642
+ * `portfolio`; every one of those three prints THIS sentence, so none composes its own and they
643
+ * cannot disagree. Absent from an older server, and from a budget that covers every commitment:
644
+ * no line, never a guess.
645
+ *
646
+ * Takes the carried field as it arrives (`unknown`), because two of the three callers read it off
647
+ * an untyped body.
648
+ */
649
+ function runBudgetFloorNote(value) {
650
+ const floor = value;
651
+ if (!floor || floor.code !== 'run_budget_below_active_commitments')
652
+ return undefined;
653
+ return typeof floor.message === 'string' && floor.message ? floor.message : undefined;
654
+ }
655
+ /**
656
+ * What `critical_commitment` on the always-ask list does and does not do (OSK-21333).
657
+ *
658
+ * The list governs PROMOTION to critical (`critical_promotion`): remove the class and a bounded_auto
659
+ * review applies a small promotion itself. It does not govern the way back: a demotion away from
660
+ * critical is held for a person in `bounded_auto` whatever the list says (`critical_demotion`,
661
+ * `NorthstarGovernancePolicy.classifyIntent`: the demotion rule never reads the list). The behaviour
662
+ * is pinned by `NorthstarGovernancePolicy.spec.js` ("critical_commitment off the approval list"),
663
+ * the words by `cli/src/commands/northstars/__tests__/critical-approval-help.spec.ts`. The flag help
664
+ * and the `Approval for` line of `governance show` both read this one sentence, so neither can
665
+ * drift from the other.
666
+ */
667
+ exports.CRITICAL_COMMITMENT_APPROVAL_NOTE = 'critical_commitment governs promotion to critical only; demotion away from critical (critical_demotion) is always held for a person, whatever this list says';
558
668
  function renderGovernance(status, log) {
559
669
  const g = status.governance;
560
670
  log(`Northstar ${status.northstarId} (v${status.northstarVersion}, ${status.status})`);
@@ -576,6 +686,9 @@ function renderGovernance(status, log) {
576
686
  }
577
687
  log(`Approval for ${g.requireApprovalFor.join(', ') ||
578
688
  'none — no change class is always held for a person (the execution mode still decides)'}`);
689
+ // True whatever the list holds, so it is printed for an empty one too: removing
690
+ // `critical_commitment` frees promotion, and never the way back.
691
+ log(` ${exports.CRITICAL_COMMITMENT_APPROVAL_NOTE}.`);
579
692
  if (g.requireApprovalFor.length === 0) {
580
693
  log(' ! No change class is on the always-ask list, so the execution mode alone decides what waits for you.');
581
694
  }
@@ -583,6 +696,10 @@ function renderGovernance(status, log) {
583
696
  const eb = status.executionBudget ?? {};
584
697
  log(`Exec budget action runs/day ${notSet(eb.maxRunsPerDay)} · concurrent ${eb.maxConcurrentRuns == null ? 'not set' : `${eb.maxConcurrentRuns} (recorded, not enforced)`} · critical reserve ${notSet(eb.reserveWeightForCritical)}`);
585
698
  log(`Observation runs/day ${notSet(eb.maxObservationRunsPerDay)} · daily budgets reset at 00:00 UTC`);
699
+ // A budget below the number of active commitments is still given to each at the floor of 1/day.
700
+ const floorNote = runBudgetFloorNote(status.runBudgetFloor);
701
+ if (floorNote)
702
+ log(` ! ${floorNote}`);
586
703
  log('');
587
704
  const trigger = status.reviewTrigger;
588
705
  log(trigger.scheduled
@@ -660,13 +777,13 @@ function reviewNote(review) {
660
777
  }
661
778
  function agentTurnNote(review) {
662
779
  if (review.status === 'failed') {
663
- return `failed: ${review.errorMessage ?? describeNorthstarReason(review.reason)}`;
780
+ return `failed: ${review.errorMessage ?? (0, data_provider_1.describeNorthstarReason)(review.reason)}`;
664
781
  }
665
782
  if (review.status === 'awaiting_agent')
666
783
  return 'waiting for the agent turn';
667
784
  if (review.analysisMode === 'agent_assisted' && review.status === 'succeeded') {
668
785
  if (review.reason && AGENT_TURN_NOT_STARTED.has(review.reason)) {
669
- return `agent turn not started: ${describeNorthstarReason(review.reason)}`;
786
+ return `agent turn not started: ${(0, data_provider_1.describeNorthstarReason)(review.reason)}`;
670
787
  }
671
788
  if (!review.autonomousRunId)
672
789
  return 'agent turn not started: no reason was recorded';
@@ -675,7 +792,7 @@ function agentTurnNote(review) {
675
792
  }
676
793
  /** `Reason` line of one review: the words, with the raw code kept beside them for grep. */
677
794
  function describeReviewReason(reason) {
678
- const words = describeNorthstarReason(reason);
795
+ const words = (0, data_provider_1.describeNorthstarReason)(reason);
679
796
  return words === reason ? reason : `${words} [${reason}]`;
680
797
  }
681
798
  function renderReviewList(reviews, log) {
@@ -734,40 +851,55 @@ function renderIntentList(intents, log, options = {}) {
734
851
  log('No management intents.');
735
852
  return;
736
853
  }
854
+ // The flattened list (`intents list`) carries the review each intent came from; one review's own
855
+ // intents (`reviews --review`, `review-now`) all share the review the command already names, so a
856
+ // column of one repeated value would only push WHY off the screen.
857
+ const withReview = intents.some((intent) => Boolean(intent.reviewId));
737
858
  const rows = intents.map((intent) => ({
738
859
  id: intent.id,
860
+ review: intent.reviewId ? shortReviewId(intent.reviewId) : '—',
739
861
  kind: intent.kind,
740
862
  target: intent.targetCommitmentId ?? '—',
741
863
  verdict: intent.verdict,
864
+ // The intent's OWN rating (see `intentRisk`), never its recommendation's.
865
+ risk: intentRisk(intent),
742
866
  // No sentence from the governor: the policy code in words (an unknown code stays as it is).
743
- why: intent.reason ?? (intent.verdictReason ? describeNorthstarReason(intent.verdictReason) : '—'),
867
+ why: intent.reason ?? (intent.verdictReason ? (0, data_provider_1.describeNorthstarReason)(intent.verdictReason) : '—'),
744
868
  code: intent.verdictReason ?? '',
745
869
  source: intent.source ?? 'deterministic',
746
870
  }));
747
871
  const headers = {
748
872
  id: 'INTENT',
873
+ review: 'REVIEW',
749
874
  kind: 'KIND',
750
875
  target: 'COMMITMENT',
751
876
  verdict: 'VERDICT',
877
+ risk: 'RISK',
752
878
  source: 'SOURCE',
753
879
  };
754
- const fixedKeys = ['id', 'kind', 'target', 'verdict', 'source'];
880
+ const fixedKeys = (withReview
881
+ ? ['id', 'review', 'kind', 'target', 'verdict', 'risk', 'source']
882
+ : ['id', 'kind', 'target', 'verdict', 'risk', 'source']);
755
883
  const fixedWidth = fixedKeys.reduce((sum, key) => sum + Math.max((0, format_1.displayWidth)(headers[key]), ...rows.map((row) => (0, format_1.displayWidth)(row[key]))), 0);
756
884
  const terminal = options.columns ?? process.stdout.columns ?? INTENT_TABLE_DEFAULT_COLUMNS;
757
- // Five gaps of two spaces separate the six columns.
758
- const whyBudget = Math.max(INTENT_WHY_MIN_WIDTH, terminal - fixedWidth - 10);
885
+ // Two spaces separate every fixed column from the next, and the last from WHY.
886
+ const whyBudget = Math.max(INTENT_WHY_MIN_WIDTH, terminal - fixedWidth - fixedKeys.length * 2);
759
887
  (0, format_1.renderTable)(rows.map((row) => ({
760
888
  id: row.id,
889
+ review: row.review,
761
890
  kind: row.kind,
762
891
  target: row.target,
763
892
  verdict: row.verdict,
893
+ risk: row.risk,
764
894
  source: row.source,
765
895
  why: whyCell(row, options.wide === true, whyBudget),
766
896
  })), [
767
897
  { key: 'id', header: headers.id },
898
+ ...(withReview ? [{ key: 'review', header: headers.review }] : []),
768
899
  { key: 'kind', header: headers.kind },
769
900
  { key: 'target', header: headers.target },
770
901
  { key: 'verdict', header: headers.verdict },
902
+ { key: 'risk', header: headers.risk },
771
903
  { key: 'source', header: headers.source },
772
904
  { key: 'why', header: 'WHY' },
773
905
  ], log);
@@ -153,11 +153,15 @@ function renderNorthstarIntentView(view) {
153
153
  return null;
154
154
  const context = view.decisionContext;
155
155
  const verdict = view.sourceStatus;
156
- const risk = context.riskLevel ?? context.snapshot.riskLevel ?? 'unknown';
156
+ // The intent's own rating (`intentRisk`), the one `northstars intents list` prints under RISK.
157
+ const risk = (0, northstars_1.intentRisk)({ riskLevel: context.riskLevel ?? context.snapshot.riskLevel });
157
158
  const hasTarget = Boolean(context.snapshot.targetCommitmentId || view.commitmentId);
158
159
  const lines = [proposalTitleFor(view), ''];
159
160
  lines.push(`Proposal ${view.id}`);
160
- lines.push(`Kind ${context.snapshot.kind} · risk ${risk}`);
161
+ lines.push(`Kind ${context.snapshot.kind}`);
162
+ // Said once, here, because this is where a person who saw a lower "recommendation risk" on the
163
+ // recommendation this came from looks for the reason the two differ.
164
+ lines.push(`Risk ${risk} (rated for this change; the recommendation behind it has its own "recommendation risk")`);
161
165
  lines.push(`Target ${hasTarget ? intentTarget(view) : 'none (a new commitment)'}`);
162
166
  if (context.reason)
163
167
  lines.push(`Why ${context.reason}`);
@@ -36,15 +36,73 @@ export declare const MAX_REFLECTION_CADENCE_MINUTES: number;
36
36
  * out to `skrr` from Bash.
37
37
  */
38
38
  export declare const REFLECTION_PROMPT = "[Self-reflection] Step back and review your own schedule and cadence \u2014 this is a planning turn, not a chat turn.\n\n1. Look at your whole schedule in one view: your recent fires (past), your upcoming triggers, your open tasks, and today's to-dos. (Use your schedule-overview tool, or run `skrr agents schedule-overview <your-agent-id>`.)\n2. Judge your own rhythm honestly: are you waking too often (many fires, little changed) or too rarely (things slipping past their time)?\n3. If your reflection cadence is wrong, retune it \u2014 reschedule THIS self-reflection trigger to a better interval.\n4. Fix what is slipping: reschedule, create, or cancel triggers and tasks so your plan matches reality.\n\nBe deliberate and minimal. Every trigger you keep spends the user's compute \u2014 keep only what earns its place. If nothing needs changing, do nothing and end the turn.";
39
+ /**
40
+ * A cadence the caller got wrong: unparseable, empty, or not a positive number of
41
+ * minutes. Mirrors `InvalidCadenceError` in the server copy, minus its 4xx `status`:
42
+ * that field is how an MCP server tells the caller's mistake from its own failure,
43
+ * and on the CLI the same field would make `handleApiError` read this as an HTTP
44
+ * response. Commands catch it and report it as a usage error.
45
+ */
46
+ export declare class InvalidCadenceError extends Error {
47
+ readonly code = "INVALID_CADENCE";
48
+ constructor(message: string);
49
+ }
39
50
  export declare function parseCadenceToMinutes(input: string): number;
40
51
  /** Clamp a cadence to the allowed reflection window. */
41
52
  export declare function clampCadenceMinutes(minutes: number): number;
53
+ /** What `applyCadenceBounds` did to a requested cadence. */
54
+ export interface CadenceBounds {
55
+ minutes: number;
56
+ requestedMinutes: number;
57
+ clamped: boolean;
58
+ /** The limit that moved the request, or null when it was inside the window. */
59
+ bound: 'minimum' | 'maximum' | null;
60
+ }
61
+ /**
62
+ * Apply the reflection window to a requested cadence and SAY what happened, so every
63
+ * caller can report "asked 5m, applied 15m" instead of only the clamped number
64
+ * (OSK-21486, OSK-21493, OSK-21602). `bound` names the limit that moved it.
65
+ */
66
+ export declare function applyCadenceBounds(requestedMinutes: number): CadenceBounds;
67
+ /**
68
+ * One sentence for a clamp ("5m is below the 15m minimum; applied 15m."), or null when
69
+ * the request was inside the window. Shared by the MCP result `note` and the CLI line.
70
+ */
71
+ export declare function describeCadenceClamp({ minutes, requestedMinutes, bound, }: Pick<CadenceBounds, 'minutes' | 'requestedMinutes' | 'bound'>): string | null;
42
72
  /** Render minutes back as a compact human cadence ("6h", "90m", "1d"). */
43
73
  export declare function describeCadenceMinutes(minutes: number): string;
44
74
  /** The agent's single self-reflection trigger, or undefined if none exists. */
45
75
  export declare function findReflectionTrigger(triggers: TriggerDefinition[]): TriggerDefinition | undefined;
46
76
  /** Read a reflection trigger's current interval in minutes, if it has one. */
47
77
  export declare function reflectionCadenceMinutes(trigger: TriggerDefinition): number | undefined;
78
+ /**
79
+ * The loop's next run, for display: its scheduled time while it is enabled, else `-`. A
80
+ * paused loop has no next run, and a loop with nothing scheduled does not have its LAST fire
81
+ * promoted to one (the server copy keeps `nextRunAt` and `lastFiredAt` apart the same way).
82
+ */
83
+ export declare function reflectionNextRunLabel(trigger: TriggerDefinition): string;
84
+ /**
85
+ * Whether the loop's fires run, as the SERVER says it (`GET /api/triggers?include=autonomy`), or
86
+ * null when it did not say. Nothing is derived here: the verdict depends on the agent's
87
+ * initiative mode and on which door created the loop, and a local copy of that judgement is free
88
+ * to disagree with the fire path (OSK-21606). A server that predates the field, or one that could
89
+ * not reach a verdict, sends nothing, and that is UNKNOWN, never `runs`.
90
+ */
91
+ export declare function reflectionAutonomy(trigger: TriggerDefinition): {
92
+ autonomy: 'runs' | 'suggests_only';
93
+ note?: string;
94
+ } | null;
95
+ /**
96
+ * The one plain sentence a text view prints under the loop when its fires will not run for the
97
+ * owner; null when they do, when the server did not say, or when the loop is paused (a paused
98
+ * loop fires nothing, so the gate is not what the reader needs to hear about).
99
+ */
100
+ export declare function reflectionAutonomyNote(trigger: TriggerDefinition, bin: string): string | null;
101
+ /** The `--json` fields for the same verdict: absent when unknown, so absence reads as unknown. */
102
+ export declare function reflectionAutonomyFields(trigger: TriggerDefinition): {
103
+ autonomy?: 'runs' | 'suggests_only';
104
+ note?: string;
105
+ };
48
106
  /**
49
107
  * Build the `POST /api/triggers` body for a fresh self-reflection loop. Uses the
50
108
  * proven `scope:agent + kind:schedule + mode:run_only` shape and coalesces so a