@alook/cli 0.0.16 → 0.0.17
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/index.js +126 -1
- package/dist/session-runner.js +68 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15853,6 +15853,7 @@ var machineToken = sqliteTable("machine_token", {
|
|
|
15853
15853
|
}, (t) => [index("idx_machine_token").on(t.token)]);
|
|
15854
15854
|
// ../shared/src/db/queries/task.ts
|
|
15855
15855
|
var DEFAULT_STALE_SECONDS = Number(process.env.ALOOK_STALE_DISPATCH_TIMEOUT_S) || 20;
|
|
15856
|
+
var DEFAULT_STALE_RUNNING_SECONDS = Number(process.env.ALOOK_STALE_RUNNING_TIMEOUT_S) || 3600;
|
|
15856
15857
|
// ../shared/src/utils/email.ts
|
|
15857
15858
|
var DOMAIN = `@${process.env.ALOOK_DOMAIN || "alook.ai"}`;
|
|
15858
15859
|
var RESERVED_HANDLES = new Set([
|
|
@@ -16084,6 +16085,7 @@ function loadDaemonConfig(profile) {
|
|
|
16084
16085
|
opencodeModel: process.env.ALOOK_OPENCODE_MODEL || "",
|
|
16085
16086
|
pollInterval: parseDuration(process.env.ALOOK_DAEMON_POLL_INTERVAL || "3s"),
|
|
16086
16087
|
agentTimeout: parseDuration(process.env.ALOOK_AGENT_TIMEOUT || "12h"),
|
|
16088
|
+
messageInactivityTimeout: parseDuration(process.env.ALOOK_MESSAGE_INACTIVITY_TIMEOUT || "5m"),
|
|
16087
16089
|
maxConcurrentTasks: parseInt(process.env.ALOOK_DAEMON_MAX_CONCURRENT_TASKS || "20"),
|
|
16088
16090
|
daemonId,
|
|
16089
16091
|
deviceName: process.env.ALOOK_DAEMON_DEVICE_NAME || h,
|
|
@@ -16551,6 +16553,7 @@ function releaseSteeringLock(baseDir, contextKey) {
|
|
|
16551
16553
|
|
|
16552
16554
|
// daemon/daemon.ts
|
|
16553
16555
|
import { existsSync, mkdirSync as mkdirSync5, openSync, closeSync, readdirSync as readdirSync2, statSync as statSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
16556
|
+
import { readdir, readFile, unlink, stat as fsStat } from "fs/promises";
|
|
16554
16557
|
import { execSync as execSync3, spawn as spawn2 } from "child_process";
|
|
16555
16558
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
16556
16559
|
import { dirname as dirname3, join as join6 } from "path";
|
|
@@ -16591,6 +16594,122 @@ function pruneSessionRunnerLogs() {
|
|
|
16591
16594
|
} catch {}
|
|
16592
16595
|
}
|
|
16593
16596
|
}
|
|
16597
|
+
function isClientError(error48) {
|
|
16598
|
+
if (!(error48 instanceof Error))
|
|
16599
|
+
return false;
|
|
16600
|
+
const match = error48.message.match(/^HTTP (\d+):/);
|
|
16601
|
+
if (!match)
|
|
16602
|
+
return false;
|
|
16603
|
+
const status = Number(match[1]);
|
|
16604
|
+
if (status === 408 || status === 429)
|
|
16605
|
+
return false;
|
|
16606
|
+
return status >= 400 && status < 500;
|
|
16607
|
+
}
|
|
16608
|
+
function isValidMarker(data) {
|
|
16609
|
+
if (!data || typeof data !== "object")
|
|
16610
|
+
return false;
|
|
16611
|
+
const d = data;
|
|
16612
|
+
if (typeof d.taskId !== "string")
|
|
16613
|
+
return false;
|
|
16614
|
+
if (typeof d.token !== "string")
|
|
16615
|
+
return false;
|
|
16616
|
+
if (typeof d.serverURL !== "string")
|
|
16617
|
+
return false;
|
|
16618
|
+
if (typeof d.createdAt !== "string" || isNaN(new Date(d.createdAt).getTime()))
|
|
16619
|
+
return false;
|
|
16620
|
+
if (!d.payload || typeof d.payload !== "object")
|
|
16621
|
+
return false;
|
|
16622
|
+
const payload = d.payload;
|
|
16623
|
+
if (d.type === "complete") {
|
|
16624
|
+
return typeof payload.output === "string";
|
|
16625
|
+
}
|
|
16626
|
+
if (d.type === "fail") {
|
|
16627
|
+
return typeof payload.error === "string";
|
|
16628
|
+
}
|
|
16629
|
+
return false;
|
|
16630
|
+
}
|
|
16631
|
+
var MARKER_STALE_MS = 24 * 60 * 60 * 1000;
|
|
16632
|
+
var TMP_STALE_MS = 60 * 60 * 1000;
|
|
16633
|
+
async function reconcilePendingCompletions(workspacesRoot) {
|
|
16634
|
+
const dir = join6(workspacesRoot, ".pending_completions");
|
|
16635
|
+
let entries;
|
|
16636
|
+
try {
|
|
16637
|
+
entries = await readdir(dir);
|
|
16638
|
+
} catch {
|
|
16639
|
+
return;
|
|
16640
|
+
}
|
|
16641
|
+
for (const name of entries) {
|
|
16642
|
+
if (!name.endsWith(".tmp"))
|
|
16643
|
+
continue;
|
|
16644
|
+
try {
|
|
16645
|
+
const s = await fsStat(join6(dir, name));
|
|
16646
|
+
if (Date.now() - s.mtimeMs > TMP_STALE_MS) {
|
|
16647
|
+
await unlink(join6(dir, name));
|
|
16648
|
+
}
|
|
16649
|
+
} catch {}
|
|
16650
|
+
}
|
|
16651
|
+
const jsonFiles = entries.filter((f) => f.endsWith(".json"));
|
|
16652
|
+
for (const name of jsonFiles) {
|
|
16653
|
+
const filePath = join6(dir, name);
|
|
16654
|
+
try {
|
|
16655
|
+
let raw;
|
|
16656
|
+
try {
|
|
16657
|
+
raw = await readFile(filePath, "utf-8");
|
|
16658
|
+
} catch {
|
|
16659
|
+
continue;
|
|
16660
|
+
}
|
|
16661
|
+
let parsed;
|
|
16662
|
+
try {
|
|
16663
|
+
parsed = JSON.parse(raw);
|
|
16664
|
+
} catch {
|
|
16665
|
+
log.warn(`reconcile: malformed marker ${name}, deleting`);
|
|
16666
|
+
try {
|
|
16667
|
+
await unlink(filePath);
|
|
16668
|
+
} catch {}
|
|
16669
|
+
continue;
|
|
16670
|
+
}
|
|
16671
|
+
if (!isValidMarker(parsed)) {
|
|
16672
|
+
log.warn(`reconcile: invalid marker structure ${name}, deleting`);
|
|
16673
|
+
try {
|
|
16674
|
+
await unlink(filePath);
|
|
16675
|
+
} catch {}
|
|
16676
|
+
continue;
|
|
16677
|
+
}
|
|
16678
|
+
const marker = parsed;
|
|
16679
|
+
const age = Date.now() - new Date(marker.createdAt).getTime();
|
|
16680
|
+
if (age > MARKER_STALE_MS) {
|
|
16681
|
+
log.warn(`reconcile: stale marker ${name} (${Math.round(age / 3600000)}h old), deleting`);
|
|
16682
|
+
try {
|
|
16683
|
+
await unlink(filePath);
|
|
16684
|
+
} catch {}
|
|
16685
|
+
continue;
|
|
16686
|
+
}
|
|
16687
|
+
const client = new DaemonClient(marker.serverURL);
|
|
16688
|
+
try {
|
|
16689
|
+
if (marker.type === "complete") {
|
|
16690
|
+
await client.completeTask(marker.token, marker.taskId, marker.payload);
|
|
16691
|
+
} else {
|
|
16692
|
+
await client.failTask(marker.token, marker.taskId, marker.payload.error);
|
|
16693
|
+
}
|
|
16694
|
+
try {
|
|
16695
|
+
await unlink(filePath);
|
|
16696
|
+
} catch (delErr) {
|
|
16697
|
+
log.warn(`reconcile: delivered marker ${name} but failed to delete: ${delErr}`);
|
|
16698
|
+
}
|
|
16699
|
+
} catch (deliverErr) {
|
|
16700
|
+
if (isClientError(deliverErr)) {
|
|
16701
|
+
try {
|
|
16702
|
+
await unlink(filePath);
|
|
16703
|
+
} catch {}
|
|
16704
|
+
} else {
|
|
16705
|
+
log.debug(`reconcile: delivery failed for ${name}, will retry next cycle`);
|
|
16706
|
+
}
|
|
16707
|
+
}
|
|
16708
|
+
} catch (e) {
|
|
16709
|
+
log.debug(`reconcile: error processing ${name}`, e);
|
|
16710
|
+
}
|
|
16711
|
+
}
|
|
16712
|
+
}
|
|
16594
16713
|
async function startDaemon(profile, serverUrl) {
|
|
16595
16714
|
pruneSessionRunnerLogs();
|
|
16596
16715
|
if (!acquireDaemonPid(profile)) {
|
|
@@ -16771,6 +16890,11 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16771
16890
|
for (const id of evictedIds) {
|
|
16772
16891
|
evictWorkspace(id);
|
|
16773
16892
|
}
|
|
16893
|
+
try {
|
|
16894
|
+
await reconcilePendingCompletions(config2.workspacesRoot);
|
|
16895
|
+
} catch (e) {
|
|
16896
|
+
log.debug("reconciliation error", e);
|
|
16897
|
+
}
|
|
16774
16898
|
if (workspaceStates.length === 0) {
|
|
16775
16899
|
log.info("All workspaces evicted — shutting down");
|
|
16776
16900
|
shutdown();
|
|
@@ -16968,7 +17092,8 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
16968
17092
|
serverURL: config2.serverURL,
|
|
16969
17093
|
token,
|
|
16970
17094
|
workspacesRoot: config2.workspacesRoot,
|
|
16971
|
-
agentTimeout: config2.agentTimeout
|
|
17095
|
+
agentTimeout: config2.agentTimeout,
|
|
17096
|
+
messageInactivityTimeout: config2.messageInactivityTimeout
|
|
16972
17097
|
};
|
|
16973
17098
|
const child = spawnSessionRunner(input);
|
|
16974
17099
|
child.on("close", () => activeTasks.delete(task.id));
|
package/dist/session-runner.js
CHANGED
|
@@ -14,7 +14,7 @@ var __export = (target, all) => {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
// daemon/session-runner.ts
|
|
17
|
-
import { mkdir, writeFile, rm } from "fs/promises";
|
|
17
|
+
import { mkdir, writeFile, rm, rename } from "fs/promises";
|
|
18
18
|
import path from "path";
|
|
19
19
|
|
|
20
20
|
// ../shared/src/constants.ts
|
|
@@ -15570,6 +15570,7 @@ var machineToken = sqliteTable("machine_token", {
|
|
|
15570
15570
|
}, (t) => [index("idx_machine_token").on(t.token)]);
|
|
15571
15571
|
// ../shared/src/db/queries/task.ts
|
|
15572
15572
|
var DEFAULT_STALE_SECONDS = Number(process.env.ALOOK_STALE_DISPATCH_TIMEOUT_S) || 20;
|
|
15573
|
+
var DEFAULT_STALE_RUNNING_SECONDS = Number(process.env.ALOOK_STALE_RUNNING_TIMEOUT_S) || 3600;
|
|
15573
15574
|
// ../shared/src/utils/email.ts
|
|
15574
15575
|
var DOMAIN = `@${process.env.ALOOK_DOMAIN || "alook.ai"}`;
|
|
15575
15576
|
var RESERVED_HANDLES = new Set([
|
|
@@ -15989,6 +15990,7 @@ class CodexBackend {
|
|
|
15989
15990
|
let notificationProtocol = "unknown";
|
|
15990
15991
|
let turnStarted = false;
|
|
15991
15992
|
let turnDoneTriggered = false;
|
|
15993
|
+
let turnCompletedSuccessfully = false;
|
|
15992
15994
|
let lastCompletedTurnId = "";
|
|
15993
15995
|
let turnError = "";
|
|
15994
15996
|
const pendingRequests = new Map;
|
|
@@ -16098,6 +16100,7 @@ class CodexBackend {
|
|
|
16098
16100
|
lastCompletedTurnId = turnId;
|
|
16099
16101
|
const status = turn?.status || params.status || "";
|
|
16100
16102
|
if (status === "completed" || status === "finished") {
|
|
16103
|
+
turnCompletedSuccessfully = true;
|
|
16101
16104
|
triggerTurnDone(false);
|
|
16102
16105
|
} else if (status === "cancelled" || status === "aborted" || status === "interrupted") {
|
|
16103
16106
|
triggerTurnDone(true);
|
|
@@ -16348,10 +16351,10 @@ class CodexBackend {
|
|
|
16348
16351
|
closeAllPending("process closed");
|
|
16349
16352
|
if (timedOut) {
|
|
16350
16353
|
resultStatus = "timeout";
|
|
16351
|
-
} else if (code !== 0 && resultStatus === "completed") {
|
|
16354
|
+
} else if (code !== 0 && resultStatus === "completed" && !turnCompletedSuccessfully) {
|
|
16352
16355
|
resultStatus = "failed";
|
|
16353
16356
|
}
|
|
16354
|
-
const stderr = stderrChunks.join("");
|
|
16357
|
+
const stderr = stderrChunks.join("").replace(/\x1b\[[0-9;]*m/g, "");
|
|
16355
16358
|
if (stderr && !lastError) {
|
|
16356
16359
|
lastError = stderr;
|
|
16357
16360
|
}
|
|
@@ -17214,6 +17217,26 @@ function buildPrompt(task, attachments) {
|
|
|
17214
17217
|
|
|
17215
17218
|
// daemon/session-runner.ts
|
|
17216
17219
|
var ATTACHMENTS_BASE = "/tmp/alook-attachments";
|
|
17220
|
+
async function writeMarkerFile(workspacesRoot, marker) {
|
|
17221
|
+
const dir = path.join(workspacesRoot, ".pending_completions");
|
|
17222
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
17223
|
+
const tmpPath = path.join(dir, `${marker.taskId}.tmp`);
|
|
17224
|
+
const finalPath = path.join(dir, `${marker.taskId}.json`);
|
|
17225
|
+
await writeFile(tmpPath, JSON.stringify(marker), { mode: 384 });
|
|
17226
|
+
await rename(tmpPath, finalPath);
|
|
17227
|
+
}
|
|
17228
|
+
async function reportToServer(fn, markerData, workspacesRoot) {
|
|
17229
|
+
try {
|
|
17230
|
+
await fn();
|
|
17231
|
+
} catch (e) {
|
|
17232
|
+
log.warn(`server report failed for task ${markerData.taskId}, writing marker: ${e}`);
|
|
17233
|
+
try {
|
|
17234
|
+
await writeMarkerFile(workspacesRoot, markerData);
|
|
17235
|
+
} catch (writeErr) {
|
|
17236
|
+
log.error(`marker write also failed for task ${markerData.taskId}: ${writeErr}`);
|
|
17237
|
+
}
|
|
17238
|
+
}
|
|
17239
|
+
}
|
|
17217
17240
|
function sanitizeFilename(name) {
|
|
17218
17241
|
return path.basename(name).replace(/[/\\]/g, "_").replace(/\.\./g, "_").slice(0, 255) || "file";
|
|
17219
17242
|
}
|
|
@@ -17241,7 +17264,7 @@ async function downloadAttachments(client, token, workspaceId, taskId, attachmen
|
|
|
17241
17264
|
return attachments;
|
|
17242
17265
|
}
|
|
17243
17266
|
async function runSession(input) {
|
|
17244
|
-
const { task, provider, cliPath, model, serverURL, token, workspacesRoot, agentTimeout } = input;
|
|
17267
|
+
const { task, provider, cliPath, model, serverURL, token, workspacesRoot, agentTimeout, messageInactivityTimeout } = input;
|
|
17245
17268
|
log.info(`starting (task=${task.id}, type=${task.type}, agent=${task.agentId}, provider=${provider}, model=${model || "default"})`);
|
|
17246
17269
|
const client = new DaemonClient(serverURL);
|
|
17247
17270
|
const backend = createBackend(provider, cliPath);
|
|
@@ -17329,27 +17352,47 @@ async function runSession(input) {
|
|
|
17329
17352
|
entry.status = "cancelled";
|
|
17330
17353
|
entry.errmsg = "cancelled by user";
|
|
17331
17354
|
});
|
|
17332
|
-
|
|
17333
|
-
await client.failTask(token, task.id, "cancelled by user");
|
|
17334
|
-
} catch {}
|
|
17355
|
+
await reportToServer(() => client.failTask(token, task.id, "cancelled by user"), { taskId: task.id, type: "fail", payload: { error: "cancelled by user" }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
17335
17356
|
} else {
|
|
17336
17357
|
updateEntry(timelineDir, task.id, (entry) => {
|
|
17337
17358
|
entry.pid = null;
|
|
17338
17359
|
entry.status = "killed";
|
|
17339
17360
|
entry.errmsg = "killed by signal";
|
|
17340
17361
|
});
|
|
17341
|
-
|
|
17342
|
-
await client.failTask(token, task.id, "killed by signal");
|
|
17343
|
-
} catch {}
|
|
17362
|
+
await reportToServer(() => client.failTask(token, task.id, "killed by signal"), { taskId: task.id, type: "fail", payload: { error: "killed by signal" }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
17344
17363
|
}
|
|
17345
17364
|
process.exit(1);
|
|
17346
17365
|
};
|
|
17347
17366
|
process.on("SIGTERM", onKill);
|
|
17348
17367
|
process.on("SIGINT", onKill);
|
|
17368
|
+
const INACTIVITY_TIMEOUT_MS = messageInactivityTimeout ?? 5 * 60 * 1000;
|
|
17369
|
+
let inactivityTimedOut = false;
|
|
17349
17370
|
try {
|
|
17350
|
-
|
|
17351
|
-
|
|
17371
|
+
const iter = session2.messages[Symbol.asyncIterator]();
|
|
17372
|
+
while (!killed) {
|
|
17373
|
+
const next = iter.next();
|
|
17374
|
+
const raceResult = await (INACTIVITY_TIMEOUT_MS > 0 ? Promise.race([
|
|
17375
|
+
next,
|
|
17376
|
+
new Promise((resolve) => {
|
|
17377
|
+
const timer = setTimeout(() => resolve("timeout"), INACTIVITY_TIMEOUT_MS);
|
|
17378
|
+
next.then(() => clearTimeout(timer), () => clearTimeout(timer));
|
|
17379
|
+
})
|
|
17380
|
+
]) : next);
|
|
17381
|
+
if (raceResult === "timeout") {
|
|
17382
|
+
inactivityTimedOut = true;
|
|
17383
|
+
log.warn(`message inactivity timeout (${INACTIVITY_TIMEOUT_MS / 1000}s) — killing agent`);
|
|
17384
|
+
if (session2.pid) {
|
|
17385
|
+
try {
|
|
17386
|
+
process.kill(session2.pid, "SIGTERM");
|
|
17387
|
+
} catch {}
|
|
17388
|
+
}
|
|
17389
|
+
iter.return?.(undefined);
|
|
17352
17390
|
break;
|
|
17391
|
+
}
|
|
17392
|
+
const iterResult = raceResult;
|
|
17393
|
+
if (iterResult.done)
|
|
17394
|
+
break;
|
|
17395
|
+
const msg = iterResult.value;
|
|
17353
17396
|
seq++;
|
|
17354
17397
|
pendingMessages.push({
|
|
17355
17398
|
seq,
|
|
@@ -17388,6 +17431,10 @@ async function runSession(input) {
|
|
|
17388
17431
|
process.removeListener("SIGINT", onKill);
|
|
17389
17432
|
if (killed)
|
|
17390
17433
|
return;
|
|
17434
|
+
if (inactivityTimedOut) {
|
|
17435
|
+
result.status = "failed";
|
|
17436
|
+
result.error = `message inactivity timeout (no messages for ${INACTIVITY_TIMEOUT_MS / 1000}s)`;
|
|
17437
|
+
}
|
|
17391
17438
|
await cleanupAttachments(task.id);
|
|
17392
17439
|
if (result.status === "completed") {
|
|
17393
17440
|
updateEntry(timelineDir, task.id, (entry) => {
|
|
@@ -17399,7 +17446,7 @@ async function runSession(input) {
|
|
|
17399
17446
|
updateEntry(timelineDir, task.id, (entry) => {
|
|
17400
17447
|
entry.pid = null;
|
|
17401
17448
|
entry.status = "failed";
|
|
17402
|
-
entry.errmsg = result.error || "
|
|
17449
|
+
entry.errmsg = result.error || "agent exited unexpectedly";
|
|
17403
17450
|
});
|
|
17404
17451
|
}
|
|
17405
17452
|
if (result.status === "completed") {
|
|
@@ -17408,11 +17455,12 @@ async function runSession(input) {
|
|
|
17408
17455
|
};
|
|
17409
17456
|
if (result.sessionId)
|
|
17410
17457
|
body.session_id = result.sessionId;
|
|
17411
|
-
await client.completeTask(token, task.id, body);
|
|
17458
|
+
await reportToServer(() => client.completeTask(token, task.id, body), { taskId: task.id, type: "complete", payload: body, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
17412
17459
|
const dur = (result.durationMs / 1000).toFixed(1);
|
|
17413
17460
|
log.info(`completed (duration=${dur}s, messages=${seq}, tools=${toolCount})`);
|
|
17414
17461
|
} else {
|
|
17415
|
-
|
|
17462
|
+
const errorMsg = result.error || "agent exited unexpectedly";
|
|
17463
|
+
await reportToServer(() => client.failTask(token, task.id, errorMsg), { taskId: task.id, type: "fail", payload: { error: errorMsg }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
17416
17464
|
const dur = (result.durationMs / 1000).toFixed(1);
|
|
17417
17465
|
log.info(`failed (duration=${dur}s, messages=${seq}, tools=${toolCount}) — ${result.error}`);
|
|
17418
17466
|
}
|
|
@@ -17437,9 +17485,8 @@ async function main() {
|
|
|
17437
17485
|
} catch (e) {
|
|
17438
17486
|
log.error(`session-runner: unhandled error for task ${input.task.id}`, e);
|
|
17439
17487
|
await cleanupAttachments(input.task.id);
|
|
17440
|
-
|
|
17441
|
-
|
|
17442
|
-
} catch {}
|
|
17488
|
+
const errorMsg = `session-runner crash: ${e}`;
|
|
17489
|
+
await reportToServer(() => client.failTask(input.token, input.task.id, errorMsg), { taskId: input.task.id, type: "fail", payload: { error: errorMsg }, token: input.token, serverURL: input.serverURL, createdAt: new Date().toISOString() }, input.workspacesRoot);
|
|
17443
17490
|
process.exit(1);
|
|
17444
17491
|
}
|
|
17445
17492
|
}
|
|
@@ -17448,5 +17495,7 @@ if (isDirectExecution) {
|
|
|
17448
17495
|
main();
|
|
17449
17496
|
}
|
|
17450
17497
|
export {
|
|
17451
|
-
|
|
17498
|
+
writeMarkerFile,
|
|
17499
|
+
runSession,
|
|
17500
|
+
reportToServer
|
|
17452
17501
|
};
|