@atolis-hq/wake 0.3.92 → 0.3.94

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.
@@ -366,7 +366,13 @@ ENV WAKE_MAIN_JS=/app/dist/src/main.js
366
366
  # for \`docker exec\` (i.e. \`wake sandbox setup\`/\`wake sandbox exec\`) even
367
367
  # when WAKE_START_ENABLED's supervised \`wake start\` child keeps crashing —
368
368
  # e.g. on first boot, before sandbox auth has been configured.
369
- ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown -R wake:wake /wake/.wake; 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'"]
369
+ #
370
+ # /wake/.wake is bind-mounted from the host and self-update actively creates
371
+ # and deletes its own lock files there while this container is starting, so
372
+ # a recursive chown can race a file disappearing mid-walk. That single ENOENT
373
+ # must not be fatal under set -eu — it would otherwise crash the container
374
+ # before wake start ever runs.
375
+ ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown -R wake:wake /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'"]
370
376
  `;
371
377
  const packagedDockerfile = `# syntax=docker/dockerfile:1
372
378
  FROM node:24-bookworm-slim
@@ -418,7 +424,13 @@ EXPOSE 4317
418
424
  # sandbox-entrypoint-command.ts) that the CLI should be invoked via the bare
419
425
  # \`wake\` binary that \`npm install -g\` puts on PATH, rather than a hardcoded
420
426
  # npm global lib path that varies by npm/OS setup.
421
- ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown -R wake:wake /wake/.wake; 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'"]
427
+ #
428
+ # /wake/.wake is bind-mounted from the host and self-update actively creates
429
+ # and deletes its own lock files there while this container is starting, so
430
+ # a recursive chown can race a file disappearing mid-walk. That single ENOENT
431
+ # must not be fatal under set -eu — it would otherwise crash the container
432
+ # before wake start ever runs.
433
+ ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown -R wake:wake /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'"]
422
434
  `;
423
435
  /** Creates an immediately-valid, human-readable target Wake root. */
424
436
  export async function initialiseWakeRoot(wakeRoot) {
@@ -1,4 +1,6 @@
1
1
  import { execFile as nodeExecFile } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
2
4
  import { promisify } from 'node:util';
3
5
  const execFile = promisify(nodeExecFile);
4
6
  export function createSourceUpdatePort(input) {
@@ -18,14 +20,17 @@ export function createSourceUpdatePort(input) {
18
20
  },
19
21
  async checkout(tag) {
20
22
  await execute('git', ['checkout', tag], input.repoRoot);
23
+ // Dependencies are part of what a tag means; without refreshing them,
24
+ // node_modules can silently drift from the checked-out
25
+ // package-lock.json and the health check below only ever proves the
26
+ // previously-installed dependencies still compile, not this tag's own.
27
+ await execute('npm', npmInstallArgs(input.repoRoot), input.repoRoot);
21
28
  },
22
29
  async healthy() {
23
30
  try {
24
31
  // Verify the checked-out code can be built (TypeScript compiles).
25
32
  // This is the minimal meaningful health check: the code must at least compile.
26
- const buildCommand = 'npm';
27
- const buildArgs = ['exec', 'tsc', '--'];
28
- await execute(buildCommand, [...buildArgs], input.repoRoot);
33
+ await execute('npm', ['exec', 'tsc', '--'], input.repoRoot);
29
34
  return true;
30
35
  }
31
36
  catch {
@@ -34,6 +39,9 @@ export function createSourceUpdatePort(input) {
34
39
  },
35
40
  };
36
41
  }
42
+ function npmInstallArgs(repoRoot) {
43
+ return existsSync(join(repoRoot, 'package-lock.json')) ? ['ci', '--include=dev'] : ['install'];
44
+ }
37
45
  async function candidateTags(execute, repoRoot) {
38
46
  await execute('git', ['fetch', '--tags'], repoRoot);
39
47
  const output = await execute('git', ['tag', '--list', 'v*', '--sort=-v:refname'], repoRoot);
@@ -43,6 +51,23 @@ async function candidateTags(execute, repoRoot) {
43
51
  .filter((line) => line.length > 0);
44
52
  }
45
53
  async function runProcess(command, args, cwd) {
54
+ // On Windows, npm resolves to an `npm.cmd` shim: execFile can't spawn it
55
+ // directly (ENOENT, since it isn't a real executable) and refuses to spawn
56
+ // it with a separate argument array either (EINVAL, Node's own hardening
57
+ // after CVE-2024-27980). Routing the whole command through a shell as one
58
+ // string is the platform-sanctioned way to invoke it without the argument
59
+ // array's deprecated, unescaped shell concatenation (DEP0190). Every
60
+ // argument passed to npm here is a fixed literal, never external input, so
61
+ // that lack of escaping is not a code-injection concern. git is a real
62
+ // executable and needs none of this.
63
+ if (process.platform === 'win32' && command === 'npm') {
64
+ const result = await execFile([command, ...args].join(' '), [], {
65
+ cwd,
66
+ encoding: 'utf8',
67
+ shell: true,
68
+ });
69
+ return result.stdout;
70
+ }
46
71
  const result = await execFile(command, args, { cwd, encoding: 'utf8' });
47
72
  return result.stdout;
48
73
  }
@@ -295,7 +295,12 @@ async function deploySandboxTag(root, docker, tag, image) {
295
295
  await port.update();
296
296
  const wakeInvocation = sandboxWakeInvocation(root);
297
297
  await verifyResidentStart(docker, root.config.host.sandbox.containerName, [...wakeInvocation, 'start', '--wake-root', '/wake'].join(' '));
298
- await port.exec(['tick', '--wake-root', `/tmp/wake-self-update-healthcheck-${tag}`]);
298
+ await port.exec([
299
+ ...wakeInvocation,
300
+ 'tick',
301
+ '--wake-root',
302
+ `/tmp/wake-self-update-healthcheck-${tag}`,
303
+ ]);
299
304
  }
300
305
  async function rollbackSandboxTag(root, docker, image) {
301
306
  const port = createSandboxDockerPort(docker, sandboxDockerOptions(root, { image }));
@@ -108,4 +108,4 @@ export function resolveWakeVersion(options = {}) {
108
108
  return `g${headHash.slice(0, 7)}`;
109
109
  return '0.1.0-dev';
110
110
  }
111
- export const wakeVersion = "ga760b3e";
111
+ export const wakeVersion = "gf1098b5";
@@ -1,7 +1,12 @@
1
1
  import { DockerProcessError, } from './docker-invocation.js';
2
2
  /** Verifies the replacement sandbox has started its resident Wake process. */
3
3
  export async function verifyResidentStart(docker, containerName, expectedCmdlineFragment, options) {
4
- const attempts = options?.attempts ?? 15;
4
+ // A mature Wake home's bind-mounted .wake can hold weeks of accumulated
5
+ // event, projection, and transcript files; the entrypoint's recursive
6
+ // chown over all of it — especially through Docker Desktop's virtualized
7
+ // filesystem on Windows — can alone take longer than a short, test-sized
8
+ // budget before the resident process ever gets to write its PID file.
9
+ const attempts = options?.attempts ?? 90;
5
10
  const intervalMs = options?.intervalMs ?? 1000;
6
11
  const check = [
7
12
  'pid="$(cat /wake/.wake/logs/start.pid)"',
package/docker/Dockerfile CHANGED
@@ -64,4 +64,9 @@ EXPOSE 4317
64
64
  # that's fine since it's the very first thing that runs.
65
65
  ENV WAKE_MAIN_JS=/app/dist/src/main.js
66
66
 
67
- ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown -R wake:wake /wake/.wake; 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'"]
67
+ # /wake/.wake is bind-mounted from the host and self-update actively creates
68
+ # and deletes its own lock files there while this container is starting, so
69
+ # a recursive chown can race a file disappearing mid-walk. That single ENOENT
70
+ # must not be fatal under set -eu — it would otherwise crash the container
71
+ # before wake start ever runs.
72
+ ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown -R wake:wake /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'"]
@@ -50,4 +50,10 @@ EXPOSE 4317
50
50
  # sandbox-entrypoint-command.ts) that the CLI should be invoked via the bare
51
51
  # `wake` binary that `npm install -g` puts on PATH, rather than a hardcoded
52
52
  # npm global lib path that varies by npm/OS setup.
53
- ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown -R wake:wake /wake/.wake; exec su wake -s /bin/sh -c 'exec wake sandbox-entrypoint'"]
53
+ #
54
+ # /wake/.wake is bind-mounted from the host and self-update actively creates
55
+ # and deletes its own lock files there while this container is starting, so
56
+ # a recursive chown can race a file disappearing mid-walk. That single ENOENT
57
+ # must not be fatal under set -eu — it would otherwise crash the container
58
+ # before wake start ever runs.
59
+ ENTRYPOINT ["sh", "-c", "set -eu; mkdir -p /wake/.wake; chown -R wake:wake /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.3.92",
3
+ "version": "0.3.94",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {