@skrr-ai/cli 0.1.21 → 0.1.23

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 (245) hide show
  1. package/README.md +83 -73
  2. package/bin/dev-fallback.js +221 -0
  3. package/bin/run.js +43 -0
  4. package/dist/base-command.d.ts +15 -0
  5. package/dist/base-command.js +71 -10
  6. package/dist/commands/agents/attach-harness.d.ts +37 -0
  7. package/dist/commands/agents/attach-harness.js +102 -3
  8. package/dist/commands/agents/chat.d.ts +69 -1
  9. package/dist/commands/agents/chat.js +202 -22
  10. package/dist/commands/agents/create.d.ts +38 -0
  11. package/dist/commands/agents/create.js +95 -2
  12. package/dist/commands/agents/schedule-trace.js +11 -0
  13. package/dist/commands/agents/show.js +29 -1
  14. package/dist/commands/api-keys/create.d.ts +24 -0
  15. package/dist/commands/api-keys/create.js +80 -0
  16. package/dist/commands/api-keys/list.d.ts +11 -0
  17. package/dist/commands/api-keys/list.js +42 -0
  18. package/dist/commands/api-keys/revoke.d.ts +14 -0
  19. package/dist/commands/api-keys/revoke.js +33 -0
  20. package/dist/commands/api-keys/rotate.d.ts +19 -0
  21. package/dist/commands/api-keys/rotate.js +42 -0
  22. package/dist/commands/balance/index.d.ts +18 -0
  23. package/dist/commands/balance/index.js +29 -0
  24. package/dist/commands/balance/show.d.ts +25 -0
  25. package/dist/commands/balance/show.js +60 -0
  26. package/dist/commands/balance/statement.d.ts +19 -0
  27. package/dist/commands/balance/statement.js +52 -0
  28. package/dist/commands/browser/doctor.d.ts +11 -0
  29. package/dist/commands/browser/doctor.js +11 -0
  30. package/dist/commands/code/jobs/approve.d.ts +25 -0
  31. package/dist/commands/code/jobs/approve.js +52 -0
  32. package/dist/commands/code/jobs/cancel.d.ts +12 -0
  33. package/dist/commands/code/jobs/cancel.js +39 -0
  34. package/dist/commands/code/jobs/index.d.ts +19 -0
  35. package/dist/commands/code/jobs/index.js +38 -0
  36. package/dist/commands/code/jobs/list.d.ts +12 -0
  37. package/dist/commands/code/jobs/list.js +87 -0
  38. package/dist/commands/code/jobs/run.d.ts +32 -0
  39. package/dist/commands/code/jobs/run.js +151 -0
  40. package/dist/commands/code/jobs/show.d.ts +12 -0
  41. package/dist/commands/code/jobs/show.js +86 -0
  42. package/dist/commands/commitments/create.d.ts +1 -0
  43. package/dist/commands/commitments/create.js +19 -5
  44. package/dist/commands/commitments/preflight.js +18 -4
  45. package/dist/commands/commitments/receipt-metrics.d.ts +39 -0
  46. package/dist/commands/commitments/receipt-metrics.js +78 -0
  47. package/dist/commands/convos/search.js +5 -5
  48. package/dist/commands/harnesses/leases/list.d.ts +3 -0
  49. package/dist/commands/harnesses/leases/list.js +59 -12
  50. package/dist/commands/harnesses/leases/show.js +36 -0
  51. package/dist/commands/harnesses/list.d.ts +18 -0
  52. package/dist/commands/harnesses/list.js +30 -0
  53. package/dist/commands/harnesses/products.js +48 -3
  54. package/dist/commands/harnesses/quota.d.ts +16 -0
  55. package/dist/commands/harnesses/quota.js +26 -1
  56. package/dist/commands/harnesses/usage.js +17 -0
  57. package/dist/commands/inbox/index.d.ts +2 -1
  58. package/dist/commands/inbox/index.js +27 -4
  59. package/dist/commands/login.js +4 -1
  60. package/dist/commands/machines/list.d.ts +1 -0
  61. package/dist/commands/machines/list.js +33 -5
  62. package/dist/commands/memory/approvals/issue.d.ts +18 -0
  63. package/dist/commands/memory/approvals/issue.js +63 -0
  64. package/dist/commands/memory/commit.js +5 -2
  65. package/dist/commands/memory/context.d.ts +3 -2
  66. package/dist/commands/memory/context.js +9 -11
  67. package/dist/commands/memory/delete.d.ts +1 -1
  68. package/dist/commands/memory/list.d.ts +1 -1
  69. package/dist/commands/memory/list.js +1 -1
  70. package/dist/commands/memory/mounts/resolve.d.ts +10 -0
  71. package/dist/commands/memory/mounts/resolve.js +27 -0
  72. package/dist/commands/memory/proposals/commit.d.ts +18 -0
  73. package/dist/commands/memory/proposals/commit.js +32 -0
  74. package/dist/commands/memory/proposals/create.d.ts +18 -0
  75. package/dist/commands/memory/proposals/create.js +56 -0
  76. package/dist/commands/memory/proposals/list.d.ts +13 -0
  77. package/dist/commands/memory/proposals/list.js +31 -0
  78. package/dist/commands/memory/proposals/promote.d.ts +22 -0
  79. package/dist/commands/memory/proposals/promote.js +33 -0
  80. package/dist/commands/memory/proposals/reject.d.ts +16 -0
  81. package/dist/commands/memory/proposals/reject.js +28 -0
  82. package/dist/commands/memory/rebuild-index.d.ts +20 -0
  83. package/dist/commands/memory/rebuild-index.js +25 -0
  84. package/dist/commands/memory/save.d.ts +1 -1
  85. package/dist/commands/memory/save.js +1 -1
  86. package/dist/commands/memory/search.d.ts +6 -3
  87. package/dist/commands/memory/search.js +27 -3
  88. package/dist/commands/memory/show.d.ts +1 -1
  89. package/dist/commands/memory/stores/archive.d.ts +14 -0
  90. package/dist/commands/memory/stores/archive.js +31 -0
  91. package/dist/commands/memory/stores/create.d.ts +12 -0
  92. package/dist/commands/memory/stores/create.js +30 -0
  93. package/dist/commands/memory/stores/delete.d.ts +15 -0
  94. package/dist/commands/memory/stores/delete.js +38 -0
  95. package/dist/commands/memory/stores/list.d.ts +10 -0
  96. package/dist/commands/memory/stores/list.js +29 -0
  97. package/dist/commands/memory/stores/show.d.ts +12 -0
  98. package/dist/commands/memory/stores/show.js +14 -0
  99. package/dist/commands/memory/stores/update.d.ts +15 -0
  100. package/dist/commands/memory/stores/update.js +39 -0
  101. package/dist/commands/memory/sync/delete.d.ts +13 -0
  102. package/dist/commands/memory/sync/delete.js +35 -0
  103. package/dist/commands/memory/sync/pull.d.ts +19 -0
  104. package/dist/commands/memory/sync/pull.js +52 -0
  105. package/dist/commands/memory/sync/push.d.ts +18 -0
  106. package/dist/commands/memory/sync/push.js +115 -0
  107. package/dist/commands/memory/sync/resolve.d.ts +23 -0
  108. package/dist/commands/memory/sync/resolve.js +27 -0
  109. package/dist/commands/memory/sync/status.d.ts +9 -0
  110. package/dist/commands/memory/sync/status.js +22 -0
  111. package/dist/commands/memory/topics/archive.d.ts +20 -0
  112. package/dist/commands/memory/topics/archive.js +29 -0
  113. package/dist/commands/memory/topics/create.d.ts +23 -0
  114. package/dist/commands/memory/topics/create.js +46 -0
  115. package/dist/commands/memory/topics/delete.d.ts +22 -0
  116. package/dist/commands/memory/topics/delete.js +30 -0
  117. package/dist/commands/memory/topics/forget.d.ts +22 -0
  118. package/dist/commands/memory/topics/forget.js +29 -0
  119. package/dist/commands/memory/topics/list.d.ts +13 -0
  120. package/dist/commands/memory/topics/list.js +22 -0
  121. package/dist/commands/memory/topics/move.d.ts +22 -0
  122. package/dist/commands/memory/topics/move.js +26 -0
  123. package/dist/commands/memory/topics/show.d.ts +17 -0
  124. package/dist/commands/memory/topics/show.js +21 -0
  125. package/dist/commands/memory/topics/update.d.ts +23 -0
  126. package/dist/commands/memory/topics/update.js +27 -0
  127. package/dist/commands/memory/tree.d.ts +5 -2
  128. package/dist/commands/memory/tree.js +20 -2
  129. package/dist/commands/memory/update.d.ts +1 -1
  130. package/dist/commands/memory/versions/list.d.ts +16 -0
  131. package/dist/commands/memory/versions/list.js +22 -0
  132. package/dist/commands/memory/versions/redact.d.ts +21 -0
  133. package/dist/commands/memory/versions/redact.js +22 -0
  134. package/dist/commands/memory/versions/restore.d.ts +20 -0
  135. package/dist/commands/memory/versions/restore.js +16 -0
  136. package/dist/commands/memory/versions/show.d.ts +15 -0
  137. package/dist/commands/memory/versions/show.js +17 -0
  138. package/dist/commands/messages/list.js +25 -8
  139. package/dist/commands/messages/show.js +10 -0
  140. package/dist/commands/store/install.js +5 -1
  141. package/dist/commands/store/releases.js +1 -1
  142. package/dist/commands/store/update.js +5 -1
  143. package/dist/commands/subscriptions/cancel.js +5 -1
  144. package/dist/commands/subscriptions/health.js +5 -1
  145. package/dist/commands/subscriptions/status.js +5 -1
  146. package/dist/commands/subscriptions/subscribe.js +5 -1
  147. package/dist/commands/tasks/activity.d.ts +2 -3
  148. package/dist/commands/tasks/activity.js +15 -5
  149. package/dist/commands/tasks/complete.d.ts +18 -150
  150. package/dist/commands/tasks/complete.js +161 -600
  151. package/dist/commands/tasks/create.d.ts +5 -0
  152. package/dist/commands/tasks/create.js +107 -4
  153. package/dist/commands/tasks/deliverable/add.d.ts +8 -0
  154. package/dist/commands/tasks/deliverable/add.js +41 -8
  155. package/dist/commands/tasks/deliverable/confirm.d.ts +5 -5
  156. package/dist/commands/tasks/deliverable/confirm.js +9 -18
  157. package/dist/commands/tasks/deliverable/list.d.ts +12 -0
  158. package/dist/commands/tasks/deliverable/list.js +37 -0
  159. package/dist/commands/tasks/events/append.d.ts +2 -0
  160. package/dist/commands/tasks/events/append.js +11 -4
  161. package/dist/commands/tasks/events/list.d.ts +1 -0
  162. package/dist/commands/tasks/events/list.js +1 -0
  163. package/dist/commands/tasks/events/tail.d.ts +1 -0
  164. package/dist/commands/tasks/events/tail.js +1 -0
  165. package/dist/commands/tasks/expectations/assess.d.ts +21 -0
  166. package/dist/commands/tasks/expectations/assess.js +77 -0
  167. package/dist/commands/tasks/expectations.js +14 -18
  168. package/dist/commands/tasks/judge.d.ts +1 -0
  169. package/dist/commands/tasks/judge.js +2 -1
  170. package/dist/commands/tasks/output.d.ts +2 -11
  171. package/dist/commands/tasks/output.js +64 -145
  172. package/dist/commands/tasks/report.d.ts +3 -3
  173. package/dist/commands/tasks/report.js +64 -41
  174. package/dist/commands/tasks/resource/add.d.ts +23 -0
  175. package/dist/commands/tasks/resource/add.js +76 -0
  176. package/dist/commands/tasks/resource/list.d.ts +13 -0
  177. package/dist/commands/tasks/resource/list.js +41 -0
  178. package/dist/commands/tasks/result/show.d.ts +13 -0
  179. package/dist/commands/tasks/result/show.js +46 -0
  180. package/dist/commands/tasks/result/submit.d.ts +26 -0
  181. package/dist/commands/tasks/result/submit.js +134 -0
  182. package/dist/commands/tasks/resume/save.d.ts +16 -0
  183. package/dist/commands/tasks/resume/save.js +59 -0
  184. package/dist/commands/tasks/resume/show.d.ts +13 -0
  185. package/dist/commands/tasks/resume/show.js +35 -0
  186. package/dist/commands/tasks/review.d.ts +17 -0
  187. package/dist/commands/tasks/review.js +54 -0
  188. package/dist/commands/tasks/runs.js +5 -0
  189. package/dist/commands/tasks/timeline.d.ts +2 -2
  190. package/dist/commands/tasks/timeline.js +10 -23
  191. package/dist/commands/tasks/updates/add.d.ts +22 -0
  192. package/dist/commands/tasks/updates/add.js +101 -0
  193. package/dist/commands/tasks/updates/list.d.ts +15 -0
  194. package/dist/commands/tasks/updates/list.js +57 -0
  195. package/dist/commands/tasks/waive.d.ts +1 -0
  196. package/dist/commands/tasks/waive.js +2 -1
  197. package/dist/lib/agentic-stream.d.ts +257 -0
  198. package/dist/lib/agentic-stream.js +490 -33
  199. package/dist/lib/balance.d.ts +8 -0
  200. package/dist/lib/commitment-product.d.ts +10 -1
  201. package/dist/lib/commitment-product.js +66 -2
  202. package/dist/lib/commitments.d.ts +4 -1
  203. package/dist/lib/commitments.js +32 -1
  204. package/dist/lib/conversation-search.d.ts +3 -0
  205. package/dist/lib/conversation-search.js +75 -0
  206. package/dist/lib/execution-target.d.ts +21 -0
  207. package/dist/lib/execution-target.js +22 -6
  208. package/dist/lib/harnesses.d.ts +82 -6
  209. package/dist/lib/harnesses.js +30 -2
  210. package/dist/lib/hosted-machines.js +9 -0
  211. package/dist/lib/inbox.d.ts +2 -1
  212. package/dist/lib/machines.d.ts +17 -0
  213. package/dist/lib/machines.js +29 -0
  214. package/dist/lib/managed-agent-keys.d.ts +104 -0
  215. package/dist/lib/managed-agent-keys.js +50 -0
  216. package/dist/lib/memory-scope.d.ts +4 -3
  217. package/dist/lib/memory-scope.js +9 -9
  218. package/dist/lib/memory-sync.d.ts +114 -0
  219. package/dist/lib/memory-sync.js +730 -0
  220. package/dist/lib/message-provenance.d.ts +42 -0
  221. package/dist/lib/message-provenance.js +89 -0
  222. package/dist/lib/scoped-memory-command.d.ts +68 -0
  223. package/dist/lib/scoped-memory-command.js +183 -0
  224. package/dist/lib/session-task-endpoints.d.ts +1 -1
  225. package/dist/lib/session-task-endpoints.js +7 -0
  226. package/dist/lib/sky-code-broker.d.ts +47 -0
  227. package/dist/lib/sky-code-broker.js +50 -0
  228. package/dist/lib/sky-code-doctor.js +10 -1
  229. package/dist/lib/task-artifact-flags.d.ts +2 -0
  230. package/dist/lib/task-artifact-flags.js +7 -0
  231. package/dist/lib/task-execution.d.ts +4 -5
  232. package/dist/lib/task-execution.js +4 -5
  233. package/dist/lib/task-extras.d.ts +1 -5
  234. package/dist/lib/task-extras.js +1 -5
  235. package/dist/lib/task-transcript.d.ts +5 -7
  236. package/dist/lib/task-transcript.js +8 -16
  237. package/dist/lib/tasks.d.ts +7 -21
  238. package/dist/lib/tasks.js +17 -20
  239. package/dist/lib/triggers.d.ts +39 -1
  240. package/dist/lib/triggers.js +70 -2
  241. package/dist/lib/usage-discovery.d.ts +17 -1
  242. package/dist/lib/usage-discovery.js +17 -1
  243. package/dist/node_modules/@skrr-ai/data-provider/index.js +3733 -3618
  244. package/oclif.manifest.json +25726 -19886
  245. package/package.json +26 -5
@@ -163,6 +163,14 @@ export interface BalanceStatementEntry {
163
163
  service: string | null;
164
164
  provider: string | null;
165
165
  model: string | null;
166
+ /**
167
+ * WHICH agent and conversation incurred it (OSK-7641).
168
+ *
169
+ * Optional because they are: the ledger carries them only where the producer
170
+ * knows them, and an older row has neither.
171
+ */
172
+ agentId?: string | null;
173
+ conversationId?: string | null;
166
174
  /** What incurred it, e.g. `session` / `run` — with `sourceId`, the thing
167
175
  * `balance history` could never name. */
168
176
  sourceType?: string;
@@ -1,4 +1,4 @@
1
- import type { CommitmentDeliveryIntent, CommitmentExecutionReceipt } from '@skrr-ai/data-provider';
1
+ import type { CommitmentDeliveryIntent, CommitmentExecutionReceipt, CommitmentExecutionReceiptQuarterlyMetricsResponse } from '@skrr-ai/data-provider';
2
2
  export declare const COMMITMENT_PRODUCT_MODES: readonly ["monitor", "prepare", "execute"];
3
3
  export declare const COMMITMENT_DELIVERY_CHOICES: readonly ["report-only", "local-patch", "draft-pr", "push-branch", "direct-branch"];
4
4
  /** Compatibility is accepted at the input boundary; everyday output uses the product contract. */
@@ -12,3 +12,12 @@ export declare function deliveryInput(mode: string, options?: {
12
12
  /** Monitor is observation-only; retain report expectations, never repo coordinates. */
13
13
  export declare function deliveryForProductMode(mode: string | undefined, delivery?: CommitmentDeliveryIntent): CommitmentDeliveryIntent | undefined;
14
14
  export declare function renderExecutionReceipt(receipt: CommitmentExecutionReceipt, log: (line: string) => void, verbose?: boolean): void;
15
+ /**
16
+ * `skrr commitments receipt-metrics <id>` — the closed-quarter rollup.
17
+ *
18
+ * Ordered as the funnel it describes rather than as the payload's field order:
19
+ * a wake arrives, some wakes were eligible to act on, some of those started an
20
+ * execution, some declared a delivery, and some of THOSE were verified. Reading
21
+ * it top to bottom is how you see which step the loop is losing work at.
22
+ */
23
+ export declare function renderExecutionReceiptQuarterlyMetrics(response: CommitmentExecutionReceiptQuarterlyMetricsResponse, log: (line: string) => void, verbose?: boolean): void;
@@ -7,6 +7,7 @@ exports.deliveryLabel = deliveryLabel;
7
7
  exports.deliveryInput = deliveryInput;
8
8
  exports.deliveryForProductMode = deliveryForProductMode;
9
9
  exports.renderExecutionReceipt = renderExecutionReceipt;
10
+ exports.renderExecutionReceiptQuarterlyMetrics = renderExecutionReceiptQuarterlyMetrics;
10
11
  exports.COMMITMENT_PRODUCT_MODES = ['monitor', 'prepare', 'execute'];
11
12
  exports.COMMITMENT_DELIVERY_CHOICES = [
12
13
  'report-only',
@@ -71,8 +72,9 @@ function renderExecutionReceipt(receipt, log, verbose = false) {
71
72
  const mark = { done: '✓', working: '→', waiting: '…', failed: '×', skipped: '–' }[phase.status];
72
73
  log(` ${mark} ${phase.phase[0].toUpperCase() + phase.phase.slice(1)}: ${phase.summary}${phase.at ? ` · ${phase.at}` : ''}`);
73
74
  for (const evidence of phase.evidence) {
74
- if (evidence.url)
75
- log(` ${evidence.label}: ${evidence.url}`);
75
+ const location = evidence.url || evidence.path;
76
+ if (location)
77
+ log(` ${evidence.label}: ${location}`);
76
78
  }
77
79
  }
78
80
  if (receipt.nextActor)
@@ -85,3 +87,65 @@ function renderExecutionReceipt(receipt, log, verbose = false) {
85
87
  if (verbose)
86
88
  log(`Diagnostics:\n${JSON.stringify({ id: receipt.id, revision: receipt.revision, evidence: receipt.evidence, diagnostics: receipt.diagnostics }, null, 2)}`);
87
89
  }
90
+ /**
91
+ * A rate the server could not compute is NOT zero.
92
+ *
93
+ * Every `*Rate` on the metrics payload is `number | null`, and the null means
94
+ * "no denominator in this cohort" — a quarter with no eligible material wake
95
+ * has no verified-outcome rate to report. Printing that as `0%` would read as
96
+ * "we tried and delivered nothing", which is the opposite of "we were never
97
+ * asked". So an absent rate renders as an em dash and stays visibly absent.
98
+ */
99
+ function rate(value) {
100
+ if (value === null || value === undefined)
101
+ return '—';
102
+ return `${(value * 100).toFixed(1)}%`;
103
+ }
104
+ /** Same rule for a ratio that is not a percentage. */
105
+ function ratio(value) {
106
+ if (value === null || value === undefined)
107
+ return '—';
108
+ return value.toFixed(2);
109
+ }
110
+ /**
111
+ * `skrr commitments receipt-metrics <id>` — the closed-quarter rollup.
112
+ *
113
+ * Ordered as the funnel it describes rather than as the payload's field order:
114
+ * a wake arrives, some wakes were eligible to act on, some of those started an
115
+ * execution, some declared a delivery, and some of THOSE were verified. Reading
116
+ * it top to bottom is how you see which step the loop is losing work at.
117
+ */
118
+ function renderExecutionReceiptQuarterlyMetrics(response, log, verbose = false) {
119
+ const { cohort, metrics } = response;
120
+ log(`Q${cohort.quarter} ${cohort.year} · ${cohort.from} → ${cohort.to}`);
121
+ log(`Checks in cohort: ${cohort.checkCount} · receipts: ${metrics.receiptCount}`);
122
+ log('');
123
+ log('Funnel');
124
+ log(` Material wakes: ${metrics.materialWakes} (${metrics.eligibleMaterialWakes} eligible)`);
125
+ log(` Execute starts: ${metrics.executeStarts}`);
126
+ log(` Declared deliveries: ${metrics.declaredDeliveries}`);
127
+ log(` Verified outcomes: ${metrics.verifiedOutcomes}`);
128
+ log(` Blocked: ${metrics.blocked}`);
129
+ log('');
130
+ log('Rates');
131
+ log(` Verified per eligible wake: ${ratio(metrics.verifiedOutcomesPerEligibleMaterialWake)}`);
132
+ log(` Verified outcome rate: ${rate(metrics.verifiedOutcomeRate)}`);
133
+ log(` Delivery ratio: ${rate(metrics.deliveryRatio)}`);
134
+ log(` Autonomous, no intervention: ${rate(metrics.autonomousDeliveryWithoutInterventionRate)}`);
135
+ log(` Human intervention rate: ${rate(metrics.humanInterventionRate)}`);
136
+ log(` Orphaned delivery rate: ${rate(metrics.orphanedDeliveryRate)}`);
137
+ log(` False ready / false blocked: ${rate(metrics.falseReadyRate)} / ${rate(metrics.falseBlockedRate)}`);
138
+ // The unknown counts decide whether the rates above are worth acting on, so
139
+ // they are one flag away rather than buried in --json.
140
+ if (verbose) {
141
+ const m = metrics.measurement;
142
+ log('');
143
+ log('Measurement');
144
+ log(` Cohort: ${m.cohort}`);
145
+ log(` Intervention: ${m.interventionDefinition}`);
146
+ log(` Eligible material wake: ${m.eligibleMaterialWakeDefinition}`);
147
+ log(` Unknown admission runs: ${m.unknownAdmissionRuns} · unknown startup runs: ${m.unknownStartupRuns}`);
148
+ log(` Tracked completed runs: ${m.trackedCompletedRuns}`);
149
+ log(` Time to explanation: p50 ${metrics.timeToExplanationMs.p50 ?? '—'}ms · p95 ${metrics.timeToExplanationMs.p95 ?? '—'}ms (n=${metrics.timeToExplanationMs.count})`);
150
+ }
151
+ }
@@ -17,7 +17,7 @@
17
17
  * "hold this state", derived from a Compass invariant — not authored here).
18
18
  */
19
19
  import { COMMITMENT_WATCHED_SOURCES, COMMITMENT_TEMPLATE_IDS, COMMITMENT_CADENCE_MODES, COMMITMENT_CADENCE_TIME_PATTERN, COMMITMENT_CADENCE_MAX_TIMES, COMMITMENT_AUTONOMY_MODES, COMMITMENT_EXECUTION_ISOLATION_MODES } from '@skrr-ai/data-provider';
20
- import type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentStatusResponse, CommitmentWorkLink, CommitmentExecutionReceiptListResponse, CommitmentCapabilitySource, CommitmentDeliveryPatchResponse, CommitmentDeliveryBundleResponse } from '@skrr-ai/data-provider';
20
+ import type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentStatusResponse, CommitmentWorkLink, CommitmentExecutionReceiptListResponse, CommitmentExecutionReceiptQuarterlyMetricsResponse, CommitmentCapabilitySource, CommitmentDeliveryPatchResponse, CommitmentDeliveryBundleResponse } from '@skrr-ai/data-provider';
21
21
  import { withQuery } from './triggers';
22
22
  export { COMMITMENT_WATCHED_SOURCES, COMMITMENT_TEMPLATE_IDS, COMMITMENT_CADENCE_MODES, COMMITMENT_CADENCE_TIME_PATTERN, COMMITMENT_CADENCE_MAX_TIMES, };
23
23
  export type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentStatusResponse, CommitmentWorkLink, };
@@ -284,6 +284,7 @@ export declare const commitmentApi: {
284
284
  mode: (id: string, body: Record<string, unknown>) => Promise<Commitment>;
285
285
  delivery: (id: string, body: Record<string, unknown>) => Promise<Commitment>;
286
286
  receipts: (id: string, limit?: number) => Promise<CommitmentExecutionReceiptListResponse>;
287
+ receiptQuarterlyMetrics: (id: string, year: number, quarter: number) => Promise<CommitmentExecutionReceiptQuarterlyMetricsResponse>;
287
288
  deliveryPatch: (id: string, taskId: string) => Promise<CommitmentDeliveryPatchResponse>;
288
289
  deliveryBundle: (id: string, taskId: string) => Promise<CommitmentDeliveryBundleResponse>;
289
290
  operatingReview: (agentId: string) => Promise<Record<string, unknown>>;
@@ -449,6 +450,8 @@ export declare function parseIntervalToMs(input: string): number;
449
450
  export type WatchFlagInputs = {
450
451
  watch?: string[];
451
452
  metricKey?: string[];
453
+ githubEvents?: string[];
454
+ githubRepository?: string;
452
455
  /** Already resolved to canonical space ids by the caller. */
453
456
  spaceIds?: string[];
454
457
  monitorIds?: string[];
@@ -183,6 +183,7 @@ exports.commitmentApi = {
183
183
  mode: (id, body) => data_provider_1.request.post(`${base(id)}/mode`, body),
184
184
  delivery: (id, body) => data_provider_1.request.post(`${base(id)}/delivery`, body),
185
185
  receipts: (id, limit = 1) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/receipts`, { limit })),
186
+ receiptQuarterlyMetrics: (id, year, quarter) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/receipt-metrics/quarterly`, { year, quarter })),
186
187
  deliveryPatch: (id, taskId) => data_provider_1.request.get(`${base(id)}/delivery/${encodeURIComponent(taskId)}/patch`),
187
188
  deliveryBundle: (id, taskId) => data_provider_1.request.get(`${base(id)}/delivery/${encodeURIComponent(taskId)}/bundle`),
188
189
  operatingReview: (agentId) => data_provider_1.request.get(`/api/commitments/agents/${encodeURIComponent(agentId)}/operating-review`),
@@ -438,11 +439,17 @@ function buildWatchedSources(inputs) {
438
439
  const metricKeys = (inputs.metricKey ?? []).filter(Boolean);
439
440
  const spaceIds = (inputs.spaceIds ?? []).filter(Boolean);
440
441
  const monitorIds = (inputs.monitorIds ?? []).filter(Boolean);
442
+ const githubEvents = (inputs.githubEvents ?? []).filter(Boolean);
443
+ const githubRepository = String(inputs.githubRepository || '')
444
+ .trim()
445
+ .toLowerCase();
441
446
  const effective = new Set(watched);
442
447
  if (spaceIds.length)
443
448
  effective.add('space');
444
449
  if (monitorIds.length)
445
450
  effective.add('monitor');
451
+ if (githubEvents.length)
452
+ effective.add('github');
446
453
  if (effective.has('space') && spaceIds.length === 0) {
447
454
  throw new Error('--watch space needs at least one --watch-space <space-id-or-ref>. ' +
448
455
  'The `space` source requires a scope id, which the enum on its own cannot carry.');
@@ -466,7 +473,7 @@ function buildWatchedSources(inputs) {
466
473
  seen.add(s);
467
474
  }
468
475
  }
469
- for (const implied of ['space', 'monitor']) {
476
+ for (const implied of ['space', 'monitor', 'github']) {
470
477
  if (effective.has(implied) && !seen.has(implied)) {
471
478
  orderedSources.push(implied);
472
479
  seen.add(implied);
@@ -487,6 +494,30 @@ function buildWatchedSources(inputs) {
487
494
  else if (source === 'metrics' && metricKeys.length) {
488
495
  rows.push({ source, enabled: true, config: { metricKeys } });
489
496
  }
497
+ else if (source === 'github' && (githubEvents.length || githubRepository)) {
498
+ rows.push({
499
+ source,
500
+ enabled: true,
501
+ config: {
502
+ ...(githubRepository ? { repository: githubRepository } : {}),
503
+ ...(githubEvents.length
504
+ ? {
505
+ events: githubEvents.map((eventType) => ({
506
+ entityType: 'github',
507
+ eventType,
508
+ ...(githubRepository
509
+ ? {
510
+ filterField: 'repoFullName',
511
+ filterMode: 'equals',
512
+ filterValue: githubRepository,
513
+ }
514
+ : {}),
515
+ })),
516
+ }
517
+ : {}),
518
+ },
519
+ });
520
+ }
490
521
  else {
491
522
  rows.push({ source, enabled: true });
492
523
  }
@@ -0,0 +1,3 @@
1
+ export declare function normalizedSnippetLength(value: number): number;
2
+ /** Keep search output a navigation result, never a transcript payload. */
3
+ export declare function compactMessageSearchHit(message: Record<string, unknown>, requestedSnippetLength: number, query?: string): Record<string, unknown>;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizedSnippetLength = normalizedSnippetLength;
4
+ exports.compactMessageSearchHit = compactMessageSearchHit;
5
+ const MAX_SEARCH_SNIPPET_CHARS = 4000;
6
+ function normalizedSnippetLength(value) {
7
+ if (!Number.isFinite(value))
8
+ return 160;
9
+ return Math.min(MAX_SEARCH_SNIPPET_CHARS, Math.max(1, Math.trunc(value)));
10
+ }
11
+ function findQueryMatch(text, query) {
12
+ const normalizedQuery = query.trim().toLocaleLowerCase();
13
+ if (!normalizedQuery)
14
+ return null;
15
+ const loweredText = text.toLocaleLowerCase();
16
+ const exactAt = loweredText.indexOf(normalizedQuery);
17
+ if (exactAt >= 0)
18
+ return { at: exactAt, length: normalizedQuery.length };
19
+ // Meilisearch tokenizes punctuation-bearing queries and can legitimately
20
+ // match AGENT_EMPTY_RESPONSE as separate words. If the exact phrase is not
21
+ // present, center on the earliest substantive query token so the bounded CLI
22
+ // result still explains why it matched instead of truncating just before the
23
+ // highlighted word returned by the server crop.
24
+ const tokens = Array.from(new Set(normalizedQuery.split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2)));
25
+ let best = null;
26
+ for (const token of tokens) {
27
+ const at = loweredText.indexOf(token);
28
+ if (at >= 0 && (!best || at < best.at))
29
+ best = { at, length: token.length };
30
+ }
31
+ return best;
32
+ }
33
+ /** Keep search output a navigation result, never a transcript payload. */
34
+ function compactMessageSearchHit(message, requestedSnippetLength, query = '') {
35
+ const limit = normalizedSnippetLength(requestedSnippetLength);
36
+ const normalizedText = String(message.text ?? '')
37
+ .replace(/\s+/g, ' ')
38
+ .trim();
39
+ const normalizedQuery = query.trim();
40
+ let text = normalizedText;
41
+ if (!text) {
42
+ text = '[matching text unavailable]';
43
+ }
44
+ else if (text.length > limit) {
45
+ const match = findQueryMatch(text, normalizedQuery);
46
+ if (!match) {
47
+ text = text.slice(0, limit);
48
+ }
49
+ else {
50
+ const matchAt = match.at;
51
+ const matchLength = Math.min(match.length, limit);
52
+ const prefixBudget = Math.floor((limit - matchLength) / 2);
53
+ let start = Math.max(0, matchAt - prefixBudget);
54
+ let prefix = start > 0 ? '…' : '';
55
+ let suffix = '…';
56
+ let contentBudget = Math.max(1, limit - prefix.length - suffix.length);
57
+ start = Math.min(start, Math.max(0, text.length - contentBudget));
58
+ prefix = start > 0 ? '…' : '';
59
+ suffix = start + contentBudget < text.length ? '…' : '';
60
+ contentBudget = Math.max(1, limit - prefix.length - suffix.length);
61
+ start = Math.min(start, Math.max(0, text.length - contentBudget));
62
+ text = `${prefix}${text.slice(start, start + contentBudget)}${suffix}`;
63
+ }
64
+ }
65
+ text = text.slice(0, limit);
66
+ return {
67
+ messageId: message.messageId,
68
+ conversationId: message.conversationId,
69
+ title: message.title,
70
+ sender: message.sender,
71
+ isCreatedByUser: message.isCreatedByUser,
72
+ createdAt: message.createdAt,
73
+ text,
74
+ };
75
+ }
@@ -21,8 +21,29 @@ export interface BuildExecutionOrderPatchInput {
21
21
  selectors: string[];
22
22
  /** Reasons that permit each non-final target to advance. */
23
23
  advanceOn?: string[];
24
+ /**
25
+ * No category permits an advance — a STRICT chain.
26
+ *
27
+ * `advanceOn` omitted defaults to `['offline','unavailable']` on every
28
+ * non-final target (see below), and the enum has no empty member, so
29
+ * "strictly pinned, with a declared next target" was inexpressible from any
30
+ * flag: the only way to build it was to bypass the flags entirely and
31
+ * hand-write `executionPolicy` through `--from-json` (OSK-7669).
32
+ */
33
+ strict?: boolean;
24
34
  }
25
35
  export declare function normalizeExecutionTargetSelector(input: string): string;
26
36
  export declare function normalizeAdvanceReasons(values: string[] | undefined): ExecutionTargetAdvanceReason[];
27
37
  export declare function executionTargetForSelector(input: string): AgentExecutionTarget;
38
+ /**
39
+ * What a non-final target may advance on when the caller said nothing.
40
+ *
41
+ * Exported because it is a DEFAULT the operator is not told about anywhere:
42
+ * `--advance-on` reads as an explicit opt-in ("Failure category that PERMITS
43
+ * the current target to advance"), so omitting it looks like opting into
44
+ * nothing — and it grants two categories on a target the caller explicitly
45
+ * pinned (OSK-7669). Naming it here lets the help text and the confirmation
46
+ * output say the same thing the code does.
47
+ */
48
+ export declare const DEFAULT_ADVANCE_REASONS: readonly ExecutionTargetAdvanceReason[];
28
49
  export declare function buildExecutionOrderPatch(input: BuildExecutionOrderPatchInput): AgentExecutionPatch;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EXECUTION_TARGET_ADVANCE_REASONS = void 0;
3
+ exports.DEFAULT_ADVANCE_REASONS = exports.EXECUTION_TARGET_ADVANCE_REASONS = void 0;
4
4
  exports.normalizeExecutionTargetSelector = normalizeExecutionTargetSelector;
5
5
  exports.normalizeAdvanceReasons = normalizeAdvanceReasons;
6
6
  exports.executionTargetForSelector = executionTargetForSelector;
@@ -78,6 +78,20 @@ function executionTargetForSelector(input) {
78
78
  managedUsageConsent: false,
79
79
  };
80
80
  }
81
+ /**
82
+ * What a non-final target may advance on when the caller said nothing.
83
+ *
84
+ * Exported because it is a DEFAULT the operator is not told about anywhere:
85
+ * `--advance-on` reads as an explicit opt-in ("Failure category that PERMITS
86
+ * the current target to advance"), so omitting it looks like opting into
87
+ * nothing — and it grants two categories on a target the caller explicitly
88
+ * pinned (OSK-7669). Naming it here lets the help text and the confirmation
89
+ * output say the same thing the code does.
90
+ */
91
+ exports.DEFAULT_ADVANCE_REASONS = [
92
+ 'offline',
93
+ 'unavailable',
94
+ ];
81
95
  function buildExecutionOrderPatch(input) {
82
96
  const values = input.selectors.map((value) => String(value || '').trim()).filter(Boolean);
83
97
  if (values.length === 0)
@@ -91,11 +105,13 @@ function buildExecutionOrderPatch(input) {
91
105
  mode: 'ordered',
92
106
  targets: targets.map((target, index) => ({
93
107
  ...target,
94
- advanceOn: index < targets.length - 1
95
- ? advanceOn.length > 0
96
- ? advanceOn
97
- : ['offline', 'unavailable']
98
- : [],
108
+ advanceOn: input.strict
109
+ ? []
110
+ : index < targets.length - 1
111
+ ? advanceOn.length > 0
112
+ ? advanceOn
113
+ : [...exports.DEFAULT_ADVANCE_REASONS]
114
+ : [],
99
115
  })),
100
116
  },
101
117
  };
@@ -51,15 +51,54 @@ export interface Harness {
51
51
  claudeEngine?: 'cli' | 'sdk';
52
52
  lastSeenAt?: string;
53
53
  }
54
+ /**
55
+ * One open lease, as `/api/machines/leases` actually returns it.
56
+ *
57
+ * This shape was previously guessed — it declared `productId`, `runtimeId` and
58
+ * `expiresAt`, none of which the server has ever sent. Optional properties made
59
+ * that compile, so `leases list` rendered three permanently empty columns while
60
+ * `--json` showed the data plainly, and the one view built to tell a
61
+ * Hosted Machine lease from a Hosted Job Machine lease could not.
62
+ *
63
+ * So: only fields `toPublicMachineLeasePayload` emits belong here. There is no
64
+ * expiry on a lease — it lives until released — and no top-level runtime id.
65
+ */
54
66
  export interface HarnessLease {
55
67
  id?: string;
56
- leaseId?: string;
57
- productId?: string;
68
+ machineProduct?: string;
69
+ machineProductLabel?: string;
70
+ displayName?: string;
58
71
  state?: string;
59
- runtimeId?: string;
60
- workspaceId?: string | null;
72
+ stateReason?: string;
73
+ lifecycleSemantics?: string;
74
+ /**
75
+ * `active | paused | storage | none` — whether this lease is still costing
76
+ * money — plus the monthly spend status the server already computes.
77
+ *
78
+ * These six were allow-listed in `PUBLIC_RUNTIME_BILLING_KEYS` and attached
79
+ * by `attachMonthlySpendStatus` on every lease read, and the web renders
80
+ * them. Nothing in the CLI read them, so the number that actually refuses a
81
+ * Hosted Machine — the MONTHLY cap — was unreadable from here while
82
+ * `skrr balance show` reported a DAILY cap with full headroom. A user who
83
+ * correctly suspected billing could not confirm it (OSK-7920).
84
+ */
85
+ billing?: {
86
+ billableState?: string;
87
+ monthlySpendLimitCents?: number | null;
88
+ currentMonthSpendCents?: number;
89
+ openUsageEstimateCents?: number;
90
+ projectedCurrentMonthSpendCents?: number;
91
+ spendingLimitExceeded?: boolean;
92
+ spendStatusUnavailable?: boolean;
93
+ monthStartedAt?: string | null;
94
+ } & Record<string, unknown>;
95
+ leaseTimestamps?: Record<string, string>;
96
+ health?: {
97
+ assignable?: boolean;
98
+ reasons?: string[];
99
+ } & Record<string, unknown>;
61
100
  createdAt?: string;
62
- expiresAt?: string;
101
+ updatedAt?: string;
63
102
  }
64
103
  /** `pause | resume | destroy | terminate | health-check` (MachineLeaseService). */
65
104
  export declare const HARNESS_LEASE_ACTIONS: string[];
@@ -80,6 +119,32 @@ export declare function getHarnessUsage(id: string): Promise<unknown>;
80
119
  /** The rollup across every harness. The web calls this key `fleetUsage`. */
81
120
  export declare function getAllHarnessUsage(): Promise<unknown>;
82
121
  export type { QuotaAccountSummary, QuotaHistory };
122
+ /** One hosted product's monthly runtime spend cap and where this month stands. */
123
+ export type RuntimeMonthlySpend = {
124
+ machineProduct: string;
125
+ hasLimit: boolean;
126
+ exceeded?: boolean;
127
+ limitCents?: number;
128
+ currentSpendCents?: number;
129
+ openUsageEstimateCents?: number;
130
+ projectedCents?: number;
131
+ monthStartedAt?: string;
132
+ };
133
+ /**
134
+ * The monthly runtime spend cap, read before it refuses anything.
135
+ *
136
+ * Distinct from the balance's own spend cap, which is a per-period RATE LIMIT
137
+ * on credits. A user can have headroom on that one and still be refused by
138
+ * this one, which is exactly what happened: every number on `balance show`
139
+ * said there was room while a monthly hosted-runtime cap turned every turn
140
+ * away (OSK-7920).
141
+ */
142
+ export declare function getRuntimeMonthlySpend(): Promise<{
143
+ scope: {
144
+ teamId: string | null;
145
+ };
146
+ spend: RuntimeMonthlySpend[];
147
+ }>;
83
148
  /** The CLI subscription accounts this user's daemons have reported readings for. */
84
149
  export declare function listQuotaAccounts(): Promise<{
85
150
  accounts: QuotaAccountSummary[];
@@ -123,7 +188,18 @@ export declare function readHarnessPing(id: string, pingId: string): Promise<{
123
188
  latencyMs?: number;
124
189
  error?: string;
125
190
  }>;
126
- export declare function listHarnessLeases(): Promise<{
191
+ /**
192
+ * List leases. The server answers newest-first and caps at 50 unless asked
193
+ * otherwise (`Math.min(Math.max(Number(limit) || 50, 1), 100)`), so an account
194
+ * with more than fifty leases could not see past the newest fifty from here —
195
+ * including `lost` ones, which are terminal, invisible, and still billed for
196
+ * the whole window the platform had lost track of them.
197
+ */
198
+ export declare function listHarnessLeases(options?: {
199
+ limit?: number;
200
+ state?: string;
201
+ machineProduct?: string;
202
+ }): Promise<{
127
203
  leases?: HarnessLease[];
128
204
  }>;
129
205
  export declare function getHarnessLease(leaseId: string): Promise<{
@@ -7,6 +7,7 @@ exports.getHarnessModels = getHarnessModels;
7
7
  exports.listHarnessProducts = listHarnessProducts;
8
8
  exports.getHarnessUsage = getHarnessUsage;
9
9
  exports.getAllHarnessUsage = getAllHarnessUsage;
10
+ exports.getRuntimeMonthlySpend = getRuntimeMonthlySpend;
10
11
  exports.listQuotaAccounts = listQuotaAccounts;
11
12
  exports.getQuotaHistory = getQuotaHistory;
12
13
  exports.updateHarness = updateHarness;
@@ -71,6 +72,18 @@ async function getAllHarnessUsage() {
71
72
  const ownerWide = '?scope=all';
72
73
  return (0, api_fetch_1.apiFetch)('/api/harnesses/usage/aggregate' + ownerWide);
73
74
  }
75
+ /**
76
+ * The monthly runtime spend cap, read before it refuses anything.
77
+ *
78
+ * Distinct from the balance's own spend cap, which is a per-period RATE LIMIT
79
+ * on credits. A user can have headroom on that one and still be refused by
80
+ * this one, which is exactly what happened: every number on `balance show`
81
+ * said there was room while a monthly hosted-runtime cap turned every turn
82
+ * away (OSK-7920).
83
+ */
84
+ async function getRuntimeMonthlySpend() {
85
+ return (0, api_fetch_1.apiFetch)('/api/harnesses/spend');
86
+ }
74
87
  /** The CLI subscription accounts this user's daemons have reported readings for. */
75
88
  async function listQuotaAccounts() {
76
89
  return (0, api_fetch_1.apiFetch)('/api/harnesses/quota/accounts');
@@ -149,8 +162,23 @@ async function readHarnessPing(id, pingId) {
149
162
  return (0, api_fetch_1.apiFetch)(`/api/harnesses/${encodeURIComponent(id)}/ping/${encodeURIComponent(pingId)}`);
150
163
  }
151
164
  // ── Leases (hosted billing plane) ──────────────────────────────────────────
152
- async function listHarnessLeases() {
153
- return (0, api_fetch_1.apiFetch)('/api/machines/leases');
165
+ /**
166
+ * List leases. The server answers newest-first and caps at 50 unless asked
167
+ * otherwise (`Math.min(Math.max(Number(limit) || 50, 1), 100)`), so an account
168
+ * with more than fifty leases could not see past the newest fifty from here —
169
+ * including `lost` ones, which are terminal, invisible, and still billed for
170
+ * the whole window the platform had lost track of them.
171
+ */
172
+ async function listHarnessLeases(options = {}) {
173
+ const query = new URLSearchParams();
174
+ if (options.limit)
175
+ query.set('limit', String(options.limit));
176
+ if (options.state)
177
+ query.set('state', options.state);
178
+ if (options.machineProduct)
179
+ query.set('machineProduct', options.machineProduct);
180
+ const suffix = query.toString() ? `?${query.toString()}` : '';
181
+ return (0, api_fetch_1.apiFetch)(`/api/machines/leases${suffix}`);
154
182
  }
155
183
  async function getHarnessLease(leaseId) {
156
184
  return (0, api_fetch_1.apiFetch)(`/api/machines/leases/${encodeURIComponent(leaseId)}`);
@@ -237,6 +237,15 @@ async function waitForHostedMachineReady(id, options) {
237
237
  const detail = hostedMachineErrorSummary(session);
238
238
  throw new Error(`Hosted Machine ${id} reached ${status}${detail ? `: ${detail}` : ''}`);
239
239
  }
240
+ // Async resume deliberately rolls a recoverable failure back to `paused`
241
+ // so the retained workspace remains resumable. That is terminal for THIS
242
+ // wait operation even though it is not terminal for the machine. The
243
+ // resuming transition clears old error state, so an error-bearing paused
244
+ // row here belongs to the attempt we just started.
245
+ if (status === 'paused') {
246
+ const detail = hostedMachineErrorSummary(session);
247
+ throw new Error(`Hosted Machine ${id} returned to paused before it became ready${detail ? `: ${detail}` : ''}`);
248
+ }
240
249
  if (options.timeoutMs && Date.now() - startedAt >= options.timeoutMs) {
241
250
  throw new HostedMachineReadyTimeoutError(id, hostedMachineProgressSummary(session));
242
251
  }
@@ -4,7 +4,8 @@
4
4
  * Gap analysis §3.3 (b): *"50 个命令组中没有 `skrr inbox` / `listen` / `tail` /
5
5
  * `notify`,CLI 全是 pull"*. The audit's third use case wants an agent that
6
6
  * *"像真人一样时不时发两三条消息"* — and the reply half of that loop already
7
- * works (`skrr agents chat --conversation <id>` stamps `Message.intentId` and
7
+ * works (`skrr agents chat <agent-id> --conversation <id> --prompt "…"` stamps
8
+ * `Message.intentId` and
8
9
  * wakes the originating run). What was missing is the other direction: nothing
9
10
  * in the CLI ever told you the agent had said something.
10
11
  *
@@ -87,6 +87,23 @@ export interface Machine {
87
87
  canTerminate: boolean;
88
88
  };
89
89
  }
90
+ /**
91
+ * What a machine's state actually is, when `offline` is doing two jobs.
92
+ *
93
+ * A TERMINATED ephemeral sandbox — destroyed, unrecoverable, `canResume:
94
+ * false`, no lease — rendered `offline`, identically to a machine that is
95
+ * merely not connected right now. Those are opposite situations: one can come
96
+ * back and one never will, and the board's original target turned out to be the
97
+ * second while every surface said the first. The only way to learn the
98
+ * difference was to run `skrr machines hosted list` separately and match
99
+ * sandbox ids by hand (OSK-7639).
100
+ *
101
+ * `canResume` is the discriminator the payload already carries: a machine that
102
+ * could come back says so, and a destroyed one cannot.
103
+ */
104
+ export declare function machineStateLabel(machine: Machine): string;
105
+ /** Is this a machine a user could still use, or bring back? */
106
+ export declare function isReachableMachine(machine: Machine): boolean;
90
107
  /** The user-editable slice. Identities, capabilities and lifecycle are not. */
91
108
  export interface MachinePreferencesPatch {
92
109
  name?: string;
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.machineStateLabel = machineStateLabel;
4
+ exports.isReachableMachine = isReachableMachine;
3
5
  exports.liveHarnessCount = liveHarnessCount;
4
6
  exports.listMachines = listMachines;
5
7
  exports.getMachine = getMachine;
@@ -7,6 +9,33 @@ exports.updateMachine = updateMachine;
7
9
  exports.disconnectMachine = disconnectMachine;
8
10
  exports.resolveMachineRef = resolveMachineRef;
9
11
  const api_fetch_1 = require("./api-fetch");
12
+ /**
13
+ * What a machine's state actually is, when `offline` is doing two jobs.
14
+ *
15
+ * A TERMINATED ephemeral sandbox — destroyed, unrecoverable, `canResume:
16
+ * false`, no lease — rendered `offline`, identically to a machine that is
17
+ * merely not connected right now. Those are opposite situations: one can come
18
+ * back and one never will, and the board's original target turned out to be the
19
+ * second while every surface said the first. The only way to learn the
20
+ * difference was to run `skrr machines hosted list` separately and match
21
+ * sandbox ids by hand (OSK-7639).
22
+ *
23
+ * `canResume` is the discriminator the payload already carries: a machine that
24
+ * could come back says so, and a destroyed one cannot.
25
+ */
26
+ function machineStateLabel(machine) {
27
+ if (machine.status !== 'offline')
28
+ return machine.status;
29
+ const controls = machine.controls;
30
+ if (controls && controls.canResume === false && controls.canTerminate === false) {
31
+ return 'terminated';
32
+ }
33
+ return 'offline';
34
+ }
35
+ /** Is this a machine a user could still use, or bring back? */
36
+ function isReachableMachine(machine) {
37
+ return machineStateLabel(machine) !== 'terminated' && machine.status !== 'offline';
38
+ }
10
39
  /**
11
40
  * How many of a machine's harnesses still represent a capability it has.
12
41
  *