@atolis-hq/wake 0.2.4 → 0.2.6
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.
- package/dist/src/adapters/docker/docker-cli.js +7 -1
- package/dist/src/adapters/github/github-issues-work-source.js +17 -1
- package/dist/src/adapters/github/github-pull-request-activity-source.js +4 -4
- package/dist/src/lib/detached-process-logging.js +46 -0
- package/dist/src/lib/log-rotation.js +42 -0
- package/dist/src/main.js +16 -6
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
|
@@ -7,10 +7,16 @@ function buildStopArgs(containerName, timeoutSeconds) {
|
|
|
7
7
|
containerName,
|
|
8
8
|
];
|
|
9
9
|
}
|
|
10
|
+
const DOCKER_LOG_MAX_SIZE = '10m';
|
|
11
|
+
const DOCKER_LOG_MAX_FILE = '3';
|
|
10
12
|
function buildRunArgs(input) {
|
|
11
13
|
return [
|
|
12
14
|
'run',
|
|
13
15
|
'-d',
|
|
16
|
+
'--log-opt',
|
|
17
|
+
`max-size=${DOCKER_LOG_MAX_SIZE}`,
|
|
18
|
+
'--log-opt',
|
|
19
|
+
`max-file=${DOCKER_LOG_MAX_FILE}`,
|
|
14
20
|
'--name',
|
|
15
21
|
input.containerName,
|
|
16
22
|
'-v',
|
|
@@ -21,7 +27,7 @@ function buildRunArgs(input) {
|
|
|
21
27
|
'-v',
|
|
22
28
|
`${mount.source}:${mount.target}${mount.readOnly === true ? ':ro' : ''}`,
|
|
23
29
|
]),
|
|
24
|
-
// Auto-started by
|
|
30
|
+
// Auto-started by wake sandbox-entrypoint; the UI binds 0.0.0.0 inside the
|
|
25
31
|
// container so this published port can reach it (127.0.0.1 inside the
|
|
26
32
|
// container would not be reachable via docker's port-forwarding NAT).
|
|
27
33
|
...(input.ui?.enabled === true
|
|
@@ -180,6 +180,22 @@ export async function readControlPlaneUiUrl(wakeRoot) {
|
|
|
180
180
|
return undefined;
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
+
export function formatGitHubError(error) {
|
|
184
|
+
if (error instanceof Error) {
|
|
185
|
+
const octokit = error;
|
|
186
|
+
if (octokit.status !== undefined) {
|
|
187
|
+
const headers = octokit.response?.headers ?? {};
|
|
188
|
+
const parts = [`status=${octokit.status}`];
|
|
189
|
+
if (headers['x-ratelimit-remaining'] !== undefined)
|
|
190
|
+
parts.push(`ratelimit-remaining=${headers['x-ratelimit-remaining']}`);
|
|
191
|
+
if (headers['retry-after'] !== undefined)
|
|
192
|
+
parts.push(`retry-after=${headers['retry-after']}`);
|
|
193
|
+
return parts.join(' ');
|
|
194
|
+
}
|
|
195
|
+
return error.message.slice(0, 300);
|
|
196
|
+
}
|
|
197
|
+
return String(error).slice(0, 300);
|
|
198
|
+
}
|
|
183
199
|
export function formatWakeComment(payload, controlPlaneUrl) {
|
|
184
200
|
const body = typeof payload.body === 'string' ? payload.body : '';
|
|
185
201
|
const kind = typeof payload.kind === 'string' ? payload.kind : undefined;
|
|
@@ -314,7 +330,7 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
314
330
|
});
|
|
315
331
|
}
|
|
316
332
|
catch (error) {
|
|
317
|
-
console.error(`[github-work-source] poll failed for ${repoRef}, skipping this tick: ${
|
|
333
|
+
console.error(`[github-work-source] poll failed for ${repoRef}, skipping this tick: ${formatGitHubError(error)}`);
|
|
318
334
|
}
|
|
319
335
|
}
|
|
320
336
|
return events;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildResourceUri } from '../../domain/resource-uri.js';
|
|
2
2
|
import { createUnkeyedEventEnvelope, createEventEnvelope } from '../../lib/event-log.js';
|
|
3
|
-
import { formatWakeComment, readControlPlaneUiUrl } from './github-issues-work-source.js';
|
|
3
|
+
import { formatGitHubError, formatWakeComment, readControlPlaneUiUrl, } from './github-issues-work-source.js';
|
|
4
4
|
const githubPrSource = 'github-pr';
|
|
5
5
|
const wakeCommentMarker = '<!-- wake:agent -->';
|
|
6
6
|
function prResourceUri(repo, number) {
|
|
@@ -122,7 +122,7 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
catch (error) {
|
|
125
|
-
console.error(`[github-pr-activity-source] discovery failed for ${repoRef}, skipping this tick: ${
|
|
125
|
+
console.error(`[github-pr-activity-source] discovery failed for ${repoRef}, skipping this tick: ${formatGitHubError(error)}`);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
return { events, seenPrData, confirmedOpenRepos };
|
|
@@ -247,7 +247,7 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
catch (error) {
|
|
250
|
-
console.error(`[github-pr-activity-source] activity poll failed for ${resourceUri}, skipping this tick: ${
|
|
250
|
+
console.error(`[github-pr-activity-source] activity poll failed for ${resourceUri}, skipping this tick: ${formatGitHubError(error)}`);
|
|
251
251
|
}
|
|
252
252
|
return events;
|
|
253
253
|
}
|
|
@@ -391,7 +391,7 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
391
391
|
return await pollRequiredCheckFailures(ref, watchRef.resourceUri, ingestedAt, seenPrData.get(watchRef.resourceUri));
|
|
392
392
|
}
|
|
393
393
|
catch (error) {
|
|
394
|
-
console.error(`[github-pr-activity-source] check poll failed for ${watchRef.resourceUri}, skipping this tick: ${
|
|
394
|
+
console.error(`[github-pr-activity-source] check poll failed for ${watchRef.resourceUri}, skipping this tick: ${formatGitHubError(error)}`);
|
|
395
395
|
return [];
|
|
396
396
|
}
|
|
397
397
|
}));
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createWriteStream } from 'node:fs';
|
|
2
|
+
import { DEFAULT_LOG_MAX_BYTES, DEFAULT_LOG_ROTATE_CHECK_INTERVAL_MS, rotateLogFileIfNeeded, } from './log-rotation.js';
|
|
3
|
+
export function createDetachedProcessLogSink(logFile, options) {
|
|
4
|
+
const maxBytes = options?.maxBytes ?? DEFAULT_LOG_MAX_BYTES;
|
|
5
|
+
const rotateCheckIntervalMs = options?.rotateCheckIntervalMs ?? DEFAULT_LOG_ROTATE_CHECK_INTERVAL_MS;
|
|
6
|
+
const stdout = options?.stdout ?? process.stdout;
|
|
7
|
+
let closed = false;
|
|
8
|
+
let fileStream = openLogFile(logFile, maxBytes);
|
|
9
|
+
const rotateInterval = setInterval(() => {
|
|
10
|
+
if (closed) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const stream = fileStream;
|
|
14
|
+
stream.end(() => {
|
|
15
|
+
if (closed) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
rotateLogFileIfNeeded(logFile, maxBytes);
|
|
19
|
+
fileStream = openLogFile(logFile, maxBytes);
|
|
20
|
+
});
|
|
21
|
+
}, rotateCheckIntervalMs);
|
|
22
|
+
rotateInterval.unref();
|
|
23
|
+
return {
|
|
24
|
+
write: (chunk) => {
|
|
25
|
+
if (closed) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
fileStream.write(chunk);
|
|
29
|
+
stdout.write(chunk);
|
|
30
|
+
},
|
|
31
|
+
close: () => {
|
|
32
|
+
if (closed) {
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
}
|
|
35
|
+
closed = true;
|
|
36
|
+
clearInterval(rotateInterval);
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
fileStream.end(resolve);
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function openLogFile(logFile, maxBytes) {
|
|
44
|
+
rotateLogFileIfNeeded(logFile, maxBytes);
|
|
45
|
+
return createWriteStream(logFile, { flags: 'a' });
|
|
46
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { renameSync, rmSync, statSync } from 'node:fs';
|
|
2
|
+
export const DEFAULT_LOG_MAX_BYTES = 20 * 1024 * 1024;
|
|
3
|
+
export const DEFAULT_LOG_ROTATE_CHECK_INTERVAL_MS = 5 * 60 * 1000;
|
|
4
|
+
export function resolveLogMaxBytes(env = process.env) {
|
|
5
|
+
const raw = env.WAKE_LOG_MAX_BYTES;
|
|
6
|
+
if (raw === undefined || raw.trim() === '') {
|
|
7
|
+
return DEFAULT_LOG_MAX_BYTES;
|
|
8
|
+
}
|
|
9
|
+
const parsed = Number(raw);
|
|
10
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
11
|
+
return DEFAULT_LOG_MAX_BYTES;
|
|
12
|
+
}
|
|
13
|
+
return Math.floor(parsed);
|
|
14
|
+
}
|
|
15
|
+
export function resolveLogRotateCheckIntervalMs(env = process.env) {
|
|
16
|
+
const raw = env.WAKE_LOG_ROTATE_CHECK_INTERVAL_MS;
|
|
17
|
+
if (raw === undefined || raw.trim() === '') {
|
|
18
|
+
return DEFAULT_LOG_ROTATE_CHECK_INTERVAL_MS;
|
|
19
|
+
}
|
|
20
|
+
const parsed = Number(raw);
|
|
21
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
22
|
+
return DEFAULT_LOG_ROTATE_CHECK_INTERVAL_MS;
|
|
23
|
+
}
|
|
24
|
+
return Math.floor(parsed);
|
|
25
|
+
}
|
|
26
|
+
export function rotateLogFileIfNeeded(logFile, maxBytes = DEFAULT_LOG_MAX_BYTES) {
|
|
27
|
+
try {
|
|
28
|
+
if (statSync(logFile).size < maxBytes) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error.code === 'ENOENT') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
const backupFile = `${logFile}.1`;
|
|
39
|
+
rmSync(backupFile, { force: true });
|
|
40
|
+
renameSync(logFile, backupFile);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
package/dist/src/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import {
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
4
|
import { access, chmod, copyFile, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { dirname, join, resolve } from 'node:path';
|
|
6
6
|
import { createInterface } from 'node:readline/promises';
|
|
@@ -32,7 +32,9 @@ import { createControlPlane } from './core/control-plane.js';
|
|
|
32
32
|
import { createOutboundSinkRouter, createWorkSourceFanIn } from './core/sink-router.js';
|
|
33
33
|
import { createTickRunner } from './core/tick-runner.js';
|
|
34
34
|
import { systemClock } from './lib/clock.js';
|
|
35
|
+
import { createDetachedProcessLogSink } from './lib/detached-process-logging.js';
|
|
35
36
|
import { readJsonFile } from './lib/json-file.js';
|
|
37
|
+
import { resolveLogMaxBytes, resolveLogRotateCheckIntervalMs } from './lib/log-rotation.js';
|
|
36
38
|
import { configuredTicketSource } from './domain/sources.js';
|
|
37
39
|
import { wakeVersion } from './version.js';
|
|
38
40
|
function commandArgsBeforeTerminator(args) {
|
|
@@ -243,16 +245,26 @@ function createSandboxEntrypointDeps() {
|
|
|
243
245
|
return {
|
|
244
246
|
env: process.env,
|
|
245
247
|
spawnDetached: (command, args, options) => {
|
|
246
|
-
const
|
|
248
|
+
const logSink = options?.logFile !== undefined
|
|
249
|
+
? createDetachedProcessLogSink(options.logFile, {
|
|
250
|
+
maxBytes: resolveLogMaxBytes(process.env),
|
|
251
|
+
rotateCheckIntervalMs: resolveLogRotateCheckIntervalMs(process.env),
|
|
252
|
+
})
|
|
253
|
+
: undefined;
|
|
247
254
|
const child = spawn(command, args, {
|
|
248
255
|
cwd: process.cwd(),
|
|
249
256
|
env: process.env,
|
|
250
|
-
stdio: ['ignore',
|
|
257
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
251
258
|
detached: true,
|
|
252
259
|
});
|
|
253
260
|
if (typeof child.pid === 'number') {
|
|
254
261
|
children.set(child.pid, child);
|
|
255
262
|
}
|
|
263
|
+
const forwardOutput = (chunk) => {
|
|
264
|
+
logSink?.write(chunk);
|
|
265
|
+
};
|
|
266
|
+
child.stdout?.on('data', forwardOutput);
|
|
267
|
+
child.stderr?.on('data', forwardOutput);
|
|
256
268
|
// Registered here (at spawn time) so it runs before any exit listener
|
|
257
269
|
// waitForExit attaches later — though it wouldn't matter either way:
|
|
258
270
|
// waitForExit captures the ChildProcess reference synchronously from
|
|
@@ -264,9 +276,7 @@ function createSandboxEntrypointDeps() {
|
|
|
264
276
|
if (typeof child.pid === 'number') {
|
|
265
277
|
children.delete(child.pid);
|
|
266
278
|
}
|
|
267
|
-
|
|
268
|
-
closeSync(logFd);
|
|
269
|
-
}
|
|
279
|
+
logSink?.close().catch(() => { });
|
|
270
280
|
});
|
|
271
281
|
return { pid: child.pid ?? -1 };
|
|
272
282
|
},
|
package/dist/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const wakeVersion = "0.2.
|
|
1
|
+
export const wakeVersion = "0.2.6+g29d62c6";
|