@atolis-hq/wake 0.4.16 → 0.4.18

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.
@@ -140,6 +140,11 @@ declare const hostConfigSchema: z.ZodDefault<z.ZodObject<{
140
140
  selfUpdate: z.ZodDefault<z.ZodObject<{
141
141
  drainTimeoutMs: z.ZodDefault<z.ZodNumber>;
142
142
  cancellationTimeoutMs: z.ZodDefault<z.ZodNumber>;
143
+ npm: z.ZodDefault<z.ZodObject<{
144
+ package: z.ZodDefault<z.ZodString>;
145
+ distTag: z.ZodDefault<z.ZodString>;
146
+ registry: z.ZodOptional<z.ZodString>;
147
+ }, z.core.$strict>>;
143
148
  }, z.core.$strict>>;
144
149
  }, z.core.$strict>>;
145
150
  export declare const rootConfigSchema: z.ZodObject<{
@@ -436,6 +441,11 @@ export declare const rootConfigSchema: z.ZodObject<{
436
441
  selfUpdate: z.ZodDefault<z.ZodObject<{
437
442
  drainTimeoutMs: z.ZodDefault<z.ZodNumber>;
438
443
  cancellationTimeoutMs: z.ZodDefault<z.ZodNumber>;
444
+ npm: z.ZodDefault<z.ZodObject<{
445
+ package: z.ZodDefault<z.ZodString>;
446
+ distTag: z.ZodDefault<z.ZodString>;
447
+ registry: z.ZodOptional<z.ZodString>;
448
+ }, z.core.$strict>>;
439
449
  }, z.core.$strict>>;
440
450
  }, z.core.$strict>>;
441
451
  }, z.core.$strict>;
@@ -92,9 +92,21 @@ const hostConfigSchema = z
92
92
  .object({
93
93
  drainTimeoutMs: z.number().int().positive().default(30_000),
94
94
  cancellationTimeoutMs: z.number().int().positive().default(30_000),
95
+ npm: z
96
+ .object({
97
+ package: z.string().trim().min(1).default('@atolis-hq/wake'),
98
+ distTag: z.string().trim().min(1).default('latest'),
99
+ registry: z.string().url().optional(),
100
+ })
101
+ .strict()
102
+ .default({ package: '@atolis-hq/wake', distTag: 'latest' }),
95
103
  })
96
104
  .strict()
97
- .default({ drainTimeoutMs: 30_000, cancellationTimeoutMs: 30_000 }),
105
+ .default({
106
+ drainTimeoutMs: 30_000,
107
+ cancellationTimeoutMs: 30_000,
108
+ npm: { package: '@atolis-hq/wake', distTag: 'latest' },
109
+ }),
98
110
  })
99
111
  .strict()
100
112
  .default({
@@ -108,7 +120,11 @@ const hostConfigSchema = z
108
120
  extraMounts: [],
109
121
  },
110
122
  development: {},
111
- selfUpdate: { drainTimeoutMs: 30_000, cancellationTimeoutMs: 30_000 },
123
+ selfUpdate: {
124
+ drainTimeoutMs: 30_000,
125
+ cancellationTimeoutMs: 30_000,
126
+ npm: { package: '@atolis-hq/wake', distTag: 'latest' },
127
+ },
112
128
  })
113
129
  .superRefine((value, context) => {
114
130
  if (value.development.mode === 'source' && value.development.repoRoot === undefined)
@@ -24,4 +24,5 @@ export * from './update-ledger.js';
24
24
  export * from './update-maintenance-lease.js';
25
25
  export * from './self-update-application.js';
26
26
  export * from './source-update-port.js';
27
+ export * from './npm-update-port.js';
27
28
  export * from './version.js';
@@ -26,4 +26,5 @@ export * from './update-ledger.js';
26
26
  export * from './update-maintenance-lease.js';
27
27
  export * from './self-update-application.js';
28
28
  export * from './source-update-port.js';
29
+ export * from './npm-update-port.js';
29
30
  export * from './version.js';
@@ -0,0 +1,11 @@
1
+ import type { SourceUpdatePort } from './self-update-application.js';
2
+ export interface NpmUpdateProcess {
3
+ execute(command: string, args: readonly string[]): Promise<string>;
4
+ }
5
+ /** Resolves immutable published package versions. Installation is performed by the rollout. */
6
+ export declare function createNpmUpdatePort(input: {
7
+ readonly packageName: string;
8
+ readonly distTag?: string;
9
+ readonly registry?: string;
10
+ readonly execute?: NpmUpdateProcess['execute'];
11
+ }): SourceUpdatePort;
@@ -0,0 +1,45 @@
1
+ import { execFile as nodeExecFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFile = promisify(nodeExecFile);
4
+ /** Resolves immutable published package versions. Installation is performed by the rollout. */
5
+ export function createNpmUpdatePort(input) {
6
+ const execute = input.execute ?? runProcess;
7
+ const distTag = input.distTag ?? 'latest';
8
+ const registry = input.registry === undefined ? [] : [`--registry=${input.registry}`];
9
+ return {
10
+ isClean: async () => true,
11
+ async latestTag() {
12
+ const resolved = await resolveVersion();
13
+ if (resolved === undefined)
14
+ throw new Error(`No npm version is available for ${input.packageName}`);
15
+ return resolved;
16
+ },
17
+ async candidateTags() {
18
+ const resolved = await resolveVersion();
19
+ return resolved === undefined ? [] : [resolved];
20
+ },
21
+ // The Docker rollout installs this exact version. Keeping this side-effect-free means
22
+ // the updater never overwrites the CLI process which is coordinating the update.
23
+ checkout: async () => { },
24
+ healthy: async () => true,
25
+ };
26
+ async function resolveVersion() {
27
+ const output = await execute('npm', [
28
+ 'view',
29
+ `${input.packageName}@${distTag}`,
30
+ 'version',
31
+ '--json',
32
+ ...registry,
33
+ ]);
34
+ const parsed = JSON.parse(output);
35
+ const value = Array.isArray(parsed) ? parsed.at(0) : parsed;
36
+ if (typeof value !== 'string' ||
37
+ !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u.test(value))
38
+ throw new Error(`npm returned an invalid version for ${input.packageName}@${distTag}`);
39
+ return value;
40
+ }
41
+ }
42
+ async function runProcess(command, args) {
43
+ const result = await execFile(command, args, { encoding: 'utf8' });
44
+ return result.stdout;
45
+ }
@@ -10,9 +10,8 @@ export function createSelfUpdateApplication(input) {
10
10
  async updateLatest(force = false) {
11
11
  const candidates = await input.source.candidateTags();
12
12
  const [latestCandidate] = candidates;
13
- if (latestCandidate === undefined) {
14
- throw new Error('No source version tags available');
15
- }
13
+ if (latestCandidate === undefined)
14
+ throw new Error('No update versions are available');
16
15
  for (const tag of candidates) {
17
16
  if (!force && (await input.ledger.isBad(tag))) {
18
17
  continue;
@@ -12,6 +12,7 @@ import { ResourceCorrelationRole, resourceId } from '../resources/index.js';
12
12
  import { DockerProcessError, createApiDispatcher, createLoggedDockerCli, createPackagedAssetSource, createPairingGrant, createProcessLogSink, createSandboxDockerPort, createSurfaceHttpServer, drainProcessOutput, loadOrCreateCredentials, redeemPairingGrant, replaceAccessKey, runDoctor, runSandbox, runSandboxEntrypoint, runSandboxSetup, runSelfUpdateLatestLoop, runTargetSmoke, verifyResidentStart, waitForActiveRuns, waitForever, } from '../surfaces/index.js';
13
13
  import { WorkStreamKind, workItemId } from '../work/index.js';
14
14
  import { loadConfig } from './config/load-config.js';
15
+ import { createNpmUpdatePort } from './npm-update-port.js';
15
16
  import { runtimeProjectionDefinitions } from './projection-runtime.js';
16
17
  import { createRunnerRegistry } from './runner-registry.js';
17
18
  import { createOneShotRunnerAdvance, createResidentRunnerAdvance, withFinalProjectionCatchUp, } from './runner-tick-adapter.js';
@@ -315,7 +316,7 @@ function sandboxDockerOptions(root, overrides) {
315
316
  wakeRoot: root.paths.wakeRoot,
316
317
  containerHomeRoot: root.paths.containerHomeRoot,
317
318
  image: overrides?.image ?? root.config.host.sandbox.image,
318
- development: root.config.host.development,
319
+ development: overrides?.development ?? root.config.host.development,
319
320
  containerName: root.config.host.sandbox.containerName,
320
321
  ...(root.config.surfaces.api.enabled ? { publishedPort: root.config.surfaces.api.port } : {}),
321
322
  wakeMountPath: root.config.host.sandbox.wakeMountPath,
@@ -324,7 +325,9 @@ function sandboxDockerOptions(root, overrides) {
324
325
  startEnabled: root.config.host.sandbox.start.enabled,
325
326
  ...(process.env.WAKE_MEMORY_PROFILE === 'runner' ? { memoryProfile: 'runner' } : {}),
326
327
  inspect: createDockerInspection(root.paths.wakeRoot),
327
- resolveBuildVersion: () => resolveSandboxBuildVersion(root),
328
+ resolveBuildVersion: async () => overrides?.buildVersion === undefined
329
+ ? resolveSandboxBuildVersion(root)
330
+ : overrides.buildVersion,
328
331
  };
329
332
  }
330
333
  function createSandboxDocker(root, overrides) {
@@ -408,23 +411,24 @@ function createSandboxRuntimeApplications(root) {
408
411
  * resident `wake start` process actually came back up. Reused for both the
409
412
  * forward deploy and a same-shaped rollback to a previously-built tag.
410
413
  */
411
- async function deploySandboxTag(root, docker, tag, image) {
412
- const port = createSandboxDockerPort(docker, sandboxDockerOptions(root, { image }));
414
+ async function deploySandboxTag(root, docker, tag, image, overrides) {
415
+ const port = createSandboxDockerPort(docker, sandboxDockerOptions(root, { image, ...overrides }));
416
+ const wakeInvocation = overrides?.development?.mode === 'source'
417
+ ? ['node', '/app/dist/src/main.js']
418
+ : sandboxWakeInvocation(root);
419
+ const healthcheckRoot = `/tmp/wake-self-update-healthcheck-${tag}`;
413
420
  await port.build();
414
421
  await port.update();
415
- const wakeInvocation = sandboxWakeInvocation(root);
416
422
  await verifyResidentStart(docker, root.config.host.sandbox.containerName, [...wakeInvocation, 'start', '--wake-root', '/wake'].join(' '));
417
- await port.exec([
418
- ...wakeInvocation,
419
- 'tick',
420
- '--wake-root',
421
- `/tmp/wake-self-update-healthcheck-${tag}`,
422
- ]);
423
- }
424
- async function rollbackSandboxTag(root, docker, image) {
423
+ await port.exec([...wakeInvocation, 'init', healthcheckRoot]);
424
+ await port.exec([...wakeInvocation, 'tick', '--wake-root', healthcheckRoot, '--no-sandbox']);
425
+ }
426
+ async function rollbackSandboxTag(root, docker, image, development) {
425
427
  const port = createSandboxDockerPort(docker, sandboxDockerOptions(root, { image }));
426
428
  await port.update();
427
- const wakeInvocation = sandboxWakeInvocation(root);
429
+ const wakeInvocation = development?.mode === 'source'
430
+ ? ['node', '/app/dist/src/main.js']
431
+ : sandboxWakeInvocation(root);
428
432
  await verifyResidentStart(docker, root.config.host.sandbox.containerName, [...wakeInvocation, 'start', '--wake-root', '/wake'].join(' '));
429
433
  }
430
434
  function createOperationalApplications(root) {
@@ -450,31 +454,56 @@ function createOperationalApplications(root) {
450
454
  sandboxEntrypoint: async () => {
451
455
  await runSandboxEntrypoint(createSandboxEntrypointDependencies(root));
452
456
  },
457
+ // eslint-disable-next-line complexity -- update source and loop options are validated at this CLI boundary.
453
458
  selfUpdate: async (arguments_) => {
454
- if (root.config.host.development.mode !== 'source')
455
- throw new Error('wake self-update requires host.development.mode: source');
456
- const repoRoot = root.config.host.development.repoRoot;
457
- if (repoRoot === undefined)
459
+ const sourceRoot = optionalOperationalOption(arguments_, '--source');
460
+ const configuredSource = root.config.host.development.mode === 'source';
461
+ const repoRoot = sourceRoot ?? root.config.host.development.repoRoot;
462
+ if (sourceRoot !== undefined && root.config.host.development.mode === 'source')
463
+ throw new Error('wake self-update --source cannot override source-mode configuration');
464
+ if (configuredSource && repoRoot === undefined)
458
465
  throw new Error('wake self-update requires host.development.repoRoot');
459
466
  const tag = optionalOperationalOption(arguments_, '--tag');
467
+ const version = optionalOperationalOption(arguments_, '--version');
468
+ if (tag !== undefined && version !== undefined)
469
+ throw new Error('wake self-update accepts only one of --tag or --version');
470
+ if (version !== undefined && (configuredSource || sourceRoot !== undefined))
471
+ throw new Error('wake self-update --version is only valid for npm updates');
472
+ const useSource = configuredSource || sourceRoot !== undefined;
473
+ const updatePort = useSource
474
+ ? createSourceUpdatePort({ repoRoot: repoRoot })
475
+ : createNpmUpdatePort({
476
+ packageName: root.config.host.selfUpdate.npm.package,
477
+ distTag: root.config.host.selfUpdate.npm.distTag,
478
+ ...(root.config.host.selfUpdate.npm.registry === undefined
479
+ ? {}
480
+ : { registry: root.config.host.selfUpdate.npm.registry }),
481
+ });
482
+ const updateTag = tag ?? version;
483
+ const updateDevelopment = useSource
484
+ ? { mode: 'source', repoRoot: repoRoot }
485
+ : { mode: 'packaged' };
460
486
  const dockerCli = createDockerCliForRoot(root);
461
487
  const imageRepository = root.config.host.sandbox.imageRepository;
462
488
  const failureLog = createSelfUpdateFailureLog(root.paths.wakeRoot);
463
489
  const application = createSelfUpdateApplication({
464
490
  ledger: createUpdateLedger(root.paths.wakeRoot),
465
- source: createSourceUpdatePort({ repoRoot }),
491
+ source: updatePort,
466
492
  rollout: {
467
493
  async deploy(deployTag) {
468
494
  await waitForActiveRuns({
469
495
  activeRunIds: () => activeExecutionRunIds(root),
470
496
  sleep: async (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
471
497
  });
472
- await deploySandboxTag(root, dockerCli, deployTag, `${imageRepository}:${deployTag}`);
498
+ await deploySandboxTag(root, dockerCli, deployTag, `${imageRepository}:${deployTag}`, {
499
+ development: updateDevelopment,
500
+ buildVersion: deployTag,
501
+ });
473
502
  // A later deploy succeeding after a prior rollback clears the stale warning.
474
503
  await failureLog.clear();
475
504
  },
476
505
  async rollback(rollbackTag) {
477
- await rollbackSandboxTag(root, dockerCli, `${imageRepository}:${rollbackTag}`);
506
+ await rollbackSandboxTag(root, dockerCli, `${imageRepository}:${rollbackTag}`, updateDevelopment);
478
507
  },
479
508
  recordFailure: (failedTag, error) => failureLog.record(failedTag, error),
480
509
  },
@@ -485,12 +514,12 @@ function createOperationalApplications(root) {
485
514
  const force = arguments_.includes('--force');
486
515
  if (arguments_.includes('--loop')) {
487
516
  const intervalMs = selfUpdateLoopInterval(arguments_);
488
- if (tag !== undefined)
489
- return runSelfUpdateLatestLoop(async () => application.update(tag, force), () => delay(intervalMs));
517
+ if (updateTag !== undefined)
518
+ return runSelfUpdateLatestLoop(async () => application.update(updateTag, force), () => delay(intervalMs));
490
519
  return runSelfUpdateLatestLoop(async () => application.updateLatest(force), () => delay(intervalMs));
491
520
  }
492
- if (tag !== undefined)
493
- return { tag, updated: await application.update(tag, force) };
521
+ if (updateTag !== undefined)
522
+ return { tag: updateTag, updated: await application.update(updateTag, force) };
494
523
  return application.updateLatest(force);
495
524
  },
496
525
  smoke: async () => runTargetSmoke(createRunnerRegistry(root.config.execution, root.fakeScenarios), root.config.execution.defaultRunnerPool, new AbortController().signal),
@@ -124,6 +124,6 @@ export function resolveWakeVersion(options = {}) {
124
124
  return `g${headHash.slice(0, 7)}`;
125
125
  return '0.1.0-dev';
126
126
  }
127
- export const wakeVersion = "v0.4.16";
127
+ export const wakeVersion = "v0.4.18";
128
128
  /** The registry-installable version used by packaged sandbox images. */
129
129
  export const wakePackageVersion = packageVersion(defaultRepoRoot) ?? wakeVersion;
@@ -24,7 +24,7 @@ export const usage = [
24
24
  ' wake run resolve <run-id> --failed --reason <message>',
25
25
  ' wake api Run the target API surface',
26
26
  ' wake sandbox-entrypoint Run the sandbox resident entrypoint',
27
- ' wake self-update Safely update a source installation',
27
+ ' wake self-update Safely update Wake from npm or a source checkout',
28
28
  '',
29
29
  'Getting started:',
30
30
  ' 1. wake init ./wake-home',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.4.16",
3
+ "version": "0.4.18",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {