@skrr-ai/cli 0.1.114 → 0.1.115
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/dist/commands/agents/chat.js +10 -1
- package/dist/commands/computer-use/audio.d.ts +6 -0
- package/dist/commands/computer-use/audio.js +9 -0
- package/dist/commands/computer-use/browser.d.ts +7 -0
- package/dist/commands/computer-use/browser.js +16 -0
- package/dist/commands/computer-use/disable.d.ts +6 -0
- package/dist/commands/computer-use/disable.js +9 -0
- package/dist/commands/computer-use/enable.d.ts +6 -0
- package/dist/commands/computer-use/enable.js +9 -0
- package/dist/commands/computer-use/index.d.ts +6 -0
- package/dist/commands/computer-use/index.js +9 -0
- package/dist/commands/computer-use/install.d.ts +6 -0
- package/dist/commands/computer-use/install.js +9 -0
- package/dist/commands/computer-use/recover.d.ts +6 -0
- package/dist/commands/computer-use/recover.js +9 -0
- package/dist/commands/computer-use/status.d.ts +6 -0
- package/dist/commands/computer-use/status.js +9 -0
- package/dist/commands/computer-use/stop.d.ts +6 -0
- package/dist/commands/computer-use/stop.js +9 -0
- package/dist/commands/daemon/login.js +1 -1
- package/dist/commands/followups/plans/cancel.js +10 -3
- package/dist/commands/followups/plans/capacity.d.ts +9 -0
- package/dist/commands/followups/plans/capacity.js +42 -0
- package/dist/commands/followups/plans/create.js +3 -0
- package/dist/commands/followups/plans/revise.js +13 -6
- package/dist/lib/agentic-stream.d.ts +5 -1
- package/dist/lib/agentic-stream.js +25 -9
- package/dist/lib/computer-use.d.ts +9 -0
- package/dist/lib/computer-use.js +41 -0
- package/dist/lib/lookbackPlans.d.ts +20 -6
- package/dist/lib/lookbackPlans.js +19 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerUse.d.ts +228 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerUse.js +375 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonScopes.d.ts +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonScopes.js +6 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +40 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopWire.d.ts +140 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopWire.js +46 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +8 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerUse.d.ts +228 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerUse.js +358 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonScopes.d.ts +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonScopes.js +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +40 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopWire.d.ts +140 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopWire.js +42 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +23687 -23456
- package/oclif.manifest.json +284 -4
- package/package.json +2 -2
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computer Use v1 is a host-to-host protocol, never a model tool schema.
|
|
3
|
+
* Models see exactly the runtime's js/js_reset surface. In particular, script
|
|
4
|
+
* contents cannot prove that a call is read-only or supply execution identity.
|
|
5
|
+
* Kept dependency-free so daemon, API, browser and native clients agree.
|
|
6
|
+
*/
|
|
7
|
+
export const COMPUTER_USE_TOOL = 'ComputerUse';
|
|
8
|
+
export const COMPUTER_USE_PROVIDER = 'lcu';
|
|
9
|
+
export const COMPUTER_USE_PROTOCOL = 'computer_use_mcp_v1';
|
|
10
|
+
export const COMPUTER_USE_ROTATE_CREDENTIAL_EVENT = 'daemon:computer-use:rotate-credential';
|
|
11
|
+
export const COMPUTER_USE_EXECUTION_CAPABILITY = 'computer_use';
|
|
12
|
+
export const COMPUTER_USE_CLOUD_CONSENT = 'computer_use_cloud_agents_v1';
|
|
13
|
+
export const COMPUTER_USE_PUBLIC_TOOLS = ['js', 'js_reset'];
|
|
14
|
+
export const COMPUTER_USE_OPERATIONS = ['describe', 'call', 'end_turn', 'close', 'status', 'control_stop'];
|
|
15
|
+
export const COMPUTER_USE_DEFAULT_BUDGETS = Object.freeze({
|
|
16
|
+
activeExecutionMs: 120_000,
|
|
17
|
+
humanResponseMs: 300_000,
|
|
18
|
+
totalLifetimeMs: 900_000,
|
|
19
|
+
cleanupGraceMs: 10_000,
|
|
20
|
+
});
|
|
21
|
+
/** Negotiated ceilings; exceeding a limit is an error, never silent clipping. */
|
|
22
|
+
export const COMPUTER_USE_LIMITS = Object.freeze({
|
|
23
|
+
maxEnvelopeBytes: 32 * 1024 * 1024,
|
|
24
|
+
maxInstructionsBytes: 1024 * 1024,
|
|
25
|
+
maxSchemaBytes: 1024 * 1024,
|
|
26
|
+
maxContentBlocks: 512,
|
|
27
|
+
maxIdentityChars: 512,
|
|
28
|
+
maxActiveExecutionMs: 900_000,
|
|
29
|
+
maxHumanResponseMs: 900_000,
|
|
30
|
+
maxTotalLifetimeMs: 1_800_000,
|
|
31
|
+
maxCleanupGraceMs: 60_000,
|
|
32
|
+
});
|
|
33
|
+
/** Preserve original documentation while explaining the host's narrower lifecycle. */
|
|
34
|
+
export function computerUseModelInstructions(surface) {
|
|
35
|
+
assertComputerUseSurface(surface);
|
|
36
|
+
if (!surface.runtimePolicy)
|
|
37
|
+
return surface.instructions;
|
|
38
|
+
return surface.instructions + '\n\nOverSky host policy\n' +
|
|
39
|
+
'Audio cleanup retires the complete runtime after js_reset and at the end of each host turn. ' +
|
|
40
|
+
'JavaScript variables and application handles persist only within the current turn. ' +
|
|
41
|
+
'Initialize fresh handles in the next turn; do not reuse handles from an earlier runtime.';
|
|
42
|
+
}
|
|
43
|
+
export const COMPUTER_USE_RUNTIME_STATES = [
|
|
44
|
+
'disabled', 'starting', 'ready', 'executing', 'awaiting_approval',
|
|
45
|
+
'ending_turn', 'unavailable', 'closed',
|
|
46
|
+
];
|
|
47
|
+
export const COMPUTER_USE_ELICITATION_CHOICES = ['once', 'session', 'always', 'decline'];
|
|
48
|
+
export class ComputerUseContractError extends Error {
|
|
49
|
+
code;
|
|
50
|
+
constructor(code, message) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.code = code;
|
|
53
|
+
this.name = 'ComputerUseContractError';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function fail(message) {
|
|
57
|
+
throw new ComputerUseContractError('invalid_computer_use_contract', message);
|
|
58
|
+
}
|
|
59
|
+
function record(value) {
|
|
60
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
61
|
+
}
|
|
62
|
+
function exactKeys(value, keys, name) {
|
|
63
|
+
if (Object.keys(value).some((key) => !keys.includes(key)))
|
|
64
|
+
fail(`Unsupported ${name} field`);
|
|
65
|
+
}
|
|
66
|
+
function identifier(value, name) {
|
|
67
|
+
if (typeof value !== 'string' || value.trim().length === 0 || value.length > COMPUTER_USE_LIMITS.maxIdentityChars)
|
|
68
|
+
fail(`Invalid ${name}`);
|
|
69
|
+
}
|
|
70
|
+
/** Reject non-JSON data before transport, including lossy NaN/undefined values. */
|
|
71
|
+
function jsonSize(value) {
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
const visit = (item) => {
|
|
74
|
+
if (item === null || typeof item === 'string' || typeof item === 'boolean')
|
|
75
|
+
return;
|
|
76
|
+
if (typeof item === 'number' && Number.isFinite(item))
|
|
77
|
+
return;
|
|
78
|
+
if (typeof item !== 'object' || !item || seen.has(item))
|
|
79
|
+
fail('Expected acyclic JSON data');
|
|
80
|
+
if (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null)
|
|
81
|
+
fail('Expected plain JSON object');
|
|
82
|
+
seen.add(item);
|
|
83
|
+
for (const child of Array.isArray(item) ? item : Object.values(item))
|
|
84
|
+
visit(child);
|
|
85
|
+
seen.delete(item);
|
|
86
|
+
};
|
|
87
|
+
visit(value);
|
|
88
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
89
|
+
}
|
|
90
|
+
function boundedJson(value, max, name) {
|
|
91
|
+
if (jsonSize(value) > max)
|
|
92
|
+
fail(`${name} exceeds negotiated byte limit`);
|
|
93
|
+
}
|
|
94
|
+
/** Validate the hosted rotation envelope before changing runtime admission. */
|
|
95
|
+
export function assertComputerUseRotateCredentialRequest(value) {
|
|
96
|
+
if (!record(value))
|
|
97
|
+
fail('Expected credential rotation request');
|
|
98
|
+
const ids = ['requestId', 'ownerUserId', 'machineUuid', 'leaseId'];
|
|
99
|
+
exactKeys(value, [...ids, 'generation'], 'credential rotation');
|
|
100
|
+
for (const key of ids) {
|
|
101
|
+
identifier(value[key], key);
|
|
102
|
+
if (value[key].length > 160)
|
|
103
|
+
fail(`Invalid ${key}`);
|
|
104
|
+
}
|
|
105
|
+
if (!Number.isSafeInteger(value.generation) || value.generation < 1)
|
|
106
|
+
fail('Invalid hosted desktop generation');
|
|
107
|
+
}
|
|
108
|
+
export function assertComputerUseHostContext(value) {
|
|
109
|
+
if (!record(value))
|
|
110
|
+
fail('Expected host context');
|
|
111
|
+
const required = ['principalId', 'machineId', 'sessionId', 'turnId'];
|
|
112
|
+
const optional = ['toolCallId', 'agentId', 'engine', 'conversationId', 'contextKey'];
|
|
113
|
+
exactKeys(value, [...required, ...optional, 'generation'], 'host context');
|
|
114
|
+
for (const key of required)
|
|
115
|
+
identifier(value[key], key);
|
|
116
|
+
for (const key of optional)
|
|
117
|
+
if (value[key] !== undefined)
|
|
118
|
+
identifier(value[key], key);
|
|
119
|
+
if (!Number.isSafeInteger(value.generation) || value.generation < 0)
|
|
120
|
+
fail('Invalid generation');
|
|
121
|
+
}
|
|
122
|
+
export function computerUseBudgets(value = {}) {
|
|
123
|
+
if (!record(value))
|
|
124
|
+
fail('Expected execution budgets');
|
|
125
|
+
const ceilings = {
|
|
126
|
+
activeExecutionMs: COMPUTER_USE_LIMITS.maxActiveExecutionMs,
|
|
127
|
+
humanResponseMs: COMPUTER_USE_LIMITS.maxHumanResponseMs,
|
|
128
|
+
totalLifetimeMs: COMPUTER_USE_LIMITS.maxTotalLifetimeMs,
|
|
129
|
+
cleanupGraceMs: COMPUTER_USE_LIMITS.maxCleanupGraceMs,
|
|
130
|
+
};
|
|
131
|
+
exactKeys(value, Object.keys(ceilings), 'budget');
|
|
132
|
+
const result = { ...COMPUTER_USE_DEFAULT_BUDGETS };
|
|
133
|
+
for (const key of Object.keys(ceilings)) {
|
|
134
|
+
if (value[key] === undefined)
|
|
135
|
+
continue;
|
|
136
|
+
if (!Number.isSafeInteger(value[key]) || value[key] <= 0 || value[key] > ceilings[key])
|
|
137
|
+
fail(`Invalid ${key}`);
|
|
138
|
+
result[key] = value[key];
|
|
139
|
+
}
|
|
140
|
+
if (result.totalLifetimeMs < result.activeExecutionMs || result.totalLifetimeMs < result.cleanupGraceMs)
|
|
141
|
+
fail('Total lifetime is shorter than execution or cleanup budget');
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
export function assertComputerUseInput(value) {
|
|
145
|
+
if (!record(value))
|
|
146
|
+
fail('Expected Computer Use operation');
|
|
147
|
+
switch (value.operation) {
|
|
148
|
+
case 'describe':
|
|
149
|
+
exactKeys(value, ['operation', 'surfaceMode'], 'describe');
|
|
150
|
+
if (value.surfaceMode !== undefined && !['computer', 'browser', 'all'].includes(value.surfaceMode))
|
|
151
|
+
fail('Unsupported surface mode');
|
|
152
|
+
break;
|
|
153
|
+
case 'call':
|
|
154
|
+
exactKeys(value, ['operation', 'tool', 'arguments', 'surfaceDigest', 'budgets'], 'call');
|
|
155
|
+
if (!COMPUTER_USE_PUBLIC_TOOLS.includes(value.tool))
|
|
156
|
+
fail('Unsupported public tool');
|
|
157
|
+
if (!record(value.arguments))
|
|
158
|
+
fail('Expected original tool arguments');
|
|
159
|
+
if (value.surfaceDigest !== undefined)
|
|
160
|
+
identifier(value.surfaceDigest, 'surfaceDigest');
|
|
161
|
+
if (value.budgets !== undefined)
|
|
162
|
+
computerUseBudgets(value.budgets);
|
|
163
|
+
break;
|
|
164
|
+
case 'end_turn':
|
|
165
|
+
exactKeys(value, ['operation', 'reason'], 'end_turn');
|
|
166
|
+
if (value.reason !== 'Stop' && value.reason !== 'Interrupt')
|
|
167
|
+
fail('Unsupported end reason');
|
|
168
|
+
break;
|
|
169
|
+
case 'close':
|
|
170
|
+
case 'control_stop':
|
|
171
|
+
exactKeys(value, ['operation', 'reason'], value.operation);
|
|
172
|
+
if (value.reason !== undefined)
|
|
173
|
+
identifier(value.reason, 'reason');
|
|
174
|
+
break;
|
|
175
|
+
case 'status':
|
|
176
|
+
exactKeys(value, ['operation'], 'status');
|
|
177
|
+
break;
|
|
178
|
+
default: fail('Unsupported Computer Use operation');
|
|
179
|
+
}
|
|
180
|
+
boundedJson(value, COMPUTER_USE_LIMITS.maxEnvelopeBytes, 'Request');
|
|
181
|
+
}
|
|
182
|
+
export function assertComputerUseRequest(value) {
|
|
183
|
+
if (!record(value) || value.protocol !== COMPUTER_USE_PROTOCOL || value.provider !== COMPUTER_USE_PROVIDER)
|
|
184
|
+
fail('Unsupported Computer Use protocol/provider');
|
|
185
|
+
assertComputerUseHostContext(value.context);
|
|
186
|
+
const { protocol: _protocol, provider: _provider, context, ...input } = value;
|
|
187
|
+
assertComputerUseInput(input);
|
|
188
|
+
if (input.operation === 'call' && !context.toolCallId)
|
|
189
|
+
fail('Calls require exact host toolCallId');
|
|
190
|
+
boundedJson(value, COMPUTER_USE_LIMITS.maxEnvelopeBytes, 'Request');
|
|
191
|
+
}
|
|
192
|
+
export function assertComputerUseSurface(value) {
|
|
193
|
+
if (!record(value) || value.protocol !== COMPUTER_USE_PROTOCOL || value.provider !== COMPUTER_USE_PROVIDER)
|
|
194
|
+
fail('Unsupported Computer Use surface');
|
|
195
|
+
if (typeof value.instructions !== 'string')
|
|
196
|
+
fail('Expected original runtime instructions');
|
|
197
|
+
if (new TextEncoder().encode(value.instructions).byteLength > COMPUTER_USE_LIMITS.maxInstructionsBytes)
|
|
198
|
+
fail('Instructions exceed negotiated byte limit');
|
|
199
|
+
identifier(value.digest, 'surface digest');
|
|
200
|
+
if (!['computer', 'browser', 'all'].includes(value.surfaceMode))
|
|
201
|
+
fail('Unsupported surface mode');
|
|
202
|
+
if (value.runtimePolicy !== undefined) {
|
|
203
|
+
if (!record(value.runtimePolicy))
|
|
204
|
+
fail('Invalid host runtime policy');
|
|
205
|
+
exactKeys(value.runtimePolicy, ['replPersistence', 'audioCleanup'], 'host runtime policy');
|
|
206
|
+
if (value.runtimePolicy.replPersistence !== 'turn' || value.runtimePolicy.audioCleanup !== 'retire-runtime')
|
|
207
|
+
fail('Unsupported host runtime policy');
|
|
208
|
+
}
|
|
209
|
+
if (!record(value.versions) || typeof value.versions.lcu !== 'string' || !value.versions.lcu)
|
|
210
|
+
fail('Missing LCU version');
|
|
211
|
+
if (Object.values(value.versions).some((v) => typeof v !== 'string'))
|
|
212
|
+
fail('Invalid runtime version');
|
|
213
|
+
if (!Array.isArray(value.tools) || value.tools.length !== 2)
|
|
214
|
+
fail('Expected js and js_reset descriptors');
|
|
215
|
+
const names = new Set();
|
|
216
|
+
for (const tool of value.tools) {
|
|
217
|
+
if (!record(tool) || !COMPUTER_USE_PUBLIC_TOOLS.includes(tool.name) || !record(tool.inputSchema))
|
|
218
|
+
fail('Invalid original tool descriptor');
|
|
219
|
+
if (tool.description !== undefined && typeof tool.description !== 'string')
|
|
220
|
+
fail('Invalid tool description');
|
|
221
|
+
if (names.has(tool.name))
|
|
222
|
+
fail('Duplicate public tool');
|
|
223
|
+
names.add(tool.name);
|
|
224
|
+
boundedJson(tool, COMPUTER_USE_LIMITS.maxSchemaBytes, 'Tool descriptor');
|
|
225
|
+
}
|
|
226
|
+
boundedJson(value, COMPUTER_USE_LIMITS.maxEnvelopeBytes, 'Surface');
|
|
227
|
+
}
|
|
228
|
+
/** Validate the envelope without rewriting blocks or discarding upstream extensions. */
|
|
229
|
+
export function assertComputerUseMcpResult(value) {
|
|
230
|
+
if (!record(value) || !Array.isArray(value.content) || value.content.length > COMPUTER_USE_LIMITS.maxContentBlocks)
|
|
231
|
+
fail('Invalid MCP result content');
|
|
232
|
+
if (value.isError !== undefined && typeof value.isError !== 'boolean')
|
|
233
|
+
fail('Invalid MCP isError');
|
|
234
|
+
if (value.structuredContent !== undefined && !record(value.structuredContent))
|
|
235
|
+
fail('Invalid MCP structuredContent');
|
|
236
|
+
for (const block of value.content) {
|
|
237
|
+
if (!record(block) || typeof block.type !== 'string' || !block.type)
|
|
238
|
+
fail('Invalid MCP content block');
|
|
239
|
+
if (block.type === 'text' && typeof block.text !== 'string')
|
|
240
|
+
fail('Invalid MCP text block');
|
|
241
|
+
if (block.type === 'image' && (typeof block.data !== 'string' || typeof block.mimeType !== 'string'))
|
|
242
|
+
fail('Invalid MCP image block');
|
|
243
|
+
}
|
|
244
|
+
boundedJson(value, COMPUTER_USE_LIMITS.maxEnvelopeBytes, 'MCP result');
|
|
245
|
+
}
|
|
246
|
+
/** Compare full execution identity; a matching conversation alone never grants access. */
|
|
247
|
+
export function sameComputerUseContext(left, right) {
|
|
248
|
+
return ['principalId', 'machineId', 'sessionId', 'turnId', 'generation', 'agentId', 'engine', 'conversationId', 'contextKey']
|
|
249
|
+
.every((key) => left[key] === right[key]);
|
|
250
|
+
}
|
|
251
|
+
export function assertComputerUseElicitationRequest(value) {
|
|
252
|
+
if (!record(value) || value.kind !== 'computer_use_runtime')
|
|
253
|
+
fail('Expected runtime elicitation');
|
|
254
|
+
exactKeys(value, ['kind', 'requestId', 'context', 'message', 'resource', 'options', 'expiresAt'], 'elicitation request');
|
|
255
|
+
identifier(value.requestId, 'requestId');
|
|
256
|
+
assertComputerUseHostContext(value.context);
|
|
257
|
+
identifier(value.context.toolCallId, 'toolCallId');
|
|
258
|
+
if (typeof value.message !== 'string' || !value.message)
|
|
259
|
+
fail('Missing runtime elicitation message');
|
|
260
|
+
if (value.resource !== undefined && !record(value.resource))
|
|
261
|
+
fail('Invalid runtime resource');
|
|
262
|
+
if (!Number.isSafeInteger(value.expiresAt) || value.expiresAt <= 0)
|
|
263
|
+
fail('Invalid runtime elicitation expiry');
|
|
264
|
+
if (!Array.isArray(value.options) || value.options.length === 0 || value.options.length > COMPUTER_USE_ELICITATION_CHOICES.length)
|
|
265
|
+
fail('Invalid runtime choices');
|
|
266
|
+
const choices = new Set();
|
|
267
|
+
for (const option of value.options) {
|
|
268
|
+
if (!record(option))
|
|
269
|
+
fail('Invalid runtime option');
|
|
270
|
+
exactKeys(option, ['value', 'label', 'description'], 'elicitation option');
|
|
271
|
+
if (!COMPUTER_USE_ELICITATION_CHOICES.includes(option.value) || choices.has(option.value))
|
|
272
|
+
fail('Unsupported or duplicate runtime choice');
|
|
273
|
+
if (typeof option.label !== 'string' || !option.label || (option.description !== undefined && typeof option.description !== 'string'))
|
|
274
|
+
fail('Invalid runtime choice label');
|
|
275
|
+
choices.add(option.value);
|
|
276
|
+
}
|
|
277
|
+
boundedJson(value, COMPUTER_USE_LIMITS.maxEnvelopeBytes, 'Runtime elicitation');
|
|
278
|
+
}
|
|
279
|
+
export function assertComputerUseElicitationResponse(value) {
|
|
280
|
+
if (!record(value))
|
|
281
|
+
fail('Expected runtime elicitation response');
|
|
282
|
+
exactKeys(value, ['requestId', 'context', 'choice'], 'elicitation response');
|
|
283
|
+
identifier(value.requestId, 'requestId');
|
|
284
|
+
assertComputerUseHostContext(value.context);
|
|
285
|
+
identifier(value.context.toolCallId, 'toolCallId');
|
|
286
|
+
if (!COMPUTER_USE_ELICITATION_CHOICES.includes(value.choice))
|
|
287
|
+
fail('Unsupported runtime choice');
|
|
288
|
+
boundedJson(value, COMPUTER_USE_LIMITS.maxEnvelopeBytes, 'Runtime elicitation response');
|
|
289
|
+
}
|
|
290
|
+
/** A human answer can only satisfy this exact pending request and offered scope.
|
|
291
|
+
* This matcher is not authentication: the caller must verify the human responder.
|
|
292
|
+
*/
|
|
293
|
+
export function acceptsComputerUseElicitationResponse(request, response, now = Date.now()) {
|
|
294
|
+
try {
|
|
295
|
+
assertComputerUseElicitationRequest(request);
|
|
296
|
+
assertComputerUseElicitationResponse(response);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
return request.kind === 'computer_use_runtime' && request.requestId === response.requestId &&
|
|
302
|
+
Number.isFinite(request.expiresAt) && now < request.expiresAt &&
|
|
303
|
+
sameComputerUseContext(request.context, response.context) &&
|
|
304
|
+
typeof request.context.toolCallId === 'string' && request.context.toolCallId === response.context.toolCallId &&
|
|
305
|
+
COMPUTER_USE_ELICITATION_CHOICES.includes(response.choice) &&
|
|
306
|
+
request.options.some((option) => option.value === response.choice);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Consumer capability projection, never a rewrite of the canonical daemon
|
|
310
|
+
* result. Some model hosts accept MCP AudioContent but silently omit its bytes.
|
|
311
|
+
* Those hosts receive explicit references to already-persisted owned audio.
|
|
312
|
+
* This function never uploads, transcribes, invents a path or grants access.
|
|
313
|
+
*/
|
|
314
|
+
export function projectComputerUseResultForModel(result, options) {
|
|
315
|
+
assertComputerUseMcpResult(result);
|
|
316
|
+
if (!options || !['inline', 'artifact-reference'].includes(options.audio))
|
|
317
|
+
fail('Unsupported Computer Use model audio capability');
|
|
318
|
+
const audioCount = result.content.filter((block) => block.type === 'audio').length;
|
|
319
|
+
if (options.audio === 'inline' || audioCount === 0)
|
|
320
|
+
return result;
|
|
321
|
+
identifier(options.principalId, 'audio artifact owner');
|
|
322
|
+
identifier(options.sessionId, 'audio artifact session');
|
|
323
|
+
if (options.consumer !== undefined)
|
|
324
|
+
identifier(options.consumer, 'audio consumer');
|
|
325
|
+
const metadata = record(result._meta) ? result._meta : {};
|
|
326
|
+
const candidates = metadata['skrr.ai/computer-use-artifacts'];
|
|
327
|
+
const references = Array.isArray(candidates)
|
|
328
|
+
? candidates.filter((item) => record(item) && typeof item.mimeType === 'string' && item.mimeType.startsWith('audio/'))
|
|
329
|
+
: [];
|
|
330
|
+
if (references.length < audioCount || references.some((item) => !record(item) || typeof item.fileId !== 'string' || !/^[A-Za-z0-9_-]{1,512}$/.test(item.fileId) ||
|
|
331
|
+
item.reference !== `file:${item.fileId}` || item.ownerUserId !== options.principalId ||
|
|
332
|
+
item.sessionId !== options.sessionId || !Number.isSafeInteger(item.bytes) || item.bytes <= 0 ||
|
|
333
|
+
typeof item.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(item.sha256) ||
|
|
334
|
+
typeof item.mimeType !== 'string' || item.mimeType.length > 256 || !/^audio\/[A-Za-z0-9.+-]+(?:;[ -~]*)?$/.test(item.mimeType))) {
|
|
335
|
+
throw new ComputerUseContractError('computer_use_audio_artifact_required', 'This model cannot receive inline audio. Every audio block requires an existing owned file artifact for this exact session.');
|
|
336
|
+
}
|
|
337
|
+
const notice = [
|
|
338
|
+
'Audio is available as owned file artifacts. This model receives references instead of inline audio and has not listened to the recording.',
|
|
339
|
+
...references.map((item) => `${item.reference} (${item.mimeType}; ${item.bytes} bytes; sha256 ${item.sha256})`),
|
|
340
|
+
].join('\n');
|
|
341
|
+
let inserted = false;
|
|
342
|
+
const content = result.content.flatMap((block) => {
|
|
343
|
+
if (block.type !== 'audio')
|
|
344
|
+
return [block];
|
|
345
|
+
if (inserted)
|
|
346
|
+
return [];
|
|
347
|
+
inserted = true;
|
|
348
|
+
return [{ type: 'text', text: notice }];
|
|
349
|
+
});
|
|
350
|
+
const projected = { ...result, content, _meta: { ...metadata,
|
|
351
|
+
'skrr.ai/computer-use-model-projection': {
|
|
352
|
+
audio: 'artifact-reference', audioBlocks: audioCount,
|
|
353
|
+
...(options.consumer ? { consumer: options.consumer } : {}),
|
|
354
|
+
},
|
|
355
|
+
} };
|
|
356
|
+
assertComputerUseMcpResult(projected);
|
|
357
|
+
return projected;
|
|
358
|
+
}
|
|
@@ -52,16 +52,19 @@ export declare const SCOPES: Readonly<{
|
|
|
52
52
|
readonly TOOLS_FS_WRITE: "daemon:tools:fs:write";
|
|
53
53
|
readonly BROWSER_CDP: "daemon:browser:cdp";
|
|
54
54
|
readonly NATIVE_UI_CONTROL: "daemon:native-ui:control";
|
|
55
|
+
readonly COMPUTER_USE_CONTROL: "daemon:computer-use:control";
|
|
55
56
|
readonly SESSION_REPLAY: "daemon:session:replay";
|
|
56
57
|
readonly OFFLINE_QUEUE: "daemon:offline-queue";
|
|
57
58
|
}>;
|
|
58
59
|
export type DaemonScope = (typeof SCOPES)[keyof typeof SCOPES];
|
|
59
60
|
/**
|
|
60
|
-
*
|
|
61
|
+
* All daemon scopes in canonical order. Login defaults are a separate opt-in policy. Frozen so consumers can
|
|
61
62
|
* pass it directly to OAuth `scope` parameters without defensive
|
|
62
63
|
* copies.
|
|
63
64
|
*/
|
|
64
65
|
export declare const ALL_DAEMON_SCOPES: readonly DaemonScope[];
|
|
66
|
+
/** Login defaults retain the existing surface; script control is explicit opt-in. */
|
|
67
|
+
export declare const DEFAULT_DAEMON_SCOPES: readonly DaemonScope[];
|
|
65
68
|
/**
|
|
66
69
|
* Set form of `ALL_DAEMON_SCOPES` for O(1) membership checks.
|
|
67
70
|
*/
|
|
@@ -52,11 +52,12 @@ export const SCOPES = Object.freeze({
|
|
|
52
52
|
TOOLS_FS_WRITE: 'daemon:tools:fs:write',
|
|
53
53
|
BROWSER_CDP: 'daemon:browser:cdp',
|
|
54
54
|
NATIVE_UI_CONTROL: 'daemon:native-ui:control',
|
|
55
|
+
COMPUTER_USE_CONTROL: 'daemon:computer-use:control',
|
|
55
56
|
SESSION_REPLAY: 'daemon:session:replay',
|
|
56
57
|
OFFLINE_QUEUE: 'daemon:offline-queue',
|
|
57
58
|
});
|
|
58
59
|
/**
|
|
59
|
-
*
|
|
60
|
+
* All daemon scopes in canonical order. Login defaults are a separate opt-in policy. Frozen so consumers can
|
|
60
61
|
* pass it directly to OAuth `scope` parameters without defensive
|
|
61
62
|
* copies.
|
|
62
63
|
*/
|
|
@@ -66,9 +67,12 @@ export const ALL_DAEMON_SCOPES = Object.freeze([
|
|
|
66
67
|
SCOPES.TOOLS_FS_WRITE,
|
|
67
68
|
SCOPES.BROWSER_CDP,
|
|
68
69
|
SCOPES.NATIVE_UI_CONTROL,
|
|
70
|
+
SCOPES.COMPUTER_USE_CONTROL,
|
|
69
71
|
SCOPES.SESSION_REPLAY,
|
|
70
72
|
SCOPES.OFFLINE_QUEUE,
|
|
71
73
|
]);
|
|
74
|
+
/** Login defaults retain the existing surface; script control is explicit opt-in. */
|
|
75
|
+
export const DEFAULT_DAEMON_SCOPES = Object.freeze(ALL_DAEMON_SCOPES.filter((scope) => scope !== SCOPES.COMPUTER_USE_CONTROL));
|
|
72
76
|
/**
|
|
73
77
|
* Set form of `ALL_DAEMON_SCOPES` for O(1) membership checks.
|
|
74
78
|
*/
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
* heard is `firstPartyHarness.ts`'s and the API's `sessionProvider.js`'s
|
|
47
47
|
* decision, not this table's.
|
|
48
48
|
*/
|
|
49
|
+
import { COMPUTER_DESKTOP_CAPABILITY, COMPUTER_DESKTOP_CONTROL_CAPABILITY, DAEMON_COMPUTER_DESKTOP_EVENTS } from './desktopWire.js';
|
|
49
50
|
import { COMPUTER_CAPABILITIES, DAEMON_COMPUTER_EVENTS, DAEMON_COMPUTER_REPORT_EVENT_KEYS, } from './computerWire.js';
|
|
50
51
|
// ---------------------------------------------------------------------------
|
|
51
52
|
// Vocabulary
|
|
@@ -121,6 +122,15 @@ const CONNECTION = [
|
|
|
121
122
|
toServer('daemon:shuttingDown', 'The daemon is draining before it exits, so the API can fail over without waiting for the socket to time out.'),
|
|
122
123
|
toDaemon('server:draining', 'This API task is draining: reconnect (after jitter) to another one.'),
|
|
123
124
|
toDaemon('auth:revoked', 'The credential this socket used was revoked: stop retrying and ask the person to sign in again.'),
|
|
125
|
+
toDaemon('error:stale-process', 'This daemon process was replaced by a fresher registration; stop transport drain recovery before disconnect.', {
|
|
126
|
+
payload: { kind: 'zod', schema: 'daemonConnectionFenceErrorSchema' },
|
|
127
|
+
}),
|
|
128
|
+
toDaemon('error:stale-generation', 'This Dedicated Runtime generation is no longer active; stop transport drain recovery before disconnect.', {
|
|
129
|
+
payload: { kind: 'zod', schema: 'daemonConnectionFenceErrorSchema' },
|
|
130
|
+
}),
|
|
131
|
+
toDaemon('error:stale-binding', 'This daemon credential was rebound to another account; stop transport drain recovery before disconnect.', {
|
|
132
|
+
payload: { kind: 'zod', schema: 'daemonConnectionFenceErrorSchema' },
|
|
133
|
+
}),
|
|
124
134
|
toServer('daemon:auth-failed', 'The daemon could not refresh its own credential and needs the person to sign in again.'),
|
|
125
135
|
toServer('daemon:auth-warning', "The daemon's credential expires soon."),
|
|
126
136
|
toDaemon('error:unauthorized', 'The API refused an event this daemon sent, for lack of authority, naming the event.'),
|
|
@@ -273,6 +283,36 @@ const TOOLS = [
|
|
|
273
283
|
capability: TOOL_RESPONSE_OUTBOX,
|
|
274
284
|
}),
|
|
275
285
|
toDaemon('daemon:tool:cancel', 'Abandon a running tool call.'),
|
|
286
|
+
toDaemon('daemon:computer:use-control', 'An authorized person observes, configures, stops or recovers the machine Computer Use provider.', {
|
|
287
|
+
stability: 'experimental', capability: 'computer_use_mcp_v1', delivery: 'acked', answeredBy: 'daemon:computer:ack',
|
|
288
|
+
payload: { kind: 'zod', schema: 'daemonComputerUseControlSchema' },
|
|
289
|
+
}),
|
|
290
|
+
toDaemon('daemon:computer-use:rotate-credential', 'The approved hosted-desktop owner requests credential rotation for this exact active machine lease generation.', {
|
|
291
|
+
stability: 'experimental', capability: 'computer_use_mcp_v1', delivery: 'acked', answeredBy: 'daemon:computer:ack',
|
|
292
|
+
payload: { kind: 'zod', schema: 'daemonComputerUseRotateCredentialSchema' },
|
|
293
|
+
}),
|
|
294
|
+
toDaemon('daemon:computer-use:revoke', 'Revoke cloud Computer Use bindings after owner machine authority was removed.', {
|
|
295
|
+
stability: 'experimental', capability: 'computer_use_mcp_v1',
|
|
296
|
+
payload: { kind: 'zod', schema: 'daemonComputerUseRevokeSchema' },
|
|
297
|
+
}),
|
|
298
|
+
...['subscribe', 'protocol', 'unsubscribe'].map((operation) => toDaemon(DAEMON_COMPUTER_DESKTOP_EVENTS[operation], `Read-only desktop stream: ${operation}; bound to an authenticated attachment and generation.`, {
|
|
299
|
+
stability: 'experimental', capability: COMPUTER_DESKTOP_CAPABILITY,
|
|
300
|
+
delivery: 'acked', answeredBy: 'daemon:computer:ack',
|
|
301
|
+
payload: { kind: 'zod', schema: `daemonComputerDesktop${operation[0].toUpperCase()}${operation.slice(1)}Schema` },
|
|
302
|
+
})),
|
|
303
|
+
...['control', 'input'].map((operation) => toDaemon(DAEMON_COMPUTER_DESKTOP_EVENTS[operation], `Human-only typed desktop ${operation}; exact authenticated attachment, stream and generation.`, {
|
|
304
|
+
stability: 'experimental', capability: COMPUTER_DESKTOP_CONTROL_CAPABILITY,
|
|
305
|
+
delivery: 'acked', answeredBy: 'daemon:computer:ack',
|
|
306
|
+
payload: { kind: 'zod', schema: `daemonComputerDesktop${operation[0].toUpperCase()}${operation.slice(1)}Schema` },
|
|
307
|
+
})),
|
|
308
|
+
toServer(DAEMON_COMPUTER_DESKTOP_EVENTS.controlChanged, 'Private desktop holder and input revocation fact; never carries a control token.', {
|
|
309
|
+
stability: 'experimental', capability: COMPUTER_DESKTOP_CONTROL_CAPABILITY,
|
|
310
|
+
payload: { kind: 'zod', schema: 'computerDesktopControlResultSchema' },
|
|
311
|
+
}),
|
|
312
|
+
toServer(DAEMON_COMPUTER_DESKTOP_EVENTS.data, 'Read-only desktop stream data or a terminal close fact for one attachment generation.', {
|
|
313
|
+
stability: 'experimental', capability: COMPUTER_DESKTOP_CAPABILITY,
|
|
314
|
+
payload: { kind: 'zod', schema: 'computerDesktopDataSchema' },
|
|
315
|
+
}),
|
|
276
316
|
toServer('daemon:wire:chunk', 'One slice of a tool or session response above the single-message ceiling, sha256-checked and reassembled by the API before the reassembled event reaches its normal handler. Sent only to a server that announced `serverCaps.wireChunking`; the last slice is acked by that handler (OSK-16616).', {
|
|
277
317
|
delivery: 'acked',
|
|
278
318
|
stability: 'experimental',
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/** A separately negotiated read-only desktop stream, not a browser surface or an input grant. */
|
|
2
|
+
export declare const COMPUTER_DESKTOP_CAPABILITY: "computer_desktop_observe_v1";
|
|
3
|
+
export declare const COMPUTER_DESKTOP_SURFACE_ID: "desktop:main";
|
|
4
|
+
export declare const COMPUTER_DESKTOP_EVENTS: {
|
|
5
|
+
readonly subscribe: "computer:desktop:subscribe";
|
|
6
|
+
readonly protocol: "computer:desktop:protocol";
|
|
7
|
+
readonly unsubscribe: "computer:desktop:unsubscribe";
|
|
8
|
+
readonly data: "computer:desktop:data";
|
|
9
|
+
readonly control: "computer:desktop:control";
|
|
10
|
+
readonly input: "computer:desktop:input";
|
|
11
|
+
readonly controlChanged: "computer:desktop:control-changed";
|
|
12
|
+
};
|
|
13
|
+
export declare const DAEMON_COMPUTER_DESKTOP_EVENTS: {
|
|
14
|
+
readonly subscribe: "daemon:computer:desktop:subscribe";
|
|
15
|
+
readonly protocol: "daemon:computer:desktop:protocol";
|
|
16
|
+
readonly unsubscribe: "daemon:computer:desktop:unsubscribe";
|
|
17
|
+
readonly data: "daemon:computer:desktop:data";
|
|
18
|
+
readonly control: "daemon:computer:desktop:control";
|
|
19
|
+
readonly input: "daemon:computer:desktop:input";
|
|
20
|
+
readonly controlChanged: "daemon:computer:desktop:control-changed";
|
|
21
|
+
};
|
|
22
|
+
export declare const COMPUTER_DESKTOP_LIMITS: Readonly<{
|
|
23
|
+
maxIdChars: 160;
|
|
24
|
+
maxProtocolBase64Chars: number;
|
|
25
|
+
maxDataBase64Chars: number;
|
|
26
|
+
}>;
|
|
27
|
+
export declare const COMPUTER_DESKTOP_CLOSED_REASONS: readonly ["unavailable", "revoked", "protocol_error", "slow_viewer", "ended", "private_control"];
|
|
28
|
+
export type ComputerDesktopClosedReason = (typeof COMPUTER_DESKTOP_CLOSED_REASONS)[number];
|
|
29
|
+
export interface ComputerDesktopSubscribe {
|
|
30
|
+
attachmentId: string;
|
|
31
|
+
surfaceId: typeof COMPUTER_DESKTOP_SURFACE_ID;
|
|
32
|
+
}
|
|
33
|
+
export interface ComputerDesktopProtocol {
|
|
34
|
+
attachmentId: string;
|
|
35
|
+
streamId: string;
|
|
36
|
+
seq: number;
|
|
37
|
+
data: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ComputerDesktopUnsubscribe {
|
|
40
|
+
attachmentId: string;
|
|
41
|
+
streamId: string;
|
|
42
|
+
}
|
|
43
|
+
export interface ComputerDesktopSubscribeResult {
|
|
44
|
+
attachmentId: string;
|
|
45
|
+
generation: number;
|
|
46
|
+
streamId: string;
|
|
47
|
+
surfaceId: typeof COMPUTER_DESKTOP_SURFACE_ID;
|
|
48
|
+
lane: 'desktop';
|
|
49
|
+
readOnly: true;
|
|
50
|
+
}
|
|
51
|
+
export type DaemonComputerDesktopSubscribe = ComputerDesktopSubscribe & {
|
|
52
|
+
requestId: string;
|
|
53
|
+
generation: number;
|
|
54
|
+
};
|
|
55
|
+
export type DaemonComputerDesktopProtocol = ComputerDesktopProtocol & {
|
|
56
|
+
requestId: string;
|
|
57
|
+
generation: number;
|
|
58
|
+
};
|
|
59
|
+
export type DaemonComputerDesktopUnsubscribe = ComputerDesktopUnsubscribe & {
|
|
60
|
+
requestId: string;
|
|
61
|
+
generation: number;
|
|
62
|
+
};
|
|
63
|
+
export type ComputerDesktopData = {
|
|
64
|
+
attachmentId: string;
|
|
65
|
+
generation: number;
|
|
66
|
+
streamId: string;
|
|
67
|
+
surfaceId: typeof COMPUTER_DESKTOP_SURFACE_ID;
|
|
68
|
+
lane: 'desktop';
|
|
69
|
+
} & ({
|
|
70
|
+
kind: 'data';
|
|
71
|
+
seq: number;
|
|
72
|
+
data: string;
|
|
73
|
+
} | {
|
|
74
|
+
kind: 'closed';
|
|
75
|
+
reason: ComputerDesktopClosedReason;
|
|
76
|
+
});
|
|
77
|
+
/** Canonical RFC 4648 base64 including zero pad bits; no whitespace/URL alphabet. */
|
|
78
|
+
export declare function isComputerDesktopBase64(value: unknown, maxChars: number): value is string;
|
|
79
|
+
/** Typed human input is independent of read-only RFB negotiation. */
|
|
80
|
+
export declare const COMPUTER_DESKTOP_CONTROL_CAPABILITY: "computer_desktop_control_v1";
|
|
81
|
+
export type ComputerDesktopInputEvent = {
|
|
82
|
+
type: 'pointer';
|
|
83
|
+
action: 'move' | 'down' | 'up' | 'scroll';
|
|
84
|
+
x: number;
|
|
85
|
+
y: number;
|
|
86
|
+
button?: 0 | 1 | 2;
|
|
87
|
+
deltaX?: number;
|
|
88
|
+
deltaY?: number;
|
|
89
|
+
} | {
|
|
90
|
+
type: 'key';
|
|
91
|
+
action: 'down' | 'up';
|
|
92
|
+
key: string;
|
|
93
|
+
} | {
|
|
94
|
+
type: 'text';
|
|
95
|
+
text: string;
|
|
96
|
+
};
|
|
97
|
+
export interface ComputerDesktopControl {
|
|
98
|
+
attachmentId: string;
|
|
99
|
+
streamId?: string;
|
|
100
|
+
action: 'takeover' | 'handback' | 'status' | 'recover_private';
|
|
101
|
+
expectedController?: {
|
|
102
|
+
sessionId: string;
|
|
103
|
+
turnId: string;
|
|
104
|
+
generation: number;
|
|
105
|
+
};
|
|
106
|
+
controlToken?: string;
|
|
107
|
+
epoch?: number;
|
|
108
|
+
}
|
|
109
|
+
export interface ComputerDesktopInput {
|
|
110
|
+
attachmentId: string;
|
|
111
|
+
streamId: string;
|
|
112
|
+
controlToken: string;
|
|
113
|
+
epoch: number;
|
|
114
|
+
seq: number;
|
|
115
|
+
event: ComputerDesktopInputEvent;
|
|
116
|
+
}
|
|
117
|
+
export interface ComputerDesktopControlResult {
|
|
118
|
+
attachmentId: string;
|
|
119
|
+
streamId?: string;
|
|
120
|
+
generation: number;
|
|
121
|
+
controlling: boolean;
|
|
122
|
+
private: boolean;
|
|
123
|
+
epoch: number;
|
|
124
|
+
controlToken?: string;
|
|
125
|
+
holderAttachmentId?: string;
|
|
126
|
+
holderUserId?: string;
|
|
127
|
+
state?: 'idle' | 'controlled' | 'private' | 'unavailable';
|
|
128
|
+
idleDeadline?: number;
|
|
129
|
+
}
|
|
130
|
+
export type DaemonComputerDesktopControl = ComputerDesktopControl & {
|
|
131
|
+
requestId: string;
|
|
132
|
+
generation: number;
|
|
133
|
+
userId: string;
|
|
134
|
+
label?: string;
|
|
135
|
+
};
|
|
136
|
+
export type DaemonComputerDesktopInput = ComputerDesktopInput & {
|
|
137
|
+
requestId: string;
|
|
138
|
+
generation: number;
|
|
139
|
+
userId: string;
|
|
140
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** A separately negotiated read-only desktop stream, not a browser surface or an input grant. */
|
|
2
|
+
export const COMPUTER_DESKTOP_CAPABILITY = 'computer_desktop_observe_v1';
|
|
3
|
+
export const COMPUTER_DESKTOP_SURFACE_ID = 'desktop:main';
|
|
4
|
+
export const COMPUTER_DESKTOP_EVENTS = {
|
|
5
|
+
subscribe: 'computer:desktop:subscribe',
|
|
6
|
+
protocol: 'computer:desktop:protocol',
|
|
7
|
+
unsubscribe: 'computer:desktop:unsubscribe',
|
|
8
|
+
data: 'computer:desktop:data',
|
|
9
|
+
control: 'computer:desktop:control',
|
|
10
|
+
input: 'computer:desktop:input',
|
|
11
|
+
controlChanged: 'computer:desktop:control-changed',
|
|
12
|
+
};
|
|
13
|
+
export const DAEMON_COMPUTER_DESKTOP_EVENTS = {
|
|
14
|
+
subscribe: 'daemon:computer:desktop:subscribe',
|
|
15
|
+
protocol: 'daemon:computer:desktop:protocol',
|
|
16
|
+
unsubscribe: 'daemon:computer:desktop:unsubscribe',
|
|
17
|
+
data: 'daemon:computer:desktop:data',
|
|
18
|
+
control: 'daemon:computer:desktop:control',
|
|
19
|
+
input: 'daemon:computer:desktop:input',
|
|
20
|
+
controlChanged: 'daemon:computer:desktop:control-changed',
|
|
21
|
+
};
|
|
22
|
+
export const COMPUTER_DESKTOP_LIMITS = Object.freeze({
|
|
23
|
+
maxIdChars: 160,
|
|
24
|
+
maxProtocolBase64Chars: 88 * 1024,
|
|
25
|
+
maxDataBase64Chars: 128 * 1024,
|
|
26
|
+
});
|
|
27
|
+
export const COMPUTER_DESKTOP_CLOSED_REASONS = ['unavailable', 'revoked', 'protocol_error', 'slow_viewer', 'ended', 'private_control'];
|
|
28
|
+
/** Canonical RFC 4648 base64 including zero pad bits; no whitespace/URL alphabet. */
|
|
29
|
+
export function isComputerDesktopBase64(value, maxChars) {
|
|
30
|
+
if (typeof value !== 'string' || !value.length || value.length > maxChars || value.length % 4 !== 0)
|
|
31
|
+
return false;
|
|
32
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value))
|
|
33
|
+
return false;
|
|
34
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
35
|
+
if (value.endsWith('=='))
|
|
36
|
+
return (alphabet.indexOf(value[value.length - 3]) & 15) === 0;
|
|
37
|
+
if (value.endsWith('='))
|
|
38
|
+
return (alphabet.indexOf(value[value.length - 2]) & 3) === 0;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
/** Typed human input is independent of read-only RFB negotiation. */
|
|
42
|
+
export const COMPUTER_DESKTOP_CONTROL_CAPABILITY = 'computer_desktop_control_v1';
|
|
@@ -365,7 +365,7 @@ export declare const FIRST_PARTY_HARNESS_ENGINE: Readonly<{
|
|
|
365
365
|
* `readFirstPartyHarnessEngineEnv` to read one the way the engine does and
|
|
366
366
|
* `firstPartyHarnessEngineEnvEntries` to set one every engine build honours.
|
|
367
367
|
*/
|
|
368
|
-
env: Readonly<Record<"appName" | "serverPassword" | "serverUsername" | "home" | "config" | "scriptName" | "allowUpstreamEgress" | "compatOpencode" | "noCompatPrompts" | "maxInputTokens"
|
|
368
|
+
env: Readonly<Record<"sessionId" | "appName" | "serverPassword" | "serverUsername" | "home" | "config" | "scriptName" | "allowUpstreamEgress" | "compatOpencode" | "noCompatPrompts" | "maxInputTokens", string>>;
|
|
369
369
|
}>;
|
|
370
370
|
/**
|
|
371
371
|
* Every name the engine reads for one purpose, current namespace first — the
|