@nowcrew/daemon 0.6.89 → 0.6.91-beta.1

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 (116) hide show
  1. package/README.md +64 -3
  2. package/dist/agent-ability/materializer.js +21 -10
  3. package/dist/agent-environment-contract.js +55 -0
  4. package/dist/agent-environment-control.js +24 -0
  5. package/dist/agent-environment-execution.js +98 -0
  6. package/dist/agent-environments.js +275 -0
  7. package/dist/automatic-daemon-installer.js +49 -1
  8. package/dist/computer-main.js +18 -0
  9. package/dist/computer-profile.js +17 -4
  10. package/dist/computer-setup-client.js +318 -0
  11. package/dist/computer-setup-orchestration.js +203 -0
  12. package/dist/computer-setup.js +244 -0
  13. package/dist/config.js +30 -12
  14. package/dist/console-formatter.js +3 -3
  15. package/dist/console.js +15 -2
  16. package/dist/daemon-update-eligibility.js +1 -1
  17. package/dist/daemon-updater.js +2 -12
  18. package/dist/execution-journal.js +9 -6
  19. package/dist/execution-protocol.js +1 -0
  20. package/dist/execution-runner.js +13 -8
  21. package/dist/i18n.js +19 -0
  22. package/dist/list-models.js +12 -12
  23. package/dist/local-execution-lease.js +22 -0
  24. package/dist/local-executor.js +57 -84
  25. package/dist/local-memory/candidate.js +43 -0
  26. package/dist/local-memory/index-budget.js +8 -0
  27. package/dist/local-memory/inventory-cli.js +61 -0
  28. package/dist/local-memory/inventory-preview.js +159 -0
  29. package/dist/local-memory/inventory-source.js +159 -0
  30. package/dist/local-memory/pending-match.js +29 -0
  31. package/dist/local-memory/pending-query.js +100 -0
  32. package/dist/local-memory/pending-schema.js +29 -0
  33. package/dist/local-memory/pending-source.js +126 -0
  34. package/dist/local-memory/pending.js +34 -84
  35. package/dist/local-memory/prompt.js +19 -12
  36. package/dist/local-memory/read-cli.js +97 -12
  37. package/dist/local-memory/rollout.js +82 -0
  38. package/dist/local-memory/runtime.js +23 -17
  39. package/dist/local-memory/work-log-execution.js +31 -0
  40. package/dist/local-memory/work-log.js +58 -0
  41. package/dist/local-memory/write.js +27 -58
  42. package/dist/local-runtime-environment.js +50 -0
  43. package/dist/machine-environment-cache.js +147 -0
  44. package/dist/machine-environment-contract.js +10 -0
  45. package/dist/machine-environment-control.js +27 -0
  46. package/dist/machine-environment-policy.js +75 -0
  47. package/dist/machine-environment-process.js +110 -0
  48. package/dist/machine-info.js +70 -25
  49. package/dist/machine-runtime-environment.js +189 -0
  50. package/dist/machine-shell-environment-capture.js +135 -0
  51. package/dist/machine-windows-environment-capture.js +215 -0
  52. package/dist/machine-windows-environment-script.js +158 -0
  53. package/dist/main.js +4 -69
  54. package/dist/marketplace-command.js +21 -0
  55. package/dist/marketplace-controller.js +80 -0
  56. package/dist/marketplace-protocol.js +38 -0
  57. package/dist/marketplace-remote.js +48 -0
  58. package/dist/marketplace-runner.js +235 -0
  59. package/dist/marketplace-serve.js +41 -0
  60. package/dist/marketplace-source.js +103 -0
  61. package/dist/message-file-preview.js +246 -0
  62. package/dist/pre-admission-rejection.js +64 -0
  63. package/dist/project-skills/execution-adapter.js +3 -2
  64. package/dist/project-skills/runtime-launch.js +14 -0
  65. package/dist/provider-env.js +26 -15
  66. package/dist/runner.js +17 -4
  67. package/dist/runtime-environment-merge.js +16 -0
  68. package/dist/runtime-health-handler.js +10 -0
  69. package/dist/runtime-health.js +3 -1
  70. package/dist/runtime-path.js +22 -12
  71. package/dist/runtime-probe.js +35 -6
  72. package/dist/runtimes/acp-probe-transport.js +69 -0
  73. package/dist/runtimes/claude-models.js +20 -9
  74. package/dist/runtimes/deepseek-harness-launch.js +10 -0
  75. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  76. package/dist/runtimes/deepseek-harness-runner.js +226 -0
  77. package/dist/runtimes/deepseek-harness.js +76 -15
  78. package/dist/runtimes/harness-probe-retry.js +42 -0
  79. package/dist/runtimes/hermes-models.js +2 -1
  80. package/dist/runtimes/kimi-acp-runner.js +78 -13
  81. package/dist/runtimes/opencode.js +2 -2
  82. package/dist/serve.js +88 -178
  83. package/dist/storage-identity.js +0 -3
  84. package/dist/storage-serve-adapter.js +6 -72
  85. package/dist/supervised-runtime.js +17 -2
  86. package/dist/token.js +0 -1
  87. package/dist/workspace-control-handler.js +27 -0
  88. package/dist/workspace-fs.js +66 -15
  89. package/dist/workspace.js +1 -1
  90. package/package.json +13 -12
  91. package/dist/codex-home-launch.js +0 -7
  92. package/dist/codex-home-mode-controller.js +0 -68
  93. package/dist/daemon-migration-controller.js +0 -211
  94. package/dist/daemon-migration-wiring.js +0 -22
  95. package/dist/runtimes/codex-home-migration-cli.js +0 -26
  96. package/dist/runtimes/codex-home-migration.js +0 -112
  97. package/dist/runtimes/codex-home.js +0 -236
  98. package/dist/storage-control.js +0 -212
  99. package/dist/storage-copy.js +0 -628
  100. package/dist/storage-credential-guard.js +0 -21
  101. package/dist/storage-doctor.js +0 -228
  102. package/dist/storage-manifest.js +0 -1027
  103. package/dist/storage-migration-controller.js +0 -440
  104. package/dist/storage-migration-manifest.js +0 -105
  105. package/dist/storage-migration-policy.js +0 -100
  106. package/dist/storage-runtime-controller.js +0 -381
  107. package/dist/storage-runtime-readiness.js +0 -241
  108. package/dist/storage-safe-fs.js +0 -302
  109. package/dist/storage-status-delivery.js +0 -224
  110. package/dist/storage-status-replay.js +0 -140
  111. package/dist/storage-target-confirmation.js +0 -129
  112. package/dist/storage-worker-command.js +0 -37
  113. package/dist/storage-worker-executor.js +0 -447
  114. package/dist/storage-worker-handoff.js +0 -151
  115. package/dist/storage-worker-operation.js +0 -294
  116. package/dist/storage-worker-status.js +0 -259
@@ -1,5 +1,6 @@
1
1
  import { homedir } from "node:os";
2
2
  import { posix, win32 } from "node:path";
3
+ import { z } from "zod";
3
4
  import { assertProfileAgentsRootUnique, listProfiles, loadProfile, MachineTokenSchema, removeProfile, saveProfile, windowsDpapiProtector, } from "./computer-profile.js";
4
5
  import { buildServiceSpec, captureExactService, hardenWindowsProfile, replaceExactService, restoreExactService, serviceAction, serviceStatus, systemCommandRunner, uninstallService, } from "./computer-service.js";
5
6
  import { daemonGlobalInstallation } from "./daemon-installation.js";
@@ -15,6 +16,7 @@ import { npmInvocation } from "./npm-command.js";
15
16
  import { discoverExactLegacyService, } from "./automatic-service-migration.js";
16
17
  import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
17
18
  import { serverHostIdentity } from "./server-url.js";
19
+ import { computerSetupRecoveryGuidance } from "./computer-setup-client.js";
18
20
  function profileStorage(platform, userHome, runner) {
19
21
  return {
20
22
  platform,
@@ -247,8 +249,33 @@ function recoveryError(primary, failures) {
247
249
  return original;
248
250
  return new Error(`${original.message}; recovery failed: ${failures.map((error) => error.message).join("; ")}`, { cause: original });
249
251
  }
252
+ function setupIdentity(input) {
253
+ if (input.workspaceId === undefined && input.machineId === undefined)
254
+ return null;
255
+ if (input.workspaceId === undefined || input.machineId === undefined) {
256
+ throw new Error("Computer setup requires both Workspace and Machine identity");
257
+ }
258
+ return {
259
+ workspaceId: z.string().uuid().parse(input.workspaceId),
260
+ machineId: z.string().uuid().parse(input.machineId),
261
+ };
262
+ }
263
+ function assertExistingProfileIdentity(previous, identity) {
264
+ if (previous === null || identity === null)
265
+ return;
266
+ if (previous.workspaceId === undefined || previous.machineId === undefined) {
267
+ throw new Error(`Profile '${previous.name}' does not record its Workspace and Machine identity`);
268
+ }
269
+ if (previous.workspaceId !== identity.workspaceId) {
270
+ throw new Error(`Profile '${previous.name}' belongs to a different Workspace identity`);
271
+ }
272
+ if (previous.machineId !== identity.machineId) {
273
+ throw new Error(`Profile '${previous.name}' belongs to a different Machine identity`);
274
+ }
275
+ }
250
276
  export async function installDaemonAutomatically(input, dependencies = defaultDependencies()) {
251
277
  const machineToken = MachineTokenSchema.parse(input.machineToken);
278
+ const identity = setupIdentity(input);
252
279
  const requestedLocalHome = input.requestedLocalHome ?? dependencies.environment.NOWWORK_LOCAL_HOME;
253
280
  const requestedLocalHomeMode = input.requestedLocalHomeMode
254
281
  ?? dependencies.environment.NOWWORK_LOCAL_HOME_MODE
@@ -281,6 +308,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
281
308
  });
282
309
  const previousProfile = await dependencies.captureProfile(target);
283
310
  await dependencies.assertProfileTarget(target, previousProfile);
311
+ assertExistingProfileIdentity(previousProfile, identity);
284
312
  const previousService = await dependencies.captureService(spec, target);
285
313
  if (previousService.installed && !previousService.exact) {
286
314
  throw new Error(`Installed service '${spec.id}' does not match the requested identity`);
@@ -331,6 +359,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
331
359
  name: target.profile,
332
360
  serverUrl: target.serverUrl,
333
361
  machineToken,
362
+ ...(identity === null ? {} : identity),
334
363
  agentsRoot: target.agentsRoot,
335
364
  releaseTag: target.releaseTag,
336
365
  ...(target.registryUrl === undefined ? {} : { registryUrl: target.registryUrl }),
@@ -342,14 +371,33 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
342
371
  const status = await dependencies.verifyService(spec);
343
372
  if (!status.running)
344
373
  throw new Error(`Service '${spec.id}' did not reach running state`);
345
- return {
374
+ const result = {
346
375
  profile: target.profile,
347
376
  version: dependencies.version,
348
377
  serviceId: spec.id,
349
378
  running: true,
379
+ localHome: localHomeForTarget(target, dependencies.platform),
380
+ daemonHome: target.daemonHome,
381
+ agentsRoot: target.agentsRoot,
350
382
  };
383
+ await input.verifyAfterStart?.(result);
384
+ return result;
351
385
  }
352
386
  catch (error) {
387
+ try {
388
+ await input.beforeRollback?.();
389
+ }
390
+ catch (rollbackError) {
391
+ throw new Error(`${error.message}; local recovery blocked: ${rollbackError.message}. `
392
+ + computerSetupRecoveryGuidance({
393
+ serverUrl: target.serverUrl,
394
+ workspaceId: input.workspaceId ?? "unknown",
395
+ machineId: input.machineId ?? "unknown",
396
+ profile: target.profile,
397
+ serviceId: spec.id,
398
+ daemonHome: target.daemonHome,
399
+ }), { cause: error });
400
+ }
353
401
  const failures = [];
354
402
  if (profileSaved) {
355
403
  try {
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { detectDaemonLang, formatDaemonText } from "./i18n.js";
3
+ import { runComputerSetupCommand, setupUsage } from "./computer-setup.js";
4
+ import { redactSetupSecrets } from "./computer-setup-client.js";
5
+ async function main() {
6
+ const result = await runComputerSetupCommand(process.argv.slice(2));
7
+ if (result !== null) {
8
+ process.exitCode = result;
9
+ return;
10
+ }
11
+ const lang = detectDaemonLang();
12
+ process.stderr.write(`${formatDaemonText(lang, "Usage:")}\n${setupUsage((message, values) => formatDaemonText(lang, message, values))}`);
13
+ process.exitCode = 2;
14
+ }
15
+ main().catch((error) => {
16
+ process.stderr.write(`nowwork-computer: ${redactSetupSecrets(error.message)}\n`);
17
+ process.exitCode = 1;
18
+ });
@@ -21,11 +21,12 @@ const ProfileFields = z.object({
21
21
  name: z.string().regex(PROFILE_NAME),
22
22
  serverUrl: ServerUrlSchema,
23
23
  machineToken: MachineTokenSchema,
24
+ workspaceId: z.string().uuid().optional(),
25
+ machineId: z.string().uuid().optional(),
24
26
  agentsRoot: z.string().min(1).optional(),
25
27
  runtimePath: z.string().min(1).max(32768).refine((value) => !value.includes("\0") && !value.includes("\n") && !value.includes("\r"), "runtimePath must be a single line").optional(),
26
28
  releaseTag: z.enum(["latest", "beta"]).optional(),
27
29
  registryUrl: z.string().url().optional(),
28
- codexHomeMode: z.enum(["shared", "private"]).optional(),
29
30
  }).strict();
30
31
  function validateProfileRegistry(value, context) {
31
32
  const fields = value;
@@ -60,7 +61,13 @@ const StoredProtectedProfileSchema = ProfileFields.omit({ machineToken: true }).
60
61
  ciphertext: z.string().min(1).max(16384),
61
62
  }).strict(),
62
63
  }).strict().superRefine(validateProfileRegistry);
63
- const StoredProfileSchema = z.union([StoredPlainProfileSchema, StoredProtectedProfileSchema]);
64
+ // 旧版本可能已保存 shared 字段;读取时丢弃退休设置,其余字段仍严格校验。
65
+ const StoredProfileSchema = z.preprocess((raw) => {
66
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
67
+ return raw;
68
+ const { codexHomeMode: _retiredMode, ...fields } = raw;
69
+ return fields;
70
+ }, z.union([StoredPlainProfileSchema, StoredProtectedProfileSchema]));
64
71
  export const PROFILE_AGENTS_ROOT_CONFLICT_MESSAGE = "Profile '{{profile}}' conflicts with profile '{{conflict}}': both resolve to agents root '{{agentsRoot}}'. Save it with a unique root, for example: {{command}}";
65
72
  export class ProfileAgentsRootConflictError extends Error {
66
73
  profile;
@@ -253,11 +260,12 @@ export async function saveProfile(input, home = daemonHome(), options = {}) {
253
260
  version: 1,
254
261
  name: profile.name,
255
262
  serverUrl: profile.serverUrl,
263
+ ...(profile.workspaceId === undefined ? {} : { workspaceId: profile.workspaceId }),
264
+ ...(profile.machineId === undefined ? {} : { machineId: profile.machineId }),
256
265
  ...(profile.agentsRoot ? { agentsRoot: profile.agentsRoot } : {}),
257
266
  ...(profile.runtimePath ? { runtimePath: profile.runtimePath } : {}),
258
267
  ...(profile.releaseTag === undefined ? {} : { releaseTag: profile.releaseTag }),
259
268
  ...(profile.registryUrl === undefined ? {} : { registryUrl: profile.registryUrl }),
260
- ...(profile.codexHomeMode === undefined ? {} : { codexHomeMode: profile.codexHomeMode }),
261
269
  machineTokenProtected: { scheme: "dpapi-current-user", ciphertext },
262
270
  });
263
271
  await atomicPrivateWrite(profilePath(profile.name, home), `${JSON.stringify(stored, null, 2)}\n`, configured.harden);
@@ -298,6 +306,8 @@ export async function loadProfile(name, home = daemonHome(), options = {}) {
298
306
  version: stored.version,
299
307
  name: stored.name,
300
308
  serverUrl: stored.serverUrl,
309
+ ...(stored.workspaceId === undefined ? {} : { workspaceId: stored.workspaceId }),
310
+ ...(stored.machineId === undefined ? {} : { machineId: stored.machineId }),
301
311
  ...(stored.agentsRoot ? { agentsRoot: stored.agentsRoot } : {}),
302
312
  ...(stored.runtimePath ? { runtimePath: stored.runtimePath } : {}),
303
313
  ...(stored.releaseTag === undefined ? {} : { releaseTag: stored.releaseTag }),
@@ -384,6 +394,8 @@ export function publicProfile(profile) {
384
394
  name: profile.name,
385
395
  serverUrl: profile.serverUrl,
386
396
  machineTokenConfigured: true,
397
+ ...(profile.workspaceId === undefined ? {} : { workspaceId: profile.workspaceId }),
398
+ ...(profile.machineId === undefined ? {} : { machineId: profile.machineId }),
387
399
  ...(profile.agentsRoot ? { agentsRoot: profile.agentsRoot } : {}),
388
400
  };
389
401
  }
@@ -404,5 +416,6 @@ export function applyProfileToEnv(profile, env) {
404
416
  env.CREW_NPM_REGISTRY = profile.registryUrl;
405
417
  else
406
418
  delete env.CREW_NPM_REGISTRY;
407
- env.CREW_CODEX_HOME_MODE = profile.codexHomeMode ?? "shared";
419
+ // 不再向运行时传播退休的模式开关;CODEX_HOME 沿用用户环境。
420
+ delete env.CREW_CODEX_HOME_MODE;
408
421
  }
@@ -0,0 +1,318 @@
1
+ import { z } from "zod";
2
+ import { hostname } from "node:os";
3
+ import { MachineTokenSchema } from "./computer-profile.js";
4
+ import { systemCommandRunner, } from "./computer-service.js";
5
+ const HttpUrlSchema = z.string().url().refine((value) => {
6
+ const url = new URL(value);
7
+ return (url.protocol === "http:" || url.protocol === "https:")
8
+ && url.username.length === 0
9
+ && url.password.length === 0;
10
+ }, "expected an HTTP(S) URL without embedded credentials");
11
+ const ServerOriginSchema = HttpUrlSchema.refine((value) => {
12
+ const url = new URL(value);
13
+ return (url.pathname === "" || url.pathname === "/")
14
+ && url.search.length === 0
15
+ && url.hash.length === 0;
16
+ }, "expected a server origin without a path, query, or hash");
17
+ const SetupStartSchema = z.object({
18
+ deviceCode: z.string().min(16).max(512),
19
+ userCode: z.string().min(4).max(128),
20
+ verificationUri: HttpUrlSchema,
21
+ expiresAt: z.string().datetime({ offset: true }),
22
+ intervalSeconds: z.number().int().positive().max(300),
23
+ }).strict();
24
+ const SetupBootstrapSchema = z.object({
25
+ setupId: z.string().uuid(),
26
+ workspaceId: z.string().uuid(),
27
+ workspaceSlug: z.string().min(1).max(80).optional(),
28
+ machineId: z.string().uuid(),
29
+ serverUrl: ServerOriginSchema,
30
+ environment: z.string().min(1).max(64),
31
+ environmentId: z.string().min(1).max(128).optional(),
32
+ profile: z.string().min(1).max(128),
33
+ minimumConnectionEpoch: z.number().int().nonnegative(),
34
+ daemonVersion: z.string().min(1).max(128).optional(),
35
+ releaseTag: z.enum(["latest", "beta"]).optional(),
36
+ registryUrl: HttpUrlSchema.optional(),
37
+ storageId: z.string().min(1).max(256).optional(),
38
+ expiresAt: z.string().datetime({ offset: true }).optional(),
39
+ machineToken: MachineTokenSchema,
40
+ }).strict();
41
+ const SetupPendingSchema = z.object({
42
+ status: z.enum(["authorization_pending", "pending"]),
43
+ intervalSeconds: z.number().int().positive().max(300).optional(),
44
+ }).strict();
45
+ const SetupSlowDownSchema = z.object({
46
+ status: z.literal("slow_down"),
47
+ intervalSeconds: z.number().int().positive().max(300),
48
+ }).strict();
49
+ const SetupDeniedSchema = z.object({
50
+ status: z.enum(["access_denied", "denied"]),
51
+ intervalSeconds: z.number().int().positive().max(300).optional(),
52
+ }).strict();
53
+ const SetupExpiredSchema = z.object({
54
+ status: z.enum(["expired_token", "expired"]),
55
+ intervalSeconds: z.number().int().positive().max(300).optional(),
56
+ }).strict();
57
+ const SetupReadySchema = z.object({
58
+ status: z.literal("ready"),
59
+ machineId: z.string().uuid(),
60
+ workspaceId: z.string().min(1).max(256),
61
+ environment: z.string().min(1).max(64),
62
+ profile: z.string().min(1).max(128),
63
+ storageId: z.string().min(1).max(256).optional(),
64
+ daemonHome: z.string().min(1).max(32768),
65
+ agentsRoot: z.string().min(1).max(32768),
66
+ }).strict();
67
+ export class ComputerSetupHttpError extends Error {
68
+ status;
69
+ code;
70
+ constructor(message, status, code) {
71
+ super(message);
72
+ this.name = "ComputerSetupHttpError";
73
+ this.status = status;
74
+ this.code = code;
75
+ }
76
+ }
77
+ export function redactSetupSecrets(value, secrets = []) {
78
+ let redacted = value.replace(/\bsk_machine_[A-Za-z0-9_-]+\b/gu, "<redacted>");
79
+ for (const secret of secrets) {
80
+ if (secret.length > 0)
81
+ redacted = redacted.replaceAll(secret, "<redacted>");
82
+ }
83
+ return redacted;
84
+ }
85
+ export function computerSetupRecoveryGuidance(target) {
86
+ const localTarget = [
87
+ ...(target.profile === undefined ? [] : [`profile '${target.profile}'`]),
88
+ ...(target.serviceId === undefined ? [] : [`service '${target.serviceId}'`]),
89
+ ...(target.daemonHome === undefined ? [] : [`daemon home '${target.daemonHome}'`]),
90
+ ];
91
+ return `Local profile/service state was preserved${localTarget.length === 0 ? "" : ` for ${localTarget.join(", ")}`}. `
92
+ + `Do not restore old credentials. Check Computer '${target.machineId}' in Workspace '${target.workspaceId}' `
93
+ + `on ${target.serverUrl}. Resolve the reported error and check connectivity, then rerun nowwork-computer setup to reconcile the installation.`;
94
+ }
95
+ function normalizeServerUrl(value) {
96
+ const parsed = new URL(ServerOriginSchema.parse(value));
97
+ return `${parsed.protocol}//${parsed.host}`;
98
+ }
99
+ function pathForMachine(machineId, suffix) {
100
+ return `/api/machines/${encodeURIComponent(machineId)}/setup/${suffix}`;
101
+ }
102
+ function parseBootstrap(raw) {
103
+ const value = (raw !== null && typeof raw === "object" && "bootstrap" in raw)
104
+ ? raw.bootstrap
105
+ : raw;
106
+ const candidate = value !== null && typeof value === "object"
107
+ ? (() => {
108
+ const record = value;
109
+ const { token: legacyToken, ...rest } = record;
110
+ return {
111
+ ...rest,
112
+ ...(rest.machineToken === undefined && typeof legacyToken === "string"
113
+ ? { machineToken: legacyToken }
114
+ : {}),
115
+ };
116
+ })()
117
+ : value;
118
+ const parsed = SetupBootstrapSchema.parse(candidate);
119
+ return {
120
+ setupId: parsed.setupId,
121
+ workspaceId: parsed.workspaceId,
122
+ ...(parsed.workspaceSlug === undefined ? {} : { workspaceSlug: parsed.workspaceSlug }),
123
+ machineId: parsed.machineId,
124
+ serverUrl: parsed.serverUrl,
125
+ environment: parsed.environment,
126
+ profile: parsed.profile,
127
+ minimumConnectionEpoch: parsed.minimumConnectionEpoch,
128
+ machineToken: parsed.machineToken,
129
+ ...(parsed.environmentId === undefined ? {} : { environmentId: parsed.environmentId }),
130
+ ...(parsed.daemonVersion === undefined ? {} : { daemonVersion: parsed.daemonVersion }),
131
+ ...(parsed.releaseTag === undefined ? {} : { releaseTag: parsed.releaseTag }),
132
+ ...(parsed.registryUrl === undefined ? {} : { registryUrl: parsed.registryUrl }),
133
+ ...(parsed.storageId === undefined ? {} : { storageId: parsed.storageId }),
134
+ ...(parsed.expiresAt === undefined ? {} : { expiresAt: parsed.expiresAt }),
135
+ };
136
+ }
137
+ function parseStart(raw) {
138
+ return SetupStartSchema.parse(raw);
139
+ }
140
+ function parsePoll(raw) {
141
+ if (raw !== null && typeof raw === "object" && "status" in raw
142
+ && raw.status === "authorized") {
143
+ const record = raw;
144
+ if ("bootstrap" in raw) {
145
+ return { status: "authorized", bootstrap: parseBootstrap(raw) };
146
+ }
147
+ if (record.target === undefined || typeof record.token !== "string") {
148
+ throw new Error("Invalid authorized computer setup response");
149
+ }
150
+ return {
151
+ status: "authorized",
152
+ bootstrap: parseBootstrap({
153
+ ...record.target,
154
+ setupId: record.setupId,
155
+ machineToken: record.token,
156
+ }),
157
+ };
158
+ }
159
+ const pending = SetupPendingSchema.safeParse(raw);
160
+ if (pending.success) {
161
+ return {
162
+ status: "authorization_pending",
163
+ intervalSeconds: pending.data.intervalSeconds ?? 5,
164
+ };
165
+ }
166
+ const slowDown = SetupSlowDownSchema.safeParse(raw);
167
+ if (slowDown.success)
168
+ return slowDown.data;
169
+ const denied = SetupDeniedSchema.safeParse(raw);
170
+ if (denied.success) {
171
+ return {
172
+ status: "access_denied",
173
+ intervalSeconds: denied.data.intervalSeconds ?? 5,
174
+ };
175
+ }
176
+ const expired = SetupExpiredSchema.safeParse(raw);
177
+ if (expired.success) {
178
+ return {
179
+ status: "expired_token",
180
+ intervalSeconds: expired.data.intervalSeconds ?? 5,
181
+ };
182
+ }
183
+ throw new Error("Invalid computer setup polling response");
184
+ }
185
+ function parseHello(raw) {
186
+ if (raw !== null && typeof raw === "object" && "helloConfirmed" in raw
187
+ && raw.helloConfirmed === true) {
188
+ const value = raw;
189
+ const parsed = SetupReadySchema.parse({
190
+ status: "ready",
191
+ machineId: value.machineId,
192
+ workspaceId: value.workspaceId,
193
+ environment: value.environment,
194
+ profile: value.profile,
195
+ ...(value.storageId === undefined ? {} : { storageId: value.storageId }),
196
+ ...(value.daemonHome === undefined ? {} : { daemonHome: value.daemonHome }),
197
+ ...(value.agentsRoot === undefined ? {} : { agentsRoot: value.agentsRoot }),
198
+ });
199
+ return {
200
+ status: "ready",
201
+ machineId: parsed.machineId,
202
+ workspaceId: parsed.workspaceId,
203
+ environment: parsed.environment,
204
+ profile: parsed.profile,
205
+ ...(parsed.storageId === undefined ? {} : { storageId: parsed.storageId }),
206
+ daemonHome: parsed.daemonHome,
207
+ agentsRoot: parsed.agentsRoot,
208
+ };
209
+ }
210
+ const ready = SetupReadySchema.safeParse(raw);
211
+ if (ready.success) {
212
+ return {
213
+ status: "ready",
214
+ machineId: ready.data.machineId,
215
+ workspaceId: ready.data.workspaceId,
216
+ environment: ready.data.environment,
217
+ profile: ready.data.profile,
218
+ ...(ready.data.storageId === undefined ? {} : { storageId: ready.data.storageId }),
219
+ daemonHome: ready.data.daemonHome,
220
+ agentsRoot: ready.data.agentsRoot,
221
+ };
222
+ }
223
+ const pending = z.object({
224
+ status: z.literal("pending"),
225
+ intervalSeconds: z.number().int().positive().max(300).optional(),
226
+ }).safeParse(raw);
227
+ if (pending.success)
228
+ return { status: "pending", intervalSeconds: pending.data.intervalSeconds ?? 2 };
229
+ throw new Error("Invalid computer setup hello response");
230
+ }
231
+ export function createComputerSetupClient(options) {
232
+ const fetchImpl = options.fetch ?? globalThis.fetch;
233
+ const serverUrl = normalizeServerUrl(options.serverUrl);
234
+ const requestTimeoutMs = options.requestTimeoutMs ?? 15_000;
235
+ const sleep = options.sleep ?? ((milliseconds) => new Promise((resolve) => {
236
+ setTimeout(resolve, milliseconds);
237
+ }));
238
+ const now = options.now ?? Date.now;
239
+ let activeMachineId;
240
+ const request = async (method, path, body, bearer) => {
241
+ const controller = new AbortController();
242
+ const timeoutId = setTimeout(() => controller.abort(), requestTimeoutMs);
243
+ try {
244
+ const response = await fetchImpl(`${serverUrl}${path}`, {
245
+ method,
246
+ headers: {
247
+ ...(body === undefined ? {} : { "content-type": "application/json" }),
248
+ ...(bearer === undefined ? {} : { authorization: `Bearer ${bearer}` }),
249
+ },
250
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
251
+ signal: controller.signal,
252
+ });
253
+ const payload = await response.json();
254
+ if (!response.ok || payload?.success === false) {
255
+ const message = redactSetupSecrets(payload?.error?.message ?? `computer setup request failed (HTTP ${response.status})`);
256
+ throw new ComputerSetupHttpError(message, response.status, payload?.error?.code ?? `HTTP_${response.status}`);
257
+ }
258
+ return (payload?.success === true ? payload.data : payload);
259
+ }
260
+ catch (error) {
261
+ if (controller.signal.aborted)
262
+ throw new Error("computer setup request timed out");
263
+ if (error instanceof ComputerSetupHttpError)
264
+ throw error;
265
+ throw new Error(redactSetupSecrets(error instanceof Error ? error.message : String(error)));
266
+ }
267
+ finally {
268
+ clearTimeout(timeoutId);
269
+ }
270
+ };
271
+ return {
272
+ start: async (input) => {
273
+ activeMachineId = input.machineId;
274
+ return parseStart(await request("POST", "/api/auth/computer-setup/start", {
275
+ ...(input.workspace.kind === "id"
276
+ ? { workspaceId: input.workspace.value }
277
+ : { workspaceSlug: input.workspace.value }),
278
+ machineId: input.machineId,
279
+ deviceName: (input.deviceName ?? hostname()).trim().slice(0, 120) || "computer",
280
+ serverUrl,
281
+ }));
282
+ },
283
+ poll: async (deviceCode) => {
284
+ if (activeMachineId === undefined)
285
+ throw new Error("computer setup polling has no active machine");
286
+ return parsePoll(await request("POST", "/api/auth/computer-setup/poll", { deviceCode }));
287
+ },
288
+ waitForHello: async (input) => {
289
+ const timeoutMs = input.timeoutMs ?? 120_000;
290
+ const deadline = now() + timeoutMs;
291
+ for (;;) {
292
+ const status = parseHello(await request("GET", `/daemon/computer-setup/status?machine_id=${encodeURIComponent(input.machineId)}&workspace_id=${encodeURIComponent(input.workspaceId)}&profile=${encodeURIComponent(input.profile)}&setup_id=${encodeURIComponent(input.setupId)}&minimum_connection_epoch=${encodeURIComponent(String(input.minimumConnectionEpoch))}`, undefined, input.machineToken));
293
+ if (status.status === "ready")
294
+ return status;
295
+ if (now() >= deadline)
296
+ throw new Error("Timed out waiting for machine hello");
297
+ await sleep(Math.min(status.intervalSeconds * 1_000, Math.max(0, deadline - now())));
298
+ }
299
+ },
300
+ finalize: async (input) => {
301
+ const result = await request("POST", "/daemon/computer-setup/finalize", { setupId: input.setupId }, input.machineToken);
302
+ if (result?.finalized !== true)
303
+ throw new Error("computer setup credential finalization not confirmed");
304
+ },
305
+ rollback: async (input) => {
306
+ const result = await request("POST", "/daemon/computer-setup/rollback", { setupId: input.setupId }, input.machineToken);
307
+ return result.rolledBack === true;
308
+ },
309
+ };
310
+ }
311
+ export async function openSetupBrowser(url, platform = process.platform, runner = systemCommandRunner) {
312
+ const command = platform === "win32" ? "explorer.exe" : platform === "darwin" ? "open" : "xdg-open";
313
+ const args = [url];
314
+ const result = await runner(command, args);
315
+ if (result.exitCode !== 0) {
316
+ throw new Error(`Unable to open the computer setup browser: ${redactSetupSecrets(result.stderr.trim() || `exit ${result.exitCode}`)}`);
317
+ }
318
+ }