@platless/axom-cli 0.1.0 → 0.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +1 -1
  2. package/dist/authScopes.js +19 -0
  3. package/dist/axom-patch/cli/README.md +110 -0
  4. package/dist/axom-patch/cli/pi-steering.json +37 -0
  5. package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +34 -0
  6. package/dist/axom-patch/cli/probes/pi-steering.mjs +80 -0
  7. package/dist/axom-patch/cli/probes/qoder-steering.mjs +89 -0
  8. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +49 -0
  9. package/dist/axom-patch/cli/qoder-background-terminal.md +81 -0
  10. package/dist/axom-patch/cli/qoder-foreground-output.md +41 -0
  11. package/dist/axom-patch/cli/qoder-steering.json +52 -0
  12. package/dist/axom-patch/cli/qoder-steering.md +30 -0
  13. package/dist/axom-patch/cli/qoder-subagent-origin.json +73 -0
  14. package/dist/axom-patch/cli/qoder-subagent-subwork.md +73 -0
  15. package/dist/axom-patch/cli/runtime.json +37 -0
  16. package/dist/axom-patch/cli/steering/claude-steering-js.patch +89 -0
  17. package/dist/axom-patch/cli/steering/claude-steering-types.patch +28 -0
  18. package/dist/axom-patch/cli/steering/codex-steering.patch +57 -0
  19. package/dist/axom-patch/cli/steering/pi-steering.js +79 -0
  20. package/dist/cli.js +9 -1
  21. package/dist/commands/auth.js +4 -13
  22. package/dist/commands/execute.js +4 -4
  23. package/dist/commands/record.js +6 -6
  24. package/dist/commands/struct.js +4 -4
  25. package/dist/commands/workstation.js +2 -2
  26. package/dist/credentials.js +2 -2
  27. package/dist/install/claude/commands.js +13 -13
  28. package/dist/mcp.js +32 -14
  29. package/dist/workstation/acp/acpAgents.js +447 -166
  30. package/dist/workstation/acp/acpClient.js +232 -18
  31. package/dist/workstation/acp/acpSession.js +502 -74
  32. package/dist/workstation/acp/activityBusy.js +43 -0
  33. package/dist/workstation/acp/activityReporting.js +54 -0
  34. package/dist/workstation/acp/adoption.js +107 -49
  35. package/dist/workstation/acp/authUrlSniffer.js +36 -0
  36. package/dist/workstation/acp/contract.js +11 -6
  37. package/dist/workstation/acp/delegationMcp.js +24 -0
  38. package/dist/workstation/acp/draftInterceptor.js +9 -0
  39. package/dist/workstation/acp/managedPatches.js +228 -0
  40. package/dist/workstation/acp/patchAssets.js +63 -0
  41. package/dist/workstation/acp/permission.js +300 -34
  42. package/dist/workstation/acp/qoderStartup.js +45 -0
  43. package/dist/workstation/acp/registry/catalog.js +263 -4
  44. package/dist/workstation/acp/registry/detect.js +55 -0
  45. package/dist/workstation/acp/sessionScope.js +19 -0
  46. package/dist/workstation/acp/sessionUpdateMapper.js +39 -21
  47. package/dist/workstation/acp/terminalLogin.js +197 -0
  48. package/dist/workstation/acp/test/fakeAgent.js +185 -9
  49. package/dist/workstation/acp/validate.js +33 -5
  50. package/dist/workstation/agentSessions.js +532 -44
  51. package/dist/workstation/channels/agent.js +7 -1
  52. package/dist/workstation/channels/computer.js +152 -4
  53. package/dist/workstation/channels/management.js +3 -3
  54. package/dist/workstation/config.js +3 -1
  55. package/dist/workstation/connection/encryptedChannel.js +88 -21
  56. package/dist/workstation/daemon.js +25 -3
  57. package/dist/workstation/desktop/binding.js +36 -0
  58. package/dist/workstation/desktop/bridge.js +212 -0
  59. package/dist/workstation/desktop/flow-control.js +52 -0
  60. package/dist/workstation/desktop/focused-text.js +130 -0
  61. package/dist/workstation/desktop/protocol.js +35 -0
  62. package/dist/workstation/fileOps.js +277 -0
  63. package/dist/workstation/filePaths.js +174 -0
  64. package/dist/workstation/fileWorker.js +81 -0
  65. package/dist/workstation/fileWorkerClient.js +121 -0
  66. package/dist/workstation/oauth/deviceAuthorization.js +21 -12
  67. package/dist/workstation/protocol/agent.js +154 -5
  68. package/dist/workstation/protocol/common.js +6 -0
  69. package/dist/workstation/protocol/computer.js +92 -3
  70. package/dist/workstation/qoderAdapter/backgroundProcess.js +106 -0
  71. package/dist/workstation/qoderAdapter/backgroundTerminal.js +262 -0
  72. package/dist/workstation/qoderAdapter/completedContinuation.js +139 -0
  73. package/dist/workstation/qoderAdapter/contract.js +78 -0
  74. package/dist/workstation/qoderAdapter/foregroundOutput.js +30 -0
  75. package/dist/workstation/qoderAdapter/history.js +87 -0
  76. package/dist/workstation/qoderAdapter/identityIndex.js +191 -0
  77. package/dist/workstation/qoderAdapter/inputQueue.js +42 -0
  78. package/dist/workstation/qoderAdapter/main.js +93 -0
  79. package/dist/workstation/qoderAdapter/mapper.js +117 -0
  80. package/dist/workstation/qoderAdapter/observerPlugin.js +245 -0
  81. package/dist/workstation/qoderAdapter/observerReceiver.js +187 -0
  82. package/dist/workstation/qoderAdapter/permissions.js +61 -0
  83. package/dist/workstation/qoderAdapter/profile.js +93 -0
  84. package/dist/workstation/qoderAdapter/sdkDriver.js +49 -0
  85. package/dist/workstation/qoderAdapter/session.js +319 -0
  86. package/dist/workstation/qoderAdapter/subagentMeta.js +28 -0
  87. package/dist/workstation/qoderAdapter/subwork.js +77 -0
  88. package/dist/workstation/qoderAdapter/taskControl.js +38 -0
  89. package/dist/workstation/qoderAdapter/taskEvent.js +26 -0
  90. package/dist/workstation/qoderAdapter/taskLedger.js +163 -0
  91. package/dist/workstation/qoderAdapter/types.js +1 -0
  92. package/dist/workstation/qoderAdapter/usage.js +37 -0
  93. package/dist/workstation/register.js +4 -0
  94. package/dist/workstation/tools/browser.js +394 -0
  95. package/dist/workstation/tools/client.js +269 -0
  96. package/dist/workstation/tools/core.js +50 -0
  97. package/dist/workstation/tools/files.js +525 -0
  98. package/dist/workstation/tools/keeperProcess.js +114 -0
  99. package/dist/workstation/tools/terminal.js +214 -0
  100. package/dist/workstation/tools/validation.js +50 -0
  101. package/dist/workstation/update/coordinator.js +29 -0
  102. package/dist/workstation/update/npmInstaller.js +2 -0
  103. package/dist/workstation/update/ptyPreflight.js +24 -0
  104. package/dist/workstation/update/taskRegistry.js +44 -20
  105. package/dist/workstation/update/watchdog.js +66 -0
  106. package/package.json +14 -4
  107. package/skills/axom/SKILL.md +55 -17
  108. package/skills/axom-definition/SKILL.md +32 -32
  109. package/skills/axom-platform/SKILL.md +39 -39
@@ -0,0 +1,212 @@
1
+ import net from 'node:net';
2
+ import { connectFocusedCdp, insertFocusedText } from './focused-text.js';
3
+ import { WebSocket } from 'ws';
4
+ import { browserCapability, resolveBrowserBinding } from './binding.js';
5
+ import { DesktopFlow } from './flow-control.js';
6
+ import { DESKTOP_LEASE_MS, DESKTOP_MAX_STREAMS, DESKTOP_QUEUE_BYTES, DESKTOP_WINDOW_BYTES } from './protocol.js';
7
+ /** Bridges only explicitly configured live providers; owns no browser or profile lifecycle. */
8
+ export class DesktopBridge {
9
+ send;
10
+ bufferedAmount;
11
+ streams = new Map();
12
+ generation = '';
13
+ wireVersion = 1;
14
+ ended = false;
15
+ timer;
16
+ binding;
17
+ constructor(binding, send, bufferedAmount = () => 0) {
18
+ this.send = send;
19
+ this.bufferedAmount = bufferedAmount;
20
+ this.binding = resolveBrowserBinding(binding, {});
21
+ this.timer = setInterval(() => { for (const s of this.streams.values())
22
+ if (Date.now() - s.lease >= DESKTOP_LEASE_MS)
23
+ this.finish(s, 'lease_expired'); }, 1000);
24
+ this.timer.unref();
25
+ }
26
+ ready(generation, wireVersion) {
27
+ for (const s of this.streams.values())
28
+ this.finish(s, 'replaced');
29
+ this.generation = generation;
30
+ this.wireVersion = wireVersion;
31
+ if (wireVersion >= 4)
32
+ this.send({ t: 'desktop_capability', ...browserCapability(this.binding) });
33
+ }
34
+ async insertText(params) {
35
+ if (!this.binding?.cdpEndpoint)
36
+ throw Error('Chinese assistance unavailable: CDP is not configured');
37
+ const stream = this.streams.get(String(params.streamId));
38
+ const active = () => !this.ended && !!stream && this.streams.get(stream.id) === stream && stream.opened &&
39
+ this.binding?.bindingId === params.bindingId && stream.generation === params.generation &&
40
+ this.generation === params.generation && Date.now() - stream.lease < DESKTOP_LEASE_MS;
41
+ return insertFocusedText(params, active, () => connectFocusedCdp(this.binding.cdpEndpoint));
42
+ }
43
+ onFrame(frame) {
44
+ if (this.ended)
45
+ return;
46
+ if (frame.t === 'desktop_open') {
47
+ this.open(frame);
48
+ return;
49
+ }
50
+ if (frame.t === 'desktop_capability')
51
+ return;
52
+ const s = this.streams.get(frame.streamId);
53
+ if (!s)
54
+ return;
55
+ if (frame.generation !== s.generation || frame.generation !== this.generation) {
56
+ this.finish(s, 'invalid_frame');
57
+ return;
58
+ }
59
+ try {
60
+ if (frame.t === 'desktop_close') {
61
+ this.finish(s, frame.code, false);
62
+ return;
63
+ }
64
+ if (frame.t === 'desktop_credit') {
65
+ s.flow.credit(frame.bytes);
66
+ s.lease = Date.now();
67
+ this.schedule(s);
68
+ return;
69
+ }
70
+ if (frame.t === 'desktop_data') {
71
+ if (!s.opened)
72
+ throw new Error('Desktop not opened');
73
+ const bytes = s.flow.receive(frame.seq, frame.data);
74
+ const consumed = (error) => {
75
+ if (!this.streams.has(s.id))
76
+ return;
77
+ if (error) {
78
+ this.finish(s, 'io_error');
79
+ return;
80
+ }
81
+ try {
82
+ s.flow.consumed(bytes.length);
83
+ this.send({ t: 'desktop_credit', streamId: s.id, generation: s.generation, bytes: bytes.length });
84
+ }
85
+ catch {
86
+ this.finish(s, 'io_error');
87
+ }
88
+ };
89
+ if (s.socket instanceof net.Socket)
90
+ s.socket.write(bytes, consumed);
91
+ else {
92
+ if (s.socket.bufferedAmount + bytes.length > DESKTOP_QUEUE_BYTES) {
93
+ this.finish(s, 'overflow');
94
+ return;
95
+ }
96
+ s.socket.send(bytes, { binary: true }, consumed);
97
+ }
98
+ }
99
+ else
100
+ this.finish(s, 'invalid_frame');
101
+ }
102
+ catch {
103
+ this.finish(s, 'invalid_frame');
104
+ }
105
+ }
106
+ close() { if (this.ended)
107
+ return; this.ended = true; clearInterval(this.timer); for (const s of this.streams.values())
108
+ this.finish(s, 'offline', false); }
109
+ open(frame) {
110
+ const refuse = (code) => this.send({ t: 'desktop_close', streamId: frame.streamId, generation: frame.generation, code });
111
+ if (this.wireVersion < 4) {
112
+ refuse('unsupported_protocol');
113
+ return;
114
+ }
115
+ if (frame.generation !== this.generation) {
116
+ refuse('invalid_frame');
117
+ return;
118
+ }
119
+ if (!this.binding || frame.bindingId !== this.binding.bindingId) {
120
+ refuse('unconfigured');
121
+ return;
122
+ }
123
+ if (this.streams.has(frame.streamId) || this.streams.size >= DESKTOP_MAX_STREAMS || this.streams.size > 0) {
124
+ refuse('busy');
125
+ return;
126
+ }
127
+ const url = new URL(this.binding.vncEndpoint);
128
+ const socket = url.protocol === 'tcp:' ? net.createConnection({ host: url.hostname.replace(/^\[|\]$/g, ''), port: Number(url.port) }) : new WebSocket(url, { maxPayload: DESKTOP_QUEUE_BYTES, perMessageDeflate: false });
129
+ const s = { id: frame.streamId, generation: frame.generation, flow: new DesktopFlow(), socket, opened: false, lease: Date.now(), connectTimer: setTimeout(() => this.finish(s, 'connect_timeout'), 10_000) };
130
+ this.streams.set(s.id, s);
131
+ const opened = () => {
132
+ if (!this.streams.has(s.id))
133
+ return;
134
+ clearTimeout(s.connectTimer);
135
+ s.opened = true;
136
+ try {
137
+ this.send({ t: 'desktop_opened', streamId: s.id, bindingId: frame.bindingId, generation: s.generation });
138
+ }
139
+ catch {
140
+ this.finish(s, 'io_error');
141
+ }
142
+ };
143
+ const data = (bytes) => {
144
+ if (!this.streams.has(s.id))
145
+ return;
146
+ try {
147
+ s.flow.enqueue(bytes);
148
+ socket.pause();
149
+ this.schedule(s);
150
+ }
151
+ catch {
152
+ this.finish(s, 'overflow');
153
+ }
154
+ };
155
+ if (socket instanceof net.Socket) {
156
+ socket.once('connect', opened);
157
+ socket.on('data', data);
158
+ }
159
+ else {
160
+ socket.once('open', opened);
161
+ socket.on('message', (raw, binary) => { if (!binary) {
162
+ this.finish(s, 'invalid_frame');
163
+ return;
164
+ } data(Array.isArray(raw) ? Buffer.concat(raw) : Buffer.from(raw)); });
165
+ }
166
+ socket.once('error', () => this.finish(s, s.opened ? 'io_error' : 'connect_failed'));
167
+ socket.once('close', () => this.finish(s, 'io_error'));
168
+ }
169
+ schedule(s) {
170
+ if (s.scheduled || !this.streams.has(s.id))
171
+ return;
172
+ // Yield after every raw block so shell/file results and channel heartbeats can run.
173
+ s.scheduled = setTimeout(() => {
174
+ s.scheduled = undefined;
175
+ if (!this.streams.has(s.id))
176
+ return;
177
+ try {
178
+ if (this.bufferedAmount() > DESKTOP_QUEUE_BYTES) {
179
+ this.finish(s, 'overflow');
180
+ return;
181
+ }
182
+ let blocks = 0;
183
+ s.flow.flush((seq, data) => this.send({ t: 'desktop_data', streamId: s.id, generation: s.generation, seq, data }), () => this.bufferedAmount() < DESKTOP_WINDOW_BYTES && blocks++ === 0);
184
+ if (s.flow.queuedBytes) {
185
+ if (s.flow.canSend)
186
+ this.schedule(s);
187
+ }
188
+ else if (s.flow.canSend)
189
+ s.socket.resume();
190
+ }
191
+ catch {
192
+ this.finish(s, 'io_error');
193
+ }
194
+ }, 1);
195
+ }
196
+ finish(s, code, notify = true) {
197
+ if (!this.streams.delete(s.id))
198
+ return;
199
+ clearTimeout(s.connectTimer);
200
+ if (s.scheduled)
201
+ clearTimeout(s.scheduled);
202
+ if (s.socket instanceof net.Socket)
203
+ s.socket.destroy();
204
+ else
205
+ s.socket.terminate();
206
+ if (notify && !this.ended)
207
+ try {
208
+ this.send({ t: 'desktop_close', streamId: s.id, generation: s.generation, code });
209
+ }
210
+ catch { /* channel closed */ }
211
+ }
212
+ }
@@ -0,0 +1,52 @@
1
+ import { desktopBytes, DESKTOP_BLOCK_BYTES, DESKTOP_WINDOW_BYTES, DESKTOP_QUEUE_BYTES } from './protocol.js';
2
+ export { DESKTOP_WINDOW_BYTES, DESKTOP_QUEUE_BYTES } from './protocol.js';
3
+ /** Independent raw-byte windows; byte queues never discard or replay an RFB block. */
4
+ export class DesktopFlow {
5
+ queue = [];
6
+ queuedBytes = 0;
7
+ sendAvailable = DESKTOP_WINDOW_BYTES;
8
+ receiveAvailable = DESKTOP_WINDOW_BYTES;
9
+ sendSeq = 0;
10
+ receiveSeq = 0;
11
+ get canSend() { return this.sendAvailable > 0; }
12
+ enqueue(bytes) {
13
+ if (this.queuedBytes + bytes.length > DESKTOP_QUEUE_BYTES || this.queue.length + Math.ceil(bytes.length / DESKTOP_BLOCK_BYTES) > 1024)
14
+ throw new Error('Desktop queue overflow');
15
+ for (let i = 0; i < bytes.length; i += DESKTOP_BLOCK_BYTES)
16
+ this.queue.push(Buffer.from(bytes.subarray(i, i + DESKTOP_BLOCK_BYTES)));
17
+ this.queuedBytes += bytes.length;
18
+ }
19
+ flush(send, writable = () => true) {
20
+ // One block per event-loop turn at the adapter keeps tool/control frames schedulable.
21
+ while (this.queue.length && this.sendAvailable && writable()) {
22
+ const head = this.queue[0];
23
+ const size = Math.min(head.length, this.sendAvailable);
24
+ const bytes = head.subarray(0, size);
25
+ if (size === head.length)
26
+ this.queue.shift();
27
+ else
28
+ this.queue[0] = head.subarray(size);
29
+ this.queuedBytes -= size;
30
+ this.sendAvailable -= size;
31
+ send(this.sendSeq++, bytes.toString('base64'));
32
+ }
33
+ }
34
+ credit(bytes) {
35
+ if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > DESKTOP_WINDOW_BYTES - this.sendAvailable)
36
+ throw new Error('Invalid desktop credit');
37
+ this.sendAvailable += bytes;
38
+ }
39
+ receive(seq, data) {
40
+ const bytes = desktopBytes(data);
41
+ if (seq !== this.receiveSeq || !Number.isSafeInteger(seq) || bytes.length > this.receiveAvailable)
42
+ throw new Error('Invalid desktop sequence/window');
43
+ this.receiveSeq++;
44
+ this.receiveAvailable -= bytes.length;
45
+ return bytes;
46
+ }
47
+ consumed(bytes) {
48
+ if (!Number.isSafeInteger(bytes) || bytes <= 0 || bytes > DESKTOP_WINDOW_BYTES - this.receiveAvailable)
49
+ throw new Error('Invalid desktop consumption');
50
+ this.receiveAvailable += bytes;
51
+ }
52
+ }
@@ -0,0 +1,130 @@
1
+ import { WebSocket } from 'ws';
2
+ import { keys, string } from '../tools/validation.js';
3
+ // activeElement on the outer document is the iframe or shadow host, not the
4
+ // actual input. Follow only the current focus chain; never search for another
5
+ // editor or force focus. Opaque cross-origin/closed roots remain noneditable.
6
+ const focusExpression = `(() => {
7
+ let e=document.activeElement;
8
+ for(let depth=0;e && depth<32;depth++) {
9
+ let child=e.shadowRoot?.activeElement;
10
+ if(!child && (e.tagName==='IFRAME' || e.tagName==='FRAME')) {
11
+ try { child=e.contentDocument?.activeElement; } catch {}
12
+ }
13
+ if(!child) break;
14
+ e=child;
15
+ }
16
+ return {focused:document.hasFocus(),editable:!!e && !e.disabled && !e.readOnly &&
17
+ (e.isContentEditable || e.tagName==='TEXTAREA' || e.tagName==='INPUT' && /^(text|search|email|url|tel|password|number)$/.test(e.type))};
18
+ })()`;
19
+ export async function connectFocusedCdp(endpoint) {
20
+ const response = await fetch(new URL('/json/version', endpoint), { signal: AbortSignal.timeout(1000), redirect: 'error' });
21
+ if (!response.ok)
22
+ throw Error('CDP unavailable');
23
+ const version = await response.json();
24
+ const url = new URL(version.webSocketDebuggerUrl ?? '');
25
+ const configured = new URL(endpoint);
26
+ if (!['ws:', 'wss:'].includes(url.protocol) || url.hostname !== configured.hostname || url.port !== configured.port || url.username || url.password)
27
+ throw Error('Invalid local CDP address');
28
+ const socket = new WebSocket(url, { maxPayload: 1024 * 1024, perMessageDeflate: false });
29
+ let counter = 0;
30
+ const pending = new Map();
31
+ const fail = () => { for (const p of pending.values()) {
32
+ clearTimeout(p.timer);
33
+ p.reject(Error('CDP unavailable'));
34
+ } pending.clear(); };
35
+ socket.on('error', fail);
36
+ socket.on('close', fail);
37
+ socket.on('message', raw => { try {
38
+ const value = JSON.parse(raw.toString());
39
+ const p = pending.get(value.id);
40
+ if (!p)
41
+ return;
42
+ pending.delete(value.id);
43
+ clearTimeout(p.timer);
44
+ value.error ? p.reject(Error('CDP operation refused')) : p.resolve(value.result);
45
+ }
46
+ catch {
47
+ socket.terminate();
48
+ } });
49
+ await new Promise((resolve, reject) => { const timer = setTimeout(() => { socket.terminate(); reject(Error('CDP unavailable')); }, 1000); socket.once('open', () => { clearTimeout(timer); resolve(); }); socket.once('error', () => { clearTimeout(timer); reject(Error('CDP unavailable')); }); });
50
+ function request(method, params = {}, sessionId) {
51
+ const id = ++counter;
52
+ return new Promise((resolve, reject) => {
53
+ const timer = setTimeout(() => { pending.delete(id); reject(Error('CDP timed out; write outcome may be unknown')); }, 1000);
54
+ pending.set(id, { resolve, reject, timer });
55
+ try {
56
+ socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }));
57
+ }
58
+ catch {
59
+ clearTimeout(timer);
60
+ pending.delete(id);
61
+ reject(Error('CDP unavailable'));
62
+ }
63
+ });
64
+ }
65
+ const sessions = new Map();
66
+ return {
67
+ pages: async () => { const { targetInfos } = await request('Target.getTargets'); return targetInfos.filter((t) => t.type === 'page').map((t) => t.targetId); },
68
+ focus: async (id) => {
69
+ let session = sessions.get(id);
70
+ if (!session) {
71
+ session = (await request('Target.attachToTarget', { targetId: id, flatten: true })).sessionId;
72
+ sessions.set(id, session);
73
+ }
74
+ const r = await request('Runtime.evaluate', { expression: focusExpression, returnByValue: true }, session);
75
+ if (r.exceptionDetails || typeof r.result?.value?.focused !== 'boolean' || typeof r.result?.value?.editable !== 'boolean')
76
+ throw Error('CDP focus unavailable');
77
+ return r.result.value;
78
+ },
79
+ insert: (id, text) => request('Input.insertText', { text }, sessions.get(id)).then(() => { }),
80
+ close: () => socket.terminate(), // Closes this debugger connection only, never Browser.close.
81
+ };
82
+ }
83
+ export async function insertFocusedText(params, active, connect) {
84
+ keys(params, ['bindingId', 'streamId', 'generation', 'text']);
85
+ for (const key of ['bindingId', 'streamId', 'generation'])
86
+ string(params, key, 128);
87
+ const text = string(params, 'text', 8192);
88
+ if (/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(text) || /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/.test(text))
89
+ throw Error('Invalid text');
90
+ const deadline = Date.now() + 6000;
91
+ const check = () => { if (!active() || Date.now() >= deadline)
92
+ throw Error('Viewer inactive, replaced or text assistance expired'); };
93
+ check();
94
+ const cdp = await connect();
95
+ try {
96
+ check();
97
+ const pages = await cdp.pages();
98
+ check();
99
+ if (pages.length > 64)
100
+ throw Error('Ambiguous focus');
101
+ const focused = [];
102
+ for (const id of pages) {
103
+ const state = await cdp.focus(id);
104
+ check();
105
+ if (state.focused)
106
+ focused.push({ id, ...state });
107
+ }
108
+ if (focused.length !== 1)
109
+ throw Error('Unique document focus unavailable');
110
+ const target = focused[0];
111
+ if (!target.editable)
112
+ throw Error('Focused element is not editable');
113
+ const final = await cdp.focus(target.id);
114
+ check();
115
+ if (!final.focused || !final.editable)
116
+ throw Error('Editable focus changed');
117
+ // Once insertion is enqueued, neither a missing reply nor takeover can establish nonexecution.
118
+ try {
119
+ await cdp.insert(target.id, text);
120
+ check();
121
+ return { ok: true };
122
+ }
123
+ catch (cause) {
124
+ throw new Error('Text outcome unknown; check remote content before sending again', { cause });
125
+ }
126
+ }
127
+ finally {
128
+ cdp.close();
129
+ }
130
+ }
@@ -0,0 +1,35 @@
1
+ export const DESKTOP_BLOCK_BYTES = 64 * 1024;
2
+ export const DESKTOP_WINDOW_BYTES = 512 * 1024;
3
+ export const DESKTOP_QUEUE_BYTES = 4 * 1024 * 1024;
4
+ export const DESKTOP_MAX_STREAMS = 4;
5
+ export const DESKTOP_LEASE_MS = 45_000;
6
+ export const DESKTOP_CLOSE_CODES = ['normal', 'unconfigured', 'busy', 'unsupported_protocol', 'invalid_frame', 'connect_failed', 'connect_timeout', 'io_error', 'overflow', 'lease_expired', 'offline', 'replaced'];
7
+ export function desktopBytes(data) {
8
+ if (!data || data.length > Math.ceil(DESKTOP_BLOCK_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(data))
9
+ throw new Error('Invalid desktop bytes');
10
+ const bytes = Buffer.from(data, 'base64');
11
+ if (bytes.length > DESKTOP_BLOCK_BYTES || bytes.toString('base64') !== data)
12
+ throw new Error('Invalid desktop bytes');
13
+ return bytes;
14
+ }
15
+ export function validDesktopFrame(frame) {
16
+ const id = (v) => typeof v === 'string' && v.length > 0 && v.length <= 128;
17
+ const keys = (allowed) => Object.keys(frame).every(k => allowed.includes(k));
18
+ if (frame.t === 'desktop_capability')
19
+ return keys(['t', 'bindingId', 'displayName', 'configured', 'cdp']) && typeof frame.configured === 'boolean' && typeof frame.cdp === 'boolean' && (frame.bindingId === null || id(frame.bindingId)) && (frame.displayName === null || id(frame.displayName)) && (frame.configured ? frame.bindingId !== null && frame.displayName !== null : frame.bindingId === null && frame.displayName === null && frame.cdp === false);
20
+ if (!id(frame.streamId) || !id(frame.generation))
21
+ return false;
22
+ switch (frame.t) {
23
+ case 'desktop_open':
24
+ case 'desktop_opened': return id(frame.bindingId) && keys(['t', 'streamId', 'generation', 'bindingId']);
25
+ case 'desktop_data': try {
26
+ return keys(['t', 'streamId', 'generation', 'seq', 'data']) && Number.isSafeInteger(frame.seq) && frame.seq >= 0 && typeof frame.data === 'string' && desktopBytes(frame.data).length > 0;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ case 'desktop_credit': return keys(['t', 'streamId', 'generation', 'bytes']) && Number.isSafeInteger(frame.bytes) && frame.bytes >= 0 && frame.bytes <= DESKTOP_WINDOW_BYTES;
32
+ case 'desktop_close': return keys(['t', 'streamId', 'generation', 'code']) && DESKTOP_CLOSE_CODES.includes(frame.code);
33
+ default: return false;
34
+ }
35
+ }