@alook/cli 0.0.26 → 0.0.28
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 +287 -82
- package/dist/meeting-runner.js +15 -3
- package/dist/session-runner.js +183 -74
- 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(),
|
|
@@ -15709,13 +15719,13 @@ var agent = sqliteTable("agent", {
|
|
|
15709
15719
|
description: text("description").notNull().default(""),
|
|
15710
15720
|
instructions: text("instructions").notNull().default(""),
|
|
15711
15721
|
avatarUrl: text("avatar_url"),
|
|
15712
|
-
runtimeId: text("runtime_id").references(() => agentRuntime.id),
|
|
15722
|
+
runtimeId: text("runtime_id").notNull().references(() => agentRuntime.id, { onDelete: "cascade" }),
|
|
15713
15723
|
runtimeMode: text("runtime_mode").notNull().default("local"),
|
|
15714
15724
|
runtimeConfig: text("runtime_config", { mode: "json" }),
|
|
15715
15725
|
visibility: text("visibility").notNull().default("private"),
|
|
15716
15726
|
status: text("status").notNull().default("idle"),
|
|
15717
15727
|
maxConcurrentTasks: integer2("max_concurrent_tasks").notNull().default(6),
|
|
15718
|
-
ownerId: text("owner_id").references(() => user.id),
|
|
15728
|
+
ownerId: text("owner_id").references(() => user.id, { onDelete: "cascade" }),
|
|
15719
15729
|
tools: text("tools", { mode: "json" }),
|
|
15720
15730
|
triggers: text("triggers", { mode: "json" }),
|
|
15721
15731
|
emailHandle: text("email_handle").unique(),
|
|
@@ -15774,9 +15784,9 @@ var message = sqliteTable("message", {
|
|
|
15774
15784
|
var agentTaskQueue = sqliteTable("agent_task_queue", {
|
|
15775
15785
|
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15776
15786
|
agentId: text("agent_id").notNull(),
|
|
15777
|
-
runtimeId: text("runtime_id").notNull().references(() => agentRuntime.id),
|
|
15778
|
-
workspaceId: text("workspace_id").notNull().references(() => workspace.id),
|
|
15779
|
-
conversationId: text("conversation_id").notNull().references(() => conversation.id),
|
|
15787
|
+
runtimeId: text("runtime_id").notNull().references(() => agentRuntime.id, { onDelete: "cascade" }),
|
|
15788
|
+
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
15789
|
+
conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
|
|
15780
15790
|
prompt: text("prompt").notNull(),
|
|
15781
15791
|
type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
|
|
15782
15792
|
contextKey: text("context_key"),
|
|
@@ -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" }),
|
|
@@ -15952,7 +15971,13 @@ var workspaceFileRequest = sqliteTable("workspace_file_request", {
|
|
|
15952
15971
|
result: text("result"),
|
|
15953
15972
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
|
|
15954
15973
|
updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15955
|
-
}, (t) => [
|
|
15974
|
+
}, (t) => [
|
|
15975
|
+
index("idx_wfr_workspace_status").on(t.workspaceId, t.status),
|
|
15976
|
+
foreignKey({
|
|
15977
|
+
columns: [t.agentId, t.workspaceId],
|
|
15978
|
+
foreignColumns: [agent.id, agent.workspaceId]
|
|
15979
|
+
}).onDelete("cascade")
|
|
15980
|
+
]);
|
|
15956
15981
|
// ../shared/src/db/queries/task.ts
|
|
15957
15982
|
var DEFAULT_STALE_SECONDS = Number(process.env.ALOOK_STALE_DISPATCH_TIMEOUT_S) || 20;
|
|
15958
15983
|
var DEFAULT_STALE_RUNNING_SECONDS = Number(process.env.ALOOK_STALE_RUNNING_TIMEOUT_S) || 3600;
|
|
@@ -16045,7 +16070,8 @@ class DaemonClient {
|
|
|
16045
16070
|
evicted: resp.evicted ?? false,
|
|
16046
16071
|
pending_update: resp.pending_update,
|
|
16047
16072
|
pending_rescan: resp.pending_rescan,
|
|
16048
|
-
file_requests: resp.file_requests
|
|
16073
|
+
file_requests: resp.file_requests,
|
|
16074
|
+
meetings: resp.meetings
|
|
16049
16075
|
};
|
|
16050
16076
|
}
|
|
16051
16077
|
startTask(token, taskId) {
|
|
@@ -16095,15 +16121,6 @@ class DaemonClient {
|
|
|
16095
16121
|
reportFileData(token, body) {
|
|
16096
16122
|
return this.request("POST", "/api/daemon/workspace/report", token, body);
|
|
16097
16123
|
}
|
|
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
16124
|
}
|
|
16108
16125
|
|
|
16109
16126
|
// daemon/config.ts
|
|
@@ -16276,7 +16293,7 @@ function fromApiTask(api2) {
|
|
|
16276
16293
|
type: api2.type,
|
|
16277
16294
|
contextKey: api2.context_key ?? null,
|
|
16278
16295
|
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,
|
|
16296
|
+
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
16297
|
sender: api2.sender ? { name: api2.sender.name, email: api2.sender.email, isOwner: api2.sender.is_owner } : undefined,
|
|
16281
16298
|
repos: undefined,
|
|
16282
16299
|
createdAt: api2.created_at
|
|
@@ -16583,8 +16600,7 @@ function recentFilenames(maxDays) {
|
|
|
16583
16600
|
}
|
|
16584
16601
|
return filenames;
|
|
16585
16602
|
}
|
|
16586
|
-
var
|
|
16587
|
-
var EMAIL_RESUME_MAX_AGE_MS = 48 * 60 * 60 * 1000;
|
|
16603
|
+
var RESUME_MAX_AGE_MS = 72 * 60 * 60 * 1000;
|
|
16588
16604
|
function findRunningPidByTaskId(timelineDir, taskId) {
|
|
16589
16605
|
for (const filename of recentFilenames(7)) {
|
|
16590
16606
|
const entries = readJsonl(join4(timelineDir, filename));
|
|
@@ -16669,7 +16685,7 @@ function releaseSteeringLock(baseDir, contextKey) {
|
|
|
16669
16685
|
|
|
16670
16686
|
// daemon/workspace-files.ts
|
|
16671
16687
|
import { readdir, stat, readFile } from "fs/promises";
|
|
16672
|
-
import { join as join6, resolve, extname, relative } from "path";
|
|
16688
|
+
import { join as join6, resolve, extname, relative, sep } from "path";
|
|
16673
16689
|
var SKIP_DIRS = new Set([".git", "node_modules", ".next", ".wrangler", "__pycache__", ".venv"]);
|
|
16674
16690
|
var TEXT_EXTENSIONS = new Set([
|
|
16675
16691
|
".md",
|
|
@@ -16755,24 +16771,61 @@ async function readFileContent(filePath) {
|
|
|
16755
16771
|
}
|
|
16756
16772
|
function validatePath(agentWorkdir, requestedPath) {
|
|
16757
16773
|
const resolved = resolve(agentWorkdir, requestedPath);
|
|
16758
|
-
if (resolved !== agentWorkdir && !resolved.startsWith(agentWorkdir +
|
|
16774
|
+
if (resolved !== agentWorkdir && !resolved.startsWith(agentWorkdir + sep))
|
|
16759
16775
|
return null;
|
|
16760
16776
|
return resolved;
|
|
16761
16777
|
}
|
|
16762
16778
|
|
|
16779
|
+
// lib/shell-env.ts
|
|
16780
|
+
import { execSync as execSync3 } from "child_process";
|
|
16781
|
+
|
|
16782
|
+
// lib/platform.ts
|
|
16783
|
+
import { tmpdir } from "os";
|
|
16784
|
+
import { join as join7, sep as sep2 } from "path";
|
|
16785
|
+
var isWindows = process.platform === "win32";
|
|
16786
|
+
function tempDir(subdir) {
|
|
16787
|
+
return join7(tmpdir(), subdir);
|
|
16788
|
+
}
|
|
16789
|
+
|
|
16790
|
+
// lib/shell-env.ts
|
|
16791
|
+
function resolveLoginShellEnv() {
|
|
16792
|
+
if (isWindows) {
|
|
16793
|
+
return { ...process.env };
|
|
16794
|
+
}
|
|
16795
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
16796
|
+
try {
|
|
16797
|
+
const output = execSync3(`${shell} -lc 'env'`, {
|
|
16798
|
+
encoding: "utf-8",
|
|
16799
|
+
timeout: 5000,
|
|
16800
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
16801
|
+
});
|
|
16802
|
+
const env = {};
|
|
16803
|
+
for (const line of output.split(`
|
|
16804
|
+
`)) {
|
|
16805
|
+
const idx = line.indexOf("=");
|
|
16806
|
+
if (idx > 0) {
|
|
16807
|
+
env[line.slice(0, idx)] = line.slice(idx + 1);
|
|
16808
|
+
}
|
|
16809
|
+
}
|
|
16810
|
+
if (env.PATH)
|
|
16811
|
+
return env;
|
|
16812
|
+
} catch {}
|
|
16813
|
+
return { ...process.env };
|
|
16814
|
+
}
|
|
16815
|
+
|
|
16763
16816
|
// daemon/daemon.ts
|
|
16764
16817
|
import { existsSync, mkdirSync as mkdirSync5, openSync, closeSync, readdirSync as readdirSync2, statSync as statSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
16765
16818
|
import { readdir as readdir2, readFile as readFile2, unlink, stat as fsStat } from "fs/promises";
|
|
16766
|
-
import { execSync as
|
|
16819
|
+
import { execSync as execSync4, spawn as spawn2 } from "child_process";
|
|
16767
16820
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
16768
|
-
import { dirname as dirname3, join as
|
|
16821
|
+
import { dirname as dirname3, join as join8 } from "path";
|
|
16769
16822
|
var _dir = dirname3(fileURLToPath2(import.meta.url));
|
|
16770
|
-
var sessionRunnerPath = existsSync(
|
|
16771
|
-
var meetingRunnerPath = existsSync(
|
|
16823
|
+
var sessionRunnerPath = existsSync(join8(_dir, "session-runner.js")) ? join8(_dir, "session-runner.js") : join8(_dir, "session-runner.ts");
|
|
16824
|
+
var meetingRunnerPath = existsSync(join8(_dir, "meeting-runner.js")) ? join8(_dir, "meeting-runner.js") : join8(_dir, "meeting-runner.ts");
|
|
16772
16825
|
function isCommandAvailable2(cmd) {
|
|
16773
16826
|
try {
|
|
16774
16827
|
const check2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
16775
|
-
|
|
16828
|
+
execSync4(check2, { stdio: "ignore" });
|
|
16776
16829
|
return true;
|
|
16777
16830
|
} catch {
|
|
16778
16831
|
return false;
|
|
@@ -16790,7 +16843,7 @@ function pruneSessionRunnerLogs() {
|
|
|
16790
16843
|
if (entries.length <= MAX_SESSION_RUNNER_LOGS)
|
|
16791
16844
|
return;
|
|
16792
16845
|
const withMtime = entries.map((name) => {
|
|
16793
|
-
const full =
|
|
16846
|
+
const full = join8(logDir, name);
|
|
16794
16847
|
try {
|
|
16795
16848
|
return { name, mtime: statSync3(full).mtimeMs };
|
|
16796
16849
|
} catch {
|
|
@@ -16800,7 +16853,7 @@ function pruneSessionRunnerLogs() {
|
|
|
16800
16853
|
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
16801
16854
|
for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
|
|
16802
16855
|
try {
|
|
16803
|
-
unlinkSync4(
|
|
16856
|
+
unlinkSync4(join8(logDir, entry.name));
|
|
16804
16857
|
} catch {}
|
|
16805
16858
|
}
|
|
16806
16859
|
}
|
|
@@ -16841,7 +16894,7 @@ function isValidMarker(data) {
|
|
|
16841
16894
|
var MARKER_STALE_MS = 24 * 60 * 60 * 1000;
|
|
16842
16895
|
var TMP_STALE_MS = 60 * 60 * 1000;
|
|
16843
16896
|
async function reconcilePendingCompletions(workspacesRoot) {
|
|
16844
|
-
const dir =
|
|
16897
|
+
const dir = join8(workspacesRoot, ".pending_completions");
|
|
16845
16898
|
let entries;
|
|
16846
16899
|
try {
|
|
16847
16900
|
entries = await readdir2(dir);
|
|
@@ -16852,15 +16905,15 @@ async function reconcilePendingCompletions(workspacesRoot) {
|
|
|
16852
16905
|
if (!name.endsWith(".tmp"))
|
|
16853
16906
|
continue;
|
|
16854
16907
|
try {
|
|
16855
|
-
const s = await fsStat(
|
|
16908
|
+
const s = await fsStat(join8(dir, name));
|
|
16856
16909
|
if (Date.now() - s.mtimeMs > TMP_STALE_MS) {
|
|
16857
|
-
await unlink(
|
|
16910
|
+
await unlink(join8(dir, name));
|
|
16858
16911
|
}
|
|
16859
16912
|
} catch {}
|
|
16860
16913
|
}
|
|
16861
16914
|
const jsonFiles = entries.filter((f) => f.endsWith(".json"));
|
|
16862
16915
|
for (const name of jsonFiles) {
|
|
16863
|
-
const filePath =
|
|
16916
|
+
const filePath = join8(dir, name);
|
|
16864
16917
|
try {
|
|
16865
16918
|
let raw;
|
|
16866
16919
|
try {
|
|
@@ -16937,9 +16990,13 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16937
16990
|
if (serverUrl)
|
|
16938
16991
|
config2.serverURL = serverUrl;
|
|
16939
16992
|
const marker = readUpdateMarker(profile);
|
|
16940
|
-
if (marker
|
|
16993
|
+
if (marker) {
|
|
16941
16994
|
clearUpdateMarker(profile);
|
|
16942
|
-
|
|
16995
|
+
if (marker === config2.cliVersion) {
|
|
16996
|
+
log.info(`Cleared update marker — now running v${config2.cliVersion}`);
|
|
16997
|
+
} else {
|
|
16998
|
+
log.info(`Cleared stale update marker (was v${marker}, running v${config2.cliVersion}) — update will be retried`);
|
|
16999
|
+
}
|
|
16943
17000
|
}
|
|
16944
17001
|
const cliConfig = loadCLIConfigForProfile(profile);
|
|
16945
17002
|
const workspaces = cliConfig.watched_workspaces || [];
|
|
@@ -17070,7 +17127,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17070
17127
|
await new Promise((r) => setTimeout(r, staggerMs));
|
|
17071
17128
|
}
|
|
17072
17129
|
try {
|
|
17073
|
-
const { tasks: apiTasks, evicted, pending_update, pending_rescan, file_requests } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
|
|
17130
|
+
const { tasks: apiTasks, evicted, pending_update, pending_rescan, file_requests, meetings } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
|
|
17074
17131
|
if (evicted) {
|
|
17075
17132
|
evictedIds.push(ws.workspaceId);
|
|
17076
17133
|
continue;
|
|
@@ -17097,6 +17154,19 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17097
17154
|
handleFileRequest(client, config2, ws.workspaceId, req, ws.token).catch((e) => log.debug("File request error", e));
|
|
17098
17155
|
}
|
|
17099
17156
|
}
|
|
17157
|
+
if (meetings) {
|
|
17158
|
+
for (const m of meetings) {
|
|
17159
|
+
spawnMeetingRunner({
|
|
17160
|
+
meetingId: m.id,
|
|
17161
|
+
meetingUrl: m.meeting_url,
|
|
17162
|
+
participants: m.participants,
|
|
17163
|
+
workspaceId: m.workspace_id,
|
|
17164
|
+
callbackUrl: config2.serverURL,
|
|
17165
|
+
authToken: ws.token,
|
|
17166
|
+
agentName: m.agent_name
|
|
17167
|
+
});
|
|
17168
|
+
}
|
|
17169
|
+
}
|
|
17100
17170
|
} catch (e) {
|
|
17101
17171
|
if (e instanceof Error && e.message.startsWith("HTTP 401")) {
|
|
17102
17172
|
log.warn(`Workspace ${ws.workspaceId} poll returned 401 — will retry next cycle`);
|
|
@@ -17108,23 +17178,6 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17108
17178
|
for (const id of evictedIds) {
|
|
17109
17179
|
evictWorkspace(id);
|
|
17110
17180
|
}
|
|
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
17181
|
try {
|
|
17129
17182
|
await reconcilePendingCompletions(config2.workspacesRoot);
|
|
17130
17183
|
} catch (e) {
|
|
@@ -17174,7 +17227,8 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17174
17227
|
}
|
|
17175
17228
|
const child = spawn2(process.execPath, args, {
|
|
17176
17229
|
detached: true,
|
|
17177
|
-
stdio: logFd != null ? ["ignore", logFd, logFd] : ["ignore", "ignore", "ignore"]
|
|
17230
|
+
stdio: logFd != null ? ["ignore", logFd, logFd] : ["ignore", "ignore", "ignore"],
|
|
17231
|
+
env: resolveLoginShellEnv()
|
|
17178
17232
|
});
|
|
17179
17233
|
child.unref();
|
|
17180
17234
|
if (logFd != null)
|
|
@@ -17192,7 +17246,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
17192
17246
|
function spawnSessionRunner(input) {
|
|
17193
17247
|
const logDir = sessionRunnerLogDir();
|
|
17194
17248
|
mkdirSync5(logDir, { recursive: true });
|
|
17195
|
-
const logFilePath =
|
|
17249
|
+
const logFilePath = join8(logDir, `${input.task.id}.log`);
|
|
17196
17250
|
input.logFilePath = logFilePath;
|
|
17197
17251
|
const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
|
|
17198
17252
|
let fd;
|
|
@@ -17213,7 +17267,7 @@ function spawnSessionRunner(input) {
|
|
|
17213
17267
|
function spawnMeetingRunner(input) {
|
|
17214
17268
|
const logDir = sessionRunnerLogDir();
|
|
17215
17269
|
mkdirSync5(logDir, { recursive: true });
|
|
17216
|
-
const logFilePath =
|
|
17270
|
+
const logFilePath = join8(logDir, `meeting-${input.meetingId}.log`);
|
|
17217
17271
|
const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
|
|
17218
17272
|
let fd;
|
|
17219
17273
|
try {
|
|
@@ -17232,7 +17286,7 @@ function spawnMeetingRunner(input) {
|
|
|
17232
17286
|
return child;
|
|
17233
17287
|
}
|
|
17234
17288
|
async function handleFileRequest(client, config2, workspaceId, req, token) {
|
|
17235
|
-
const agentWorkdir =
|
|
17289
|
+
const agentWorkdir = join8(config2.workspacesRoot, workspaceId, req.agent_id, "workdir");
|
|
17236
17290
|
const resolved = validatePath(agentWorkdir, req.path);
|
|
17237
17291
|
if (!resolved) {
|
|
17238
17292
|
await client.reportFileData(token, { request_id: req.id, error: "invalid path", path: req.path });
|
|
@@ -17263,9 +17317,18 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
17263
17317
|
activeTasks.delete(task.id);
|
|
17264
17318
|
return;
|
|
17265
17319
|
}
|
|
17266
|
-
const agentBaseDir =
|
|
17267
|
-
const timelineDir =
|
|
17268
|
-
const
|
|
17320
|
+
const agentBaseDir = join8(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
17321
|
+
const timelineDir = join8(agentBaseDir, ".context_timeline");
|
|
17322
|
+
const MAX_WAIT_MS = Number(process.env.ALOOK_KILL_TASK_MAX_WAIT_MS) || 15000;
|
|
17323
|
+
const POLL_MS = Number(process.env.ALOOK_KILL_TASK_POLL_MS) || 200;
|
|
17324
|
+
const waitStart = Date.now();
|
|
17325
|
+
let pid = null;
|
|
17326
|
+
while (Date.now() - waitStart < MAX_WAIT_MS) {
|
|
17327
|
+
pid = findRunningPidByTaskId(timelineDir, targetTaskId);
|
|
17328
|
+
if (pid != null)
|
|
17329
|
+
break;
|
|
17330
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
17331
|
+
}
|
|
17269
17332
|
if (pid != null) {
|
|
17270
17333
|
writeKillIntent(agentBaseDir, {
|
|
17271
17334
|
reason: "cancelled",
|
|
@@ -17306,9 +17369,9 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
17306
17369
|
}
|
|
17307
17370
|
const provider = runtimeData.provider;
|
|
17308
17371
|
if (task.contextKey) {
|
|
17309
|
-
const agentBaseDir =
|
|
17372
|
+
const agentBaseDir = join8(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
17310
17373
|
cleanupStaleIntents(agentBaseDir);
|
|
17311
|
-
const timelineDir =
|
|
17374
|
+
const timelineDir = join8(agentBaseDir, ".context_timeline");
|
|
17312
17375
|
const lockAcquired = acquireSteeringLock(agentBaseDir, task.contextKey);
|
|
17313
17376
|
if (!lockAcquired) {
|
|
17314
17377
|
log.warn(`Steering lock contention for context_key=${task.contextKey}, proceeding without steering`);
|
|
@@ -17418,7 +17481,8 @@ async function startInBackground(profile, serverUrl) {
|
|
|
17418
17481
|
const logFd = openSync2(logPath, "a", 384);
|
|
17419
17482
|
const child = spawn3(process.execPath, buildChildArgs(profile, serverUrl), {
|
|
17420
17483
|
detached: true,
|
|
17421
|
-
stdio: ["ignore", logFd, logFd]
|
|
17484
|
+
stdio: ["ignore", logFd, logFd],
|
|
17485
|
+
env: resolveLoginShellEnv()
|
|
17422
17486
|
});
|
|
17423
17487
|
child.unref();
|
|
17424
17488
|
closeSync2(logFd);
|
|
@@ -17471,10 +17535,12 @@ async function stopCommand(profile) {
|
|
|
17471
17535
|
}
|
|
17472
17536
|
await sleep(STOP_POLL_INTERVAL_MS);
|
|
17473
17537
|
}
|
|
17474
|
-
console.warn(`Daemon did not exit within ${shutdownMs}ms —
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17538
|
+
console.warn(`Daemon did not exit within ${shutdownMs}ms — force killing.`);
|
|
17539
|
+
if (!isWindows) {
|
|
17540
|
+
try {
|
|
17541
|
+
process.kill(pid, "SIGKILL");
|
|
17542
|
+
} catch {}
|
|
17543
|
+
}
|
|
17478
17544
|
removePidFileIfMatches(pid, profile);
|
|
17479
17545
|
console.log("Daemon stopped.");
|
|
17480
17546
|
}
|
|
@@ -17533,10 +17599,11 @@ function configCommand() {
|
|
|
17533
17599
|
// commands/email.ts
|
|
17534
17600
|
import { Command as Command5 } from "commander";
|
|
17535
17601
|
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
17536
|
-
import { basename, join as
|
|
17602
|
+
import { basename, join as join9 } from "path";
|
|
17537
17603
|
import PostalMime from "postal-mime";
|
|
17538
|
-
var VALID_STATUSES = ["unread", "read", "archived"];
|
|
17539
|
-
var
|
|
17604
|
+
var VALID_STATUSES = ["unread", "read", "archived", "sent"];
|
|
17605
|
+
var VALID_FOLDERS = ["inbox", "sent", "untrust"];
|
|
17606
|
+
var EMAIL_BASE = tempDir("alook-emails");
|
|
17540
17607
|
var MIME_BY_EXT = {
|
|
17541
17608
|
".pdf": "application/pdf",
|
|
17542
17609
|
".png": "image/png",
|
|
@@ -17604,18 +17671,42 @@ function resolveClientOpts(command, opts) {
|
|
|
17604
17671
|
}
|
|
17605
17672
|
function emailCommand() {
|
|
17606
17673
|
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) => {
|
|
17674
|
+
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
17675
|
const { serverUrl, token, workspaceId } = resolveClientOpts(command, { workspace: opts.workspace, agentId: opts.agent_id });
|
|
17609
17676
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
17610
17677
|
if (opts.status && !VALID_STATUSES.includes(opts.status)) {
|
|
17611
17678
|
console.error(`Error: invalid status "${opts.status}", must be one of: ${VALID_STATUSES.join(", ")}`);
|
|
17612
17679
|
process.exit(1);
|
|
17613
17680
|
}
|
|
17614
|
-
|
|
17681
|
+
if (opts.folder && !VALID_FOLDERS.includes(opts.folder)) {
|
|
17682
|
+
console.error(`Error: invalid folder "${opts.folder}", must be one of: ${VALID_FOLDERS.join(", ")}`);
|
|
17683
|
+
process.exit(1);
|
|
17684
|
+
}
|
|
17685
|
+
if (opts.limit != null) {
|
|
17686
|
+
const n = parseInt(opts.limit, 10);
|
|
17687
|
+
if (isNaN(n) || n < 1 || n > 100) {
|
|
17688
|
+
console.error(`Error: --limit must be an integer between 1 and 100`);
|
|
17689
|
+
process.exit(1);
|
|
17690
|
+
}
|
|
17691
|
+
}
|
|
17692
|
+
if (opts.offset != null) {
|
|
17693
|
+
const n = parseInt(opts.offset, 10);
|
|
17694
|
+
if (isNaN(n) || n < 0) {
|
|
17695
|
+
console.error(`Error: --offset must be a non-negative integer`);
|
|
17696
|
+
process.exit(1);
|
|
17697
|
+
}
|
|
17698
|
+
}
|
|
17699
|
+
const emailDir_base = join9(EMAIL_BASE, workspaceId, opts.agent_id);
|
|
17615
17700
|
try {
|
|
17616
17701
|
let query = `/api/email?agentId=${opts.agent_id}`;
|
|
17617
17702
|
if (opts.status)
|
|
17618
17703
|
query += `&status=${opts.status}`;
|
|
17704
|
+
if (opts.folder)
|
|
17705
|
+
query += `&folder=${opts.folder}`;
|
|
17706
|
+
if (opts.limit)
|
|
17707
|
+
query += `&limit=${opts.limit}`;
|
|
17708
|
+
if (opts.offset)
|
|
17709
|
+
query += `&offset=${opts.offset}`;
|
|
17619
17710
|
const emails2 = await client.getJSON(query);
|
|
17620
17711
|
if (!emails2.length) {
|
|
17621
17712
|
console.log("No emails found.");
|
|
@@ -17628,7 +17719,7 @@ function emailCommand() {
|
|
|
17628
17719
|
mkdirSync7(emailDir_base, { recursive: true });
|
|
17629
17720
|
const downloadedPaths = [];
|
|
17630
17721
|
for (const email3 of emails2) {
|
|
17631
|
-
const emailDir =
|
|
17722
|
+
const emailDir = join9(emailDir_base, email3.id);
|
|
17632
17723
|
mkdirSync7(emailDir, { recursive: true });
|
|
17633
17724
|
const metadata = {
|
|
17634
17725
|
id: email3.id,
|
|
@@ -17641,7 +17732,7 @@ function emailCommand() {
|
|
|
17641
17732
|
in_reply_to: email3.in_reply_to || "",
|
|
17642
17733
|
references: email3.references || ""
|
|
17643
17734
|
};
|
|
17644
|
-
const metadataPath =
|
|
17735
|
+
const metadataPath = join9(emailDir, "metadata.json");
|
|
17645
17736
|
writeFileSync6(metadataPath, JSON.stringify(metadata, null, 2));
|
|
17646
17737
|
downloadedPaths.push(metadataPath);
|
|
17647
17738
|
let rawMime;
|
|
@@ -17657,17 +17748,17 @@ function emailCommand() {
|
|
|
17657
17748
|
}
|
|
17658
17749
|
const parsed = await new PostalMime().parse(rawMime);
|
|
17659
17750
|
if (parsed.text) {
|
|
17660
|
-
const bodyPath =
|
|
17751
|
+
const bodyPath = join9(emailDir, "body.txt");
|
|
17661
17752
|
writeFileSync6(bodyPath, parsed.text);
|
|
17662
17753
|
downloadedPaths.push(bodyPath);
|
|
17663
17754
|
}
|
|
17664
17755
|
if (parsed.html) {
|
|
17665
|
-
const htmlPath =
|
|
17756
|
+
const htmlPath = join9(emailDir, "body.html");
|
|
17666
17757
|
writeFileSync6(htmlPath, parsed.html);
|
|
17667
17758
|
downloadedPaths.push(htmlPath);
|
|
17668
17759
|
}
|
|
17669
17760
|
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
17670
|
-
const attDir =
|
|
17761
|
+
const attDir = join9(emailDir, "attachments");
|
|
17671
17762
|
mkdirSync7(attDir, { recursive: true });
|
|
17672
17763
|
const usedFilenames = new Set;
|
|
17673
17764
|
for (let i = 0;i < parsed.attachments.length; i++) {
|
|
@@ -17677,7 +17768,7 @@ function emailCommand() {
|
|
|
17677
17768
|
filename = `${i}-${filename}`;
|
|
17678
17769
|
}
|
|
17679
17770
|
usedFilenames.add(filename);
|
|
17680
|
-
const attPath =
|
|
17771
|
+
const attPath = join9(attDir, filename);
|
|
17681
17772
|
const content = att.content;
|
|
17682
17773
|
let buf;
|
|
17683
17774
|
if (typeof content === "string") {
|
|
@@ -17773,6 +17864,7 @@ function emailCommand() {
|
|
|
17773
17864
|
console.warn(`Warning: could not fetch parent email ${opts.inReplyTo}, sending without threading`);
|
|
17774
17865
|
}
|
|
17775
17866
|
}
|
|
17867
|
+
const conversationId = process.env.ALOOK_CONVERSATION_ID;
|
|
17776
17868
|
const res = await client.postJSON("/api/email/send", {
|
|
17777
17869
|
agentId: opts.agent_id,
|
|
17778
17870
|
to: opts.to,
|
|
@@ -17780,7 +17872,8 @@ function emailCommand() {
|
|
|
17780
17872
|
htmlBody,
|
|
17781
17873
|
attachments,
|
|
17782
17874
|
...inReplyTo ? { inReplyTo, references } : {},
|
|
17783
|
-
...opts.from ? { from: opts.from } : {}
|
|
17875
|
+
...opts.from ? { from: opts.from } : {},
|
|
17876
|
+
...conversationId ? { conversationId } : {}
|
|
17784
17877
|
});
|
|
17785
17878
|
console.log(`Sent email to ${res.to_email} (id: ${res.id})`);
|
|
17786
17879
|
} catch (err) {
|
|
@@ -17788,6 +17881,118 @@ function emailCommand() {
|
|
|
17788
17881
|
process.exit(1);
|
|
17789
17882
|
}
|
|
17790
17883
|
});
|
|
17884
|
+
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) => {
|
|
17885
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
|
17886
|
+
workspace: opts.workspace,
|
|
17887
|
+
agentId: opts.agent_id
|
|
17888
|
+
});
|
|
17889
|
+
const client = new APIClient(serverUrl, token, workspaceId);
|
|
17890
|
+
try {
|
|
17891
|
+
let original;
|
|
17892
|
+
try {
|
|
17893
|
+
original = await client.getJSON(`/api/email/${opts.email_id}`);
|
|
17894
|
+
} catch (err) {
|
|
17895
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17896
|
+
if (msg.includes("404")) {
|
|
17897
|
+
console.error(`Error: email ${opts.email_id} not found`);
|
|
17898
|
+
process.exit(1);
|
|
17899
|
+
}
|
|
17900
|
+
throw err;
|
|
17901
|
+
}
|
|
17902
|
+
let rawMime;
|
|
17903
|
+
try {
|
|
17904
|
+
rawMime = await client.getText(`/api/email/${opts.email_id}/raw`);
|
|
17905
|
+
} catch (err) {
|
|
17906
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17907
|
+
if (msg.includes("404")) {
|
|
17908
|
+
console.error(`Error: raw email body not available for ${opts.email_id}`);
|
|
17909
|
+
process.exit(1);
|
|
17910
|
+
}
|
|
17911
|
+
throw err;
|
|
17912
|
+
}
|
|
17913
|
+
const parsed = await new PostalMime().parse(rawMime);
|
|
17914
|
+
const attachments = [];
|
|
17915
|
+
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
17916
|
+
for (const att of parsed.attachments) {
|
|
17917
|
+
const filename = att.filename || "attachment.bin";
|
|
17918
|
+
const contentType = att.mimeType || "application/octet-stream";
|
|
17919
|
+
const content = att.content;
|
|
17920
|
+
let buf;
|
|
17921
|
+
if (typeof content === "string") {
|
|
17922
|
+
buf = Buffer.from(content, "base64");
|
|
17923
|
+
} else if (content instanceof ArrayBuffer) {
|
|
17924
|
+
buf = Buffer.from(new Uint8Array(content));
|
|
17925
|
+
} else {
|
|
17926
|
+
buf = Buffer.from(content);
|
|
17927
|
+
}
|
|
17928
|
+
const form = new FormData;
|
|
17929
|
+
form.append("file", new Blob([new Uint8Array(buf)], { type: contentType }), filename);
|
|
17930
|
+
const uploaded = await client.postMultipart("/api/email/upload", form);
|
|
17931
|
+
attachments.push({
|
|
17932
|
+
key: uploaded.key,
|
|
17933
|
+
filename: uploaded.filename,
|
|
17934
|
+
size: uploaded.size ?? buf.byteLength,
|
|
17935
|
+
contentType: uploaded.contentType ?? contentType
|
|
17936
|
+
});
|
|
17937
|
+
}
|
|
17938
|
+
}
|
|
17939
|
+
const extraPaths = opts.attachment ?? [];
|
|
17940
|
+
for (const path of extraPaths) {
|
|
17941
|
+
let bytes;
|
|
17942
|
+
let size;
|
|
17943
|
+
try {
|
|
17944
|
+
bytes = readFileSync7(path);
|
|
17945
|
+
size = statSync4(path).size;
|
|
17946
|
+
} catch (err) {
|
|
17947
|
+
console.error(`Error: cannot read attachment "${path}": ${err instanceof Error ? err.message : err}`);
|
|
17948
|
+
process.exit(1);
|
|
17949
|
+
}
|
|
17950
|
+
const filename = basename(path);
|
|
17951
|
+
const contentType = guessContentType(filename);
|
|
17952
|
+
const form = new FormData;
|
|
17953
|
+
form.append("file", new Blob([new Uint8Array(bytes)], { type: contentType }), filename);
|
|
17954
|
+
const uploaded = await client.postMultipart("/api/email/upload", form);
|
|
17955
|
+
attachments.push({
|
|
17956
|
+
key: uploaded.key,
|
|
17957
|
+
filename: uploaded.filename,
|
|
17958
|
+
size: uploaded.size ?? size,
|
|
17959
|
+
contentType: uploaded.contentType ?? contentType
|
|
17960
|
+
});
|
|
17961
|
+
}
|
|
17962
|
+
let htmlBody = "";
|
|
17963
|
+
if (opts.note) {
|
|
17964
|
+
htmlBody += `<p>${opts.note}</p>`;
|
|
17965
|
+
}
|
|
17966
|
+
htmlBody += `<br><br>---------- Forwarded message ----------<br>`;
|
|
17967
|
+
htmlBody += `From: ${original.from_email}<br>`;
|
|
17968
|
+
htmlBody += `Date: ${original.created_at}<br>`;
|
|
17969
|
+
htmlBody += `Subject: ${original.subject}<br>`;
|
|
17970
|
+
htmlBody += `To: ${original.to_email}<br><br>`;
|
|
17971
|
+
if (parsed.html) {
|
|
17972
|
+
htmlBody += parsed.html;
|
|
17973
|
+
} else if (parsed.text) {
|
|
17974
|
+
htmlBody += `<pre>${parsed.text}</pre>`;
|
|
17975
|
+
}
|
|
17976
|
+
const subject = /^fwd:/i.test(original.subject) ? original.subject : `Fwd: ${original.subject}`;
|
|
17977
|
+
const conversationId = process.env.ALOOK_CONVERSATION_ID;
|
|
17978
|
+
const res = await client.postJSON("/api/email/send", {
|
|
17979
|
+
agentId: opts.agent_id,
|
|
17980
|
+
to: opts.to,
|
|
17981
|
+
subject,
|
|
17982
|
+
htmlBody,
|
|
17983
|
+
attachments,
|
|
17984
|
+
...opts.from ? { from: opts.from } : {},
|
|
17985
|
+
...conversationId ? { conversationId } : {}
|
|
17986
|
+
});
|
|
17987
|
+
console.log(`Forwarded email to ${res.to_email} (id: ${res.id})`);
|
|
17988
|
+
} catch (err) {
|
|
17989
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17990
|
+
if (msg === "__exit__")
|
|
17991
|
+
throw err;
|
|
17992
|
+
console.error(`Error: ${msg}`);
|
|
17993
|
+
process.exit(1);
|
|
17994
|
+
}
|
|
17995
|
+
});
|
|
17791
17996
|
const whitelistCmd = new Command5("whitelist").description("Manage email whitelist (allowed senders)");
|
|
17792
17997
|
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
17998
|
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(),
|
|
@@ -15426,13 +15436,13 @@ var agent = sqliteTable("agent", {
|
|
|
15426
15436
|
description: text("description").notNull().default(""),
|
|
15427
15437
|
instructions: text("instructions").notNull().default(""),
|
|
15428
15438
|
avatarUrl: text("avatar_url"),
|
|
15429
|
-
runtimeId: text("runtime_id").references(() => agentRuntime.id),
|
|
15439
|
+
runtimeId: text("runtime_id").notNull().references(() => agentRuntime.id, { onDelete: "cascade" }),
|
|
15430
15440
|
runtimeMode: text("runtime_mode").notNull().default("local"),
|
|
15431
15441
|
runtimeConfig: text("runtime_config", { mode: "json" }),
|
|
15432
15442
|
visibility: text("visibility").notNull().default("private"),
|
|
15433
15443
|
status: text("status").notNull().default("idle"),
|
|
15434
15444
|
maxConcurrentTasks: integer2("max_concurrent_tasks").notNull().default(6),
|
|
15435
|
-
ownerId: text("owner_id").references(() => user.id),
|
|
15445
|
+
ownerId: text("owner_id").references(() => user.id, { onDelete: "cascade" }),
|
|
15436
15446
|
tools: text("tools", { mode: "json" }),
|
|
15437
15447
|
triggers: text("triggers", { mode: "json" }),
|
|
15438
15448
|
emailHandle: text("email_handle").unique(),
|
|
@@ -15491,9 +15501,9 @@ var message = sqliteTable("message", {
|
|
|
15491
15501
|
var agentTaskQueue = sqliteTable("agent_task_queue", {
|
|
15492
15502
|
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15493
15503
|
agentId: text("agent_id").notNull(),
|
|
15494
|
-
runtimeId: text("runtime_id").notNull().references(() => agentRuntime.id),
|
|
15495
|
-
workspaceId: text("workspace_id").notNull().references(() => workspace.id),
|
|
15496
|
-
conversationId: text("conversation_id").notNull().references(() => conversation.id),
|
|
15504
|
+
runtimeId: text("runtime_id").notNull().references(() => agentRuntime.id, { onDelete: "cascade" }),
|
|
15505
|
+
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
15506
|
+
conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
|
|
15497
15507
|
prompt: text("prompt").notNull(),
|
|
15498
15508
|
type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
|
|
15499
15509
|
contextKey: text("context_key"),
|
|
@@ -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" }),
|
|
@@ -15669,7 +15688,13 @@ var workspaceFileRequest = sqliteTable("workspace_file_request", {
|
|
|
15669
15688
|
result: text("result"),
|
|
15670
15689
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
|
|
15671
15690
|
updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15672
|
-
}, (t) => [
|
|
15691
|
+
}, (t) => [
|
|
15692
|
+
index("idx_wfr_workspace_status").on(t.workspaceId, t.status),
|
|
15693
|
+
foreignKey({
|
|
15694
|
+
columns: [t.agentId, t.workspaceId],
|
|
15695
|
+
foreignColumns: [agent.id, agent.workspaceId]
|
|
15696
|
+
}).onDelete("cascade")
|
|
15697
|
+
]);
|
|
15673
15698
|
// ../shared/src/db/queries/task.ts
|
|
15674
15699
|
var DEFAULT_STALE_SECONDS = Number(process.env.ALOOK_STALE_DISPATCH_TIMEOUT_S) || 20;
|
|
15675
15700
|
var DEFAULT_STALE_RUNNING_SECONDS = Number(process.env.ALOOK_STALE_RUNNING_TIMEOUT_S) || 3600;
|
|
@@ -15751,7 +15776,8 @@ class DaemonClient {
|
|
|
15751
15776
|
evicted: resp.evicted ?? false,
|
|
15752
15777
|
pending_update: resp.pending_update,
|
|
15753
15778
|
pending_rescan: resp.pending_rescan,
|
|
15754
|
-
file_requests: resp.file_requests
|
|
15779
|
+
file_requests: resp.file_requests,
|
|
15780
|
+
meetings: resp.meetings
|
|
15755
15781
|
};
|
|
15756
15782
|
}
|
|
15757
15783
|
startTask(token, taskId) {
|
|
@@ -15801,15 +15827,6 @@ class DaemonClient {
|
|
|
15801
15827
|
reportFileData(token, body) {
|
|
15802
15828
|
return this.request("POST", "/api/daemon/workspace/report", token, body);
|
|
15803
15829
|
}
|
|
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
15830
|
}
|
|
15814
15831
|
|
|
15815
15832
|
// daemon/agent/claude.ts
|
|
@@ -16735,11 +16752,21 @@ function createBackend(provider, cliPath) {
|
|
|
16735
16752
|
}
|
|
16736
16753
|
|
|
16737
16754
|
// daemon/execenv/index.ts
|
|
16738
|
-
import { mkdirSync
|
|
16755
|
+
import { mkdirSync } from "fs";
|
|
16739
16756
|
import { join as join3 } from "path";
|
|
16740
16757
|
|
|
16741
16758
|
// daemon/execenv/context.ts
|
|
16742
16759
|
import { createHash } from "crypto";
|
|
16760
|
+
|
|
16761
|
+
// lib/platform.ts
|
|
16762
|
+
import { tmpdir } from "os";
|
|
16763
|
+
import { join, sep } from "path";
|
|
16764
|
+
var isWindows = process.platform === "win32";
|
|
16765
|
+
function tempDir(subdir) {
|
|
16766
|
+
return join(tmpdir(), subdir);
|
|
16767
|
+
}
|
|
16768
|
+
|
|
16769
|
+
// daemon/execenv/context.ts
|
|
16743
16770
|
import {
|
|
16744
16771
|
writeFileSync,
|
|
16745
16772
|
readFileSync,
|
|
@@ -16749,7 +16776,7 @@ import {
|
|
|
16749
16776
|
existsSync,
|
|
16750
16777
|
readlinkSync
|
|
16751
16778
|
} from "fs";
|
|
16752
|
-
import { join } from "path";
|
|
16779
|
+
import { join as join2 } from "path";
|
|
16753
16780
|
var CANONICAL_FILE = "AGENTS.md";
|
|
16754
16781
|
var SYMLINK_ALIASES = ["CLAUDE.md"];
|
|
16755
16782
|
var SYSTEM_PROMPT_BODY = `## Memory Management
|
|
@@ -16805,7 +16832,15 @@ those json are sorted by datetime in asc order.
|
|
|
16805
16832
|
`;
|
|
16806
16833
|
function buildInstructionContent(task) {
|
|
16807
16834
|
const displayName = task.agent?.name || "Alook Agent";
|
|
16808
|
-
|
|
16835
|
+
const alookAddr = task.agent?.emailHandle ? toAlookAddress(task.agent.emailHandle) : null;
|
|
16836
|
+
const customAddrs = (task.agent?.emailAddresses ?? []).filter((a) => a !== alookAddr);
|
|
16837
|
+
const primaryEmail = alookAddr ?? customAddrs[0] ?? null;
|
|
16838
|
+
let agentLine = `You're ${displayName}${primaryEmail ? ` (${primaryEmail})` : ""} in the Alook Platform.`;
|
|
16839
|
+
if (task.agent?.userName || task.agent?.userEmail) {
|
|
16840
|
+
const ownerParts = [task.agent.userName, task.agent.userEmail ? `(${task.agent.userEmail})` : null].filter(Boolean).join(" ");
|
|
16841
|
+
agentLine += ` Your owner and creator is ${ownerParts}.`;
|
|
16842
|
+
}
|
|
16843
|
+
let content = `${agentLine}
|
|
16809
16844
|
${SYSTEM_PROMPT_BODY}`;
|
|
16810
16845
|
if (task.agent?.instructions) {
|
|
16811
16846
|
content += `## BIG BOSS Instructions
|
|
@@ -16819,29 +16854,34 @@ ${task.agent.instructions}
|
|
|
16819
16854
|
You can communicate with the world through Alook CLI.
|
|
16820
16855
|
Your alook agent id is '${task.agentId}'. remember this, most of alook cli will requires you input your agent id.
|
|
16821
16856
|
`;
|
|
16822
|
-
const alookAddr = task.agent?.emailHandle ? toAlookAddress(task.agent.emailHandle) : null;
|
|
16823
|
-
const customAddrs = (task.agent?.emailAddresses ?? []).filter((a) => a !== alookAddr);
|
|
16824
16857
|
if (alookAddr || customAddrs.length > 0) {
|
|
16825
16858
|
const lines = [];
|
|
16826
16859
|
if (alookAddr)
|
|
16827
16860
|
lines.push(`- '${alookAddr}' (default, Alook platform address)`);
|
|
16828
16861
|
for (const a of customAddrs)
|
|
16829
16862
|
lines.push(`- '${a}' (custom IMAP/SMTP mailbox)`);
|
|
16830
|
-
content += `
|
|
16863
|
+
content += `
|
|
16864
|
+
Your email addresses:
|
|
16831
16865
|
${lines.join(`
|
|
16832
16866
|
`)}
|
|
16833
|
-
|
|
16867
|
+
|
|
16834
16868
|
|
|
16835
16869
|
### Emails
|
|
16836
16870
|
---
|
|
16837
|
-
Run 'npx @alook/cli email pull --agent_id ${task.agentId} --status unread' to download unread emails to '
|
|
16838
|
-
|
|
16871
|
+
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}/'.
|
|
16872
|
+
---
|
|
16873
|
+
To download sent emails, add '--folder sent': 'npx @alook/cli email pull --agent_id ${task.agentId} --folder sent'
|
|
16874
|
+
Valid folders: inbox (default), sent, untrust.
|
|
16875
|
+
To limit the number of emails downloaded, add '--limit <N>' (e.g. '--limit 20'). Use '--offset <N>' to skip emails for pagination.
|
|
16876
|
+
Example: 'npx @alook/cli email pull --agent_id ${task.agentId} --status unread --limit 20 --offset 0'
|
|
16877
|
+
---
|
|
16878
|
+
Each email is saved to '${tempDir("alook-emails")}/${task.workspaceId}/${task.agentId}/<emailId>/' with:
|
|
16839
16879
|
- 'metadata.json' — sender, recipient, subject, date, status, message_id, in_reply_to, references
|
|
16840
16880
|
- 'body.txt' — plain text body
|
|
16841
16881
|
- 'body.html' — HTML body (if available)
|
|
16842
16882
|
- 'attachments/' — extracted attachment files (if any)
|
|
16843
16883
|
---
|
|
16844
|
-
Before starting to process an email, mark it as read:
|
|
16884
|
+
Before starting to process an INBOX email, mark it as read:
|
|
16845
16885
|
- Run 'npx @alook/cli email set --agent_id ${task.agentId} --email_id <EMAIL_ID> --status read'
|
|
16846
16886
|
---
|
|
16847
16887
|
|
|
@@ -16862,6 +16902,15 @@ Tips:
|
|
|
16862
16902
|
- 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
16903
|
---
|
|
16864
16904
|
|
|
16905
|
+
#### Forwarding an email
|
|
16906
|
+
Forward any email to a new recipient, with an optional note prepended above the original content. All original attachments are re-attached automatically.
|
|
16907
|
+
- Run 'npx @alook/cli email forward --agent_id ${task.agentId} --email_id <EMAIL_ID> --to <RECIPIENT>'
|
|
16908
|
+
- Add '--note "FYI, see the request below."' to prepend a note above the forwarded body.
|
|
16909
|
+
- Add '--from <YOUR_EMAIL_ADDRESS>' to send from a specific mailbox.
|
|
16910
|
+
- Add '--attachment <PATH>' to attach extra files (repeatable).
|
|
16911
|
+
- Example: 'npx @alook/cli email forward --agent_id ${task.agentId} --email_id em_abc --to boss@company.com --note "FYI" --attachment /tmp/summary.pdf'
|
|
16912
|
+
---
|
|
16913
|
+
|
|
16865
16914
|
#### Email Whitelist (Allowed Senders)
|
|
16866
16915
|
Manage which email addresses are allowed to send you emails.
|
|
16867
16916
|
- List: 'npx @alook/cli email whitelist list --agent_id ${task.agentId}' (add '--json' for machine-readable output)
|
|
@@ -16944,13 +16993,13 @@ function hasContentChanged(filePath, newContent) {
|
|
|
16944
16993
|
}
|
|
16945
16994
|
}
|
|
16946
16995
|
function ensureSymlinks(workDir) {
|
|
16947
|
-
const canonicalPath =
|
|
16996
|
+
const canonicalPath = join2(workDir, CANONICAL_FILE);
|
|
16948
16997
|
if (!existsSync(canonicalPath))
|
|
16949
16998
|
return;
|
|
16950
16999
|
for (const alias of SYMLINK_ALIASES) {
|
|
16951
17000
|
if (alias === CANONICAL_FILE)
|
|
16952
17001
|
continue;
|
|
16953
|
-
const aliasPath =
|
|
17002
|
+
const aliasPath = join2(workDir, alias);
|
|
16954
17003
|
try {
|
|
16955
17004
|
const stat = lstatSync(aliasPath);
|
|
16956
17005
|
if (stat.isSymbolicLink()) {
|
|
@@ -16970,7 +17019,7 @@ function ensureSymlinks(workDir) {
|
|
|
16970
17019
|
}
|
|
16971
17020
|
function writeInstructionFileIfChanged(workDir, task) {
|
|
16972
17021
|
const content = buildInstructionContent(task);
|
|
16973
|
-
const filePath =
|
|
17022
|
+
const filePath = join2(workDir, CANONICAL_FILE);
|
|
16974
17023
|
const changed = hasContentChanged(filePath, content);
|
|
16975
17024
|
if (changed) {
|
|
16976
17025
|
writeFileSync(filePath, content, "utf-8");
|
|
@@ -16978,16 +17027,34 @@ function writeInstructionFileIfChanged(workDir, task) {
|
|
|
16978
17027
|
ensureSymlinks(workDir);
|
|
16979
17028
|
return changed;
|
|
16980
17029
|
}
|
|
17030
|
+
|
|
17031
|
+
// daemon/execenv/index.ts
|
|
17032
|
+
function prepare(config2, task) {
|
|
17033
|
+
const workDir = join3(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
17034
|
+
mkdirSync(workDir, { recursive: true });
|
|
17035
|
+
const timelineDir = join3(workDir, ".context_timeline");
|
|
17036
|
+
mkdirSync(timelineDir, { recursive: true });
|
|
17037
|
+
writeInstructionFileIfChanged(workDir, task);
|
|
17038
|
+
const env = {
|
|
17039
|
+
ALOOK_WORKSPACE_ID: task.workspaceId,
|
|
17040
|
+
ALOOK_AGENT_ID: task.agentId,
|
|
17041
|
+
ALOOK_TASK_ID: task.id,
|
|
17042
|
+
ALOOK_CONVERSATION_ID: task.conversationId,
|
|
17043
|
+
ALOOK_HEALTH_PORT: process.env.ALOOK_HEALTH_PORT || "19514"
|
|
17044
|
+
};
|
|
17045
|
+
return { workDir, timelineDir, env };
|
|
17046
|
+
}
|
|
17047
|
+
|
|
16981
17048
|
// daemon/execenv/timeline.ts
|
|
16982
17049
|
import { appendFileSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync } from "fs";
|
|
16983
|
-
import { join as
|
|
17050
|
+
import { join as join4 } from "path";
|
|
16984
17051
|
|
|
16985
17052
|
// daemon/execenv/filelock.ts
|
|
16986
|
-
import { mkdirSync, rmdirSync, statSync } from "fs";
|
|
17053
|
+
import { mkdirSync as mkdirSync2, rmdirSync, statSync } from "fs";
|
|
16987
17054
|
var DEFAULT_STALE_MS = 3600000;
|
|
16988
17055
|
function acquireLock(lockPath, staleMs = DEFAULT_STALE_MS) {
|
|
16989
17056
|
try {
|
|
16990
|
-
|
|
17057
|
+
mkdirSync2(lockPath);
|
|
16991
17058
|
return true;
|
|
16992
17059
|
} catch {
|
|
16993
17060
|
try {
|
|
@@ -16995,7 +17062,7 @@ function acquireLock(lockPath, staleMs = DEFAULT_STALE_MS) {
|
|
|
16995
17062
|
if (Date.now() - stat.mtimeMs > staleMs) {
|
|
16996
17063
|
rmdirSync(lockPath);
|
|
16997
17064
|
try {
|
|
16998
|
-
|
|
17065
|
+
mkdirSync2(lockPath);
|
|
16999
17066
|
return true;
|
|
17000
17067
|
} catch {
|
|
17001
17068
|
return false;
|
|
@@ -17003,7 +17070,7 @@ function acquireLock(lockPath, staleMs = DEFAULT_STALE_MS) {
|
|
|
17003
17070
|
}
|
|
17004
17071
|
} catch {
|
|
17005
17072
|
try {
|
|
17006
|
-
|
|
17073
|
+
mkdirSync2(lockPath);
|
|
17007
17074
|
return true;
|
|
17008
17075
|
} catch {
|
|
17009
17076
|
return false;
|
|
@@ -17166,14 +17233,14 @@ function localISOString() {
|
|
|
17166
17233
|
return `${y}-${mo}-${d}T${h}:${mi}:${s}${sign}${hh}:${mm}`;
|
|
17167
17234
|
}
|
|
17168
17235
|
function lockPathFor(timelineDir, filename) {
|
|
17169
|
-
return
|
|
17236
|
+
return join4(timelineDir, `.${filename}.lock`);
|
|
17170
17237
|
}
|
|
17171
17238
|
function sleep(ms) {
|
|
17172
17239
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17173
17240
|
}
|
|
17174
17241
|
async function initEntryAsync(timelineDir, entry) {
|
|
17175
17242
|
const filename = todayFilename();
|
|
17176
|
-
const filePath =
|
|
17243
|
+
const filePath = join4(timelineDir, filename);
|
|
17177
17244
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
17178
17245
|
try {
|
|
17179
17246
|
let acquired = acquireLock(lockPath);
|
|
@@ -17197,7 +17264,7 @@ async function initEntryAsync(timelineDir, entry) {
|
|
|
17197
17264
|
}
|
|
17198
17265
|
function updateEntry(timelineDir, taskId, updater) {
|
|
17199
17266
|
for (const filename of recentFilenames(7)) {
|
|
17200
|
-
const filePath =
|
|
17267
|
+
const filePath = join4(timelineDir, filename);
|
|
17201
17268
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
17202
17269
|
try {
|
|
17203
17270
|
const acquired = acquireLock(lockPath);
|
|
@@ -17225,7 +17292,7 @@ function updateEntry(timelineDir, taskId, updater) {
|
|
|
17225
17292
|
});
|
|
17226
17293
|
if (!found)
|
|
17227
17294
|
continue;
|
|
17228
|
-
const tmpPath =
|
|
17295
|
+
const tmpPath = join4(timelineDir, `.${filename}.tmp`);
|
|
17229
17296
|
writeFileSync2(tmpPath, updated.join(`
|
|
17230
17297
|
`) + `
|
|
17231
17298
|
`);
|
|
@@ -17256,16 +17323,13 @@ function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider, con
|
|
|
17256
17323
|
detailed_log: detailedLog ?? null
|
|
17257
17324
|
};
|
|
17258
17325
|
}
|
|
17259
|
-
var
|
|
17260
|
-
var EMAIL_RESUME_MAX_AGE_MS = 48 * 60 * 60 * 1000;
|
|
17326
|
+
var RESUME_MAX_AGE_MS = 72 * 60 * 60 * 1000;
|
|
17261
17327
|
function findResumableSessionByContextKey(timelineDir, contextKey, provider) {
|
|
17262
|
-
const maxAgeMs = contextKey.startsWith("email:") ? EMAIL_RESUME_MAX_AGE_MS : DEFAULT_RESUME_MAX_AGE_MS;
|
|
17263
17328
|
const now = new Date;
|
|
17264
|
-
const cutoff = new Date(now.getTime() -
|
|
17265
|
-
const daysToScan = Math.ceil(maxAgeMs / 86400000) + 1;
|
|
17329
|
+
const cutoff = new Date(now.getTime() - RESUME_MAX_AGE_MS);
|
|
17266
17330
|
const entries = [];
|
|
17267
|
-
for (const filename of recentFilenames(
|
|
17268
|
-
entries.push(...readJsonl(
|
|
17331
|
+
for (const filename of recentFilenames(7)) {
|
|
17332
|
+
entries.push(...readJsonl(join4(timelineDir, filename)));
|
|
17269
17333
|
}
|
|
17270
17334
|
entries.sort((a, b) => new Date(b.datetime).getTime() - new Date(a.datetime).getTime());
|
|
17271
17335
|
for (const entry of entries) {
|
|
@@ -17276,30 +17340,13 @@ function findResumableSessionByContextKey(timelineDir, contextKey, provider) {
|
|
|
17276
17340
|
return null;
|
|
17277
17341
|
}
|
|
17278
17342
|
|
|
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
17343
|
// daemon/execenv/steering.ts
|
|
17297
17344
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3, unlinkSync as unlinkSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
17298
|
-
import { join as
|
|
17345
|
+
import { join as join5 } from "path";
|
|
17299
17346
|
var INTENT_DIR_NAME = ".kill_intents";
|
|
17300
17347
|
var INTENT_STALE_MS = 10 * 60 * 1000;
|
|
17301
17348
|
function intentFilePath(baseDir, taskId) {
|
|
17302
|
-
return
|
|
17349
|
+
return join5(baseDir, INTENT_DIR_NAME, `${taskId}.json`);
|
|
17303
17350
|
}
|
|
17304
17351
|
function readKillIntent(baseDir, taskId) {
|
|
17305
17352
|
const filePath = intentFilePath(baseDir, taskId);
|
|
@@ -17319,10 +17366,19 @@ function clearKillIntent(baseDir, taskId) {
|
|
|
17319
17366
|
|
|
17320
17367
|
// daemon/prompt.ts
|
|
17321
17368
|
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.";
|
|
17369
|
+
function buildDmNotice(name, email3) {
|
|
17370
|
+
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.`;
|
|
17371
|
+
}
|
|
17322
17372
|
function buildPrompt(task, attachments) {
|
|
17323
17373
|
const obj = { type: task.type, instruction: task.prompt };
|
|
17324
17374
|
if (task.type === "email_notification") {
|
|
17325
|
-
|
|
17375
|
+
const ctx = task.context;
|
|
17376
|
+
const dmUser = ctx?.dmUser;
|
|
17377
|
+
if (ctx?.conversationType === "user_dm_message" && dmUser) {
|
|
17378
|
+
obj.notice = buildDmNotice(dmUser.name, dmUser.email);
|
|
17379
|
+
} else {
|
|
17380
|
+
obj.notice = EMAIL_NOTICE;
|
|
17381
|
+
}
|
|
17326
17382
|
}
|
|
17327
17383
|
if (task.sender) {
|
|
17328
17384
|
obj.sender = {
|
|
@@ -17342,7 +17398,7 @@ function buildPrompt(task, attachments) {
|
|
|
17342
17398
|
}
|
|
17343
17399
|
|
|
17344
17400
|
// daemon/session-runner.ts
|
|
17345
|
-
var ATTACHMENTS_BASE = "
|
|
17401
|
+
var ATTACHMENTS_BASE = tempDir("alook-attachments");
|
|
17346
17402
|
async function writeMarkerFile(workspacesRoot, marker) {
|
|
17347
17403
|
const dir = path.join(workspacesRoot, ".pending_completions");
|
|
17348
17404
|
await mkdir(dir, { recursive: true, mode: 448 });
|
|
@@ -17395,6 +17451,46 @@ async function runSession(input) {
|
|
|
17395
17451
|
const client = new DaemonClient(serverURL);
|
|
17396
17452
|
const backend = createBackend(provider, cliPath);
|
|
17397
17453
|
const { workDir, timelineDir, env } = prepare({ workspacesRoot }, task);
|
|
17454
|
+
const agentBaseDir = path.dirname(timelineDir);
|
|
17455
|
+
await initEntryAsync(timelineDir, createTimelineEntry(task.id, task.prompt, task.type, undefined, process.pid, provider, task.contextKey, input.logFilePath));
|
|
17456
|
+
let killed = false;
|
|
17457
|
+
const earlyOnKill = async () => {
|
|
17458
|
+
if (killed)
|
|
17459
|
+
return;
|
|
17460
|
+
killed = true;
|
|
17461
|
+
log.info(`killed by signal (messages=0, tools=0)`);
|
|
17462
|
+
await cleanupAttachments(task.id);
|
|
17463
|
+
const intent = readKillIntent(agentBaseDir, task.id);
|
|
17464
|
+
clearKillIntent(agentBaseDir, task.id);
|
|
17465
|
+
if (intent?.reason === "superseded") {
|
|
17466
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17467
|
+
entry.pid = null;
|
|
17468
|
+
entry.status = "superseded";
|
|
17469
|
+
entry.successor_task_id = intent.successorTaskId ?? null;
|
|
17470
|
+
entry.supersede_reason = "superseded by newer task";
|
|
17471
|
+
});
|
|
17472
|
+
try {
|
|
17473
|
+
await client.supersedeTask(token, task.id);
|
|
17474
|
+
} catch {}
|
|
17475
|
+
} else if (intent?.reason === "cancelled") {
|
|
17476
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17477
|
+
entry.pid = null;
|
|
17478
|
+
entry.status = "cancelled";
|
|
17479
|
+
entry.errmsg = "cancelled by user";
|
|
17480
|
+
});
|
|
17481
|
+
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);
|
|
17482
|
+
} else {
|
|
17483
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17484
|
+
entry.pid = null;
|
|
17485
|
+
entry.status = "killed";
|
|
17486
|
+
entry.errmsg = "killed by signal";
|
|
17487
|
+
});
|
|
17488
|
+
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);
|
|
17489
|
+
}
|
|
17490
|
+
process.exit(1);
|
|
17491
|
+
};
|
|
17492
|
+
process.on("SIGTERM", earlyOnKill);
|
|
17493
|
+
process.on("SIGINT", earlyOnKill);
|
|
17398
17494
|
const attachmentIds = task.context?.attachment_ids ?? [];
|
|
17399
17495
|
let attachments;
|
|
17400
17496
|
if (attachmentIds.length > 0) {
|
|
@@ -17406,7 +17502,14 @@ async function runSession(input) {
|
|
|
17406
17502
|
await cleanupAttachments(task.id);
|
|
17407
17503
|
const errMsg = `failed to download attachments: ${e}`;
|
|
17408
17504
|
log.error(errMsg);
|
|
17505
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17506
|
+
entry.pid = null;
|
|
17507
|
+
entry.status = "failed";
|
|
17508
|
+
entry.errmsg = errMsg;
|
|
17509
|
+
});
|
|
17409
17510
|
await client.failTask(token, task.id, errMsg);
|
|
17511
|
+
process.removeListener("SIGTERM", earlyOnKill);
|
|
17512
|
+
process.removeListener("SIGINT", earlyOnKill);
|
|
17410
17513
|
return;
|
|
17411
17514
|
}
|
|
17412
17515
|
}
|
|
@@ -17426,7 +17529,9 @@ async function runSession(input) {
|
|
|
17426
17529
|
const earlySessionId = await session2.sessionId;
|
|
17427
17530
|
log.info(`agent started (pid=${agentPid ?? "unknown"}, session=${earlySessionId})`);
|
|
17428
17531
|
log.info(JSON.stringify({ role: "user", type: "text", content: prompt }));
|
|
17429
|
-
|
|
17532
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17533
|
+
entry.session_id = earlySessionId || null;
|
|
17534
|
+
});
|
|
17430
17535
|
const pendingMessages = [];
|
|
17431
17536
|
let seq = 0;
|
|
17432
17537
|
let toolCount = 0;
|
|
@@ -17443,8 +17548,8 @@ async function runSession(input) {
|
|
|
17443
17548
|
}
|
|
17444
17549
|
};
|
|
17445
17550
|
const flushTimer = setInterval(flushMessages, FLUSH_INTERVAL_MS);
|
|
17446
|
-
|
|
17447
|
-
|
|
17551
|
+
process.removeListener("SIGTERM", earlyOnKill);
|
|
17552
|
+
process.removeListener("SIGINT", earlyOnKill);
|
|
17448
17553
|
const onKill = async () => {
|
|
17449
17554
|
if (killed)
|
|
17450
17555
|
return;
|
|
@@ -17531,7 +17636,11 @@ async function runSession(input) {
|
|
|
17531
17636
|
});
|
|
17532
17637
|
if (msg.type === "tool-use")
|
|
17533
17638
|
toolCount++;
|
|
17534
|
-
|
|
17639
|
+
if (msg.type === "tool-result" && msg.output && msg.output.length > 500) {
|
|
17640
|
+
log.info(JSON.stringify({ role: "assistant", ...msg, output: msg.output.slice(0, 500) + `... (${msg.output.length} chars)` }));
|
|
17641
|
+
} else {
|
|
17642
|
+
log.info(JSON.stringify({ role: "assistant", ...msg }));
|
|
17643
|
+
}
|
|
17535
17644
|
if (msg.type === "text" && msg.content) {
|
|
17536
17645
|
updateEntry(timelineDir, task.id, (entry) => {
|
|
17537
17646
|
entry.agent_responses.push(msg.content);
|