@alook/cli 0.0.54 → 0.0.56
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 +101 -69
- package/dist/meeting-runner.js +141 -23
- package/dist/session-runner.js +62 -33
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -297,6 +297,7 @@ var COLORS = {
|
|
|
297
297
|
};
|
|
298
298
|
var RESET = "\x1B[0m";
|
|
299
299
|
var DIM = "\x1B[2m";
|
|
300
|
+
var BOLD = "\x1B[1m";
|
|
300
301
|
function useColor() {
|
|
301
302
|
if (process.env.NO_COLOR !== undefined)
|
|
302
303
|
return false;
|
|
@@ -318,13 +319,20 @@ function timestamp() {
|
|
|
318
319
|
class Logger {
|
|
319
320
|
level;
|
|
320
321
|
color;
|
|
321
|
-
|
|
322
|
-
|
|
322
|
+
module;
|
|
323
|
+
constructor(opts = {}) {
|
|
324
|
+
const envLevel = process.env.ALOOK_LOG_LEVEL;
|
|
325
|
+
this.level = LEVELS[opts.level ?? envLevel ?? "info"];
|
|
323
326
|
this.color = useColor();
|
|
327
|
+
this.module = opts.module;
|
|
324
328
|
}
|
|
325
329
|
setLevel(level) {
|
|
326
330
|
this.level = LEVELS[level];
|
|
327
331
|
}
|
|
332
|
+
child(module) {
|
|
333
|
+
const child = new Logger({ level: this.levelName(), module });
|
|
334
|
+
return child;
|
|
335
|
+
}
|
|
328
336
|
debug(msg, ...args) {
|
|
329
337
|
this.write("debug", msg, args);
|
|
330
338
|
}
|
|
@@ -337,17 +345,27 @@ class Logger {
|
|
|
337
345
|
error(msg, ...args) {
|
|
338
346
|
this.write("error", msg, args);
|
|
339
347
|
}
|
|
348
|
+
levelName() {
|
|
349
|
+
for (const [name, num] of Object.entries(LEVELS)) {
|
|
350
|
+
if (num === this.level)
|
|
351
|
+
return name;
|
|
352
|
+
}
|
|
353
|
+
return "info";
|
|
354
|
+
}
|
|
340
355
|
write(level, msg, args) {
|
|
341
356
|
if (LEVELS[level] < this.level)
|
|
342
357
|
return;
|
|
343
358
|
const ts = timestamp();
|
|
344
359
|
const label = LABELS[level];
|
|
360
|
+
const mod = this.module ? `[${this.module}]` : "";
|
|
345
361
|
let line;
|
|
346
362
|
if (this.color) {
|
|
347
363
|
const c = COLORS[level];
|
|
348
|
-
|
|
364
|
+
const modStr = mod ? ` ${BOLD}${mod}${RESET}` : "";
|
|
365
|
+
line = `${DIM}${ts}${RESET} ${c}${label}${RESET}${modStr} ${msg}`;
|
|
349
366
|
} else {
|
|
350
|
-
|
|
367
|
+
const modStr = mod ? ` ${mod}` : "";
|
|
368
|
+
line = `${ts} ${label}${modStr} ${msg}`;
|
|
351
369
|
}
|
|
352
370
|
const dest = level === "error" ? process.stderr : process.stdout;
|
|
353
371
|
dest.write(line + `
|
|
@@ -355,6 +373,15 @@ class Logger {
|
|
|
355
373
|
for (const a of args) {
|
|
356
374
|
if (a instanceof Error) {
|
|
357
375
|
dest.write(` ${a.message}
|
|
376
|
+
`);
|
|
377
|
+
if (a.stack && this.level <= LEVELS.debug) {
|
|
378
|
+
dest.write(` ${a.stack}
|
|
379
|
+
`);
|
|
380
|
+
}
|
|
381
|
+
} else if (a !== null && typeof a === "object") {
|
|
382
|
+
const pairs = Object.entries(a).map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : v}`).join(" ");
|
|
383
|
+
if (pairs)
|
|
384
|
+
dest.write(` ${pairs}
|
|
358
385
|
`);
|
|
359
386
|
} else if (a !== undefined) {
|
|
360
387
|
dest.write(` ${String(a)}
|
|
@@ -363,13 +390,13 @@ class Logger {
|
|
|
363
390
|
}
|
|
364
391
|
}
|
|
365
392
|
}
|
|
366
|
-
function createLogger(
|
|
367
|
-
|
|
368
|
-
return new Logger(level ?? envLevel ?? "info");
|
|
393
|
+
function createLogger(opts) {
|
|
394
|
+
return new Logger(opts);
|
|
369
395
|
}
|
|
370
396
|
var log = createLogger();
|
|
371
397
|
|
|
372
398
|
// daemon/pidfile.ts
|
|
399
|
+
var log2 = createLogger({ module: "pidfile" });
|
|
373
400
|
function isProcessAlive(pid) {
|
|
374
401
|
try {
|
|
375
402
|
process.kill(pid, 0);
|
|
@@ -393,7 +420,7 @@ function acquireDaemonPid(profile) {
|
|
|
393
420
|
const content = readFileSync3(pidPath, "utf-8").trim();
|
|
394
421
|
const existingPid = parseInt(content, 10);
|
|
395
422
|
if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
|
|
396
|
-
|
|
423
|
+
log2.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
|
|
397
424
|
return false;
|
|
398
425
|
}
|
|
399
426
|
} catch {}
|
|
@@ -17454,6 +17481,7 @@ function runNpmUpdate(targetVersion) {
|
|
|
17454
17481
|
}
|
|
17455
17482
|
|
|
17456
17483
|
// daemon/update-handler.ts
|
|
17484
|
+
var log3 = createLogger({ module: "updater" });
|
|
17457
17485
|
var updating = false;
|
|
17458
17486
|
var retryCount = 0;
|
|
17459
17487
|
var MAX_RETRIES = 3;
|
|
@@ -17484,24 +17512,24 @@ async function handleCliUpdate(version3, onSuccess, profile) {
|
|
|
17484
17512
|
return;
|
|
17485
17513
|
const marker = readUpdateMarker(profile);
|
|
17486
17514
|
if (marker === version3) {
|
|
17487
|
-
|
|
17515
|
+
log3.info(`Skipping update to v${version3} — already attempted (marker exists)`);
|
|
17488
17516
|
return;
|
|
17489
17517
|
}
|
|
17490
17518
|
updating = true;
|
|
17491
17519
|
try {
|
|
17492
|
-
|
|
17520
|
+
log3.info(`Updating CLI to v${version3}...`);
|
|
17493
17521
|
const result = await runNpmUpdate(version3);
|
|
17494
17522
|
if (result.success) {
|
|
17495
17523
|
writeUpdateMarker(version3, profile);
|
|
17496
|
-
|
|
17524
|
+
log3.info(`CLI updated to v${version3} — restarting`);
|
|
17497
17525
|
onSuccess();
|
|
17498
17526
|
} else {
|
|
17499
17527
|
retryCount++;
|
|
17500
|
-
|
|
17528
|
+
log3.error(`CLI update failed (attempt ${retryCount}/${MAX_RETRIES}): ${result.output}`);
|
|
17501
17529
|
}
|
|
17502
17530
|
} catch (e) {
|
|
17503
17531
|
retryCount++;
|
|
17504
|
-
|
|
17532
|
+
log3.error(`CLI update error (attempt ${retryCount}/${MAX_RETRIES})`, e);
|
|
17505
17533
|
} finally {
|
|
17506
17534
|
updating = false;
|
|
17507
17535
|
}
|
|
@@ -17548,6 +17576,7 @@ function releaseLock(lockPath) {
|
|
|
17548
17576
|
}
|
|
17549
17577
|
|
|
17550
17578
|
// daemon/execenv/timeline.ts
|
|
17579
|
+
var log4 = createLogger({ module: "timeline" });
|
|
17551
17580
|
function readJsonl(filePath) {
|
|
17552
17581
|
let content;
|
|
17553
17582
|
try {
|
|
@@ -17610,6 +17639,7 @@ function findRunningEntryByContextKey(timelineDir, contextKey, provider) {
|
|
|
17610
17639
|
// daemon/execenv/steering.ts
|
|
17611
17640
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync3, readdirSync, statSync as statSync2 } from "fs";
|
|
17612
17641
|
import { join as join5 } from "path";
|
|
17642
|
+
var log5 = createLogger({ module: "steering" });
|
|
17613
17643
|
var INTENT_DIR_NAME = ".kill_intents";
|
|
17614
17644
|
var STEERING_LOCK_DIR = ".steering_locks";
|
|
17615
17645
|
var INTENT_STALE_MS = 10 * 60 * 1000;
|
|
@@ -17648,7 +17678,7 @@ function cleanupStaleIntents(baseDir) {
|
|
|
17648
17678
|
const stat = statSync2(filePath);
|
|
17649
17679
|
if (now - stat.mtimeMs > INTENT_STALE_MS) {
|
|
17650
17680
|
unlinkSync3(filePath);
|
|
17651
|
-
|
|
17681
|
+
log5.debug(`Cleaned up stale kill intent for task ${intent.targetTaskId}`);
|
|
17652
17682
|
}
|
|
17653
17683
|
} catch {}
|
|
17654
17684
|
}
|
|
@@ -17806,6 +17836,7 @@ import { readdir as readdir2, readFile as readFile2, unlink, stat as fsStat } fr
|
|
|
17806
17836
|
import { execSync as execSync4, spawn as spawn2 } from "child_process";
|
|
17807
17837
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
17808
17838
|
import { dirname as dirname3, join as join8 } from "path";
|
|
17839
|
+
var log6 = createLogger({ module: "daemon" });
|
|
17809
17840
|
var _dir = dirname3(fileURLToPath2(import.meta.url));
|
|
17810
17841
|
var sessionRunnerPath = existsSync(join8(_dir, "session-runner.js")) ? join8(_dir, "session-runner.js") : join8(_dir, "session-runner.ts");
|
|
17811
17842
|
var meetingRunnerPath = existsSync(join8(_dir, "meeting-runner.js")) ? join8(_dir, "meeting-runner.js") : join8(_dir, "meeting-runner.ts");
|
|
@@ -17912,14 +17943,14 @@ async function reconcilePendingCompletions(workspacesRoot) {
|
|
|
17912
17943
|
try {
|
|
17913
17944
|
parsed = JSON.parse(raw);
|
|
17914
17945
|
} catch {
|
|
17915
|
-
|
|
17946
|
+
log6.warn(`reconcile: malformed marker ${name}, deleting`);
|
|
17916
17947
|
try {
|
|
17917
17948
|
await unlink(filePath);
|
|
17918
17949
|
} catch {}
|
|
17919
17950
|
continue;
|
|
17920
17951
|
}
|
|
17921
17952
|
if (!isValidMarker(parsed)) {
|
|
17922
|
-
|
|
17953
|
+
log6.warn(`reconcile: invalid marker structure ${name}, deleting`);
|
|
17923
17954
|
try {
|
|
17924
17955
|
await unlink(filePath);
|
|
17925
17956
|
} catch {}
|
|
@@ -17928,7 +17959,7 @@ async function reconcilePendingCompletions(workspacesRoot) {
|
|
|
17928
17959
|
const marker = parsed;
|
|
17929
17960
|
const age = Date.now() - new Date(marker.createdAt).getTime();
|
|
17930
17961
|
if (age > MARKER_STALE_MS) {
|
|
17931
|
-
|
|
17962
|
+
log6.warn(`reconcile: stale marker ${name} (${Math.round(age / 3600000)}h old), deleting`);
|
|
17932
17963
|
try {
|
|
17933
17964
|
await unlink(filePath);
|
|
17934
17965
|
} catch {}
|
|
@@ -17944,7 +17975,7 @@ async function reconcilePendingCompletions(workspacesRoot) {
|
|
|
17944
17975
|
try {
|
|
17945
17976
|
await unlink(filePath);
|
|
17946
17977
|
} catch (delErr) {
|
|
17947
|
-
|
|
17978
|
+
log6.warn(`reconcile: delivered marker ${name} but failed to delete: ${delErr}`);
|
|
17948
17979
|
}
|
|
17949
17980
|
} catch (deliverErr) {
|
|
17950
17981
|
if (isClientError(deliverErr)) {
|
|
@@ -17952,11 +17983,11 @@ async function reconcilePendingCompletions(workspacesRoot) {
|
|
|
17952
17983
|
await unlink(filePath);
|
|
17953
17984
|
} catch {}
|
|
17954
17985
|
} else {
|
|
17955
|
-
|
|
17986
|
+
log6.debug(`reconcile: delivery failed for ${name}, will retry next cycle`);
|
|
17956
17987
|
}
|
|
17957
17988
|
}
|
|
17958
17989
|
} catch (e) {
|
|
17959
|
-
|
|
17990
|
+
log6.debug(`reconcile: error processing ${name}`, e);
|
|
17960
17991
|
}
|
|
17961
17992
|
}
|
|
17962
17993
|
}
|
|
@@ -17967,7 +17998,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17967
17998
|
}
|
|
17968
17999
|
process.once("exit", () => releaseDaemonPid(profile));
|
|
17969
18000
|
const bailOnUnexpected = (label, err) => {
|
|
17970
|
-
|
|
18001
|
+
log6.error(`${label} — shutting down`, err);
|
|
17971
18002
|
releaseDaemonPid(profile);
|
|
17972
18003
|
process.exit(1);
|
|
17973
18004
|
};
|
|
@@ -17980,21 +18011,21 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17980
18011
|
if (marker) {
|
|
17981
18012
|
clearUpdateMarker(profile);
|
|
17982
18013
|
if (marker === config2.cliVersion) {
|
|
17983
|
-
|
|
18014
|
+
log6.info(`Cleared update marker — now running v${config2.cliVersion}`);
|
|
17984
18015
|
} else {
|
|
17985
|
-
|
|
18016
|
+
log6.info(`Cleared stale update marker (was v${marker}, running v${config2.cliVersion}) — update will be retried`);
|
|
17986
18017
|
}
|
|
17987
18018
|
}
|
|
17988
18019
|
const cliConfig = loadCLIConfigForProfile(profile);
|
|
17989
18020
|
const workspaces = cliConfig.watched_workspaces || [];
|
|
17990
18021
|
if (workspaces.length === 0) {
|
|
17991
|
-
|
|
18022
|
+
log6.error("No watched workspaces configured.");
|
|
17992
18023
|
process.exit(1);
|
|
17993
18024
|
return;
|
|
17994
18025
|
}
|
|
17995
18026
|
const hasPerWorkspaceTokens = workspaces.every((ws) => !!ws.token);
|
|
17996
18027
|
if (!hasPerWorkspaceTokens) {
|
|
17997
|
-
|
|
18028
|
+
log6.error(`Config uses old format. Run '${cmdPrefix()} register --token <token>' for each workspace to upgrade.`);
|
|
17998
18029
|
process.exit(1);
|
|
17999
18030
|
return;
|
|
18000
18031
|
}
|
|
@@ -18014,11 +18045,11 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18014
18045
|
}
|
|
18015
18046
|
}
|
|
18016
18047
|
if (providers.length === 0) {
|
|
18017
|
-
|
|
18048
|
+
log6.error("No agent CLI tools found on PATH.");
|
|
18018
18049
|
process.exit(1);
|
|
18019
18050
|
return;
|
|
18020
18051
|
}
|
|
18021
|
-
|
|
18052
|
+
log6.info(`Detected providers: ${providers.map((p) => `${p.type}@${p.version}`).join(", ")}`);
|
|
18022
18053
|
const workspaceStates = [];
|
|
18023
18054
|
const runtimeIndex = new Map;
|
|
18024
18055
|
for (const ws of workspaces) {
|
|
@@ -18026,7 +18057,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18026
18057
|
type: p.type,
|
|
18027
18058
|
version: p.version
|
|
18028
18059
|
}));
|
|
18029
|
-
|
|
18060
|
+
log6.info(`Registering workspace ${ws.id} (${ws.name ?? "unnamed"}) with ${runtimes.length} runtime(s)...`);
|
|
18030
18061
|
let resp;
|
|
18031
18062
|
try {
|
|
18032
18063
|
resp = await client.register(ws.token, {
|
|
@@ -18038,13 +18069,13 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18038
18069
|
});
|
|
18039
18070
|
} catch (e) {
|
|
18040
18071
|
if (e instanceof Error && e.message.startsWith("HTTP 401")) {
|
|
18041
|
-
|
|
18072
|
+
log6.warn(`Workspace ${ws.id} token invalid — skipping (run '${cmdPrefix()} register --token <token>' to fix)`);
|
|
18042
18073
|
} else {
|
|
18043
|
-
|
|
18074
|
+
log6.error(`Failed to register workspace ${ws.id}, skipping`, e);
|
|
18044
18075
|
}
|
|
18045
18076
|
continue;
|
|
18046
18077
|
}
|
|
18047
|
-
|
|
18078
|
+
log6.info(`Workspace ${ws.id} registered — ${resp.runtimes.length} runtime(s)`);
|
|
18048
18079
|
const runtimeIds = resp.runtimes.map((r) => r.id);
|
|
18049
18080
|
workspaceStates.push({ workspaceId: ws.id, token: ws.token, runtimeIds });
|
|
18050
18081
|
for (let i = 0;i < runtimeIds.length; i++) {
|
|
@@ -18056,13 +18087,13 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18056
18087
|
}
|
|
18057
18088
|
}
|
|
18058
18089
|
if (workspaceStates.length === 0) {
|
|
18059
|
-
|
|
18090
|
+
log6.error("No workspaces registered successfully.");
|
|
18060
18091
|
process.exit(1);
|
|
18061
18092
|
return;
|
|
18062
18093
|
}
|
|
18063
18094
|
const allRuntimeIds = workspaceStates.flatMap((ws) => ws.runtimeIds);
|
|
18064
18095
|
health.setRuntimeCount(allRuntimeIds.length);
|
|
18065
|
-
|
|
18096
|
+
log6.info(`Daemon started — ${allRuntimeIds.length} runtime(s) across ${workspaceStates.length} workspace(s)`);
|
|
18066
18097
|
const activeTasks = new Set;
|
|
18067
18098
|
const knownAgentIds = new Set(workspaces.flatMap((ws) => ws.agent_ids ?? []));
|
|
18068
18099
|
function syncAgentId(agentId, workspaceId) {
|
|
@@ -18097,7 +18128,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18097
18128
|
cfg.watched_workspaces = (cfg.watched_workspaces || []).filter((w) => w.id !== workspaceId);
|
|
18098
18129
|
saveCLIConfigForProfile(profile, cfg);
|
|
18099
18130
|
} catch {}
|
|
18100
|
-
|
|
18131
|
+
log6.info(`Workspace ${workspaceId} deleted server-side — removed from config`);
|
|
18101
18132
|
}
|
|
18102
18133
|
const pollCycle = async () => {
|
|
18103
18134
|
let remaining = config2.maxConcurrentTasks - activeTasks.size;
|
|
@@ -18123,7 +18154,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18123
18154
|
handleCliUpdate(pending_update.version, () => requestRestart(), profile);
|
|
18124
18155
|
}
|
|
18125
18156
|
if (pending_rescan) {
|
|
18126
|
-
|
|
18157
|
+
log6.info("Rescan requested — restarting daemon to re-detect runtimes");
|
|
18127
18158
|
for (const id of evictedIds) {
|
|
18128
18159
|
evictWorkspace(id);
|
|
18129
18160
|
}
|
|
@@ -18136,13 +18167,13 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18136
18167
|
activeTasks.add(task.id);
|
|
18137
18168
|
remaining--;
|
|
18138
18169
|
handleTask(client, config2, runtimeIndex, task, ws.token, activeTasks).catch((e) => {
|
|
18139
|
-
|
|
18170
|
+
log6.error("Task error", e);
|
|
18140
18171
|
activeTasks.delete(task.id);
|
|
18141
18172
|
});
|
|
18142
18173
|
}
|
|
18143
18174
|
if (file_requests) {
|
|
18144
18175
|
for (const req of file_requests) {
|
|
18145
|
-
handleFileRequest(client, config2, ws.workspaceId, req, ws.token).catch((e) =>
|
|
18176
|
+
handleFileRequest(client, config2, ws.workspaceId, req, ws.token).catch((e) => log6.debug("File request error", e));
|
|
18146
18177
|
}
|
|
18147
18178
|
}
|
|
18148
18179
|
if (meetings) {
|
|
@@ -18160,9 +18191,9 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18160
18191
|
}
|
|
18161
18192
|
} catch (e) {
|
|
18162
18193
|
if (e instanceof Error && e.message.startsWith("HTTP 401")) {
|
|
18163
|
-
|
|
18194
|
+
log6.warn(`Workspace ${ws.workspaceId} poll returned 401 — will retry next cycle`);
|
|
18164
18195
|
} else {
|
|
18165
|
-
|
|
18196
|
+
log6.debug("Poll error", e);
|
|
18166
18197
|
}
|
|
18167
18198
|
}
|
|
18168
18199
|
}
|
|
@@ -18172,10 +18203,10 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18172
18203
|
try {
|
|
18173
18204
|
await reconcilePendingCompletions(config2.workspacesRoot);
|
|
18174
18205
|
} catch (e) {
|
|
18175
|
-
|
|
18206
|
+
log6.debug("reconciliation error", e);
|
|
18176
18207
|
}
|
|
18177
18208
|
if (workspaceStates.length === 0) {
|
|
18178
|
-
|
|
18209
|
+
log6.info("All workspaces evicted — shutting down");
|
|
18179
18210
|
shutdown();
|
|
18180
18211
|
}
|
|
18181
18212
|
};
|
|
@@ -18190,7 +18221,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18190
18221
|
if (shuttingDown)
|
|
18191
18222
|
return;
|
|
18192
18223
|
shuttingDown = true;
|
|
18193
|
-
|
|
18224
|
+
log6.info(restartRequested ? "Restarting..." : "Shutting down...");
|
|
18194
18225
|
clearInterval(pollTimer);
|
|
18195
18226
|
const shutdownMs = restartRequested ? 30000 : Number(process.env.ALOOK_SHUTDOWN_TIMEOUT_MS) || 5000;
|
|
18196
18227
|
const timeout = setTimeout(() => process.exit(1), shutdownMs);
|
|
@@ -18214,7 +18245,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18214
18245
|
mkdirSync5(dirname3(logPath), { recursive: true, mode: 448 });
|
|
18215
18246
|
logFd = openSync(logPath, "a", 384);
|
|
18216
18247
|
} catch (e) {
|
|
18217
|
-
|
|
18248
|
+
log6.error(`Failed to open daemon log file ${logPath}`, e);
|
|
18218
18249
|
}
|
|
18219
18250
|
const child = spawn2(process.execPath, args, {
|
|
18220
18251
|
detached: true,
|
|
@@ -18224,7 +18255,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18224
18255
|
child.unref();
|
|
18225
18256
|
if (logFd != null)
|
|
18226
18257
|
closeSync(logFd);
|
|
18227
|
-
|
|
18258
|
+
log6.info(`Spawned new daemon (pid=${child.pid}), logs: ${logPath}`);
|
|
18228
18259
|
}
|
|
18229
18260
|
clearTimeout(timeout);
|
|
18230
18261
|
process.exit(0);
|
|
@@ -18235,7 +18266,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18235
18266
|
process.on("SIGHUP", async () => {
|
|
18236
18267
|
if (shuttingDown)
|
|
18237
18268
|
return;
|
|
18238
|
-
|
|
18269
|
+
log6.info("SIGHUP received — reloading config...");
|
|
18239
18270
|
try {
|
|
18240
18271
|
const freshConfig = loadCLIConfigForProfile(profile);
|
|
18241
18272
|
const freshWorkspaces = freshConfig.watched_workspaces || [];
|
|
@@ -18243,7 +18274,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18243
18274
|
const newWorkspaces = freshWorkspaces.filter((ws) => ws.token && !existingIds.has(ws.id));
|
|
18244
18275
|
for (const ws of newWorkspaces) {
|
|
18245
18276
|
const runtimes = providers.map((p) => ({ type: p.type, version: p.version }));
|
|
18246
|
-
|
|
18277
|
+
log6.info(`Registering new workspace ${ws.id} (${ws.name ?? "unnamed"})...`);
|
|
18247
18278
|
try {
|
|
18248
18279
|
const resp = await client.register(ws.token, {
|
|
18249
18280
|
workspace_id: ws.id,
|
|
@@ -18261,19 +18292,19 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18261
18292
|
provider: providers[i].type
|
|
18262
18293
|
});
|
|
18263
18294
|
}
|
|
18264
|
-
|
|
18295
|
+
log6.info(`Workspace ${ws.id} added — ${runtimeIds.length} runtime(s)`);
|
|
18265
18296
|
} catch (e) {
|
|
18266
|
-
|
|
18297
|
+
log6.error(`Failed to register new workspace ${ws.id}`, e);
|
|
18267
18298
|
}
|
|
18268
18299
|
}
|
|
18269
18300
|
if (newWorkspaces.length > 0) {
|
|
18270
18301
|
health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
|
|
18271
|
-
|
|
18302
|
+
log6.info(`Reload complete — now polling ${workspaceStates.length} workspace(s)`);
|
|
18272
18303
|
} else {
|
|
18273
|
-
|
|
18304
|
+
log6.info("Reload complete — no new workspaces found");
|
|
18274
18305
|
}
|
|
18275
18306
|
} catch (e) {
|
|
18276
|
-
|
|
18307
|
+
log6.error("Failed to reload config", e);
|
|
18277
18308
|
}
|
|
18278
18309
|
});
|
|
18279
18310
|
await pollCycle();
|
|
@@ -18288,7 +18319,7 @@ function spawnSessionRunner(input) {
|
|
|
18288
18319
|
try {
|
|
18289
18320
|
fd = openSync(logFilePath, "a");
|
|
18290
18321
|
} catch (e) {
|
|
18291
|
-
|
|
18322
|
+
log6.error(`Failed to open log file ${logFilePath}`, e);
|
|
18292
18323
|
}
|
|
18293
18324
|
const child = spawn2(process.execPath, [sessionRunnerPath, encoded], {
|
|
18294
18325
|
detached: true,
|
|
@@ -18308,7 +18339,7 @@ function spawnMeetingRunner(input) {
|
|
|
18308
18339
|
try {
|
|
18309
18340
|
fd = openSync(logFilePath, "a");
|
|
18310
18341
|
} catch (e) {
|
|
18311
|
-
|
|
18342
|
+
log6.error(`Failed to open meeting log file ${logFilePath}`, e);
|
|
18312
18343
|
}
|
|
18313
18344
|
const child = spawn2(process.execPath, [meetingRunnerPath, encoded], {
|
|
18314
18345
|
detached: true,
|
|
@@ -18317,7 +18348,7 @@ function spawnMeetingRunner(input) {
|
|
|
18317
18348
|
child.unref();
|
|
18318
18349
|
if (fd != null)
|
|
18319
18350
|
closeSync(fd);
|
|
18320
|
-
|
|
18351
|
+
log6.info(`Spawned meeting runner for ${input.meetingId} (pid=${child.pid})`);
|
|
18321
18352
|
return child;
|
|
18322
18353
|
}
|
|
18323
18354
|
async function handleFileRequest(client, config2, workspaceId, req, token) {
|
|
@@ -18344,7 +18375,7 @@ async function handleFileRequest(client, config2, workspaceId, req, token) {
|
|
|
18344
18375
|
}
|
|
18345
18376
|
}
|
|
18346
18377
|
async function handleTask(client, config2, runtimeIndex, task, token, activeTasks) {
|
|
18347
|
-
|
|
18378
|
+
log6.info(`Task ${task.id} claimed agent=${task.agentId}`);
|
|
18348
18379
|
if (task.type === TASK_TYPES.KILL_TASK) {
|
|
18349
18380
|
const targetTaskId = task.context?.target_task_id;
|
|
18350
18381
|
if (!targetTaskId) {
|
|
@@ -18373,18 +18404,18 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18373
18404
|
try {
|
|
18374
18405
|
process.kill(pid, "SIGTERM");
|
|
18375
18406
|
await client.failTask(token, task.id, "killed");
|
|
18376
|
-
|
|
18407
|
+
log6.info(`Kill task ${task.id}: sent SIGTERM to pid=${pid} for target=${targetTaskId}`);
|
|
18377
18408
|
} catch (e) {
|
|
18378
18409
|
if (e?.code === "ESRCH") {
|
|
18379
18410
|
await client.failTask(token, task.id, "target process already exited");
|
|
18380
|
-
|
|
18411
|
+
log6.info(`Kill task ${task.id}: target pid=${pid} already exited`);
|
|
18381
18412
|
} else {
|
|
18382
18413
|
await client.failTask(token, task.id, `kill failed: ${e}`);
|
|
18383
18414
|
}
|
|
18384
18415
|
}
|
|
18385
18416
|
} else {
|
|
18386
18417
|
await client.failTask(token, task.id, "target not found in timeline");
|
|
18387
|
-
|
|
18418
|
+
log6.info(`Kill task ${task.id}: target ${targetTaskId} not found in timeline`);
|
|
18388
18419
|
}
|
|
18389
18420
|
activeTasks.delete(task.id);
|
|
18390
18421
|
return;
|
|
@@ -18409,12 +18440,12 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18409
18440
|
const timelineDir = join8(agentBaseDir, ".context_timeline");
|
|
18410
18441
|
const lockAcquired = acquireSteeringLock(agentBaseDir, task.contextKey);
|
|
18411
18442
|
if (!lockAcquired) {
|
|
18412
|
-
|
|
18443
|
+
log6.warn(`Steering lock contention for context_key=${task.contextKey}, proceeding without steering`);
|
|
18413
18444
|
} else {
|
|
18414
18445
|
try {
|
|
18415
18446
|
const predecessor = findRunningEntryByContextKey(timelineDir, task.contextKey, provider);
|
|
18416
18447
|
if (predecessor && predecessor.task_id !== task.id) {
|
|
18417
|
-
|
|
18448
|
+
log6.info(`Steering: task ${task.id} supersedes predecessor ${predecessor.task_id} (context_key=${task.contextKey})`);
|
|
18418
18449
|
if (predecessor.pid != null) {
|
|
18419
18450
|
writeKillIntent(agentBaseDir, {
|
|
18420
18451
|
reason: "superseded",
|
|
@@ -18424,12 +18455,12 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18424
18455
|
});
|
|
18425
18456
|
try {
|
|
18426
18457
|
process.kill(predecessor.pid, "SIGTERM");
|
|
18427
|
-
|
|
18458
|
+
log6.info(`Steering: sent SIGTERM to predecessor pid=${predecessor.pid}`);
|
|
18428
18459
|
} catch (e) {
|
|
18429
18460
|
if (e?.code === "ESRCH") {
|
|
18430
|
-
|
|
18461
|
+
log6.info(`Steering: predecessor pid=${predecessor.pid} already exited`);
|
|
18431
18462
|
} else {
|
|
18432
|
-
|
|
18463
|
+
log6.warn(`Steering: kill failed for pid=${predecessor.pid}`, e);
|
|
18433
18464
|
}
|
|
18434
18465
|
}
|
|
18435
18466
|
const waitStart = Date.now();
|
|
@@ -18442,14 +18473,14 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18442
18473
|
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
18443
18474
|
}
|
|
18444
18475
|
if (findRunningPidByTaskId(timelineDir, predecessor.task_id) != null) {
|
|
18445
|
-
|
|
18476
|
+
log6.warn(`Steering: predecessor pid=${predecessor.pid} did not exit within ${MAX_WAIT_MS}ms, proceeding anyway`);
|
|
18446
18477
|
}
|
|
18447
18478
|
}
|
|
18448
18479
|
try {
|
|
18449
18480
|
await client.supersedeTask(token, predecessor.task_id);
|
|
18450
|
-
|
|
18481
|
+
log6.info(`Steering: predecessor ${predecessor.task_id} marked superseded`);
|
|
18451
18482
|
} catch (e) {
|
|
18452
|
-
|
|
18483
|
+
log6.warn(`Steering: failed to mark predecessor superseded server-side`, e);
|
|
18453
18484
|
}
|
|
18454
18485
|
}
|
|
18455
18486
|
} finally {
|
|
@@ -18474,7 +18505,7 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18474
18505
|
};
|
|
18475
18506
|
const child = spawnSessionRunner(input);
|
|
18476
18507
|
child.on("close", () => activeTasks.delete(task.id));
|
|
18477
|
-
|
|
18508
|
+
log6.info(`Task ${task.id} dispatched to session-runner (pid=${child.pid})`);
|
|
18478
18509
|
}
|
|
18479
18510
|
|
|
18480
18511
|
// commands/daemon.ts
|
|
@@ -18636,6 +18667,7 @@ import { Command as Command5 } from "commander";
|
|
|
18636
18667
|
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
18637
18668
|
import { basename, join as join9 } from "path";
|
|
18638
18669
|
import PostalMime from "postal-mime";
|
|
18670
|
+
var log7 = createLogger({ module: "email" });
|
|
18639
18671
|
var VALID_STATUSES = ["unread", "read", "archived", "sent"];
|
|
18640
18672
|
var VALID_FOLDERS = ["inbox", "sent", "untrust"];
|
|
18641
18673
|
var EMAIL_BASE = tempDir("alook-emails");
|
|
@@ -18776,7 +18808,7 @@ function emailCommand() {
|
|
|
18776
18808
|
} catch (err) {
|
|
18777
18809
|
const msg = err instanceof Error ? err.message : String(err);
|
|
18778
18810
|
if (msg.includes("404")) {
|
|
18779
|
-
|
|
18811
|
+
log7.warn(`email body not available for ${email3.id}, skipping`);
|
|
18780
18812
|
continue;
|
|
18781
18813
|
}
|
|
18782
18814
|
throw err;
|
|
@@ -18896,7 +18928,7 @@ function emailCommand() {
|
|
|
18896
18928
|
references = [parentEmail.references, parentEmail.message_id].filter(Boolean).join(" ").trim() || undefined;
|
|
18897
18929
|
}
|
|
18898
18930
|
} catch {
|
|
18899
|
-
|
|
18931
|
+
log7.warn(`could not fetch parent email ${opts.inReplyTo}, sending without threading`);
|
|
18900
18932
|
}
|
|
18901
18933
|
}
|
|
18902
18934
|
const conversationId = process.env.ALOOK_CONVERSATION_ID;
|
package/dist/meeting-runner.js
CHANGED
|
@@ -401,14 +401,132 @@ function tempDir(subdir) {
|
|
|
401
401
|
return join(tmpdir(), subdir);
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
+
// lib/logger.ts
|
|
405
|
+
var LEVELS = {
|
|
406
|
+
debug: 0,
|
|
407
|
+
info: 1,
|
|
408
|
+
warn: 2,
|
|
409
|
+
error: 3,
|
|
410
|
+
silent: 4
|
|
411
|
+
};
|
|
412
|
+
var LABELS = {
|
|
413
|
+
debug: "DEBUG",
|
|
414
|
+
info: "INFO ",
|
|
415
|
+
warn: "WARN ",
|
|
416
|
+
error: "ERROR"
|
|
417
|
+
};
|
|
418
|
+
var COLORS = {
|
|
419
|
+
debug: "\x1B[90m",
|
|
420
|
+
info: "\x1B[36m",
|
|
421
|
+
warn: "\x1B[33m",
|
|
422
|
+
error: "\x1B[31m"
|
|
423
|
+
};
|
|
424
|
+
var RESET = "\x1B[0m";
|
|
425
|
+
var DIM = "\x1B[2m";
|
|
426
|
+
var BOLD = "\x1B[1m";
|
|
427
|
+
function useColor() {
|
|
428
|
+
if (process.env.NO_COLOR !== undefined)
|
|
429
|
+
return false;
|
|
430
|
+
if (process.env.FORCE_COLOR !== undefined)
|
|
431
|
+
return true;
|
|
432
|
+
return process.stdout.isTTY === true;
|
|
433
|
+
}
|
|
434
|
+
function timestamp() {
|
|
435
|
+
const d = new Date;
|
|
436
|
+
const Y = d.getFullYear();
|
|
437
|
+
const M = String(d.getMonth() + 1).padStart(2, "0");
|
|
438
|
+
const D = String(d.getDate()).padStart(2, "0");
|
|
439
|
+
const h = String(d.getHours()).padStart(2, "0");
|
|
440
|
+
const m = String(d.getMinutes()).padStart(2, "0");
|
|
441
|
+
const s = String(d.getSeconds()).padStart(2, "0");
|
|
442
|
+
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
class Logger {
|
|
446
|
+
level;
|
|
447
|
+
color;
|
|
448
|
+
module;
|
|
449
|
+
constructor(opts = {}) {
|
|
450
|
+
const envLevel = process.env.ALOOK_LOG_LEVEL;
|
|
451
|
+
this.level = LEVELS[opts.level ?? envLevel ?? "info"];
|
|
452
|
+
this.color = useColor();
|
|
453
|
+
this.module = opts.module;
|
|
454
|
+
}
|
|
455
|
+
setLevel(level) {
|
|
456
|
+
this.level = LEVELS[level];
|
|
457
|
+
}
|
|
458
|
+
child(module) {
|
|
459
|
+
const child = new Logger({ level: this.levelName(), module });
|
|
460
|
+
return child;
|
|
461
|
+
}
|
|
462
|
+
debug(msg, ...args) {
|
|
463
|
+
this.write("debug", msg, args);
|
|
464
|
+
}
|
|
465
|
+
info(msg, ...args) {
|
|
466
|
+
this.write("info", msg, args);
|
|
467
|
+
}
|
|
468
|
+
warn(msg, ...args) {
|
|
469
|
+
this.write("warn", msg, args);
|
|
470
|
+
}
|
|
471
|
+
error(msg, ...args) {
|
|
472
|
+
this.write("error", msg, args);
|
|
473
|
+
}
|
|
474
|
+
levelName() {
|
|
475
|
+
for (const [name, num] of Object.entries(LEVELS)) {
|
|
476
|
+
if (num === this.level)
|
|
477
|
+
return name;
|
|
478
|
+
}
|
|
479
|
+
return "info";
|
|
480
|
+
}
|
|
481
|
+
write(level, msg, args) {
|
|
482
|
+
if (LEVELS[level] < this.level)
|
|
483
|
+
return;
|
|
484
|
+
const ts = timestamp();
|
|
485
|
+
const label = LABELS[level];
|
|
486
|
+
const mod = this.module ? `[${this.module}]` : "";
|
|
487
|
+
let line;
|
|
488
|
+
if (this.color) {
|
|
489
|
+
const c = COLORS[level];
|
|
490
|
+
const modStr = mod ? ` ${BOLD}${mod}${RESET}` : "";
|
|
491
|
+
line = `${DIM}${ts}${RESET} ${c}${label}${RESET}${modStr} ${msg}`;
|
|
492
|
+
} else {
|
|
493
|
+
const modStr = mod ? ` ${mod}` : "";
|
|
494
|
+
line = `${ts} ${label}${modStr} ${msg}`;
|
|
495
|
+
}
|
|
496
|
+
const dest = level === "error" ? process.stderr : process.stdout;
|
|
497
|
+
dest.write(line + `
|
|
498
|
+
`);
|
|
499
|
+
for (const a of args) {
|
|
500
|
+
if (a instanceof Error) {
|
|
501
|
+
dest.write(` ${a.message}
|
|
502
|
+
`);
|
|
503
|
+
if (a.stack && this.level <= LEVELS.debug) {
|
|
504
|
+
dest.write(` ${a.stack}
|
|
505
|
+
`);
|
|
506
|
+
}
|
|
507
|
+
} else if (a !== null && typeof a === "object") {
|
|
508
|
+
const pairs = Object.entries(a).map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : v}`).join(" ");
|
|
509
|
+
if (pairs)
|
|
510
|
+
dest.write(` ${pairs}
|
|
511
|
+
`);
|
|
512
|
+
} else if (a !== undefined) {
|
|
513
|
+
dest.write(` ${String(a)}
|
|
514
|
+
`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function createLogger(opts) {
|
|
520
|
+
return new Logger(opts);
|
|
521
|
+
}
|
|
522
|
+
var log = createLogger();
|
|
523
|
+
|
|
404
524
|
// daemon/meeting-runner.ts
|
|
525
|
+
var log2 = createLogger({ module: "meeting-runner" });
|
|
405
526
|
var SCRAPE_INTERVAL_MS = 3000;
|
|
406
527
|
var DEFAULT_BOT_NAME = "Alook Meeting Bot";
|
|
407
528
|
var MAX_RETRY_DURATION_MS = 30 * 60 * 1000;
|
|
408
529
|
var RETRY_BACKOFF = [30000, 60000, 120000, 300000];
|
|
409
|
-
function log(msg) {
|
|
410
|
-
console.log(`[meeting-runner] ${new Date().toISOString()} ${msg}`);
|
|
411
|
-
}
|
|
412
530
|
async function callbackWeb(input, status, transcript, error) {
|
|
413
531
|
const payload = JSON.stringify({
|
|
414
532
|
meetingId: input.meetingId,
|
|
@@ -426,9 +544,9 @@ async function callbackWeb(input, status, transcript, error) {
|
|
|
426
544
|
},
|
|
427
545
|
body: payload
|
|
428
546
|
});
|
|
429
|
-
|
|
547
|
+
log2.info(`callback ${status} → HTTP ${res.status}`, { meeting: input.meetingId });
|
|
430
548
|
} catch (err) {
|
|
431
|
-
|
|
549
|
+
log2.error(`callback failed: ${err instanceof Error ? err.message : err}`, { meeting: input.meetingId });
|
|
432
550
|
}
|
|
433
551
|
}
|
|
434
552
|
function launchBrowser(chromePath) {
|
|
@@ -458,7 +576,7 @@ async function tryJoinAndRecord(input, chromePath) {
|
|
|
458
576
|
try {
|
|
459
577
|
const botName = input.agentName ? `${input.agentName} (Alook)` : DEFAULT_BOT_NAME;
|
|
460
578
|
await joinMeeting(page, input.meetingUrl, botName);
|
|
461
|
-
|
|
579
|
+
log2.info("joined meeting, waiting for UI ready...", { meeting: input.meetingId });
|
|
462
580
|
await waitForMeetingReady(page);
|
|
463
581
|
await page.evaluate(() => {
|
|
464
582
|
for (const btn of document.querySelectorAll("button")) {
|
|
@@ -468,11 +586,11 @@ async function tryJoinAndRecord(input, chromePath) {
|
|
|
468
586
|
}
|
|
469
587
|
}
|
|
470
588
|
});
|
|
471
|
-
|
|
589
|
+
log2.info("meeting ready, enabling captions...", { meeting: input.meetingId });
|
|
472
590
|
await enableCaptions(page);
|
|
473
591
|
await page.evaluate(buildCaptionObserverScript());
|
|
474
592
|
await page.evaluate(buildAloneDetectorScript());
|
|
475
|
-
|
|
593
|
+
log2.info("captions enabled, scraping loop started", { meeting: input.meetingId });
|
|
476
594
|
let scrapeCount = 0;
|
|
477
595
|
while (true) {
|
|
478
596
|
try {
|
|
@@ -482,9 +600,9 @@ async function tryJoinAndRecord(input, chromePath) {
|
|
|
482
600
|
const finalCaptions = parseCaptionElements(finalRaw);
|
|
483
601
|
if (finalCaptions.length > 0) {
|
|
484
602
|
transcript = deduplicateCaptions(transcript, finalCaptions, meetingStartMs, Date.now());
|
|
485
|
-
|
|
603
|
+
log2.debug(`final scrape: ${finalCaptions.length} caption(s), total ${transcript.length}`, { meeting: input.meetingId });
|
|
486
604
|
}
|
|
487
|
-
|
|
605
|
+
log2.info("meeting ended (no longer active)", { meeting: input.meetingId });
|
|
488
606
|
break;
|
|
489
607
|
}
|
|
490
608
|
const rawElements = await page.evaluate(buildCaptionScrapeScript());
|
|
@@ -494,13 +612,13 @@ async function tryJoinAndRecord(input, chromePath) {
|
|
|
494
612
|
const prevLen = transcript.length;
|
|
495
613
|
transcript = deduplicateCaptions(transcript, captions, meetingStartMs, Date.now());
|
|
496
614
|
if (transcript.length > prevLen) {
|
|
497
|
-
|
|
615
|
+
log2.debug(`caption: ${captions[captions.length - 1].speaker}: "${captions[captions.length - 1].text}" (total ${transcript.length})`, { meeting: input.meetingId });
|
|
498
616
|
}
|
|
499
617
|
} else if (scrapeCount <= 5) {
|
|
500
|
-
|
|
618
|
+
log2.debug(`scrape #${scrapeCount}: no captions yet`, { meeting: input.meetingId });
|
|
501
619
|
}
|
|
502
620
|
} catch (err) {
|
|
503
|
-
|
|
621
|
+
log2.error(`scrape error: ${err instanceof Error ? err.message : err}`, { meeting: input.meetingId });
|
|
504
622
|
break;
|
|
505
623
|
}
|
|
506
624
|
await new Promise((resolve) => setTimeout(resolve, SCRAPE_INTERVAL_MS));
|
|
@@ -512,10 +630,10 @@ async function tryJoinAndRecord(input, chromePath) {
|
|
|
512
630
|
if (msg.includes("Blocked from joining")) {
|
|
513
631
|
const screenshotPath = join2(tempDir("alook-meetings"), `meeting-${input.meetingId}-blocked.png`);
|
|
514
632
|
await page.screenshot({ path: screenshotPath }).catch(() => {});
|
|
515
|
-
|
|
633
|
+
log2.warn(`blocked from joining, screenshot saved: ${screenshotPath}`, { meeting: input.meetingId });
|
|
516
634
|
return { status: "blocked", transcript, error: msg };
|
|
517
635
|
}
|
|
518
|
-
|
|
636
|
+
log2.error(`unexpected error: ${msg}`, { meeting: input.meetingId });
|
|
519
637
|
return { status: "error", transcript, error: msg };
|
|
520
638
|
} finally {
|
|
521
639
|
await page.close().catch(() => {});
|
|
@@ -523,14 +641,14 @@ async function tryJoinAndRecord(input, chromePath) {
|
|
|
523
641
|
}
|
|
524
642
|
}
|
|
525
643
|
async function run(input) {
|
|
526
|
-
|
|
644
|
+
log2.info(`starting meeting: url=${input.meetingUrl}`, { meeting: input.meetingId, workspace: input.workspaceId });
|
|
527
645
|
let chromePath;
|
|
528
646
|
try {
|
|
529
647
|
chromePath = ensureChrome();
|
|
530
|
-
|
|
648
|
+
log2.debug(`chrome found: ${chromePath}`);
|
|
531
649
|
} catch (err) {
|
|
532
650
|
const msg = err instanceof Error ? err.message : String(err);
|
|
533
|
-
|
|
651
|
+
log2.error(`chrome setup failed: ${msg}`, { meeting: input.meetingId });
|
|
534
652
|
await callbackWeb(input, "failed", undefined, `Chrome setup failed: ${msg}`);
|
|
535
653
|
process.exit(1);
|
|
536
654
|
}
|
|
@@ -538,23 +656,23 @@ async function run(input) {
|
|
|
538
656
|
let attempt = 0;
|
|
539
657
|
while (true) {
|
|
540
658
|
attempt++;
|
|
541
|
-
|
|
659
|
+
log2.info(attempt > 1 ? `retry attempt #${attempt}` : "launching browser (en-US, stealth)...", { meeting: input.meetingId });
|
|
542
660
|
const result = await tryJoinAndRecord(input, chromePath);
|
|
543
661
|
if (result.status === "completed") {
|
|
544
662
|
const transcriptText = formatTranscript(result.transcript);
|
|
545
|
-
|
|
663
|
+
log2.info(`completed: ${result.transcript.length} transcript entries`, { meeting: input.meetingId });
|
|
546
664
|
await callbackWeb(input, "completed", transcriptText);
|
|
547
665
|
return;
|
|
548
666
|
}
|
|
549
667
|
if (result.status === "blocked") {
|
|
550
668
|
const elapsed = Date.now() - startTime;
|
|
551
669
|
if (elapsed >= MAX_RETRY_DURATION_MS) {
|
|
552
|
-
|
|
670
|
+
log2.warn(`giving up after ${Math.round(elapsed / 60000)}min of retries`, { meeting: input.meetingId });
|
|
553
671
|
await callbackWeb(input, "failed", undefined, result.error);
|
|
554
672
|
return;
|
|
555
673
|
}
|
|
556
674
|
const backoff = RETRY_BACKOFF[Math.min(attempt - 1, RETRY_BACKOFF.length - 1)];
|
|
557
|
-
|
|
675
|
+
log2.info(`blocked, retrying in ${backoff / 1000}s (attempt=${attempt}, elapsed=${Math.round(elapsed / 60000)}min)`, { meeting: input.meetingId });
|
|
558
676
|
await new Promise((resolve) => setTimeout(resolve, backoff));
|
|
559
677
|
continue;
|
|
560
678
|
}
|
|
@@ -569,6 +687,6 @@ if (!encoded) {
|
|
|
569
687
|
}
|
|
570
688
|
var input = JSON.parse(Buffer.from(encoded, "base64").toString("utf-8"));
|
|
571
689
|
run(input).then(() => process.exit(0)).catch((err) => {
|
|
572
|
-
|
|
690
|
+
log2.error(`fatal: ${err instanceof Error ? err.message : err}`);
|
|
573
691
|
process.exit(1);
|
|
574
692
|
});
|
package/dist/session-runner.js
CHANGED
|
@@ -18120,6 +18120,7 @@ var COLORS = {
|
|
|
18120
18120
|
};
|
|
18121
18121
|
var RESET = "\x1B[0m";
|
|
18122
18122
|
var DIM = "\x1B[2m";
|
|
18123
|
+
var BOLD = "\x1B[1m";
|
|
18123
18124
|
function useColor() {
|
|
18124
18125
|
if (process.env.NO_COLOR !== undefined)
|
|
18125
18126
|
return false;
|
|
@@ -18141,13 +18142,20 @@ function timestamp() {
|
|
|
18141
18142
|
class Logger2 {
|
|
18142
18143
|
level;
|
|
18143
18144
|
color;
|
|
18144
|
-
|
|
18145
|
-
|
|
18145
|
+
module;
|
|
18146
|
+
constructor(opts = {}) {
|
|
18147
|
+
const envLevel = process.env.ALOOK_LOG_LEVEL;
|
|
18148
|
+
this.level = LEVELS[opts.level ?? envLevel ?? "info"];
|
|
18146
18149
|
this.color = useColor();
|
|
18150
|
+
this.module = opts.module;
|
|
18147
18151
|
}
|
|
18148
18152
|
setLevel(level) {
|
|
18149
18153
|
this.level = LEVELS[level];
|
|
18150
18154
|
}
|
|
18155
|
+
child(module) {
|
|
18156
|
+
const child = new Logger2({ level: this.levelName(), module });
|
|
18157
|
+
return child;
|
|
18158
|
+
}
|
|
18151
18159
|
debug(msg, ...args) {
|
|
18152
18160
|
this.write("debug", msg, args);
|
|
18153
18161
|
}
|
|
@@ -18160,17 +18168,27 @@ class Logger2 {
|
|
|
18160
18168
|
error(msg, ...args) {
|
|
18161
18169
|
this.write("error", msg, args);
|
|
18162
18170
|
}
|
|
18171
|
+
levelName() {
|
|
18172
|
+
for (const [name, num] of Object.entries(LEVELS)) {
|
|
18173
|
+
if (num === this.level)
|
|
18174
|
+
return name;
|
|
18175
|
+
}
|
|
18176
|
+
return "info";
|
|
18177
|
+
}
|
|
18163
18178
|
write(level, msg, args) {
|
|
18164
18179
|
if (LEVELS[level] < this.level)
|
|
18165
18180
|
return;
|
|
18166
18181
|
const ts = timestamp();
|
|
18167
18182
|
const label = LABELS[level];
|
|
18183
|
+
const mod = this.module ? `[${this.module}]` : "";
|
|
18168
18184
|
let line;
|
|
18169
18185
|
if (this.color) {
|
|
18170
18186
|
const c = COLORS[level];
|
|
18171
|
-
|
|
18187
|
+
const modStr = mod ? ` ${BOLD}${mod}${RESET}` : "";
|
|
18188
|
+
line = `${DIM}${ts}${RESET} ${c}${label}${RESET}${modStr} ${msg}`;
|
|
18172
18189
|
} else {
|
|
18173
|
-
|
|
18190
|
+
const modStr = mod ? ` ${mod}` : "";
|
|
18191
|
+
line = `${ts} ${label}${modStr} ${msg}`;
|
|
18174
18192
|
}
|
|
18175
18193
|
const dest = level === "error" ? process.stderr : process.stdout;
|
|
18176
18194
|
dest.write(line + `
|
|
@@ -18178,6 +18196,15 @@ class Logger2 {
|
|
|
18178
18196
|
for (const a of args) {
|
|
18179
18197
|
if (a instanceof Error) {
|
|
18180
18198
|
dest.write(` ${a.message}
|
|
18199
|
+
`);
|
|
18200
|
+
if (a.stack && this.level <= LEVELS.debug) {
|
|
18201
|
+
dest.write(` ${a.stack}
|
|
18202
|
+
`);
|
|
18203
|
+
}
|
|
18204
|
+
} else if (a !== null && typeof a === "object") {
|
|
18205
|
+
const pairs = Object.entries(a).map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : v}`).join(" ");
|
|
18206
|
+
if (pairs)
|
|
18207
|
+
dest.write(` ${pairs}
|
|
18181
18208
|
`);
|
|
18182
18209
|
} else if (a !== undefined) {
|
|
18183
18210
|
dest.write(` ${String(a)}
|
|
@@ -18186,13 +18213,13 @@ class Logger2 {
|
|
|
18186
18213
|
}
|
|
18187
18214
|
}
|
|
18188
18215
|
}
|
|
18189
|
-
function createLogger2(
|
|
18190
|
-
|
|
18191
|
-
return new Logger2(level ?? envLevel ?? "info");
|
|
18216
|
+
function createLogger2(opts) {
|
|
18217
|
+
return new Logger2(opts);
|
|
18192
18218
|
}
|
|
18193
18219
|
var log = createLogger2();
|
|
18194
18220
|
|
|
18195
18221
|
// daemon/execenv/timeline.ts
|
|
18222
|
+
var log2 = createLogger2({ module: "timeline" });
|
|
18196
18223
|
function readJsonl(filePath) {
|
|
18197
18224
|
let content;
|
|
18198
18225
|
try {
|
|
@@ -18262,7 +18289,7 @@ async function initEntryAsync(timelineDir, entry) {
|
|
|
18262
18289
|
acquired = acquireLock(lockPath);
|
|
18263
18290
|
}
|
|
18264
18291
|
if (!acquired) {
|
|
18265
|
-
|
|
18292
|
+
log2.debug(`Timeline initEntry: could not acquire lock for ${filename}`);
|
|
18266
18293
|
return;
|
|
18267
18294
|
}
|
|
18268
18295
|
try {
|
|
@@ -18272,7 +18299,7 @@ async function initEntryAsync(timelineDir, entry) {
|
|
|
18272
18299
|
releaseLock(lockPath);
|
|
18273
18300
|
}
|
|
18274
18301
|
} catch (err) {
|
|
18275
|
-
|
|
18302
|
+
log2.debug("Timeline initEntry failed", err);
|
|
18276
18303
|
}
|
|
18277
18304
|
}
|
|
18278
18305
|
function updateEntry(timelineDir, taskId, updater) {
|
|
@@ -18282,7 +18309,7 @@ function updateEntry(timelineDir, taskId, updater) {
|
|
|
18282
18309
|
try {
|
|
18283
18310
|
const acquired = acquireLock(lockPath);
|
|
18284
18311
|
if (!acquired) {
|
|
18285
|
-
|
|
18312
|
+
log2.debug(`Timeline updateEntry: lock held for ${filename}, skipping`);
|
|
18286
18313
|
continue;
|
|
18287
18314
|
}
|
|
18288
18315
|
try {
|
|
@@ -18315,10 +18342,10 @@ function updateEntry(timelineDir, taskId, updater) {
|
|
|
18315
18342
|
releaseLock(lockPath);
|
|
18316
18343
|
}
|
|
18317
18344
|
} catch (err) {
|
|
18318
|
-
|
|
18345
|
+
log2.debug(`Timeline updateEntry failed for ${filename}`, err);
|
|
18319
18346
|
}
|
|
18320
18347
|
}
|
|
18321
|
-
|
|
18348
|
+
log2.debug(`Timeline updateEntry: task_id ${taskId} not found in last 7 days`);
|
|
18322
18349
|
}
|
|
18323
18350
|
function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider, contextKey, detailedLog) {
|
|
18324
18351
|
return {
|
|
@@ -18356,6 +18383,7 @@ function findResumableSessionByContextKey(timelineDir, contextKey, provider) {
|
|
|
18356
18383
|
// daemon/execenv/steering.ts
|
|
18357
18384
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3, unlinkSync as unlinkSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
18358
18385
|
import { join as join5 } from "path";
|
|
18386
|
+
var log3 = createLogger2({ module: "steering" });
|
|
18359
18387
|
var INTENT_DIR_NAME = ".kill_intents";
|
|
18360
18388
|
var INTENT_STALE_MS = 10 * 60 * 1000;
|
|
18361
18389
|
function intentFilePath(baseDir, taskId) {
|
|
@@ -18380,7 +18408,7 @@ function clearKillIntent(baseDir, taskId) {
|
|
|
18380
18408
|
// daemon/prompt.ts
|
|
18381
18409
|
var DM_RESPONSE_NOTICE = "IMPORTANT: Only your final text response is visible to the user." + " Tool calls, intermediate reasoning, and mid-process outputs are NOT displayed." + " Put all key information, answers, and conclusions in your final response — that is the only thing the user will read.";
|
|
18382
18410
|
var EMAIL_NOTICE = "This task was triggered automatically by an incoming email. There is no human in this session." + " If you need to communicate with a human, you MUST send an email using the email sending tool." + " If you need more information or confirmation from the human, send them an email asking for it and then exit." + " Do not wait — when the human replies, a new task will be triggered automatically and you will be woken up with their response.";
|
|
18383
|
-
var ISSUE_NOTICE = "This task was triggered by an assigned issue. The issue_id is provided in this message." + " Use `alook issue show --agent_id <your_agent_id> --issue_id <issue_id>` to read full context." + " Use `alook issue update --agent_id <your_agent_id> --issue_id <issue_id> --status <status>` to change status." + " Use `alook issue comment --agent_id <your_agent_id> --issue_id <issue_id> --body <text>` to leave a comment." + "
|
|
18411
|
+
var ISSUE_NOTICE = "This task was triggered by an assigned issue. The issue_id is provided in this message." + " Use `alook issue show --agent_id <your_agent_id> --issue_id <issue_id>` to read full context." + " Use `alook issue update --agent_id <your_agent_id> --issue_id <issue_id> --status <status>` to change status." + " Use `alook issue comment --agent_id <your_agent_id> --issue_id <issue_id> --body <text>` to leave a comment." + " CRITICAL — You MUST update the issue status before you finish. This is NOT optional:" + " 1. Set status to 'in_progress' when you start working." + " 2. Set status to 'review' as your LAST action before exiting — this means your work is done and ready for the owner to review. You cannot improve the issue further without user feedback." + " If you delegated work to colleagues, wait for their responses, then set 'review' once everything is complete." + " NEVER exit without updating the status. A task left in 'in_progress' is a failed task." + " Always leave a comment summarizing what you did before changing status to 'review'.";
|
|
18384
18412
|
function buildDmNotice(name, email3) {
|
|
18385
18413
|
return `This task was triggered by an incoming email on a conversation with ${name} (${email3}).` + ` ${name} is present in this session — reply to them directly.` + ` If you need to communicate with anyone else, use the email sending tool.`;
|
|
18386
18414
|
}
|
|
@@ -18423,6 +18451,7 @@ function buildPrompt(task, attachments) {
|
|
|
18423
18451
|
}
|
|
18424
18452
|
|
|
18425
18453
|
// daemon/session-runner.ts
|
|
18454
|
+
var log4 = createLogger2({ module: "session-runner" });
|
|
18426
18455
|
var ATTACHMENTS_BASE = tempDir("alook-attachments");
|
|
18427
18456
|
async function writeMarkerFile(workspacesRoot, marker) {
|
|
18428
18457
|
const dir = path.join(workspacesRoot, ".pending_completions");
|
|
@@ -18436,11 +18465,11 @@ async function reportToServer(fn, markerData, workspacesRoot) {
|
|
|
18436
18465
|
try {
|
|
18437
18466
|
await fn();
|
|
18438
18467
|
} catch (e) {
|
|
18439
|
-
|
|
18468
|
+
log4.warn(`server report failed for task ${markerData.taskId}, writing marker: ${e}`);
|
|
18440
18469
|
try {
|
|
18441
18470
|
await writeMarkerFile(workspacesRoot, markerData);
|
|
18442
18471
|
} catch (writeErr) {
|
|
18443
|
-
|
|
18472
|
+
log4.error(`marker write also failed for task ${markerData.taskId}: ${writeErr}`);
|
|
18444
18473
|
}
|
|
18445
18474
|
}
|
|
18446
18475
|
}
|
|
@@ -18472,7 +18501,7 @@ async function downloadAttachments(client, token, workspaceId, taskId, attachmen
|
|
|
18472
18501
|
}
|
|
18473
18502
|
async function runSession(input) {
|
|
18474
18503
|
const { task, provider, cliPath, model, serverURL, token, workspacesRoot, agentTimeout, messageInactivityTimeout } = input;
|
|
18475
|
-
|
|
18504
|
+
log4.info(`starting (task=${task.id}, type=${task.type}, agent=${task.agentId}, provider=${provider}, model=${model || "default"})`);
|
|
18476
18505
|
const client = new DaemonClient(serverURL);
|
|
18477
18506
|
const backend = createBackend(provider, cliPath);
|
|
18478
18507
|
const { workDir, timelineDir, env } = prepare({ workspacesRoot }, task);
|
|
@@ -18483,7 +18512,7 @@ async function runSession(input) {
|
|
|
18483
18512
|
if (killed)
|
|
18484
18513
|
return;
|
|
18485
18514
|
killed = true;
|
|
18486
|
-
|
|
18515
|
+
log4.info(`killed by signal (messages=0, tools=0)`);
|
|
18487
18516
|
await cleanupAttachments(task.id);
|
|
18488
18517
|
const intent = readKillIntent(agentBaseDir, task.id);
|
|
18489
18518
|
clearKillIntent(agentBaseDir, task.id);
|
|
@@ -18519,14 +18548,14 @@ async function runSession(input) {
|
|
|
18519
18548
|
const attachmentIds = task.context?.attachment_ids ?? [];
|
|
18520
18549
|
let attachments;
|
|
18521
18550
|
if (attachmentIds.length > 0) {
|
|
18522
|
-
|
|
18551
|
+
log4.info(`downloading ${attachmentIds.length} attachment(s)`);
|
|
18523
18552
|
try {
|
|
18524
18553
|
attachments = await downloadAttachments(client, token, task.workspaceId, task.id, attachmentIds);
|
|
18525
|
-
|
|
18554
|
+
log4.info(`attachments ready (${attachments.length} file(s))`);
|
|
18526
18555
|
} catch (e) {
|
|
18527
18556
|
await cleanupAttachments(task.id);
|
|
18528
18557
|
const errMsg = `failed to download attachments: ${e}`;
|
|
18529
|
-
|
|
18558
|
+
log4.error(errMsg);
|
|
18530
18559
|
updateEntry(timelineDir, task.id, (entry) => {
|
|
18531
18560
|
entry.pid = null;
|
|
18532
18561
|
entry.status = "failed";
|
|
@@ -18541,7 +18570,7 @@ async function runSession(input) {
|
|
|
18541
18570
|
const prompt = buildPrompt(task, attachments);
|
|
18542
18571
|
const resumeSessionId = task.contextKey ? findResumableSessionByContextKey(timelineDir, task.contextKey, provider) ?? undefined : undefined;
|
|
18543
18572
|
if (resumeSessionId) {
|
|
18544
|
-
|
|
18573
|
+
log4.info(`resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
|
|
18545
18574
|
}
|
|
18546
18575
|
const session2 = backend.execute(prompt, {
|
|
18547
18576
|
cwd: workDir,
|
|
@@ -18552,8 +18581,8 @@ async function runSession(input) {
|
|
|
18552
18581
|
});
|
|
18553
18582
|
const agentPid = session2.pid;
|
|
18554
18583
|
const earlySessionId = await session2.sessionId;
|
|
18555
|
-
|
|
18556
|
-
|
|
18584
|
+
log4.info(`agent started (pid=${agentPid ?? "unknown"}, session=${earlySessionId})`);
|
|
18585
|
+
log4.info(JSON.stringify({ role: "user", type: "text", content: prompt }));
|
|
18557
18586
|
updateEntry(timelineDir, task.id, (entry) => {
|
|
18558
18587
|
entry.session_id = earlySessionId || null;
|
|
18559
18588
|
});
|
|
@@ -18569,7 +18598,7 @@ async function runSession(input) {
|
|
|
18569
18598
|
try {
|
|
18570
18599
|
await client.reportMessages(token, task.id, batch);
|
|
18571
18600
|
} catch (e) {
|
|
18572
|
-
|
|
18601
|
+
log4.debug("message report failed", e);
|
|
18573
18602
|
}
|
|
18574
18603
|
};
|
|
18575
18604
|
const flushTimer = setInterval(flushMessages, FLUSH_INTERVAL_MS);
|
|
@@ -18579,7 +18608,7 @@ async function runSession(input) {
|
|
|
18579
18608
|
if (killed)
|
|
18580
18609
|
return;
|
|
18581
18610
|
killed = true;
|
|
18582
|
-
|
|
18611
|
+
log4.info(`killed by signal (messages=${seq}, tools=${toolCount})`);
|
|
18583
18612
|
if (agentPid) {
|
|
18584
18613
|
try {
|
|
18585
18614
|
process.kill(agentPid, "SIGTERM");
|
|
@@ -18636,7 +18665,7 @@ async function runSession(input) {
|
|
|
18636
18665
|
]) : next);
|
|
18637
18666
|
if (raceResult === "timeout") {
|
|
18638
18667
|
inactivityTimedOut = true;
|
|
18639
|
-
|
|
18668
|
+
log4.warn(`message inactivity timeout (${INACTIVITY_TIMEOUT_MS / 1000}s) — killing agent`);
|
|
18640
18669
|
if (session2.pid) {
|
|
18641
18670
|
try {
|
|
18642
18671
|
process.kill(session2.pid, "SIGTERM");
|
|
@@ -18662,9 +18691,9 @@ async function runSession(input) {
|
|
|
18662
18691
|
if (msg.type === "tool-use")
|
|
18663
18692
|
toolCount++;
|
|
18664
18693
|
if (msg.type === "tool-result" && msg.output && msg.output.length > 500) {
|
|
18665
|
-
|
|
18694
|
+
log4.info(JSON.stringify({ role: "assistant", ...msg, output: msg.output.slice(0, 500) + `... (${msg.output.length} chars)` }));
|
|
18666
18695
|
} else {
|
|
18667
|
-
|
|
18696
|
+
log4.info(JSON.stringify({ role: "assistant", ...msg }));
|
|
18668
18697
|
}
|
|
18669
18698
|
if (msg.type === "text" && msg.content) {
|
|
18670
18699
|
updateEntry(timelineDir, task.id, (entry) => {
|
|
@@ -18717,18 +18746,18 @@ async function runSession(input) {
|
|
|
18717
18746
|
body.session_id = result.sessionId;
|
|
18718
18747
|
await reportToServer(() => client.completeTask(token, task.id, body), { taskId: task.id, type: "complete", payload: body, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
18719
18748
|
const dur = (result.durationMs / 1000).toFixed(1);
|
|
18720
|
-
|
|
18749
|
+
log4.info(`completed (duration=${dur}s, messages=${seq}, tools=${toolCount})`);
|
|
18721
18750
|
} else {
|
|
18722
18751
|
const errorMsg = result.error || "agent exited unexpectedly";
|
|
18723
18752
|
await reportToServer(() => client.failTask(token, task.id, errorMsg), { taskId: task.id, type: "fail", payload: { error: errorMsg }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
18724
18753
|
const dur = (result.durationMs / 1000).toFixed(1);
|
|
18725
|
-
|
|
18754
|
+
log4.info(`failed (duration=${dur}s, messages=${seq}, tools=${toolCount}) — ${result.error}`);
|
|
18726
18755
|
}
|
|
18727
18756
|
}
|
|
18728
18757
|
async function main() {
|
|
18729
18758
|
const encoded = process.argv[2];
|
|
18730
18759
|
if (!encoded) {
|
|
18731
|
-
|
|
18760
|
+
log4.error("session-runner: missing base64-encoded input argument");
|
|
18732
18761
|
process.exit(1);
|
|
18733
18762
|
}
|
|
18734
18763
|
let input;
|
|
@@ -18736,14 +18765,14 @@ async function main() {
|
|
|
18736
18765
|
const json2 = Buffer.from(encoded, "base64").toString("utf-8");
|
|
18737
18766
|
input = JSON.parse(json2);
|
|
18738
18767
|
} catch (e) {
|
|
18739
|
-
|
|
18768
|
+
log4.error("session-runner: failed to parse input", e);
|
|
18740
18769
|
process.exit(1);
|
|
18741
18770
|
}
|
|
18742
18771
|
const client = new DaemonClient(input.serverURL);
|
|
18743
18772
|
try {
|
|
18744
18773
|
await runSession(input);
|
|
18745
18774
|
} catch (e) {
|
|
18746
|
-
|
|
18775
|
+
log4.error(`session-runner: unhandled error for task ${input.task.id}`, e);
|
|
18747
18776
|
await cleanupAttachments(input.task.id);
|
|
18748
18777
|
const errorMsg = `session-runner crash: ${e}`;
|
|
18749
18778
|
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);
|