@alook/cli 0.0.25 → 0.0.27
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 +269 -74
- package/dist/meeting-runner.js +15 -3
- package/dist/session-runner.js +171 -68
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13908,7 +13908,8 @@ var TaskAgentDataApiSchema = exports_external.object({
|
|
|
13908
13908
|
runtime_config: exports_external.record(exports_external.string(), exports_external.unknown()).default({}),
|
|
13909
13909
|
email_handle: exports_external.string().nullable().optional(),
|
|
13910
13910
|
email_addresses: exports_external.array(exports_external.string()).default([]),
|
|
13911
|
-
user_email: exports_external.string().nullable().optional()
|
|
13911
|
+
user_email: exports_external.string().nullable().optional(),
|
|
13912
|
+
user_name: exports_external.string().nullable().optional()
|
|
13912
13913
|
});
|
|
13913
13914
|
var TaskApiBaseSchema = exports_external.object({
|
|
13914
13915
|
id: exports_external.string(),
|
|
@@ -13949,12 +13950,20 @@ var FileRequestItemSchema = exports_external.object({
|
|
|
13949
13950
|
request_type: exports_external.enum(["tree", "read"]),
|
|
13950
13951
|
path: exports_external.string()
|
|
13951
13952
|
});
|
|
13953
|
+
var PollMeetingItemSchema = exports_external.object({
|
|
13954
|
+
id: exports_external.string(),
|
|
13955
|
+
meeting_url: exports_external.string(),
|
|
13956
|
+
participants: exports_external.array(exports_external.string()),
|
|
13957
|
+
workspace_id: exports_external.string(),
|
|
13958
|
+
agent_name: exports_external.string()
|
|
13959
|
+
});
|
|
13952
13960
|
var PollResponseSchema = exports_external.object({
|
|
13953
13961
|
tasks: exports_external.array(TaskApiSchema),
|
|
13954
13962
|
evicted: exports_external.boolean().optional(),
|
|
13955
13963
|
pending_update: exports_external.object({ version: exports_external.string() }).optional(),
|
|
13956
13964
|
pending_rescan: exports_external.boolean().optional(),
|
|
13957
|
-
file_requests: exports_external.array(FileRequestItemSchema).optional()
|
|
13965
|
+
file_requests: exports_external.array(FileRequestItemSchema).optional(),
|
|
13966
|
+
meetings: exports_external.array(PollMeetingItemSchema).optional()
|
|
13958
13967
|
});
|
|
13959
13968
|
var RegisterResponseSchema = exports_external.object({
|
|
13960
13969
|
runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
|
|
@@ -14102,10 +14111,11 @@ var SendEmailRequestSchema = exports_external.object({
|
|
|
14102
14111
|
references: exports_external.string().optional(),
|
|
14103
14112
|
attachments: exports_external.array(EmailAttachmentSchema).optional(),
|
|
14104
14113
|
customAccountId: exports_external.string().optional(),
|
|
14105
|
-
from: exports_external.string().email().optional()
|
|
14114
|
+
from: exports_external.string().email().optional(),
|
|
14115
|
+
conversationId: exports_external.string().optional()
|
|
14106
14116
|
});
|
|
14107
14117
|
var UpdateEmailStatusRequestSchema = exports_external.object({
|
|
14108
|
-
status: exports_external.enum(["unread", "read", "archived"])
|
|
14118
|
+
status: exports_external.enum(["unread", "read", "archived", "sent"])
|
|
14109
14119
|
});
|
|
14110
14120
|
var MeetingInfoSchema = exports_external.object({
|
|
14111
14121
|
title: exports_external.string(),
|
|
@@ -15942,6 +15952,15 @@ var machineToken = sqliteTable("machine_token", {
|
|
|
15942
15952
|
lastUsedAt: text("last_used_at"),
|
|
15943
15953
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15944
15954
|
}, (t) => [index("idx_machine_token").on(t.token)]);
|
|
15955
|
+
var conversationMap = sqliteTable("conversation_map", {
|
|
15956
|
+
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15957
|
+
key: text("key").notNull(),
|
|
15958
|
+
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
15959
|
+
conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
|
|
15960
|
+
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15961
|
+
}, (t) => [
|
|
15962
|
+
unique("conversation_map_key_workspace").on(t.key, t.workspaceId)
|
|
15963
|
+
]);
|
|
15945
15964
|
var workspaceFileRequest = sqliteTable("workspace_file_request", {
|
|
15946
15965
|
id: text("id").primaryKey().$defaultFn(() => "wfr_" + nanoid3()),
|
|
15947
15966
|
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
@@ -16045,7 +16064,8 @@ class DaemonClient {
|
|
|
16045
16064
|
evicted: resp.evicted ?? false,
|
|
16046
16065
|
pending_update: resp.pending_update,
|
|
16047
16066
|
pending_rescan: resp.pending_rescan,
|
|
16048
|
-
file_requests: resp.file_requests
|
|
16067
|
+
file_requests: resp.file_requests,
|
|
16068
|
+
meetings: resp.meetings
|
|
16049
16069
|
};
|
|
16050
16070
|
}
|
|
16051
16071
|
startTask(token, taskId) {
|
|
@@ -16095,15 +16115,6 @@ class DaemonClient {
|
|
|
16095
16115
|
reportFileData(token, body) {
|
|
16096
16116
|
return this.request("POST", "/api/daemon/workspace/report", token, body);
|
|
16097
16117
|
}
|
|
16098
|
-
async claimMeetings(token, daemonId) {
|
|
16099
|
-
const raw = await this.request("POST", "/api/daemon/meetings/claim", token, { daemon_id: daemonId });
|
|
16100
|
-
return raw.map((m) => ({
|
|
16101
|
-
id: m.id,
|
|
16102
|
-
meetingUrl: m.meeting_url,
|
|
16103
|
-
participants: m.participants,
|
|
16104
|
-
workspaceId: m.workspace_id
|
|
16105
|
-
}));
|
|
16106
|
-
}
|
|
16107
16118
|
}
|
|
16108
16119
|
|
|
16109
16120
|
// daemon/config.ts
|
|
@@ -16276,7 +16287,7 @@ function fromApiTask(api2) {
|
|
|
16276
16287
|
type: api2.type,
|
|
16277
16288
|
contextKey: api2.context_key ?? null,
|
|
16278
16289
|
context: api2.context ?? undefined,
|
|
16279
|
-
agent: api2.agent ? { name: api2.agent.name, instructions: api2.agent.instructions, emailHandle: api2.agent.email_handle ?? undefined, emailAddresses: api2.agent.email_addresses ?? [], userEmail: api2.agent.user_email ?? undefined, runtimeConfig: api2.agent.runtime_config ?? undefined } : undefined,
|
|
16290
|
+
agent: api2.agent ? { name: api2.agent.name, instructions: api2.agent.instructions, emailHandle: api2.agent.email_handle ?? undefined, emailAddresses: api2.agent.email_addresses ?? [], userEmail: api2.agent.user_email ?? undefined, userName: api2.agent.user_name ?? undefined, runtimeConfig: api2.agent.runtime_config ?? undefined } : undefined,
|
|
16280
16291
|
sender: api2.sender ? { name: api2.sender.name, email: api2.sender.email, isOwner: api2.sender.is_owner } : undefined,
|
|
16281
16292
|
repos: undefined,
|
|
16282
16293
|
createdAt: api2.created_at
|
|
@@ -16583,8 +16594,7 @@ function recentFilenames(maxDays) {
|
|
|
16583
16594
|
}
|
|
16584
16595
|
return filenames;
|
|
16585
16596
|
}
|
|
16586
|
-
var
|
|
16587
|
-
var EMAIL_RESUME_MAX_AGE_MS = 48 * 60 * 60 * 1000;
|
|
16597
|
+
var RESUME_MAX_AGE_MS = 72 * 60 * 60 * 1000;
|
|
16588
16598
|
function findRunningPidByTaskId(timelineDir, taskId) {
|
|
16589
16599
|
for (const filename of recentFilenames(7)) {
|
|
16590
16600
|
const entries = readJsonl(join4(timelineDir, filename));
|
|
@@ -16669,7 +16679,7 @@ function releaseSteeringLock(baseDir, contextKey) {
|
|
|
16669
16679
|
|
|
16670
16680
|
// daemon/workspace-files.ts
|
|
16671
16681
|
import { readdir, stat, readFile } from "fs/promises";
|
|
16672
|
-
import { join as join6, resolve, extname, relative } from "path";
|
|
16682
|
+
import { join as join6, resolve, extname, relative, sep } from "path";
|
|
16673
16683
|
var SKIP_DIRS = new Set([".git", "node_modules", ".next", ".wrangler", "__pycache__", ".venv"]);
|
|
16674
16684
|
var TEXT_EXTENSIONS = new Set([
|
|
16675
16685
|
".md",
|
|
@@ -16755,24 +16765,61 @@ async function readFileContent(filePath) {
|
|
|
16755
16765
|
}
|
|
16756
16766
|
function validatePath(agentWorkdir, requestedPath) {
|
|
16757
16767
|
const resolved = resolve(agentWorkdir, requestedPath);
|
|
16758
|
-
if (resolved !== agentWorkdir && !resolved.startsWith(agentWorkdir +
|
|
16768
|
+
if (resolved !== agentWorkdir && !resolved.startsWith(agentWorkdir + sep))
|
|
16759
16769
|
return null;
|
|
16760
16770
|
return resolved;
|
|
16761
16771
|
}
|
|
16762
16772
|
|
|
16773
|
+
// lib/shell-env.ts
|
|
16774
|
+
import { execSync as execSync3 } from "child_process";
|
|
16775
|
+
|
|
16776
|
+
// lib/platform.ts
|
|
16777
|
+
import { tmpdir } from "os";
|
|
16778
|
+
import { join as join7, sep as sep2 } from "path";
|
|
16779
|
+
var isWindows = process.platform === "win32";
|
|
16780
|
+
function tempDir(subdir) {
|
|
16781
|
+
return join7(tmpdir(), subdir);
|
|
16782
|
+
}
|
|
16783
|
+
|
|
16784
|
+
// lib/shell-env.ts
|
|
16785
|
+
function resolveLoginShellEnv() {
|
|
16786
|
+
if (isWindows) {
|
|
16787
|
+
return { ...process.env };
|
|
16788
|
+
}
|
|
16789
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
16790
|
+
try {
|
|
16791
|
+
const output = execSync3(`${shell} -lc 'env'`, {
|
|
16792
|
+
encoding: "utf-8",
|
|
16793
|
+
timeout: 5000,
|
|
16794
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
16795
|
+
});
|
|
16796
|
+
const env = {};
|
|
16797
|
+
for (const line of output.split(`
|
|
16798
|
+
`)) {
|
|
16799
|
+
const idx = line.indexOf("=");
|
|
16800
|
+
if (idx > 0) {
|
|
16801
|
+
env[line.slice(0, idx)] = line.slice(idx + 1);
|
|
16802
|
+
}
|
|
16803
|
+
}
|
|
16804
|
+
if (env.PATH)
|
|
16805
|
+
return env;
|
|
16806
|
+
} catch {}
|
|
16807
|
+
return { ...process.env };
|
|
16808
|
+
}
|
|
16809
|
+
|
|
16763
16810
|
// daemon/daemon.ts
|
|
16764
16811
|
import { existsSync, mkdirSync as mkdirSync5, openSync, closeSync, readdirSync as readdirSync2, statSync as statSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
16765
16812
|
import { readdir as readdir2, readFile as readFile2, unlink, stat as fsStat } from "fs/promises";
|
|
16766
|
-
import { execSync as
|
|
16813
|
+
import { execSync as execSync4, spawn as spawn2 } from "child_process";
|
|
16767
16814
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
16768
|
-
import { dirname as dirname3, join as
|
|
16815
|
+
import { dirname as dirname3, join as join8 } from "path";
|
|
16769
16816
|
var _dir = dirname3(fileURLToPath2(import.meta.url));
|
|
16770
|
-
var sessionRunnerPath = existsSync(
|
|
16771
|
-
var meetingRunnerPath = existsSync(
|
|
16817
|
+
var sessionRunnerPath = existsSync(join8(_dir, "session-runner.js")) ? join8(_dir, "session-runner.js") : join8(_dir, "session-runner.ts");
|
|
16818
|
+
var meetingRunnerPath = existsSync(join8(_dir, "meeting-runner.js")) ? join8(_dir, "meeting-runner.js") : join8(_dir, "meeting-runner.ts");
|
|
16772
16819
|
function isCommandAvailable2(cmd) {
|
|
16773
16820
|
try {
|
|
16774
16821
|
const check2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
16775
|
-
|
|
16822
|
+
execSync4(check2, { stdio: "ignore" });
|
|
16776
16823
|
return true;
|
|
16777
16824
|
} catch {
|
|
16778
16825
|
return false;
|
|
@@ -16790,7 +16837,7 @@ function pruneSessionRunnerLogs() {
|
|
|
16790
16837
|
if (entries.length <= MAX_SESSION_RUNNER_LOGS)
|
|
16791
16838
|
return;
|
|
16792
16839
|
const withMtime = entries.map((name) => {
|
|
16793
|
-
const full =
|
|
16840
|
+
const full = join8(logDir, name);
|
|
16794
16841
|
try {
|
|
16795
16842
|
return { name, mtime: statSync3(full).mtimeMs };
|
|
16796
16843
|
} catch {
|
|
@@ -16800,7 +16847,7 @@ function pruneSessionRunnerLogs() {
|
|
|
16800
16847
|
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
16801
16848
|
for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
|
|
16802
16849
|
try {
|
|
16803
|
-
unlinkSync4(
|
|
16850
|
+
unlinkSync4(join8(logDir, entry.name));
|
|
16804
16851
|
} catch {}
|
|
16805
16852
|
}
|
|
16806
16853
|
}
|
|
@@ -16841,7 +16888,7 @@ function isValidMarker(data) {
|
|
|
16841
16888
|
var MARKER_STALE_MS = 24 * 60 * 60 * 1000;
|
|
16842
16889
|
var TMP_STALE_MS = 60 * 60 * 1000;
|
|
16843
16890
|
async function reconcilePendingCompletions(workspacesRoot) {
|
|
16844
|
-
const dir =
|
|
16891
|
+
const dir = join8(workspacesRoot, ".pending_completions");
|
|
16845
16892
|
let entries;
|
|
16846
16893
|
try {
|
|
16847
16894
|
entries = await readdir2(dir);
|
|
@@ -16852,15 +16899,15 @@ async function reconcilePendingCompletions(workspacesRoot) {
|
|
|
16852
16899
|
if (!name.endsWith(".tmp"))
|
|
16853
16900
|
continue;
|
|
16854
16901
|
try {
|
|
16855
|
-
const s = await fsStat(
|
|
16902
|
+
const s = await fsStat(join8(dir, name));
|
|
16856
16903
|
if (Date.now() - s.mtimeMs > TMP_STALE_MS) {
|
|
16857
|
-
await unlink(
|
|
16904
|
+
await unlink(join8(dir, name));
|
|
16858
16905
|
}
|
|
16859
16906
|
} catch {}
|
|
16860
16907
|
}
|
|
16861
16908
|
const jsonFiles = entries.filter((f) => f.endsWith(".json"));
|
|
16862
16909
|
for (const name of jsonFiles) {
|
|
16863
|
-
const filePath =
|
|
16910
|
+
const filePath = join8(dir, name);
|
|
16864
16911
|
try {
|
|
16865
16912
|
let raw;
|
|
16866
16913
|
try {
|
|
@@ -17070,7 +17117,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17070
17117
|
await new Promise((r) => setTimeout(r, staggerMs));
|
|
17071
17118
|
}
|
|
17072
17119
|
try {
|
|
17073
|
-
const { tasks: apiTasks, evicted, pending_update, pending_rescan, file_requests } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
|
|
17120
|
+
const { tasks: apiTasks, evicted, pending_update, pending_rescan, file_requests, meetings } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
|
|
17074
17121
|
if (evicted) {
|
|
17075
17122
|
evictedIds.push(ws.workspaceId);
|
|
17076
17123
|
continue;
|
|
@@ -17097,6 +17144,19 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17097
17144
|
handleFileRequest(client, config2, ws.workspaceId, req, ws.token).catch((e) => log.debug("File request error", e));
|
|
17098
17145
|
}
|
|
17099
17146
|
}
|
|
17147
|
+
if (meetings) {
|
|
17148
|
+
for (const m of meetings) {
|
|
17149
|
+
spawnMeetingRunner({
|
|
17150
|
+
meetingId: m.id,
|
|
17151
|
+
meetingUrl: m.meeting_url,
|
|
17152
|
+
participants: m.participants,
|
|
17153
|
+
workspaceId: m.workspace_id,
|
|
17154
|
+
callbackUrl: config2.serverURL,
|
|
17155
|
+
authToken: ws.token,
|
|
17156
|
+
agentName: m.agent_name
|
|
17157
|
+
});
|
|
17158
|
+
}
|
|
17159
|
+
}
|
|
17100
17160
|
} catch (e) {
|
|
17101
17161
|
if (e instanceof Error && e.message.startsWith("HTTP 401")) {
|
|
17102
17162
|
log.warn(`Workspace ${ws.workspaceId} poll returned 401 — will retry next cycle`);
|
|
@@ -17108,23 +17168,6 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17108
17168
|
for (const id of evictedIds) {
|
|
17109
17169
|
evictWorkspace(id);
|
|
17110
17170
|
}
|
|
17111
|
-
for (const ws of workspaceStates) {
|
|
17112
|
-
try {
|
|
17113
|
-
const meetings = await client.claimMeetings(ws.token, config2.daemonId);
|
|
17114
|
-
for (const m of meetings) {
|
|
17115
|
-
spawnMeetingRunner({
|
|
17116
|
-
meetingId: m.id,
|
|
17117
|
-
meetingUrl: m.meetingUrl,
|
|
17118
|
-
participants: m.participants,
|
|
17119
|
-
workspaceId: m.workspaceId,
|
|
17120
|
-
callbackUrl: config2.serverURL,
|
|
17121
|
-
authToken: ws.token
|
|
17122
|
-
});
|
|
17123
|
-
}
|
|
17124
|
-
} catch (e) {
|
|
17125
|
-
log.debug("Meeting claim error", e);
|
|
17126
|
-
}
|
|
17127
|
-
}
|
|
17128
17171
|
try {
|
|
17129
17172
|
await reconcilePendingCompletions(config2.workspacesRoot);
|
|
17130
17173
|
} catch (e) {
|
|
@@ -17174,7 +17217,8 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17174
17217
|
}
|
|
17175
17218
|
const child = spawn2(process.execPath, args, {
|
|
17176
17219
|
detached: true,
|
|
17177
|
-
stdio: logFd != null ? ["ignore", logFd, logFd] : ["ignore", "ignore", "ignore"]
|
|
17220
|
+
stdio: logFd != null ? ["ignore", logFd, logFd] : ["ignore", "ignore", "ignore"],
|
|
17221
|
+
env: resolveLoginShellEnv()
|
|
17178
17222
|
});
|
|
17179
17223
|
child.unref();
|
|
17180
17224
|
if (logFd != null)
|
|
@@ -17192,7 +17236,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17192
17236
|
function spawnSessionRunner(input) {
|
|
17193
17237
|
const logDir = sessionRunnerLogDir();
|
|
17194
17238
|
mkdirSync5(logDir, { recursive: true });
|
|
17195
|
-
const logFilePath =
|
|
17239
|
+
const logFilePath = join8(logDir, `${input.task.id}.log`);
|
|
17196
17240
|
input.logFilePath = logFilePath;
|
|
17197
17241
|
const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
|
|
17198
17242
|
let fd;
|
|
@@ -17213,7 +17257,7 @@ function spawnSessionRunner(input) {
|
|
|
17213
17257
|
function spawnMeetingRunner(input) {
|
|
17214
17258
|
const logDir = sessionRunnerLogDir();
|
|
17215
17259
|
mkdirSync5(logDir, { recursive: true });
|
|
17216
|
-
const logFilePath =
|
|
17260
|
+
const logFilePath = join8(logDir, `meeting-${input.meetingId}.log`);
|
|
17217
17261
|
const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
|
|
17218
17262
|
let fd;
|
|
17219
17263
|
try {
|
|
@@ -17232,7 +17276,7 @@ function spawnMeetingRunner(input) {
|
|
|
17232
17276
|
return child;
|
|
17233
17277
|
}
|
|
17234
17278
|
async function handleFileRequest(client, config2, workspaceId, req, token) {
|
|
17235
|
-
const agentWorkdir =
|
|
17279
|
+
const agentWorkdir = join8(config2.workspacesRoot, workspaceId, req.agent_id, "workdir");
|
|
17236
17280
|
const resolved = validatePath(agentWorkdir, req.path);
|
|
17237
17281
|
if (!resolved) {
|
|
17238
17282
|
await client.reportFileData(token, { request_id: req.id, error: "invalid path", path: req.path });
|
|
@@ -17263,9 +17307,18 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
17263
17307
|
activeTasks.delete(task.id);
|
|
17264
17308
|
return;
|
|
17265
17309
|
}
|
|
17266
|
-
const agentBaseDir =
|
|
17267
|
-
const timelineDir =
|
|
17268
|
-
const
|
|
17310
|
+
const agentBaseDir = join8(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
17311
|
+
const timelineDir = join8(agentBaseDir, ".context_timeline");
|
|
17312
|
+
const MAX_WAIT_MS = Number(process.env.ALOOK_KILL_TASK_MAX_WAIT_MS) || 15000;
|
|
17313
|
+
const POLL_MS = Number(process.env.ALOOK_KILL_TASK_POLL_MS) || 200;
|
|
17314
|
+
const waitStart = Date.now();
|
|
17315
|
+
let pid = null;
|
|
17316
|
+
while (Date.now() - waitStart < MAX_WAIT_MS) {
|
|
17317
|
+
pid = findRunningPidByTaskId(timelineDir, targetTaskId);
|
|
17318
|
+
if (pid != null)
|
|
17319
|
+
break;
|
|
17320
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
17321
|
+
}
|
|
17269
17322
|
if (pid != null) {
|
|
17270
17323
|
writeKillIntent(agentBaseDir, {
|
|
17271
17324
|
reason: "cancelled",
|
|
@@ -17306,9 +17359,9 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
17306
17359
|
}
|
|
17307
17360
|
const provider = runtimeData.provider;
|
|
17308
17361
|
if (task.contextKey) {
|
|
17309
|
-
const agentBaseDir =
|
|
17362
|
+
const agentBaseDir = join8(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
17310
17363
|
cleanupStaleIntents(agentBaseDir);
|
|
17311
|
-
const timelineDir =
|
|
17364
|
+
const timelineDir = join8(agentBaseDir, ".context_timeline");
|
|
17312
17365
|
const lockAcquired = acquireSteeringLock(agentBaseDir, task.contextKey);
|
|
17313
17366
|
if (!lockAcquired) {
|
|
17314
17367
|
log.warn(`Steering lock contention for context_key=${task.contextKey}, proceeding without steering`);
|
|
@@ -17418,7 +17471,8 @@ async function startInBackground(profile, serverUrl) {
|
|
|
17418
17471
|
const logFd = openSync2(logPath, "a", 384);
|
|
17419
17472
|
const child = spawn3(process.execPath, buildChildArgs(profile, serverUrl), {
|
|
17420
17473
|
detached: true,
|
|
17421
|
-
stdio: ["ignore", logFd, logFd]
|
|
17474
|
+
stdio: ["ignore", logFd, logFd],
|
|
17475
|
+
env: resolveLoginShellEnv()
|
|
17422
17476
|
});
|
|
17423
17477
|
child.unref();
|
|
17424
17478
|
closeSync2(logFd);
|
|
@@ -17471,10 +17525,12 @@ async function stopCommand(profile) {
|
|
|
17471
17525
|
}
|
|
17472
17526
|
await sleep(STOP_POLL_INTERVAL_MS);
|
|
17473
17527
|
}
|
|
17474
|
-
console.warn(`Daemon did not exit within ${shutdownMs}ms —
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17528
|
+
console.warn(`Daemon did not exit within ${shutdownMs}ms — force killing.`);
|
|
17529
|
+
if (!isWindows) {
|
|
17530
|
+
try {
|
|
17531
|
+
process.kill(pid, "SIGKILL");
|
|
17532
|
+
} catch {}
|
|
17533
|
+
}
|
|
17478
17534
|
removePidFileIfMatches(pid, profile);
|
|
17479
17535
|
console.log("Daemon stopped.");
|
|
17480
17536
|
}
|
|
@@ -17533,10 +17589,11 @@ function configCommand() {
|
|
|
17533
17589
|
// commands/email.ts
|
|
17534
17590
|
import { Command as Command5 } from "commander";
|
|
17535
17591
|
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
17536
|
-
import { basename, join as
|
|
17592
|
+
import { basename, join as join9 } from "path";
|
|
17537
17593
|
import PostalMime from "postal-mime";
|
|
17538
|
-
var VALID_STATUSES = ["unread", "read", "archived"];
|
|
17539
|
-
var
|
|
17594
|
+
var VALID_STATUSES = ["unread", "read", "archived", "sent"];
|
|
17595
|
+
var VALID_FOLDERS = ["inbox", "sent", "untrust"];
|
|
17596
|
+
var EMAIL_BASE = tempDir("alook-emails");
|
|
17540
17597
|
var MIME_BY_EXT = {
|
|
17541
17598
|
".pdf": "application/pdf",
|
|
17542
17599
|
".png": "image/png",
|
|
@@ -17604,18 +17661,42 @@ function resolveClientOpts(command, opts) {
|
|
|
17604
17661
|
}
|
|
17605
17662
|
function emailCommand() {
|
|
17606
17663
|
const cmd = new Command5("email").description("Manage agent emails");
|
|
17607
|
-
cmd.command("pull").description("Download and parse emails to /tmp/alook-emails/{workspaceId}/{agentId}/").requiredOption("--agent_id <id>", "Agent ID").option("--status <status>", "Filter by status (unread, read, archived)").option("--workspace <id>", "Workspace ID").option("--json", "Output as JSON instead of files").action(async (opts, command) => {
|
|
17664
|
+
cmd.command("pull").description("Download and parse emails to /tmp/alook-emails/{workspaceId}/{agentId}/").requiredOption("--agent_id <id>", "Agent ID").option("--status <status>", "Filter by status (unread, read, archived)").option("--folder <folder>", "Email folder (inbox, sent, untrust)").option("--limit <n>", "Maximum number of emails to download").option("--offset <n>", "Number of emails to skip").option("--workspace <id>", "Workspace ID").option("--json", "Output as JSON instead of files").action(async (opts, command) => {
|
|
17608
17665
|
const { serverUrl, token, workspaceId } = resolveClientOpts(command, { workspace: opts.workspace, agentId: opts.agent_id });
|
|
17609
17666
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
17610
17667
|
if (opts.status && !VALID_STATUSES.includes(opts.status)) {
|
|
17611
17668
|
console.error(`Error: invalid status "${opts.status}", must be one of: ${VALID_STATUSES.join(", ")}`);
|
|
17612
17669
|
process.exit(1);
|
|
17613
17670
|
}
|
|
17614
|
-
|
|
17671
|
+
if (opts.folder && !VALID_FOLDERS.includes(opts.folder)) {
|
|
17672
|
+
console.error(`Error: invalid folder "${opts.folder}", must be one of: ${VALID_FOLDERS.join(", ")}`);
|
|
17673
|
+
process.exit(1);
|
|
17674
|
+
}
|
|
17675
|
+
if (opts.limit != null) {
|
|
17676
|
+
const n = parseInt(opts.limit, 10);
|
|
17677
|
+
if (isNaN(n) || n < 1 || n > 100) {
|
|
17678
|
+
console.error(`Error: --limit must be an integer between 1 and 100`);
|
|
17679
|
+
process.exit(1);
|
|
17680
|
+
}
|
|
17681
|
+
}
|
|
17682
|
+
if (opts.offset != null) {
|
|
17683
|
+
const n = parseInt(opts.offset, 10);
|
|
17684
|
+
if (isNaN(n) || n < 0) {
|
|
17685
|
+
console.error(`Error: --offset must be a non-negative integer`);
|
|
17686
|
+
process.exit(1);
|
|
17687
|
+
}
|
|
17688
|
+
}
|
|
17689
|
+
const emailDir_base = join9(EMAIL_BASE, workspaceId, opts.agent_id);
|
|
17615
17690
|
try {
|
|
17616
17691
|
let query = `/api/email?agentId=${opts.agent_id}`;
|
|
17617
17692
|
if (opts.status)
|
|
17618
17693
|
query += `&status=${opts.status}`;
|
|
17694
|
+
if (opts.folder)
|
|
17695
|
+
query += `&folder=${opts.folder}`;
|
|
17696
|
+
if (opts.limit)
|
|
17697
|
+
query += `&limit=${opts.limit}`;
|
|
17698
|
+
if (opts.offset)
|
|
17699
|
+
query += `&offset=${opts.offset}`;
|
|
17619
17700
|
const emails2 = await client.getJSON(query);
|
|
17620
17701
|
if (!emails2.length) {
|
|
17621
17702
|
console.log("No emails found.");
|
|
@@ -17628,7 +17709,7 @@ function emailCommand() {
|
|
|
17628
17709
|
mkdirSync7(emailDir_base, { recursive: true });
|
|
17629
17710
|
const downloadedPaths = [];
|
|
17630
17711
|
for (const email3 of emails2) {
|
|
17631
|
-
const emailDir =
|
|
17712
|
+
const emailDir = join9(emailDir_base, email3.id);
|
|
17632
17713
|
mkdirSync7(emailDir, { recursive: true });
|
|
17633
17714
|
const metadata = {
|
|
17634
17715
|
id: email3.id,
|
|
@@ -17641,7 +17722,7 @@ function emailCommand() {
|
|
|
17641
17722
|
in_reply_to: email3.in_reply_to || "",
|
|
17642
17723
|
references: email3.references || ""
|
|
17643
17724
|
};
|
|
17644
|
-
const metadataPath =
|
|
17725
|
+
const metadataPath = join9(emailDir, "metadata.json");
|
|
17645
17726
|
writeFileSync6(metadataPath, JSON.stringify(metadata, null, 2));
|
|
17646
17727
|
downloadedPaths.push(metadataPath);
|
|
17647
17728
|
let rawMime;
|
|
@@ -17657,17 +17738,17 @@ function emailCommand() {
|
|
|
17657
17738
|
}
|
|
17658
17739
|
const parsed = await new PostalMime().parse(rawMime);
|
|
17659
17740
|
if (parsed.text) {
|
|
17660
|
-
const bodyPath =
|
|
17741
|
+
const bodyPath = join9(emailDir, "body.txt");
|
|
17661
17742
|
writeFileSync6(bodyPath, parsed.text);
|
|
17662
17743
|
downloadedPaths.push(bodyPath);
|
|
17663
17744
|
}
|
|
17664
17745
|
if (parsed.html) {
|
|
17665
|
-
const htmlPath =
|
|
17746
|
+
const htmlPath = join9(emailDir, "body.html");
|
|
17666
17747
|
writeFileSync6(htmlPath, parsed.html);
|
|
17667
17748
|
downloadedPaths.push(htmlPath);
|
|
17668
17749
|
}
|
|
17669
17750
|
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
17670
|
-
const attDir =
|
|
17751
|
+
const attDir = join9(emailDir, "attachments");
|
|
17671
17752
|
mkdirSync7(attDir, { recursive: true });
|
|
17672
17753
|
const usedFilenames = new Set;
|
|
17673
17754
|
for (let i = 0;i < parsed.attachments.length; i++) {
|
|
@@ -17677,7 +17758,7 @@ function emailCommand() {
|
|
|
17677
17758
|
filename = `${i}-${filename}`;
|
|
17678
17759
|
}
|
|
17679
17760
|
usedFilenames.add(filename);
|
|
17680
|
-
const attPath =
|
|
17761
|
+
const attPath = join9(attDir, filename);
|
|
17681
17762
|
const content = att.content;
|
|
17682
17763
|
let buf;
|
|
17683
17764
|
if (typeof content === "string") {
|
|
@@ -17773,6 +17854,7 @@ function emailCommand() {
|
|
|
17773
17854
|
console.warn(`Warning: could not fetch parent email ${opts.inReplyTo}, sending without threading`);
|
|
17774
17855
|
}
|
|
17775
17856
|
}
|
|
17857
|
+
const conversationId = process.env.ALOOK_CONVERSATION_ID;
|
|
17776
17858
|
const res = await client.postJSON("/api/email/send", {
|
|
17777
17859
|
agentId: opts.agent_id,
|
|
17778
17860
|
to: opts.to,
|
|
@@ -17780,7 +17862,8 @@ function emailCommand() {
|
|
|
17780
17862
|
htmlBody,
|
|
17781
17863
|
attachments,
|
|
17782
17864
|
...inReplyTo ? { inReplyTo, references } : {},
|
|
17783
|
-
...opts.from ? { from: opts.from } : {}
|
|
17865
|
+
...opts.from ? { from: opts.from } : {},
|
|
17866
|
+
...conversationId ? { conversationId } : {}
|
|
17784
17867
|
});
|
|
17785
17868
|
console.log(`Sent email to ${res.to_email} (id: ${res.id})`);
|
|
17786
17869
|
} catch (err) {
|
|
@@ -17788,6 +17871,118 @@ function emailCommand() {
|
|
|
17788
17871
|
process.exit(1);
|
|
17789
17872
|
}
|
|
17790
17873
|
});
|
|
17874
|
+
cmd.command("forward").description("Forward an email to a new recipient").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--email_id <id>", "Source email ID to forward").requiredOption("--to <addr>", "Recipient email address").option("--from <addr>", "Send from a specific email address (custom mailbox)").option("--note <text>", "Text to prepend above the forwarded message").option("--attachment <path>", "Extra file to attach (repeatable)", collectRepeated, []).option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
|
|
17875
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
|
17876
|
+
workspace: opts.workspace,
|
|
17877
|
+
agentId: opts.agent_id
|
|
17878
|
+
});
|
|
17879
|
+
const client = new APIClient(serverUrl, token, workspaceId);
|
|
17880
|
+
try {
|
|
17881
|
+
let original;
|
|
17882
|
+
try {
|
|
17883
|
+
original = await client.getJSON(`/api/email/${opts.email_id}`);
|
|
17884
|
+
} catch (err) {
|
|
17885
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17886
|
+
if (msg.includes("404")) {
|
|
17887
|
+
console.error(`Error: email ${opts.email_id} not found`);
|
|
17888
|
+
process.exit(1);
|
|
17889
|
+
}
|
|
17890
|
+
throw err;
|
|
17891
|
+
}
|
|
17892
|
+
let rawMime;
|
|
17893
|
+
try {
|
|
17894
|
+
rawMime = await client.getText(`/api/email/${opts.email_id}/raw`);
|
|
17895
|
+
} catch (err) {
|
|
17896
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17897
|
+
if (msg.includes("404")) {
|
|
17898
|
+
console.error(`Error: raw email body not available for ${opts.email_id}`);
|
|
17899
|
+
process.exit(1);
|
|
17900
|
+
}
|
|
17901
|
+
throw err;
|
|
17902
|
+
}
|
|
17903
|
+
const parsed = await new PostalMime().parse(rawMime);
|
|
17904
|
+
const attachments = [];
|
|
17905
|
+
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
17906
|
+
for (const att of parsed.attachments) {
|
|
17907
|
+
const filename = att.filename || "attachment.bin";
|
|
17908
|
+
const contentType = att.mimeType || "application/octet-stream";
|
|
17909
|
+
const content = att.content;
|
|
17910
|
+
let buf;
|
|
17911
|
+
if (typeof content === "string") {
|
|
17912
|
+
buf = Buffer.from(content, "base64");
|
|
17913
|
+
} else if (content instanceof ArrayBuffer) {
|
|
17914
|
+
buf = Buffer.from(new Uint8Array(content));
|
|
17915
|
+
} else {
|
|
17916
|
+
buf = Buffer.from(content);
|
|
17917
|
+
}
|
|
17918
|
+
const form = new FormData;
|
|
17919
|
+
form.append("file", new Blob([new Uint8Array(buf)], { type: contentType }), filename);
|
|
17920
|
+
const uploaded = await client.postMultipart("/api/email/upload", form);
|
|
17921
|
+
attachments.push({
|
|
17922
|
+
key: uploaded.key,
|
|
17923
|
+
filename: uploaded.filename,
|
|
17924
|
+
size: uploaded.size ?? buf.byteLength,
|
|
17925
|
+
contentType: uploaded.contentType ?? contentType
|
|
17926
|
+
});
|
|
17927
|
+
}
|
|
17928
|
+
}
|
|
17929
|
+
const extraPaths = opts.attachment ?? [];
|
|
17930
|
+
for (const path of extraPaths) {
|
|
17931
|
+
let bytes;
|
|
17932
|
+
let size;
|
|
17933
|
+
try {
|
|
17934
|
+
bytes = readFileSync7(path);
|
|
17935
|
+
size = statSync4(path).size;
|
|
17936
|
+
} catch (err) {
|
|
17937
|
+
console.error(`Error: cannot read attachment "${path}": ${err instanceof Error ? err.message : err}`);
|
|
17938
|
+
process.exit(1);
|
|
17939
|
+
}
|
|
17940
|
+
const filename = basename(path);
|
|
17941
|
+
const contentType = guessContentType(filename);
|
|
17942
|
+
const form = new FormData;
|
|
17943
|
+
form.append("file", new Blob([new Uint8Array(bytes)], { type: contentType }), filename);
|
|
17944
|
+
const uploaded = await client.postMultipart("/api/email/upload", form);
|
|
17945
|
+
attachments.push({
|
|
17946
|
+
key: uploaded.key,
|
|
17947
|
+
filename: uploaded.filename,
|
|
17948
|
+
size: uploaded.size ?? size,
|
|
17949
|
+
contentType: uploaded.contentType ?? contentType
|
|
17950
|
+
});
|
|
17951
|
+
}
|
|
17952
|
+
let htmlBody = "";
|
|
17953
|
+
if (opts.note) {
|
|
17954
|
+
htmlBody += `<p>${opts.note}</p>`;
|
|
17955
|
+
}
|
|
17956
|
+
htmlBody += `<br><br>---------- Forwarded message ----------<br>`;
|
|
17957
|
+
htmlBody += `From: ${original.from_email}<br>`;
|
|
17958
|
+
htmlBody += `Date: ${original.created_at}<br>`;
|
|
17959
|
+
htmlBody += `Subject: ${original.subject}<br>`;
|
|
17960
|
+
htmlBody += `To: ${original.to_email}<br><br>`;
|
|
17961
|
+
if (parsed.html) {
|
|
17962
|
+
htmlBody += parsed.html;
|
|
17963
|
+
} else if (parsed.text) {
|
|
17964
|
+
htmlBody += `<pre>${parsed.text}</pre>`;
|
|
17965
|
+
}
|
|
17966
|
+
const subject = /^fwd:/i.test(original.subject) ? original.subject : `Fwd: ${original.subject}`;
|
|
17967
|
+
const conversationId = process.env.ALOOK_CONVERSATION_ID;
|
|
17968
|
+
const res = await client.postJSON("/api/email/send", {
|
|
17969
|
+
agentId: opts.agent_id,
|
|
17970
|
+
to: opts.to,
|
|
17971
|
+
subject,
|
|
17972
|
+
htmlBody,
|
|
17973
|
+
attachments,
|
|
17974
|
+
...opts.from ? { from: opts.from } : {},
|
|
17975
|
+
...conversationId ? { conversationId } : {}
|
|
17976
|
+
});
|
|
17977
|
+
console.log(`Forwarded email to ${res.to_email} (id: ${res.id})`);
|
|
17978
|
+
} catch (err) {
|
|
17979
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17980
|
+
if (msg === "__exit__")
|
|
17981
|
+
throw err;
|
|
17982
|
+
console.error(`Error: ${msg}`);
|
|
17983
|
+
process.exit(1);
|
|
17984
|
+
}
|
|
17985
|
+
});
|
|
17791
17986
|
const whitelistCmd = new Command5("whitelist").description("Manage email whitelist (allowed senders)");
|
|
17792
17987
|
whitelistCmd.command("list").description("List all whitelisted emails for an agent").requiredOption("--agent_id <id>", "Agent ID").option("--workspace <id>", "Workspace ID").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
17793
17988
|
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
package/dist/meeting-runner.js
CHANGED
|
@@ -390,9 +390,20 @@ function ensureChrome() {
|
|
|
390
390
|
throw new Error("Failed to install Chromium via Playwright");
|
|
391
391
|
return installed;
|
|
392
392
|
}
|
|
393
|
+
// daemon/meeting-runner.ts
|
|
394
|
+
import { join as join2 } from "path";
|
|
395
|
+
|
|
396
|
+
// lib/platform.ts
|
|
397
|
+
import { tmpdir } from "os";
|
|
398
|
+
import { join, sep } from "path";
|
|
399
|
+
var isWindows = process.platform === "win32";
|
|
400
|
+
function tempDir(subdir) {
|
|
401
|
+
return join(tmpdir(), subdir);
|
|
402
|
+
}
|
|
403
|
+
|
|
393
404
|
// daemon/meeting-runner.ts
|
|
394
405
|
var SCRAPE_INTERVAL_MS = 3000;
|
|
395
|
-
var
|
|
406
|
+
var DEFAULT_BOT_NAME = "Alook Meeting Bot";
|
|
396
407
|
var MAX_RETRY_DURATION_MS = 30 * 60 * 1000;
|
|
397
408
|
var RETRY_BACKOFF = [30000, 60000, 120000, 300000];
|
|
398
409
|
function log(msg) {
|
|
@@ -445,7 +456,8 @@ async function tryJoinAndRecord(input, chromePath) {
|
|
|
445
456
|
const meetingStartMs = Date.now();
|
|
446
457
|
let transcript = [];
|
|
447
458
|
try {
|
|
448
|
-
|
|
459
|
+
const botName = input.agentName ? `${input.agentName} (Alook)` : DEFAULT_BOT_NAME;
|
|
460
|
+
await joinMeeting(page, input.meetingUrl, botName);
|
|
449
461
|
log("Joined. Waiting for meeting UI...");
|
|
450
462
|
await waitForMeetingReady(page);
|
|
451
463
|
await page.evaluate(() => {
|
|
@@ -498,7 +510,7 @@ async function tryJoinAndRecord(input, chromePath) {
|
|
|
498
510
|
} catch (err) {
|
|
499
511
|
const msg = err instanceof Error ? err.message : String(err);
|
|
500
512
|
if (msg.includes("Blocked from joining")) {
|
|
501
|
-
const screenshotPath =
|
|
513
|
+
const screenshotPath = join2(tempDir("alook-meetings"), `meeting-${input.meetingId}-blocked.png`);
|
|
502
514
|
await page.screenshot({ path: screenshotPath }).catch(() => {});
|
|
503
515
|
log(`Blocked — screenshot: ${screenshotPath}`);
|
|
504
516
|
return { status: "blocked", transcript, error: msg };
|
package/dist/session-runner.js
CHANGED
|
@@ -13625,7 +13625,8 @@ var TaskAgentDataApiSchema = exports_external.object({
|
|
|
13625
13625
|
runtime_config: exports_external.record(exports_external.string(), exports_external.unknown()).default({}),
|
|
13626
13626
|
email_handle: exports_external.string().nullable().optional(),
|
|
13627
13627
|
email_addresses: exports_external.array(exports_external.string()).default([]),
|
|
13628
|
-
user_email: exports_external.string().nullable().optional()
|
|
13628
|
+
user_email: exports_external.string().nullable().optional(),
|
|
13629
|
+
user_name: exports_external.string().nullable().optional()
|
|
13629
13630
|
});
|
|
13630
13631
|
var TaskApiBaseSchema = exports_external.object({
|
|
13631
13632
|
id: exports_external.string(),
|
|
@@ -13666,12 +13667,20 @@ var FileRequestItemSchema = exports_external.object({
|
|
|
13666
13667
|
request_type: exports_external.enum(["tree", "read"]),
|
|
13667
13668
|
path: exports_external.string()
|
|
13668
13669
|
});
|
|
13670
|
+
var PollMeetingItemSchema = exports_external.object({
|
|
13671
|
+
id: exports_external.string(),
|
|
13672
|
+
meeting_url: exports_external.string(),
|
|
13673
|
+
participants: exports_external.array(exports_external.string()),
|
|
13674
|
+
workspace_id: exports_external.string(),
|
|
13675
|
+
agent_name: exports_external.string()
|
|
13676
|
+
});
|
|
13669
13677
|
var PollResponseSchema = exports_external.object({
|
|
13670
13678
|
tasks: exports_external.array(TaskApiSchema),
|
|
13671
13679
|
evicted: exports_external.boolean().optional(),
|
|
13672
13680
|
pending_update: exports_external.object({ version: exports_external.string() }).optional(),
|
|
13673
13681
|
pending_rescan: exports_external.boolean().optional(),
|
|
13674
|
-
file_requests: exports_external.array(FileRequestItemSchema).optional()
|
|
13682
|
+
file_requests: exports_external.array(FileRequestItemSchema).optional(),
|
|
13683
|
+
meetings: exports_external.array(PollMeetingItemSchema).optional()
|
|
13675
13684
|
});
|
|
13676
13685
|
var RegisterResponseSchema = exports_external.object({
|
|
13677
13686
|
runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
|
|
@@ -13819,10 +13828,11 @@ var SendEmailRequestSchema = exports_external.object({
|
|
|
13819
13828
|
references: exports_external.string().optional(),
|
|
13820
13829
|
attachments: exports_external.array(EmailAttachmentSchema).optional(),
|
|
13821
13830
|
customAccountId: exports_external.string().optional(),
|
|
13822
|
-
from: exports_external.string().email().optional()
|
|
13831
|
+
from: exports_external.string().email().optional(),
|
|
13832
|
+
conversationId: exports_external.string().optional()
|
|
13823
13833
|
});
|
|
13824
13834
|
var UpdateEmailStatusRequestSchema = exports_external.object({
|
|
13825
|
-
status: exports_external.enum(["unread", "read", "archived"])
|
|
13835
|
+
status: exports_external.enum(["unread", "read", "archived", "sent"])
|
|
13826
13836
|
});
|
|
13827
13837
|
var MeetingInfoSchema = exports_external.object({
|
|
13828
13838
|
title: exports_external.string(),
|
|
@@ -15659,6 +15669,15 @@ var machineToken = sqliteTable("machine_token", {
|
|
|
15659
15669
|
lastUsedAt: text("last_used_at"),
|
|
15660
15670
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15661
15671
|
}, (t) => [index("idx_machine_token").on(t.token)]);
|
|
15672
|
+
var conversationMap = sqliteTable("conversation_map", {
|
|
15673
|
+
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15674
|
+
key: text("key").notNull(),
|
|
15675
|
+
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
15676
|
+
conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
|
|
15677
|
+
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15678
|
+
}, (t) => [
|
|
15679
|
+
unique("conversation_map_key_workspace").on(t.key, t.workspaceId)
|
|
15680
|
+
]);
|
|
15662
15681
|
var workspaceFileRequest = sqliteTable("workspace_file_request", {
|
|
15663
15682
|
id: text("id").primaryKey().$defaultFn(() => "wfr_" + nanoid3()),
|
|
15664
15683
|
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
@@ -15751,7 +15770,8 @@ class DaemonClient {
|
|
|
15751
15770
|
evicted: resp.evicted ?? false,
|
|
15752
15771
|
pending_update: resp.pending_update,
|
|
15753
15772
|
pending_rescan: resp.pending_rescan,
|
|
15754
|
-
file_requests: resp.file_requests
|
|
15773
|
+
file_requests: resp.file_requests,
|
|
15774
|
+
meetings: resp.meetings
|
|
15755
15775
|
};
|
|
15756
15776
|
}
|
|
15757
15777
|
startTask(token, taskId) {
|
|
@@ -15801,15 +15821,6 @@ class DaemonClient {
|
|
|
15801
15821
|
reportFileData(token, body) {
|
|
15802
15822
|
return this.request("POST", "/api/daemon/workspace/report", token, body);
|
|
15803
15823
|
}
|
|
15804
|
-
async claimMeetings(token, daemonId) {
|
|
15805
|
-
const raw = await this.request("POST", "/api/daemon/meetings/claim", token, { daemon_id: daemonId });
|
|
15806
|
-
return raw.map((m) => ({
|
|
15807
|
-
id: m.id,
|
|
15808
|
-
meetingUrl: m.meeting_url,
|
|
15809
|
-
participants: m.participants,
|
|
15810
|
-
workspaceId: m.workspace_id
|
|
15811
|
-
}));
|
|
15812
|
-
}
|
|
15813
15824
|
}
|
|
15814
15825
|
|
|
15815
15826
|
// daemon/agent/claude.ts
|
|
@@ -16735,11 +16746,21 @@ function createBackend(provider, cliPath) {
|
|
|
16735
16746
|
}
|
|
16736
16747
|
|
|
16737
16748
|
// daemon/execenv/index.ts
|
|
16738
|
-
import { mkdirSync
|
|
16749
|
+
import { mkdirSync } from "fs";
|
|
16739
16750
|
import { join as join3 } from "path";
|
|
16740
16751
|
|
|
16741
16752
|
// daemon/execenv/context.ts
|
|
16742
16753
|
import { createHash } from "crypto";
|
|
16754
|
+
|
|
16755
|
+
// lib/platform.ts
|
|
16756
|
+
import { tmpdir } from "os";
|
|
16757
|
+
import { join, sep } from "path";
|
|
16758
|
+
var isWindows = process.platform === "win32";
|
|
16759
|
+
function tempDir(subdir) {
|
|
16760
|
+
return join(tmpdir(), subdir);
|
|
16761
|
+
}
|
|
16762
|
+
|
|
16763
|
+
// daemon/execenv/context.ts
|
|
16743
16764
|
import {
|
|
16744
16765
|
writeFileSync,
|
|
16745
16766
|
readFileSync,
|
|
@@ -16749,7 +16770,7 @@ import {
|
|
|
16749
16770
|
existsSync,
|
|
16750
16771
|
readlinkSync
|
|
16751
16772
|
} from "fs";
|
|
16752
|
-
import { join } from "path";
|
|
16773
|
+
import { join as join2 } from "path";
|
|
16753
16774
|
var CANONICAL_FILE = "AGENTS.md";
|
|
16754
16775
|
var SYMLINK_ALIASES = ["CLAUDE.md"];
|
|
16755
16776
|
var SYSTEM_PROMPT_BODY = `## Memory Management
|
|
@@ -16805,7 +16826,15 @@ those json are sorted by datetime in asc order.
|
|
|
16805
16826
|
`;
|
|
16806
16827
|
function buildInstructionContent(task) {
|
|
16807
16828
|
const displayName = task.agent?.name || "Alook Agent";
|
|
16808
|
-
|
|
16829
|
+
const alookAddr = task.agent?.emailHandle ? toAlookAddress(task.agent.emailHandle) : null;
|
|
16830
|
+
const customAddrs = (task.agent?.emailAddresses ?? []).filter((a) => a !== alookAddr);
|
|
16831
|
+
const primaryEmail = alookAddr ?? customAddrs[0] ?? null;
|
|
16832
|
+
let agentLine = `You're ${displayName}${primaryEmail ? ` (${primaryEmail})` : ""} in the Alook Platform.`;
|
|
16833
|
+
if (task.agent?.userName || task.agent?.userEmail) {
|
|
16834
|
+
const ownerParts = [task.agent.userName, task.agent.userEmail ? `(${task.agent.userEmail})` : null].filter(Boolean).join(" ");
|
|
16835
|
+
agentLine += ` Your owner and creator is ${ownerParts}.`;
|
|
16836
|
+
}
|
|
16837
|
+
let content = `${agentLine}
|
|
16809
16838
|
${SYSTEM_PROMPT_BODY}`;
|
|
16810
16839
|
if (task.agent?.instructions) {
|
|
16811
16840
|
content += `## BIG BOSS Instructions
|
|
@@ -16819,29 +16848,34 @@ ${task.agent.instructions}
|
|
|
16819
16848
|
You can communicate with the world through Alook CLI.
|
|
16820
16849
|
Your alook agent id is '${task.agentId}'. remember this, most of alook cli will requires you input your agent id.
|
|
16821
16850
|
`;
|
|
16822
|
-
const alookAddr = task.agent?.emailHandle ? toAlookAddress(task.agent.emailHandle) : null;
|
|
16823
|
-
const customAddrs = (task.agent?.emailAddresses ?? []).filter((a) => a !== alookAddr);
|
|
16824
16851
|
if (alookAddr || customAddrs.length > 0) {
|
|
16825
16852
|
const lines = [];
|
|
16826
16853
|
if (alookAddr)
|
|
16827
16854
|
lines.push(`- '${alookAddr}' (default, Alook platform address)`);
|
|
16828
16855
|
for (const a of customAddrs)
|
|
16829
16856
|
lines.push(`- '${a}' (custom IMAP/SMTP mailbox)`);
|
|
16830
|
-
content += `
|
|
16857
|
+
content += `
|
|
16858
|
+
Your email addresses:
|
|
16831
16859
|
${lines.join(`
|
|
16832
16860
|
`)}
|
|
16833
|
-
|
|
16861
|
+
|
|
16834
16862
|
|
|
16835
16863
|
### Emails
|
|
16836
16864
|
---
|
|
16837
|
-
Run 'npx @alook/cli email pull --agent_id ${task.agentId} --status unread' to download unread emails to '
|
|
16838
|
-
|
|
16865
|
+
Run 'npx @alook/cli email pull --agent_id ${task.agentId} --status unread' to download unread emails from inbox to '${tempDir("alook-emails")}/${task.workspaceId}/${task.agentId}/'.
|
|
16866
|
+
---
|
|
16867
|
+
To download sent emails, add '--folder sent': 'npx @alook/cli email pull --agent_id ${task.agentId} --folder sent'
|
|
16868
|
+
Valid folders: inbox (default), sent, untrust.
|
|
16869
|
+
To limit the number of emails downloaded, add '--limit <N>' (e.g. '--limit 20'). Use '--offset <N>' to skip emails for pagination.
|
|
16870
|
+
Example: 'npx @alook/cli email pull --agent_id ${task.agentId} --status unread --limit 20 --offset 0'
|
|
16871
|
+
---
|
|
16872
|
+
Each email is saved to '${tempDir("alook-emails")}/${task.workspaceId}/${task.agentId}/<emailId>/' with:
|
|
16839
16873
|
- 'metadata.json' — sender, recipient, subject, date, status, message_id, in_reply_to, references
|
|
16840
16874
|
- 'body.txt' — plain text body
|
|
16841
16875
|
- 'body.html' — HTML body (if available)
|
|
16842
16876
|
- 'attachments/' — extracted attachment files (if any)
|
|
16843
16877
|
---
|
|
16844
|
-
Before starting to process an email, mark it as read:
|
|
16878
|
+
Before starting to process an INBOX email, mark it as read:
|
|
16845
16879
|
- Run 'npx @alook/cli email set --agent_id ${task.agentId} --email_id <EMAIL_ID> --status read'
|
|
16846
16880
|
---
|
|
16847
16881
|
|
|
@@ -16862,6 +16896,15 @@ Tips:
|
|
|
16862
16896
|
- 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.
|
|
16863
16897
|
---
|
|
16864
16898
|
|
|
16899
|
+
#### Forwarding an email
|
|
16900
|
+
Forward any email to a new recipient, with an optional note prepended above the original content. All original attachments are re-attached automatically.
|
|
16901
|
+
- Run 'npx @alook/cli email forward --agent_id ${task.agentId} --email_id <EMAIL_ID> --to <RECIPIENT>'
|
|
16902
|
+
- Add '--note "FYI, see the request below."' to prepend a note above the forwarded body.
|
|
16903
|
+
- Add '--from <YOUR_EMAIL_ADDRESS>' to send from a specific mailbox.
|
|
16904
|
+
- Add '--attachment <PATH>' to attach extra files (repeatable).
|
|
16905
|
+
- Example: 'npx @alook/cli email forward --agent_id ${task.agentId} --email_id em_abc --to boss@company.com --note "FYI" --attachment /tmp/summary.pdf'
|
|
16906
|
+
---
|
|
16907
|
+
|
|
16865
16908
|
#### Email Whitelist (Allowed Senders)
|
|
16866
16909
|
Manage which email addresses are allowed to send you emails.
|
|
16867
16910
|
- List: 'npx @alook/cli email whitelist list --agent_id ${task.agentId}' (add '--json' for machine-readable output)
|
|
@@ -16944,13 +16987,13 @@ function hasContentChanged(filePath, newContent) {
|
|
|
16944
16987
|
}
|
|
16945
16988
|
}
|
|
16946
16989
|
function ensureSymlinks(workDir) {
|
|
16947
|
-
const canonicalPath =
|
|
16990
|
+
const canonicalPath = join2(workDir, CANONICAL_FILE);
|
|
16948
16991
|
if (!existsSync(canonicalPath))
|
|
16949
16992
|
return;
|
|
16950
16993
|
for (const alias of SYMLINK_ALIASES) {
|
|
16951
16994
|
if (alias === CANONICAL_FILE)
|
|
16952
16995
|
continue;
|
|
16953
|
-
const aliasPath =
|
|
16996
|
+
const aliasPath = join2(workDir, alias);
|
|
16954
16997
|
try {
|
|
16955
16998
|
const stat = lstatSync(aliasPath);
|
|
16956
16999
|
if (stat.isSymbolicLink()) {
|
|
@@ -16970,7 +17013,7 @@ function ensureSymlinks(workDir) {
|
|
|
16970
17013
|
}
|
|
16971
17014
|
function writeInstructionFileIfChanged(workDir, task) {
|
|
16972
17015
|
const content = buildInstructionContent(task);
|
|
16973
|
-
const filePath =
|
|
17016
|
+
const filePath = join2(workDir, CANONICAL_FILE);
|
|
16974
17017
|
const changed = hasContentChanged(filePath, content);
|
|
16975
17018
|
if (changed) {
|
|
16976
17019
|
writeFileSync(filePath, content, "utf-8");
|
|
@@ -16978,16 +17021,34 @@ function writeInstructionFileIfChanged(workDir, task) {
|
|
|
16978
17021
|
ensureSymlinks(workDir);
|
|
16979
17022
|
return changed;
|
|
16980
17023
|
}
|
|
17024
|
+
|
|
17025
|
+
// daemon/execenv/index.ts
|
|
17026
|
+
function prepare(config2, task) {
|
|
17027
|
+
const workDir = join3(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
17028
|
+
mkdirSync(workDir, { recursive: true });
|
|
17029
|
+
const timelineDir = join3(workDir, ".context_timeline");
|
|
17030
|
+
mkdirSync(timelineDir, { recursive: true });
|
|
17031
|
+
writeInstructionFileIfChanged(workDir, task);
|
|
17032
|
+
const env = {
|
|
17033
|
+
ALOOK_WORKSPACE_ID: task.workspaceId,
|
|
17034
|
+
ALOOK_AGENT_ID: task.agentId,
|
|
17035
|
+
ALOOK_TASK_ID: task.id,
|
|
17036
|
+
ALOOK_CONVERSATION_ID: task.conversationId,
|
|
17037
|
+
ALOOK_HEALTH_PORT: process.env.ALOOK_HEALTH_PORT || "19514"
|
|
17038
|
+
};
|
|
17039
|
+
return { workDir, timelineDir, env };
|
|
17040
|
+
}
|
|
17041
|
+
|
|
16981
17042
|
// daemon/execenv/timeline.ts
|
|
16982
17043
|
import { appendFileSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync } from "fs";
|
|
16983
|
-
import { join as
|
|
17044
|
+
import { join as join4 } from "path";
|
|
16984
17045
|
|
|
16985
17046
|
// daemon/execenv/filelock.ts
|
|
16986
|
-
import { mkdirSync, rmdirSync, statSync } from "fs";
|
|
17047
|
+
import { mkdirSync as mkdirSync2, rmdirSync, statSync } from "fs";
|
|
16987
17048
|
var DEFAULT_STALE_MS = 3600000;
|
|
16988
17049
|
function acquireLock(lockPath, staleMs = DEFAULT_STALE_MS) {
|
|
16989
17050
|
try {
|
|
16990
|
-
|
|
17051
|
+
mkdirSync2(lockPath);
|
|
16991
17052
|
return true;
|
|
16992
17053
|
} catch {
|
|
16993
17054
|
try {
|
|
@@ -16995,7 +17056,7 @@ function acquireLock(lockPath, staleMs = DEFAULT_STALE_MS) {
|
|
|
16995
17056
|
if (Date.now() - stat.mtimeMs > staleMs) {
|
|
16996
17057
|
rmdirSync(lockPath);
|
|
16997
17058
|
try {
|
|
16998
|
-
|
|
17059
|
+
mkdirSync2(lockPath);
|
|
16999
17060
|
return true;
|
|
17000
17061
|
} catch {
|
|
17001
17062
|
return false;
|
|
@@ -17003,7 +17064,7 @@ function acquireLock(lockPath, staleMs = DEFAULT_STALE_MS) {
|
|
|
17003
17064
|
}
|
|
17004
17065
|
} catch {
|
|
17005
17066
|
try {
|
|
17006
|
-
|
|
17067
|
+
mkdirSync2(lockPath);
|
|
17007
17068
|
return true;
|
|
17008
17069
|
} catch {
|
|
17009
17070
|
return false;
|
|
@@ -17166,14 +17227,14 @@ function localISOString() {
|
|
|
17166
17227
|
return `${y}-${mo}-${d}T${h}:${mi}:${s}${sign}${hh}:${mm}`;
|
|
17167
17228
|
}
|
|
17168
17229
|
function lockPathFor(timelineDir, filename) {
|
|
17169
|
-
return
|
|
17230
|
+
return join4(timelineDir, `.${filename}.lock`);
|
|
17170
17231
|
}
|
|
17171
17232
|
function sleep(ms) {
|
|
17172
17233
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17173
17234
|
}
|
|
17174
17235
|
async function initEntryAsync(timelineDir, entry) {
|
|
17175
17236
|
const filename = todayFilename();
|
|
17176
|
-
const filePath =
|
|
17237
|
+
const filePath = join4(timelineDir, filename);
|
|
17177
17238
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
17178
17239
|
try {
|
|
17179
17240
|
let acquired = acquireLock(lockPath);
|
|
@@ -17197,7 +17258,7 @@ async function initEntryAsync(timelineDir, entry) {
|
|
|
17197
17258
|
}
|
|
17198
17259
|
function updateEntry(timelineDir, taskId, updater) {
|
|
17199
17260
|
for (const filename of recentFilenames(7)) {
|
|
17200
|
-
const filePath =
|
|
17261
|
+
const filePath = join4(timelineDir, filename);
|
|
17201
17262
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
17202
17263
|
try {
|
|
17203
17264
|
const acquired = acquireLock(lockPath);
|
|
@@ -17225,7 +17286,7 @@ function updateEntry(timelineDir, taskId, updater) {
|
|
|
17225
17286
|
});
|
|
17226
17287
|
if (!found)
|
|
17227
17288
|
continue;
|
|
17228
|
-
const tmpPath =
|
|
17289
|
+
const tmpPath = join4(timelineDir, `.${filename}.tmp`);
|
|
17229
17290
|
writeFileSync2(tmpPath, updated.join(`
|
|
17230
17291
|
`) + `
|
|
17231
17292
|
`);
|
|
@@ -17256,16 +17317,13 @@ function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider, con
|
|
|
17256
17317
|
detailed_log: detailedLog ?? null
|
|
17257
17318
|
};
|
|
17258
17319
|
}
|
|
17259
|
-
var
|
|
17260
|
-
var EMAIL_RESUME_MAX_AGE_MS = 48 * 60 * 60 * 1000;
|
|
17320
|
+
var RESUME_MAX_AGE_MS = 72 * 60 * 60 * 1000;
|
|
17261
17321
|
function findResumableSessionByContextKey(timelineDir, contextKey, provider) {
|
|
17262
|
-
const maxAgeMs = contextKey.startsWith("email:") ? EMAIL_RESUME_MAX_AGE_MS : DEFAULT_RESUME_MAX_AGE_MS;
|
|
17263
17322
|
const now = new Date;
|
|
17264
|
-
const cutoff = new Date(now.getTime() -
|
|
17265
|
-
const daysToScan = Math.ceil(maxAgeMs / 86400000) + 1;
|
|
17323
|
+
const cutoff = new Date(now.getTime() - RESUME_MAX_AGE_MS);
|
|
17266
17324
|
const entries = [];
|
|
17267
|
-
for (const filename of recentFilenames(
|
|
17268
|
-
entries.push(...readJsonl(
|
|
17325
|
+
for (const filename of recentFilenames(7)) {
|
|
17326
|
+
entries.push(...readJsonl(join4(timelineDir, filename)));
|
|
17269
17327
|
}
|
|
17270
17328
|
entries.sort((a, b) => new Date(b.datetime).getTime() - new Date(a.datetime).getTime());
|
|
17271
17329
|
for (const entry of entries) {
|
|
@@ -17276,30 +17334,13 @@ function findResumableSessionByContextKey(timelineDir, contextKey, provider) {
|
|
|
17276
17334
|
return null;
|
|
17277
17335
|
}
|
|
17278
17336
|
|
|
17279
|
-
// daemon/execenv/index.ts
|
|
17280
|
-
function prepare(config2, task) {
|
|
17281
|
-
const workDir = join3(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
17282
|
-
mkdirSync2(workDir, { recursive: true });
|
|
17283
|
-
const timelineDir = join3(workDir, ".context_timeline");
|
|
17284
|
-
mkdirSync2(timelineDir, { recursive: true });
|
|
17285
|
-
writeInstructionFileIfChanged(workDir, task);
|
|
17286
|
-
const env = {
|
|
17287
|
-
ALOOK_WORKSPACE_ID: task.workspaceId,
|
|
17288
|
-
ALOOK_AGENT_ID: task.agentId,
|
|
17289
|
-
ALOOK_TASK_ID: task.id,
|
|
17290
|
-
ALOOK_CONVERSATION_ID: task.conversationId,
|
|
17291
|
-
ALOOK_HEALTH_PORT: process.env.ALOOK_HEALTH_PORT || "19514"
|
|
17292
|
-
};
|
|
17293
|
-
return { workDir, timelineDir, env };
|
|
17294
|
-
}
|
|
17295
|
-
|
|
17296
17337
|
// daemon/execenv/steering.ts
|
|
17297
17338
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3, unlinkSync as unlinkSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
17298
|
-
import { join as
|
|
17339
|
+
import { join as join5 } from "path";
|
|
17299
17340
|
var INTENT_DIR_NAME = ".kill_intents";
|
|
17300
17341
|
var INTENT_STALE_MS = 10 * 60 * 1000;
|
|
17301
17342
|
function intentFilePath(baseDir, taskId) {
|
|
17302
|
-
return
|
|
17343
|
+
return join5(baseDir, INTENT_DIR_NAME, `${taskId}.json`);
|
|
17303
17344
|
}
|
|
17304
17345
|
function readKillIntent(baseDir, taskId) {
|
|
17305
17346
|
const filePath = intentFilePath(baseDir, taskId);
|
|
@@ -17319,10 +17360,19 @@ function clearKillIntent(baseDir, taskId) {
|
|
|
17319
17360
|
|
|
17320
17361
|
// daemon/prompt.ts
|
|
17321
17362
|
var EMAIL_NOTICE = "This task was triggered automatically by an incoming email. There is no human in this session." + " If you need to communicate with a human, you MUST send an email using the email sending tool." + " If you need more information or confirmation from the human, send them an email asking for it and then exit." + " Do not wait — when the human replies, a new task will be triggered automatically and you will be woken up with their response.";
|
|
17363
|
+
function buildDmNotice(name, email3) {
|
|
17364
|
+
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.`;
|
|
17365
|
+
}
|
|
17322
17366
|
function buildPrompt(task, attachments) {
|
|
17323
17367
|
const obj = { type: task.type, instruction: task.prompt };
|
|
17324
17368
|
if (task.type === "email_notification") {
|
|
17325
|
-
|
|
17369
|
+
const ctx = task.context;
|
|
17370
|
+
const dmUser = ctx?.dmUser;
|
|
17371
|
+
if (ctx?.conversationType === "user_dm_message" && dmUser) {
|
|
17372
|
+
obj.notice = buildDmNotice(dmUser.name, dmUser.email);
|
|
17373
|
+
} else {
|
|
17374
|
+
obj.notice = EMAIL_NOTICE;
|
|
17375
|
+
}
|
|
17326
17376
|
}
|
|
17327
17377
|
if (task.sender) {
|
|
17328
17378
|
obj.sender = {
|
|
@@ -17342,7 +17392,7 @@ function buildPrompt(task, attachments) {
|
|
|
17342
17392
|
}
|
|
17343
17393
|
|
|
17344
17394
|
// daemon/session-runner.ts
|
|
17345
|
-
var ATTACHMENTS_BASE = "
|
|
17395
|
+
var ATTACHMENTS_BASE = tempDir("alook-attachments");
|
|
17346
17396
|
async function writeMarkerFile(workspacesRoot, marker) {
|
|
17347
17397
|
const dir = path.join(workspacesRoot, ".pending_completions");
|
|
17348
17398
|
await mkdir(dir, { recursive: true, mode: 448 });
|
|
@@ -17395,6 +17445,46 @@ async function runSession(input) {
|
|
|
17395
17445
|
const client = new DaemonClient(serverURL);
|
|
17396
17446
|
const backend = createBackend(provider, cliPath);
|
|
17397
17447
|
const { workDir, timelineDir, env } = prepare({ workspacesRoot }, task);
|
|
17448
|
+
const agentBaseDir = path.dirname(timelineDir);
|
|
17449
|
+
await initEntryAsync(timelineDir, createTimelineEntry(task.id, task.prompt, task.type, undefined, process.pid, provider, task.contextKey, input.logFilePath));
|
|
17450
|
+
let killed = false;
|
|
17451
|
+
const earlyOnKill = async () => {
|
|
17452
|
+
if (killed)
|
|
17453
|
+
return;
|
|
17454
|
+
killed = true;
|
|
17455
|
+
log.info(`killed by signal (messages=0, tools=0)`);
|
|
17456
|
+
await cleanupAttachments(task.id);
|
|
17457
|
+
const intent = readKillIntent(agentBaseDir, task.id);
|
|
17458
|
+
clearKillIntent(agentBaseDir, task.id);
|
|
17459
|
+
if (intent?.reason === "superseded") {
|
|
17460
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17461
|
+
entry.pid = null;
|
|
17462
|
+
entry.status = "superseded";
|
|
17463
|
+
entry.successor_task_id = intent.successorTaskId ?? null;
|
|
17464
|
+
entry.supersede_reason = "superseded by newer task";
|
|
17465
|
+
});
|
|
17466
|
+
try {
|
|
17467
|
+
await client.supersedeTask(token, task.id);
|
|
17468
|
+
} catch {}
|
|
17469
|
+
} else if (intent?.reason === "cancelled") {
|
|
17470
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17471
|
+
entry.pid = null;
|
|
17472
|
+
entry.status = "cancelled";
|
|
17473
|
+
entry.errmsg = "cancelled by user";
|
|
17474
|
+
});
|
|
17475
|
+
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);
|
|
17476
|
+
} else {
|
|
17477
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17478
|
+
entry.pid = null;
|
|
17479
|
+
entry.status = "killed";
|
|
17480
|
+
entry.errmsg = "killed by signal";
|
|
17481
|
+
});
|
|
17482
|
+
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);
|
|
17483
|
+
}
|
|
17484
|
+
process.exit(1);
|
|
17485
|
+
};
|
|
17486
|
+
process.on("SIGTERM", earlyOnKill);
|
|
17487
|
+
process.on("SIGINT", earlyOnKill);
|
|
17398
17488
|
const attachmentIds = task.context?.attachment_ids ?? [];
|
|
17399
17489
|
let attachments;
|
|
17400
17490
|
if (attachmentIds.length > 0) {
|
|
@@ -17406,7 +17496,14 @@ async function runSession(input) {
|
|
|
17406
17496
|
await cleanupAttachments(task.id);
|
|
17407
17497
|
const errMsg = `failed to download attachments: ${e}`;
|
|
17408
17498
|
log.error(errMsg);
|
|
17499
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17500
|
+
entry.pid = null;
|
|
17501
|
+
entry.status = "failed";
|
|
17502
|
+
entry.errmsg = errMsg;
|
|
17503
|
+
});
|
|
17409
17504
|
await client.failTask(token, task.id, errMsg);
|
|
17505
|
+
process.removeListener("SIGTERM", earlyOnKill);
|
|
17506
|
+
process.removeListener("SIGINT", earlyOnKill);
|
|
17410
17507
|
return;
|
|
17411
17508
|
}
|
|
17412
17509
|
}
|
|
@@ -17426,7 +17523,9 @@ async function runSession(input) {
|
|
|
17426
17523
|
const earlySessionId = await session2.sessionId;
|
|
17427
17524
|
log.info(`agent started (pid=${agentPid ?? "unknown"}, session=${earlySessionId})`);
|
|
17428
17525
|
log.info(JSON.stringify({ role: "user", type: "text", content: prompt }));
|
|
17429
|
-
|
|
17526
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17527
|
+
entry.session_id = earlySessionId || null;
|
|
17528
|
+
});
|
|
17430
17529
|
const pendingMessages = [];
|
|
17431
17530
|
let seq = 0;
|
|
17432
17531
|
let toolCount = 0;
|
|
@@ -17443,8 +17542,8 @@ async function runSession(input) {
|
|
|
17443
17542
|
}
|
|
17444
17543
|
};
|
|
17445
17544
|
const flushTimer = setInterval(flushMessages, FLUSH_INTERVAL_MS);
|
|
17446
|
-
|
|
17447
|
-
|
|
17545
|
+
process.removeListener("SIGTERM", earlyOnKill);
|
|
17546
|
+
process.removeListener("SIGINT", earlyOnKill);
|
|
17448
17547
|
const onKill = async () => {
|
|
17449
17548
|
if (killed)
|
|
17450
17549
|
return;
|
|
@@ -17531,7 +17630,11 @@ async function runSession(input) {
|
|
|
17531
17630
|
});
|
|
17532
17631
|
if (msg.type === "tool-use")
|
|
17533
17632
|
toolCount++;
|
|
17534
|
-
|
|
17633
|
+
if (msg.type === "tool-result" && msg.output && msg.output.length > 500) {
|
|
17634
|
+
log.info(JSON.stringify({ role: "assistant", ...msg, output: msg.output.slice(0, 500) + `... (${msg.output.length} chars)` }));
|
|
17635
|
+
} else {
|
|
17636
|
+
log.info(JSON.stringify({ role: "assistant", ...msg }));
|
|
17637
|
+
}
|
|
17535
17638
|
if (msg.type === "text" && msg.content) {
|
|
17536
17639
|
updateEntry(timelineDir, task.id, (entry) => {
|
|
17537
17640
|
entry.agent_responses.push(msg.content);
|