@skrr-ai/cli 0.1.127 → 0.1.129

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/dist/base-command.d.ts +35 -0
  2. package/dist/base-command.js +107 -0
  3. package/dist/commands/agenda/trigger/list.d.ts +2 -1
  4. package/dist/commands/agenda/trigger/list.js +55 -6
  5. package/dist/commands/agents/chat.js +7 -0
  6. package/dist/commands/agents/create.js +3 -1
  7. package/dist/commands/browser/persistent-profile.d.ts +22 -0
  8. package/dist/commands/browser/persistent-profile.js +46 -0
  9. package/dist/commands/commitments/action-history.js +1 -1
  10. package/dist/commands/commitments/cases.js +3 -3
  11. package/dist/commands/commitments/check-now.d.ts +10 -2
  12. package/dist/commands/commitments/check-now.js +53 -3
  13. package/dist/commands/commitments/create.d.ts +6 -14
  14. package/dist/commands/commitments/create.js +64 -7
  15. package/dist/commands/commitments/doctor.js +3 -1
  16. package/dist/commands/commitments/effective-policy.d.ts +11 -1
  17. package/dist/commands/commitments/effective-policy.js +90 -4
  18. package/dist/commands/commitments/explain.js +10 -1
  19. package/dist/commands/commitments/hypotheses.js +12 -12
  20. package/dist/commands/commitments/next-tick.js +5 -0
  21. package/dist/commands/commitments/observations.js +18 -17
  22. package/dist/commands/commitments/pack/list.js +2 -1
  23. package/dist/commands/commitments/pack/plan.d.ts +6 -0
  24. package/dist/commands/commitments/pack/plan.js +19 -0
  25. package/dist/commands/commitments/plan.d.ts +14 -0
  26. package/dist/commands/commitments/plan.js +32 -0
  27. package/dist/commands/commitments/preflight.d.ts +20 -1
  28. package/dist/commands/commitments/preflight.js +118 -12
  29. package/dist/commands/commitments/show.d.ts +9 -0
  30. package/dist/commands/commitments/show.js +14 -1
  31. package/dist/commands/commitments/trigger-status.js +8 -0
  32. package/dist/commands/commitments/update.d.ts +1 -0
  33. package/dist/commands/commitments/update.js +45 -4
  34. package/dist/commands/commitments/wake-capabilities.js +13 -0
  35. package/dist/commands/compass/archive.js +4 -0
  36. package/dist/commands/compass/commitments.js +15 -1
  37. package/dist/commands/compass/create.js +11 -3
  38. package/dist/commands/compass/effective-policy.d.ts +1 -0
  39. package/dist/commands/compass/effective-policy.js +8 -7
  40. package/dist/commands/compass/revert.d.ts +8 -0
  41. package/dist/commands/compass/revert.js +25 -1
  42. package/dist/commands/compass/revisions.d.ts +6 -0
  43. package/dist/commands/compass/revisions.js +26 -2
  44. package/dist/commands/compass/tactic.d.ts +10 -3
  45. package/dist/commands/compass/tactic.js +22 -7
  46. package/dist/commands/compass/update.js +19 -3
  47. package/dist/commands/followups/act.js +1 -1
  48. package/dist/commands/followups/cancel.js +13 -0
  49. package/dist/commands/followups/observability.js +20 -1
  50. package/dist/commands/followups/plans/create.js +12 -1
  51. package/dist/commands/followups/show.js +13 -0
  52. package/dist/commands/followups/watch.js +1 -1
  53. package/dist/commands/goals/planner-config.d.ts +11 -0
  54. package/dist/commands/goals/planner-config.js +16 -0
  55. package/dist/commands/harnesses/list.js +1 -1
  56. package/dist/commands/machines/update.d.ts +5 -3
  57. package/dist/commands/machines/update.js +70 -9
  58. package/dist/commands/northstars/create.js +3 -1
  59. package/dist/commands/northstars/governance/configure.d.ts +4 -0
  60. package/dist/commands/northstars/governance/configure.js +76 -10
  61. package/dist/commands/northstars/intents/decide.d.ts +6 -0
  62. package/dist/commands/northstars/intents/decide.js +68 -15
  63. package/dist/commands/northstars/intents/list.d.ts +2 -1
  64. package/dist/commands/northstars/intents/list.js +15 -5
  65. package/dist/commands/northstars/intents.d.ts +26 -0
  66. package/dist/commands/northstars/intents.js +49 -0
  67. package/dist/commands/northstars/list.d.ts +1 -0
  68. package/dist/commands/northstars/list.js +24 -3
  69. package/dist/commands/northstars/recommendations/list.d.ts +1 -1
  70. package/dist/commands/northstars/recommendations/list.js +3 -3
  71. package/dist/commands/northstars/recommendations.d.ts +19 -0
  72. package/dist/commands/northstars/recommendations.js +42 -0
  73. package/dist/commands/northstars/review-now.d.ts +1 -0
  74. package/dist/commands/northstars/review-now.js +15 -4
  75. package/dist/commands/northstars/reviews.d.ts +1 -0
  76. package/dist/commands/northstars/reviews.js +34 -6
  77. package/dist/commands/northstars/update.js +8 -1
  78. package/dist/commands/proposals/list.js +11 -2
  79. package/dist/commands/proposals/show.js +9 -1
  80. package/dist/commands/spaces/auto-executor/preview.d.ts +14 -1
  81. package/dist/commands/spaces/auto-executor/preview.js +35 -1
  82. package/dist/commands/spaces/auto-executor/run.d.ts +17 -0
  83. package/dist/commands/spaces/auto-executor/run.js +60 -2
  84. package/dist/commands/spaces/auto-executor/runs.d.ts +9 -1
  85. package/dist/commands/spaces/auto-executor/runs.js +18 -1
  86. package/dist/commands/spaces/files/upload.js +1 -2
  87. package/dist/commands/spaces/heartbeats/create.js +7 -5
  88. package/dist/commands/spaces/heartbeats/update.js +5 -3
  89. package/dist/commands/spaces/index.js +23 -0
  90. package/dist/commands/store/artifacts/releases.d.ts +20 -0
  91. package/dist/commands/store/artifacts/releases.js +85 -0
  92. package/dist/commands/store/library.d.ts +14 -0
  93. package/dist/commands/store/library.js +71 -0
  94. package/dist/commands/tasks/list.d.ts +48 -0
  95. package/dist/commands/tasks/list.js +141 -9
  96. package/dist/commands/tasks/remind.js +23 -11
  97. package/dist/commands/tasks/retry-schedule.js +6 -3
  98. package/dist/commands/tasks/schedule.js +29 -7
  99. package/dist/commands/tasks/show.js +4 -0
  100. package/dist/generated/agent-guides.json +69 -69
  101. package/dist/lib/agent-trigger-listing.d.ts +26 -0
  102. package/dist/lib/agent-trigger-listing.js +63 -0
  103. package/dist/lib/agentic-stream.d.ts +9 -0
  104. package/dist/lib/agentic-stream.js +152 -41
  105. package/dist/lib/command-miss.js +11 -2
  106. package/dist/lib/commitment-attention-copy.d.ts +96 -0
  107. package/dist/lib/commitment-attention-copy.js +209 -0
  108. package/dist/lib/commitment-ledgers.js +3 -1
  109. package/dist/lib/commitment-loop-status.d.ts +6 -0
  110. package/dist/lib/commitment-loop-status.js +16 -6
  111. package/dist/lib/commitments.d.ts +112 -0
  112. package/dist/lib/commitments.js +250 -2
  113. package/dist/lib/compass-body-help.d.ts +20 -0
  114. package/dist/lib/compass-body-help.js +61 -0
  115. package/dist/lib/compass-refusals.d.ts +35 -0
  116. package/dist/lib/compass-refusals.js +50 -0
  117. package/dist/lib/compasses.d.ts +82 -4
  118. package/dist/lib/compasses.js +232 -12
  119. package/dist/lib/followups.d.ts +22 -1
  120. package/dist/lib/followups.js +21 -1
  121. package/dist/lib/format.d.ts +11 -0
  122. package/dist/lib/format.js +29 -0
  123. package/dist/lib/initiatives.d.ts +9 -2
  124. package/dist/lib/initiatives.js +13 -2
  125. package/dist/lib/iso-instant.d.ts +52 -0
  126. package/dist/lib/iso-instant.js +162 -0
  127. package/dist/lib/machines.d.ts +14 -1
  128. package/dist/lib/machines.js +24 -0
  129. package/dist/lib/northstars.d.ts +201 -24
  130. package/dist/lib/northstars.js +655 -27
  131. package/dist/lib/proposal-decision-command.js +21 -1
  132. package/dist/lib/proposals.d.ts +26 -1
  133. package/dist/lib/proposals.js +167 -0
  134. package/dist/lib/rate-limit-retry.d.ts +5 -0
  135. package/dist/lib/rate-limit-retry.js +19 -0
  136. package/dist/lib/roles.d.ts +1 -1
  137. package/dist/lib/space-charter.js +9 -0
  138. package/dist/lib/store-artifact-releases.d.ts +39 -0
  139. package/dist/lib/store-artifact-releases.js +12 -0
  140. package/dist/lib/store-library.d.ts +37 -0
  141. package/dist/lib/store-library.js +12 -0
  142. package/dist/lib/task-asset-upload.js +1 -2
  143. package/dist/lib/task-calendar.d.ts +19 -0
  144. package/dist/lib/task-calendar.js +72 -0
  145. package/dist/lib/task-recurrence-outcome.js +3 -1
  146. package/dist/lib/task-recurrence-summary.d.ts +15 -0
  147. package/dist/lib/task-recurrence-summary.js +228 -0
  148. package/dist/lib/task-reminder.d.ts +28 -0
  149. package/dist/lib/task-reminder.js +50 -0
  150. package/dist/lib/triggers.d.ts +13 -2
  151. package/dist/lib/triggers.js +31 -13
  152. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +11 -1
  153. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.d.ts +123 -0
  154. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.js +160 -0
  155. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -0
  156. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +17 -0
  157. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +11 -1
  158. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.d.ts +123 -0
  159. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.js +173 -0
  160. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -0
  161. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -0
  162. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  163. package/dist/node_modules/@skrr-ai/data-provider/index.js +6922 -6828
  164. package/oclif.manifest.json +612 -62
  165. package/package.json +4 -4
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /**
3
+ * `skrr agenda trigger list <agent>` and the triggers it did not show (OSK-21454).
4
+ *
5
+ * `/api/agents/:id/triggers` is the legacy per-agent facade and lists `scope: 'agent'` rows only. A
6
+ * passive agent (`agents create --initial-always-on off`) still owns system-managed Commitment
7
+ * triggers - the daily self-reflection check is `commitment-check:obj_agent_reflection_*`, scope
8
+ * `commitment_watcher` - so the one command whose name says "list this agent's triggers" answered
9
+ * "No triggers." while `triggers list --ref-entity agent:<id>` and the web Upcoming page listed it.
10
+ *
11
+ * The universal list, filtered by the agent ref, is the truth; this reads it and returns the rows the
12
+ * facade left out, marked so a person can tell the platform's from their own.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.isSystemManaged = isSystemManaged;
16
+ exports.triggersMissingFromAgentList = triggersMissingFromAgentList;
17
+ exports.isSuspended = isSuspended;
18
+ exports.sourceLabel = sourceLabel;
19
+ exports.systemManagedLegend = systemManagedLegend;
20
+ function idOf(row) {
21
+ return typeof row.id === 'string' ? row.id : '';
22
+ }
23
+ function scopeKindOf(row) {
24
+ const scope = row.scope;
25
+ return typeof scope?.kind === 'string' ? scope.kind : '';
26
+ }
27
+ /** Created and run by the platform for a Commitment, not authored by the person. */
28
+ function isSystemManaged(row) {
29
+ return row.producerOwned === true || /^commitment-(check|reflect):/.test(idOf(row));
30
+ }
31
+ /**
32
+ * The universal-list rows the per-agent facade did not return, each tagged `systemManaged`.
33
+ * Rows already listed are not repeated; a row with no id cannot be matched and is kept.
34
+ */
35
+ function triggersMissingFromAgentList(listed, universal) {
36
+ const known = new Set(listed.map(idOf).filter(Boolean));
37
+ return universal
38
+ .filter((row) => !idOf(row) || !known.has(idOf(row)))
39
+ .map((row) => ({ ...row, systemManaged: isSystemManaged(row) }));
40
+ }
41
+ /** The platform marked this managed trigger suspended (Always-On is off for the agent). */
42
+ function isSuspended(row) {
43
+ const management = row.management;
44
+ return management?.suspended === true;
45
+ }
46
+ /** What the SOURCE column says: the platform, the scope it belongs to, a turn capture, or nothing. */
47
+ function sourceLabel(row, unconfirmedTurnCapture) {
48
+ if (row.systemManaged === true)
49
+ return isSuspended(row) ? 'system, suspended' : 'system';
50
+ if (unconfirmedTurnCapture)
51
+ return 'turn*';
52
+ if (row.systemManaged === false)
53
+ return scopeKindOf(row) || '-';
54
+ return '-';
55
+ }
56
+ /** The legend printed under a table that includes platform-managed rows. */
57
+ function systemManagedLegend(agentId, bin = 'skrr') {
58
+ return [
59
+ 'system = created and run by the platform for a Commitment, not by you (for example the daily self-reflection check every agent is seeded with, passive or not).',
60
+ ' "suspended" = the platform is holding it because Always-On is off for this agent.',
61
+ ` Find the Commitment behind one with: ${bin} commitments list --agent ${agentId} --include-system`,
62
+ ];
63
+ }
@@ -47,6 +47,14 @@ export type AgenticStreamResult = {
47
47
  * (OSK-13543). Absent on every ordinary run.
48
48
  */
49
49
  repaired?: true;
50
+ /**
51
+ * Tool calls refused because nobody could be asked and the run was not started with
52
+ * `--approve-tools`, as the same one-line description the streamed `[permission]` line uses.
53
+ * A quiet run discards the rendering, so without this a `--no-stream --json` turn read as a
54
+ * completed turn that did nothing, beside an agent telling the person to resend from another
55
+ * surface (which a CLI cannot offer). Absent when nothing was refused.
56
+ */
57
+ permissionDenials?: string[];
50
58
  lastEvent?: AgenticStreamEvent;
51
59
  };
52
60
  /**
@@ -605,6 +613,7 @@ export declare class AgenticStreamClient {
605
613
  private readonly openQuestionPrompts;
606
614
  /** Text already written to the terminal, for suffix-only rendering. */
607
615
  private renderedText;
616
+ private permissionDenials;
608
617
  /** The "still waiting" line for a terminal run with no output yet (OSK-20427). */
609
618
  private waitingTimer;
610
619
  private waitingStartedAt;
@@ -206,7 +206,10 @@ async function discardTypeAhead(input, windowMs = exports.TYPE_AHEAD_DISCARD_MS)
206
206
  process.kill(process.pid, 'SIGINT');
207
207
  }
208
208
  const permissionGeneration = (event) => JSON.stringify([
209
- event.sessionId, event.toolUseId, event.permissionBinding?.epoch, event.permissionBinding?.requestId,
209
+ event.sessionId,
210
+ event.toolUseId,
211
+ event.permissionBinding?.epoch,
212
+ event.permissionBinding?.requestId,
210
213
  ]);
211
214
  const PERMISSION_DETAIL_MAX = 400;
212
215
  /**
@@ -855,6 +858,7 @@ class AgenticStreamClient {
855
858
  openQuestionPrompts = new Map();
856
859
  /** Text already written to the terminal, for suffix-only rendering. */
857
860
  renderedText = '';
861
+ permissionDenials = [];
858
862
  /** The "still waiting" line for a terminal run with no output yet (OSK-20427). */
859
863
  waitingTimer = null;
860
864
  waitingStartedAt = 0;
@@ -1522,7 +1526,8 @@ class AgenticStreamClient {
1522
1526
  return; // a pure re-delivery — drop it whole
1523
1527
  const scoped = { ...event, envelopes: fresh };
1524
1528
  if (fresh.some((env) => env?.ev?.t === 'text' &&
1525
- env.ev?.publicProjection === 'lookback_answer_v1'))
1529
+ env.ev?.publicProjection ===
1530
+ 'lookback_answer_v1'))
1526
1531
  this.publicProjection = 'lookback_answer_v1';
1527
1532
  // Note what never arrived before folding this batch in (OSK-7631,
1528
1533
  // OSK-13518). Checked against the FRESH set: a re-delivery carries old
@@ -1686,7 +1691,8 @@ class AgenticStreamClient {
1686
1691
  socket.on('execution:aborted', (event) => {
1687
1692
  if (!match(event))
1688
1693
  return;
1689
- if (typeof event.permissionEpoch === 'string' && this.permissionEpochs.has(this.options.sessionId) &&
1694
+ if (typeof event.permissionEpoch === 'string' &&
1695
+ this.permissionEpochs.has(this.options.sessionId) &&
1690
1696
  this.permissionEpochs.get(this.options.sessionId) !== event.permissionEpoch)
1691
1697
  return;
1692
1698
  if (this.handover)
@@ -1697,7 +1703,8 @@ class AgenticStreamClient {
1697
1703
  socket.on('execution:cancelled', (event) => {
1698
1704
  if (!match(event))
1699
1705
  return;
1700
- if (typeof event.permissionEpoch === 'string' && this.permissionEpochs.has(this.options.sessionId) &&
1706
+ if (typeof event.permissionEpoch === 'string' &&
1707
+ this.permissionEpochs.has(this.options.sessionId) &&
1701
1708
  this.permissionEpochs.get(this.options.sessionId) !== event.permissionEpoch)
1702
1709
  return;
1703
1710
  if (this.handover)
@@ -1730,7 +1737,10 @@ class AgenticStreamClient {
1730
1737
  return;
1731
1738
  if (event.toolUseId) {
1732
1739
  this.openPermissionPrompts.get(event.toolUseId)?.abort();
1733
- this.permissionGenerations.set(event.toolUseId, { key: permissionGeneration(event), bound: !!event.permissionBinding });
1740
+ this.permissionGenerations.set(event.toolUseId, {
1741
+ key: permissionGeneration(event),
1742
+ bound: !!event.permissionBinding,
1743
+ });
1734
1744
  if (event.permissionBinding)
1735
1745
  this.permissionEpochs.set(this.options.sessionId, event.permissionBinding.epoch);
1736
1746
  }
@@ -1764,21 +1774,27 @@ class AgenticStreamClient {
1764
1774
  // Runtime-authored application consent is exclusively an interactive app
1765
1775
  // decision. A TTY or --approve-tools is not that authenticated human lane.
1766
1776
  // Recognize malformed runtime asks too, so they never fall into generic grants.
1767
- if (event.purpose === 'runtime-elicitation' || event.runtimeElicitation !== undefined || event.toolName === 'ComputerUse') {
1777
+ if (event.purpose === 'runtime-elicitation' ||
1778
+ event.runtimeElicitation !== undefined ||
1779
+ event.toolName === 'ComputerUse') {
1768
1780
  let message = 'This Computer Use request cannot be verified. Wait for a new request in the app.';
1769
1781
  let requestValid = false;
1770
1782
  try {
1771
1783
  const runtime = event.runtimeElicitation;
1772
1784
  (0, computer_use_1.assertComputerUseElicitationRequest)(runtime);
1773
- if (event.purpose !== 'runtime-elicitation' || event.toolName !== 'ComputerUse' ||
1774
- event.toolUseId !== runtime.requestId || runtime.context.sessionId !== this.options.sessionId ||
1785
+ if (event.purpose !== 'runtime-elicitation' ||
1786
+ event.toolName !== 'ComputerUse' ||
1787
+ event.toolUseId !== runtime.requestId ||
1788
+ runtime.context.sessionId !== this.options.sessionId ||
1775
1789
  runtime.expiresAt <= Date.now())
1776
1790
  throw new Error('Mismatched runtime permission');
1777
1791
  const display = (text) => describePermissionTarget({ command: text }) ?? '';
1778
- message = [display(runtime.message),
1792
+ message = [
1793
+ display(runtime.message),
1779
1794
  `Choices in the app: ${runtime.options.map((option) => display(option.label)).join(' | ')}`,
1780
1795
  `Expires: ${new Date(runtime.expiresAt).toISOString()}`,
1781
- 'Answer in the signed-in app. This CLI cannot grant or decline application access.'].join('\n');
1796
+ 'Answer in the signed-in app. This CLI cannot grant or decline application access.',
1797
+ ].join('\n');
1782
1798
  requestValid = true;
1783
1799
  }
1784
1800
  catch {
@@ -1790,10 +1806,17 @@ class AgenticStreamClient {
1790
1806
  if (approvalUrl)
1791
1807
  message += `\nOpen ${approvalUrl} and choose Review pending decisions.`;
1792
1808
  else
1793
- message += '\nOpen this conversation in the signed-in app and choose Review pending decisions.';
1809
+ message +=
1810
+ '\nOpen this conversation in the signed-in app and choose Review pending decisions.';
1794
1811
  if (event.toolUseId)
1795
1812
  this.browserPermissionRequests.add(event.toolUseId);
1796
- this.emit({ ...event, type: 'permission.browser_required', requestValid, approvalUrl, message });
1813
+ this.emit({
1814
+ ...event,
1815
+ type: 'permission.browser_required',
1816
+ requestValid,
1817
+ approvalUrl,
1818
+ message,
1819
+ });
1797
1820
  return;
1798
1821
  }
1799
1822
  if (!this.options.approveTools || !this.socket) {
@@ -1802,7 +1825,10 @@ class AgenticStreamClient {
1802
1825
  // response was admitted. Tool execution is not confirmed.", which read like an approval.
1803
1826
  this.emit({ ...event, type: 'permission.denied', reason: 'noninteractive_or_not_enabled' });
1804
1827
  if (event.toolUseId) {
1805
- if (await this.sendBoundPermissionDecision(event, false, { reason: 'CLI was not started with --approve-tools', automatic: true }))
1828
+ if (await this.sendBoundPermissionDecision(event, false, {
1829
+ reason: 'CLI was not started with --approve-tools',
1830
+ automatic: true,
1831
+ }))
1806
1832
  return;
1807
1833
  this.socket?.emit('permission:deny', {
1808
1834
  sessionId: this.options.sessionId,
@@ -1876,14 +1902,22 @@ class AgenticStreamClient {
1876
1902
  this.emit({ ...event, type: approved ? 'permission.approved' : 'permission.denied' });
1877
1903
  }
1878
1904
  replayPermissionDecision(event) {
1879
- if (!event.permissionBinding || event.decisionStatus === undefined || event.purpose === 'task-command-once' ||
1880
- event.purpose === 'runtime-elicitation' || event.runtimeElicitation !== undefined || event.toolName === 'ComputerUse')
1905
+ if (!event.permissionBinding ||
1906
+ event.decisionStatus === undefined ||
1907
+ event.purpose === 'task-command-once' ||
1908
+ event.purpose === 'runtime-elicitation' ||
1909
+ event.runtimeElicitation !== undefined ||
1910
+ event.toolName === 'ComputerUse')
1881
1911
  return false;
1882
1912
  const generation = permissionGeneration(event);
1883
1913
  if (event.toolUseId) {
1884
1914
  this.openPermissionPrompts.get(event.toolUseId)?.abort();
1885
1915
  this.openPermissionPrompts.delete(event.toolUseId);
1886
- this.permissionGenerations.set(event.toolUseId, { key: generation, bound: true, readonly: true });
1916
+ this.permissionGenerations.set(event.toolUseId, {
1917
+ key: generation,
1918
+ bound: true,
1919
+ readonly: true,
1920
+ });
1887
1921
  this.permissionEpochs.set(this.options.sessionId, event.permissionBinding.epoch);
1888
1922
  }
1889
1923
  this.firstTimeSeen('permission', generation);
@@ -1891,11 +1925,18 @@ class AgenticStreamClient {
1891
1925
  const status = event.decisionStatus === 'recorded' ? 'recorded' : 'delivery_unconfirmed';
1892
1926
  if (this.firstTimeSeen('permission-receipt', `${generation}:${valid ? status : 'unknown'}`))
1893
1927
  this.emit({
1894
- ...event, type: status === 'recorded' ? 'permission.response_recorded' : 'permission.delivery_unconfirmed',
1895
- status, previouslyRecorded: valid, executionConfirmed: false, readonly: true,
1928
+ ...event,
1929
+ type: status === 'recorded'
1930
+ ? 'permission.response_recorded'
1931
+ : 'permission.delivery_unconfirmed',
1932
+ status,
1933
+ previouslyRecorded: valid,
1934
+ executionConfirmed: false,
1935
+ readonly: true,
1896
1936
  message: status === 'recorded'
1897
1937
  ? 'Response recorded. Waiting for execution to admit this response; no tool execution or continuing permission grant is confirmed.'
1898
- : valid ? 'Response recorded; delivery unconfirmed. Check execution or Stop before starting a new turn.'
1938
+ : valid
1939
+ ? 'Response recorded; delivery unconfirmed. Check execution or Stop before starting a new turn.'
1899
1940
  : 'Your response could not be confirmed. Check execution or Stop before starting a new turn.',
1900
1941
  });
1901
1942
  return true;
@@ -1905,7 +1946,9 @@ class AgenticStreamClient {
1905
1946
  if (!event.permissionBinding)
1906
1947
  return false;
1907
1948
  const generation = permissionGeneration(event);
1908
- const current = () => !this.closed && !!event.toolUseId && this.permissionGenerations.get(event.toolUseId)?.key === generation &&
1949
+ const current = () => !this.closed &&
1950
+ !!event.toolUseId &&
1951
+ this.permissionGenerations.get(event.toolUseId)?.key === generation &&
1909
1952
  !this.permissionGenerations.get(event.toolUseId)?.readonly;
1910
1953
  if (!current())
1911
1954
  return true;
@@ -1922,27 +1965,54 @@ class AgenticStreamClient {
1922
1965
  const timer = setTimeout(() => finish({ type: 'permission.delivery_unconfirmed', message: unknown }), 10_000);
1923
1966
  try {
1924
1967
  if (!this.socket?.connected) {
1925
- finish({ type: 'permission.response_refused', message: 'The connection is offline. No response was sent.' });
1968
+ finish({
1969
+ type: 'permission.response_refused',
1970
+ message: 'The connection is offline. No response was sent.',
1971
+ });
1926
1972
  return;
1927
1973
  }
1928
1974
  this.socket.emit(approved ? 'permission:approve' : 'permission:deny', {
1929
- sessionId: this.options.sessionId, toolUseId: event.toolUseId, toolName: event.toolName,
1930
- permissionBinding: event.permissionBinding, ...extra,
1975
+ sessionId: this.options.sessionId,
1976
+ toolUseId: event.toolUseId,
1977
+ toolName: event.toolName,
1978
+ permissionBinding: event.permissionBinding,
1979
+ ...extra,
1931
1980
  }, (receipt) => {
1932
1981
  const result = receipt?.result;
1933
- if (receipt?.ok === false && ['invalid', 'unauthorized'].includes(receipt.error?.code || ''))
1934
- finish({ type: 'permission.response_refused', message: receipt.error?.message || 'The response was refused. No new permission was granted.' });
1982
+ if (receipt?.ok === false &&
1983
+ ['invalid', 'unauthorized'].includes(receipt.error?.code || ''))
1984
+ finish({
1985
+ type: 'permission.response_refused',
1986
+ message: receipt.error?.message ||
1987
+ 'The response was refused. No new permission was granted.',
1988
+ });
1935
1989
  else if (receipt?.ok === false)
1936
1990
  finish({ type: 'permission.delivery_unconfirmed', message: unknown });
1937
1991
  else if (!result || result.toolUseId !== event.toolUseId)
1938
1992
  finish({ type: 'permission.delivery_unconfirmed', message: unknown });
1939
1993
  else if (result.status === 'closed')
1940
- finish({ type: 'permission.response_refused', status: 'closed', message: 'This approval request is closed. No new permission was granted.' });
1994
+ finish({
1995
+ type: 'permission.response_refused',
1996
+ status: 'closed',
1997
+ message: 'This approval request is closed. No new permission was granted.',
1998
+ });
1941
1999
  else if (result.status === 'delivery_unconfirmed')
1942
- finish({ type: 'permission.delivery_unconfirmed', status: result.status, previouslyRecorded: result.previouslyRecorded === true,
1943
- message: result.previouslyRecorded === true ? 'Response recorded; delivery unconfirmed. Check execution or Stop before starting a new turn.' : unknown });
1944
- else if (receipt.ok === true && result.accepted === true && ['recorded', 'admitted'].includes(result.status || ''))
1945
- finish({ type: 'permission.response_recorded', status: result.status, message: 'Response recorded. This does not confirm tool execution or a continuing permission grant.' });
2000
+ finish({
2001
+ type: 'permission.delivery_unconfirmed',
2002
+ status: result.status,
2003
+ previouslyRecorded: result.previouslyRecorded === true,
2004
+ message: result.previouslyRecorded === true
2005
+ ? 'Response recorded; delivery unconfirmed. Check execution or Stop before starting a new turn.'
2006
+ : unknown,
2007
+ });
2008
+ else if (receipt.ok === true &&
2009
+ result.accepted === true &&
2010
+ ['recorded', 'admitted'].includes(result.status || ''))
2011
+ finish({
2012
+ type: 'permission.response_recorded',
2013
+ status: result.status,
2014
+ message: 'Response recorded. This does not confirm tool execution or a continuing permission grant.',
2015
+ });
1946
2016
  else
1947
2017
  finish({ type: 'permission.delivery_unconfirmed', message: unknown });
1948
2018
  });
@@ -1952,7 +2022,12 @@ class AgenticStreamClient {
1952
2022
  }
1953
2023
  });
1954
2024
  if (current())
1955
- this.emit({ ...event, ...result, decision: approved ? 'allow' : 'deny', executionConfirmed: false });
2025
+ this.emit({
2026
+ ...event,
2027
+ ...result,
2028
+ decision: approved ? 'allow' : 'deny',
2029
+ executionConfirmed: false,
2030
+ });
1956
2031
  return true;
1957
2032
  }
1958
2033
  async handleQuestion(event) {
@@ -2020,7 +2095,8 @@ class AgenticStreamClient {
2020
2095
  handlePermissionCancel(event) {
2021
2096
  const toolUseId = typeof event.toolUseId === 'string' ? event.toolUseId : undefined;
2022
2097
  const generation = toolUseId ? this.permissionGenerations.get(toolUseId) : undefined;
2023
- if (generation && (generation.bound || event.permissionBinding) &&
2098
+ if (generation &&
2099
+ (generation.bound || event.permissionBinding) &&
2024
2100
  (!event.permissionBinding || generation.key !== permissionGeneration(event)))
2025
2101
  return;
2026
2102
  const open = toolUseId ? this.openPermissionPrompts.get(toolUseId) : undefined;
@@ -2058,8 +2134,17 @@ class AgenticStreamClient {
2058
2134
  if (questionId)
2059
2135
  this.openQuestionPrompts.delete(questionId);
2060
2136
  open?.abort();
2137
+ // `answered` is the server telling every surface that SOMEONE answered. When
2138
+ // this client's own answer comes back as one there is nothing to report (it
2139
+ // already printed `question.answered`); when another surface answered while
2140
+ // this prompt was open, the prompt was just closed and that is worth saying.
2141
+ if (event.reason === 'answered' && !open)
2142
+ return;
2061
2143
  this.emit({
2062
2144
  ...event,
2145
+ ...(event.reason === 'answered' && !event.message
2146
+ ? { message: 'This question was answered in another window.' }
2147
+ : {}),
2063
2148
  type: 'question.cancelled',
2064
2149
  promptClosed: Boolean(open),
2065
2150
  });
@@ -2206,13 +2291,26 @@ class AgenticStreamClient {
2206
2291
  // result, so there the line goes to stderr (OSK-13543).
2207
2292
  if (!this.options.jsonl && this.renderHandoverStatus(event))
2208
2293
  return;
2209
- if (event.type === 'permission.browser_required' && (this.options.quiet || !this.options.jsonl)) {
2294
+ if (event.type === 'permission.browser_required' &&
2295
+ (this.options.quiet || !this.options.jsonl)) {
2210
2296
  const output = this.options.quiet
2211
- ? this.options.statusOutput ?? process.stderr
2212
- : this.options.output ?? process.stdout;
2297
+ ? (this.options.statusOutput ?? process.stderr)
2298
+ : (this.options.output ?? process.stdout);
2213
2299
  output.write(`\n[application permission] ${String(event.message)}\n`);
2214
2300
  return;
2215
2301
  }
2302
+ if (event.type === 'permission.denied' && event.reason === 'noninteractive_or_not_enabled') {
2303
+ const described = describePermission(event);
2304
+ // The stream asks and is refused twice for one call (observed: both appear, identical), and
2305
+ // the result should name a refused call once.
2306
+ if (!this.permissionDenials.includes(described))
2307
+ this.permissionDenials.push(described);
2308
+ // A quiet run withholds the answer's rendering, not a refusal: say it on stderr, as the
2309
+ // handover and browser-permission lines above do, so stdout stays the final result.
2310
+ if (this.options.quiet && !this.options.jsonl) {
2311
+ (this.options.statusOutput ?? process.stderr).write(`\n[permission] ${described} denied: nobody could be asked, and this run was not started with --approve-tools\n`);
2312
+ }
2313
+ }
2216
2314
  // Accumulation and `onEvent` still run — only the incremental rendering is
2217
2315
  // suppressed, so a quiet run is the same turn with a different display.
2218
2316
  if (this.options.quiet)
@@ -2279,7 +2377,11 @@ class AgenticStreamClient {
2279
2377
  output.write(`\n[${event.type}] ${describePermission(event)}\n`);
2280
2378
  this.renderedText = this.answerText();
2281
2379
  }
2282
- else if (['permission.response_recorded', 'permission.delivery_unconfirmed', 'permission.response_refused'].includes(event.type)) {
2380
+ else if ([
2381
+ 'permission.response_recorded',
2382
+ 'permission.delivery_unconfirmed',
2383
+ 'permission.response_refused',
2384
+ ].includes(event.type)) {
2283
2385
  output.write(`\n[permission] ${String(event.message ?? '')}\n`);
2284
2386
  this.renderedText = this.answerText();
2285
2387
  }
@@ -2304,7 +2406,8 @@ class AgenticStreamClient {
2304
2406
  output.write(`\n[question] ${why}\n`);
2305
2407
  this.renderedText = this.answerText();
2306
2408
  }
2307
- else if (event.type === 'permission.denied' && event.reason === 'noninteractive_or_not_enabled') {
2409
+ else if (event.type === 'permission.denied' &&
2410
+ event.reason === 'noninteractive_or_not_enabled') {
2308
2411
  // Said out loud, because nobody was asked and the agent will not say it plainly: this run
2309
2412
  // had no terminal to ask at and was not given --approve-tools, so the call was refused.
2310
2413
  output.write(`\n[permission] ${describePermission(event)} denied: nobody could be asked, and this run was not started with --approve-tools\n`);
@@ -2420,10 +2523,16 @@ class AgenticStreamClient {
2420
2523
  try {
2421
2524
  const terminal = await reconcile();
2422
2525
  if (terminal && typeof terminal.text === 'string' && terminal.text) {
2423
- return { text: terminal.text, ...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}) };
2526
+ return {
2527
+ text: terminal.text,
2528
+ ...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}),
2529
+ };
2424
2530
  }
2425
2531
  if (terminal)
2426
- empty = { text: '', ...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}) };
2532
+ empty = {
2533
+ text: '',
2534
+ ...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}),
2535
+ };
2427
2536
  }
2428
2537
  catch {
2429
2538
  // No evidence either way; try again, then settle on the stream.
@@ -2485,8 +2594,9 @@ class AgenticStreamClient {
2485
2594
  // other way round).
2486
2595
  // (Compared as the reader sees it: the record holds a widget as its fallback sentence,
2487
2596
  // which is shorter than the block the stream carried, and the raw length would never lose.)
2488
- if (recorded && (recorded.publicProjection === 'lookback_answer_v1' ||
2489
- recorded.text.length > (0, widgetTerminalText_1.widgetsAsText)(this.accumulated, { final: true }).length)) {
2597
+ if (recorded &&
2598
+ (recorded.publicProjection === 'lookback_answer_v1' ||
2599
+ recorded.text.length > (0, widgetTerminalText_1.widgetsAsText)(this.accumulated, { final: true }).length)) {
2490
2600
  this.accumulated = recorded.text;
2491
2601
  this.publicProjection = recorded.publicProjection;
2492
2602
  }
@@ -2522,6 +2632,7 @@ class AgenticStreamClient {
2522
2632
  text: this.accumulated,
2523
2633
  status,
2524
2634
  ...(status === 'completed' && event.repaired === true ? { repaired: true } : {}),
2635
+ ...(this.permissionDenials.length ? { permissionDenials: [...this.permissionDenials] } : {}),
2525
2636
  lastEvent: event,
2526
2637
  };
2527
2638
  this.resolve(this.terminalResult);
@@ -359,8 +359,17 @@ function nearestCommands(id, all, limit = 3) {
359
359
  // A third of the length keeps this to genuine near-misses; without a bound
360
360
  // every miss suggests three unrelated commands, which is noise wearing the
361
361
  // shape of help.
362
- .filter(({ d }) => d <= Math.max(2, Math.floor(id.length / 3)))
363
- .sort((x, y) => x.d - y.d)
362
+ // A command NESTED under what was typed (`northstars:intents` -> `northstars:intents:list`)
363
+ // is the correction the reader was reaching for, whatever its edit distance, so it is
364
+ // neither bound-filtered nor ranked by distance. Distance alone kept `intents:decide` out
365
+ // and put shorter siblings ahead of `intents:list`, so `northstars intents <id>` was
366
+ // answered with "Did you mean: northstars create, northstars delete, northstars intents
367
+ // list?" and the first guess a reader acts on was a destructive one.
368
+ .filter(({ d, candidate }) => candidate.startsWith(`${id}:`) || d <= Math.max(2, Math.floor(id.length / 3)))
369
+ .sort((x, y) => {
370
+ const nested = (c) => (c.startsWith(`${id}:`) ? 0 : 1);
371
+ return nested(x.candidate) - nested(y.candidate) || x.d - y.d;
372
+ })
364
373
  .slice(0, limit)
365
374
  .map(({ candidate }) => candidate));
366
375
  }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The attention settings of a Commitment, said in words.
3
+ *
4
+ * `show`, `effective-policy` and `preflight` all print these, from this one
5
+ * file, so the three cannot phrase a setting three ways. A value is described
6
+ * from the RAW stored setting and never through a coercion: `null` is "no
7
+ * limit" and `0` is "muted", and `Number(null)` is `0`, which is exactly how a
8
+ * cap that was never set would read as a mute (the server documents the same
9
+ * trap at `policySchema.maxUserNudgesPerDay` in api/server/routes/commitments.js).
10
+ */
11
+ /** What the owner can type for `--max-nudges`, as `--help` prints it. */
12
+ export declare const MAX_NUDGES_FLAG_HELP: string;
13
+ /**
14
+ * Parse `--max-nudges`: a whole number, or the word `unlimited` (stored as `null`).
15
+ * The server accepts 0 up to MAX_SAFE_INTEGER, so there is no 0..20 ceiling here.
16
+ */
17
+ export declare function parseMaxNudgesFlag(raw: string | undefined): number | null | undefined;
18
+ /** `null` and an unset cap are "no daily limit"; `0` is a mute; N is "at most N a day". */
19
+ export declare function describeNudgeCap(value: unknown): string;
20
+ /** The least time the platform leaves between two nudges to you. */
21
+ export declare function describeNudgeGap(ms: unknown): string;
22
+ /** The back-off for a person who is not answering. The server's default count is 2. */
23
+ export declare function describeNoReplyRule(noReply: {
24
+ enabled?: boolean;
25
+ afterNudges?: number;
26
+ } | null | undefined): string;
27
+ export type NudgeSettings = {
28
+ /** `null` = unlimited, `0` = muted. `undefined` = never set, which is unlimited. */
29
+ maxUserNudgesPerDay?: number | null;
30
+ minUserNudgeIntervalMs?: number | null;
31
+ noReply?: {
32
+ enabled?: boolean;
33
+ afterNudges?: number;
34
+ } | null;
35
+ };
36
+ /** Read the three settings out of a stored `policy`. */
37
+ export declare function nudgeSettingsFromPolicy(policy: Record<string, any>): NudgeSettings;
38
+ /**
39
+ * The lines that say how much a Commitment may interrupt its owner. A muted one
40
+ * prints the mute alone: a gap and a back-off on a Commitment that never speaks
41
+ * would only suggest it does. `withNoReply: false` leaves the back-off out when the
42
+ * stored rule could not be read, rather than claim it is off.
43
+ */
44
+ export declare function nudgeSettingLines(settings: NudgeSettings, { withNoReply }?: {
45
+ withNoReply?: boolean;
46
+ }): string[];
47
+ /** What a time of day is, in the words a person can type. */
48
+ export declare const CLOCK_TIME_HINT = "use HH:MM, 00:00-23:59, for example 22:00";
49
+ /**
50
+ * How to set quiet hours, for the `--from-json` help of `create` and `update`. There is no flag for
51
+ * them, so without this sentence a user had to know the `policy.quietHours` key from outside the CLI
52
+ * (OSK-21123).
53
+ */
54
+ export declare const QUIET_HOURS_HELP: string;
55
+ /**
56
+ * What `policy.executionBudget.allowedEventSources` means, for the same help. An empty list is the
57
+ * unrestricted case, which is the opposite of what `[]` suggests (OSK-21113).
58
+ */
59
+ export declare const EVENT_SOURCES_HELP: string;
60
+ /**
61
+ * The allowlist in words. An empty or missing list means every source is allowed
62
+ * (`sourceAllowed` in api/server/services/EventBus/commitmentWakeCapabilities.js and the check in
63
+ * WakeIntentResolver.js both treat it so); the stored form of "never set" is the same `[]`.
64
+ */
65
+ export declare function describeEventSources(allowed: unknown): string;
66
+ /**
67
+ * The problem with the quiet-hours times in a `policy` about to be sent, or null. The server
68
+ * answered a bad time with its raw pattern (`must match pattern /^\d{1,2}:\d{2}$/`), which says
69
+ * nothing a person can type, and accepted `25:00` (a window that then never applies). Checked
70
+ * here, so the request never leaves with either.
71
+ */
72
+ export declare function quietHoursInputProblem(policy: unknown): string | null;
73
+ type QuietHoursPolicy = {
74
+ enabled?: boolean;
75
+ start?: string;
76
+ end?: string;
77
+ timezone?: string;
78
+ } | null;
79
+ /**
80
+ * The Commitment's own quiet-hours window in words. Without one of its own a Commitment uses its
81
+ * Agent's Compass window (`scheduleIssues.js` documents the same rule), so "none set" says where
82
+ * else a window may come from rather than claiming there is no quiet time at all.
83
+ */
84
+ export declare function describeQuietHours(quietHours: QuietHoursPolicy | undefined): string;
85
+ /**
86
+ * When a held message is released, for the preflight line "Held until your quiet hours end".
87
+ * The report's `quietHours` carries the end as typed, the zone, and the exact instant (`endsAt`,
88
+ * daylight saving included); null when it carries no instant, so the caller keeps its generic
89
+ * sentence rather than inventing a time.
90
+ */
91
+ export declare function describeQuietHoursEnd(quietHours: {
92
+ end?: string;
93
+ endsAt?: string | null;
94
+ timezone?: string;
95
+ } | null | undefined): string | null;
96
+ export {};