@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 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
- constructor(level = "info") {
322
- this.level = LEVELS[level];
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
- line = `${DIM}${ts}${RESET} ${c}${label}${RESET} ${msg}`;
364
+ const modStr = mod ? ` ${BOLD}${mod}${RESET}` : "";
365
+ line = `${DIM}${ts}${RESET} ${c}${label}${RESET}${modStr} ${msg}`;
349
366
  } else {
350
- line = `${ts} ${label} ${msg}`;
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(level) {
367
- const envLevel = process.env.ALOOK_LOG_LEVEL;
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
- log.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
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
- log.info(`Skipping update to v${version3} — already attempted (marker exists)`);
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
- log.info(`Updating CLI to v${version3}...`);
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
- log.info(`CLI updated to v${version3} — restarting`);
17524
+ log3.info(`CLI updated to v${version3} — restarting`);
17497
17525
  onSuccess();
17498
17526
  } else {
17499
17527
  retryCount++;
17500
- log.error(`CLI update failed (attempt ${retryCount}/${MAX_RETRIES}): ${result.output}`);
17528
+ log3.error(`CLI update failed (attempt ${retryCount}/${MAX_RETRIES}): ${result.output}`);
17501
17529
  }
17502
17530
  } catch (e) {
17503
17531
  retryCount++;
17504
- log.error(`CLI update error (attempt ${retryCount}/${MAX_RETRIES})`, e);
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
- log.debug(`Cleaned up stale kill intent for task ${intent.targetTaskId}`);
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
- log.warn(`reconcile: malformed marker ${name}, deleting`);
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
- log.warn(`reconcile: invalid marker structure ${name}, deleting`);
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
- log.warn(`reconcile: stale marker ${name} (${Math.round(age / 3600000)}h old), deleting`);
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
- log.warn(`reconcile: delivered marker ${name} but failed to delete: ${delErr}`);
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
- log.debug(`reconcile: delivery failed for ${name}, will retry next cycle`);
17986
+ log6.debug(`reconcile: delivery failed for ${name}, will retry next cycle`);
17956
17987
  }
17957
17988
  }
17958
17989
  } catch (e) {
17959
- log.debug(`reconcile: error processing ${name}`, e);
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
- log.error(`${label} — shutting down`, err);
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
- log.info(`Cleared update marker — now running v${config2.cliVersion}`);
18014
+ log6.info(`Cleared update marker — now running v${config2.cliVersion}`);
17984
18015
  } else {
17985
- log.info(`Cleared stale update marker (was v${marker}, running v${config2.cliVersion}) — update will be retried`);
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
- log.error("No watched workspaces configured.");
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
- log.error(`Config uses old format. Run '${cmdPrefix()} register --token <token>' for each workspace to upgrade.`);
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
- log.error("No agent CLI tools found on PATH.");
18048
+ log6.error("No agent CLI tools found on PATH.");
18018
18049
  process.exit(1);
18019
18050
  return;
18020
18051
  }
18021
- log.info(`Detected providers: ${providers.map((p) => `${p.type}@${p.version}`).join(", ")}`);
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
- log.info(`Registering workspace ${ws.id} (${ws.name ?? "unnamed"}) with ${runtimes.length} runtime(s)...`);
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
- log.warn(`Workspace ${ws.id} token invalid — skipping (run '${cmdPrefix()} register --token <token>' to fix)`);
18072
+ log6.warn(`Workspace ${ws.id} token invalid — skipping (run '${cmdPrefix()} register --token <token>' to fix)`);
18042
18073
  } else {
18043
- log.error(`Failed to register workspace ${ws.id}, skipping`, e);
18074
+ log6.error(`Failed to register workspace ${ws.id}, skipping`, e);
18044
18075
  }
18045
18076
  continue;
18046
18077
  }
18047
- log.info(`Workspace ${ws.id} registered — ${resp.runtimes.length} runtime(s)`);
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
- log.error("No workspaces registered successfully.");
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
- log.info(`Daemon started — ${allRuntimeIds.length} runtime(s) across ${workspaceStates.length} workspace(s)`);
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
- log.info(`Workspace ${workspaceId} deleted server-side — removed from config`);
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
- log.info("Rescan requested — restarting daemon to re-detect runtimes");
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
- log.error("Task error", e);
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) => log.debug("File request error", 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
- log.warn(`Workspace ${ws.workspaceId} poll returned 401 — will retry next cycle`);
18194
+ log6.warn(`Workspace ${ws.workspaceId} poll returned 401 — will retry next cycle`);
18164
18195
  } else {
18165
- log.debug("Poll error", e);
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
- log.debug("reconciliation error", e);
18206
+ log6.debug("reconciliation error", e);
18176
18207
  }
18177
18208
  if (workspaceStates.length === 0) {
18178
- log.info("All workspaces evicted — shutting down");
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
- log.info(restartRequested ? "Restarting..." : "Shutting down...");
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
- log.error(`Failed to open daemon log file ${logPath}`, e);
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
- log.info(`Spawned new daemon (pid=${child.pid}), logs: ${logPath}`);
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
- log.info("SIGHUP received — reloading config...");
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
- log.info(`Registering new workspace ${ws.id} (${ws.name ?? "unnamed"})...`);
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
- log.info(`Workspace ${ws.id} added — ${runtimeIds.length} runtime(s)`);
18295
+ log6.info(`Workspace ${ws.id} added — ${runtimeIds.length} runtime(s)`);
18265
18296
  } catch (e) {
18266
- log.error(`Failed to register new workspace ${ws.id}`, e);
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
- log.info(`Reload complete — now polling ${workspaceStates.length} workspace(s)`);
18302
+ log6.info(`Reload complete — now polling ${workspaceStates.length} workspace(s)`);
18272
18303
  } else {
18273
- log.info("Reload complete — no new workspaces found");
18304
+ log6.info("Reload complete — no new workspaces found");
18274
18305
  }
18275
18306
  } catch (e) {
18276
- log.error("Failed to reload config", e);
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
- log.error(`Failed to open log file ${logFilePath}`, e);
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
- log.error(`Failed to open meeting log file ${logFilePath}`, e);
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
- log.info(`Spawned meeting runner for ${input.meetingId} (pid=${child.pid})`);
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
- log.info(`Task ${task.id} claimed agent=${task.agentId}`);
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
- log.info(`Kill task ${task.id}: sent SIGTERM to pid=${pid} for target=${targetTaskId}`);
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
- log.info(`Kill task ${task.id}: target pid=${pid} already exited`);
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
- log.info(`Kill task ${task.id}: target ${targetTaskId} not found in timeline`);
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
- log.warn(`Steering lock contention for context_key=${task.contextKey}, proceeding without steering`);
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
- log.info(`Steering: task ${task.id} supersedes predecessor ${predecessor.task_id} (context_key=${task.contextKey})`);
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
- log.info(`Steering: sent SIGTERM to predecessor pid=${predecessor.pid}`);
18458
+ log6.info(`Steering: sent SIGTERM to predecessor pid=${predecessor.pid}`);
18428
18459
  } catch (e) {
18429
18460
  if (e?.code === "ESRCH") {
18430
- log.info(`Steering: predecessor pid=${predecessor.pid} already exited`);
18461
+ log6.info(`Steering: predecessor pid=${predecessor.pid} already exited`);
18431
18462
  } else {
18432
- log.warn(`Steering: kill failed for pid=${predecessor.pid}`, e);
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
- log.warn(`Steering: predecessor pid=${predecessor.pid} did not exit within ${MAX_WAIT_MS}ms, proceeding anyway`);
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
- log.info(`Steering: predecessor ${predecessor.task_id} marked superseded`);
18481
+ log6.info(`Steering: predecessor ${predecessor.task_id} marked superseded`);
18451
18482
  } catch (e) {
18452
- log.warn(`Steering: failed to mark predecessor superseded server-side`, e);
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
- log.info(`Task ${task.id} dispatched to session-runner (pid=${child.pid})`);
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
- console.warn(`Warning: email body not available for ${email3.id}, skipping`);
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
- console.warn(`Warning: could not fetch parent email ${opts.inReplyTo}, sending without threading`);
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;
@@ -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
- log(`Callback ${status} → ${res.status}`);
547
+ log2.info(`callback ${status} → HTTP ${res.status}`, { meeting: input.meetingId });
430
548
  } catch (err) {
431
- log(`Callback failed: ${err instanceof Error ? err.message : err}`);
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
- log("Joined. Waiting for meeting UI...");
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
- log("Meeting ready. Enabling captions...");
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
- log("Captions enabled, observer injected. Scraping loop started.");
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
- log(`Final scrape: ${finalCaptions.length} caption(s), total ${transcript.length}`);
603
+ log2.debug(`final scrape: ${finalCaptions.length} caption(s), total ${transcript.length}`, { meeting: input.meetingId });
486
604
  }
487
- log("Meeting ended (no longer active)");
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
- log(`Caption: ${captions[captions.length - 1].speaker}: "${captions[captions.length - 1].text}" (total ${transcript.length})`);
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
- log(`Scrape #${scrapeCount}: no captions yet`);
618
+ log2.debug(`scrape #${scrapeCount}: no captions yet`, { meeting: input.meetingId });
501
619
  }
502
620
  } catch (err) {
503
- log(`Scrape error: ${err instanceof Error ? err.message : err}`);
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
- log(`Blocked screenshot: ${screenshotPath}`);
633
+ log2.warn(`blocked from joining, screenshot saved: ${screenshotPath}`, { meeting: input.meetingId });
516
634
  return { status: "blocked", transcript, error: msg };
517
635
  }
518
- log(`Error: ${msg}`);
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
- log(`Starting: ${input.meetingUrl} (${input.meetingId})`);
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
- log(`Chrome found: ${chromePath}`);
648
+ log2.debug(`chrome found: ${chromePath}`);
531
649
  } catch (err) {
532
650
  const msg = err instanceof Error ? err.message : String(err);
533
- log(`Chrome setup failed: ${msg}`);
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
- log(attempt > 1 ? `Retry attempt #${attempt}...` : "Launching browser (en-US, stealth)...");
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
- log(`Completed: ${result.transcript.length} transcript entries`);
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
- log(`Giving up after ${Math.round(elapsed / 60000)}min of retries`);
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
- log(`Blocked, retrying in ${backoff / 1000}s (attempt ${attempt}, ${Math.round(elapsed / 60000)}min elapsed)`);
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
- log(`Fatal: ${err instanceof Error ? err.message : err}`);
690
+ log2.error(`fatal: ${err instanceof Error ? err.message : err}`);
573
691
  process.exit(1);
574
692
  });
@@ -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
- constructor(level = "info") {
18145
- this.level = LEVELS[level];
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
- line = `${DIM}${ts}${RESET} ${c}${label}${RESET} ${msg}`;
18187
+ const modStr = mod ? ` ${BOLD}${mod}${RESET}` : "";
18188
+ line = `${DIM}${ts}${RESET} ${c}${label}${RESET}${modStr} ${msg}`;
18172
18189
  } else {
18173
- line = `${ts} ${label} ${msg}`;
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(level) {
18190
- const envLevel = process.env.ALOOK_LOG_LEVEL;
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
- log.debug(`Timeline initEntry: could not acquire lock for ${filename}`);
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
- log.debug("Timeline initEntry failed", err);
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
- log.debug(`Timeline updateEntry: lock held for ${filename}, skipping`);
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
- log.debug(`Timeline updateEntry failed for ${filename}`, err);
18345
+ log2.debug(`Timeline updateEntry failed for ${filename}`, err);
18319
18346
  }
18320
18347
  }
18321
- log.debug(`Timeline updateEntry: task_id ${taskId} not found in last 7 days`);
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." + " You are responsible for setting the issue status: move to in_progress when you start working on the task." + " IMPORTANT: You MUST move the status to 'review' when your task is fully complete — this signals to the owner that your work is ready for review." + " Only set 'review' when the task is done, never prematurely. Do not set 'review' for partial work or while still in progress." + " Always leave a comment summarizing what you did before changing status.";
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
- log.warn(`server report failed for task ${markerData.taskId}, writing marker: ${e}`);
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
- log.error(`marker write also failed for task ${markerData.taskId}: ${writeErr}`);
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
- log.info(`starting (task=${task.id}, type=${task.type}, agent=${task.agentId}, provider=${provider}, model=${model || "default"})`);
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
- log.info(`killed by signal (messages=0, tools=0)`);
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
- log.info(`downloading ${attachmentIds.length} attachment(s)`);
18551
+ log4.info(`downloading ${attachmentIds.length} attachment(s)`);
18523
18552
  try {
18524
18553
  attachments = await downloadAttachments(client, token, task.workspaceId, task.id, attachmentIds);
18525
- log.info(`attachments ready (${attachments.length} file(s))`);
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
- log.error(errMsg);
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
- log.info(`resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
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
- log.info(`agent started (pid=${agentPid ?? "unknown"}, session=${earlySessionId})`);
18556
- log.info(JSON.stringify({ role: "user", type: "text", content: prompt }));
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
- log.debug("message report failed", e);
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
- log.info(`killed by signal (messages=${seq}, tools=${toolCount})`);
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
- log.warn(`message inactivity timeout (${INACTIVITY_TIMEOUT_MS / 1000}s) — killing agent`);
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
- log.info(JSON.stringify({ role: "assistant", ...msg, output: msg.output.slice(0, 500) + `... (${msg.output.length} chars)` }));
18694
+ log4.info(JSON.stringify({ role: "assistant", ...msg, output: msg.output.slice(0, 500) + `... (${msg.output.length} chars)` }));
18666
18695
  } else {
18667
- log.info(JSON.stringify({ role: "assistant", ...msg }));
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
- log.info(`completed (duration=${dur}s, messages=${seq}, tools=${toolCount})`);
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
- log.info(`failed (duration=${dur}s, messages=${seq}, tools=${toolCount}) — ${result.error}`);
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
- log.error("session-runner: missing base64-encoded input argument");
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
- log.error("session-runner: failed to parse input", e);
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
- log.error(`session-runner: unhandled error for task ${input.task.id}`, e);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "description": "Alook CLI — Enable Your Person Colleague",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",