@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
@@ -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
  /**
@@ -1523,7 +1526,8 @@ class AgenticStreamClient {
1523
1526
  return; // a pure re-delivery — drop it whole
1524
1527
  const scoped = { ...event, envelopes: fresh };
1525
1528
  if (fresh.some((env) => env?.ev?.t === 'text' &&
1526
- env.ev?.publicProjection === 'lookback_answer_v1'))
1529
+ env.ev?.publicProjection ===
1530
+ 'lookback_answer_v1'))
1527
1531
  this.publicProjection = 'lookback_answer_v1';
1528
1532
  // Note what never arrived before folding this batch in (OSK-7631,
1529
1533
  // OSK-13518). Checked against the FRESH set: a re-delivery carries old
@@ -1687,7 +1691,8 @@ class AgenticStreamClient {
1687
1691
  socket.on('execution:aborted', (event) => {
1688
1692
  if (!match(event))
1689
1693
  return;
1690
- if (typeof event.permissionEpoch === 'string' && this.permissionEpochs.has(this.options.sessionId) &&
1694
+ if (typeof event.permissionEpoch === 'string' &&
1695
+ this.permissionEpochs.has(this.options.sessionId) &&
1691
1696
  this.permissionEpochs.get(this.options.sessionId) !== event.permissionEpoch)
1692
1697
  return;
1693
1698
  if (this.handover)
@@ -1698,7 +1703,8 @@ class AgenticStreamClient {
1698
1703
  socket.on('execution:cancelled', (event) => {
1699
1704
  if (!match(event))
1700
1705
  return;
1701
- if (typeof event.permissionEpoch === 'string' && this.permissionEpochs.has(this.options.sessionId) &&
1706
+ if (typeof event.permissionEpoch === 'string' &&
1707
+ this.permissionEpochs.has(this.options.sessionId) &&
1702
1708
  this.permissionEpochs.get(this.options.sessionId) !== event.permissionEpoch)
1703
1709
  return;
1704
1710
  if (this.handover)
@@ -1731,7 +1737,10 @@ class AgenticStreamClient {
1731
1737
  return;
1732
1738
  if (event.toolUseId) {
1733
1739
  this.openPermissionPrompts.get(event.toolUseId)?.abort();
1734
- 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
+ });
1735
1744
  if (event.permissionBinding)
1736
1745
  this.permissionEpochs.set(this.options.sessionId, event.permissionBinding.epoch);
1737
1746
  }
@@ -1765,21 +1774,27 @@ class AgenticStreamClient {
1765
1774
  // Runtime-authored application consent is exclusively an interactive app
1766
1775
  // decision. A TTY or --approve-tools is not that authenticated human lane.
1767
1776
  // Recognize malformed runtime asks too, so they never fall into generic grants.
1768
- 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') {
1769
1780
  let message = 'This Computer Use request cannot be verified. Wait for a new request in the app.';
1770
1781
  let requestValid = false;
1771
1782
  try {
1772
1783
  const runtime = event.runtimeElicitation;
1773
1784
  (0, computer_use_1.assertComputerUseElicitationRequest)(runtime);
1774
- if (event.purpose !== 'runtime-elicitation' || event.toolName !== 'ComputerUse' ||
1775
- 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 ||
1776
1789
  runtime.expiresAt <= Date.now())
1777
1790
  throw new Error('Mismatched runtime permission');
1778
1791
  const display = (text) => describePermissionTarget({ command: text }) ?? '';
1779
- message = [display(runtime.message),
1792
+ message = [
1793
+ display(runtime.message),
1780
1794
  `Choices in the app: ${runtime.options.map((option) => display(option.label)).join(' | ')}`,
1781
1795
  `Expires: ${new Date(runtime.expiresAt).toISOString()}`,
1782
- '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');
1783
1798
  requestValid = true;
1784
1799
  }
1785
1800
  catch {
@@ -1791,10 +1806,17 @@ class AgenticStreamClient {
1791
1806
  if (approvalUrl)
1792
1807
  message += `\nOpen ${approvalUrl} and choose Review pending decisions.`;
1793
1808
  else
1794
- 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.';
1795
1811
  if (event.toolUseId)
1796
1812
  this.browserPermissionRequests.add(event.toolUseId);
1797
- 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
+ });
1798
1820
  return;
1799
1821
  }
1800
1822
  if (!this.options.approveTools || !this.socket) {
@@ -1803,7 +1825,10 @@ class AgenticStreamClient {
1803
1825
  // response was admitted. Tool execution is not confirmed.", which read like an approval.
1804
1826
  this.emit({ ...event, type: 'permission.denied', reason: 'noninteractive_or_not_enabled' });
1805
1827
  if (event.toolUseId) {
1806
- 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
+ }))
1807
1832
  return;
1808
1833
  this.socket?.emit('permission:deny', {
1809
1834
  sessionId: this.options.sessionId,
@@ -1877,14 +1902,22 @@ class AgenticStreamClient {
1877
1902
  this.emit({ ...event, type: approved ? 'permission.approved' : 'permission.denied' });
1878
1903
  }
1879
1904
  replayPermissionDecision(event) {
1880
- if (!event.permissionBinding || event.decisionStatus === undefined || event.purpose === 'task-command-once' ||
1881
- 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')
1882
1911
  return false;
1883
1912
  const generation = permissionGeneration(event);
1884
1913
  if (event.toolUseId) {
1885
1914
  this.openPermissionPrompts.get(event.toolUseId)?.abort();
1886
1915
  this.openPermissionPrompts.delete(event.toolUseId);
1887
- 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
+ });
1888
1921
  this.permissionEpochs.set(this.options.sessionId, event.permissionBinding.epoch);
1889
1922
  }
1890
1923
  this.firstTimeSeen('permission', generation);
@@ -1892,11 +1925,18 @@ class AgenticStreamClient {
1892
1925
  const status = event.decisionStatus === 'recorded' ? 'recorded' : 'delivery_unconfirmed';
1893
1926
  if (this.firstTimeSeen('permission-receipt', `${generation}:${valid ? status : 'unknown'}`))
1894
1927
  this.emit({
1895
- ...event, type: status === 'recorded' ? 'permission.response_recorded' : 'permission.delivery_unconfirmed',
1896
- 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,
1897
1936
  message: status === 'recorded'
1898
1937
  ? 'Response recorded. Waiting for execution to admit this response; no tool execution or continuing permission grant is confirmed.'
1899
- : 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.'
1900
1940
  : 'Your response could not be confirmed. Check execution or Stop before starting a new turn.',
1901
1941
  });
1902
1942
  return true;
@@ -1906,7 +1946,9 @@ class AgenticStreamClient {
1906
1946
  if (!event.permissionBinding)
1907
1947
  return false;
1908
1948
  const generation = permissionGeneration(event);
1909
- 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 &&
1910
1952
  !this.permissionGenerations.get(event.toolUseId)?.readonly;
1911
1953
  if (!current())
1912
1954
  return true;
@@ -1923,27 +1965,54 @@ class AgenticStreamClient {
1923
1965
  const timer = setTimeout(() => finish({ type: 'permission.delivery_unconfirmed', message: unknown }), 10_000);
1924
1966
  try {
1925
1967
  if (!this.socket?.connected) {
1926
- 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
+ });
1927
1972
  return;
1928
1973
  }
1929
1974
  this.socket.emit(approved ? 'permission:approve' : 'permission:deny', {
1930
- sessionId: this.options.sessionId, toolUseId: event.toolUseId, toolName: event.toolName,
1931
- permissionBinding: event.permissionBinding, ...extra,
1975
+ sessionId: this.options.sessionId,
1976
+ toolUseId: event.toolUseId,
1977
+ toolName: event.toolName,
1978
+ permissionBinding: event.permissionBinding,
1979
+ ...extra,
1932
1980
  }, (receipt) => {
1933
1981
  const result = receipt?.result;
1934
- if (receipt?.ok === false && ['invalid', 'unauthorized'].includes(receipt.error?.code || ''))
1935
- 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
+ });
1936
1989
  else if (receipt?.ok === false)
1937
1990
  finish({ type: 'permission.delivery_unconfirmed', message: unknown });
1938
1991
  else if (!result || result.toolUseId !== event.toolUseId)
1939
1992
  finish({ type: 'permission.delivery_unconfirmed', message: unknown });
1940
1993
  else if (result.status === 'closed')
1941
- 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
+ });
1942
1999
  else if (result.status === 'delivery_unconfirmed')
1943
- finish({ type: 'permission.delivery_unconfirmed', status: result.status, previouslyRecorded: result.previouslyRecorded === true,
1944
- message: result.previouslyRecorded === true ? 'Response recorded; delivery unconfirmed. Check execution or Stop before starting a new turn.' : unknown });
1945
- else if (receipt.ok === true && result.accepted === true && ['recorded', 'admitted'].includes(result.status || ''))
1946
- 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
+ });
1947
2016
  else
1948
2017
  finish({ type: 'permission.delivery_unconfirmed', message: unknown });
1949
2018
  });
@@ -1953,7 +2022,12 @@ class AgenticStreamClient {
1953
2022
  }
1954
2023
  });
1955
2024
  if (current())
1956
- 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
+ });
1957
2031
  return true;
1958
2032
  }
1959
2033
  async handleQuestion(event) {
@@ -2021,7 +2095,8 @@ class AgenticStreamClient {
2021
2095
  handlePermissionCancel(event) {
2022
2096
  const toolUseId = typeof event.toolUseId === 'string' ? event.toolUseId : undefined;
2023
2097
  const generation = toolUseId ? this.permissionGenerations.get(toolUseId) : undefined;
2024
- if (generation && (generation.bound || event.permissionBinding) &&
2098
+ if (generation &&
2099
+ (generation.bound || event.permissionBinding) &&
2025
2100
  (!event.permissionBinding || generation.key !== permissionGeneration(event)))
2026
2101
  return;
2027
2102
  const open = toolUseId ? this.openPermissionPrompts.get(toolUseId) : undefined;
@@ -2059,8 +2134,17 @@ class AgenticStreamClient {
2059
2134
  if (questionId)
2060
2135
  this.openQuestionPrompts.delete(questionId);
2061
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;
2062
2143
  this.emit({
2063
2144
  ...event,
2145
+ ...(event.reason === 'answered' && !event.message
2146
+ ? { message: 'This question was answered in another window.' }
2147
+ : {}),
2064
2148
  type: 'question.cancelled',
2065
2149
  promptClosed: Boolean(open),
2066
2150
  });
@@ -2207,10 +2291,11 @@ class AgenticStreamClient {
2207
2291
  // result, so there the line goes to stderr (OSK-13543).
2208
2292
  if (!this.options.jsonl && this.renderHandoverStatus(event))
2209
2293
  return;
2210
- 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)) {
2211
2296
  const output = this.options.quiet
2212
- ? this.options.statusOutput ?? process.stderr
2213
- : this.options.output ?? process.stdout;
2297
+ ? (this.options.statusOutput ?? process.stderr)
2298
+ : (this.options.output ?? process.stdout);
2214
2299
  output.write(`\n[application permission] ${String(event.message)}\n`);
2215
2300
  return;
2216
2301
  }
@@ -2292,7 +2377,11 @@ class AgenticStreamClient {
2292
2377
  output.write(`\n[${event.type}] ${describePermission(event)}\n`);
2293
2378
  this.renderedText = this.answerText();
2294
2379
  }
2295
- 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)) {
2296
2385
  output.write(`\n[permission] ${String(event.message ?? '')}\n`);
2297
2386
  this.renderedText = this.answerText();
2298
2387
  }
@@ -2317,7 +2406,8 @@ class AgenticStreamClient {
2317
2406
  output.write(`\n[question] ${why}\n`);
2318
2407
  this.renderedText = this.answerText();
2319
2408
  }
2320
- 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') {
2321
2411
  // Said out loud, because nobody was asked and the agent will not say it plainly: this run
2322
2412
  // had no terminal to ask at and was not given --approve-tools, so the call was refused.
2323
2413
  output.write(`\n[permission] ${describePermission(event)} denied: nobody could be asked, and this run was not started with --approve-tools\n`);
@@ -2433,10 +2523,16 @@ class AgenticStreamClient {
2433
2523
  try {
2434
2524
  const terminal = await reconcile();
2435
2525
  if (terminal && typeof terminal.text === 'string' && terminal.text) {
2436
- return { text: terminal.text, ...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}) };
2526
+ return {
2527
+ text: terminal.text,
2528
+ ...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}),
2529
+ };
2437
2530
  }
2438
2531
  if (terminal)
2439
- empty = { text: '', ...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}) };
2532
+ empty = {
2533
+ text: '',
2534
+ ...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}),
2535
+ };
2440
2536
  }
2441
2537
  catch {
2442
2538
  // No evidence either way; try again, then settle on the stream.
@@ -2498,8 +2594,9 @@ class AgenticStreamClient {
2498
2594
  // other way round).
2499
2595
  // (Compared as the reader sees it: the record holds a widget as its fallback sentence,
2500
2596
  // which is shorter than the block the stream carried, and the raw length would never lose.)
2501
- if (recorded && (recorded.publicProjection === 'lookback_answer_v1' ||
2502
- 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)) {
2503
2600
  this.accumulated = recorded.text;
2504
2601
  this.publicProjection = recorded.publicProjection;
2505
2602
  }
@@ -45,7 +45,9 @@ function renderActionHistory(page, log) {
45
45
  status: text(row.status),
46
46
  check: text(row.checkId),
47
47
  // A failed or refused action's reason is the point of reading history.
48
- why: [text(row.errorCode, ''), brief(row.errorMessage ?? row.reason)]
48
+ // A skipped action says why in `skippedReason` (a zero budget, a fenced Check), which this column
49
+ // never read, so every skipped row printed an empty WHY (OSK-21610).
50
+ why: [text(row.errorCode, ''), brief(row.errorMessage ?? row.skippedReason ?? row.reason)]
49
51
  .filter(Boolean)
50
52
  .join(' · '),
51
53
  })), [
@@ -125,6 +125,18 @@ export type CommitmentPreflightReport = {
125
125
  * for an allowed activation or an older server that does not send the block.
126
126
  */
127
127
  export declare function activationRefusalLines(activation: CommitmentPreflightReport['activation']): string[];
128
+ /**
129
+ * Whether the server's activation answer names this gate as one that refuses activation (OSK-21258).
130
+ * A gate can be advisory for a run (`admission: 'advisory'`) and still be what `resume` refuses with a
131
+ * 409; the gate list printed it `info (advisory: does not stop a run)` right under an "Activation:
132
+ * would be refused" section naming the same gate. Matches on the gate id, and on the reason too when
133
+ * both sides carry one, so a gate advisory for one reason is not promoted by another. An older server
134
+ * that sends no answer promotes nothing.
135
+ */
136
+ export declare function gateRefusesActivation(activation: CommitmentPreflightReport['activation'], gate: {
137
+ id?: string;
138
+ reason?: string | null;
139
+ }): boolean;
128
140
  /**
129
141
  * The lines every Commitment read surface prints for "whose permissions does a
130
142
  * run use?" — `show`, `effective-policy` and `preflight` share it so the three
@@ -1023,6 +1035,18 @@ export declare function describeCommitmentPriority(priority: CommitmentView['pri
1023
1035
  * stated: the id, and the command that says whether it is active (OSK-21242).
1024
1036
  */
1025
1037
  export declare function describeBoundCompass(commitment: Pick<CommitmentView, 'compassId' | 'agentId'>): string;
1038
+ /**
1039
+ * The bound Compass, as `explain` prints it (OSK-21332). `explain` is where a person asks "why does
1040
+ * this Commitment behave this way", and a bound Compass (or an archived one, which runs the
1041
+ * Commitment on defaults) is part of the answer. The explain payload names neither the Compass nor
1042
+ * its status, so the caller reads the Commitment itself, the same read `show` renders, and hands the
1043
+ * result here: the line is `describeBoundCompass`, the one `show` prints, not a second wording.
1044
+ *
1045
+ * `null` means that read did not come back. It is said, not skipped: a silent skip is
1046
+ * indistinguishable from "no Compass is bound". A Commitment bound to none prints nothing, as in
1047
+ * `show`.
1048
+ */
1049
+ export declare function explainCompassLines(commitmentId: string, commitment: Pick<CommitmentView, 'compassId' | 'agentId'> | null | undefined): string[];
1026
1050
  export declare function renderCommitmentSummary(commitment: CommitmentView, log: (line: string) => void,
1027
1051
  /**
1028
1052
  * The most recent check, when the caller has one. Optional because pause /
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.COMMITMENT_CLI_EVALUATORS = exports.COMMITMENT_CREATE_KEYS = exports.COMMITMENT_REMEDIATION_ACTIONS = exports.COMMITMENT_FEEDBACK_KINDS = exports.commitmentApi = exports.commitmentPackApi = exports.MAX_COMMITMENT_INTERVAL_MS = exports.MIN_COMMITMENT_INTERVAL_MS = exports.COMMITMENT_EXECUTION_ISOLATION_MODES = exports.COMMITMENT_AUTHORABLE_CADENCE_MODES = exports.COMMITMENT_KIND_VALUES = exports.COMMITMENT_STATUSES = exports.COMMITMENT_CADENCE_MAX_TIMES = exports.COMMITMENT_CADENCE_TIME_PATTERN = exports.COMMITMENT_CADENCE_MODES = exports.COMMITMENT_TEMPLATE_IDS = exports.COMMITMENT_WATCHED_SOURCES = void 0;
4
4
  exports.activationRefusalLines = activationRefusalLines;
5
+ exports.gateRefusesActivation = gateRefusesActivation;
5
6
  exports.permissionLines = permissionLines;
6
7
  exports.scheduleIssueLines = scheduleIssueLines;
7
8
  exports.retiredFieldsLine = retiredFieldsLine;
@@ -35,6 +36,7 @@ exports.renderCommitmentWriteWarnings = renderCommitmentWriteWarnings;
35
36
  exports.describeCommitmentRevision = describeCommitmentRevision;
36
37
  exports.describeCommitmentPriority = describeCommitmentPriority;
37
38
  exports.describeBoundCompass = describeBoundCompass;
39
+ exports.explainCompassLines = explainCompassLines;
38
40
  exports.renderCommitmentSummary = renderCommitmentSummary;
39
41
  exports.describeWakeDecision = describeWakeDecision;
40
42
  exports.renderCheckList = renderCheckList;
@@ -124,6 +126,20 @@ function activationRefusalLines(activation) {
124
126
  }
125
127
  return lines;
126
128
  }
129
+ /**
130
+ * Whether the server's activation answer names this gate as one that refuses activation (OSK-21258).
131
+ * A gate can be advisory for a run (`admission: 'advisory'`) and still be what `resume` refuses with a
132
+ * 409; the gate list printed it `info (advisory: does not stop a run)` right under an "Activation:
133
+ * would be refused" section naming the same gate. Matches on the gate id, and on the reason too when
134
+ * both sides carry one, so a gate advisory for one reason is not promoted by another. An older server
135
+ * that sends no answer promotes nothing.
136
+ */
137
+ function gateRefusesActivation(activation, gate) {
138
+ if (!activation || activation.canActivate !== false || !gate?.id)
139
+ return false;
140
+ return (activation.blockers ?? []).some((blocker) => blocker.gate === gate.id &&
141
+ (!blocker.reason || !gate.reason || blocker.reason === gate.reason));
142
+ }
127
143
  /**
128
144
  * The lines every Commitment read surface prints for "whose permissions does a
129
145
  * run use?" — `show`, `effective-policy` and `preflight` share it so the three
@@ -499,7 +515,8 @@ function unrecognizedCreateKeys(src) {
499
515
  if (known.includes(key) || SERVER_OWNED_CONTRACT_KEYS.has(key))
500
516
  continue;
501
517
  const near = key.length >= 4
502
- ? known.find((candidate) => candidate.length >= 4 && editDistance(key.toLowerCase(), candidate.toLowerCase()) <= 2)
518
+ ? known.find((candidate) => candidate.length >= 4 &&
519
+ editDistance(key.toLowerCase(), candidate.toLowerCase()) <= 2)
503
520
  : undefined;
504
521
  if (near)
505
522
  typos.push({ key, suggestion: near });
@@ -521,7 +538,7 @@ function ignoredCreateKeysWarning(keys) {
521
538
  if (keys.length === 0)
522
539
  return null;
523
540
  return (`--from-json: ${keys.map((key) => `"${key}"`).join(', ')} ${keys.length === 1 ? 'is' : 'are'} not sent ` +
524
- "(the CLI does not forward it), so the Commitment is built without it.");
541
+ '(the CLI does not forward it), so the Commitment is built without it.');
525
542
  }
526
543
  /**
527
544
  * Normalize a `--from-json` body into something the create endpoint accepts.
@@ -1291,6 +1308,25 @@ function describeBoundCompass(commitment) {
1291
1308
  const agent = commitment.agentId ?? '<agent-id>';
1292
1309
  return `Compass: ${id} — status not shown here; an archived compass governs nothing: \`skrr compass show ${id} --agent ${agent}\``;
1293
1310
  }
1311
+ /**
1312
+ * The bound Compass, as `explain` prints it (OSK-21332). `explain` is where a person asks "why does
1313
+ * this Commitment behave this way", and a bound Compass (or an archived one, which runs the
1314
+ * Commitment on defaults) is part of the answer. The explain payload names neither the Compass nor
1315
+ * its status, so the caller reads the Commitment itself, the same read `show` renders, and hands the
1316
+ * result here: the line is `describeBoundCompass`, the one `show` prints, not a second wording.
1317
+ *
1318
+ * `null` means that read did not come back. It is said, not skipped: a silent skip is
1319
+ * indistinguishable from "no Compass is bound". A Commitment bound to none prints nothing, as in
1320
+ * `show`.
1321
+ */
1322
+ function explainCompassLines(commitmentId, commitment) {
1323
+ if (!commitment) {
1324
+ return [
1325
+ `Compass: not shown — the commitment could not be read just now; \`skrr commitments show ${commitmentId}\` prints it`,
1326
+ ];
1327
+ }
1328
+ return commitment.compassId ? [describeBoundCompass(commitment)] : [];
1329
+ }
1294
1330
  function renderCommitmentSummary(commitment, log,
1295
1331
  /**
1296
1332
  * The most recent check, when the caller has one. Optional because pause /
@@ -1809,6 +1845,9 @@ const CYCLE_GLYPH = {
1809
1845
  stopped: '●',
1810
1846
  quiet: '○',
1811
1847
  in_flight: '◌',
1848
+ // An applied result whose agent said work is warranted and left nothing the platform can see
1849
+ // (OSK-21608): not a tick, because the work is still owed, and not a stop, because it applied.
1850
+ owed: '◐',
1812
1851
  };
1813
1852
  /** `MM-DD HH:MM` in the reader's local time; `-` when the time is unknown. */
1814
1853
  function cycleTime(value) {
@@ -1838,6 +1877,11 @@ function cycleSummary(cycle) {
1838
1877
  return `${question} ${reason || gate} — ${fix || detail || 'stopped'}${moved}`;
1839
1878
  }
1840
1879
  if (cycle.outcome === 'advanced') {
1880
+ if (cycle.owed) {
1881
+ return cycle.advancementKinds?.length
1882
+ ? `${cycle.advancementKinds.join(', ')} — ${data_provider_2.COMMITMENT_CYCLE_OWED_SENTENCE}`
1883
+ : data_provider_2.COMMITMENT_CYCLE_OWED_SENTENCE;
1884
+ }
1841
1885
  return cycle.advancementKinds?.length
1842
1886
  ? cycle.advancementKinds.join(', ')
1843
1887
  : 'applied an agent result';
@@ -1848,6 +1892,13 @@ function cycleSummary(cycle) {
1848
1892
  }
1849
1893
  return cycle.note || cycle.decision?.summary || cycle.decision?.posture || 'nothing to act on';
1850
1894
  }
1895
+ /** A record-only follow-through says what it is, and what it noted, instead of claiming progress. */
1896
+ function recordOnlyEchoSummary(cycle) {
1897
+ const noted = cycle.advancementKinds?.length
1898
+ ? ` (noted: ${cycle.advancementKinds.join(', ')})`
1899
+ : '';
1900
+ return `records the earlier run's outcome; no work of its own${noted}`;
1901
+ }
1851
1902
  /** Content-free rows admitted by the native sensor loader; licenses stay unknown. */
1852
1903
  function platformFetchLines(cycle) {
1853
1904
  const lines = [];
@@ -1879,8 +1930,17 @@ function formatCommitmentCycles(page) {
1879
1930
  return ['No cycles recorded yet.'];
1880
1931
  const lines = [];
1881
1932
  for (const cycle of cycles) {
1882
- const glyph = CYCLE_GLYPH[cycle.outcome] || '?';
1883
- lines.push(`${cycleTime(cycle.startedAt)} ${glyph} ${String(cycle.outcome).padEnd(9)} ${cycleWake(cycle).padEnd(18)} ${cycleSummary(cycle)}`);
1933
+ // A follow-through Check that only records the Run before it is not what the loop did, and
1934
+ // `✓ advanced` above a stopped Run reads as if the work had moved (OSK-21240).
1935
+ const echo = (0, data_provider_2.isRecordOnlyEchoCycle)(cycle);
1936
+ const owed = !echo && cycle.outcome === 'advanced' && cycle.owed === true;
1937
+ const glyph = echo
1938
+ ? CYCLE_GLYPH.quiet
1939
+ : owed
1940
+ ? CYCLE_GLYPH.owed
1941
+ : CYCLE_GLYPH[cycle.outcome] || '?';
1942
+ const word = echo ? 'recorded' : owed ? 'owed' : String(cycle.outcome);
1943
+ lines.push(`${cycleTime(cycle.startedAt)} ${glyph} ${word.padEnd(9)} ${cycleWake(cycle).padEnd(18)} ${echo ? recordOnlyEchoSummary(cycle) : cycleSummary(cycle)}`);
1884
1944
  lines.push(...platformFetchLines(cycle));
1885
1945
  lines.push(...formatCycleLatency(cycle));
1886
1946
  for (const task of cycle.tasks || []) {
@@ -134,6 +134,8 @@ export declare const compassApi: {
134
134
  data: CompassRevision[];
135
135
  /** True when the ledger holds rows older than this page. */
136
136
  has_more?: boolean;
137
+ /** How many revisions the ledger holds in all; absent from a server that predates it. */
138
+ total?: number;
137
139
  }>;
138
140
  trace: (id: string, query: Parameters<typeof withQuery>[1]) => Promise<CompassTrace>;
139
141
  revert: (id: string, body: Record<string, unknown>) => Promise<Compass>;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * One reading of "an ISO-8601 UTC instant" for every CLI flag that takes one.
3
+ *
4
+ * `tasks remind --at` and `tasks schedule --at` each had their own: remind ran
5
+ * `new Date(value)` behind a `/^\d{4}-\d{2}-\d{2}T/` check, and schedule forwarded
6
+ * the string and printed the server's zod issue. `new Date()` is the wrong tool
7
+ * for an instant, in two ways that both stored a different time than the one
8
+ * typed and said nothing (OSK-21504, OSK-21506):
9
+ *
10
+ * - a time with no zone is read in the zone of the MACHINE running the CLI, so
11
+ * the same command stored different instants on different laptops;
12
+ * - a day that does not exist overflows into the next month
13
+ * (`2027-02-30` is `2027-03-02`).
14
+ *
15
+ * The rule here is the one `tasks schedule --help` already states and the server's
16
+ * `z.string().datetime()` enforces: a calendar-real date, a time with seconds, and
17
+ * a literal `Z`. A numeric offset is refused too, even though it names an instant:
18
+ * the two commands must agree, and the server's schedule door does not take one.
19
+ *
20
+ * Never reads the machine's zone. Pure.
21
+ */
22
+ export type InstantProblem = 'not_a_date' | 'date_only' | 'no_zone' | 'numeric_offset' | 'not_a_real_date' | 'not_a_real_time';
23
+ export type InstantParse = {
24
+ ok: true;
25
+ date: Date;
26
+ iso: string;
27
+ } | {
28
+ ok: false;
29
+ problem: InstantProblem;
30
+ message: string;
31
+ };
32
+ /**
33
+ * Parse a flag value as a UTC instant, or say precisely what is wrong with it.
34
+ *
35
+ * `label` is what the user typed (`--at`, `scheduledFor`), so the message names
36
+ * their input rather than a REST field they never saw.
37
+ */
38
+ export declare function parseUtcInstant(raw: string, label?: string): InstantParse;
39
+ /** `2027-02-02T09:00:00Z` - the instant in UTC, whole seconds when there are no milliseconds. */
40
+ export declare function formatUtcInstant(date: Date): string;
41
+ /**
42
+ * `2027-02-02 01:00:00 PST` - the same instant on this machine's wall clock, with the
43
+ * zone NAMED. `toLocaleString()` printed neither the zone nor anything that mapped back
44
+ * to the instant that was typed (OSK-21505). `timeZone` is for tests and defaults to
45
+ * the machine's own.
46
+ */
47
+ export declare function formatLocalInstant(date: Date, timeZone?: string): string;
48
+ /**
49
+ * The instant for a person: UTC first (it is what was typed and what is stored), then the
50
+ * local reading with its zone name. A machine already on UTC gets the UTC form alone.
51
+ */
52
+ export declare function describeInstant(date: Date, timeZone?: string): string;