@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.
- package/README.md +7 -1
- package/dist/axom-patch/cli/README.md +31 -2
- package/dist/axom-patch/cli/account-limits/generate.py +2 -2
- package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
- package/dist/axom-patch/cli/codex-terminal-output.json +2 -2
- package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
- package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +1 -1
- package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +137 -41
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +5 -88
- package/dist/axom-patch/cli/qoder-native-acp.json +23 -23
- package/dist/axom-patch/cli/runtime.json +3 -1
- package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
- package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
- package/dist/axom-patch/cli/zcode-account-limits.json +7 -7
- package/dist/axom-patch/cli/zcode-env-account.json +58 -0
- package/dist/axom-patch/cli/zcode-models.json +9 -9
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -10
- package/dist/cli.js +9 -11
- package/dist/commands/auth.js +4 -1
- package/dist/commands/update.js +68 -7
- package/dist/commands/workstation.js +94 -223
- package/dist/commands/workstationRegistrations.js +116 -0
- package/dist/credentialBroker.js +13 -2
- package/dist/credentials.js +16 -0
- package/dist/install/claude/commands.js +2 -2
- package/dist/keepers/terminal/keeper.mjs +1089 -673
- package/dist/keepers/terminal/manifest.json +4 -4
- package/dist/native/darwin-x64/axom-quic +0 -0
- package/dist/workstation/acp/acpAgents.js +11 -4
- package/dist/workstation/acp/outbox.js +12 -5
- package/dist/workstation/acp/permission.js +2 -2
- package/dist/workstation/acp/processor/checkpoint.js +4 -3
- package/dist/workstation/acp/processor/control.js +2 -2
- package/dist/workstation/acp/processor/jsonrpc.js +8 -0
- package/dist/workstation/acp/processor/session.js +3 -2
- package/dist/workstation/acp/processor/state.js +1 -0
- package/dist/workstation/acp/processor/transport/local.js +19 -3
- package/dist/workstation/acp/processor/updates.js +25 -0
- package/dist/workstation/acp/processorSession.js +12 -1
- package/dist/workstation/acp/registry/catalog.js +6 -6
- package/dist/workstation/acp/test/keeperWorld.js +4 -3
- package/dist/workstation/agentSessions.js +76 -23
- package/dist/workstation/channels/agent.js +9 -1
- package/dist/workstation/channels/computer.js +27 -4
- package/dist/workstation/channels/management.js +36 -5
- package/dist/workstation/config.js +18 -6
- package/dist/workstation/control/bindingStore.js +4 -0
- package/dist/workstation/control/commands.js +1 -0
- package/dist/workstation/control/daemonIdentity.js +90 -0
- package/dist/workstation/control/deviceCommands.js +191 -0
- package/dist/workstation/control/deviceProtocol.js +68 -0
- package/dist/workstation/control/locks.js +51 -4
- package/dist/workstation/control/rootFormat.js +16 -0
- package/dist/workstation/control/runtimeClient.js +60 -5
- package/dist/workstation/control/runtimeHost.js +96 -9
- package/dist/workstation/control/serverIdentity.js +2 -2
- package/dist/workstation/control/updateTransition.js +91 -0
- package/dist/workstation/daemon.js +262 -360
- package/dist/workstation/daemonctl.js +150 -12
- package/dist/workstation/desktopControl/automationDevice.js +219 -0
- package/dist/workstation/desktopControl/automationJson.js +74 -0
- package/dist/workstation/desktopControl/automationLocalState.js +23 -0
- package/dist/workstation/desktopControl/automationPreparation.js +1 -0
- package/dist/workstation/desktopControl/automationProtocol.js +253 -0
- package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
- package/dist/workstation/desktopControl/automationSession.js +81 -0
- package/dist/workstation/desktopControl/automationTransport.js +138 -0
- package/dist/workstation/desktopControl/capabilities.js +2 -1
- package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
- package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
- package/dist/workstation/desktopControl/deviceSockets.js +2 -2
- package/dist/workstation/desktopControl/helperProcess.js +250 -1
- package/dist/workstation/desktopControl/hostGrant.js +65 -0
- package/dist/workstation/desktopControl/lobby.js +4 -2
- package/dist/workstation/desktopControl/macProvider.js +41 -40
- package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
- package/dist/workstation/desktopControl/nativeProvider.js +31 -5
- package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
- package/dist/workstation/desktopControl/protocol.js +15 -6
- package/dist/workstation/desktopControl/supervisor.js +110 -17
- package/dist/workstation/desktopControl/wallpaper.js +26 -6
- package/dist/workstation/exec/policy.js +5 -0
- package/dist/workstation/files/client.js +10 -1
- package/dist/workstation/files/host.js +9 -2
- package/dist/workstation/journal.js +5 -2
- package/dist/workstation/keepers/acp/client.js +38 -2
- package/dist/workstation/keepers/kit/buildVersion.js +35 -11
- package/dist/workstation/keepers/kit/client.js +2 -2
- package/dist/workstation/keepers/kit/launcher.js +80 -4
- package/dist/workstation/keepers/legacy.js +16 -2
- package/dist/workstation/keepers/policy.js +5 -34
- package/dist/workstation/keepers/terminal/client.js +115 -23
- package/dist/workstation/keepers/terminal/exec.js +6 -3
- package/dist/workstation/keepers/terminal/frames.js +73 -0
- package/dist/workstation/keepers/terminal/generations.js +5 -8
- package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
- package/dist/workstation/keepers/terminal/input.js +146 -0
- package/dist/workstation/keepers/terminal/keeper.js +22 -8
- package/dist/workstation/keepers/terminal/main.js +1 -1
- package/dist/workstation/keepers/terminal/metadata.js +138 -0
- package/dist/workstation/keepers/terminal/protocol.js +4 -28
- package/dist/workstation/keepers/terminal/pty.js +1 -330
- package/dist/workstation/keepers/terminal/screen.js +246 -0
- package/dist/workstation/keepers/terminal/sessions.js +526 -0
- package/dist/workstation/oauth/deviceAuthorization.js +31 -17
- package/dist/workstation/protocol/management.js +23 -4
- package/dist/workstation/rd/artifact.js +4 -3
- package/dist/workstation/rd/control.js +3 -5
- package/dist/workstation/rd/host.js +213 -0
- package/dist/workstation/rd/migrationReadiness.js +46 -0
- package/dist/workstation/rd/provider.js +44 -10
- package/dist/workstation/rd/state.js +14 -2
- package/dist/workstation/rd/update.js +5 -8
- package/dist/workstation/rd/upkeep.js +44 -2
- package/dist/workstation/registrations/enrollment.js +250 -0
- package/dist/workstation/registrations/enrollmentPending.js +361 -0
- package/dist/workstation/registrations/migration.js +356 -0
- package/dist/workstation/registrations/model.js +34 -0
- package/dist/workstation/registrations/removal.js +222 -0
- package/dist/workstation/registrations/store.js +737 -0
- package/dist/workstation/runtime/desktopBinding.js +20 -0
- package/dist/workstation/runtime/deviceDesktop.js +193 -0
- package/dist/workstation/runtime/deviceRuntime.js +568 -0
- package/dist/workstation/runtime/deviceServices.js +117 -0
- package/dist/workstation/runtime/events.js +20 -0
- package/dist/workstation/runtime/ownership.js +100 -0
- package/dist/workstation/runtime/registrationRuntime.js +575 -0
- package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
- package/dist/workstation/runtime/removalActivity.js +184 -0
- package/dist/workstation/service/installer.js +93 -1
- package/dist/workstation/service/launchd.js +1 -0
- package/dist/workstation/service/systemd.js +1 -0
- package/dist/workstation/systemMetrics.js +3 -0
- package/dist/workstation/tools/client.js +8 -27
- package/dist/workstation/tools/info.js +6 -13
- package/dist/workstation/tools/terminalRouter.js +17 -172
- package/dist/workstation/tools/validation.js +55 -1
- package/dist/workstation/update/archiveEvidence.js +206 -0
- package/dist/workstation/update/coordinator.js +34 -13
- package/dist/workstation/update/deviceCompatibility.js +119 -0
- package/dist/workstation/update/deviceCoordinator.js +369 -0
- package/dist/workstation/update/deviceHealth.js +104 -0
- package/dist/workstation/update/deviceHealthProtocol.js +26 -0
- package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
- package/dist/workstation/update/deviceRollback.js +78 -0
- package/dist/workstation/update/globalInstall.js +80 -10
- package/dist/workstation/update/legacyBridge.js +414 -0
- package/dist/workstation/update/legacyOwner.js +39 -0
- package/dist/workstation/update/npmInstaller.js +2 -2
- package/dist/workstation/update/privateDurability.js +61 -0
- package/dist/workstation/update/recoveryPlan.js +124 -0
- package/dist/workstation/update/runUpdate.js +7 -1
- package/dist/workstation/update/taskRegistry.js +161 -3
- package/dist/workstation/update/watchdog.js +27427 -14410
- package/dist/workstation/update/watchdogProcess.js +72 -0
- package/package.json +7 -3
- package/dist/native/darwin-arm64/axom-quic +0 -0
|
@@ -1,40 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { claimDeviceDaemonIdentity } from './control/daemonIdentity.js';
|
|
2
|
+
import { openDeviceRuntimeHost } from './control/runtimeHost.js';
|
|
3
|
+
import { applyDeviceEnrollment, applyDeviceRemoval, inspectDeviceState } from './control/deviceCommands.js';
|
|
4
|
+
import { RegistrationRemovalController } from './registrations/removal.js';
|
|
5
|
+
import { LocalControlError } from './control/protocol.js';
|
|
6
|
+
import { DeviceRuntime } from './runtime/deviceRuntime.js';
|
|
7
|
+
import { createRegistrationRuntime } from './runtime/registrationRuntime.js';
|
|
8
|
+
import { createDeviceServices } from './runtime/deviceServices.js';
|
|
9
|
+
import { readLegacyRegistrationSources } from './registrations/migration.js';
|
|
5
10
|
import { promises as fs } from 'node:fs';
|
|
6
11
|
import os from 'node:os';
|
|
7
12
|
import path from 'node:path';
|
|
8
13
|
import { fileURLToPath } from 'node:url';
|
|
9
14
|
import { AGENT_WIRE_MIN_VERSION } from './protocol/agent.js';
|
|
10
|
-
import { clientDir,
|
|
11
|
-
import {
|
|
12
|
-
import { AcpAgents } from './acp/acpAgents.js';
|
|
13
|
-
import { ConnectionSupervisor } from './connection/supervisor.js';
|
|
14
|
-
import { ManagementChannel } from './channels/management.js';
|
|
15
|
-
import { ComputerChannel } from './channels/computer.js';
|
|
16
|
-
import { AgentChannel, dispatchAgentFrame as dispatchAgentChannelFrame } from './channels/agent.js';
|
|
17
|
-
import { TaskRegistry } from './update/taskRegistry.js';
|
|
18
|
-
import { RdUpkeep } from './rd/upkeep.js';
|
|
15
|
+
import { clientDir, updateServiceContext } from './config.js';
|
|
16
|
+
import { dispatchAgentFrame as dispatchAgentChannelFrame } from './channels/agent.js';
|
|
19
17
|
import { createUpdateCoordinator } from './update/runUpdate.js';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import { KeeperTransport } from './acp/processor/transport/keeper.js';
|
|
33
|
-
import { keeperGenerations } from './acp/processor/transport/generations.js';
|
|
34
|
-
import { ExecPolicy } from './exec/policy.js';
|
|
35
|
-
import { LegacyToolsKeeper } from './keepers/legacy.js';
|
|
36
|
-
import { TerminalRouter, terminalEnv, terminalShell } from './tools/terminalRouter.js';
|
|
37
|
-
import { ToolchainProbe } from './toolchain.js';
|
|
18
|
+
import { CLIENT_VERSION } from './version.js';
|
|
19
|
+
import { captureStartupPackageEvidence, deviceHealthExpectationSchema } from './update/deviceHealth.js';
|
|
20
|
+
import { readDeviceBootstrapRecovery } from './update/recoveryPlan.js';
|
|
21
|
+
import { readLegacyBridge, releaseLegacyBridge, prepareLegacyContainerBridge, recoverLegacyContainer, legacyBridgeUpdateSnapshot, resumeLegacyManagedBridge } from './update/legacyBridge.js';
|
|
22
|
+
import { prepareLegacyRdReadiness } from './rd/migrationReadiness.js';
|
|
23
|
+
import { createDeviceRecoveryTick } from './update/deviceRecoveryLifecycle.js';
|
|
24
|
+
import { UpdateStateStore } from './update/stateStore.js';
|
|
25
|
+
import { prepareDeviceRollbackHandoff } from './update/deviceRollback.js';
|
|
26
|
+
import { RdUpkeep } from './rd/upkeep.js';
|
|
27
|
+
import { adoptBundledRd, synchronizeRd } from './rd/update.js';
|
|
28
|
+
import { readRdState } from './rd/state.js';
|
|
29
|
+
import { resolveCliPath, serviceContext } from './daemonctl.js';
|
|
38
30
|
/**
|
|
39
31
|
* The path `runDaemon` hands `createUpdateCoordinator` as `cliPath`, used to re-provision the OS
|
|
40
32
|
* service after an update (see runUpdate.ts's `provisionService`).
|
|
@@ -240,8 +232,6 @@ export async function existingDirectories(roots) {
|
|
|
240
232
|
}
|
|
241
233
|
/** How often the daemon re-reads the workspaces config (and root existence) to restart the files host on a change. */
|
|
242
234
|
export const FILES_ROOTS_POLL_MS = 10_000;
|
|
243
|
-
/** How often the legacy tools keeper is re-probed and checked for retirement (keepers/legacy.ts). */
|
|
244
|
-
export const LEGACY_TICK_MS = 30_000;
|
|
245
235
|
/**
|
|
246
236
|
* Builds the object `ManagementChannelOptions.keepers` reports (Task 26): the policy's statuses, never a rejection (a failure is
|
|
247
237
|
* `null`, which the channel also guarantees). `countedExecs` (final review minor 7): keeper-terminal execs `activeShellTasks` also
|
|
@@ -256,7 +246,7 @@ export async function buildKeeperStatuses(policy, countedExecs = () => 0) {
|
|
|
256
246
|
catch {
|
|
257
247
|
return null;
|
|
258
248
|
}
|
|
259
|
-
const terminals = statuses.filter((status) => status.kind === 'terminal'
|
|
249
|
+
const terminals = statuses.filter((status) => status.kind === 'terminal');
|
|
260
250
|
const target = terminals.find((status) => status.replacement === 'none') ?? terminals[0];
|
|
261
251
|
if (!target)
|
|
262
252
|
return statuses;
|
|
@@ -287,342 +277,254 @@ export function startExclusivePoll(poll, intervalMs) {
|
|
|
287
277
|
return () => clearInterval(timer);
|
|
288
278
|
}
|
|
289
279
|
export async function runDaemon(options = {}) {
|
|
290
|
-
const directory = clientDir();
|
|
291
|
-
const shutdown = new AbortController();
|
|
292
|
-
const state = { snapshot: async () => ({ ...stoppedRuntime(), connection: 'connecting' }) };
|
|
293
|
-
const { host, identity } = await claimDaemonIdentity(directory, async (identity) => {
|
|
294
|
-
const config = identity.config;
|
|
295
|
-
const host = await openRuntimeHost({ directory, binding: { server: normalizeWorkstationServer(config.server), workstationId: config.workstationId, ...(config.ownerUserId ? { ownerUserId: config.ownerUserId } : {}) },
|
|
296
|
-
mode: options.supervisedByServiceManager ? 'launchd' : 'foreground', snapshot: () => state.snapshot(), shutdown: async () => { shutdown.abort(); },
|
|
297
|
-
update: async (release, operationId) => {
|
|
298
|
-
if (!state.update)
|
|
299
|
-
return { phase: 'BUSY', commandId: operationId };
|
|
300
|
-
void state.update.handle({ commandId: operationId, release, force: false }).catch(error => console.error('[update] local request failed', error));
|
|
301
|
-
return { phase: 'REQUESTED', commandId: operationId, version: release.version };
|
|
302
|
-
},
|
|
303
|
-
});
|
|
304
|
-
return { host, identity };
|
|
305
|
-
});
|
|
306
280
|
try {
|
|
307
|
-
await
|
|
281
|
+
await runDeviceDaemon(options);
|
|
308
282
|
}
|
|
309
|
-
|
|
310
|
-
await
|
|
283
|
+
catch (error) {
|
|
284
|
+
const bridge = await readLegacyBridge(options.directory ?? clientDir());
|
|
285
|
+
if (bridge?.lifecycle !== 'container')
|
|
286
|
+
throw error;
|
|
287
|
+
console.error('[device] Container migration startup failed; restoring the verified predecessor', error instanceof Error ? error.message : String(error));
|
|
288
|
+
await recoverLegacyContainer(bridge);
|
|
311
289
|
}
|
|
312
290
|
}
|
|
313
|
-
async function
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
291
|
+
async function runDeviceDaemon(options) {
|
|
292
|
+
const directory = options.directory ?? clientDir();
|
|
293
|
+
const lifecycle = resolveServiceLifecycle(options);
|
|
294
|
+
if (lifecycle === 'container')
|
|
295
|
+
await prepareLegacyContainerBridge(directory, { ...serviceContext(), configDir: directory });
|
|
296
|
+
const packageRoot = updateServiceContext(directory).packageRoot;
|
|
297
|
+
const startupPackage = await captureStartupPackageEvidence(packageRoot, CLIENT_VERSION);
|
|
298
|
+
const legacyBridge = await readLegacyBridge(directory, startupPackage);
|
|
299
|
+
if (legacyBridge?.lifecycle === 'managed')
|
|
300
|
+
await resumeLegacyManagedBridge(directory, startupPackage);
|
|
301
|
+
let legacyStartupHeld = !!legacyBridge;
|
|
302
|
+
if (options.desktopProvider || options.onDesktopCommand)
|
|
303
|
+
throw new LocalControlError('UNSUPPORTED_PROTOCOL', 'Device remote desktop integration is unavailable until the physical isolation contract is integrated');
|
|
304
|
+
const shutdown = new AbortController();
|
|
305
|
+
const services = options.deviceServices ?? createDeviceServices({ root: directory });
|
|
306
|
+
const previousUpdateSnapshot = services.updates.snapshot.bind(services.updates);
|
|
307
|
+
services.updates.snapshot = async (registrationId) => await legacyBridgeUpdateSnapshot(directory) ?? previousUpdateSnapshot(registrationId);
|
|
308
|
+
let device;
|
|
309
|
+
let removal;
|
|
310
|
+
let generation = '';
|
|
311
|
+
let fullyInitialized = false;
|
|
312
|
+
let recoveryLease;
|
|
313
|
+
let rollbackLease;
|
|
314
|
+
let rollbackPreparation;
|
|
315
|
+
let rollbackBinding;
|
|
316
|
+
let rdUpkeep;
|
|
317
|
+
let stopRdPoll = () => { };
|
|
318
|
+
const mode = options.supervisedByServiceManager ? 'launchd' : 'foreground';
|
|
319
|
+
const snapshot = async () => ({ ...await inspectDeviceState({ store, device, generation, mode }), desktop: services.desktopFacts?.() });
|
|
320
|
+
let store;
|
|
321
|
+
let initialized;
|
|
322
|
+
let initializationFailed;
|
|
323
|
+
const initialization = new Promise((resolve, reject) => { initialized = resolve; initializationFailed = reject; });
|
|
324
|
+
void initialization.catch(() => { });
|
|
325
|
+
let preparing;
|
|
326
|
+
let preparedMode;
|
|
327
|
+
const prepareStop = (stopMode) => {
|
|
328
|
+
if (preparedMode)
|
|
329
|
+
return preparedMode === 'terminate' || preparedMode === stopMode ? Promise.resolve() : Promise.reject(new LocalControlError('OUTCOME_UNKNOWN', 'Detached work requires verified termination before the device can be stopped'));
|
|
330
|
+
if (preparing)
|
|
331
|
+
return preparing.then(() => prepareStop(stopMode));
|
|
332
|
+
const retiring = device?.stop(stopMode);
|
|
333
|
+
void retiring?.catch(() => { });
|
|
334
|
+
preparing = (async () => {
|
|
335
|
+
await initialization;
|
|
336
|
+
if (retiring)
|
|
337
|
+
await retiring;
|
|
338
|
+
else
|
|
339
|
+
await device?.stop(stopMode);
|
|
340
|
+
preparedMode = stopMode;
|
|
341
|
+
})().catch(error => { preparing = undefined; throw error; });
|
|
342
|
+
return preparing;
|
|
343
|
+
};
|
|
344
|
+
let stopping;
|
|
345
|
+
const stop = (stopMode) => {
|
|
346
|
+
fullyInitialized = false;
|
|
347
|
+
stopping ??= (async () => {
|
|
348
|
+
stopRdPoll();
|
|
349
|
+
await rdUpkeep?.stop();
|
|
350
|
+
await prepareStop(stopMode);
|
|
351
|
+
await services.stop();
|
|
352
|
+
shutdown.abort();
|
|
353
|
+
})().catch(error => { stopping = undefined; throw error; });
|
|
354
|
+
return stopping;
|
|
355
|
+
};
|
|
356
|
+
const host = await claimDeviceDaemonIdentity(directory, async (identity) => {
|
|
357
|
+
store = identity.store;
|
|
358
|
+
return openDeviceRuntimeHost({ directory, mode, snapshot,
|
|
359
|
+
shutdown: stop,
|
|
360
|
+
prepareShutdown: prepareStop,
|
|
361
|
+
addRegistration: async (request) => {
|
|
362
|
+
if (!fullyInitialized || !device || preparing || stopping)
|
|
363
|
+
throw new LocalControlError('BUSY', 'Device lifecycle transition is in progress');
|
|
364
|
+
const finishMutation = device.beginMembershipMutation();
|
|
365
|
+
let result;
|
|
366
|
+
try {
|
|
367
|
+
result = await applyDeviceEnrollment(request, directory, snapshot, () => device.assertMutationAllowed());
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
finishMutation();
|
|
371
|
+
}
|
|
372
|
+
await device.reconcile();
|
|
373
|
+
return result;
|
|
374
|
+
},
|
|
375
|
+
removeRegistration: async (request) => {
|
|
376
|
+
if (!fullyInitialized || !removal || preparing || stopping)
|
|
377
|
+
throw new LocalControlError('BUSY', 'Device lifecycle transition is in progress');
|
|
378
|
+
return applyDeviceRemoval(request, { controller: removal, snapshot });
|
|
379
|
+
},
|
|
380
|
+
reconcile: async () => { if (!fullyInitialized || !device)
|
|
381
|
+
throw new LocalControlError('BUSY', 'Device initialization is in progress'); await device.reconcile(); },
|
|
382
|
+
update: async (release, operationId, source) => { if (!fullyInitialized)
|
|
383
|
+
throw new LocalControlError('BUSY', 'Device initialization is in progress'); return services.updates.local(operationId, release, source); },
|
|
384
|
+
updateHealth: (request, currentGeneration) => services.updates.health(request, currentGeneration),
|
|
385
|
+
prepareUpdateRollback: async (request, currentGeneration) => {
|
|
386
|
+
if (!fullyInitialized || !device)
|
|
387
|
+
throw new LocalControlError('OUTCOME_UNKNOWN', 'Original target initialization is incomplete');
|
|
388
|
+
const binding = JSON.stringify(deviceHealthExpectationSchema.parse(request));
|
|
389
|
+
if (rollbackBinding && rollbackBinding !== binding)
|
|
390
|
+
throw new LocalControlError('BINDING_CONFLICT', 'Original rollback preparation request changed');
|
|
391
|
+
rollbackBinding = binding;
|
|
392
|
+
rollbackPreparation ??= prepareDeviceRollbackHandoff({ root: directory, generation: currentGeneration, startup: startupPackage,
|
|
393
|
+
lease: () => { rollbackLease ??= recoveryLease ?? device.acquireMaintenance({ transactionId: request.transactionId }); return rollbackLease; },
|
|
394
|
+
}, request).catch(error => { rollbackPreparation = undefined; throw error; });
|
|
395
|
+
return rollbackPreparation;
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
}, startupPackage);
|
|
399
|
+
generation = host.generation;
|
|
400
|
+
let stopPoll = () => { };
|
|
401
|
+
const signalStop = () => { void stop('detach').catch(error => console.error('[device] shutdown needs verification', error)); };
|
|
402
|
+
const rejection = (reason) => console.error('[device] unhandled rejection', reason instanceof Error ? reason.message : 'unknown');
|
|
403
|
+
process.on('unhandledRejection', rejection);
|
|
404
|
+
process.once('SIGINT', signalStop);
|
|
405
|
+
process.once('SIGTERM', signalStop);
|
|
406
|
+
try {
|
|
407
|
+
let bootstrap;
|
|
403
408
|
try {
|
|
404
|
-
|
|
405
|
-
const current = await existingDirectories(roots);
|
|
406
|
-
if (JSON.stringify(current) !== JSON.stringify(hostRoots))
|
|
407
|
-
filesHost.reconfigure();
|
|
409
|
+
bootstrap = await readDeviceBootstrapRecovery(directory, startupPackage);
|
|
408
410
|
}
|
|
409
|
-
catch {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const drainKeeper = oneShotDrain((kind, gen) => (kind === 'acp' ? acpLauncher : terminalLauncher).pathsFor(kind, gen));
|
|
449
|
-
const keeperPolicy = new KeeperPolicy({
|
|
450
|
-
dir: path.join(dir, 'keepers'),
|
|
451
|
-
probe: oneShotProbe((kind, gen) => (kind === 'acp' ? acpLauncher : terminalLauncher).pathsFor(kind, gen)),
|
|
452
|
-
terminal: {
|
|
453
|
-
currentGen: () => terminalKeepers.currentGen(),
|
|
454
|
-
live: async () => (await terminalKeepers.running()).map((generation) => generation.gen),
|
|
455
|
-
manifest: () => terminalKeepers.manifest(),
|
|
456
|
-
stop: async (gen, mode) => {
|
|
457
|
-
const result = await terminalLauncher.stop('terminal', gen, mode);
|
|
458
|
-
if (result.stopped)
|
|
459
|
-
terminalKeepers.forget(gen);
|
|
460
|
-
return result;
|
|
411
|
+
catch (error) {
|
|
412
|
+
if (error.code !== 'ENOENT')
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
if (legacyBridge && process.platform === 'linux') {
|
|
416
|
+
try {
|
|
417
|
+
const readiness = await prepareLegacyRdReadiness(directory);
|
|
418
|
+
if (readiness.kind === 'unavailable')
|
|
419
|
+
console.warn(`[rd] ${readiness.reason}`);
|
|
420
|
+
else if (readiness.kind === 'staged')
|
|
421
|
+
console.warn('[rd] Compatible desktop runtime staged; activation is deferred until the original desktop authority is retired and physically accepted.');
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
console.warn('[rd] Legacy desktop configuration remains unavailable', error instanceof Error ? error.message : String(error));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
await services.start();
|
|
428
|
+
device = new DeviceRuntime({ store, desktopActivity: () => services.desktopActivity?.() ?? { known: false, consumers: 1 }, factory: async (identity) => createRegistrationRuntime({ ...identity,
|
|
429
|
+
registrationRoot: store.root,
|
|
430
|
+
startupHeld: legacyStartupHeld || !!bootstrap && (bootstrap.transaction.phase === 'RESTARTING' || !!bootstrap.transaction.installedRollback && !!bootstrap.transaction.recoveryRequired),
|
|
431
|
+
paths: { legacySources: await readLegacyRegistrationSources(directory, identity.registration.id) }, deviceServices: services,
|
|
432
|
+
}) });
|
|
433
|
+
services.updates.configure({ directory, device, generation, lifecycle, startupPackage, packageRoot, initialized: () => fullyInitialized,
|
|
434
|
+
execute: (command, context) => createUpdateCoordinator({ dir: directory, server: context.source, cliPath: daemonCliPath(), serviceLifecycle: lifecycle, deviceUpdate: context, restart: async () => { await stop('detach'); } }).handle(command),
|
|
435
|
+
completeRecovery: async (persist) => {
|
|
436
|
+
if (!recoveryLease) {
|
|
437
|
+
await persist();
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const original = recoveryLease;
|
|
441
|
+
await original.release(async () => {
|
|
442
|
+
const recovery = await readDeviceBootstrapRecovery(directory, startupPackage);
|
|
443
|
+
if (!recovery)
|
|
444
|
+
throw new LocalControlError('OUTCOME_UNKNOWN', 'Original device restart recovery evidence is missing');
|
|
445
|
+
await new UpdateStateStore(directory).save({ phase: 'SUCCEEDED', commandId: recovery.transaction.transactionId, version: recovery.transaction.release.version, releaseSha512: recovery.transaction.release.sha512, remoteForcePolicy: 'drain-only' });
|
|
446
|
+
await persist();
|
|
447
|
+
});
|
|
448
|
+
if (recoveryLease === original)
|
|
449
|
+
recoveryLease = undefined;
|
|
461
450
|
},
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
});
|
|
497
|
-
const metrics = new SystemMetricsSampler(cfg.workspaces ?? []);
|
|
498
|
-
await metrics.start();
|
|
499
|
-
const toolchain = new ToolchainProbe(() => terminalShell(), () => terminalEnv());
|
|
500
|
-
toolchain.start();
|
|
501
|
-
const desktopSupervisor = desktopProvider && !options.onDesktopCommand ? new DesktopSessionSupervisor({ provider: desktopProvider,
|
|
502
|
-
sockets: new DeviceSockets(cfg.server, process.env.AXOM_DESKTOP_ALLOW_PRIVATE_WS === '1'),
|
|
503
|
-
report: (generation, frame) => management.sendDesktopState(generation, frame),
|
|
504
|
-
refreshCapabilities: generation => management.refreshDesktopCapabilities(generation), }) : undefined;
|
|
505
|
-
let rdGeneration;
|
|
506
|
-
const rdUpkeep = process.platform === 'linux' && desktopSupervisor ? new RdUpkeep({
|
|
507
|
-
synchronize: async () => {
|
|
508
|
-
if (!rdGeneration || shutdown.signal.aborted)
|
|
509
|
-
return;
|
|
510
|
-
const { synchronizeRd } = await import('./rd/update.js');
|
|
511
|
-
return synchronizeRd(dir, cfg.server, { deferActivation: true });
|
|
512
|
-
},
|
|
513
|
-
whenIdle: operation => desktopSupervisor.whenIdle(async () => {
|
|
514
|
-
if (!tasks.canUpdate() || !rdGeneration || shutdown.signal.aborted)
|
|
515
|
-
return;
|
|
516
|
-
await operation();
|
|
517
|
-
}),
|
|
518
|
-
refresh: () => { if (rdGeneration)
|
|
519
|
-
management.refreshDesktopCapabilities(rdGeneration); },
|
|
520
|
-
log: message => console.error(`${new Date().toISOString()} ${message}`),
|
|
521
|
-
}) : undefined;
|
|
522
|
-
const stopRdUpkeep = rdUpkeep ? startExclusivePoll(() => rdUpkeep.tick(), 60_000) : () => { };
|
|
523
|
-
const management = new ManagementChannel({
|
|
524
|
-
server: cfg.server, workstationId: cfg.workstationId, workstation: cfg.workstationId, clientKey, serverPub,
|
|
525
|
-
bridges: () => bridges,
|
|
526
|
-
workspaces: cfg.workspaces,
|
|
527
|
-
playgroundCwd,
|
|
528
|
-
metrics: () => metrics.snapshot(),
|
|
529
|
-
toolchain: () => toolchain.current(),
|
|
530
|
-
allowInsecureWs,
|
|
531
|
-
onReady: async (ready) => {
|
|
532
|
-
await updates.managementReady(ready.release, ready.generation);
|
|
533
|
-
await activateRdAfterReadiness?.();
|
|
534
|
-
if (ready.selectedWire >= 9)
|
|
535
|
-
desktopSupervisor?.managementReady(ready.generation);
|
|
536
|
-
rdGeneration = ready.selectedWire >= 9 ? ready.generation : undefined;
|
|
537
|
-
void rdUpkeep?.tick();
|
|
538
|
-
},
|
|
539
|
-
desktopProvider,
|
|
540
|
-
onDesktopCommand: options.onDesktopCommand ?? ((frame, generation) => desktopSupervisor?.dispatch(frame, generation) ?? Promise.resolve()),
|
|
541
|
-
onClosed: generation => {
|
|
542
|
-
if (rdGeneration === generation)
|
|
543
|
-
rdGeneration = undefined;
|
|
544
|
-
desktopSupervisor?.managementClosed(generation);
|
|
545
|
-
options.onDesktopManagementClosed?.(generation);
|
|
546
|
-
},
|
|
547
|
-
onUpdate: (command) => updates.handle(command),
|
|
548
|
-
onExternalAgentsScan: async () => {
|
|
549
|
-
bridges = await scanRoster();
|
|
550
|
-
return bridges;
|
|
551
|
-
},
|
|
552
|
-
snapshot: () => buildManagementSnapshot(supervisor, tasks, updates),
|
|
553
|
-
keepers: () => buildKeeperStatuses(keeperPolicy, () => tasks.countedKeeperExecs),
|
|
554
|
-
onKeeperControl: (frame) => keeperPolicy.control(frame),
|
|
555
|
-
});
|
|
556
|
-
options.onDesktopManagement?.(management);
|
|
557
|
-
const agent = new AgentChannel({ server: cfg.server, agents, bridges: () => bridges, allowInsecureWs });
|
|
558
|
-
// Spec §6.2: first take over what an older CLI left (`_npx` copies, pre-build binary dirs) so agents
|
|
559
|
-
// that worked keep working; then §6.1: a version this CLI certifies (runtime.json/catalog pin) or a
|
|
560
|
-
// changed patch set moves an installed agent's current build forward by itself ("Updated by Axom").
|
|
561
|
-
// In the background: either may copy or download, an open of an agent not done yet waits on that
|
|
562
|
-
// agent's lock, and running sessions keep the build they were spawned from either way. A changed
|
|
563
|
-
// roster reaches the server the way a scan's does; offline, the next hello carries it.
|
|
564
|
-
void maintainAdapterBuilds(acp, async () => {
|
|
565
|
-
bridges = await scanRoster();
|
|
566
|
-
management.pushRoster(bridges);
|
|
567
|
-
});
|
|
568
|
-
// A quota capability learned mid-flight (this process's first spawn of an agent) changes what the
|
|
569
|
-
// roster would say; push it the way a build change does. Coalesced: several agents flipping
|
|
570
|
-
// around one burst of opens share a single scan, and a flip during that scan queues one more.
|
|
571
|
-
let rosterPushQueued = false;
|
|
572
|
-
acp.onRosterChanged(() => {
|
|
573
|
-
if (rosterPushQueued)
|
|
451
|
+
});
|
|
452
|
+
removal = new RegistrationRemovalController({ store, device, generation });
|
|
453
|
+
const finishInitialization = device.beginMembershipMutation();
|
|
454
|
+
try {
|
|
455
|
+
await device.reconcile();
|
|
456
|
+
}
|
|
457
|
+
finally {
|
|
458
|
+
finishInitialization();
|
|
459
|
+
}
|
|
460
|
+
let legacyLease;
|
|
461
|
+
if (legacyBridge) {
|
|
462
|
+
legacyLease = device.acquireMaintenance({ transactionId: legacyBridge.commandId });
|
|
463
|
+
await legacyLease.ready;
|
|
464
|
+
}
|
|
465
|
+
if (bootstrap && (bootstrap.transaction.phase === 'RESTARTING' || bootstrap.transaction.installedRollback && bootstrap.transaction.recoveryRequired)) {
|
|
466
|
+
recoveryLease = device.acquireMaintenance({ transactionId: bootstrap.transaction.transactionId });
|
|
467
|
+
await recoveryLease.ready;
|
|
468
|
+
}
|
|
469
|
+
const rdSource = async () => (await readRdState(directory))?.artifactSource ?? services.rdSource?.();
|
|
470
|
+
let pinnedRdSource;
|
|
471
|
+
rdUpkeep = new RdUpkeep({ device,
|
|
472
|
+
adopt: async () => { pinnedRdSource = await rdSource(); if (pinnedRdSource)
|
|
473
|
+
await adoptBundledRd(directory, pinnedRdSource); },
|
|
474
|
+
synchronize: async () => pinnedRdSource ? synchronizeRd(directory, pinnedRdSource, { deferActivation: true }) : undefined,
|
|
475
|
+
whenIdle: async (operation) => { await operation(); return true; }, refresh: () => services.publishToolState?.(), log: message => console.warn(message),
|
|
476
|
+
});
|
|
477
|
+
const tickRd = async () => { if (process.platform === 'linux' && await rdSource())
|
|
478
|
+
await rdUpkeep.tick(); };
|
|
479
|
+
await tickRd();
|
|
480
|
+
fullyInitialized = true;
|
|
481
|
+
initialized();
|
|
482
|
+
if (stopping)
|
|
483
|
+
await stopping.catch(() => { });
|
|
484
|
+
if (shutdown.signal.aborted)
|
|
574
485
|
return;
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}).catch((error) => {
|
|
581
|
-
console.warn(`[acp-builds] roster push after capability change failed: ${String(error)}`);
|
|
486
|
+
stopRdPoll = startExclusivePoll(tickRd, 30_000);
|
|
487
|
+
const recoveryTick = createDeviceRecoveryTick({ root: directory, startup: startupPackage, generation, bootstrap, signal: shutdown.signal,
|
|
488
|
+
initialized: () => fullyInitialized, lease: () => recoveryLease,
|
|
489
|
+
released: original => { if (recoveryLease === original)
|
|
490
|
+
recoveryLease = undefined; }, reconcile: () => device.reconcile(),
|
|
582
491
|
});
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const sum = (key) => keepers?.reduce((n, k) => n + (k.metrics[key] ?? 0), 0) ?? 0;
|
|
598
|
-
return { ...stoppedRuntime(), connection: channels.management === 'CONNECTED' ? 'online' : 'reconnecting',
|
|
599
|
-
activity: keepers === null ? null : { commands: Math.max(0, active.activeShellTasks + sum('execRunning') - sum('execCounted')), terminals: sum('ptyOpen'), agents: active.activeAgentTasks + sum('sessions'), transfers: 0 } };
|
|
600
|
-
};
|
|
601
|
-
const stop = () => shutdown.abort();
|
|
602
|
-
process.once('SIGINT', stop);
|
|
603
|
-
process.once('SIGTERM', stop);
|
|
604
|
-
try {
|
|
605
|
-
await supervisor.run(shutdown.signal);
|
|
492
|
+
stopPoll = startExclusivePoll(async () => {
|
|
493
|
+
if (legacyLease && await releaseLegacyBridge(directory, startupPackage, generation, complete => legacyLease.release(complete))) {
|
|
494
|
+
legacyLease = undefined;
|
|
495
|
+
legacyStartupHeld = false;
|
|
496
|
+
await device.reconcile();
|
|
497
|
+
}
|
|
498
|
+
await recoveryTick();
|
|
499
|
+
}, 2_000);
|
|
500
|
+
if (!shutdown.signal.aborted)
|
|
501
|
+
await new Promise(resolve => shutdown.signal.addEventListener('abort', () => resolve(), { once: true }));
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
initializationFailed(error);
|
|
505
|
+
throw error;
|
|
606
506
|
}
|
|
607
507
|
finally {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
process.removeListener('
|
|
612
|
-
process.removeListener('
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
508
|
+
stopRdPoll();
|
|
509
|
+
stopPoll();
|
|
510
|
+
process.removeListener('SIGINT', signalStop);
|
|
511
|
+
process.removeListener('SIGTERM', signalStop);
|
|
512
|
+
process.removeListener('unhandledRejection', rejection);
|
|
513
|
+
try {
|
|
514
|
+
await rdUpkeep?.stop();
|
|
515
|
+
}
|
|
516
|
+
finally {
|
|
517
|
+
try {
|
|
518
|
+
await device?.stop('detach');
|
|
519
|
+
}
|
|
520
|
+
finally {
|
|
521
|
+
try {
|
|
522
|
+
await services.stop();
|
|
523
|
+
}
|
|
524
|
+
finally {
|
|
525
|
+
await host.close();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
627
529
|
}
|
|
628
530
|
}
|