@platless/axom-cli 0.6.36 → 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 (159) hide show
  1. package/README.md +7 -1
  2. package/dist/axom-patch/cli/README.md +31 -2
  3. package/dist/axom-patch/cli/account-limits/generate.py +2 -2
  4. package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
  5. package/dist/axom-patch/cli/codex-terminal-output.json +2 -2
  6. package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
  7. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +1 -1
  8. package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
  9. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
  10. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +137 -41
  11. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +5 -88
  12. package/dist/axom-patch/cli/qoder-native-acp.json +23 -23
  13. package/dist/axom-patch/cli/runtime.json +3 -1
  14. package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
  15. package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
  16. package/dist/axom-patch/cli/zcode-account-limits.json +7 -7
  17. package/dist/axom-patch/cli/zcode-env-account.json +58 -0
  18. package/dist/axom-patch/cli/zcode-models.json +9 -9
  19. package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -10
  20. package/dist/cli.js +9 -11
  21. package/dist/commands/auth.js +4 -1
  22. package/dist/commands/update.js +68 -7
  23. package/dist/commands/workstation.js +94 -223
  24. package/dist/commands/workstationRegistrations.js +116 -0
  25. package/dist/credentialBroker.js +13 -2
  26. package/dist/credentials.js +16 -0
  27. package/dist/install/claude/commands.js +2 -2
  28. package/dist/keepers/terminal/keeper.mjs +1089 -673
  29. package/dist/keepers/terminal/manifest.json +4 -4
  30. package/dist/native/darwin-x64/axom-quic +0 -0
  31. package/dist/workstation/acp/acpAgents.js +11 -4
  32. package/dist/workstation/acp/outbox.js +12 -5
  33. package/dist/workstation/acp/permission.js +2 -2
  34. package/dist/workstation/acp/processor/checkpoint.js +4 -3
  35. package/dist/workstation/acp/processor/control.js +2 -2
  36. package/dist/workstation/acp/processor/jsonrpc.js +8 -0
  37. package/dist/workstation/acp/processor/session.js +3 -2
  38. package/dist/workstation/acp/processor/state.js +1 -0
  39. package/dist/workstation/acp/processor/transport/local.js +19 -3
  40. package/dist/workstation/acp/processor/updates.js +25 -0
  41. package/dist/workstation/acp/processorSession.js +12 -1
  42. package/dist/workstation/acp/registry/catalog.js +6 -6
  43. package/dist/workstation/acp/test/keeperWorld.js +4 -3
  44. package/dist/workstation/agentSessions.js +76 -23
  45. package/dist/workstation/channels/agent.js +9 -1
  46. package/dist/workstation/channels/computer.js +27 -4
  47. package/dist/workstation/channels/management.js +36 -5
  48. package/dist/workstation/config.js +18 -6
  49. package/dist/workstation/control/bindingStore.js +4 -0
  50. package/dist/workstation/control/commands.js +1 -0
  51. package/dist/workstation/control/daemonIdentity.js +90 -0
  52. package/dist/workstation/control/deviceCommands.js +191 -0
  53. package/dist/workstation/control/deviceProtocol.js +68 -0
  54. package/dist/workstation/control/locks.js +51 -4
  55. package/dist/workstation/control/rootFormat.js +16 -0
  56. package/dist/workstation/control/runtimeClient.js +60 -5
  57. package/dist/workstation/control/runtimeHost.js +96 -9
  58. package/dist/workstation/control/serverIdentity.js +2 -2
  59. package/dist/workstation/control/updateTransition.js +91 -0
  60. package/dist/workstation/daemon.js +262 -360
  61. package/dist/workstation/daemonctl.js +150 -12
  62. package/dist/workstation/desktopControl/automationDevice.js +219 -0
  63. package/dist/workstation/desktopControl/automationJson.js +74 -0
  64. package/dist/workstation/desktopControl/automationLocalState.js +23 -0
  65. package/dist/workstation/desktopControl/automationPreparation.js +1 -0
  66. package/dist/workstation/desktopControl/automationProtocol.js +253 -0
  67. package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
  68. package/dist/workstation/desktopControl/automationSession.js +81 -0
  69. package/dist/workstation/desktopControl/automationTransport.js +138 -0
  70. package/dist/workstation/desktopControl/capabilities.js +2 -1
  71. package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
  72. package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
  73. package/dist/workstation/desktopControl/deviceSockets.js +2 -2
  74. package/dist/workstation/desktopControl/helperProcess.js +250 -1
  75. package/dist/workstation/desktopControl/hostGrant.js +65 -0
  76. package/dist/workstation/desktopControl/lobby.js +4 -2
  77. package/dist/workstation/desktopControl/macProvider.js +41 -40
  78. package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
  79. package/dist/workstation/desktopControl/nativeProvider.js +31 -5
  80. package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
  81. package/dist/workstation/desktopControl/protocol.js +15 -6
  82. package/dist/workstation/desktopControl/supervisor.js +110 -17
  83. package/dist/workstation/desktopControl/wallpaper.js +26 -6
  84. package/dist/workstation/exec/policy.js +5 -0
  85. package/dist/workstation/files/client.js +10 -1
  86. package/dist/workstation/files/host.js +9 -2
  87. package/dist/workstation/journal.js +5 -2
  88. package/dist/workstation/keepers/acp/client.js +38 -2
  89. package/dist/workstation/keepers/kit/buildVersion.js +35 -11
  90. package/dist/workstation/keepers/kit/client.js +2 -2
  91. package/dist/workstation/keepers/kit/launcher.js +80 -4
  92. package/dist/workstation/keepers/legacy.js +16 -2
  93. package/dist/workstation/keepers/policy.js +5 -34
  94. package/dist/workstation/keepers/terminal/client.js +115 -23
  95. package/dist/workstation/keepers/terminal/exec.js +6 -3
  96. package/dist/workstation/keepers/terminal/frames.js +73 -0
  97. package/dist/workstation/keepers/terminal/generations.js +5 -8
  98. package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
  99. package/dist/workstation/keepers/terminal/input.js +146 -0
  100. package/dist/workstation/keepers/terminal/keeper.js +22 -8
  101. package/dist/workstation/keepers/terminal/main.js +1 -1
  102. package/dist/workstation/keepers/terminal/metadata.js +138 -0
  103. package/dist/workstation/keepers/terminal/protocol.js +4 -28
  104. package/dist/workstation/keepers/terminal/pty.js +1 -330
  105. package/dist/workstation/keepers/terminal/screen.js +246 -0
  106. package/dist/workstation/keepers/terminal/sessions.js +526 -0
  107. package/dist/workstation/oauth/deviceAuthorization.js +31 -17
  108. package/dist/workstation/protocol/management.js +23 -4
  109. package/dist/workstation/rd/artifact.js +4 -3
  110. package/dist/workstation/rd/control.js +3 -5
  111. package/dist/workstation/rd/host.js +213 -0
  112. package/dist/workstation/rd/migrationReadiness.js +46 -0
  113. package/dist/workstation/rd/provider.js +44 -10
  114. package/dist/workstation/rd/state.js +14 -2
  115. package/dist/workstation/rd/update.js +5 -8
  116. package/dist/workstation/rd/upkeep.js +44 -2
  117. package/dist/workstation/registrations/enrollment.js +250 -0
  118. package/dist/workstation/registrations/enrollmentPending.js +361 -0
  119. package/dist/workstation/registrations/migration.js +356 -0
  120. package/dist/workstation/registrations/model.js +34 -0
  121. package/dist/workstation/registrations/removal.js +222 -0
  122. package/dist/workstation/registrations/store.js +737 -0
  123. package/dist/workstation/runtime/desktopBinding.js +20 -0
  124. package/dist/workstation/runtime/deviceDesktop.js +193 -0
  125. package/dist/workstation/runtime/deviceRuntime.js +568 -0
  126. package/dist/workstation/runtime/deviceServices.js +117 -0
  127. package/dist/workstation/runtime/events.js +20 -0
  128. package/dist/workstation/runtime/ownership.js +100 -0
  129. package/dist/workstation/runtime/registrationRuntime.js +575 -0
  130. package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
  131. package/dist/workstation/runtime/removalActivity.js +184 -0
  132. package/dist/workstation/service/installer.js +93 -1
  133. package/dist/workstation/service/launchd.js +1 -0
  134. package/dist/workstation/service/systemd.js +1 -0
  135. package/dist/workstation/systemMetrics.js +3 -0
  136. package/dist/workstation/tools/client.js +8 -27
  137. package/dist/workstation/tools/info.js +6 -13
  138. package/dist/workstation/tools/terminalRouter.js +17 -172
  139. package/dist/workstation/tools/validation.js +55 -1
  140. package/dist/workstation/update/archiveEvidence.js +206 -0
  141. package/dist/workstation/update/coordinator.js +34 -13
  142. package/dist/workstation/update/deviceCompatibility.js +119 -0
  143. package/dist/workstation/update/deviceCoordinator.js +369 -0
  144. package/dist/workstation/update/deviceHealth.js +104 -0
  145. package/dist/workstation/update/deviceHealthProtocol.js +26 -0
  146. package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
  147. package/dist/workstation/update/deviceRollback.js +78 -0
  148. package/dist/workstation/update/globalInstall.js +80 -10
  149. package/dist/workstation/update/legacyBridge.js +414 -0
  150. package/dist/workstation/update/legacyOwner.js +39 -0
  151. package/dist/workstation/update/npmInstaller.js +2 -2
  152. package/dist/workstation/update/privateDurability.js +61 -0
  153. package/dist/workstation/update/recoveryPlan.js +124 -0
  154. package/dist/workstation/update/runUpdate.js +7 -1
  155. package/dist/workstation/update/taskRegistry.js +161 -3
  156. package/dist/workstation/update/watchdog.js +27427 -14410
  157. package/dist/workstation/update/watchdogProcess.js +72 -0
  158. package/package.json +7 -3
  159. package/dist/native/darwin-arm64/axom-quic +0 -0
@@ -0,0 +1,575 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { replaceDesktopBinding, holdDesktopForTargetProbe } from './desktopBinding.js';
5
+ import { AgentSessionManager } from '../agentSessions.js';
6
+ import { AcpAgents } from '../acp/acpAgents.js';
7
+ import { ConnectionSupervisor } from '../connection/supervisor.js';
8
+ import { ManagementChannel } from '../channels/management.js';
9
+ import { ComputerChannel } from '../channels/computer.js';
10
+ import { AgentChannel } from '../channels/agent.js';
11
+ import { TaskRegistry } from '../update/taskRegistry.js';
12
+ import { FilesHostClient } from '../files/client.js';
13
+ import { WorkstationTools } from '../tools/router.js';
14
+ import { aggregateToolsInfo, computeFilesInfo } from '../tools/info.js';
15
+ import { defaultDistDir, KeeperLauncher } from '../keepers/kit/launcher.js';
16
+ import { readKeeperManifest } from '../keepers/kit/bundle.js';
17
+ import { KEEPER_POLICY_TICK_MS, KeeperPolicy, oneShotDrain, oneShotProbe } from '../keepers/policy.js';
18
+ import { TerminalGenerations } from '../keepers/terminal/generations.js';
19
+ import { AcpKeeperClient } from '../keepers/acp/client.js';
20
+ import { KeeperTransport } from '../acp/processor/transport/keeper.js';
21
+ import { keeperGenerations } from '../acp/processor/transport/generations.js';
22
+ import { ExecPolicy } from '../exec/policy.js';
23
+ import { TerminalRouter, terminalEnv, terminalShell } from '../tools/terminalRouter.js';
24
+ import { LocalTransport } from '../acp/processor/transport/local.js';
25
+ import { RegistrationDesktopAdapter } from './remoteDesktopAdapter.js';
26
+ import { resolveRegistrationOwnership } from './ownership.js';
27
+ import { randomUUID } from 'node:crypto';
28
+ import { assertRemovalNamespace } from '../registrations/migration.js';
29
+ import { TargetRemovalActivity } from './removalActivity.js';
30
+ import { LocalControlError } from '../control/protocol.js';
31
+ import { withWorkstationLock } from '../control/locks.js';
32
+ import { resolveRemovalNamespaceLocked } from '../registrations/migration.js';
33
+ import { buildKeeperStatuses, existingDirectories, workspaceRoots, startExclusivePoll, startIdleSweep, maintainAdapterBuilds, FILES_ROOTS_POLL_MS, SWITCH_POLL_INTERVAL_MS } from '../daemon.js';
34
+ export function createRegistrationRuntime(input) {
35
+ const shutdown = new AbortController();
36
+ const admission = { open: true, external: 0, final: false, background: 0, backgroundKnown: true };
37
+ const lifetime = {};
38
+ let admissionRevision = 0;
39
+ const holds = new WeakMap();
40
+ let removal;
41
+ const removalFor = (preparation) => {
42
+ if (!removal || removal.preparation !== preparation)
43
+ throw new LocalControlError('BINDING_CONFLICT', 'Original target resource preparation is unavailable');
44
+ return removal;
45
+ };
46
+ const originalHold = (hold) => {
47
+ const state = holds.get(hold);
48
+ if (!state || state.lifetime !== lifetime || shutdown.signal.aborted)
49
+ throw new Error('Invalid or stale target admission hold');
50
+ return state;
51
+ };
52
+ const cleanup = [];
53
+ let initialized;
54
+ let stopped;
55
+ let terminationSettled = false;
56
+ let terminationPreparation;
57
+ let deviceAdmissionHold;
58
+ const release = async () => {
59
+ let failure;
60
+ const retry = [];
61
+ for (const close of cleanup.splice(0).reverse())
62
+ try {
63
+ await close();
64
+ }
65
+ catch (error) {
66
+ failure ??= error;
67
+ retry.push(close);
68
+ }
69
+ cleanup.push(...retry.reverse());
70
+ if (failure)
71
+ throw failure;
72
+ };
73
+ const runtime = {
74
+ async start() {
75
+ if (shutdown.signal.aborted)
76
+ throw new Error('Registration is stopped');
77
+ initialized ??= initializeRegistration(input, shutdown, cleanup, admission).catch(async (error) => {
78
+ shutdown.abort();
79
+ try {
80
+ await release();
81
+ }
82
+ catch (cleanupError) {
83
+ throw new AggregateError([error, cleanupError], 'Registration initialization and retained cleanup failed');
84
+ }
85
+ throw error;
86
+ });
87
+ await initialized;
88
+ },
89
+ async releaseStartupAdmission() {
90
+ const original = admission.startup;
91
+ if (!original)
92
+ return;
93
+ admission.tasks.releaseExternalAdmission(original.tasks);
94
+ admission.external -= 1;
95
+ admission.startup = undefined;
96
+ if (!admission.external && !admission.final && !shutdown.signal.aborted)
97
+ admission.desktop?.resumeNewAdmission();
98
+ },
99
+ async prepareTargetAdmission(target) {
100
+ if (removal)
101
+ throw new LocalControlError('BUSY', 'Original target resource preparation is already active');
102
+ if (admission.final || target.id !== input.registration.id || target.expectedRevision !== input.registration.revision || !target.operationId || shutdown.signal.aborted || stopped || !admission.tasks)
103
+ throw new Error('Target runtime is unavailable or changed');
104
+ if (!Number.isSafeInteger(admission.external + 1))
105
+ throw new Error('Target admission accounting is uncertain');
106
+ const taskHold = admission.tasks.holdExternalAdmission();
107
+ admission.external += 1;
108
+ const hold = Object.freeze({});
109
+ holds.set(hold, { lifetime, active: true, operationId: target.operationId, tasks: admission.tasks, taskHold });
110
+ try {
111
+ await holdDesktopForTargetProbe(admission.desktop);
112
+ }
113
+ catch (error) {
114
+ admission.tasks.releaseExternalAdmission(taskHold);
115
+ admission.external -= 1;
116
+ holds.delete(hold);
117
+ if (!admission.external && !admission.final)
118
+ admission.desktop?.resumeNewAdmission();
119
+ throw error;
120
+ }
121
+ return hold;
122
+ },
123
+ observeTargetAdmission(hold) {
124
+ const state = originalHold(hold);
125
+ if (!state.active)
126
+ throw new Error('Target admission hold is released');
127
+ const observed = state.tasks.observeTargetPending();
128
+ return { status: observed.pending > 0 || observed.registered > 0 ? 'busy' : 'unknown', pending: observed.pending, registered: observed.registered,
129
+ diagnostics: [...(!observed.known ? ['Agent accounting is uncertain'] : []), 'Files, native and legacy membership remains unresolved'] };
130
+ },
131
+ async holdTargetEvidenceAdmission(hold) {
132
+ const state = originalHold(hold);
133
+ if (!state.active)
134
+ throw new LocalControlError('BINDING_CONFLICT', 'Target admission hold is released');
135
+ },
136
+ async releaseTargetAdmission(hold) {
137
+ const state = originalHold(hold);
138
+ if (!state.active)
139
+ return;
140
+ state.tasks.releaseExternalAdmission(state.taskHold);
141
+ state.active = false;
142
+ admission.external -= 1;
143
+ if (!admission.external && !admission.final && !shutdown.signal.aborted)
144
+ admission.desktop?.resumeNewAdmission();
145
+ },
146
+ async prepareTargetResources(hold, restoreAdmission) {
147
+ const state = originalHold(hold);
148
+ if (removal)
149
+ throw new LocalControlError('BUSY', 'Original target resource preparation is already active');
150
+ if (!state.active || !admission.resources)
151
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Target resources are unavailable');
152
+ removal = new TargetRemovalActivity({ ...admission.resources, registration: input.registration, lifetime, operationId: state.operationId,
153
+ validate: () => { if (originalHold(hold) !== state || !state.active)
154
+ throw new LocalControlError('BINDING_CONFLICT', 'Original target hold changed'); },
155
+ background: () => ({ pending: admission.background, known: admission.backgroundKnown }),
156
+ desktopActivity: admission.desktop ? () => admission.desktop.activity() : undefined,
157
+ beginFinal: () => { admission.final = true; admission.open = false; admission.set?.(false); },
158
+ closeDesktopAdmission: admission.desktop || input.remoteDesktopLifecycle ? async () => {
159
+ const results = await Promise.allSettled([admission.desktop?.closeAdmission(), input.remoteDesktopLifecycle?.closeAdmission()]);
160
+ for (const result of results)
161
+ if (result.status === 'rejected')
162
+ throw result.reason;
163
+ } : undefined,
164
+ isolateDesktop: admission.desktop || input.remoteDesktopLifecycle ? async () => {
165
+ const results = await Promise.allSettled([admission.desktop?.isolateOriginal(), input.remoteDesktopLifecycle?.isolateOriginal()]);
166
+ for (const result of results)
167
+ if (result.status === 'rejected')
168
+ throw result.reason;
169
+ } : undefined,
170
+ restore: async () => {
171
+ if (restoreAdmission)
172
+ await restoreAdmission();
173
+ else
174
+ await runtime.releaseTargetAdmission(hold);
175
+ if (state.active)
176
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original target admission restoration is incomplete');
177
+ } });
178
+ return removal.preparation;
179
+ },
180
+ observeTargetResources: async (preparation) => removalFor(preparation).observe(),
181
+ sealTargetResources: async (preparation, mode) => removalFor(preparation).seal(mode),
182
+ settleTargetResources: async (preparation, namespace) => removalFor(preparation).settle(namespace),
183
+ prepareTargetDisposition: async (preparation, namespace) => removalFor(preparation).prepareDisposition(lifetime, namespace),
184
+ async disposeTargetResources(settlement, namespace) {
185
+ if (!removal)
186
+ throw new LocalControlError('BINDING_CONFLICT', 'Original target settlement is unavailable');
187
+ if (!removal.continueDisposition(settlement, lifetime, namespace)) {
188
+ removal.assertSettlement(settlement, lifetime, namespace);
189
+ removal.consume(settlement, lifetime, namespace);
190
+ }
191
+ terminationSettled = true;
192
+ await runtime.stop('detach');
193
+ assertRemovalNamespace(namespace);
194
+ removal.disposed();
195
+ },
196
+ async releaseTargetResources(preparation) { const original = removalFor(preparation); await original.release(); if (removal === original)
197
+ removal = undefined; },
198
+ async pauseAdmission() {
199
+ admissionRevision += 1;
200
+ admission.open = false;
201
+ admission.set?.(false);
202
+ if (admission.tasks)
203
+ deviceAdmissionHold ??= admission.tasks.holdExternalAdmission();
204
+ const outcomes = await Promise.allSettled([admission.desktop?.closeAdmission(), input.remoteDesktopLifecycle?.closeAdmission()]);
205
+ const failures = outcomes.flatMap(outcome => outcome.status === 'rejected' ? [outcome.reason] : []);
206
+ if (failures.length)
207
+ throw new AggregateError(failures, 'Original registration admission remains pending');
208
+ },
209
+ resumeAdmission() {
210
+ if (admission.final)
211
+ throw new LocalControlError('OUTCOME_UNKNOWN', 'Original final resource settlement cannot reopen admission');
212
+ admissionRevision += 1;
213
+ const revision = admissionRevision;
214
+ const reopen = () => {
215
+ if (!shutdown.signal.aborted && revision === admissionRevision) {
216
+ if (deviceAdmissionHold) {
217
+ admission.tasks.releaseExternalAdmission(deviceAdmissionHold);
218
+ deviceAdmissionHold = undefined;
219
+ }
220
+ admission.open = true;
221
+ admission.set?.(true);
222
+ admission.desktop?.resumeAdmission();
223
+ }
224
+ };
225
+ reopen();
226
+ },
227
+ async activity() { return initialized ? (await initialized).activity() : { known: false, agents: 0, terminals: 0, commands: 0, transfers: 0, desktopConsumers: 0 }; },
228
+ async connection() { return initialized ? (await initialized).connection?.() ?? 'unknown' : 'offline'; },
229
+ stop(mode) {
230
+ admissionRevision += 1;
231
+ admission.open = false;
232
+ admission.set?.(false);
233
+ if (admission.tasks)
234
+ deviceAdmissionHold ??= admission.tasks.holdExternalAdmission();
235
+ stopped ??= (async () => {
236
+ if (mode === 'terminate' && !terminationSettled && admission.resources && input.registrationRoot) {
237
+ if (!removal) {
238
+ terminationPreparation ??= (async () => {
239
+ const hold = await runtime.prepareTargetAdmission({ id: input.registration.id, expectedRevision: input.registration.revision, operationId: randomUUID() });
240
+ return runtime.prepareTargetResources(hold);
241
+ })();
242
+ await terminationPreparation;
243
+ }
244
+ await removal.prepareTermination();
245
+ await withWorkstationLock(input.registrationRoot, 'operation', async () => {
246
+ const namespace = await resolveRemovalNamespaceLocked(input.registrationRoot, input.registration.id, input.registration.revision);
247
+ const settlement = await removal.prepareDisposition(lifetime, namespace);
248
+ if (!removal.continueDisposition(settlement, lifetime, namespace))
249
+ removal.consume(settlement, lifetime, namespace);
250
+ terminationSettled = true;
251
+ });
252
+ }
253
+ shutdown.abort();
254
+ let failure;
255
+ try {
256
+ await (await initialized?.catch(() => undefined))?.stop(terminationSettled ? 'detach' : mode);
257
+ }
258
+ catch (error) {
259
+ failure = error;
260
+ }
261
+ try {
262
+ await release();
263
+ }
264
+ catch (error) {
265
+ failure ??= error;
266
+ }
267
+ if (failure)
268
+ throw failure;
269
+ })().catch(error => { stopped = undefined; throw error; });
270
+ return stopped;
271
+ },
272
+ };
273
+ return runtime;
274
+ }
275
+ async function initializeRegistration(input, shutdown, cleanup, admission) {
276
+ const { registration, clientKey, serverPub, paths, deviceServices } = input;
277
+ const dir = registration.dataDirectory;
278
+ const cfg = input.configuration ?? { server: registration.server, workstationId: registration.workstationId, ownerUserId: registration.ownerUserId };
279
+ const keeperServer = paths.legacySources?.keeperServer ?? registration.server;
280
+ const ownership = await resolveRegistrationOwnership(registration, paths.legacySources);
281
+ const tasks = new TaskRegistry();
282
+ admission.tasks = tasks;
283
+ shutdown.signal.addEventListener('abort', () => tasks.closeAgentWorkLifetime(), { once: true });
284
+ tasks.accepting = admission.open;
285
+ admission.set = open => { tasks.accepting = open; };
286
+ const background = async (work) => {
287
+ if (admission.final)
288
+ return;
289
+ admission.background += 1;
290
+ if (!Number.isSafeInteger(admission.background)) {
291
+ admission.backgroundKnown = false;
292
+ throw new Error('Background accounting is uncertain');
293
+ }
294
+ try {
295
+ await work();
296
+ }
297
+ catch (error) {
298
+ admission.backgroundKnown = false;
299
+ throw error;
300
+ }
301
+ finally {
302
+ admission.background -= 1;
303
+ }
304
+ };
305
+ deviceServices.register(registration.id, cfg);
306
+ cleanup.push(async () => deviceServices.unregister(registration.id));
307
+ const acpLauncher = new KeeperLauncher({ configDir: registration.keeperConfigDirectory, workstationId: cfg.workstationId, server: keeperServer, ownership });
308
+ const acpTransport = new KeeperTransport(new AcpKeeperClient({ launcher: acpLauncher }));
309
+ const acpGenerations = keeperGenerations({ launcher: acpLauncher, current: acpTransport });
310
+ const localTransport = new LocalTransport({ configDir: dir, outboxDirectory: paths.legacySources?.localOutboxDirectory, ownership });
311
+ const acp = new AcpAgents({ cacheDir: dir, adapterCacheDirectory: paths.legacySources?.adapterCacheDirectory, ownership, serverOrigin: cfg.server, transports: { keeper: acpTransport, local: localTransport }, keeperGenerations: acpGenerations });
312
+ const agents = new AgentSessionManager(acp, null, paths.legacySources ? path.dirname(paths.legacySources.journalDirectory) : path.join(dir, 'agents'), tasks, () => admission.open, ownership);
313
+ cleanup.push(async () => { await agents.detachAll(); await acp.closeTransports(); });
314
+ await agents.adoptSessions();
315
+ let bridges = await acp.roster();
316
+ const scanRoster = () => acp.roster({ sessions: (session) => agents.rosterSessionState(session) });
317
+ let supervisor;
318
+ const allowInsecureWs = input.allowInsecureWs ?? process.env.AXOM_WORKSTATION_ALLOW_INSECURE_WS === '1';
319
+ const home = input.homeDirectory ?? os.homedir();
320
+ const playgroundCwd = path.join(home, '.axom', 'playground');
321
+ await fs.mkdir(playgroundCwd, { recursive: true });
322
+ let roots = workspaceRoots(cfg, home);
323
+ let hostRoots = [];
324
+ const filesHost = new FilesHostClient({
325
+ configDir: dir,
326
+ options: async () => { hostRoots = await existingDirectories(roots); return { home, roots: hostRoots, mediaCache: path.join(dir, 'media-cache') }; },
327
+ onActivityChanged: () => tasks.externalActivityChanged(),
328
+ log: (line) => console.error(`${new Date().toISOString()} ${line}`),
329
+ });
330
+ tasks.externalActivity = () => filesHost.activity();
331
+ cleanup.push(() => filesHost.stop());
332
+ filesHost.start();
333
+ const stopRootsPoll = startExclusivePoll(async () => {
334
+ if (admission.external || admission.final)
335
+ return;
336
+ await background(async () => {
337
+ try {
338
+ roots = workspaceRoots(cfg, home);
339
+ const current = await existingDirectories(roots);
340
+ if (!admission.final && !admission.external && JSON.stringify(current) !== JSON.stringify(hostRoots))
341
+ filesHost.reconfigure();
342
+ }
343
+ catch { }
344
+ });
345
+ }, FILES_ROOTS_POLL_MS);
346
+ cleanup.push(async () => stopRootsPoll());
347
+ const toolsConfig = { configDir: registration.keeperConfigDirectory, workstationId: cfg.workstationId, server: keeperServer, roots };
348
+ const terminalLauncher = new KeeperLauncher({ configDir: registration.keeperConfigDirectory, workstationId: cfg.workstationId, server: keeperServer, ownership });
349
+ admission.resources = { tasks, filesHost, localTransport, launchers: [acpLauncher, terminalLauncher], activity: () => activity() };
350
+ const terminalKeepers = new TerminalGenerations({ launcher: terminalLauncher });
351
+ cleanup.push(async () => terminalKeepers.disconnect());
352
+ const terminals = new TerminalRouter({
353
+ kit: terminalKeepers.primary,
354
+ launch: async (cwd) => ({ cwd: await filesHost.terminalCwd(cwd), shell: terminalShell(), env: terminalEnv() }),
355
+ });
356
+ const workstationTools = new WorkstationTools({
357
+ files: filesHost,
358
+ terminal: (method, params) => terminals.request(method, params),
359
+ browser: async () => { throw new Error('Device desktop control is unavailable until physical isolation is integrated'); },
360
+ info: () => aggregateToolsInfo({
361
+ files: async () => filesHost.info() ?? computeFilesInfo(home, roots),
362
+ terminal: () => terminalKeepers.primary.terminalStatus(() => ({ terminal: false })),
363
+ browser: async () => ({ browser: false, browserError: 'Device desktop control is unavailable until physical isolation is integrated' }),
364
+ }),
365
+ });
366
+ const rawDrainKeeper = oneShotDrain((kind, gen) => (kind === 'acp' ? acpLauncher : terminalLauncher).pathsFor(kind, gen));
367
+ const drainKeeper = async (kind, gen) => {
368
+ await (kind === 'acp' ? acpLauncher : terminalLauncher).assertKeeperResources(kind, gen);
369
+ await rawDrainKeeper(kind, gen);
370
+ };
371
+ const probeKeeper = oneShotProbe((kind, gen) => (kind === 'acp' ? acpLauncher : terminalLauncher).pathsFor(kind, gen));
372
+ const keeperPolicy = new KeeperPolicy({
373
+ dir: path.join(registration.keeperConfigDirectory, 'keepers'),
374
+ probe: async (kind, gen, timeoutMs) => {
375
+ await (kind === 'acp' ? acpLauncher : terminalLauncher).assertKeeperResources(kind, gen, timeoutMs);
376
+ return probeKeeper(kind, gen, timeoutMs);
377
+ },
378
+ terminal: {
379
+ currentGen: () => terminalKeepers.currentGen(),
380
+ live: async () => (await terminalKeepers.running()).map((generation) => generation.gen),
381
+ manifest: () => terminalKeepers.manifest(),
382
+ stop: async (gen, mode) => {
383
+ const result = await terminalLauncher.stop('terminal', gen, mode);
384
+ if (result.stopped)
385
+ terminalKeepers.forget(gen);
386
+ return result;
387
+ },
388
+ reachable: async (gen) => (await terminalKeepers.client(gen)).runningHello(),
389
+ drain: (gen) => drainKeeper('terminal', gen),
390
+ },
391
+ acp: {
392
+ currentGen: () => acpGenerations.current(),
393
+ live: () => acpGenerations.live(),
394
+ manifest: () => readKeeperManifest(path.join(defaultDistDir(), 'acp')),
395
+ stop: (gen, mode) => acpLauncher.stop('acp', gen, mode),
396
+ moveOff: (gens) => agents.moveOffOlderKeeperGenerations(undefined, gens),
397
+ cancelMoves: async (refs) => { for (const sessionRef of refs)
398
+ await agents.cancelSwitch({ sessionRef }); },
399
+ retireIdle: () => acp.retireIdleGenerations(),
400
+ session: (ref) => agents.sessionView(ref),
401
+ },
402
+ agentName: (agentId) => bridges.find((bridge) => bridge.type === agentId)?.displayName || agentId,
403
+ log: (line) => console.error(`${new Date().toISOString()} ${line}`),
404
+ });
405
+ terminalKeepers.picker = (current, older) => keeperPolicy.terminalServing(current, older);
406
+ const stopKeeperPolicy = startExclusivePoll(async () => { if (admission.open && admission.external === 0)
407
+ await background(() => keeperPolicy.tick()); }, KEEPER_POLICY_TICK_MS);
408
+ cleanup.push(async () => stopKeeperPolicy());
409
+ const stopSocketTouch = startExclusivePoll(async () => {
410
+ await acpLauncher.touchSockets('acp');
411
+ await terminalLauncher.touchSockets('terminal');
412
+ }, KEEPER_POLICY_TICK_MS);
413
+ cleanup.push(async () => stopSocketTouch());
414
+ if (admission.open && admission.external === 0)
415
+ void background(() => keeperPolicy.tick());
416
+ const execPolicy = new ExecPolicy({ generations: terminalKeepers, log: (line) => console.error(`${new Date().toISOString()} ${line}`) });
417
+ void background(() => execPolicy.recover());
418
+ const computer = new ComputerChannel({ server: cfg.server, allowInsecureWs, taskRegistry: tasks, desktopEnabled: false,
419
+ registrationId: registration.id,
420
+ admission: () => admission.open && admission.external === 0,
421
+ runExec: (request, onOutput, signal, hooks) => execPolicy.run(request, onOutput, signal, hooks),
422
+ execs: execPolicy, log: (line) => console.error(`${new Date().toISOString()} ${line}`),
423
+ tools: (method, params) => { if ((!admission.open || admission.external > 0) && method !== 'tools.info')
424
+ return Promise.reject(new Error('Registration admission is paused')); return workstationTools.request(method, params); },
425
+ runFrame: (request, signal) => workstationTools.runFrame(request, signal),
426
+ });
427
+ let boundDesktopProvider = deviceServices.desktopProvider;
428
+ let desktopManagementGeneration;
429
+ const createDesktop = (provider) => deviceServices.desktopOwnership ? new RegistrationDesktopAdapter({ registrationId: registration.id,
430
+ owner: deviceServices.desktopOwnership, provider, server: cfg.server, stateDirectory: dir, allowInsecureWs,
431
+ report: (generation, frame) => management.sendDesktopState(generation, frame) }) : undefined;
432
+ let remoteDesktop = createDesktop(boundDesktopProvider);
433
+ if (!admission.open || admission.final)
434
+ await remoteDesktop?.closeAdmission();
435
+ if (admission.external)
436
+ await remoteDesktop?.holdNewAdmission();
437
+ admission.desktop = remoteDesktop;
438
+ if (remoteDesktop)
439
+ cleanup.push(async () => { const original = remoteDesktop; if (original) {
440
+ await original.closeAdmission();
441
+ await original.isolateOriginal();
442
+ original.admission.dispose();
443
+ } });
444
+ const management = new ManagementChannel({
445
+ server: cfg.server, workstationId: cfg.workstationId, workstation: cfg.workstationId, clientKey, serverPub,
446
+ bridges: () => bridges,
447
+ workspaces: cfg.workspaces,
448
+ playgroundCwd,
449
+ metrics: () => deviceServices.metrics(registration.id),
450
+ toolchain: () => deviceServices.toolchain(),
451
+ allowInsecureWs,
452
+ desktopProvider: {
453
+ probe: () => admission.open && !admission.final ? remoteDesktop?.provider?.probe() ?? Promise.resolve(null) : Promise.resolve(null),
454
+ prepare: (selection, binding) => { const provider = remoteDesktop?.provider; if (!provider)
455
+ throw new Error('unavailable'); return provider.prepare(selection, binding); },
456
+ recoverDisplay: (binding, reserved) => { const provider = remoteDesktop?.provider; if (!provider)
457
+ throw new Error('unavailable'); return provider.recoverDisplay(binding, reserved); },
458
+ },
459
+ onDesktopCommand: (frame, generation) => remoteDesktop?.dispatch(frame, generation) ?? Promise.resolve(),
460
+ onAutomationPrepare: (frame, generation) => remoteDesktop?.prepareAutomation(frame, generation) ?? Promise.resolve(),
461
+ onReady: ready => { desktopManagementGeneration = ready.selectedWire >= 10 ? ready.generation : undefined; if (ready.selectedWire >= 10)
462
+ remoteDesktop?.ready(ready.generation); return deviceServices.updates.ready({ registrationId: registration.id, managementGeneration: ready.generation, server: registration.server }, ready.release); },
463
+ onClosed: generation => { if (desktopManagementGeneration === generation)
464
+ desktopManagementGeneration = undefined; remoteDesktop?.closed(generation); deviceServices.updates.closed({ registrationId: registration.id, managementGeneration: generation, server: registration.server }); },
465
+ onUpdate: (command, generation) => deviceServices.updates.handle({ registrationId: registration.id, managementGeneration: generation, server: registration.server }, command),
466
+ requiredRemoteUpdatePolicy: 'drain-only',
467
+ onExternalAgentsScan: async () => {
468
+ bridges = await scanRoster();
469
+ return bridges;
470
+ },
471
+ snapshot: async () => ({ channels: supervisor.channelSnapshot(), ...tasks.snapshot(), update: await deviceServices.updates.snapshot(registration.id) }),
472
+ keepers: () => buildKeeperStatuses(keeperPolicy, () => tasks.countedKeeperExecs),
473
+ onKeeperControl: frame => admission.open && admission.external === 0 ? keeperPolicy.control(frame) : Promise.resolve({ t: 'keeper_control_result', commandId: frame.commandId, kind: frame.kind, action: frame.action, ok: false, affected: [], error: 'Registration admission is paused' }),
474
+ });
475
+ const unwatchDesktop = await deviceServices.watchDesktop?.(registration.id, {
476
+ activity: () => remoteDesktop?.activity() ?? { known: true, consumers: 0 },
477
+ bind: async (provider) => {
478
+ if (provider === boundDesktopProvider)
479
+ return;
480
+ let next = await replaceDesktopBinding({ original: remoteDesktop, create: () => createDesktop(provider),
481
+ allowed: () => admission.open && !admission.final && !shutdown.signal.aborted, held: () => admission.external > 0,
482
+ final: () => admission.final || shutdown.signal.aborted });
483
+ if (admission.final || shutdown.signal.aborted) {
484
+ await next?.closeAdmission();
485
+ await next?.isolateOriginal();
486
+ next?.admission.dispose();
487
+ next = undefined;
488
+ }
489
+ next?.initializeAdmission(admission.open, admission.external > 0);
490
+ remoteDesktop = next;
491
+ boundDesktopProvider = provider;
492
+ admission.desktop = remoteDesktop;
493
+ if (desktopManagementGeneration)
494
+ remoteDesktop?.ready(desktopManagementGeneration);
495
+ management.refreshDesktopCapabilities();
496
+ },
497
+ });
498
+ if (unwatchDesktop)
499
+ cleanup.push(async () => unwatchDesktop());
500
+ const agent = new AgentChannel({ server: cfg.server, agents, bridges: () => bridges, allowInsecureWs, registrationId: registration.id });
501
+ const unwatchFacts = deviceServices.watchFacts?.(registration.id, event => { void management.receiveDeviceFact(event).catch(() => console.warn('[registration] Device fact refresh failed')); });
502
+ if (unwatchFacts)
503
+ cleanup.push(async () => unwatchFacts());
504
+ void deviceServices.maintainAdapters(async () => {
505
+ if (admission.external || admission.final)
506
+ return;
507
+ await background(() => maintainAdapterBuilds(acp, async () => {
508
+ bridges = await scanRoster();
509
+ management.pushRoster(bridges);
510
+ }));
511
+ });
512
+ let rosterPushQueued = false;
513
+ acp.onRosterChanged(() => {
514
+ if (admission.final)
515
+ return;
516
+ deviceServices.publishToolState?.();
517
+ if (rosterPushQueued)
518
+ return;
519
+ rosterPushQueued = true;
520
+ void Promise.resolve().then(async () => {
521
+ rosterPushQueued = false;
522
+ if (admission.final)
523
+ return;
524
+ await background(async () => {
525
+ bridges = await scanRoster();
526
+ management.pushRoster(bridges);
527
+ });
528
+ }).catch((error) => {
529
+ console.warn(`[acp-builds] roster push after capability change failed: ${String(error)}`);
530
+ });
531
+ });
532
+ supervisor = new ConnectionSupervisor({
533
+ management, computer, agent,
534
+ log: (message) => console.log(`${new Date().toISOString()} ${message}`),
535
+ });
536
+ const stopIdleSweep = startIdleSweep(agents);
537
+ const stopSwitchUpkeep = startExclusivePoll(async () => { if (!admission.final)
538
+ await background(() => agents.pollSwitches()); }, SWITCH_POLL_INTERVAL_MS);
539
+ cleanup.push(async () => { stopIdleSweep(); stopSwitchUpkeep(); });
540
+ const activity = async () => {
541
+ const active = tasks.snapshot();
542
+ const keepers = await buildKeeperStatuses(keeperPolicy, () => tasks.countedKeeperExecs);
543
+ const sum = (key) => keepers?.reduce((total, keeper) => total + (keeper.metrics[key] ?? 0), 0) ?? 0;
544
+ const files = filesHost.activitySnapshot();
545
+ const desktop = remoteDesktop?.activity() ?? { known: true, consumers: 0 };
546
+ return { known: keepers !== null && files.known && desktop.known, commands: Math.max(0, active.activeShellTasks + sum('execRunning') - sum('execCounted')), terminals: sum('ptyOpen'), agents: active.activeAgentTasks + sum('sessions'), transfers: files.transfers, desktopConsumers: desktop.consumers };
547
+ };
548
+ if (input.startupHeld) {
549
+ admission.external += 1;
550
+ admission.startup = { tasks: tasks.holdExternalAdmission() };
551
+ await holdDesktopForTargetProbe(admission.desktop);
552
+ }
553
+ const loop = supervisor.run(shutdown.signal).catch(error => { if (!shutdown.signal.aborted)
554
+ console.error('[registration] connection loop failed', registration.id, error instanceof Error ? error.message : 'unknown'); });
555
+ cleanup.push(async () => { shutdown.abort(); supervisor.stop(); await loop; });
556
+ return {
557
+ activity,
558
+ connection: async () => { const state = supervisor.channelSnapshot().management; return state === 'CONNECTED' ? 'online' : state === 'CONNECTING' ? 'connecting' : state === 'RECONNECTING' ? 'reconnecting' : 'offline'; },
559
+ pauseAdmission: async () => { admission.open = false; tasks.accepting = false; },
560
+ resumeAdmission: () => { if (!shutdown.signal.aborted) {
561
+ admission.open = true;
562
+ tasks.accepting = true;
563
+ } },
564
+ async stop(mode) {
565
+ admission.open = false;
566
+ tasks.accepting = false;
567
+ shutdown.abort();
568
+ supervisor.stop();
569
+ await loop;
570
+ if (mode === 'terminate') {
571
+ await tasks.stopAll();
572
+ }
573
+ },
574
+ };
575
+ }