@skrr-ai/cli 0.1.86 → 0.1.88

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/bin/run.js +11 -0
  2. package/dist/base-command.js +1 -0
  3. package/dist/commands/agents/chat.d.ts +58 -1
  4. package/dist/commands/agents/chat.js +206 -4
  5. package/dist/commands/computer/index.d.ts +19 -0
  6. package/dist/commands/computer/index.js +37 -0
  7. package/dist/commands/labels/create.d.ts +1 -0
  8. package/dist/commands/labels/create.js +2 -0
  9. package/dist/commands/labels/restore.d.ts +1 -0
  10. package/dist/commands/labels/restore.js +6 -3
  11. package/dist/commands/labels/update.d.ts +1 -0
  12. package/dist/commands/labels/update.js +2 -0
  13. package/dist/commands/machines/computer-adoption.d.ts +15 -0
  14. package/dist/commands/machines/computer-adoption.js +33 -0
  15. package/dist/commands/machines/dedicated/archive.d.ts +26 -0
  16. package/dist/commands/machines/dedicated/archive.js +113 -0
  17. package/dist/commands/machines/dedicated/audit.d.ts +25 -0
  18. package/dist/commands/machines/dedicated/audit.js +113 -0
  19. package/dist/commands/machines/dedicated/computer.d.ts +23 -0
  20. package/dist/commands/machines/dedicated/computer.js +64 -0
  21. package/dist/commands/machines/dedicated/cp.d.ts +2 -0
  22. package/dist/commands/machines/dedicated/cp.js +74 -2
  23. package/dist/commands/machines/dedicated/git-status.d.ts +15 -0
  24. package/dist/commands/machines/dedicated/git-status.js +56 -0
  25. package/dist/commands/machines/dedicated/index.js +15 -1
  26. package/dist/commands/machines/dedicated/ls.d.ts +27 -0
  27. package/dist/commands/machines/dedicated/ls.js +100 -0
  28. package/dist/commands/machines/dedicated/mkdir.d.ts +18 -0
  29. package/dist/commands/machines/dedicated/mkdir.js +57 -0
  30. package/dist/commands/machines/dedicated/mv.d.ts +24 -0
  31. package/dist/commands/machines/dedicated/mv.js +83 -0
  32. package/dist/commands/machines/dedicated/rm.d.ts +26 -0
  33. package/dist/commands/machines/dedicated/rm.js +100 -0
  34. package/dist/commands/machines/dedicated/search.d.ts +24 -0
  35. package/dist/commands/machines/dedicated/search.js +78 -0
  36. package/dist/commands/machines/dedicated/stat.d.ts +22 -0
  37. package/dist/commands/machines/dedicated/stat.js +92 -0
  38. package/dist/commands/machines/hosted/index.js +1 -1
  39. package/dist/commands/machines/hosted/list.js +1 -1
  40. package/dist/commands/machines/hosted/start.js +1 -1
  41. package/dist/commands/machines/services/declare.d.ts +24 -0
  42. package/dist/commands/machines/services/declare.js +73 -0
  43. package/dist/commands/machines/services/index.d.ts +15 -0
  44. package/dist/commands/machines/services/index.js +31 -0
  45. package/dist/commands/machines/services/ls.d.ts +17 -0
  46. package/dist/commands/machines/services/ls.js +60 -0
  47. package/dist/commands/machines/services/withdraw.d.ts +19 -0
  48. package/dist/commands/machines/services/withdraw.js +47 -0
  49. package/dist/commands/machines/share.d.ts +29 -0
  50. package/dist/commands/machines/share.js +100 -0
  51. package/dist/commands/machines/shared.d.ts +19 -0
  52. package/dist/commands/machines/shared.js +62 -0
  53. package/dist/commands/machines/shares.d.ts +16 -0
  54. package/dist/commands/machines/shares.js +69 -0
  55. package/dist/commands/machines/unshare.d.ts +19 -0
  56. package/dist/commands/machines/unshare.js +61 -0
  57. package/dist/commands/tasks/labels/create.d.ts +1 -0
  58. package/dist/commands/tasks/labels/create.js +3 -0
  59. package/dist/commands/views/create.d.ts +1 -0
  60. package/dist/commands/views/create.js +5 -0
  61. package/dist/lib/agent-home-workspace.d.ts +26 -0
  62. package/dist/lib/agent-home-workspace.js +174 -0
  63. package/dist/lib/agentic-stream.d.ts +135 -0
  64. package/dist/lib/agentic-stream.js +341 -6
  65. package/dist/lib/auth-storage.d.ts +4 -0
  66. package/dist/lib/auth-storage.js +33 -0
  67. package/dist/lib/computer-adoption.d.ts +55 -0
  68. package/dist/lib/computer-adoption.js +67 -0
  69. package/dist/lib/computer-consent.d.ts +13 -0
  70. package/dist/lib/computer-consent.js +33 -0
  71. package/dist/lib/computer-files.d.ts +239 -0
  72. package/dist/lib/computer-files.js +707 -0
  73. package/dist/lib/dedicated-copy.d.ts +45 -9
  74. package/dist/lib/dedicated-copy.js +141 -44
  75. package/dist/lib/dedicated-machines.d.ts +49 -0
  76. package/dist/lib/dedicated-machines.js +97 -6
  77. package/dist/lib/keychain.d.ts +1 -0
  78. package/dist/lib/keychain.js +67 -14
  79. package/dist/lib/label-scope.d.ts +12 -0
  80. package/dist/lib/label-scope.js +15 -1
  81. package/dist/lib/machine-audit.d.ts +34 -0
  82. package/dist/lib/machine-audit.js +50 -0
  83. package/dist/lib/machine-grants.d.ts +70 -0
  84. package/dist/lib/machine-grants.js +47 -0
  85. package/dist/lib/machine-services.d.ts +64 -0
  86. package/dist/lib/machine-services.js +60 -0
  87. package/dist/lib/refresh.d.ts +7 -0
  88. package/dist/lib/refresh.js +2 -1
  89. package/dist/lib/task-view-render.d.ts +2 -0
  90. package/dist/lib/task-view-render.js +1 -1
  91. package/dist/lib/views/vocabulary.d.ts +1 -1
  92. package/dist/lib/views/vocabulary.js +2 -1
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +955 -0
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +1070 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +7 -0
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +955 -0
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +1051 -0
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +7 -0
  99. package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
  100. package/dist/node_modules/@skrr-ai/data-provider/index.js +22143 -20728
  101. package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
  102. package/oclif.manifest.json +18855 -17260
  103. package/package.json +2 -2
@@ -0,0 +1,1051 @@
1
+ /**
2
+ * computerWire.ts — the ONE declaration of every name the computer protocol
3
+ * puts on the wire, and the pure rules the daemon applies to them (design
4
+ * docs/architecture/dedicated-runtime-computer-2026-10-01.md §4, §7.1).
5
+ *
6
+ * The daemon is published and cannot depend on `@skrr-ai/data-provider`, which
7
+ * is private. Without this module it would declare its own copy of the event
8
+ * names and refusal codes, and two hand-typed copies of a wire contract drift
9
+ * in exactly the way neither copy's tests can see (OSK-8588). So the names live
10
+ * here, in the published package, the way `firstPartyHarness.ts` does for the
11
+ * harness; `@skrr-ai/data-provider` re-exports this module and builds its zod
12
+ * schemas over these constants (`z.enum(COMPUTER_REFUSAL_CODES)`), so each name
13
+ * is still declared exactly once. `packages/data-provider/specs/computerWireBoundary.spec.ts`
14
+ * fails on a `computer:*` event name or a refusal-code table declared anywhere
15
+ * else.
16
+ *
17
+ * Pure TypeScript: no zod, no Node-only imports, so the browser, React Native
18
+ * and a bun-compiled daemon all load it. Most payload SHAPES are not here — they
19
+ * are zod schemas in data-provider; the daemon re-validates every field it
20
+ * reads. The exceptions are shapes the DAEMON produces and the API re-validates
21
+ * (activity details, the browser key input): those are declared here as plain
22
+ * interfaces, and data-provider's zod schemas are checked against them at
23
+ * compile time, because a producer and a validator that each spell the shape
24
+ * dropped every command event for a release (OSK-13605).
25
+ */
26
+ // ---------------------------------------------------------------------------
27
+ // Events (§7.1)
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * Client ⇄ API, on the existing `/ws/agentic` socket. A request the client
31
+ * needs answered is acknowledged through the Socket.IO acknowledgement
32
+ * callback.
33
+ */
34
+ export const COMPUTER_EVENTS = {
35
+ /** client → API: attach to a machine. */
36
+ attach: 'computer:attach',
37
+ /** client → API: leave. */
38
+ detach: 'computer:detach',
39
+ /** API → client: this attachment was detached, with the reason (revoked, stale generation, …). */
40
+ detached: 'computer:detached',
41
+ /** API → client: a whole manifest, or a revisioned delta. */
42
+ manifest: 'computer:manifest',
43
+ /** client → API: observe one surface. */
44
+ subscribe: 'computer:surface:subscribe',
45
+ /** client → API: stop observing one surface. */
46
+ unsubscribe: 'computer:surface:unsubscribe',
47
+ /** API → client: terminal bytes with a byte cursor, or a page frame. */
48
+ data: 'computer:surface:data',
49
+ /**
50
+ * client → API: this viewer painted the page frame `seq` and wants the next.
51
+ * A viewer's slot is latest-wins: until it acknowledges, newer frames
52
+ * replace the one waiting for it and nothing queues (§6.2, §7.2 R3).
53
+ * Never acknowledged.
54
+ */
55
+ frameAck: 'computer:surface:frame:ack',
56
+ /** client → API: take / release / request / grant / decline. */
57
+ control: 'computer:surface:control',
58
+ /** client → API: share or unshare one of the person's own surfaces with an agent session. */
59
+ share: 'computer:surface:share',
60
+ /** client → API: one input, carrying the lease epoch. Never acknowledged. */
61
+ input: 'computer:surface:input',
62
+ /** client → API: open a person's own terminal or browser window. */
63
+ open: 'computer:surface:open',
64
+ /** client → API: close a surface. */
65
+ close: 'computer:surface:close',
66
+ /** API → client: activity events. */
67
+ activity: 'computer:activity',
68
+ /** client → API: the activity events after a `seq`, to fill a gap. */
69
+ activityFill: 'computer:activity:fill',
70
+ /** client → API: the directories expanded and files open, which scope the file watches. */
71
+ filesWatch: 'computer:files:watch',
72
+ /**
73
+ * API → client: what this attachment's scoped watches saw — paths and kinds,
74
+ * never contents — or that they went stale and the tree must be refreshed
75
+ * (§6.3 layer 2). Needs `computer_files_watch_v1`.
76
+ */
77
+ filesChanged: 'computer:files:changed',
78
+ /**
79
+ * client → API: answer a dialog the page is blocked on. Only the attachment
80
+ * holding the browser's lease, at the current epoch. Needs
81
+ * `computer_browser_page_v1`.
82
+ */
83
+ dialog: 'computer:surface:dialog',
84
+ /**
85
+ * client → API: copy-out — the text the person selected on the page, answered
86
+ * to the asking attachment alone and only while it holds the lease at the
87
+ * current epoch. Needs `computer_browser_page_v1`.
88
+ */
89
+ copy: 'computer:surface:copy',
90
+ /**
91
+ * client → API: answer the file chooser a page opened (§6.2 "a file chooser
92
+ * opens a picker over the computer's files, or a local upload"): paths on the
93
+ * computer, or none to cancel. Only the attachment holding the browser's
94
+ * lease, at the current epoch; the paths are read as the file surface reads
95
+ * them. Needs `computer_browser_file_chooser_v1`.
96
+ */
97
+ fileChooser: 'computer:surface:filechooser',
98
+ };
99
+ /**
100
+ * API ⇄ daemon. The daemon transports (Socket.IO and SSE) carry no
101
+ * acknowledgement callback, so a request the API needs answered carries a
102
+ * `requestId` and the daemon answers with `daemon:computer:ack`.
103
+ */
104
+ export const DAEMON_COMPUTER_EVENTS = {
105
+ // API → daemon
106
+ /** Bind an attachment: its principal, operations and (dedicated) generation. */
107
+ attach: 'daemon:computer:attach',
108
+ detach: 'daemon:computer:detach',
109
+ /** The machine's grant set, whole, on registration and on every change. */
110
+ grants: 'daemon:computer:grants',
111
+ subscribe: 'daemon:computer:surface:subscribe',
112
+ unsubscribe: 'daemon:computer:surface:unsubscribe',
113
+ frameAck: 'daemon:computer:surface:frame:ack',
114
+ control: 'daemon:computer:surface:control',
115
+ share: 'daemon:computer:surface:share',
116
+ input: 'daemon:computer:surface:input',
117
+ open: 'daemon:computer:surface:open',
118
+ close: 'daemon:computer:surface:close',
119
+ activityFill: 'daemon:computer:activity:fill',
120
+ filesWatch: 'daemon:computer:files:watch',
121
+ /** One file operation, relayed from `/api/machines/:machineId/files/<op>`. */
122
+ files: 'daemon:computer:files',
123
+ dialog: 'daemon:computer:surface:dialog',
124
+ copy: 'daemon:computer:surface:copy',
125
+ fileChooser: 'daemon:computer:surface:filechooser',
126
+ /**
127
+ * Pin or unpin the surface an open human-input request names (§8): a pinned
128
+ * surface is not reclaimed by idle or wall-clock expiry. Fire-and-forget,
129
+ * idempotent on the pin id, bounded by its own `until`. Needs
130
+ * `computer_surface_pin_v1`.
131
+ */
132
+ pin: 'daemon:computer:surface:pin',
133
+ /**
134
+ * Declare, withdraw or list the machine's declared services (§20.5): a web
135
+ * service someone started on the machine, named and opened in the
136
+ * computer's own browser. Carries a `requestId`, the principal the API
137
+ * authorized and, for a declaration, who declared it; the daemon checks the
138
+ * principal against the grant set it holds. Needs
139
+ * `computer_declared_services_v1`.
140
+ */
141
+ services: 'daemon:computer:services',
142
+ // daemon → API
143
+ /** The answer to any request that carried a `requestId`. */
144
+ ack: 'daemon:computer:ack',
145
+ manifest: 'daemon:computer:manifest',
146
+ data: 'daemon:computer:surface:data',
147
+ activity: 'daemon:computer:activity',
148
+ /** The daemon detached an attachment itself, e.g. its grant was revoked. */
149
+ detached: 'daemon:computer:detached',
150
+ /** One attachment's scoped watches saw changes, or went stale (§6.3). */
151
+ filesChanged: 'daemon:computer:files:changed',
152
+ /**
153
+ * The daemon's own frame-lane counters (§7.2, OSK-13616), every
154
+ * `COMPUTER_DIAGNOSTICS_INTERVAL_MS` while anyone is attached: counters and
155
+ * per-viewer timestamps from a closed vocabulary, never content. Needs
156
+ * `computer_diagnostics_v1`. Fire-and-forget; a lost report is the next one.
157
+ */
158
+ diagnostics: 'daemon:computer:diagnostics',
159
+ };
160
+ export const COMPUTER_EVENT_NAMES = Object.values(COMPUTER_EVENTS);
161
+ export const DAEMON_COMPUTER_EVENT_NAMES = Object.values(DAEMON_COMPUTER_EVENTS);
162
+ /** The daemon → API events; every other `daemon:computer:*` event flows API → daemon. */
163
+ export const DAEMON_COMPUTER_REPORT_EVENT_KEYS = [
164
+ 'ack',
165
+ 'manifest',
166
+ 'data',
167
+ 'activity',
168
+ 'detached',
169
+ 'filesChanged',
170
+ 'diagnostics',
171
+ ];
172
+ /** The API → client events; every other `computer:*` event flows client → API. */
173
+ export const COMPUTER_SERVER_EVENT_KEYS = [
174
+ 'detached',
175
+ 'manifest',
176
+ 'data',
177
+ 'activity',
178
+ 'filesChanged',
179
+ ];
180
+ // ---------------------------------------------------------------------------
181
+ // Capabilities (§7.1)
182
+ // ---------------------------------------------------------------------------
183
+ /**
184
+ * One capability per wire addition, each withheld positively at dispatch: the
185
+ * API never sends a computer event to a daemon that did not announce the
186
+ * capability that event belongs to. Activity is part of `computer_v1`.
187
+ */
188
+ export const COMPUTER_CAPABILITIES = {
189
+ /** Manifest, surfaces, control leases, terminal surfaces, activity core. */
190
+ computer: 'computer_v1',
191
+ /** The grant set and the daemon's own check of every attachment against it (§9). */
192
+ access: 'computer_access_v1',
193
+ /** The file surface: `daemon:computer:files` and `computer:files:watch` (§6.3). */
194
+ files: 'computer_files_v1',
195
+ /**
196
+ * Filename and content search and git status decorations over the tenant
197
+ * roots, as the workload (§6.3 "Later, same contract"): the `FileSearch` and
198
+ * `FileGitStatus` tools. A daemon without it is never asked.
199
+ */
200
+ filesSearch: 'computer_files_search_v1',
201
+ /**
202
+ * File mutations fenced on what the person saw (review CW-01, CW-02 and the
203
+ * folder-delete finding): an upload commits create-only (`exists` when the
204
+ * name was claimed) or over exactly the version chosen to be replaced
205
+ * (`replaceTag`, else `file_changed`); a committed transfer leaves a receipt,
206
+ * so a resume after a lost final acknowledgement answers "already
207
+ * committed"; and `stat` with `contents: true` gives a folder a contents tag that
208
+ * a recursive delete presents. A daemon without it would ignore those
209
+ * conditions, so the API refuses them `capability_missing` instead of
210
+ * sending them.
211
+ */
212
+ filesFenced: 'computer_files_fenced_v1',
213
+ /** Observe attachments on a browser surface, separate from control (§6.2). */
214
+ browserObserve: 'computer_browser_observe_v1',
215
+ /** A browser surface's tab strip and tab input (§6.2). */
216
+ browserTabs: 'computer_browser_tabs_v1',
217
+ /**
218
+ * The rest of a real browser on a browser surface (§6.2): its pending
219
+ * dialogs and its downloads on the descriptor, answering a dialog
220
+ * (`computer:surface:dialog`) and copy-out (`computer:surface:copy`).
221
+ * Permission prompts are not part of it: Chromium exposes no CDP event for
222
+ * them (headless refuses them), so no surface may claim to show one.
223
+ */
224
+ browserPage: 'computer_browser_page_v1',
225
+ /** Sharing a person's own surface with an agent session (§5.4). */
226
+ surfaceShare: 'computer_surface_share_v1',
227
+ /** Pinning a surface an open human-input request names: `daemon:computer:surface:pin` (§8). */
228
+ surfacePin: 'computer_surface_pin_v1',
229
+ /**
230
+ * "Reset browser" — sign out everywhere (§6.2, OSK-13499): the
231
+ * `reset_browser` control action on a browser surface. Announced only by a
232
+ * daemon whose browser keeps one profile across sessions (a Dedicated
233
+ * Runtime guest); a personal machine's browser keeps nothing to reset.
234
+ */
235
+ browserReset: 'computer_browser_reset_v1',
236
+ /**
237
+ * Scoped file watches (§6.3 layer 2): `computer:files:watch` is served and
238
+ * `computer:files:changed` reports. Announced only where the daemon runs a
239
+ * watcher it can stand behind; without it the file tree says it does not
240
+ * update by itself and offers a refresh.
241
+ */
242
+ filesWatch: 'computer_files_watch_v1',
243
+ /**
244
+ * The file chooser a page opens while a person holds the browser (§6.2,
245
+ * OSK-13481): the pending chooser on the descriptor (`fileChooser`) and
246
+ * answering it with files on the computer (`computer:surface:file-chooser`).
247
+ * Announced only by a daemon whose browser intercepts the chooser.
248
+ */
249
+ browserFileChooser: 'computer_browser_file_chooser_v1',
250
+ /**
251
+ * A person's own terminal opens in a folder (§6.3 "Open a terminal here",
252
+ * OSK-13615): `computer:surface:open` carries `cwd`, which the daemon checks
253
+ * the way the file surface would (the tenant roots as the workload; on a
254
+ * personal machine the deny set, `blockedPaths` and home) and refuses typed
255
+ * when it may not. Without it the client types `cd` once it holds control.
256
+ */
257
+ terminalCwd: 'computer_terminal_cwd_v1',
258
+ /**
259
+ * A run in an agent's interactive shell (`terminal.run`, tool `Terminal`)
260
+ * says where in its terminal's byte stream it began: `streamOffset` on its
261
+ * `command` activity, the terminal lane's cursor at that moment (§6.1, task
262
+ * 5-04b, OSK-13614). A client places such a block inline at that point of
263
+ * the terminal; without the capability it keeps every block in the list
264
+ * below the terminal, as before.
265
+ */
266
+ terminalCommandOffset: 'computer_terminal_command_offset_v1',
267
+ /**
268
+ * The daemon reports its frame-lane counters (`daemon:computer:diagnostics`,
269
+ * OSK-13616), so the API's own logs can tell "the daemon stopped sending"
270
+ * from "the page did not change". Announced by the relay that sends them;
271
+ * the API asks nothing of a daemon without it, and reads its silence as
272
+ * "unreported", never as healthy.
273
+ */
274
+ diagnostics: 'computer_diagnostics_v1',
275
+ /**
276
+ * Declared services (§20.5, 9-17, OSK-13579): the manifest carries
277
+ * `services`, the web services an agent or a person declared on this
278
+ * machine, and `daemon:computer:services` (and the agent's
279
+ * `computer.services` tool call) declares, withdraws and lists them. A
280
+ * service is a localhost port on the machine, opened in the computer's own
281
+ * browser; nothing is proxied to it. Announced by the relay that serves it.
282
+ */
283
+ declaredServices: 'computer_declared_services_v1',
284
+ };
285
+ export const COMPUTER_CAPABILITY_VALUES = Object.values(COMPUTER_CAPABILITIES);
286
+ // ---------------------------------------------------------------------------
287
+ // Refusals (§5.2, §6.3, §7.2 R4/R8, §9, §18)
288
+ // ---------------------------------------------------------------------------
289
+ /**
290
+ * Every refusal and limit the computer protocol can answer with. Callers branch
291
+ * on the code; a message beside it is free text and may change.
292
+ */
293
+ export const COMPUTER_REFUSAL_CODES = [
294
+ /** A person holds the surface; the agent may wait, do other work, or ask. */
295
+ 'human_control',
296
+ /** The input was issued under a lease epoch that is no longer current. */
297
+ 'stale_epoch',
298
+ /** The attachment is bound to a Dedicated Runtime generation that has been replaced. */
299
+ 'stale_generation',
300
+ /** The file changed since it was read; the refusal carries the current tag. */
301
+ 'file_changed',
302
+ /** The machine's daemon did not announce the capability this needs. */
303
+ 'capability_missing',
304
+ /** The principal may not perform this operation on this machine. */
305
+ 'unauthorized',
306
+ /** Control cannot move this way, e.g. an agent asking to take from a person. */
307
+ 'control_denied',
308
+ /** The attachment does not hold the lease it is acting under. */
309
+ 'not_holder',
310
+ 'attachment_limit',
311
+ 'observer_limit',
312
+ 'watch_limit',
313
+ 'grant_limit',
314
+ /** The machine runs as many browsers as it allows; a person's own window waits for one to close. */
315
+ 'browser_limit',
316
+ 'rate_limited',
317
+ /** The machine, surface, tab or request does not exist. */
318
+ 'not_found',
319
+ /** The surface has ended. */
320
+ 'surface_ended',
321
+ /** The machine is not running (§10 decides the picture). */
322
+ 'machine_unavailable',
323
+ /** The machine's daemon is not answering. */
324
+ 'daemon_unreachable',
325
+ /** A bounded wait ran out (§7.2 R4). */
326
+ 'timeout',
327
+ 'invalid_request',
328
+ /** A personal machine has not allowed this part of the computer (§18, D11). */
329
+ 'consent_missing',
330
+ /** The path is withheld by the daemon's deny set, `blockedPaths` or a sensitive pattern (§18). */
331
+ 'path_withheld',
332
+ /** The file is over the size the editor or the read accepts. */
333
+ 'too_large',
334
+ /** The machine has as many declared services as it allows (`COMPUTER_DECLARED_SERVICES_MAX`). */
335
+ 'service_limit',
336
+ ];
337
+ /**
338
+ * Older spellings the daemon boundary accepts for one release (§5.2). After
339
+ * that release this map is emptied, and nothing else names the old spelling.
340
+ */
341
+ export const COMPUTER_LEGACY_REFUSAL_SPELLINGS = Object.freeze({
342
+ live_view_active: 'human_control',
343
+ });
344
+ /** The canonical code for a received spelling, or `null` when it is not a computer refusal. */
345
+ export function normalizeComputerRefusalCode(code) {
346
+ if (typeof code !== 'string') {
347
+ return null;
348
+ }
349
+ if (COMPUTER_REFUSAL_CODES.includes(code)) {
350
+ return code;
351
+ }
352
+ return Object.prototype.hasOwnProperty.call(COMPUTER_LEGACY_REFUSAL_SPELLINGS, code)
353
+ ? COMPUTER_LEGACY_REFUSAL_SPELLINGS[code]
354
+ : null;
355
+ }
356
+ // ---------------------------------------------------------------------------
357
+ // Actors, operations and grant tiers (§4.2, §9, D10)
358
+ // ---------------------------------------------------------------------------
359
+ export const COMPUTER_ACTOR_KINDS = ['agent', 'human'];
360
+ /**
361
+ * Who an activity event is attributed to. `system` is what happened with no
362
+ * actor behind it — a process exited, the machine stopped — and appears ONLY
363
+ * on activity: it never holds a lease, is never present, is never audited as
364
+ * an actor. Without it on the wire every lifecycle event was dropped by the
365
+ * API and a client saw a `seq` hole no fill could close.
366
+ */
367
+ export const COMPUTER_ACTIVITY_ACTOR_KINDS = [...COMPUTER_ACTOR_KINDS, 'system'];
368
+ /**
369
+ * How a person present on the machine reached it: as its owner, or through a
370
+ * grant at one of the two tiers. Agents carry no access level — an agent
371
+ * session acts with its own run's authority, not a grant.
372
+ */
373
+ export const COMPUTER_ACCESS_LEVELS = ['owner', 'watch', 'operate'];
374
+ /**
375
+ * What a principal may do on a computer. Kept separate so a refusal says
376
+ * exactly what was refused. `power`, closing other people's surfaces and
377
+ * managing grants stay with the owner; no tier contains `power`.
378
+ */
379
+ export const COMPUTER_OPERATIONS = [
380
+ 'observe',
381
+ 'control',
382
+ 'files.read',
383
+ 'files.write',
384
+ 'open',
385
+ 'power',
386
+ ];
387
+ /** A grant is one of two fixed tiers: a finer grant cannot be enforced on a shell. */
388
+ export const COMPUTER_GRANT_TIERS = ['watch', 'operate'];
389
+ const TIER_OPERATIONS = Object.freeze({
390
+ /** See every surface on the machine and its activity. No input, no file browsing. */
391
+ watch: Object.freeze(['observe']),
392
+ /** The machine's account in another person's hands. */
393
+ operate: Object.freeze(['observe', 'control', 'files.read', 'files.write', 'open']),
394
+ });
395
+ /** The fixed set of operations a grant tier carries. */
396
+ export function computerTierOperations(tier) {
397
+ return TIER_OPERATIONS[tier];
398
+ }
399
+ /** Whether a grant tier carries `operation`. */
400
+ export function computerOperationAllowed(tier, operation) {
401
+ return TIER_OPERATIONS[tier].includes(operation);
402
+ }
403
+ /** The operations an access level carries: the owner holds every one. */
404
+ export function computerAccessOperations(level) {
405
+ return level === 'owner' ? COMPUTER_OPERATIONS : TIER_OPERATIONS[level];
406
+ }
407
+ // ---------------------------------------------------------------------------
408
+ // Control leases (§4.3, §5)
409
+ // ---------------------------------------------------------------------------
410
+ /** A person releasing control may leave a note for the agent (§8). */
411
+ export const COMPUTER_HANDBACK_NOTE_MAX_LENGTH = 2000;
412
+ /** A person holding control is released after this long without human input (§5.1 rule 4). */
413
+ export const COMPUTER_CONTROL_IDLE_MS = 10 * 60 * 1000;
414
+ /** How long before idle reclaim of a person's hold the daemon says it is coming (§8). */
415
+ export const COMPUTER_RECLAIM_WARNING_MS = 60 * 1000;
416
+ /** A transport blip shorter than this changes no lease (§7.2 R6). */
417
+ export const COMPUTER_RECONNECT_GRACE_MS = 10 * 1000;
418
+ /** An agent terminal write waits this long for a person to hand back before refusing (§5.2). */
419
+ export const COMPUTER_AGENT_WRITE_WAIT_MS = 60 * 1000;
420
+ /** What a person can do to a surface's lease (§5.1, §5.3). */
421
+ export const COMPUTER_CONTROL_ACTIONS = [
422
+ /** A person takes an agent-held surface. Human priority over the agent is unconditional. */
423
+ 'take',
424
+ /** The holder hands back, optionally with a note. */
425
+ 'release',
426
+ /** Ask a PERSON who holds the surface for it; leaves the lease untouched. */
427
+ 'request',
428
+ /** The holding person gives the surface to the person who requested it. */
429
+ 'grant',
430
+ /** The holding person declines a request. Changes nothing. */
431
+ 'decline',
432
+ /**
433
+ * The person who stopped driving a browser without handing it back says the
434
+ * agent may see the page again (D12). Changes no holder.
435
+ */
436
+ 'reveal',
437
+ /**
438
+ * Sign out everywhere (§6.2, OSK-13499): end every browser window on the
439
+ * computer — the agent's and the person's, which share one cookie jar —
440
+ * stop the browser and remove its profile. On a browser surface, by the
441
+ * owner or the person holding it; never an observer. Needs
442
+ * `computer_browser_reset_v1`. Answers with the asking surface's last lease.
443
+ */
444
+ 'reset_browser',
445
+ ];
446
+ /** Sharing one of a person's own surfaces with one agent session (§5.4). */
447
+ export const COMPUTER_SHARE_ACTIONS = ['share', 'unshare'];
448
+ export const COMPUTER_SHARE_MODES = ['observe', 'control'];
449
+ /** `daemon:computer:surface:pin` actions (§8). */
450
+ export const COMPUTER_SURFACE_PIN_ACTIONS = ['pin', 'unpin'];
451
+ /** Why a pin ended: the request it held the surface for left `open`. */
452
+ export const COMPUTER_SURFACE_UNPIN_REASONS = ['answered', 'dismissed', 'expired'];
453
+ /** Why a lease changed holder. Carried by `control` activity events and the audit record. */
454
+ export const COMPUTER_CONTROL_CHANGE_REASONS = [
455
+ 'take',
456
+ 'release',
457
+ 'grant',
458
+ /** No human input for `COMPUTER_CONTROL_IDLE_MS`. */
459
+ 'idle',
460
+ /** The holding client stayed disconnected past the reconnect grace. */
461
+ 'disconnect',
462
+ /** A person's surface was shared with an agent session for control. */
463
+ 'share',
464
+ /** The share ended: unshared, or the agent session ended. */
465
+ 'unshare',
466
+ /** The holder's grant was revoked. */
467
+ 'revoked',
468
+ 'machine_stopped',
469
+ ];
470
+ /**
471
+ * What a `control` activity event reports: a change of holder (one of
472
+ * `COMPUTER_CONTROL_CHANGE_REASONS`), a person-to-person request's lifecycle,
473
+ * or a reveal (D12). Only the first changes the holder or the epoch's owner;
474
+ * {@link computerControlChangeIsHolderChange} tells them apart.
475
+ */
476
+ export const COMPUTER_CONTROL_ACTIVITY_CHANGES = [
477
+ ...COMPUTER_CONTROL_CHANGE_REASONS,
478
+ /** A person asked the holding person for the surface. */
479
+ 'request',
480
+ 'request_declined',
481
+ 'request_expired',
482
+ 'request_withdrawn',
483
+ /** The page is visible to the agent again; no holder changed. */
484
+ 'revealed',
485
+ ];
486
+ /** Whether a `control` activity change moved the lease to a new holder. */
487
+ export function computerControlChangeIsHolderChange(change) {
488
+ return COMPUTER_CONTROL_CHANGE_REASONS.includes(change);
489
+ }
490
+ /**
491
+ * Why an input from `attachmentId`, issued under `epoch`, must be dropped — or
492
+ * `null` when the lease accepts it.
493
+ *
494
+ * The epoch is checked first: an input issued under an epoch that is no longer
495
+ * current is the in-flight keystroke the fence exists for, whoever sent it.
496
+ * Only a current-epoch input from an attachment that does not hold the lease
497
+ * is `not_holder`. An agent-held lease accepts no attachment's input.
498
+ */
499
+ export function computerLeaseInputRefusal(lease, attachmentId, epoch) {
500
+ if (lease.epoch !== epoch) {
501
+ return 'stale_epoch';
502
+ }
503
+ if (lease.holder.kind !== 'human' || !lease.holderAttachmentId) {
504
+ return 'not_holder';
505
+ }
506
+ return lease.holderAttachmentId === attachmentId ? null : 'not_holder';
507
+ }
508
+ /** Whether the lease accepts an input from `attachmentId` issued under `epoch`. */
509
+ export function computerLeaseAcceptsInput(lease, attachmentId, epoch) {
510
+ return computerLeaseInputRefusal(lease, attachmentId, epoch) === null;
511
+ }
512
+ // ---------------------------------------------------------------------------
513
+ // Surfaces and lanes (§4.2, §6.1, §6.2, §7.2 R3/R7)
514
+ // ---------------------------------------------------------------------------
515
+ /** Files are a namespace, not a leased surface (§6.3). */
516
+ export const COMPUTER_SURFACE_KINDS = ['terminal', 'browser'];
517
+ export const COMPUTER_SURFACE_STATES = ['live', 'ended'];
518
+ /**
519
+ * Why a surface ended (§10). An ended surface stays as a tombstone with its
520
+ * history until dismissed, and its reason is shown, never swallowed.
521
+ */
522
+ export const COMPUTER_SURFACE_END_REASONS = [
523
+ 'machine_stopped',
524
+ /** The machine's owner closed a surface someone else opened (§5.1 rule 8). */
525
+ 'closed_by_owner',
526
+ /** Whoever opened the surface closed it. */
527
+ 'closed',
528
+ 'process_exited',
529
+ 'daemon_restarted',
530
+ 'browser_crashed',
531
+ /** The browser session was reclaimed by idle or wall-clock expiry. */
532
+ 'expired',
533
+ /** Someone signed the computer's browser out everywhere, which ends every browser window. */
534
+ 'browser_reset',
535
+ ];
536
+ /** The browser's fixed logical viewport, whoever is looking (§6.2). */
537
+ export const COMPUTER_BROWSER_VIEWPORT = Object.freeze({ width: 1280, height: 800 });
538
+ /** One frame never exceeds this, so it fits the SSE transport every guest uses (§7.2 R7). */
539
+ export const COMPUTER_FRAME_MAX_BYTES = 1024 * 1024;
540
+ /**
541
+ * A viewer's frame slot frees itself after this long without the viewer's
542
+ * acknowledgement. The relay cannot promise delivery of the acknowledgement
543
+ * either, and a lost one must not freeze a viewer that is still painting; a
544
+ * viewer that really stalled still gets at most one frame per bound, never a
545
+ * backlog.
546
+ */
547
+ export const COMPUTER_FRAME_ACK_TIMEOUT_MS = 5_000;
548
+ /**
549
+ * The daemon's frame-lane counters (`daemon:computer:diagnostics`, OSK-13616).
550
+ * Cumulative since `since`, so a lost report loses nothing but its moment.
551
+ */
552
+ export const COMPUTER_DIAGNOSTIC_COUNTERS = [
553
+ /** The browser handed the lane a frame for a live viewer: the page changed. */
554
+ 'frames_produced',
555
+ /** A browser frame handed to the transport for a viewer. */
556
+ 'frames_sent',
557
+ /** The transport was down when a frame was produced; the live view keeps it for its resume. */
558
+ 'frames_held_disconnected',
559
+ /** A frame larger than the smallest transport, dropped at the source (R7). */
560
+ 'frames_dropped_oversize',
561
+ /** The transport refused a frame; the next one is the newest anyway. */
562
+ 'frames_send_failed',
563
+ /** A viewer acknowledged the frame in flight. */
564
+ 'frames_acked',
565
+ /** No acknowledgement within the bound: the slot freed itself. */
566
+ 'frames_ack_timeout',
567
+ /** A viewer's frame slot was freed by a resubscribe, detach or stop. */
568
+ 'frame_slots_released',
569
+ ];
570
+ /** How often a daemon with anyone attached reports its counters. */
571
+ export const COMPUTER_DIAGNOSTICS_INTERVAL_MS = 15_000;
572
+ /** Viewers one report names, newest first; the rest are in the totals only. */
573
+ export const COMPUTER_DIAGNOSTICS_MAX_STREAMS = 64;
574
+ export const COMPUTER_TERMINAL_SIGNALS = ['interrupt', 'eof'];
575
+ /** Input a terminal surface takes. */
576
+ export const COMPUTER_TERMINAL_INPUT_TYPES = ['bytes', 'paste', 'resize', 'signal'];
577
+ /**
578
+ * How long a terminal lives (§6.1, §18), so its window can say so instead of
579
+ * implying a guest's guarantees on a laptop (OSK-13535):
580
+ * - `host`: its shell is in the terminal host; a daemon restart or update
581
+ * leaves it running. Off a guest the host closes it an hour after its
582
+ * daemon stopped for good. Its output is kept on disk, so after the machine
583
+ * (or the host) restarts it reopens with its history above a divider;
584
+ * - `tmux`: a guest's persistent terminal from before the host; it outlives
585
+ * the daemon and ends with the machine, with no history;
586
+ * - `daemon`: its shell is in the daemon's own process and ends with it.
587
+ */
588
+ export const COMPUTER_TERMINAL_LIFETIMES = ['host', 'tmux', 'daemon'];
589
+ /**
590
+ * Why a reopened terminal's shell is not the one that wrote the history above
591
+ * its divider (OSK-13474). The terminal host decides it from its records.
592
+ */
593
+ export const COMPUTER_TERMINAL_HISTORY_CAUSES = [
594
+ /** The machine stopped or rebooted. */
595
+ 'machine_restarted',
596
+ /** The terminal host was stopped while the shell ran. */
597
+ 'host_stopped',
598
+ /** Off a guest: the host closed its terminals after its daemon stayed away for an hour. */
599
+ 'host_orphaned',
600
+ /** The terminal host died without stopping and was started again. */
601
+ 'host_restarted',
602
+ ];
603
+ /**
604
+ * What a terminal's window cannot promise on this machine, said rather than
605
+ * left silent. `windows_pty_unverified`: a Windows machine's terminal is a
606
+ * ConPTY in the daemon's process; its resize and PTY behaviour have not been
607
+ * verified (OSK-13535).
608
+ */
609
+ export const COMPUTER_TERMINAL_CAVEATS = ['windows_pty_unverified'];
610
+ /** Input a browser surface takes. `tab` needs `computer_browser_tabs_v1`. */
611
+ export const COMPUTER_BROWSER_INPUT_TYPES = [
612
+ 'pointer',
613
+ 'key',
614
+ 'text',
615
+ 'navigate',
616
+ 'history',
617
+ 'tab',
618
+ ];
619
+ /**
620
+ * The client OS a browser `key` input comes from. A person's shortcuts are
621
+ * mapped onto the Linux page's by the daemon: on `mac`, Command is Control,
622
+ * Command+Arrow is Home/End and Option+Arrow is Control+Arrow. Omitted, the
623
+ * keys are sent as they are (the client has already mapped them, or needs none).
624
+ */
625
+ export const COMPUTER_BROWSER_INPUT_PLATFORMS = ['mac', 'windows', 'linux', 'other'];
626
+ /** The most text one `text` input, `paste` input or `key` input's `text` carries. */
627
+ export const COMPUTER_INPUT_TEXT_MAX_LENGTH = 16 * 1024;
628
+ /** What a page can block on until someone answers (§6.2). Permission prompts have no CDP event. */
629
+ export const COMPUTER_BROWSER_DIALOG_KINDS = [
630
+ 'alert',
631
+ 'confirm',
632
+ 'prompt',
633
+ 'beforeunload',
634
+ 'auth',
635
+ ];
636
+ /** How a dialog ended; `cancelled` is the page or a navigation closing it first. */
637
+ export const COMPUTER_BROWSER_DIALOG_OUTCOMES = [
638
+ 'accepted',
639
+ 'dismissed',
640
+ 'credentials_supplied',
641
+ 'cancelled',
642
+ ];
643
+ /** Who answered: the standing policy, the agent, the person, or the page itself. */
644
+ export const COMPUTER_BROWSER_DIALOG_ANSWERERS = [
645
+ 'agent_policy',
646
+ 'agent',
647
+ 'person',
648
+ 'page',
649
+ ];
650
+ /** The most of a dialog's message the surface carries; the page's text is cut there. */
651
+ export const COMPUTER_BROWSER_DIALOG_MESSAGE_MAX_LENGTH = 2000;
652
+ /** The longest answer a `prompt` dialog takes. */
653
+ export const COMPUTER_BROWSER_DIALOG_PROMPT_MAX_LENGTH = 4096;
654
+ /** The longest username or password an `auth` dialog takes; forwarded to the page only. */
655
+ export const COMPUTER_BROWSER_DIALOG_CREDENTIAL_MAX_LENGTH = 1024;
656
+ /** A download's state on the surface (§6.2); a finished one also appears in Files. */
657
+ export const COMPUTER_BROWSER_DOWNLOAD_STATES = ['in_progress', 'completed', 'canceled'];
658
+ /** The downloads one browser surface lists, newest first; older ones are in Files. */
659
+ export const COMPUTER_BROWSER_DOWNLOADS_MAX = 20;
660
+ /** Whether the page's file input takes one file or several. */
661
+ export const COMPUTER_BROWSER_FILE_CHOOSER_MODES = ['single', 'multiple'];
662
+ /** How a file chooser ended: files went to the page, or the person chose none. */
663
+ export const COMPUTER_BROWSER_FILE_CHOOSER_OUTCOMES = ['files_chosen', 'cancelled'];
664
+ /** The most files one answer hands the page. */
665
+ export const COMPUTER_BROWSER_FILE_CHOOSER_MAX_FILES = 32;
666
+ /** The longest `accept` attribute the descriptor carries; the page's is cut there. */
667
+ export const COMPUTER_BROWSER_FILE_CHOOSER_ACCEPT_MAX_LENGTH = 512;
668
+ /** The longest path one answer names. */
669
+ export const COMPUTER_BROWSER_FILE_CHOOSER_PATH_MAX_LENGTH = 4096;
670
+ /** The longest folder `computer:surface:open` may name as a terminal's `cwd`. */
671
+ export const COMPUTER_TERMINAL_CWD_MAX_LENGTH = 4096;
672
+ /** The most text one copy-out returns; a longer selection is cut there. */
673
+ export const COMPUTER_BROWSER_COPY_MAX_LENGTH = 64 * 1024;
674
+ /** `history` input: the browser's own back, forward and reload. */
675
+ export const COMPUTER_BROWSER_HISTORY_ACTIONS = ['back', 'forward', 'reload'];
676
+ /** `tab` input. `activate` and `close` name a `tabId`; `open` opens a blank tab. */
677
+ export const COMPUTER_BROWSER_TAB_ACTIONS = ['activate', 'open', 'close'];
678
+ /** The longest URL a `navigate` input carries. */
679
+ export const COMPUTER_BROWSER_URL_MAX_LENGTH = 4096;
680
+ // ---------------------------------------------------------------------------
681
+ // Files (§6.3, §18)
682
+ // ---------------------------------------------------------------------------
683
+ export const COMPUTER_FILE_OPERATIONS = [
684
+ 'list',
685
+ 'stat',
686
+ 'read',
687
+ 'write',
688
+ 'mkdir',
689
+ 'move',
690
+ 'delete',
691
+ 'archive',
692
+ ];
693
+ /**
694
+ * The read-only file routes that arrived with `computer_files_search_v1`
695
+ * (§6.3). A separate list: the operations above are the original contract and
696
+ * the audit/activity vocabulary pins them; these two never mutate.
697
+ */
698
+ export const COMPUTER_FILE_SEARCH_OPERATIONS = ['search', 'gitstatus'];
699
+ /** How a search ended: all of it read, cut at the result limit, or cut at the time bound. */
700
+ export const COMPUTER_FILE_SEARCH_STATES = ['complete', 'truncated', 'timed_out'];
701
+ /** What `git status` says about one entry of a directory. */
702
+ export const COMPUTER_FILE_GIT_MARKS = [
703
+ 'modified',
704
+ 'added',
705
+ 'untracked',
706
+ 'ignored',
707
+ 'conflicted',
708
+ ];
709
+ /** Search bounds, one declaration for the daemon, the API and the client. */
710
+ export const COMPUTER_FILE_SEARCH_DEFAULT_LIMIT = 200;
711
+ export const COMPUTER_FILE_SEARCH_MAX_LIMIT = 1000;
712
+ export const COMPUTER_FILE_SEARCH_DEFAULT_BUDGET_MS = 8_000;
713
+ export const COMPUTER_FILE_SEARCH_MAX_BUDGET_MS = 20_000;
714
+ export const COMPUTER_FILE_SEARCH_MAX_QUERY_LENGTH = 200;
715
+ export const COMPUTER_FILE_GIT_STATUS_BUDGET_MS = 4_000;
716
+ export const COMPUTER_FILE_TYPES = ['file', 'directory', 'symlink', 'other'];
717
+ /**
718
+ * Why an entry is withheld rather than listed as readable (§18): the daemon's
719
+ * own deny set, the machine's `blockedPaths`, a built-in sensitive pattern, or
720
+ * an operating-system privacy control denying the daemon the folder.
721
+ */
722
+ export const COMPUTER_FILE_WITHHELD_REASONS = [
723
+ 'deny_set',
724
+ 'blocked_path',
725
+ 'sensitive_pattern',
726
+ 'os_permission',
727
+ ];
728
+ /**
729
+ * Why an attachment's scoped watches went stale (§6.3 "degrades to stale, not
730
+ * to wrong"). The tree then says so and offers a refresh — it never sits on a
731
+ * silently partial picture.
732
+ *
733
+ * - `spawn_failed`: the watcher could not start.
734
+ * - `watcher_exited`: the watcher ended (tenant code may kill it on a guest).
735
+ * - `watch_quota`: the account's watch quota is spent.
736
+ * - `events_overflow`: more changes arrived than are forwarded (a build
737
+ * writing thousands of files), so the watcher stopped rather than queue.
738
+ * - `delivery_gap`: a report was lost between the machine and this viewer.
739
+ */
740
+ export const COMPUTER_FILES_WATCH_DEGRADED_REASONS = [
741
+ 'spawn_failed',
742
+ 'watcher_exited',
743
+ 'watch_quota',
744
+ 'events_overflow',
745
+ 'delivery_gap',
746
+ ];
747
+ /** Changes one `computer:files:changed` report carries; more in one window is `events_overflow`. */
748
+ export const COMPUTER_FILES_CHANGED_MAX_CHANGES = 256;
749
+ /** The kinds of change a `file_change` activity event reports. */
750
+ export const COMPUTER_FILE_CHANGES = ['created', 'modified', 'deleted', 'moved'];
751
+ /** The most entries below a folder a contents tag covers; a larger folder is `too_large`. */
752
+ export const COMPUTER_FILE_CONTENTS_TAG_MAX_ENTRIES = 100_000;
753
+ /** A contents tag's shape: SHA-256 as lowercase hex. */
754
+ export const COMPUTER_FILE_CONTENTS_TAG_PATTERN = /^[0-9a-f]{64}$/;
755
+ export function computerFileEntityTagsEqual(a, b) {
756
+ return (a.inode === b.inode &&
757
+ a.size === b.size &&
758
+ a.mtimeNs === b.mtimeNs &&
759
+ (a.contents ?? null) === (b.contents ?? null));
760
+ }
761
+ // ---------------------------------------------------------------------------
762
+ // Activity (§4.4, §6.4)
763
+ // ---------------------------------------------------------------------------
764
+ export const COMPUTER_ACTIVITY_KINDS = [
765
+ 'command',
766
+ 'browser_action',
767
+ 'file_change',
768
+ 'control',
769
+ 'lifecycle',
770
+ ];
771
+ /** `summary` is one line, safe to show. */
772
+ export const COMPUTER_ACTIVITY_SUMMARY_MAX_LENGTH = 500;
773
+ /** The most command output one event carries; longer output is split across events. */
774
+ export const COMPUTER_ACTIVITY_OUTPUT_MAX_LENGTH = 64 * 1024;
775
+ /**
776
+ * What a viewer without file access reads where a withheld path stood
777
+ * (security review PM-10, OSK-13701): one stable token, so a client can say
778
+ * "withheld" rather than render a path-shaped string.
779
+ */
780
+ export const COMPUTER_ACTIVITY_WITHHELD_PLACEHOLDER = '[withheld]';
781
+ export const COMPUTER_COMMAND_PHASES = ['started', 'output', 'exited'];
782
+ export const COMPUTER_LIFECYCLE_EVENTS = [
783
+ 'surface_opened',
784
+ 'surface_ended',
785
+ 'surface_shared',
786
+ 'surface_unshared',
787
+ /**
788
+ * A person whose access was revoked left this surface open: it is theirs and
789
+ * still running, and closing it is the owner's visible decision (§5.1 rule 8).
790
+ */
791
+ 'surface_orphaned',
792
+ /**
793
+ * A person holds this surface and idle reclaim will hand it back to the agent
794
+ * at `deadline` unless they provide input or take it again (§8, D12). Emitted
795
+ * `COMPUTER_RECLAIM_WARNING_MS` before the deadline, once per idle window;
796
+ * input moves the deadline and the warning is stale from that moment.
797
+ */
798
+ 'reclaim_imminent',
799
+ 'power',
800
+ /** The computer's browser was signed out everywhere: its profile removed, every window ended. */
801
+ 'browser_reset',
802
+ ];
803
+ /** How a command call ended, as its producer stated it — never inferred from output. */
804
+ export const COMPUTER_COMMAND_OUTCOMES = ['succeeded', 'failed'];
805
+ /**
806
+ * The `signal` of a command that ended without a process exit status, beyond
807
+ * `timeout`, `error`, `cancelled` and signal names (task 5-04b, OSK-13614):
808
+ * `not_started` — the run was refused before it began, and `refusal` says
809
+ * why; `unknown` — the shell it ran in ended first, so its own exit status
810
+ * cannot be known.
811
+ */
812
+ export const COMPUTER_COMMAND_NOT_STARTED_SIGNAL = 'not_started';
813
+ export const COMPUTER_COMMAND_UNKNOWN_END_SIGNAL = 'unknown';
814
+ /**
815
+ * Why a run in an agent's shell never started (`refusal` beside
816
+ * `signal: 'not_started'`): the shell was busy with another run, had ended,
817
+ * was still opening, a person held the terminal, the command could not be
818
+ * handed to the shell, or the request was incomplete. A client shows an
819
+ * unknown value as "did not start" without a reason.
820
+ */
821
+ export const COMPUTER_COMMAND_START_REFUSALS = [
822
+ 'session_busy',
823
+ 'session_closed',
824
+ 'session_not_ready',
825
+ 'human_control',
826
+ 'start_failed',
827
+ 'invalid_request',
828
+ ];
829
+ export const COMPUTER_COMMAND_OUTPUT_STREAMS = ['stdout', 'stderr'];
830
+ // ---------------------------------------------------------------------------
831
+ // Declared services (§20.5; 9-17, OSK-13579)
832
+ // ---------------------------------------------------------------------------
833
+ /**
834
+ * What `daemon:computer:services` (and the agent's `computer.services` call)
835
+ * asks of the machine: add or replace one service by name, remove one, or
836
+ * answer the list. Declaring and withdrawing need `open`; listing needs
837
+ * `observe`.
838
+ */
839
+ export const COMPUTER_DECLARED_SERVICE_ACTIONS = ['declare', 'withdraw', 'list'];
840
+ /**
841
+ * The tool an agent session's declaration rides to its own daemon: the same
842
+ * `daemon:tool:request` channel that carries its other calls, answered by the
843
+ * same gate as `daemon:computer:services`. The input is the API's (action,
844
+ * principal, agent, service or name); the session is the request's own.
845
+ */
846
+ export const COMPUTER_SERVICES_TOOL_NAME = 'computer.services';
847
+ /** A refused `computer.services` call: this prefix, then the refusal as JSON. */
848
+ export const COMPUTER_SERVICES_TOOL_ERROR_PREFIX = 'computer_services_refused:';
849
+ /** The operation each action needs, by the one access vocabulary. */
850
+ export function computerDeclaredServiceOperation(action) {
851
+ return action === 'list' ? 'observe' : 'open';
852
+ }
853
+ export const COMPUTER_DECLARED_SERVICE_PROTOCOLS = ['http', 'https'];
854
+ /** Services one machine holds at once; one more is `service_limit`. */
855
+ export const COMPUTER_DECLARED_SERVICES_MAX = 16;
856
+ export const COMPUTER_DECLARED_SERVICE_NAME_MAX_LENGTH = 48;
857
+ export const COMPUTER_DECLARED_SERVICE_PATH_MAX_LENGTH = 512;
858
+ /**
859
+ * The one host a declared service names. A declaration carries a port and a
860
+ * path, never a host or a URL: the target is always this machine's own
861
+ * loopback, by construction, and a request that names anything else is
862
+ * refused as malformed rather than narrowed.
863
+ */
864
+ export const COMPUTER_DECLARED_SERVICE_HOST = 'localhost';
865
+ /** A service's name: its key on the machine, lower case, shown as written. */
866
+ export const COMPUTER_DECLARED_SERVICE_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
867
+ /**
868
+ * A path on the service: absolute, no query and no fragment (a token in a
869
+ * query string would be shown to everyone who can see the machine), no
870
+ * whitespace or backslash, and not starting with two slashes.
871
+ */
872
+ const DECLARED_SERVICE_PATH_PATTERN = /^\/(?!\/)[A-Za-z0-9\-._~!$&'()*+,;=:@%/]*$/;
873
+ const DECLARED_SERVICE_KEYS = new Set(['name', 'port', 'path', 'protocol']);
874
+ /** Which part of a declaration was refused. */
875
+ export const COMPUTER_DECLARED_SERVICE_PROBLEMS = [
876
+ /** Not an object, or a field other than name, port, path and protocol (a host, a URL). */
877
+ 'shape',
878
+ 'name',
879
+ 'port',
880
+ 'path',
881
+ 'protocol',
882
+ ];
883
+ /**
884
+ * A declaration, checked and filled in, or the part of it that was refused.
885
+ * The daemon, the API, the client and the CLI all read a declaration through
886
+ * this one rule, so a service none of them would accept never reaches the
887
+ * manifest.
888
+ */
889
+ export function computerDeclaredServiceSpec(raw) {
890
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
891
+ return { ok: false, problem: 'shape' };
892
+ }
893
+ const data = raw;
894
+ if (Object.keys(data).some((key) => !DECLARED_SERVICE_KEYS.has(key))) {
895
+ return { ok: false, problem: 'shape' };
896
+ }
897
+ const { name, port, path, protocol } = data;
898
+ if (typeof name !== 'string' ||
899
+ name.length === 0 ||
900
+ name.length > COMPUTER_DECLARED_SERVICE_NAME_MAX_LENGTH ||
901
+ !COMPUTER_DECLARED_SERVICE_NAME_PATTERN.test(name)) {
902
+ return { ok: false, problem: 'name' };
903
+ }
904
+ if (typeof port !== 'number' || !Number.isInteger(port) || port < 1 || port > 65535) {
905
+ return { ok: false, problem: 'port' };
906
+ }
907
+ if (path !== undefined &&
908
+ (typeof path !== 'string' ||
909
+ path.length > COMPUTER_DECLARED_SERVICE_PATH_MAX_LENGTH ||
910
+ !DECLARED_SERVICE_PATH_PATTERN.test(path))) {
911
+ return { ok: false, problem: 'path' };
912
+ }
913
+ if (protocol !== undefined &&
914
+ (typeof protocol !== 'string' ||
915
+ !COMPUTER_DECLARED_SERVICE_PROTOCOLS.includes(protocol))) {
916
+ return { ok: false, problem: 'protocol' };
917
+ }
918
+ return {
919
+ ok: true,
920
+ spec: {
921
+ name,
922
+ port,
923
+ path: typeof path === 'string' ? path : '/',
924
+ protocol: protocol ?? 'http',
925
+ },
926
+ };
927
+ }
928
+ /**
929
+ * The address a declared service opens at in the computer's browser: always
930
+ * this machine's loopback. Throws on a spec the rule above would refuse, so a
931
+ * caller can never be handed an address off the machine.
932
+ */
933
+ export function computerDeclaredServiceUrl(service) {
934
+ const checked = computerDeclaredServiceSpec({
935
+ name: 'service',
936
+ port: service.port,
937
+ ...(service.path !== undefined ? { path: service.path } : {}),
938
+ ...(service.protocol !== undefined ? { protocol: service.protocol } : {}),
939
+ });
940
+ if (!checked.ok) {
941
+ throw new Error(`not a declared service address (${checked.problem})`);
942
+ }
943
+ const { protocol, port, path } = checked.spec;
944
+ return `${protocol}://${COMPUTER_DECLARED_SERVICE_HOST}:${port}${path}`;
945
+ }
946
+ // ---------------------------------------------------------------------------
947
+ // Machines and power (§10, §18)
948
+ // ---------------------------------------------------------------------------
949
+ export const COMPUTER_MACHINE_KINDS = ['dedicated', 'personal'];
950
+ /**
951
+ * One picture per machine state; none of them is a spinner without a bound,
952
+ * and none collapses two states into the reassuring one.
953
+ */
954
+ export const COMPUTER_POWER_PHASES = [
955
+ // A Dedicated Runtime, from its lease state.
956
+ /** `ready`, `active`, `idle`: the desktop. */
957
+ 'on',
958
+ /** On the way up: a boot screen naming the step, with elapsed time. */
959
+ 'booting',
960
+ /** `stopping`, `snapshotting`: saving files, with the checkpoint outcome when known. */
961
+ 'stopping',
962
+ /** `stopped`: off, files preserved. */
963
+ 'off',
964
+ /** Files retained until a date. */
965
+ 'archived',
966
+ /** `terminating`: being removed. */
967
+ 'removing',
968
+ /** `failed`, `lost`, `terminated`: the reason, and what is recoverable. */
969
+ 'gone',
970
+ /** The provider could not be read. Explicitly not "off". */
971
+ 'blind',
972
+ /** The lease is healthy and the daemon is not answering. */
973
+ 'unresponsive',
974
+ /** A lease state that belongs to another machine product (`pausing`, `paused`, `resuming`). */
975
+ 'not_a_computer',
976
+ // Either kind.
977
+ /** The daemon does not announce `computer_v1`. */
978
+ 'needs_update',
979
+ // A personal machine: no lease, no power actions.
980
+ 'online',
981
+ 'offline',
982
+ 'needs_sign_in',
983
+ /** The machine has not allowed the computer (§18). */
984
+ 'not_allowed',
985
+ ];
986
+ /**
987
+ * Phases a personal machine can be in. `blind` is shared with a Dedicated
988
+ * Runtime: the machine's presence could not be READ, which is not the same
989
+ * fact as "offline" and must not be shown as it (§10, §18; 10-09).
990
+ */
991
+ export const COMPUTER_PERSONAL_POWER_PHASES = [
992
+ 'online',
993
+ 'offline',
994
+ 'needs_sign_in',
995
+ 'not_allowed',
996
+ 'needs_update',
997
+ 'blind',
998
+ ];
999
+ /**
1000
+ * Why a personal machine needs signing in again — `power.reason` on
1001
+ * `needs_sign_in`. `needs_reauth`: the daemon's credential stopped refreshing
1002
+ * (it latches the Harness mark and waits). `revoked`: the machine's sign-in was
1003
+ * revoked from the account, so it is no longer this person's machine until it
1004
+ * is signed in again. Same next action, different fact, different picture.
1005
+ */
1006
+ export const COMPUTER_PERSONAL_SIGN_IN_REASONS = ['needs_reauth', 'revoked'];
1007
+ /** Why a stopped machine would also refuse a start, so the screen does not offer one (§10). */
1008
+ export const COMPUTER_START_REFUSALS = ['spend_cap', 'entitlement'];
1009
+ /**
1010
+ * The picture for each Dedicated Runtime lease state. Its keys are the lease
1011
+ * states; `@skrr-ai/data-provider` asserts at compile time that they are
1012
+ * exactly `MachineLeaseStateName`, so a new lease state fails to compile there
1013
+ * until it is given a picture here — never a default.
1014
+ */
1015
+ export const COMPUTER_LEASE_STATE_POWER_PHASE = Object.freeze({
1016
+ ready: 'on',
1017
+ active: 'on',
1018
+ idle: 'on',
1019
+ requested: 'booting',
1020
+ provisioning: 'booting',
1021
+ bootstrapping: 'booting',
1022
+ starting: 'booting',
1023
+ restarting: 'booting',
1024
+ recovering: 'booting',
1025
+ stopping: 'stopping',
1026
+ snapshotting: 'stopping',
1027
+ stopped: 'off',
1028
+ archived: 'archived',
1029
+ terminating: 'removing',
1030
+ failed: 'gone',
1031
+ lost: 'gone',
1032
+ terminated: 'gone',
1033
+ pausing: 'not_a_computer',
1034
+ paused: 'not_a_computer',
1035
+ resuming: 'not_a_computer',
1036
+ });
1037
+ /** The phase a Dedicated Runtime lease state shows as, or `null` for a state this build does not know. */
1038
+ export function computerPowerPhaseForLeaseState(state) {
1039
+ return typeof state === 'string' &&
1040
+ Object.prototype.hasOwnProperty.call(COMPUTER_LEASE_STATE_POWER_PHASE, state)
1041
+ ? COMPUTER_LEASE_STATE_POWER_PHASE[state]
1042
+ : null;
1043
+ }
1044
+ /** Whether the phase shows the desktop. */
1045
+ export function computerShowsDesktop(phase) {
1046
+ return phase === 'on' || phase === 'online';
1047
+ }
1048
+ /** Power actions are offered for a Dedicated Runtime only; a personal machine has none (§18). */
1049
+ export function computerOffersPowerActions(machineKind) {
1050
+ return machineKind === 'dedicated';
1051
+ }