@platless/axom-cli 0.5.9 → 0.6.52

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 (258) hide show
  1. package/README.md +25 -6
  2. package/dist/axom-patch/cli/README.md +129 -12
  3. package/dist/axom-patch/cli/account-limits/generate.py +15 -12
  4. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
  5. package/dist/axom-patch/cli/claude-account-limits.json +9 -9
  6. package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
  7. package/dist/axom-patch/cli/codex-account-limits.json +11 -11
  8. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  9. package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
  10. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  11. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  12. package/dist/axom-patch/cli/nightly-task.md +55 -13
  13. package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
  14. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  15. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  16. package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
  17. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
  18. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
  19. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
  20. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
  21. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
  22. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  23. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  24. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  25. package/dist/axom-patch/cli/runtime.json +19 -19
  26. package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
  27. package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
  28. package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
  29. package/dist/axom-patch/cli/zcode-env-account.json +58 -0
  30. package/dist/axom-patch/cli/zcode-models.json +9 -10
  31. package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
  32. package/dist/cli.js +11 -45
  33. package/dist/commands/auth.js +4 -1
  34. package/dist/commands/knowledge.js +47 -0
  35. package/dist/commands/update.js +126 -3
  36. package/dist/commands/workstation.js +101 -164
  37. package/dist/commands/workstationRegistrations.js +116 -0
  38. package/dist/credentialBroker.js +13 -2
  39. package/dist/credentials.js +16 -0
  40. package/dist/install/claude/commands.js +15 -15
  41. package/dist/keepers/terminal/keeper.mjs +1089 -673
  42. package/dist/keepers/terminal/manifest.json +4 -4
  43. package/dist/mcp.js +1 -1
  44. package/dist/native/darwin-x64/axom-quic +0 -0
  45. package/dist/workstation/acp/acpAgents.js +113 -102
  46. package/dist/workstation/acp/acpClient.js +26 -0
  47. package/dist/workstation/acp/builds/install/npm.js +99 -10
  48. package/dist/workstation/acp/initializeResponse.js +6 -1
  49. package/dist/workstation/acp/managedPatches.js +189 -25
  50. package/dist/workstation/acp/managedRuntime.js +314 -0
  51. package/dist/workstation/acp/outbox.js +12 -5
  52. package/dist/workstation/acp/patchAssets.js +40 -0
  53. package/dist/workstation/acp/permission.js +2 -2
  54. package/dist/workstation/acp/processor/checkpoint.js +26 -34
  55. package/dist/workstation/acp/processor/control.js +7 -80
  56. package/dist/workstation/acp/processor/jsonrpc.js +8 -0
  57. package/dist/workstation/acp/processor/runtime.js +5 -81
  58. package/dist/workstation/acp/processor/session.js +54 -82
  59. package/dist/workstation/acp/processor/state.js +7 -13
  60. package/dist/workstation/acp/processor/step.js +3 -20
  61. package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
  62. package/dist/workstation/acp/processor/transport/local.js +19 -3
  63. package/dist/workstation/acp/processor/updates.js +156 -105
  64. package/dist/workstation/acp/processorSession.js +20 -173
  65. package/dist/workstation/acp/registry/catalog.js +65 -39
  66. package/dist/workstation/acp/registry/launch.js +16 -1
  67. package/dist/workstation/acp/switching.js +0 -3
  68. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  69. package/dist/workstation/acp/test/keeperWorld.js +32 -3
  70. package/dist/workstation/acp/test/raw/sources.js +16 -130
  71. package/dist/workstation/acp/test/realAdapter.js +1 -47
  72. package/dist/workstation/acp/test/scriptAgent.js +11 -3
  73. package/dist/workstation/agentFlow.js +1 -1
  74. package/dist/workstation/agentSessions.js +114 -166
  75. package/dist/workstation/channels/agent.js +9 -1
  76. package/dist/workstation/channels/computer.js +27 -4
  77. package/dist/workstation/channels/management.js +130 -15
  78. package/dist/workstation/channels/url.js +1 -1
  79. package/dist/workstation/config.js +33 -9
  80. package/dist/workstation/connection/encryptedChannel.js +4 -1
  81. package/dist/workstation/control/bindingStore.js +204 -0
  82. package/dist/workstation/control/commands.js +125 -0
  83. package/dist/workstation/control/daemonIdentity.js +143 -0
  84. package/dist/workstation/control/deviceCommands.js +191 -0
  85. package/dist/workstation/control/deviceProtocol.js +68 -0
  86. package/dist/workstation/control/enrollment.js +106 -0
  87. package/dist/workstation/control/inspect.js +71 -0
  88. package/dist/workstation/control/locks.js +110 -0
  89. package/dist/workstation/control/protocol.js +40 -0
  90. package/dist/workstation/control/restartGuard.js +46 -0
  91. package/dist/workstation/control/rootFormat.js +16 -0
  92. package/dist/workstation/control/runtimeClient.js +152 -0
  93. package/dist/workstation/control/runtimeHost.js +235 -0
  94. package/dist/workstation/control/serverIdentity.js +35 -0
  95. package/dist/workstation/control/updateTransition.js +91 -0
  96. package/dist/workstation/daemon.js +266 -264
  97. package/dist/workstation/daemonctl.js +226 -25
  98. package/dist/workstation/desktopControl/audio.js +99 -0
  99. package/dist/workstation/desktopControl/automationDevice.js +219 -0
  100. package/dist/workstation/desktopControl/automationJson.js +74 -0
  101. package/dist/workstation/desktopControl/automationLocalState.js +23 -0
  102. package/dist/workstation/desktopControl/automationProtocol.js +253 -0
  103. package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
  104. package/dist/workstation/desktopControl/automationSession.js +81 -0
  105. package/dist/workstation/desktopControl/automationTransport.js +138 -0
  106. package/dist/workstation/desktopControl/capabilities.js +93 -0
  107. package/dist/workstation/desktopControl/captureModes.js +9 -0
  108. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  109. package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
  110. package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
  111. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  112. package/dist/workstation/desktopControl/helperProcess.js +602 -0
  113. package/dist/workstation/desktopControl/hostGrant.js +65 -0
  114. package/dist/workstation/desktopControl/lobby.js +131 -0
  115. package/dist/workstation/desktopControl/macProvider.js +156 -0
  116. package/dist/workstation/desktopControl/media.js +331 -0
  117. package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
  118. package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
  119. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  120. package/dist/workstation/desktopControl/overview.js +58 -0
  121. package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
  122. package/dist/workstation/desktopControl/protocol.js +311 -0
  123. package/dist/workstation/desktopControl/provider.js +1 -0
  124. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  125. package/dist/workstation/desktopControl/rateController.js +226 -0
  126. package/dist/workstation/desktopControl/supervisor.js +1774 -0
  127. package/dist/workstation/desktopControl/transport.js +27 -0
  128. package/dist/workstation/desktopControl/wallpaper.js +98 -0
  129. package/dist/workstation/exec/policy.js +5 -0
  130. package/dist/workstation/files/client.js +10 -1
  131. package/dist/workstation/files/credentials.js +142 -0
  132. package/dist/workstation/files/host.js +9 -2
  133. package/dist/workstation/files/text.js +42 -8
  134. package/dist/workstation/journal.js +5 -2
  135. package/dist/workstation/keepers/acp/client.js +38 -2
  136. package/dist/workstation/keepers/kit/buildVersion.js +35 -11
  137. package/dist/workstation/keepers/kit/bundle.js +21 -17
  138. package/dist/workstation/keepers/kit/client.js +2 -2
  139. package/dist/workstation/keepers/kit/launcher.js +123 -7
  140. package/dist/workstation/keepers/kit/paths.js +2 -1
  141. package/dist/workstation/keepers/legacy.js +16 -2
  142. package/dist/workstation/keepers/policy.js +5 -34
  143. package/dist/workstation/keepers/terminal/client.js +115 -23
  144. package/dist/workstation/keepers/terminal/exec.js +6 -3
  145. package/dist/workstation/keepers/terminal/frames.js +73 -0
  146. package/dist/workstation/keepers/terminal/generations.js +5 -8
  147. package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
  148. package/dist/workstation/keepers/terminal/input.js +146 -0
  149. package/dist/workstation/keepers/terminal/keeper.js +22 -8
  150. package/dist/workstation/keepers/terminal/main.js +1 -1
  151. package/dist/workstation/keepers/terminal/metadata.js +138 -0
  152. package/dist/workstation/keepers/terminal/protocol.js +4 -28
  153. package/dist/workstation/keepers/terminal/pty.js +1 -330
  154. package/dist/workstation/keepers/terminal/screen.js +246 -0
  155. package/dist/workstation/keepers/terminal/sessions.js +526 -0
  156. package/dist/workstation/managedInstall.js +57 -0
  157. package/dist/workstation/oauth/deviceAuthorization.js +31 -17
  158. package/dist/workstation/protocol/agent.js +2 -15
  159. package/dist/workstation/protocol/management.js +113 -5
  160. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  161. package/dist/workstation/rd/artifact.js +120 -0
  162. package/dist/workstation/rd/control.js +117 -0
  163. package/dist/workstation/rd/display.js +29 -0
  164. package/dist/workstation/rd/host.js +213 -0
  165. package/dist/workstation/rd/migrationReadiness.js +46 -0
  166. package/dist/workstation/rd/provider.js +256 -0
  167. package/dist/workstation/rd/sessions.js +391 -0
  168. package/dist/workstation/rd/state.js +57 -0
  169. package/dist/workstation/rd/update.js +168 -0
  170. package/dist/workstation/rd/upkeep.js +80 -0
  171. package/dist/workstation/register.js +27 -37
  172. package/dist/workstation/registrations/enrollment.js +250 -0
  173. package/dist/workstation/registrations/enrollmentPending.js +361 -0
  174. package/dist/workstation/registrations/migration.js +356 -0
  175. package/dist/workstation/registrations/model.js +34 -0
  176. package/dist/workstation/registrations/removal.js +222 -0
  177. package/dist/workstation/registrations/store.js +737 -0
  178. package/dist/workstation/runtime/desktopBinding.js +20 -0
  179. package/dist/workstation/runtime/deviceDesktop.js +193 -0
  180. package/dist/workstation/runtime/deviceRuntime.js +568 -0
  181. package/dist/workstation/runtime/deviceServices.js +117 -0
  182. package/dist/workstation/runtime/events.js +20 -0
  183. package/dist/workstation/runtime/ownership.js +100 -0
  184. package/dist/workstation/runtime/registrationRuntime.js +575 -0
  185. package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
  186. package/dist/workstation/runtime/removalActivity.js +184 -0
  187. package/dist/workstation/service/installer.js +197 -14
  188. package/dist/workstation/service/launchd.js +2 -0
  189. package/dist/workstation/service/systemd.js +1 -0
  190. package/dist/workstation/storageMetrics.js +118 -0
  191. package/dist/workstation/systemMetrics.js +47 -92
  192. package/dist/workstation/toolchain.js +94 -0
  193. package/dist/workstation/tools/browser.js +28 -4
  194. package/dist/workstation/tools/client.js +8 -27
  195. package/dist/workstation/tools/info.js +6 -13
  196. package/dist/workstation/tools/terminalRouter.js +17 -172
  197. package/dist/workstation/tools/validation.js +55 -1
  198. package/dist/workstation/update/archiveEvidence.js +206 -0
  199. package/dist/workstation/update/artifact.js +2 -1
  200. package/dist/workstation/update/coordinator.js +34 -13
  201. package/dist/workstation/update/deviceCompatibility.js +119 -0
  202. package/dist/workstation/update/deviceCoordinator.js +369 -0
  203. package/dist/workstation/update/deviceHealth.js +104 -0
  204. package/dist/workstation/update/deviceHealthProtocol.js +26 -0
  205. package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
  206. package/dist/workstation/update/deviceRollback.js +78 -0
  207. package/dist/workstation/update/globalInstall.js +186 -0
  208. package/dist/workstation/update/legacyBridge.js +414 -0
  209. package/dist/workstation/update/legacyOwner.js +39 -0
  210. package/dist/workstation/update/npmInstaller.js +51 -25
  211. package/dist/workstation/update/privateDurability.js +61 -0
  212. package/dist/workstation/update/rdRollback.js +145 -0
  213. package/dist/workstation/update/recoveryPlan.js +124 -0
  214. package/dist/workstation/update/runUpdate.js +10 -3
  215. package/dist/workstation/update/taskRegistry.js +161 -3
  216. package/dist/workstation/update/watchdog.js +29806 -333
  217. package/dist/workstation/update/watchdogProcess.js +72 -0
  218. package/package.json +11 -7
  219. package/skills/axom/SKILL.md +11 -11
  220. package/skills/axom-definition/SKILL.md +3 -3
  221. package/skills/axom-platform/SKILL.md +3 -3
  222. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  223. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
  224. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  225. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  226. package/dist/commands/guide.js +0 -38
  227. package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
  228. package/dist/runtime-deps/qoder-sdk/package.json +0 -8
  229. package/dist/workstation/acp/delegationMcp.js +0 -24
  230. package/dist/workstation/acp/processor/sideChannel.js +0 -80
  231. package/dist/workstation/acp/qoderStartup.js +0 -45
  232. package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
  233. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
  234. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
  235. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
  236. package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
  237. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  238. package/dist/workstation/qoderAdapter/contract.js +0 -78
  239. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
  240. package/dist/workstation/qoderAdapter/history.js +0 -85
  241. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  242. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  243. package/dist/workstation/qoderAdapter/main.js +0 -95
  244. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  245. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  246. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  247. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  248. package/dist/workstation/qoderAdapter/profile.js +0 -93
  249. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
  250. package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
  251. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
  252. package/dist/workstation/qoderAdapter/session.js +0 -319
  253. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  254. package/dist/workstation/qoderAdapter/subwork.js +0 -85
  255. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  256. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  257. package/dist/workstation/qoderAdapter/usage.js +0 -37
  258. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/automationPreparation.js} +0 -0
@@ -0,0 +1,213 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomBytes, createHash, randomUUID } from 'node:crypto';
4
+ import { spawn } from 'node:child_process';
5
+ import { createConnection } from 'node:net';
6
+ import { NativeDeviceAdmission } from '../desktopControl/nativeDeviceAdmission.js';
7
+ import { bounded } from '../desktopControl/helperProcess.js';
8
+ import { privateDirectory } from '../keepers/kit/paths.js';
9
+ import { readDeviceHostDescriptor, deviceHostGrantSchema } from '../desktopControl/hostGrant.js';
10
+ export class LinuxDeviceHost {
11
+ directory;
12
+ cleanupDescriptor;
13
+ static pending = new Map();
14
+ child;
15
+ owner;
16
+ exit;
17
+ stopCommandId = randomUUID();
18
+ admission;
19
+ grant;
20
+ bootstrapGrant;
21
+ enableAttempted = false;
22
+ published = false;
23
+ failed = false;
24
+ exitObserved = false;
25
+ constructor(directory, cleanupDescriptor) {
26
+ this.directory = directory;
27
+ this.cleanupDescriptor = cleanupDescriptor;
28
+ }
29
+ static async recoverPending(root) {
30
+ const host = this.pending.get(root);
31
+ if (!host)
32
+ return;
33
+ if (host.failed && !host.enableAttempted && !host.published && host.exitObserved) {
34
+ host.owner?.destroy();
35
+ await fs.rm(path.join(host.directory, 'launch.json'), { force: true });
36
+ await fs.rm(path.join(host.directory, 'host.sock'), { force: true });
37
+ await fs.rmdir(host.directory).catch(error => { if (error.code !== 'ENOENT')
38
+ throw error; });
39
+ this.pending.delete(root);
40
+ return;
41
+ }
42
+ if (!host.admission) {
43
+ const original = host.bootstrapGrant;
44
+ if (!original)
45
+ throw Error('native_host_launch_unconfirmed');
46
+ host.admission = await NativeDeviceAdmission.connect(original);
47
+ host.grant = { ...original, hostGeneration: host.admission.hostLifetimeId };
48
+ }
49
+ return host;
50
+ }
51
+ static async launch(root, executable, environment) {
52
+ if (this.pending.has(root))
53
+ throw Error('original_native_host_unconfirmed');
54
+ const cleanup = path.join(root, 'desktop-cleanup');
55
+ await privateDirectory(cleanup);
56
+ const directory = await fs.mkdtemp('/tmp/axom-device-host-');
57
+ await fs.chmod(directory, 0o700);
58
+ const host = new LinuxDeviceHost(directory, path.join(cleanup, randomBytes(16).toString('hex') + '.json'));
59
+ const binary = await fs.realpath(executable), sha256 = createHash('sha256').update(await fs.readFile(binary)).digest('hex');
60
+ const endpoint = { socket: path.join(directory, 'host.sock'), token: randomBytes(32).toString('hex') };
61
+ host.bootstrapGrant = { schemaVersion: 2, executable: binary, sha256, ...endpoint };
62
+ const bootstrap = path.join(directory, 'launch.json');
63
+ await fs.writeFile(bootstrap, JSON.stringify({ ...endpoint, parentPid: process.pid, cleanupDescriptor: host.cleanupDescriptor, executable: binary, sha256 }), { flag: 'wx', mode: 0o600 });
64
+ this.pending.set(root, host);
65
+ const child = spawn(binary, ['--device-host', bootstrap], { env: { ...environment, AXOM_ACCESS_TOKEN: undefined }, stdio: 'ignore' });
66
+ host.child = child;
67
+ host.exit = new Promise((resolve, reject) => { child.once('exit', () => { host.exitObserved = true; resolve(); }); child.once('error', reject); });
68
+ void host.exit.catch(() => { });
69
+ try {
70
+ const deadline = Date.now() + 5000;
71
+ while (Date.now() < deadline) {
72
+ if (child.exitCode !== null)
73
+ throw new Error('native_host_launch_failed');
74
+ try {
75
+ await fs.lstat(endpoint.socket);
76
+ break;
77
+ }
78
+ catch (error) {
79
+ if (error.code !== 'ENOENT')
80
+ throw error;
81
+ }
82
+ await new Promise(resolve => setTimeout(resolve, 10));
83
+ }
84
+ const owner = createConnection(endpoint.socket);
85
+ host.owner = owner;
86
+ await bounded(new Promise((resolve, reject) => {
87
+ owner.once('error', reject);
88
+ owner.once('connect', () => owner.write(endpoint.token));
89
+ owner.once('data', bytes => { if (bytes.length !== 1 || bytes[0] !== 1) {
90
+ reject(new Error('native_host_authentication_failed'));
91
+ return;
92
+ } owner.write('owner\n'); resolve(); });
93
+ owner.once('end', () => reject(new Error('native_host_owner_closed')));
94
+ }), 5000);
95
+ host.admission = await NativeDeviceAdmission.connect(endpoint);
96
+ host.grant = { schemaVersion: 2, hostGeneration: host.admission.hostLifetimeId, executable: binary, sha256, ...endpoint };
97
+ host.published = true;
98
+ await host.persistOriginal();
99
+ await new Promise((resolve, reject) => {
100
+ const peer = createConnection(endpoint.socket);
101
+ const timer = setTimeout(() => { peer.destroy(); reject(new Error('native_host_enable_unconfirmed')); }, 5000);
102
+ let authenticated = false, bytes = Buffer.alloc(0);
103
+ peer.once('connect', () => peer.write(endpoint.token));
104
+ peer.once('error', error => { clearTimeout(timer); reject(error); });
105
+ peer.on('data', chunk => { bytes = Buffer.concat([bytes, chunk]); if (!authenticated) {
106
+ if (bytes[0] !== 1) {
107
+ clearTimeout(timer);
108
+ peer.destroy();
109
+ reject(new Error('native_host_authentication_failed'));
110
+ return;
111
+ }
112
+ authenticated = true;
113
+ bytes = bytes.subarray(1);
114
+ host.enableAttempted = true;
115
+ peer.write('enable\n');
116
+ } if (bytes.includes(10)) {
117
+ clearTimeout(timer);
118
+ peer.destroy();
119
+ try {
120
+ if (JSON.stringify(JSON.parse(bytes.subarray(0, bytes.indexOf(10)).toString())) !== '{}')
121
+ throw Error();
122
+ resolve();
123
+ }
124
+ catch {
125
+ reject(new Error('native_host_enable_unconfirmed'));
126
+ }
127
+ } });
128
+ });
129
+ this.pending.delete(root);
130
+ return host;
131
+ }
132
+ catch (error) {
133
+ host.failed = true;
134
+ host.owner?.destroy();
135
+ throw error;
136
+ }
137
+ }
138
+ async persistOriginal() {
139
+ const expected = deviceHostGrantSchema.parse(this.grant);
140
+ try {
141
+ if (JSON.stringify(await readDeviceHostDescriptor(this.cleanupDescriptor)) !== JSON.stringify(expected))
142
+ throw Error('original_native_host_unconfirmed');
143
+ return;
144
+ }
145
+ catch (error) {
146
+ if (error.code !== 'ENOENT')
147
+ throw error;
148
+ }
149
+ const staging = this.cleanupDescriptor + '.writing-' + randomUUID();
150
+ const file = await fs.open(staging, 'wx', 0o600);
151
+ try {
152
+ await file.writeFile(JSON.stringify(expected));
153
+ await file.sync();
154
+ await file.close();
155
+ try {
156
+ await fs.link(staging, this.cleanupDescriptor);
157
+ }
158
+ catch (error) {
159
+ if (error.code !== 'EEXIST')
160
+ throw error;
161
+ }
162
+ if (JSON.stringify(await readDeviceHostDescriptor(this.cleanupDescriptor)) !== JSON.stringify(expected))
163
+ throw Error('original_native_host_unconfirmed');
164
+ }
165
+ finally {
166
+ await file.close();
167
+ await fs.unlink(staging);
168
+ }
169
+ }
170
+ async stopOriginal() {
171
+ let failure;
172
+ try {
173
+ await this.persistOriginal();
174
+ }
175
+ catch (error) {
176
+ failure = error;
177
+ }
178
+ try {
179
+ await this.admission.stopHost(this.stopCommandId);
180
+ }
181
+ catch (error) {
182
+ failure ??= error;
183
+ }
184
+ if (failure)
185
+ throw failure;
186
+ }
187
+ async finalizeOriginal() {
188
+ this.owner?.destroy();
189
+ if (this.exit)
190
+ try {
191
+ await bounded(this.exit, 5000);
192
+ }
193
+ catch (error) {
194
+ this.child?.kill('SIGTERM');
195
+ await bounded(this.exit, 5000);
196
+ }
197
+ try {
198
+ const descriptor = await readDeviceHostDescriptor(this.cleanupDescriptor);
199
+ if (descriptor.hostGeneration === this.grant.hostGeneration && descriptor.token === this.grant.token && descriptor.socket === this.grant.socket)
200
+ await fs.unlink(this.cleanupDescriptor);
201
+ }
202
+ catch (error) {
203
+ if (error.code !== 'ENOENT')
204
+ throw error;
205
+ }
206
+ await fs.rm(path.join(this.directory, 'launch.json'), { force: true });
207
+ await fs.rmdir(this.directory).catch(error => { if (error.code !== 'ENOENT')
208
+ throw error; });
209
+ for (const [root, original] of LinuxDeviceHost.pending)
210
+ if (original === this)
211
+ LinuxDeviceHost.pending.delete(root);
212
+ }
213
+ }
@@ -0,0 +1,46 @@
1
+ import path from 'node:path';
2
+ import { promises as fs } from 'node:fs';
3
+ import { execFile } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ import { z } from 'zod';
6
+ import { installRd, rdPlatform, verifyRdInstallation } from './artifact.js';
7
+ import { readRdState } from './state.js';
8
+ import { withPrivateLock } from '../control/locks.js';
9
+ const execute = promisify(execFile);
10
+ const capabilities = z.strictObject({ deviceAdmissionProtocol: z.literal(1), deviceHost: z.literal(true) });
11
+ /** Stage only. The caller must retain the legacy physical fence: qualification is not retirement
12
+ * of predecessor capture/input authority, and must never activate this state on its own. */
13
+ export async function prepareLegacyRdReadiness(directory, options = {}) {
14
+ return withPrivateLock(path.join(directory, 'rd', 'install'), async () => {
15
+ const previous = await readRdState(directory);
16
+ if (!previous?.enabled)
17
+ return { kind: 'not-enabled' };
18
+ const qualify = async (installation) => {
19
+ await verifyRdInstallation(installation.directory, installation.artifact);
20
+ const result = await (options.execute ?? execute)(path.join(installation.directory, 'bin/axom-desktop-keeper'), ['--device-capabilities'], {
21
+ timeout: 5000, maxBuffer: 8192,
22
+ env: { ...process.env, AXOM_ACCESS_TOKEN: undefined, AXOM_DEVICE_SCOPE: undefined, AXOM_DEVICE_CONTEXT: undefined,
23
+ AXOM_KEEPER_SOCKET: undefined, AXOM_KEEPER_TOKEN: undefined },
24
+ });
25
+ capabilities.parse(JSON.parse(result.stdout));
26
+ };
27
+ try {
28
+ await qualify({ directory: previous.installation, artifact: previous.artifact });
29
+ return { kind: 'ready', state: previous };
30
+ }
31
+ catch {
32
+ // A legacy --self-test or IPC version says nothing about device admission support.
33
+ }
34
+ try {
35
+ const platform = options.platform ?? rdPlatform(await fs.readFile('/etc/os-release', 'utf8'), process.arch);
36
+ const candidate = await (options.install ?? installRd)(directory, previous.artifactSource, platform);
37
+ await qualify(candidate);
38
+ if (JSON.stringify(await readRdState(directory)) !== JSON.stringify(previous))
39
+ throw Error('Remote desktop selection changed during migration staging');
40
+ return { kind: 'staged', state: { ...previous, installation: candidate.directory, artifact: candidate.artifact } };
41
+ }
42
+ catch (error) {
43
+ return { kind: 'unavailable', reason: `Remote desktop requires a qualified device host: ${error instanceof Error ? error.message : String(error)}` };
44
+ }
45
+ });
46
+ }
@@ -0,0 +1,256 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFile } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ import { z } from 'zod';
6
+ import { NativeProvider } from '../desktopControl/nativeProvider.js';
7
+ import { HelperProcess, NativeCommandError } from '../desktopControl/helperProcess.js';
8
+ import { desktopCapabilitiesSchema } from '../desktopControl/protocol.js';
9
+ import { captureTargetLabel } from '../desktopControl/captureTargetLabel.js';
10
+ import { privateDirectory } from '../keepers/kit/paths.js';
11
+ import { verifyRdInstallation } from './artifact.js';
12
+ import { readRdState } from './state.js';
13
+ import { chooseSession, discoverSessions, ensureManagedSession, sessionAlive } from './sessions.js';
14
+ import { desktopDisplayControl, parseXrandr } from './display.js';
15
+ import { LinuxDeviceHost } from './host.js';
16
+ const execute = promisify(execFile);
17
+ const sourceAxis = z.number().int().min(2).max(4096);
18
+ const geometrySchema = z.object({ width: sourceAxis, height: sourceAxis, audio: z.boolean().optional() });
19
+ const windowSchema = z.array(z.object({ id: z.string().regex(/^[a-f0-9]{32}$/), kind: z.literal('window'), label: z.string().max(4096), width: sourceAxis, height: sourceAxis })).max(127);
20
+ export class LinuxDesktopProvider {
21
+ directory;
22
+ server;
23
+ legacy;
24
+ host;
25
+ get deviceAdmission() { return this.host?.admission; }
26
+ static async connect(directory) {
27
+ if (!(await readRdState(directory))?.enabled)
28
+ return;
29
+ const provider = new LinuxDesktopProvider(directory, '');
30
+ await provider.initialize();
31
+ if (!provider.host)
32
+ throw new NativeCommandError('unavailable');
33
+ return provider;
34
+ }
35
+ async stopOriginal() { if (this.host)
36
+ await this.host.stopOriginal(); }
37
+ async finalizeOriginal() { if (this.host)
38
+ await this.host.finalizeOriginal(); }
39
+ async retirementRequired() {
40
+ const current = await readRdState(this.directory);
41
+ return !current?.enabled || !this.state || current.artifact.sha512 !== this.state.artifact.sha512 || current.session !== this.state.session || current.display !== this.state.display || !this.session || !await sessionAlive(this.session);
42
+ }
43
+ async prepareAutomation(binding) {
44
+ await this.initialize();
45
+ if (!this.usingLegacy)
46
+ await this.validate();
47
+ if (!this.provider?.prepareAutomation)
48
+ throw new NativeCommandError('unsupported_automation_protocol');
49
+ return this.provider.prepareAutomation(binding);
50
+ }
51
+ session;
52
+ provider;
53
+ state;
54
+ runtime = '';
55
+ revision = 0;
56
+ geometryIdentity = '';
57
+ initialization;
58
+ usingLegacy = false;
59
+ targetRevisions = new Map();
60
+ constructor(directory, server, legacy) {
61
+ this.directory = directory;
62
+ this.server = server;
63
+ this.legacy = legacy;
64
+ }
65
+ get automaticDpi() {
66
+ if (this.usingLegacy)
67
+ return true;
68
+ const setting = this.session?.environment.AXOM_DESKTOP_AUTO_DPI;
69
+ if (setting !== undefined)
70
+ return setting === '1';
71
+ return this.legacy !== undefined && this.session?.managed === true && this.session.environment.AXOM_DESKTOP_OWNED_DUMMY === '1';
72
+ }
73
+ async validate(session = this.session) {
74
+ const current = await readRdState(this.directory);
75
+ if (!current?.enabled || !this.state || current.artifact.sha512 !== this.state.artifact.sha512 ||
76
+ current.session !== this.state.session || current.display !== this.state.display ||
77
+ !session || this.session !== session || !await sessionAlive(session)) {
78
+ throw new NativeCommandError('unavailable');
79
+ }
80
+ }
81
+ async initialize() {
82
+ if (this.initialization)
83
+ return this.initialization;
84
+ this.initialization = this.initializeCurrent();
85
+ try {
86
+ await this.initialization;
87
+ }
88
+ finally {
89
+ this.initialization = undefined;
90
+ }
91
+ }
92
+ async initializeCurrent() {
93
+ const state = await readRdState(this.directory);
94
+ this.usingLegacy = state === undefined && this.legacy !== undefined;
95
+ if (this.usingLegacy) {
96
+ this.provider = this.legacy;
97
+ this.state = undefined;
98
+ return;
99
+ }
100
+ if (!state?.enabled) {
101
+ this.provider = undefined;
102
+ this.state = undefined;
103
+ return;
104
+ }
105
+ if (this.host) {
106
+ if (await this.retirementRequired())
107
+ throw new NativeCommandError('unavailable');
108
+ return;
109
+ }
110
+ if (this.provider && this.state?.artifact.sha512 === state.artifact.sha512 && this.state.session === state.session && this.state.display === state.display && this.session && await sessionAlive(this.session))
111
+ return;
112
+ this.provider = undefined;
113
+ this.session = undefined;
114
+ await verifyRdInstallation(state.installation, state.artifact);
115
+ const session = chooseSession(await discoverSessions(), state) ?? await ensureManagedSession(this.directory);
116
+ this.session = session;
117
+ this.state = state;
118
+ const runtimeRoot = path.join(session.environment.XDG_RUNTIME_DIR ?? `/run/user/${process.getuid?.()}`, 'axom-rd');
119
+ await privateDirectory(runtimeRoot);
120
+ this.runtime = session.managed && session.environment.AXOM_DESKTOP_RUNTIME_DIR && path.isAbsolute(session.environment.AXOM_DESKTOP_RUNTIME_DIR) ?
121
+ session.environment.AXOM_DESKTOP_RUNTIME_DIR : path.join(runtimeRoot, session.identity);
122
+ await privateDirectory(this.runtime);
123
+ const executable = path.join(state.installation, 'bin/axom-desktop-keeper');
124
+ const originalHost = await LinuxDeviceHost.launch(this.directory, executable, this.environment());
125
+ this.host = originalHost;
126
+ const originalEnvironment = { ...this.environment(), AXOM_KEEPER_SOCKET: originalHost.grant.socket, AXOM_KEEPER_TOKEN: originalHost.grant.token };
127
+ this.provider = new NativeProvider({ helperPath: executable, runtimeDir: this.runtime,
128
+ deviceAdmission: originalHost.admission, environment: originalEnvironment, enumerate: () => this.capabilities(), validateSession: () => this.validate(session),
129
+ sessionHelperFactory: async (binding) => {
130
+ await this.validate(session);
131
+ const helper = await HelperProcess.launch(executable, { ...originalEnvironment, AXOM_KEEPER_TARGET: binding.displayId,
132
+ AXOM_DEVICE_CONTEXT: originalHost.admission.context(binding.deviceGrant?.registrationId ?? '', binding.deviceGeneration, binding.sessionId, binding.deviceGrant) });
133
+ return helper;
134
+ } });
135
+ process.env.AXOM_QUIC_BINARY = path.join(state.installation, 'bin/axom-quic');
136
+ }
137
+ environment() {
138
+ return { ...process.env, ...this.session.environment, AXOM_DESKTOP_RUNTIME_DIR: this.runtime, AXOM_ACCESS_TOKEN: undefined,
139
+ ...(this.state.artifact.ipcVersion === 1 ? { XDG_RUNTIME_DIR: this.runtime } : {}),
140
+ AXOM_DESKTOP_DISPLAY_CONTROL: path.join(this.state.installation, 'bin/axom-display'),
141
+ AXOM_DESKTOP_PROVIDER_FILE: path.join(this.runtime, 'provider.json'),
142
+ AXOM_DESKTOP_OWNED_DUMMY: this.session.managed && this.state.artifact.ipcVersion === 2 ? '1' : undefined };
143
+ }
144
+ async capabilities() {
145
+ await this.validate();
146
+ const executable = path.join(this.state.installation, 'bin/axom-desktop-keeper');
147
+ const options = { env: this.environment(), timeout: 2000, maxBuffer: 49152 };
148
+ const { stdout } = await execute(executable, ['--probe'], options);
149
+ const probe = geometrySchema.parse(JSON.parse(stdout));
150
+ const geometry = { width: probe.width, height: probe.height };
151
+ const sharedRuntime = this.state.artifact.ipcVersion === 2;
152
+ let actual = { output: '', ...geometry, modes: [] };
153
+ if (sharedRuntime) {
154
+ try {
155
+ actual = parseXrandr((await execute('xrandr', ['--current'], options)).stdout);
156
+ }
157
+ catch (error) {
158
+ if (this.session.managed)
159
+ throw error;
160
+ }
161
+ }
162
+ let dpi;
163
+ if (sharedRuntime && this.session.managed) {
164
+ const resources = (await execute('xrdb', ['-query'], options)).stdout;
165
+ const value = Number(/^Xft\.dpi:\s*(\d+)\s*$/m.exec(resources)?.[1] ?? 96);
166
+ dpi = z.number().int().min(64).max(384).parse(value);
167
+ }
168
+ const geometryIdentity = `${this.session.identity}:${geometry.width}:${geometry.height}:${dpi ?? ''}`;
169
+ if (geometryIdentity !== this.geometryIdentity) {
170
+ this.revision++;
171
+ this.geometryIdentity = geometryIdentity;
172
+ }
173
+ const staging = path.join(this.runtime, '.geometry.json');
174
+ await fs.writeFile(staging, JSON.stringify({ ...geometry, ...(dpi === undefined ? {} : { dpi }) }), { mode: 0o600 });
175
+ await fs.rename(staging, path.join(this.runtime, 'geometry.json'));
176
+ const targets = windowSchema.parse(JSON.parse((await execute(executable, ['--capture-targets'], options)).stdout));
177
+ const root = { id: '00000000000000000000000000000001', kind: 'display', label: this.session.managed ? 'Virtual desktop' : 'Desktop', ...geometry };
178
+ const displays = [root, ...targets].map(target => {
179
+ const identity = `${this.session.identity}:${target.width}:${target.height}`;
180
+ const previous = this.targetRevisions.get(target.id);
181
+ const revision = target.id === root.id ? this.revision : previous ? previous.revision + Number(previous.geometry !== identity) : 1;
182
+ this.targetRevisions.set(target.id, { geometry: identity, revision });
183
+ const display = desktopDisplayControl({ ...target, label: captureTargetLabel(target.label), revision }, actual, sharedRuntime && this.session.managed);
184
+ return { ...display, ...(target.id === root.id && dpi !== undefined && display.captureViewport !== true ? {
185
+ dpi, maxDpi: 384, appearanceSync: true,
186
+ } : {}) };
187
+ });
188
+ const live = new Set(displays.map(display => display.id));
189
+ for (const id of this.targetRevisions.keys())
190
+ if (!live.has(id))
191
+ this.targetRevisions.delete(id);
192
+ const failed = await Promise.all(['display-failed', 'input-failed'].map(async (name) => {
193
+ try {
194
+ await fs.lstat(path.join(this.runtime, name));
195
+ return true;
196
+ }
197
+ catch (error) {
198
+ if (error.code !== 'ENOENT')
199
+ throw error;
200
+ return false;
201
+ }
202
+ }));
203
+ const capabilities = desktopCapabilitiesSchema.parse({ protocols: { min: 2, max: probe.audio ? 3 : 2 }, providerId: 'linux-x11-v1', providerVersion: 'axom-linux-2',
204
+ configured: true, permission: 'granted', health: failed.some(Boolean) ? 'unavailable' : 'ready', reason: failed.some(Boolean) ? 'recovery_unverified' : null,
205
+ cursorEmbedded: false, formats: ['h264-420-v1'], encoderBackends: [{ id: 'libx264', kind: 'software', formats: [{ format: 'h264-420-v1',
206
+ maxWidth: 4096, maxHeight: 4096, maxPixels: 4000000, maxFps: 30, cappedRate: true }] }],
207
+ input: { pointer: true, physicalKeyboard: true, unicodeText: true, clipboard: true, textPaste: true }, displays });
208
+ const manifest = path.join(this.runtime, '.provider.json');
209
+ await fs.writeFile(manifest, JSON.stringify(capabilities), { mode: 0o600 });
210
+ await fs.rename(manifest, path.join(this.runtime, 'provider.json'));
211
+ return capabilities;
212
+ }
213
+ async probe() {
214
+ try {
215
+ await this.initialize();
216
+ if (!this.provider)
217
+ return null;
218
+ if (this.usingLegacy)
219
+ return this.provider.probe();
220
+ const capabilities = await this.provider.probe();
221
+ if (!capabilities)
222
+ return null;
223
+ await fs.writeFile(path.join(this.directory, 'rd', 'status.json'), JSON.stringify({ updatedAt: new Date().toISOString(), state: capabilities.health,
224
+ session: this.session.managed ? 'virtual' : 'existing', display: this.session.display, width: capabilities.displays[0].width, height: capabilities.displays[0].height,
225
+ resolution: capabilities.displays[0].captureViewport === true ? 'sampling' : capabilities.displays[0].resize,
226
+ modes: capabilities.displays[0].captureViewport === true ? [] : capabilities.displays[0].modes }), { mode: 0o600 });
227
+ return capabilities;
228
+ }
229
+ catch (error) {
230
+ await fs.writeFile(path.join(this.directory, 'rd', 'status.json'), JSON.stringify({ updatedAt: new Date().toISOString(), state: 'unavailable', reason: String(error) }), { mode: 0o600 }).catch(() => undefined);
231
+ throw error;
232
+ }
233
+ }
234
+ async prepare(selection, binding, current) {
235
+ await this.initialize();
236
+ if (!this.provider)
237
+ throw new NativeCommandError('unavailable');
238
+ return this.provider.prepare(selection, binding, current);
239
+ }
240
+ async wallpaper(targetId, pixels = { width: 1920, height: 1080 }) {
241
+ await this.initialize();
242
+ if (!this.usingLegacy)
243
+ await this.validate();
244
+ if (!this.provider)
245
+ throw new NativeCommandError('unavailable');
246
+ return this.provider.wallpaper(targetId, pixels);
247
+ }
248
+ async recoverDisplay(binding, reservation) {
249
+ await this.initialize();
250
+ if (!this.usingLegacy)
251
+ await this.validate();
252
+ if (!this.provider)
253
+ throw new NativeCommandError('unavailable');
254
+ return this.provider.recoverDisplay(binding, reservation);
255
+ }
256
+ }