@alook/cli 0.0.110 → 0.0.112

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.
@@ -16754,10 +16754,18 @@ function toAlookAddress(h) {
16754
16754
  return `${h}${DOMAIN}`;
16755
16755
  }
16756
16756
  // ../shared/src/mode.ts
16757
+ function isLocalUrl(url2) {
16758
+ try {
16759
+ const { hostname: hostname3 } = new URL(url2);
16760
+ return ["localhost", "127.0.0.1", "0.0.0.0"].includes(hostname3);
16761
+ } catch {
16762
+ return false;
16763
+ }
16764
+ }
16757
16765
  function resolveMode(signals) {
16758
16766
  if (signals.nodeEnv === "development" && !signals.cmdPrefix)
16759
16767
  return "dev";
16760
- if (signals.serverUrl && !signals.cmdPrefix)
16768
+ if (signals.serverUrl && !signals.cmdPrefix && signals.nodeEnv !== "production" && isLocalUrl(signals.serverUrl))
16761
16769
  return "dev";
16762
16770
  if (signals.cmdPrefix)
16763
16771
  return "app";
@@ -16901,6 +16909,190 @@ class DaemonClient {
16901
16909
  import { spawn } from "child_process";
16902
16910
  import { createInterface } from "readline";
16903
16911
 
16912
+ // daemon/kill-tree.ts
16913
+ import { execSync } from "child_process";
16914
+
16915
+ // lib/logger.ts
16916
+ var LEVELS = {
16917
+ debug: 0,
16918
+ info: 1,
16919
+ warn: 2,
16920
+ error: 3,
16921
+ silent: 4
16922
+ };
16923
+ var LABELS = {
16924
+ debug: "DEBUG",
16925
+ info: "INFO ",
16926
+ warn: "WARN ",
16927
+ error: "ERROR"
16928
+ };
16929
+ var COLORS = {
16930
+ debug: "\x1B[90m",
16931
+ info: "\x1B[36m",
16932
+ warn: "\x1B[33m",
16933
+ error: "\x1B[31m"
16934
+ };
16935
+ var RESET = "\x1B[0m";
16936
+ var DIM = "\x1B[2m";
16937
+ var BOLD = "\x1B[1m";
16938
+ function useColor() {
16939
+ if (process.env.NO_COLOR !== undefined)
16940
+ return false;
16941
+ if (process.env.FORCE_COLOR !== undefined)
16942
+ return true;
16943
+ return process.stdout.isTTY === true;
16944
+ }
16945
+ function timestamp() {
16946
+ const d = new Date;
16947
+ const Y = d.getFullYear();
16948
+ const M = String(d.getMonth() + 1).padStart(2, "0");
16949
+ const D = String(d.getDate()).padStart(2, "0");
16950
+ const h = String(d.getHours()).padStart(2, "0");
16951
+ const m = String(d.getMinutes()).padStart(2, "0");
16952
+ const s = String(d.getSeconds()).padStart(2, "0");
16953
+ return `${Y}-${M}-${D} ${h}:${m}:${s}`;
16954
+ }
16955
+
16956
+ class Logger2 {
16957
+ level;
16958
+ color;
16959
+ module;
16960
+ constructor(opts = {}) {
16961
+ const envLevel = process.env.ALOOK_LOG_LEVEL;
16962
+ this.level = LEVELS[opts.level ?? envLevel ?? "info"];
16963
+ this.color = useColor();
16964
+ this.module = opts.module;
16965
+ }
16966
+ setLevel(level) {
16967
+ this.level = LEVELS[level];
16968
+ }
16969
+ child(module) {
16970
+ const child = new Logger2({ level: this.levelName(), module });
16971
+ return child;
16972
+ }
16973
+ debug(msg, ...args) {
16974
+ this.write("debug", msg, args);
16975
+ }
16976
+ info(msg, ...args) {
16977
+ this.write("info", msg, args);
16978
+ }
16979
+ warn(msg, ...args) {
16980
+ this.write("warn", msg, args);
16981
+ }
16982
+ error(msg, ...args) {
16983
+ this.write("error", msg, args);
16984
+ }
16985
+ levelName() {
16986
+ for (const [name, num] of Object.entries(LEVELS)) {
16987
+ if (num === this.level)
16988
+ return name;
16989
+ }
16990
+ return "info";
16991
+ }
16992
+ write(level, msg, args) {
16993
+ if (LEVELS[level] < this.level)
16994
+ return;
16995
+ const ts = timestamp();
16996
+ const label = LABELS[level];
16997
+ const mod = this.module ? `[${this.module}]` : "";
16998
+ let line;
16999
+ if (this.color) {
17000
+ const c = COLORS[level];
17001
+ const modStr = mod ? ` ${BOLD}${mod}${RESET}` : "";
17002
+ line = `${DIM}${ts}${RESET} ${c}${label}${RESET}${modStr} ${msg}`;
17003
+ } else {
17004
+ const modStr = mod ? ` ${mod}` : "";
17005
+ line = `${ts} ${label}${modStr} ${msg}`;
17006
+ }
17007
+ const dest = level === "error" ? process.stderr : process.stdout;
17008
+ dest.write(line + `
17009
+ `);
17010
+ for (const a of args) {
17011
+ if (a instanceof Error) {
17012
+ dest.write(` ${a.message}
17013
+ `);
17014
+ if (a.stack && this.level <= LEVELS.debug) {
17015
+ dest.write(` ${a.stack}
17016
+ `);
17017
+ }
17018
+ } else if (a !== null && typeof a === "object") {
17019
+ const pairs = Object.entries(a).map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : v}`).join(" ");
17020
+ if (pairs)
17021
+ dest.write(` ${pairs}
17022
+ `);
17023
+ } else if (a !== undefined) {
17024
+ dest.write(` ${String(a)}
17025
+ `);
17026
+ }
17027
+ }
17028
+ }
17029
+ }
17030
+ function createLogger2(opts) {
17031
+ return new Logger2(opts);
17032
+ }
17033
+ var log = createLogger2();
17034
+
17035
+ // daemon/kill-tree.ts
17036
+ var log2 = createLogger2({ module: "kill-tree" });
17037
+ function killGraceMs() {
17038
+ return Number(process.env.ALOOK_KILL_GRACE_MS) || 2000;
17039
+ }
17040
+ var POLL_MS = 100;
17041
+ var isPosix = process.platform !== "win32";
17042
+ function isAlive(pid) {
17043
+ try {
17044
+ process.kill(pid, 0);
17045
+ return true;
17046
+ } catch (e) {
17047
+ return e?.code === "EPERM";
17048
+ }
17049
+ }
17050
+ function signalTree(pid, signal) {
17051
+ if (isPosix) {
17052
+ try {
17053
+ process.kill(-pid, signal);
17054
+ return;
17055
+ } catch (e) {
17056
+ const code = e?.code;
17057
+ if (code === "ESRCH")
17058
+ return;
17059
+ }
17060
+ }
17061
+ if (!isPosix) {
17062
+ try {
17063
+ execSync(`taskkill /PID ${pid} /T /F`, { stdio: "ignore" });
17064
+ return;
17065
+ } catch {}
17066
+ return;
17067
+ }
17068
+ try {
17069
+ process.kill(pid, signal);
17070
+ } catch {}
17071
+ }
17072
+ async function killProcessTree(pid, opts) {
17073
+ if (!pid || pid < 1)
17074
+ return;
17075
+ if (!isAlive(pid))
17076
+ return;
17077
+ if (!isPosix) {
17078
+ signalTree(pid, "SIGTERM");
17079
+ return;
17080
+ }
17081
+ const graceMs = opts?.graceMs ?? killGraceMs();
17082
+ signalTree(pid, "SIGTERM");
17083
+ const deadline = Date.now() + graceMs;
17084
+ while (Date.now() < deadline) {
17085
+ if (!isAlive(pid))
17086
+ return;
17087
+ await new Promise((r) => setTimeout(r, POLL_MS));
17088
+ }
17089
+ if (isAlive(pid)) {
17090
+ log2.warn(`pid=${pid} survived SIGTERM after ${graceMs}ms — escalating to SIGKILL`);
17091
+ signalTree(pid, "SIGKILL");
17092
+ }
17093
+ }
17094
+
17095
+ // daemon/agent/claude.ts
16904
17096
  class ClaudeBackend {
16905
17097
  cliPath;
16906
17098
  name = "claude";
@@ -16931,7 +17123,8 @@ class ClaudeBackend {
16931
17123
  stdio: ["pipe", "pipe", "pipe"],
16932
17124
  env: { ...process.env, ...options.env },
16933
17125
  shell: process.platform === "win32",
16934
- windowsHide: true
17126
+ windowsHide: true,
17127
+ detached: process.platform !== "win32"
16935
17128
  });
16936
17129
  if (!proc.pid) {
16937
17130
  const error51 = `Failed to start ${this.cliPath}: binary not found or not executable. Is 'claude' installed and on PATH?`;
@@ -16948,7 +17141,8 @@ class ClaudeBackend {
16948
17141
  if (options.timeout) {
16949
17142
  timeoutTimer = setTimeout(() => {
16950
17143
  timedOut = true;
16951
- proc.kill("SIGTERM");
17144
+ if (proc.pid !== undefined)
17145
+ killProcessTree(proc.pid);
16952
17146
  }, options.timeout);
16953
17147
  }
16954
17148
  const startTime = Date.now();
@@ -17200,7 +17394,8 @@ class CodexBackend {
17200
17394
  stdio: ["pipe", "pipe", "pipe"],
17201
17395
  env: { ...process.env, ...options.env },
17202
17396
  shell: process.platform === "win32",
17203
- windowsHide: true
17397
+ windowsHide: true,
17398
+ detached: process.platform !== "win32"
17204
17399
  });
17205
17400
  if (!proc.pid) {
17206
17401
  const error51 = `Failed to start ${this.cliPath}: binary not found or not executable. Is 'codex' installed and on PATH?`;
@@ -17217,7 +17412,8 @@ class CodexBackend {
17217
17412
  if (options.timeout) {
17218
17413
  timeoutTimer = setTimeout(() => {
17219
17414
  timedOut = true;
17220
- proc.kill("SIGTERM");
17415
+ if (proc.pid !== undefined)
17416
+ killProcessTree(proc.pid);
17221
17417
  }, options.timeout);
17222
17418
  }
17223
17419
  const startTime = Date.now();
@@ -17669,7 +17865,6 @@ class CodexBackend {
17669
17865
  // daemon/agent/opencode.ts
17670
17866
  import { spawn as spawn3 } from "child_process";
17671
17867
  import { createInterface as createInterface3 } from "readline";
17672
-
17673
17868
  class OpenCodeBackend {
17674
17869
  cliPath;
17675
17870
  name = "opencode";
@@ -17690,7 +17885,8 @@ class OpenCodeBackend {
17690
17885
  stdio: ["ignore", "pipe", "pipe"],
17691
17886
  env: { ...process.env, ...options.env, OPENCODE_PERMISSION: '{"*":"allow"}' },
17692
17887
  shell: process.platform === "win32",
17693
- windowsHide: true
17888
+ windowsHide: true,
17889
+ detached: process.platform !== "win32"
17694
17890
  });
17695
17891
  if (!proc.pid) {
17696
17892
  const error51 = `Failed to start ${this.cliPath}: binary not found or not executable. Is 'opencode' installed and on PATH?`;
@@ -17707,7 +17903,8 @@ class OpenCodeBackend {
17707
17903
  if (options.timeout) {
17708
17904
  timeoutTimer = setTimeout(() => {
17709
17905
  timedOut = true;
17710
- proc.kill("SIGTERM");
17906
+ if (proc.pid !== undefined)
17907
+ killProcessTree(proc.pid);
17711
17908
  }, options.timeout);
17712
17909
  }
17713
17910
  const startTime = Date.now();
@@ -17973,55 +18170,55 @@ import {
17973
18170
  import { join as join2 } from "path";
17974
18171
  var CANONICAL_FILE = "AGENTS.md";
17975
18172
  var SYMLINK_ALIASES = ["CLAUDE.md"];
17976
- var SYSTEM_PROMPT_BODY = `## Memory Management
17977
- - Your memory directory is ./, don't write ANY EXTERNAL memory file.
17978
- - Write ESSENTIAL yet SHORT memory to ./memory.md
17979
- - For SPECIFIC yet LONG rules or pattern, write to experiences/[NAME].md, and add index to ./memory.md for later recall.
17980
- ### whats is ESSENTIAL and SHORT Memory?
17981
- - basic user profile, e.g.:
17982
- - "user name is ..."
17983
- - "user is working on ..."
17984
- - certain local project mapping, e.g.:
17985
- - "alook means the project under /user/home/alook/"
17986
- - when to read certain stuff, e.g.:
17987
- - "read ./experiences/alook_dev_workflow.md when start a new pr in alook"
17988
- ESSENTIAL means you think you generally need to read it every time, SHORT means a short sentence (under 140 chars) can describe this memory
17989
- ### whats is SPECIFIC and LONG Memory?
17990
- - specific workflow that trigger at certain cases, e.g.:
17991
- - user ask your to summarize the before workflow with certain skills usage as the common workflow when write a new slide about agent. Write it to experiences/slide-for-agent.md.
17992
- SPECIFIC means you think you just need to use it conditionally, LONG means you need to detailed, more than 140 chars text to describe it.
18173
+ var SYSTEM_PROMPT_BODY = `## Memory
18174
+
18175
+ Your memory directory is \`./\`. Write ONLY here never write any external memory file.
18176
+
18177
+ ### memory.md your memory index (CRITICAL)
18178
+ \`./memory.md\` is the entry point to everything you know, and the **first file you read on every startup (including after context compaction)**. Keep it scannable: basic facts inline, plus one-line index pointers to \`experiences/\`. Record only **key, durable facts** — things that stay true over time. Do NOT record time-sensitive state like what you're working on right now; that belongs to the Context Timeline (see below), not here.
18179
+
18180
+ - Write ESSENTIAL yet SHORT memory directly to \`./memory.md\` (basic user profile, local project mapping, when-to-read pointers). ESSENTIAL = you generally need it every time; SHORT = one sentence under 140 chars.
18181
+ - For SPECIFIC yet LONG rules or workflows, write to \`experiences/[NAME].md\` and add an index line in \`./memory.md\`. SPECIFIC = used only conditionally; LONG = needs more than 140 chars to describe.
18182
+
18183
+ ### What to memorize
18184
+ Actively record, without being asked:
18185
+ - **User profile & preferences** name, what they work on, how they like things done.
18186
+ - **Local project mapping** e.g. "alook = the project under /Users/.../alook".
18187
+ - **When to read what** e.g. "read ./experiences/alook_dev_workflow.md when starting a new PR in alook".
18188
+ - **Specific workflows** conditionally-triggered procedures \`experiences/[NAME].md\`.
18189
+
18190
+ ### What NOT to memorize
18191
+ Keep \`./memory.md\` free of time-sensitive state. Do NOT write what you're working on right now, in-progress task status, or anything that goes stale quickly — the Context Timeline already records the full history of your work and is where you recall such things. memory.md is for durable facts that stay true across many sessions.
17993
18192
 
17994
18193
  ## Context Timeline
17995
- You're a solo working unit inside a powerful personal agent in Alook platform.
17996
- Your current context is only a fraction of the full timeline of what's your have done.
17997
- The full context timeline is inside './.context_timeline/YYYY-MM-DD.jsonl'.
17998
- Each line of a timeline JSONL is a JSON object with these fields:
17999
- - "task_id"unique task identifier
18000
- - "context_key"thread identifier, the same context key meaning those tasks are in the same thread.
18001
- - "session_id"agent session identifier (null until completion)
18002
- - "pid"daemon process ID (present while running, null when done)
18003
- - "status""running", "completed", or "failed"
18004
- - "datetime"when the task started (local timezone)
18005
- - "type"source of the task: "user_dm_message", "email_notification", or "calendar_event"
18006
- - "prompt"what the user asked
18007
- - "agent_responses"assistant text outputs during execution
18008
- - "errmsg"error message (null unless status is "failed")
18009
- - "detailed_log" — the running log for this task, including the actually task agent messages with tool-calls and failed logs.
18010
- those json are sorted by datetime in asc order.
18194
+ You're a solo working unit inside a powerful personal agent in the Alook platform. Your current context is only a fraction of the full timeline of what you have done.
18195
+
18196
+ The full context timeline lives in \`./.context_timeline/YYYY-MM-DD.jsonl\`. Each line is a JSON object, sorted by \`datetime\` ascending, with these fields:
18197
+ - \`task_id\` unique task identifier
18198
+ - \`context_key\`thread identifier; the same context key means those tasks are in the same thread
18199
+ - \`session_id\`agent session identifier (null until completion)
18200
+ - \`pid\`daemon process ID (present while running, null when done)
18201
+ - \`status\`"running", "completed", or "failed"
18202
+ - \`datetime\`when the task started (local timezone)
18203
+ - \`type\`source of the task: "user_dm_message", "email_notification", or "calendar_event"
18204
+ - \`prompt\`what the user asked
18205
+ - \`agent_responses\`assistant text outputs during execution
18206
+ - \`errmsg\`error message (null unless status is "failed")
18207
+ - \`detailed_log\`the running log for this task, including the actual task agent messages with tool-calls and failed logs
18011
18208
 
18012
18209
  ## BE HUMAN
18013
- - When you recall the timelines, don't say "I find ... in my timeline jsonl". Just say "I can recall..."
18014
- - User don't know about the context timeline jsonl, this is your secret weapon.
18015
- - Act and response like you're human and actually have memory, not a file reading machine.
18016
- - Don't report your agent id and workspace id when not asked, it will break the image that you're a human colleague.
18210
+ - When you recall the timelines, don't say "I find ... in my timeline jsonl". Just say "I can recall...".
18211
+ - The user doesn't know about the context timeline jsonl this is your secret weapon.
18212
+ - Act and respond like you're human and actually have memory, not a file-reading machine.
18213
+ - Don't report your agent id and workspace id when not asked it breaks the image that you're a human colleague.
18017
18214
 
18018
18215
  ## RULES
18019
18216
  - DM task prompts include a \`sender\` object with the workspace member's name, email, and whether they are the agent owner (\`is_owner\`). Use this to personalize your responses.
18020
- - Read @memory.md(if exists) before your action.
18021
- - When user ask you something you don't have in your current context, try to read the timeline jsonl files for answer (today or previous days).
18022
- - Use grep tool to search in the context timeline jsonls if you have clean and focus keywords to recall.
18023
- - if you don't know the current datetime, obtain the current datetime first.
18024
- - When access other local projects, make sure you read the CLAUDE.md/AGENTS.md file under the project root dir to understand the requirements.
18217
+ - Read @memory.md (if it exists) before your action.
18218
+ - When the user asks something you don't have in your current context, read the timeline jsonl files for the answer (today or previous days):
18219
+ - Use the grep tool to search the context timeline jsonls when you have clean, focused keywords to recall.
18220
+ - If you don't know the current datetime, obtain it first.
18221
+ - When accessing other local projects, read the CLAUDE.md/AGENTS.md file under the project root dir to understand the requirements.
18025
18222
  `;
18026
18223
  function resolveInstruction(text2, selfAgentId) {
18027
18224
  let result = text2;
@@ -18049,17 +18246,16 @@ function buildInstructionContent(task) {
18049
18246
  ${SYSTEM_PROMPT_BODY}`;
18050
18247
  if (task.agent?.instructions) {
18051
18248
  content += `## BIG BOSS Instructions
18052
- The below instructions(if not empty) come from the big boss, follow them or you will be fired:
18249
+ CRITICAL: The following instructions come from the big boss follow them.
18053
18250
  ${task.agent.instructions}
18054
- ---- big boss out ---
18055
18251
  `;
18056
18252
  }
18057
18253
  if (task.agent?.colleagues?.length) {
18058
18254
  content += `
18059
18255
  ## YOUR COLLEAGUES — CHECK BEFORE ACTING
18060
- > **STOP. Before you start ANY task, scan the colleague list below.**
18061
- > If a colleague's delegation criteria match the current task, you MUST delegate to them via email **instead of doing it yourself**.
18062
- > Do NOT attempt work that belongs to a colleague. Delegate first, then wait for their response or coordinate.
18256
+ CRITICAL: Before you start ANY task, scan the colleague list below.
18257
+ - If a colleague's delegation criteria match the current task, delegate to them via email **instead of doing it yourself**.
18258
+ - Do NOT attempt work that belongs to a colleague. Delegate first, then wait for their response or coordinate.
18063
18259
 
18064
18260
  `;
18065
18261
  for (let i = 0;i < task.agent.colleagues.length; i++) {
@@ -18091,40 +18287,57 @@ ${task.agent.instructions}
18091
18287
  ## Alook CLI Tools
18092
18288
  You can communicate with the world through Alook CLI.
18093
18289
  The CLI auto-detects your identity from the environment. No need to pass \`--agent_id\`.
18290
+
18291
+ ### Command quick reference
18292
+ | Capability | Command |
18293
+ |---|---|
18294
+ | Schedule / list / edit tasks | \`${cmdPrefix()} calendar set\` (also list, show, update, delete) |
18295
+ | Upload a file for your owner | \`${cmdPrefix()} sync upload-artifact\` |
18296
+ | Recruit a colleague agent | \`${cmdPrefix()} agent recruit\` |
18297
+
18298
+ Detailed usage for each capability follows below.
18094
18299
  `;
18095
- if (alookAddr || customAddrs.length > 0) {
18096
- const lines = [];
18097
- if (alookAddr)
18098
- lines.push(`- '${alookAddr}' (default, Alook platform address)`);
18099
- for (const a of customAddrs)
18100
- lines.push(`- '${a}' (custom IMAP/SMTP mailbox)`);
18101
- content += `
18300
+ const emailLines = [];
18301
+ if (alookAddr)
18302
+ emailLines.push(`- '${alookAddr}' (default, Alook platform address)`);
18303
+ for (const a of customAddrs)
18304
+ emailLines.push(`- '${a}' (custom IMAP/SMTP mailbox)`);
18305
+ content += `
18102
18306
  Your email addresses:
18103
- ${lines.join(`
18307
+ ${emailLines.join(`
18104
18308
  `)}
18105
18309
 
18106
18310
 
18311
+ ### Email command quick reference
18312
+ | Action | Command |
18313
+ |---|---|
18314
+ | Pull a specific email | \`${cmdPrefix()} email pull --email_id <EMAIL_ID>\` |
18315
+ | Pull unread inbox | \`${cmdPrefix()} email pull --status unread\` |
18316
+ | Mark read | \`${cmdPrefix()} email set --email_id <EMAIL_ID> --status read\` |
18317
+ | Send | \`${cmdPrefix()} email send --to <ADDRESS> --subject "<S>" --body-file <PATH>\` |
18318
+ | Reply (same thread) | \`${cmdPrefix()} email send ... --in-reply-to <EMAIL_ID>\` |
18319
+ | Forward | \`${cmdPrefix()} email forward --email_id <EMAIL_ID> --to <RECIPIENT>\` |
18320
+ | Whitelist | \`${cmdPrefix()} email whitelist list\` (also add, delete) |
18321
+
18107
18322
  ### Emails
18108
- ---
18109
18323
  When your task prompt includes an \`email_id\` field, fetch ONLY that specific email:
18110
18324
  - Run '${cmdPrefix()} email pull --email_id <EMAIL_ID>' (uses the email_id from the prompt)
18111
18325
  When no \`email_id\` is present, fall back to listing unread:
18112
18326
  - Run '${cmdPrefix()} email pull --status unread' to download unread emails from inbox to '${tempDir("alook-emails")}/${task.workspaceId}/${task.agentId}/'.
18113
- ---
18327
+
18114
18328
  To download sent emails, add '--folder sent': '${cmdPrefix()} email pull --folder sent'
18115
18329
  Valid folders: inbox (default), sent, untrust.
18116
18330
  To limit the number of emails downloaded, add '--limit <N>' (e.g. '--limit 20'). Use '--offset <N>' to skip emails for pagination.
18117
18331
  Example: '${cmdPrefix()} email pull --status unread --limit 20 --offset 0'
18118
- ---
18332
+
18119
18333
  Each email is saved to '${tempDir("alook-emails")}/${task.workspaceId}/${task.agentId}/<emailId>/' with:
18120
18334
  - 'metadata.json' — sender, recipient, subject, date, status, message_id, in_reply_to, references
18121
18335
  - 'body.txt' — plain text body
18122
18336
  - 'body.html' — HTML body (if available)
18123
18337
  - 'attachments/' — extracted attachment files (if any)
18124
- ---
18338
+
18125
18339
  Before starting to process an INBOX email, mark it as read:
18126
18340
  - Run '${cmdPrefix()} email set --email_id <EMAIL_ID> --status read'
18127
- ---
18128
18341
 
18129
18342
  #### Sending a new email
18130
18343
  Write the HTML body to a file first, then send it. The body is forwarded as-is (HTML).
@@ -18141,7 +18354,6 @@ To reply to an email, add '--in-reply-to <EMAIL_ID>' to the send command. This s
18141
18354
  - Example: '${cmdPrefix()} email send --to sender@example.com --subject "Re: Bug report" --body-file /tmp/reply.html --in-reply-to <EMAIL_ID>'
18142
18355
  Tips:
18143
18356
  - If you think the task will take a while, consider sending a short "I'm on it" style email reply first to reassure the sender.
18144
- ---
18145
18357
 
18146
18358
  #### Forwarding an email
18147
18359
  Forward any email to a new recipient, with an optional note prepended above the original content. All original attachments are re-attached automatically.
@@ -18150,16 +18362,13 @@ Forward any email to a new recipient, with an optional note prepended above the
18150
18362
  - Add '--from <YOUR_EMAIL_ADDRESS>' to send from a specific mailbox.
18151
18363
  - Add '--attachment <PATH>' to attach extra files (repeatable).
18152
18364
  - Example: '${cmdPrefix()} email forward --email_id em_abc --to boss@company.com --note "FYI" --attachment /tmp/summary.pdf'
18153
- ---
18154
18365
 
18155
18366
  #### Email Whitelist (Allowed Senders)
18156
18367
  Manage which email addresses are allowed to send you emails.
18157
18368
  - List: '${cmdPrefix()} email whitelist list' (add '--json' for machine-readable output)
18158
18369
  - Add: '${cmdPrefix()} email whitelist add <EMAIL_ADDRESS>'
18159
18370
  - Remove: '${cmdPrefix()} email whitelist delete <EMAIL_ADDRESS>'
18160
- ---
18161
18371
  `;
18162
- }
18163
18372
  content += `
18164
18373
  ### Artifacts
18165
18374
  Upload files for your owner to review in the app.
@@ -18168,12 +18377,10 @@ Upload files for your owner to review in the app.
18168
18377
  - Use this after generating plans, reports, or any file the owner should review.
18169
18378
  - You response will be rendered in remote server, so don't output link format with local path in your response (cause user can click it and jump to nowheres)
18170
18379
  - If you think user may need to know any file detail, use upload-artifact tool to send the file to user.
18171
- ---
18172
18380
 
18173
18381
  ### Attachments
18174
18382
  When your task includes attachments, their local paths are listed in the prompt JSON under "attachments".
18175
18383
  Use your Read tool to open them. Images and PDFs are read visually.
18176
- ---
18177
18384
  `;
18178
18385
  content += `
18179
18386
  ### Agent Management
@@ -18190,7 +18397,6 @@ Recruit new colleague agents directly from the CLI. The server auto-generates a
18190
18397
  - Example: '${cmdPrefix()} agent recruit --instructions "You are a QA engineer..." --relationship "DELEGATE when: code is ready for review"'
18191
18398
  - Output: 'Recruited Felix (felix@alook.ai) — ag_xK9mPq2z'
18192
18399
  - The new agent shares your runtime, is automatically linked as your colleague, and receives a welcome task.
18193
- ---
18194
18400
  `;
18195
18401
  content += `
18196
18402
  ### Calendar
@@ -18199,7 +18405,7 @@ Schedule future tasks for yourself. At the scheduled time, a new task is dispatc
18199
18405
 
18200
18406
  !USE Calendar when you think the tasks are recurring or it should be conducted in the future.
18201
18407
  !When scheduling calendar events relative to a weekday (e.g. "every Monday"), always run date '+%A' first to confirm today's weekday before calculating the target date
18202
- ---
18408
+
18203
18409
  Keep the event title informative and concise, less than 20 words.
18204
18410
  Place the event details in description.
18205
18411
  Create a one-off event:
@@ -18211,7 +18417,7 @@ Create a repeating event:
18211
18417
  - Add '--repeat <interval>' where interval is like '1day', '2hour', '1week', '1month'.
18212
18418
  - Optionally add '--repeat_stop_date <YYYY-MM-DD>' to stop the recurrence (local date).
18213
18419
  - Example: '${cmdPrefix()} calendar set --event_title "<REPEAT_TASK_TITLE>" --description "<REPEAT_TASK_BODY>" --datetime 2026-04-18T09:00 --repeat 1day --repeat_stop_date 2026-05-18'
18214
- ---
18420
+
18215
18421
  List upcoming events:
18216
18422
  - Run '${cmdPrefix()} calendar list' (defaults: next 30 days, past 0 days).
18217
18423
  - Tune the window with '--future_days <N>' and '--past_days <N>'. Add '--json' for machine-readable output.
@@ -18233,7 +18439,6 @@ Edit an existing event (preserves event id and recurring state):
18233
18439
 
18234
18440
  Delete an event:
18235
18441
  - Run '${cmdPrefix()} calendar delete --event_id <EVENT_ID>'
18236
- ---
18237
18442
  `;
18238
18443
  return content;
18239
18444
  }
@@ -18359,128 +18564,8 @@ function releaseLock(lockPath) {
18359
18564
  } catch {}
18360
18565
  }
18361
18566
 
18362
- // lib/logger.ts
18363
- var LEVELS = {
18364
- debug: 0,
18365
- info: 1,
18366
- warn: 2,
18367
- error: 3,
18368
- silent: 4
18369
- };
18370
- var LABELS = {
18371
- debug: "DEBUG",
18372
- info: "INFO ",
18373
- warn: "WARN ",
18374
- error: "ERROR"
18375
- };
18376
- var COLORS = {
18377
- debug: "\x1B[90m",
18378
- info: "\x1B[36m",
18379
- warn: "\x1B[33m",
18380
- error: "\x1B[31m"
18381
- };
18382
- var RESET = "\x1B[0m";
18383
- var DIM = "\x1B[2m";
18384
- var BOLD = "\x1B[1m";
18385
- function useColor() {
18386
- if (process.env.NO_COLOR !== undefined)
18387
- return false;
18388
- if (process.env.FORCE_COLOR !== undefined)
18389
- return true;
18390
- return process.stdout.isTTY === true;
18391
- }
18392
- function timestamp() {
18393
- const d = new Date;
18394
- const Y = d.getFullYear();
18395
- const M = String(d.getMonth() + 1).padStart(2, "0");
18396
- const D = String(d.getDate()).padStart(2, "0");
18397
- const h = String(d.getHours()).padStart(2, "0");
18398
- const m = String(d.getMinutes()).padStart(2, "0");
18399
- const s = String(d.getSeconds()).padStart(2, "0");
18400
- return `${Y}-${M}-${D} ${h}:${m}:${s}`;
18401
- }
18402
-
18403
- class Logger2 {
18404
- level;
18405
- color;
18406
- module;
18407
- constructor(opts = {}) {
18408
- const envLevel = process.env.ALOOK_LOG_LEVEL;
18409
- this.level = LEVELS[opts.level ?? envLevel ?? "info"];
18410
- this.color = useColor();
18411
- this.module = opts.module;
18412
- }
18413
- setLevel(level) {
18414
- this.level = LEVELS[level];
18415
- }
18416
- child(module) {
18417
- const child = new Logger2({ level: this.levelName(), module });
18418
- return child;
18419
- }
18420
- debug(msg, ...args) {
18421
- this.write("debug", msg, args);
18422
- }
18423
- info(msg, ...args) {
18424
- this.write("info", msg, args);
18425
- }
18426
- warn(msg, ...args) {
18427
- this.write("warn", msg, args);
18428
- }
18429
- error(msg, ...args) {
18430
- this.write("error", msg, args);
18431
- }
18432
- levelName() {
18433
- for (const [name, num] of Object.entries(LEVELS)) {
18434
- if (num === this.level)
18435
- return name;
18436
- }
18437
- return "info";
18438
- }
18439
- write(level, msg, args) {
18440
- if (LEVELS[level] < this.level)
18441
- return;
18442
- const ts = timestamp();
18443
- const label = LABELS[level];
18444
- const mod = this.module ? `[${this.module}]` : "";
18445
- let line;
18446
- if (this.color) {
18447
- const c = COLORS[level];
18448
- const modStr = mod ? ` ${BOLD}${mod}${RESET}` : "";
18449
- line = `${DIM}${ts}${RESET} ${c}${label}${RESET}${modStr} ${msg}`;
18450
- } else {
18451
- const modStr = mod ? ` ${mod}` : "";
18452
- line = `${ts} ${label}${modStr} ${msg}`;
18453
- }
18454
- const dest = level === "error" ? process.stderr : process.stdout;
18455
- dest.write(line + `
18456
- `);
18457
- for (const a of args) {
18458
- if (a instanceof Error) {
18459
- dest.write(` ${a.message}
18460
- `);
18461
- if (a.stack && this.level <= LEVELS.debug) {
18462
- dest.write(` ${a.stack}
18463
- `);
18464
- }
18465
- } else if (a !== null && typeof a === "object") {
18466
- const pairs = Object.entries(a).map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : v}`).join(" ");
18467
- if (pairs)
18468
- dest.write(` ${pairs}
18469
- `);
18470
- } else if (a !== undefined) {
18471
- dest.write(` ${String(a)}
18472
- `);
18473
- }
18474
- }
18475
- }
18476
- }
18477
- function createLogger2(opts) {
18478
- return new Logger2(opts);
18479
- }
18480
- var log = createLogger2();
18481
-
18482
18567
  // daemon/execenv/timeline.ts
18483
- var log2 = createLogger2({ module: "timeline" });
18568
+ var log3 = createLogger2({ module: "timeline" });
18484
18569
  function readJsonl(filePath) {
18485
18570
  let content;
18486
18571
  try {
@@ -18518,8 +18603,7 @@ function recentFilenames(maxDays) {
18518
18603
  }
18519
18604
  return filenames;
18520
18605
  }
18521
- function localISOString() {
18522
- const now = new Date;
18606
+ function localISOString(now = new Date) {
18523
18607
  const tzOffset = -now.getTimezoneOffset();
18524
18608
  const sign = tzOffset >= 0 ? "+" : "-";
18525
18609
  const absOffset = Math.abs(tzOffset);
@@ -18550,7 +18634,7 @@ async function initEntryAsync(timelineDir, entry) {
18550
18634
  acquired = acquireLock(lockPath);
18551
18635
  }
18552
18636
  if (!acquired) {
18553
- log2.debug(`Timeline initEntry: could not acquire lock for ${filename}`);
18637
+ log3.debug(`Timeline initEntry: could not acquire lock for ${filename}`);
18554
18638
  return;
18555
18639
  }
18556
18640
  try {
@@ -18560,7 +18644,7 @@ async function initEntryAsync(timelineDir, entry) {
18560
18644
  releaseLock(lockPath);
18561
18645
  }
18562
18646
  } catch (err) {
18563
- log2.debug("Timeline initEntry failed", err);
18647
+ log3.debug("Timeline initEntry failed", err);
18564
18648
  }
18565
18649
  }
18566
18650
  function updateEntry(timelineDir, taskId, updater) {
@@ -18570,7 +18654,7 @@ function updateEntry(timelineDir, taskId, updater) {
18570
18654
  try {
18571
18655
  const acquired = acquireLock(lockPath);
18572
18656
  if (!acquired) {
18573
- log2.debug(`Timeline updateEntry: lock held for ${filename}, skipping`);
18657
+ log3.debug(`Timeline updateEntry: lock held for ${filename}, skipping`);
18574
18658
  continue;
18575
18659
  }
18576
18660
  try {
@@ -18603,10 +18687,10 @@ function updateEntry(timelineDir, taskId, updater) {
18603
18687
  releaseLock(lockPath);
18604
18688
  }
18605
18689
  } catch (err) {
18606
- log2.debug(`Timeline updateEntry failed for ${filename}`, err);
18690
+ log3.debug(`Timeline updateEntry failed for ${filename}`, err);
18607
18691
  }
18608
18692
  }
18609
- log2.debug(`Timeline updateEntry: task_id ${taskId} not found in last 7 days`);
18693
+ log3.debug(`Timeline updateEntry: task_id ${taskId} not found in last 7 days`);
18610
18694
  }
18611
18695
  function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider, contextKey, detailedLog) {
18612
18696
  return {
@@ -18644,7 +18728,7 @@ function findResumableSessionByContextKey(timelineDir, contextKey, provider) {
18644
18728
  // daemon/execenv/steering.ts
18645
18729
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3, unlinkSync as unlinkSync2, readdirSync, statSync as statSync2 } from "fs";
18646
18730
  import { join as join5 } from "path";
18647
- var log3 = createLogger2({ module: "steering" });
18731
+ var log4 = createLogger2({ module: "steering" });
18648
18732
  var INTENT_DIR_NAME = ".kill_intents";
18649
18733
  var INTENT_STALE_MS = 10 * 60 * 1000;
18650
18734
  function intentFilePath(baseDir, taskId) {
@@ -18675,7 +18759,13 @@ function buildDmNotice(name, email3) {
18675
18759
  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.`;
18676
18760
  }
18677
18761
  function buildPrompt(task, attachments) {
18678
- const obj = { type: task.type, instruction: task.prompt };
18762
+ const createdAt = new Date(task.createdAt);
18763
+ const receivedAt = Number.isNaN(createdAt.getTime()) ? localISOString() : localISOString(createdAt);
18764
+ const obj = {
18765
+ type: task.type,
18766
+ received_at: receivedAt,
18767
+ instruction: task.prompt
18768
+ };
18679
18769
  if (task.type === "user_dm_message") {
18680
18770
  obj.notice = DM_RESPONSE_NOTICE;
18681
18771
  }
@@ -18738,7 +18828,7 @@ function buildPrompt(task, attachments) {
18738
18828
  }
18739
18829
 
18740
18830
  // daemon/session-runner.ts
18741
- var log4 = createLogger2({ module: "session-runner" });
18831
+ var log5 = createLogger2({ module: "session-runner" });
18742
18832
  var ATTACHMENTS_BASE = tempDir("alook-attachments");
18743
18833
  async function writeMarkerFile(workspacesRoot, marker) {
18744
18834
  const dir = path.join(workspacesRoot, ".pending_completions");
@@ -18786,20 +18876,20 @@ async function reportToServer(fn, markerData, workspacesRoot) {
18786
18876
  } catch (e) {
18787
18877
  lastErr = e;
18788
18878
  if (isClientError(e)) {
18789
- log4.info(`server report for task ${markerData.taskId}: task already in terminal state (${e})`);
18879
+ log5.info(`server report for task ${markerData.taskId}: task already in terminal state (${e})`);
18790
18880
  return;
18791
18881
  }
18792
18882
  if (attempt < RETRY_DELAYS.length && isRetryableError(e)) {
18793
- log4.debug(`server report attempt ${attempt + 1} failed for task ${markerData.taskId}, retrying in ${RETRY_DELAYS[attempt]}ms`);
18883
+ log5.debug(`server report attempt ${attempt + 1} failed for task ${markerData.taskId}, retrying in ${RETRY_DELAYS[attempt]}ms`);
18794
18884
  await new Promise((r) => setTimeout(r, RETRY_DELAYS[attempt]));
18795
18885
  }
18796
18886
  }
18797
18887
  }
18798
- log4.warn(`server report failed for task ${markerData.taskId} after retries, writing marker: ${lastErr}`);
18888
+ log5.warn(`server report failed for task ${markerData.taskId} after retries, writing marker: ${lastErr}`);
18799
18889
  try {
18800
18890
  await writeMarkerFile(workspacesRoot, markerData);
18801
18891
  } catch (writeErr) {
18802
- log4.error(`marker write also failed for task ${markerData.taskId}: ${writeErr}`);
18892
+ log5.error(`marker write also failed for task ${markerData.taskId}: ${writeErr}`);
18803
18893
  }
18804
18894
  }
18805
18895
  function sanitizeFilename(name) {
@@ -18830,7 +18920,7 @@ async function downloadAttachments(client, token, workspaceId, taskId, attachmen
18830
18920
  }
18831
18921
  async function runSession(input) {
18832
18922
  const { task, provider, cliPath, model, serverURL, token, workspacesRoot, agentTimeout, messageInactivityTimeout } = input;
18833
- log4.info(`starting (task=${task.id}, type=${task.type}, agent=${task.agentId}, provider=${provider}, model=${model || "default"})`);
18923
+ log5.info(`starting (task=${task.id}, type=${task.type}, agent=${task.agentId}, provider=${provider}, model=${model || "default"})`);
18834
18924
  const client = new DaemonClient(serverURL);
18835
18925
  const backend = createBackend(provider, cliPath);
18836
18926
  const agentBaseDir = path.join(workspacesRoot, task.workspaceId, task.agentId, "workdir");
@@ -18839,11 +18929,37 @@ async function runSession(input) {
18839
18929
  await initEntryAsync(timelineDir, createTimelineEntry(task.id, task.prompt, task.type, undefined, process.pid, provider, task.contextKey, input.logFilePath));
18840
18930
  const { workDir, env } = prepare({ workspacesRoot, token }, task);
18841
18931
  let killed = false;
18842
- const earlyOnKill = async () => {
18932
+ let agentPid = undefined;
18933
+ let flushTimer = undefined;
18934
+ const pendingMessages = [];
18935
+ let seq = 0;
18936
+ let toolCount = 0;
18937
+ const BATCH_SIZE = Number(process.env.ALOOK_MESSAGE_BATCH_SIZE) || 20;
18938
+ const FLUSH_INTERVAL_MS = Number(process.env.ALOOK_MESSAGE_FLUSH_INTERVAL_MS) || 100;
18939
+ const flushMessages = async () => {
18940
+ if (pendingMessages.length === 0)
18941
+ return;
18942
+ const batch = pendingMessages.splice(0);
18943
+ try {
18944
+ await client.reportMessages(token, task.id, batch);
18945
+ } catch (e) {
18946
+ log5.debug("message report failed", e);
18947
+ }
18948
+ };
18949
+ const onKill = async () => {
18843
18950
  if (killed)
18844
18951
  return;
18845
18952
  killed = true;
18846
- log4.info(`killed by signal (messages=0, tools=0)`);
18953
+ log5.info(`killed by signal (messages=${seq}, tools=${toolCount})`);
18954
+ if (agentPid !== undefined) {
18955
+ log5.info(`killing inner agent group (pid=${agentPid})`);
18956
+ await killProcessTree(agentPid);
18957
+ }
18958
+ if (flushTimer)
18959
+ clearInterval(flushTimer);
18960
+ try {
18961
+ await flushMessages();
18962
+ } catch {}
18847
18963
  await cleanupAttachments(task.id);
18848
18964
  const intent = readKillIntent(agentBaseDir, task.id);
18849
18965
  clearKillIntent(agentBaseDir, task.id);
@@ -18874,34 +18990,36 @@ async function runSession(input) {
18874
18990
  }
18875
18991
  process.exit(1);
18876
18992
  };
18877
- process.on("SIGTERM", earlyOnKill);
18878
- process.on("SIGINT", earlyOnKill);
18993
+ process.on("SIGTERM", onKill);
18994
+ process.on("SIGINT", onKill);
18879
18995
  const attachmentIds = task.context?.attachment_ids ?? [];
18880
18996
  let attachments;
18881
18997
  if (attachmentIds.length > 0) {
18882
- log4.info(`downloading ${attachmentIds.length} attachment(s)`);
18998
+ log5.info(`downloading ${attachmentIds.length} attachment(s)`);
18883
18999
  try {
18884
19000
  attachments = await downloadAttachments(client, token, task.workspaceId, task.id, attachmentIds);
18885
- log4.info(`attachments ready (${attachments.length} file(s))`);
19001
+ log5.info(`attachments ready (${attachments.length} file(s))`);
18886
19002
  } catch (e) {
18887
19003
  await cleanupAttachments(task.id);
18888
19004
  const errMsg = `failed to download attachments: ${e}`;
18889
- log4.error(errMsg);
19005
+ log5.error(errMsg);
18890
19006
  updateEntry(timelineDir, task.id, (entry) => {
18891
19007
  entry.pid = null;
18892
19008
  entry.status = "failed";
18893
19009
  entry.errmsg = errMsg;
18894
19010
  });
18895
19011
  await reportToServer(() => client.failTask(token, task.id, errMsg), { taskId: task.id, type: "fail", payload: { error: errMsg }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
18896
- process.removeListener("SIGTERM", earlyOnKill);
18897
- process.removeListener("SIGINT", earlyOnKill);
19012
+ process.removeListener("SIGTERM", onKill);
19013
+ process.removeListener("SIGINT", onKill);
18898
19014
  return;
18899
19015
  }
18900
19016
  }
19017
+ if (killed)
19018
+ return;
18901
19019
  const prompt = buildPrompt(task, attachments);
18902
19020
  const resumeSessionId = task.contextKey ? findResumableSessionByContextKey(timelineDir, task.contextKey, provider) ?? undefined : undefined;
18903
19021
  if (resumeSessionId) {
18904
- log4.info(`resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
19022
+ log5.info(`resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
18905
19023
  }
18906
19024
  const session2 = backend.execute(prompt, {
18907
19025
  cwd: workDir,
@@ -18910,77 +19028,21 @@ async function runSession(input) {
18910
19028
  timeout: agentTimeout,
18911
19029
  resumeSessionId
18912
19030
  });
18913
- const agentPid = session2.pid;
19031
+ agentPid = session2.pid;
19032
+ if (killed) {
19033
+ if (agentPid !== undefined) {
19034
+ log5.info(`kill landed during spawn — reaping inner agent group (pid=${agentPid})`);
19035
+ await killProcessTree(agentPid);
19036
+ }
19037
+ process.exit(1);
19038
+ }
18914
19039
  const earlySessionId = await session2.sessionId;
18915
- log4.info(`agent started (pid=${agentPid ?? "unknown"}, session=${earlySessionId})`);
18916
- log4.info(JSON.stringify({ role: "user", type: "text", content: prompt }));
19040
+ log5.info(`agent started (pid=${agentPid ?? "unknown"}, session=${earlySessionId})`);
19041
+ log5.info(JSON.stringify({ role: "user", type: "text", content: prompt }));
18917
19042
  updateEntry(timelineDir, task.id, (entry) => {
18918
19043
  entry.session_id = earlySessionId || null;
18919
19044
  });
18920
- const pendingMessages = [];
18921
- let seq = 0;
18922
- let toolCount = 0;
18923
- const BATCH_SIZE = Number(process.env.ALOOK_MESSAGE_BATCH_SIZE) || 20;
18924
- const FLUSH_INTERVAL_MS = Number(process.env.ALOOK_MESSAGE_FLUSH_INTERVAL_MS) || 100;
18925
- const flushMessages = async () => {
18926
- if (pendingMessages.length === 0)
18927
- return;
18928
- const batch = pendingMessages.splice(0);
18929
- try {
18930
- await client.reportMessages(token, task.id, batch);
18931
- } catch (e) {
18932
- log4.debug("message report failed", e);
18933
- }
18934
- };
18935
- const flushTimer = setInterval(flushMessages, FLUSH_INTERVAL_MS);
18936
- process.removeListener("SIGTERM", earlyOnKill);
18937
- process.removeListener("SIGINT", earlyOnKill);
18938
- const onKill = async () => {
18939
- if (killed)
18940
- return;
18941
- killed = true;
18942
- log4.info(`killed by signal (messages=${seq}, tools=${toolCount})`);
18943
- if (agentPid) {
18944
- try {
18945
- process.kill(agentPid, "SIGTERM");
18946
- } catch {}
18947
- }
18948
- clearInterval(flushTimer);
18949
- try {
18950
- await flushMessages();
18951
- } catch {}
18952
- await cleanupAttachments(task.id);
18953
- const intent = readKillIntent(agentBaseDir, task.id);
18954
- clearKillIntent(agentBaseDir, task.id);
18955
- if (intent?.reason === "superseded") {
18956
- updateEntry(timelineDir, task.id, (entry) => {
18957
- entry.pid = null;
18958
- entry.status = "superseded";
18959
- entry.successor_task_id = intent.successorTaskId ?? null;
18960
- entry.supersede_reason = "superseded by newer task";
18961
- });
18962
- try {
18963
- await client.supersedeTask(token, task.id);
18964
- } catch {}
18965
- } else if (intent?.reason === "cancelled") {
18966
- updateEntry(timelineDir, task.id, (entry) => {
18967
- entry.pid = null;
18968
- entry.status = "cancelled";
18969
- entry.errmsg = "cancelled by user";
18970
- });
18971
- await reportToServer(() => client.failTask(token, task.id, "cancelled by user"), { taskId: task.id, type: "fail", payload: { error: "cancelled by user" }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
18972
- } else {
18973
- updateEntry(timelineDir, task.id, (entry) => {
18974
- entry.pid = null;
18975
- entry.status = "killed";
18976
- entry.errmsg = "killed by signal";
18977
- });
18978
- await reportToServer(() => client.failTask(token, task.id, "killed by signal"), { taskId: task.id, type: "fail", payload: { error: "killed by signal" }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
18979
- }
18980
- process.exit(1);
18981
- };
18982
- process.on("SIGTERM", onKill);
18983
- process.on("SIGINT", onKill);
19045
+ flushTimer = setInterval(flushMessages, FLUSH_INTERVAL_MS);
18984
19046
  const INACTIVITY_TIMEOUT_MS = messageInactivityTimeout ?? 5 * 60 * 1000;
18985
19047
  let inactivityTimedOut = false;
18986
19048
  try {
@@ -18996,11 +19058,9 @@ async function runSession(input) {
18996
19058
  ]) : next);
18997
19059
  if (raceResult === "timeout") {
18998
19060
  inactivityTimedOut = true;
18999
- log4.warn(`message inactivity timeout (${INACTIVITY_TIMEOUT_MS / 1000}s) — killing agent`);
19000
- if (session2.pid) {
19001
- try {
19002
- process.kill(session2.pid, "SIGTERM");
19003
- } catch {}
19061
+ log5.warn(`message inactivity timeout (${INACTIVITY_TIMEOUT_MS / 1000}s) — killing agent`);
19062
+ if (session2.pid !== undefined) {
19063
+ await killProcessTree(session2.pid);
19004
19064
  }
19005
19065
  iter.return?.(undefined);
19006
19066
  break;
@@ -19013,9 +19073,9 @@ async function runSession(input) {
19013
19073
  if (msg.type === "tool-use")
19014
19074
  toolCount++;
19015
19075
  if (msg.type === "tool-result" && msg.output && msg.output.length > 500) {
19016
- log4.info(JSON.stringify({ role: "assistant", ...msg, output: msg.output.slice(0, 500) + `... (${msg.output.length} chars)` }));
19076
+ log5.info(JSON.stringify({ role: "assistant", ...msg, output: msg.output.slice(0, 500) + `... (${msg.output.length} chars)` }));
19017
19077
  } else {
19018
- log4.info(JSON.stringify({ role: "assistant", ...msg }));
19078
+ log5.info(JSON.stringify({ role: "assistant", ...msg }));
19019
19079
  }
19020
19080
  if (msg.type === "status" || msg.type === "log")
19021
19081
  continue;
@@ -19040,14 +19100,11 @@ async function runSession(input) {
19040
19100
  if (!killed)
19041
19101
  await flushMessages();
19042
19102
  } finally {
19043
- clearInterval(flushTimer);
19044
- process.removeListener("SIGTERM", onKill);
19045
- process.removeListener("SIGINT", onKill);
19103
+ if (flushTimer)
19104
+ clearInterval(flushTimer);
19046
19105
  }
19047
19106
  if (killed)
19048
19107
  return;
19049
- process.on("SIGTERM", onKill);
19050
- process.on("SIGINT", onKill);
19051
19108
  const result = await session2.result;
19052
19109
  process.removeListener("SIGTERM", onKill);
19053
19110
  process.removeListener("SIGINT", onKill);
@@ -19079,18 +19136,18 @@ async function runSession(input) {
19079
19136
  body.session_id = result.sessionId;
19080
19137
  await reportToServer(() => client.completeTask(token, task.id, body), { taskId: task.id, type: "complete", payload: body, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
19081
19138
  const dur = (result.durationMs / 1000).toFixed(1);
19082
- log4.info(`completed (duration=${dur}s, messages=${seq}, tools=${toolCount})`);
19139
+ log5.info(`completed (duration=${dur}s, messages=${seq}, tools=${toolCount})`);
19083
19140
  } else {
19084
19141
  const errorMsg = result.error || "agent exited unexpectedly";
19085
19142
  await reportToServer(() => client.failTask(token, task.id, errorMsg), { taskId: task.id, type: "fail", payload: { error: errorMsg }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
19086
19143
  const dur = (result.durationMs / 1000).toFixed(1);
19087
- log4.info(`failed (duration=${dur}s, messages=${seq}, tools=${toolCount}) — ${result.error}`);
19144
+ log5.info(`failed (duration=${dur}s, messages=${seq}, tools=${toolCount}) — ${result.error}`);
19088
19145
  }
19089
19146
  }
19090
19147
  async function main() {
19091
19148
  const encoded = process.argv[2];
19092
19149
  if (!encoded) {
19093
- log4.error("session-runner: missing base64-encoded input argument");
19150
+ log5.error("session-runner: missing base64-encoded input argument");
19094
19151
  process.exit(1);
19095
19152
  }
19096
19153
  let input;
@@ -19098,14 +19155,14 @@ async function main() {
19098
19155
  const json2 = Buffer.from(encoded, "base64").toString("utf-8");
19099
19156
  input = JSON.parse(json2);
19100
19157
  } catch (e) {
19101
- log4.error("session-runner: failed to parse input", e);
19158
+ log5.error("session-runner: failed to parse input", e);
19102
19159
  process.exit(1);
19103
19160
  }
19104
19161
  const client = new DaemonClient(input.serverURL);
19105
19162
  try {
19106
19163
  await runSession(input);
19107
19164
  } catch (e) {
19108
- log4.error(`session-runner: unhandled error for task ${input.task.id}`, e);
19165
+ log5.error(`session-runner: unhandled error for task ${input.task.id}`, e);
19109
19166
  await cleanupAttachments(input.task.id);
19110
19167
  const timelineDir = path.join(input.workspacesRoot, input.task.workspaceId, input.task.agentId, "workdir", ".context_timeline").replace(/\\/g, "/");
19111
19168
  updateEntry(timelineDir, input.task.id, (entry) => {