@atolis-hq/wake 0.3.32 → 0.3.33

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.
@@ -9,7 +9,7 @@ import { IntakeHost, ResidentHost, TickHost } from '../control-plane/index.js';
9
9
  import { ExecutionCancellationReason, RunStatus, loadPromptTemplate } from '../execution/index.js';
10
10
  import { EventActorKind, correlationId } from '../kernel/index.js';
11
11
  import { ResourceCorrelationRole, resourceId } from '../resources/index.js';
12
- import { createApiDispatcher, createApiHttpServer, createLoggedDockerCli, createPackagedAssetSource, createProcessLogSink, createSandboxDockerPort, drainProcessOutput, runDoctor, runSandbox, runSandboxEntrypoint, runSandboxSetup, runSelfUpdateLatestLoop, runTargetSmoke, verifyResidentStart, waitForActiveRuns, } from '../surfaces/index.js';
12
+ import { DockerProcessError, createApiDispatcher, createApiHttpServer, createLoggedDockerCli, createPackagedAssetSource, createProcessLogSink, createSandboxDockerPort, drainProcessOutput, runDoctor, runSandbox, runSandboxEntrypoint, runSandboxSetup, runSelfUpdateLatestLoop, runTargetSmoke, verifyResidentStart, waitForActiveRuns, } 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 { runtimeProjectionDefinitions } from './projection-runtime.js';
@@ -244,7 +244,9 @@ function createSandboxRuntimeApplications(root) {
244
244
  return false;
245
245
  }
246
246
  },
247
- exec: (arguments_) => docker.exec([...wakeInvocation, ...arguments_]),
247
+ async exec(arguments_) {
248
+ await docker.exec([...wakeInvocation, ...arguments_]);
249
+ },
248
250
  };
249
251
  }
250
252
  /**
@@ -371,32 +373,35 @@ function spawnDocker(arguments_, onChunk, cwd, options) {
371
373
  child.on('error', reject);
372
374
  child.on('close', (code) => {
373
375
  if (code === 0)
374
- resolve();
376
+ resolve({ stdout: '', stderr: '' });
375
377
  else
376
- reject(new Error('docker ' + arguments_.join(' ') + ' exited with code ' + String(code)));
378
+ reject(new DockerProcessError('docker ' + arguments_.join(' ') + ' exited with code ' + String(code), { stdout: '', stderr: '' }));
377
379
  });
378
380
  return;
379
381
  }
380
382
  let chain = Promise.resolve();
381
- let stderrTail = '';
383
+ let stdout = '';
384
+ let stderr = '';
382
385
  const enqueue = (stream, text) => {
383
- if (stream === 'stderr')
384
- stderrTail = `${stderrTail}${text}`.slice(-4000);
386
+ if (stream === 'stdout')
387
+ stdout += text;
388
+ else
389
+ stderr += text;
385
390
  chain = chain.then(() => onChunk({ stream, text }));
386
391
  };
387
392
  child.stdout.on('data', (buffer) => enqueue('stdout', buffer.toString('utf8')));
388
393
  child.stderr.on('data', (buffer) => enqueue('stderr', buffer.toString('utf8')));
389
394
  child.on('error', (error) => {
390
- void chain.then(() => reject(error));
395
+ void chain.then(() => reject(new DockerProcessError(error.message, { stdout, stderr }, error)));
391
396
  });
392
397
  child.on('close', (code) => {
393
398
  void chain.then(() => {
394
399
  if (code === 0)
395
- resolve();
400
+ resolve({ stdout, stderr });
396
401
  else {
397
- const detail = stderrTail.trim();
398
- reject(new Error(`docker ${arguments_.join(' ')} exited with code ${String(code)}` +
399
- (detail.length > 0 ? `: ${detail}` : '')));
402
+ const detail = stderr.trim();
403
+ reject(new DockerProcessError(`docker ${arguments_.join(' ')} exited with code ${String(code)}` +
404
+ (detail.length > 0 ? `: ${detail}` : ''), { stdout, stderr }));
400
405
  }
401
406
  });
402
407
  });
@@ -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 = "g629cc1e";
111
+ export const wakeVersion = "g0a8e435";
@@ -1,20 +1,50 @@
1
1
  import { dirname, normalize } from 'node:path/posix';
2
+ import { DockerProcessError, } from './docker-invocation.js';
2
3
  import { scrubProcessLog } from './process-log.js';
4
+ export { DockerProcessError, } from './docker-invocation.js';
5
+ export { verifyResidentStart } from './resident-start.js';
6
+ const emptyDockerInvocationResult = { stdout: '', stderr: '' };
3
7
  export function createDockerCli(invoke) {
4
- return { invoke };
8
+ return {
9
+ async invoke(arguments_, options) {
10
+ return (await invoke(arguments_, options)) ?? emptyDockerInvocationResult;
11
+ },
12
+ };
5
13
  }
6
14
  /** Streams Docker output through the target-owned scrubbed log boundary as it arrives. */
7
15
  export function createLoggedDockerCli(process, log) {
8
16
  return {
9
17
  async invoke(arguments_, options) {
10
- await process.execute(arguments_, (chunk) => {
11
- if (chunk.text.length === 0)
18
+ let stdout = '';
19
+ let stderr = '';
20
+ const onChunk = (chunk) => {
21
+ if (chunk.stream === 'stdout')
22
+ stdout += chunk.text;
23
+ else
24
+ stderr += chunk.text;
25
+ if (options?.suppressOutput || chunk.text.length === 0)
12
26
  return;
13
27
  return log.write(scrubProcessLog(chunk.text));
14
- }, options);
28
+ };
29
+ try {
30
+ const result = await process.execute(arguments_, onChunk, options);
31
+ return mergeDockerOutput(result, { stdout, stderr });
32
+ }
33
+ catch (error) {
34
+ const result = mergeDockerOutput(error instanceof DockerProcessError ? error.result : undefined, { stdout, stderr });
35
+ throw new DockerProcessError(error instanceof Error ? error.message : String(error), result, error);
36
+ }
15
37
  },
16
38
  };
17
39
  }
40
+ function mergeDockerOutput(result, captured) {
41
+ if (result === undefined)
42
+ return captured;
43
+ return {
44
+ stdout: result.stdout.length === 0 ? captured.stdout : result.stdout,
45
+ stderr: result.stderr.length === 0 ? captured.stderr : result.stderr,
46
+ };
47
+ }
18
48
  /** Bounded target sandbox lifecycle; domain modules never name Docker. */
19
49
  const dockerRunCommand = String.fromCharCode(114, 117, 110);
20
50
  // The bounded lifecycle is intentionally co-located for command parity.
@@ -108,34 +138,6 @@ export function createSandboxDockerPort(docker, options) {
108
138
  },
109
139
  };
110
140
  }
111
- /**
112
- * Polls until the resident `wake start` process is alive inside the
113
- * container (pid file + live pid + expected cmdline), retrying on any
114
- * failure — `docker.invoke` already throws on a non-zero exit, so each
115
- * attempt is just resolve-or-throw. Used after a self-update container swap
116
- * to confirm the new image actually came up before trusting it.
117
- */
118
- export async function verifyResidentStart(docker, containerName, expectedCmdlineFragment, options) {
119
- const attempts = options?.attempts ?? 15;
120
- const intervalMs = options?.intervalMs ?? 1000;
121
- const check = [
122
- 'pid="$(cat /wake/.wake/logs/start.pid)"',
123
- 'test -n "$pid"',
124
- 'kill -0 "$pid"',
125
- `tr '\\0' ' ' < "/proc/$pid/cmdline" | grep -F ${shellQuote(expectedCmdlineFragment)} >/dev/null`,
126
- ].join(' && ');
127
- for (let attempt = 1; attempt <= attempts; attempt += 1) {
128
- try {
129
- await docker.invoke(['exec', '-i', containerName, 'sh', '-lc', check]);
130
- return;
131
- }
132
- catch (error) {
133
- if (attempt === attempts)
134
- throw error;
135
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
136
- }
137
- }
138
- }
139
141
  function shellQuote(value) {
140
142
  return `'${value.replaceAll("'", `'\\''`)}'`;
141
143
  }
@@ -0,0 +1,9 @@
1
+ /** A failed Docker invocation whose streamed output remains available for a concise caller diagnostic. */
2
+ export class DockerProcessError extends Error {
3
+ result;
4
+ constructor(message, result, cause) {
5
+ super(message, cause === undefined ? undefined : { cause });
6
+ this.result = result;
7
+ this.name = 'DockerProcessError';
8
+ }
9
+ }
@@ -0,0 +1,42 @@
1
+ import { DockerProcessError, } from './docker-invocation.js';
2
+ /** Verifies the replacement sandbox has started its resident Wake process. */
3
+ export async function verifyResidentStart(docker, containerName, expectedCmdlineFragment, options) {
4
+ const attempts = options?.attempts ?? 15;
5
+ const intervalMs = options?.intervalMs ?? 1000;
6
+ const check = [
7
+ 'pid="$(cat /wake/.wake/logs/start.pid)"',
8
+ 'test -n "$pid"',
9
+ 'kill -0 "$pid"',
10
+ `tr '\\0' ' ' < "/proc/$pid/cmdline" | grep -F ${shellQuote(expectedCmdlineFragment)} >/dev/null`,
11
+ ].join(' && ');
12
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
13
+ try {
14
+ await docker.invoke(['exec', '-i', containerName, 'sh', '-lc', check], {
15
+ suppressOutput: attempt < attempts,
16
+ });
17
+ return;
18
+ }
19
+ catch (error) {
20
+ if (attempt === attempts)
21
+ throw residentStartFailure(attempts, error);
22
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
23
+ }
24
+ }
25
+ }
26
+ function residentStartFailure(attempts, error) {
27
+ const detail = error instanceof DockerProcessError
28
+ ? [
29
+ error.message,
30
+ error.result.stdout.length === 0 ? '' : `stdout: ${error.result.stdout.trim()}`,
31
+ error.result.stderr.length === 0 ? '' : `stderr: ${error.result.stderr.trim()}`,
32
+ ]
33
+ .filter((value) => value.length > 0)
34
+ .join('; ')
35
+ : error instanceof Error
36
+ ? error.message
37
+ : String(error);
38
+ return new Error(`Wake resident start did not become healthy after ${String(attempts)} attempts: ${detail}`);
39
+ }
40
+ function shellQuote(value) {
41
+ return `'${value.replaceAll("'", `'\\''`)}'`;
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.3.32",
3
+ "version": "0.3.33",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {