@atolis-hq/wake 0.4.21 → 0.4.23

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.
@@ -401,7 +401,7 @@ ENV WAKE_MAIN_JS=/app/dist/src/main.js
401
401
  # Startup ownership repair is best effort because bind-mounted paths or host
402
402
  # permissions may prevent chown. The bounded direct-child pass avoids traversing
403
403
  # accumulated runtime data.
404
- ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown wake:wake /wake/.wake || true; find /wake/.wake -mindepth 1 -maxdepth 1 -type d -exec chown wake:wake {} + || true; if [ -n \\"$WAKE_HOME_INIT_DIRS\\" ]; then printf '%s\\n' \\"$WAKE_HOME_INIT_DIRS\\" | while IFS= read -r directory; do case \\"$directory\\" in \\"$WAKE_HOME_INIT_ROOT\\"/*) mkdir -p \\"$directory\\"; chown wake:wake \\"$directory\\" ;; *) exit 1 ;; esac; done; fi; exec su wake -s /bin/sh -c 'HOME=/home/wake exec node \\"$WAKE_MAIN_JS\\" sandbox-entrypoint --wake-root /wake'"]
404
+ ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake /wake/workspaces; chown wake:wake /wake/.wake /wake/workspaces || true; find /wake/.wake -mindepth 1 -maxdepth 1 -type d -exec chown wake:wake {} + || true; if [ -n \\"$WAKE_HOME_INIT_DIRS\\" ]; then printf '%s\\n' \\"$WAKE_HOME_INIT_DIRS\\" | while IFS= read -r directory; do case \\"$directory\\" in \\"$WAKE_HOME_INIT_ROOT\\"/*) mkdir -p \\"$directory\\"; chown wake:wake \\"$directory\\" ;; *) exit 1 ;; esac; done; fi; exec su wake -s /bin/sh -c 'HOME=/home/wake exec node \\"$WAKE_MAIN_JS\\" sandbox-entrypoint --wake-root /wake'"]
405
405
  `;
406
406
  const packagedDockerfile = `# syntax=docker/dockerfile:1
407
407
  FROM node:24-bookworm-slim
@@ -468,7 +468,7 @@ EXPOSE 4317
468
468
  # Startup ownership repair is best effort because bind-mounted paths or host
469
469
  # permissions may prevent chown. The bounded direct-child pass avoids traversing
470
470
  # accumulated runtime data.
471
- ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown wake:wake /wake/.wake || true; find /wake/.wake -mindepth 1 -maxdepth 1 -type d -exec chown wake:wake {} + || true; if [ -n \\"$WAKE_HOME_INIT_DIRS\\" ]; then printf '%s\\n' \\"$WAKE_HOME_INIT_DIRS\\" | while IFS= read -r directory; do case \\"$directory\\" in \\"$WAKE_HOME_INIT_ROOT\\"/*) mkdir -p \\"$directory\\"; chown wake:wake \\"$directory\\" ;; *) exit 1 ;; esac; done; fi; exec su wake -s /bin/sh -c 'HOME=/home/wake exec wake sandbox-entrypoint'"]
471
+ ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake /wake/workspaces; chown wake:wake /wake/.wake /wake/workspaces || true; find /wake/.wake -mindepth 1 -maxdepth 1 -type d -exec chown wake:wake {} + || true; if [ -n \\"$WAKE_HOME_INIT_DIRS\\" ]; then printf '%s\\n' \\"$WAKE_HOME_INIT_DIRS\\" | while IFS= read -r directory; do case \\"$directory\\" in \\"$WAKE_HOME_INIT_ROOT\\"/*) mkdir -p \\"$directory\\"; chown wake:wake \\"$directory\\" ;; *) exit 1 ;; esac; done; fi; exec su wake -s /bin/sh -c 'HOME=/home/wake exec wake sandbox-entrypoint'"]
472
472
  `;
473
473
  /** Creates an immediately-valid, human-readable target Wake root. */
474
474
  export async function initialiseWakeRoot(wakeRoot) {
@@ -9,7 +9,7 @@ import { BuiltInActivityName, agentActivityDefinition } from '../activities/inde
9
9
  import { IntakeHost, ResidentHost, TickHost, } from '../control-plane/index.js';
10
10
  import { ExecutionCancellationReason, ExecutionFailureCode, RunStatus, isActiveRunStatus, loadPromptTemplate, } from '../execution/index.js';
11
11
  import { ResourceCorrelationRole, resourceId } from '../resources/index.js';
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';
12
+ import { DockerProcessError, createApiDispatcher, createLoggedDockerCli, createPackagedAssetSource, createPairingGrant, createProcessLogSink, createSandboxDockerPort, createSurfaceHttpServer, drainProcessOutput, loadOrCreateCredentials, promoteSandboxImage, 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
15
  import { createNpmUpdatePort } from './npm-update-port.js';
@@ -333,6 +333,18 @@ function sandboxDockerOptions(root, overrides) {
333
333
  function createSandboxDocker(root, overrides) {
334
334
  return createSandboxDockerPort(createDockerCliForRoot(root), sandboxDockerOptions(root, overrides));
335
335
  }
336
+ async function createSandboxDockerForCommand(root, arguments_) {
337
+ if (arguments_[0] !== 'build' || root.config.host.development.mode === 'source')
338
+ return createSandboxDocker(root);
339
+ const buildVersion = await createNpmUpdatePort({
340
+ packageName: root.config.host.selfUpdate.npm.package,
341
+ distTag: root.config.host.selfUpdate.npm.distTag,
342
+ ...(root.config.host.selfUpdate.npm.registry === undefined
343
+ ? {}
344
+ : { registry: root.config.host.selfUpdate.npm.registry }),
345
+ }).latestTag();
346
+ return createSandboxDocker(root, { buildVersion });
347
+ }
336
348
  async function resolveSandboxBuildVersion(root) {
337
349
  const development = root.config.host.development;
338
350
  return development.mode === 'source' && development.repoRoot !== undefined
@@ -422,6 +434,7 @@ async function deploySandboxTag(root, docker, tag, image, overrides) {
422
434
  await verifyResidentStart(docker, root.config.host.sandbox.containerName, [...wakeInvocation, 'start', '--wake-root', '/wake'].join(' '));
423
435
  await port.exec([...wakeInvocation, 'init', healthcheckRoot]);
424
436
  await port.exec([...wakeInvocation, 'tick', '--wake-root', healthcheckRoot, '--no-sandbox']);
437
+ await promoteSandboxImage(docker, image, root.config.host.sandbox.image);
425
438
  }
426
439
  async function rollbackSandboxTag(root, docker, image, development) {
427
440
  const port = createSandboxDockerPort(docker, sandboxDockerOptions(root, { image }));
@@ -430,6 +443,7 @@ async function rollbackSandboxTag(root, docker, image, development) {
430
443
  ? ['node', '/app/dist/src/main.js']
431
444
  : sandboxWakeInvocation(root);
432
445
  await verifyResidentStart(docker, root.config.host.sandbox.containerName, [...wakeInvocation, 'start', '--wake-root', '/wake'].join(' '));
446
+ await promoteSandboxImage(docker, image, root.config.host.sandbox.image);
433
447
  }
434
448
  function createOperationalApplications(root) {
435
449
  return {
@@ -450,7 +464,7 @@ function createOperationalApplications(root) {
450
464
  throw new Error('wake sandbox-setup accepts no arguments');
451
465
  await runSandboxSetup(createSandboxSetupDependencies(root.config.host.sandbox.containerHomeMountPath));
452
466
  },
453
- sandbox: async (arguments_) => runSandbox(arguments_, createSandboxDocker(root)),
467
+ sandbox: async (arguments_) => runSandbox(arguments_, await createSandboxDockerForCommand(root, arguments_)),
454
468
  sandboxEntrypoint: async () => {
455
469
  await runSandboxEntrypoint(createSandboxEntrypointDependencies(root));
456
470
  },
@@ -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.21";
127
+ export const wakeVersion = "v0.4.23";
128
128
  /** The registry-installable version used by packaged sandbox images. */
129
129
  export const wakePackageVersion = packageVersion(defaultRepoRoot) ?? wakeVersion;
@@ -59,6 +59,8 @@ export declare function createSandboxDockerPort(docker: DockerCli, options: Sand
59
59
  setup: () => Promise<DockerInvocationResult>;
60
60
  resume: ({ sessionId, cwd, cli }: SandboxResumeTarget) => Promise<DockerInvocationResult>;
61
61
  };
62
+ /** Promotes a verified versioned sandbox image to the configured default image. */
63
+ export declare function promoteSandboxImage(docker: DockerCli, sourceImage: string, defaultImage: string): Promise<void>;
62
64
  export interface SandboxResumeTarget {
63
65
  readonly sessionId: string;
64
66
  readonly cwd: string;
@@ -1,6 +1,7 @@
1
1
  import { dirname, normalize } from 'node:path/posix';
2
2
  import { DockerProcessError, } from './docker-invocation.js';
3
3
  import { scrubProcessLog } from './process-log.js';
4
+ import { ensureSandboxWorkspaceOwnership } from './sandbox-workspace-ownership.js';
4
5
  export { DockerProcessError, } from './docker-invocation.js';
5
6
  export { verifyResidentStart } from './resident-start.js';
6
7
  const emptyDockerInvocationResult = { stdout: '', stderr: '' };
@@ -82,13 +83,13 @@ export function createSandboxDockerPort(docker, options) {
82
83
  up: async () => {
83
84
  await requireImage(inspect, options.image);
84
85
  const state = await inspect.containerState(options.containerName);
85
- if (state === 'live')
86
- return;
87
86
  if (state === 'halted') {
88
87
  await docker.invoke(['start', options.containerName]);
89
- return;
90
88
  }
91
- await createContainer(docker, options);
89
+ else if (state === null) {
90
+ await createContainer(docker, options);
91
+ }
92
+ await ensureSandboxWorkspaceOwnership(docker, options);
92
93
  },
93
94
  down: () => docker.invoke(['stop', '--time', '60', options.containerName]),
94
95
  update: async () => {
@@ -99,6 +100,7 @@ export function createSandboxDockerPort(docker, options) {
99
100
  if (state !== null)
100
101
  await docker.invoke(['rm', options.containerName]);
101
102
  await createContainer(docker, options);
103
+ await ensureSandboxWorkspaceOwnership(docker, options);
102
104
  },
103
105
  exec: (command) => docker.invoke(command.length === 0
104
106
  ? ['exec', '-u', 'wake', '-it', options.containerName, 'bash']
@@ -138,6 +140,12 @@ export function createSandboxDockerPort(docker, options) {
138
140
  },
139
141
  };
140
142
  }
143
+ /** Promotes a verified versioned sandbox image to the configured default image. */
144
+ export async function promoteSandboxImage(docker, sourceImage, defaultImage) {
145
+ if (sourceImage === defaultImage)
146
+ return;
147
+ await docker.invoke(['tag', sourceImage, defaultImage]);
148
+ }
141
149
  function shellQuote(value) {
142
150
  return `'${value.replaceAll("'", `'\\''`)}'`;
143
151
  }
@@ -0,0 +1,10 @@
1
+ interface RootDockerExecutor {
2
+ invoke(arguments_: readonly string[]): Promise<unknown>;
3
+ }
4
+ interface WorkspaceSandbox {
5
+ readonly containerName: string;
6
+ readonly wakeMountPath?: string;
7
+ }
8
+ /** Ensures the bind-mounted workspace root is usable by the unprivileged sandbox process. */
9
+ export declare function ensureSandboxWorkspaceOwnership(docker: RootDockerExecutor, options: WorkspaceSandbox): Promise<void>;
10
+ export {};
@@ -0,0 +1,14 @@
1
+ /** Ensures the bind-mounted workspace root is usable by the unprivileged sandbox process. */
2
+ export async function ensureSandboxWorkspaceOwnership(docker, options) {
3
+ await docker.invoke([
4
+ 'exec',
5
+ '-u',
6
+ 'root',
7
+ options.containerName,
8
+ 'sh',
9
+ '-c',
10
+ 'mkdir -p "$1" && chown wake:wake "$1"',
11
+ 'wake-workspace-ownership',
12
+ `${options.wakeMountPath ?? '/wake'}/workspaces`,
13
+ ]);
14
+ }
package/docker/Dockerfile CHANGED
@@ -82,4 +82,4 @@ ENV WAKE_MAIN_JS=/app/dist/src/main.js
82
82
  # Startup ownership repair is best effort because bind-mounted paths or host
83
83
  # permissions may prevent chown. The bounded direct-child pass avoids traversing
84
84
  # accumulated runtime data.
85
- ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown wake:wake /wake/.wake || true; find /wake/.wake -mindepth 1 -maxdepth 1 -type d -exec chown wake:wake {} + || true; exec su wake -s /bin/sh -c 'if [ \"$WAKE_START_ENABLED\" = \"true\" ]; then exec node /app/dist/src/main.js start --wake-root /wake --no-sandbox; else exec sleep infinity; fi'"]
85
+ ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake /wake/workspaces; chown wake:wake /wake/.wake /wake/workspaces || true; find /wake/.wake -mindepth 1 -maxdepth 1 -type d -exec chown wake:wake {} + || true; exec su wake -s /bin/sh -c 'if [ \"$WAKE_START_ENABLED\" = \"true\" ]; then exec node /app/dist/src/main.js start --wake-root /wake --no-sandbox; else exec sleep infinity; fi'"]
@@ -65,4 +65,4 @@ EXPOSE 4317
65
65
  # Startup ownership repair is best effort because bind-mounted paths or host
66
66
  # permissions may prevent chown. The bounded direct-child pass avoids traversing
67
67
  # accumulated runtime data.
68
- ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown wake:wake /wake/.wake || true; find /wake/.wake -mindepth 1 -maxdepth 1 -type d -exec chown wake:wake {} + || true; exec su wake -s /bin/sh -c 'exec wake sandbox-entrypoint'"]
68
+ ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake /wake/workspaces; chown wake:wake /wake/.wake /wake/workspaces || true; find /wake/.wake -mindepth 1 -maxdepth 1 -type d -exec chown wake:wake {} + || true; exec su wake -s /bin/sh -c 'exec wake sandbox-entrypoint'"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.4.21",
3
+ "version": "0.4.23",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {