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