@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.
- package/dist/index.js +460 -309
- package/dist/meeting-runner.js +1 -2
- package/dist/session-runner.js +365 -308
- package/package.json +1 -1
package/dist/session-runner.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
17977
|
-
|
|
17978
|
-
|
|
17979
|
-
|
|
17980
|
-
###
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
17984
|
-
-
|
|
17985
|
-
|
|
17986
|
-
|
|
17987
|
-
|
|
17988
|
-
|
|
17989
|
-
|
|
17990
|
-
-
|
|
17991
|
-
|
|
17992
|
-
|
|
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
|
-
|
|
17997
|
-
The full context timeline
|
|
17998
|
-
|
|
17999
|
-
-
|
|
18000
|
-
-
|
|
18001
|
-
-
|
|
18002
|
-
-
|
|
18003
|
-
-
|
|
18004
|
-
-
|
|
18005
|
-
-
|
|
18006
|
-
-
|
|
18007
|
-
-
|
|
18008
|
-
-
|
|
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
|
-
-
|
|
18015
|
-
- Act and
|
|
18016
|
-
- Don't report your agent id and workspace id when not asked
|
|
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
|
|
18022
|
-
- Use grep tool to search
|
|
18023
|
-
-
|
|
18024
|
-
- When
|
|
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
|
|
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
|
-
|
|
18061
|
-
|
|
18062
|
-
|
|
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
|
-
|
|
18096
|
-
|
|
18097
|
-
|
|
18098
|
-
|
|
18099
|
-
|
|
18100
|
-
|
|
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
|
-
${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18690
|
+
log3.debug(`Timeline updateEntry failed for ${filename}`, err);
|
|
18607
18691
|
}
|
|
18608
18692
|
}
|
|
18609
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
18878
|
-
process.on("SIGINT",
|
|
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
|
-
|
|
18998
|
+
log5.info(`downloading ${attachmentIds.length} attachment(s)`);
|
|
18883
18999
|
try {
|
|
18884
19000
|
attachments = await downloadAttachments(client, token, task.workspaceId, task.id, attachmentIds);
|
|
18885
|
-
|
|
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
|
-
|
|
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",
|
|
18897
|
-
process.removeListener("SIGINT",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18916
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19000
|
-
if (session2.pid) {
|
|
19001
|
-
|
|
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
|
-
|
|
19076
|
+
log5.info(JSON.stringify({ role: "assistant", ...msg, output: msg.output.slice(0, 500) + `... (${msg.output.length} chars)` }));
|
|
19017
19077
|
} else {
|
|
19018
|
-
|
|
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
|
-
|
|
19044
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) => {
|