@alook/cli 0.0.7 → 0.0.9
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 +236 -76
- package/dist/session-runner.js +40 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// src/index.ts
|
|
18
|
-
import { Command as
|
|
18
|
+
import { Command as Command9 } from "commander";
|
|
19
19
|
|
|
20
20
|
// commands/register.ts
|
|
21
21
|
import { Command } from "commander";
|
|
@@ -167,7 +167,8 @@ function saveCLIConfigForProfile(profile, profileConfig) {
|
|
|
167
167
|
// commands/register.ts
|
|
168
168
|
function isCommandAvailable(cmd) {
|
|
169
169
|
try {
|
|
170
|
-
|
|
170
|
+
const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
171
|
+
execSync(check, { stdio: "ignore" });
|
|
171
172
|
return true;
|
|
172
173
|
} catch {
|
|
173
174
|
return false;
|
|
@@ -295,9 +296,9 @@ function statusCommand() {
|
|
|
295
296
|
|
|
296
297
|
// commands/daemon.ts
|
|
297
298
|
import { Command as Command3 } from "commander";
|
|
298
|
-
import { spawn as
|
|
299
|
+
import { spawn as spawn3 } from "child_process";
|
|
299
300
|
import { openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync4 } from "fs";
|
|
300
|
-
import { dirname as
|
|
301
|
+
import { dirname as dirname4 } from "path";
|
|
301
302
|
|
|
302
303
|
// ../shared/src/constants.ts
|
|
303
304
|
var TASK_TYPES = {
|
|
@@ -13864,6 +13865,7 @@ var ClaimedTaskRowSchema = exports_external.object({
|
|
|
13864
13865
|
result: exports_external.unknown().nullable(),
|
|
13865
13866
|
context: exports_external.unknown().nullable(),
|
|
13866
13867
|
type: exports_external.string().default(TASK_TYPES.USER_DM_MESSAGE),
|
|
13868
|
+
contextKey: exports_external.string().nullable().optional(),
|
|
13867
13869
|
sessionId: exports_external.string().nullable(),
|
|
13868
13870
|
createdAt: exports_external.coerce.date(),
|
|
13869
13871
|
dispatchedAt: exports_external.coerce.date().nullable(),
|
|
@@ -13893,18 +13895,21 @@ var TaskApiBaseSchema = exports_external.object({
|
|
|
13893
13895
|
result: exports_external.unknown().nullable(),
|
|
13894
13896
|
error: exports_external.string().nullable(),
|
|
13895
13897
|
created_at: exports_external.string(),
|
|
13896
|
-
type: exports_external.string()
|
|
13898
|
+
type: exports_external.string(),
|
|
13899
|
+
context_key: exports_external.string().nullable().optional()
|
|
13897
13900
|
});
|
|
13898
13901
|
var TaskApiSchema = TaskApiBaseSchema.extend({
|
|
13899
13902
|
agent: TaskAgentDataApiSchema.nullable().optional()
|
|
13900
13903
|
});
|
|
13901
13904
|
var PollRequestSchema = exports_external.object({
|
|
13902
13905
|
daemon_id: exports_external.string().min(1),
|
|
13903
|
-
max_tasks: exports_external.number().int().min(1).default(1)
|
|
13906
|
+
max_tasks: exports_external.number().int().min(1).default(1),
|
|
13907
|
+
cli_version: exports_external.string().optional()
|
|
13904
13908
|
});
|
|
13905
13909
|
var PollResponseSchema = exports_external.object({
|
|
13906
13910
|
tasks: exports_external.array(TaskApiSchema),
|
|
13907
|
-
evicted: exports_external.boolean().optional()
|
|
13911
|
+
evicted: exports_external.boolean().optional(),
|
|
13912
|
+
pending_update: exports_external.object({ version: exports_external.string() }).optional()
|
|
13908
13913
|
});
|
|
13909
13914
|
var RegisterResponseSchema = exports_external.object({
|
|
13910
13915
|
runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
|
|
@@ -14007,6 +14012,9 @@ var CalendarEventApiSchema = exports_external.object({
|
|
|
14007
14012
|
created_at: exports_external.string(),
|
|
14008
14013
|
updated_at: exports_external.string()
|
|
14009
14014
|
});
|
|
14015
|
+
var AddWhitelistRequestSchema = exports_external.object({
|
|
14016
|
+
email: exports_external.string().email()
|
|
14017
|
+
});
|
|
14010
14018
|
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260418.1_@opentelemetry+api@1.9.1_bun-types@1.3.12_kysely@0.28.16/node_modules/drizzle-orm/entity.js
|
|
14011
14019
|
var entityKind = Symbol.for("drizzle:entityKind");
|
|
14012
14020
|
var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
|
|
@@ -15443,6 +15451,7 @@ var machine = sqliteTable("machine", {
|
|
|
15443
15451
|
deviceInfo: text("device_info").notNull().default(""),
|
|
15444
15452
|
lastSeenAt: text("last_seen_at"),
|
|
15445
15453
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
|
|
15454
|
+
pendingUpdateVersion: text("pending_update_version"),
|
|
15446
15455
|
updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15447
15456
|
}, (t) => [primaryKey({ columns: [t.workspaceId, t.daemonId] })]);
|
|
15448
15457
|
var agentRuntime = sqliteTable("agent_runtime", {
|
|
@@ -15522,6 +15531,7 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
|
|
|
15522
15531
|
conversationId: text("conversation_id").notNull().references(() => conversation.id),
|
|
15523
15532
|
prompt: text("prompt").notNull(),
|
|
15524
15533
|
type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
|
|
15534
|
+
contextKey: text("context_key"),
|
|
15525
15535
|
status: text("status").notNull().default("queued"),
|
|
15526
15536
|
priority: integer2("priority").notNull().default(0),
|
|
15527
15537
|
result: text("result", { mode: "json" }),
|
|
@@ -15626,6 +15636,20 @@ var RESERVED_HANDLES = new Set([
|
|
|
15626
15636
|
"system",
|
|
15627
15637
|
"alook"
|
|
15628
15638
|
]);
|
|
15639
|
+
// ../shared/src/semver.ts
|
|
15640
|
+
function semverGte(a, b) {
|
|
15641
|
+
const pa = a.split(".").map(Number);
|
|
15642
|
+
const pb = b.split(".").map(Number);
|
|
15643
|
+
for (let i = 0;i < Math.max(pa.length, pb.length); i++) {
|
|
15644
|
+
const sa = pa[i] ?? 0;
|
|
15645
|
+
const sb = pb[i] ?? 0;
|
|
15646
|
+
if (sa > sb)
|
|
15647
|
+
return true;
|
|
15648
|
+
if (sa < sb)
|
|
15649
|
+
return false;
|
|
15650
|
+
}
|
|
15651
|
+
return true;
|
|
15652
|
+
}
|
|
15629
15653
|
// daemon/client.ts
|
|
15630
15654
|
class DaemonClient {
|
|
15631
15655
|
baseURL;
|
|
@@ -15657,10 +15681,14 @@ class DaemonClient {
|
|
|
15657
15681
|
daemon_id: daemonId
|
|
15658
15682
|
});
|
|
15659
15683
|
}
|
|
15660
|
-
async poll(token, daemonId, maxTasks) {
|
|
15661
|
-
const raw = await this.request("POST", "/api/daemon/tasks/poll", token, { daemon_id: daemonId, max_tasks: maxTasks });
|
|
15684
|
+
async poll(token, daemonId, maxTasks, cliVersion) {
|
|
15685
|
+
const raw = await this.request("POST", "/api/daemon/tasks/poll", token, { daemon_id: daemonId, max_tasks: maxTasks, ...cliVersion && { cli_version: cliVersion } });
|
|
15662
15686
|
const resp = PollResponseSchema.parse(raw);
|
|
15663
|
-
return {
|
|
15687
|
+
return {
|
|
15688
|
+
tasks: resp.tasks,
|
|
15689
|
+
evicted: resp.evicted ?? false,
|
|
15690
|
+
pending_update: resp.pending_update
|
|
15691
|
+
};
|
|
15664
15692
|
}
|
|
15665
15693
|
startTask(token, taskId) {
|
|
15666
15694
|
return this.request("POST", `/api/daemon/tasks/${taskId}/start`, token);
|
|
@@ -15680,22 +15708,44 @@ class DaemonClient {
|
|
|
15680
15708
|
|
|
15681
15709
|
// daemon/config.ts
|
|
15682
15710
|
import { hostname as hostname4 } from "os";
|
|
15683
|
-
import { join as
|
|
15711
|
+
import { join as join3 } from "path";
|
|
15712
|
+
|
|
15713
|
+
// lib/version.ts
|
|
15714
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
15715
|
+
import { join as join2, dirname } from "path";
|
|
15716
|
+
import { fileURLToPath } from "url";
|
|
15717
|
+
function getCurrentVersion() {
|
|
15718
|
+
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
15719
|
+
const candidates = [
|
|
15720
|
+
join2(__dirname2, "..", "package.json"),
|
|
15721
|
+
join2(__dirname2, "..", "..", "package.json")
|
|
15722
|
+
];
|
|
15723
|
+
for (const candidate of candidates) {
|
|
15724
|
+
try {
|
|
15725
|
+
const pkg = JSON.parse(readFileSync2(candidate, "utf-8"));
|
|
15726
|
+
if (typeof pkg.version === "string")
|
|
15727
|
+
return pkg.version;
|
|
15728
|
+
} catch {}
|
|
15729
|
+
}
|
|
15730
|
+
return "unknown";
|
|
15731
|
+
}
|
|
15732
|
+
|
|
15733
|
+
// daemon/config.ts
|
|
15684
15734
|
function pidFilePath(profile) {
|
|
15685
15735
|
const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
|
|
15686
|
-
return
|
|
15736
|
+
return join3(configDir(), name);
|
|
15687
15737
|
}
|
|
15688
15738
|
function daemonLogDir() {
|
|
15689
|
-
return
|
|
15739
|
+
return join3(configDir(), "daemon", "logs");
|
|
15690
15740
|
}
|
|
15691
15741
|
function sessionRunnerLogDir() {
|
|
15692
|
-
return
|
|
15742
|
+
return join3(configDir(), "daemon", "session-runners");
|
|
15693
15743
|
}
|
|
15694
15744
|
function daemonLogFilePath(date5 = new Date) {
|
|
15695
15745
|
const y = date5.getFullYear();
|
|
15696
15746
|
const m = String(date5.getMonth() + 1).padStart(2, "0");
|
|
15697
15747
|
const d = String(date5.getDate()).padStart(2, "0");
|
|
15698
|
-
return
|
|
15748
|
+
return join3(daemonLogDir(), `${y}-${m}-${d}.log`);
|
|
15699
15749
|
}
|
|
15700
15750
|
function parseDuration(s) {
|
|
15701
15751
|
if (!s)
|
|
@@ -15735,7 +15785,7 @@ function loadDaemonConfig(profile) {
|
|
|
15735
15785
|
if (profile && !daemonId.endsWith(`-${profile}`)) {
|
|
15736
15786
|
daemonId = `${daemonId}-${profile}`;
|
|
15737
15787
|
}
|
|
15738
|
-
const defaultRoot =
|
|
15788
|
+
const defaultRoot = join3(configDir(), profile ? `workspaces_${profile}` : "workspaces");
|
|
15739
15789
|
const workspacesRoot = process.env.ALOOK_WORKSPACES_ROOT || defaultRoot;
|
|
15740
15790
|
return {
|
|
15741
15791
|
serverURL: normalizeServerBaseURL(process.env.ALOOK_SERVER_URL || "https://alook.ai"),
|
|
@@ -15752,7 +15802,7 @@ function loadDaemonConfig(profile) {
|
|
|
15752
15802
|
deviceName: process.env.ALOOK_DAEMON_DEVICE_NAME || h,
|
|
15753
15803
|
runtimeName: process.env.ALOOK_AGENT_RUNTIME_NAME || "Local Agent",
|
|
15754
15804
|
workspacesRoot,
|
|
15755
|
-
cliVersion:
|
|
15805
|
+
cliVersion: getCurrentVersion()
|
|
15756
15806
|
};
|
|
15757
15807
|
}
|
|
15758
15808
|
function normalizeServerBaseURL(url2) {
|
|
@@ -15820,6 +15870,7 @@ function fromApiTask(api2) {
|
|
|
15820
15870
|
status: api2.status,
|
|
15821
15871
|
priority: api2.priority,
|
|
15822
15872
|
type: api2.type,
|
|
15873
|
+
contextKey: api2.context_key ?? null,
|
|
15823
15874
|
agent: api2.agent ? { name: api2.agent.name, instructions: api2.agent.instructions, emailHandle: api2.agent.email_handle ?? undefined, userEmail: api2.agent.user_email ?? undefined, runtimeConfig: api2.agent.runtime_config ?? undefined } : undefined,
|
|
15824
15875
|
repos: undefined,
|
|
15825
15876
|
createdAt: api2.created_at
|
|
@@ -15918,8 +15969,8 @@ function createLogger2(level) {
|
|
|
15918
15969
|
var log = createLogger2();
|
|
15919
15970
|
|
|
15920
15971
|
// daemon/pidfile.ts
|
|
15921
|
-
import { readFileSync as
|
|
15922
|
-
import { dirname } from "path";
|
|
15972
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
|
|
15973
|
+
import { dirname as dirname2 } from "path";
|
|
15923
15974
|
function isProcessAlive(pid) {
|
|
15924
15975
|
try {
|
|
15925
15976
|
process.kill(pid, 0);
|
|
@@ -15930,7 +15981,7 @@ function isProcessAlive(pid) {
|
|
|
15930
15981
|
}
|
|
15931
15982
|
function readDaemonPid(profile) {
|
|
15932
15983
|
try {
|
|
15933
|
-
const content =
|
|
15984
|
+
const content = readFileSync3(pidFilePath(profile), "utf-8").trim();
|
|
15934
15985
|
const pid = parseInt(content, 10);
|
|
15935
15986
|
return Number.isNaN(pid) ? null : pid;
|
|
15936
15987
|
} catch {
|
|
@@ -15940,14 +15991,14 @@ function readDaemonPid(profile) {
|
|
|
15940
15991
|
function acquireDaemonPid(profile) {
|
|
15941
15992
|
const pidPath = pidFilePath(profile);
|
|
15942
15993
|
try {
|
|
15943
|
-
const content =
|
|
15994
|
+
const content = readFileSync3(pidPath, "utf-8").trim();
|
|
15944
15995
|
const existingPid = parseInt(content, 10);
|
|
15945
15996
|
if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
|
|
15946
15997
|
log.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
|
|
15947
15998
|
return false;
|
|
15948
15999
|
}
|
|
15949
16000
|
} catch {}
|
|
15950
|
-
mkdirSync2(
|
|
16001
|
+
mkdirSync2(dirname2(pidPath), { recursive: true, mode: 448 });
|
|
15951
16002
|
writeFileSync2(pidPath, String(process.pid), { mode: 384 });
|
|
15952
16003
|
return true;
|
|
15953
16004
|
}
|
|
@@ -15964,16 +16015,75 @@ function releaseDaemonPid(profile) {
|
|
|
15964
16015
|
removePidFileIfMatches(process.pid, profile);
|
|
15965
16016
|
}
|
|
15966
16017
|
|
|
16018
|
+
// lib/update.ts
|
|
16019
|
+
import { spawn } from "child_process";
|
|
16020
|
+
function fetchLatestVersion() {
|
|
16021
|
+
return fetch("https://registry.npmjs.org/@alook/cli/latest").then((res) => {
|
|
16022
|
+
if (!res.ok)
|
|
16023
|
+
return null;
|
|
16024
|
+
return res.json();
|
|
16025
|
+
}).then((data) => data?.version ?? null).catch(() => null);
|
|
16026
|
+
}
|
|
16027
|
+
function runNpmUpdate(targetVersion) {
|
|
16028
|
+
return new Promise((resolve) => {
|
|
16029
|
+
const chunks = [];
|
|
16030
|
+
const child = spawn("npm", ["install", "-g", `@alook/cli@${targetVersion}`], {
|
|
16031
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
16032
|
+
});
|
|
16033
|
+
child.stdout?.on("data", (d) => chunks.push(d));
|
|
16034
|
+
child.stderr?.on("data", (d) => chunks.push(d));
|
|
16035
|
+
child.on("error", (err) => {
|
|
16036
|
+
resolve({ success: false, output: err.message });
|
|
16037
|
+
});
|
|
16038
|
+
child.on("close", (code) => {
|
|
16039
|
+
const output = Buffer.concat(chunks).toString();
|
|
16040
|
+
resolve({ success: code === 0, output });
|
|
16041
|
+
});
|
|
16042
|
+
});
|
|
16043
|
+
}
|
|
16044
|
+
|
|
16045
|
+
// daemon/update-handler.ts
|
|
16046
|
+
var updating = false;
|
|
16047
|
+
var retryCount = 0;
|
|
16048
|
+
var MAX_RETRIES = 3;
|
|
16049
|
+
function isUpdating() {
|
|
16050
|
+
return updating;
|
|
16051
|
+
}
|
|
16052
|
+
async function handleCliUpdate(version3, onSuccess) {
|
|
16053
|
+
if (updating)
|
|
16054
|
+
return;
|
|
16055
|
+
if (retryCount >= MAX_RETRIES)
|
|
16056
|
+
return;
|
|
16057
|
+
updating = true;
|
|
16058
|
+
try {
|
|
16059
|
+
log.info(`Updating CLI to v${version3}...`);
|
|
16060
|
+
const result = await runNpmUpdate(version3);
|
|
16061
|
+
if (result.success) {
|
|
16062
|
+
log.info(`CLI updated to v${version3} — restarting`);
|
|
16063
|
+
onSuccess();
|
|
16064
|
+
} else {
|
|
16065
|
+
retryCount++;
|
|
16066
|
+
log.error(`CLI update failed (attempt ${retryCount}/${MAX_RETRIES}): ${result.output}`);
|
|
16067
|
+
}
|
|
16068
|
+
} catch (e) {
|
|
16069
|
+
retryCount++;
|
|
16070
|
+
log.error(`CLI update error (attempt ${retryCount}/${MAX_RETRIES})`, e);
|
|
16071
|
+
} finally {
|
|
16072
|
+
updating = false;
|
|
16073
|
+
}
|
|
16074
|
+
}
|
|
16075
|
+
|
|
15967
16076
|
// daemon/daemon.ts
|
|
15968
16077
|
import { existsSync, mkdirSync as mkdirSync3, openSync, closeSync, renameSync, readdirSync, statSync, unlinkSync as unlinkSync2 } from "fs";
|
|
15969
|
-
import { execSync as execSync3, spawn } from "child_process";
|
|
15970
|
-
import { fileURLToPath } from "url";
|
|
15971
|
-
import { dirname as
|
|
15972
|
-
var _dir =
|
|
15973
|
-
var sessionRunnerPath = existsSync(
|
|
16078
|
+
import { execSync as execSync3, spawn as spawn2 } from "child_process";
|
|
16079
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
16080
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
16081
|
+
var _dir = dirname3(fileURLToPath2(import.meta.url));
|
|
16082
|
+
var sessionRunnerPath = existsSync(join4(_dir, "session-runner.js")) ? join4(_dir, "session-runner.js") : join4(_dir, "session-runner.ts");
|
|
15974
16083
|
function isCommandAvailable2(cmd) {
|
|
15975
16084
|
try {
|
|
15976
|
-
|
|
16085
|
+
const check2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
16086
|
+
execSync3(check2, { stdio: "ignore" });
|
|
15977
16087
|
return true;
|
|
15978
16088
|
} catch {
|
|
15979
16089
|
return false;
|
|
@@ -15991,7 +16101,7 @@ function pruneSessionRunnerLogs() {
|
|
|
15991
16101
|
if (entries.length <= MAX_SESSION_RUNNER_LOGS)
|
|
15992
16102
|
return;
|
|
15993
16103
|
const withMtime = entries.map((name) => {
|
|
15994
|
-
const full =
|
|
16104
|
+
const full = join4(logDir, name);
|
|
15995
16105
|
try {
|
|
15996
16106
|
return { name, mtime: statSync(full).mtimeMs };
|
|
15997
16107
|
} catch {
|
|
@@ -16001,7 +16111,7 @@ function pruneSessionRunnerLogs() {
|
|
|
16001
16111
|
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
16002
16112
|
for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
|
|
16003
16113
|
try {
|
|
16004
|
-
unlinkSync2(
|
|
16114
|
+
unlinkSync2(join4(logDir, entry.name));
|
|
16005
16115
|
} catch {}
|
|
16006
16116
|
}
|
|
16007
16117
|
}
|
|
@@ -16074,7 +16184,16 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16074
16184
|
runtimes
|
|
16075
16185
|
});
|
|
16076
16186
|
} catch (e) {
|
|
16077
|
-
|
|
16187
|
+
if (e instanceof Error && e.message.startsWith("HTTP 401")) {
|
|
16188
|
+
log.warn(`Workspace ${ws.id} token invalid — removing from config`);
|
|
16189
|
+
try {
|
|
16190
|
+
const cfg = loadCLIConfigForProfile(profile);
|
|
16191
|
+
cfg.watched_workspaces = (cfg.watched_workspaces || []).filter((w) => w.id !== ws.id);
|
|
16192
|
+
saveCLIConfigForProfile(profile, cfg);
|
|
16193
|
+
} catch {}
|
|
16194
|
+
} else {
|
|
16195
|
+
log.error(`Failed to register workspace ${ws.id}, skipping`, e);
|
|
16196
|
+
}
|
|
16078
16197
|
continue;
|
|
16079
16198
|
}
|
|
16080
16199
|
log.info(`Workspace ${ws.id} registered — ${resp.runtimes.length} runtime(s)`);
|
|
@@ -16147,11 +16266,14 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16147
16266
|
await new Promise((r) => setTimeout(r, staggerMs));
|
|
16148
16267
|
}
|
|
16149
16268
|
try {
|
|
16150
|
-
const { tasks: apiTasks, evicted } = await client.poll(ws.token, config2.daemonId, remaining);
|
|
16269
|
+
const { tasks: apiTasks, evicted, pending_update } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
|
|
16151
16270
|
if (evicted) {
|
|
16152
16271
|
evictedIds.push(ws.workspaceId);
|
|
16153
16272
|
continue;
|
|
16154
16273
|
}
|
|
16274
|
+
if (pending_update && !isUpdating()) {
|
|
16275
|
+
handleCliUpdate(pending_update.version, () => requestRestart());
|
|
16276
|
+
}
|
|
16155
16277
|
for (const apiTask of apiTasks) {
|
|
16156
16278
|
const task = fromApiTask(apiTask);
|
|
16157
16279
|
syncAgentId(task.agentId, ws.workspaceId);
|
|
@@ -16163,7 +16285,11 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16163
16285
|
});
|
|
16164
16286
|
}
|
|
16165
16287
|
} catch (e) {
|
|
16166
|
-
|
|
16288
|
+
if (e instanceof Error && e.message.startsWith("HTTP 401")) {
|
|
16289
|
+
evictedIds.push(ws.workspaceId);
|
|
16290
|
+
} else {
|
|
16291
|
+
log.debug("Poll error", e);
|
|
16292
|
+
}
|
|
16167
16293
|
}
|
|
16168
16294
|
}
|
|
16169
16295
|
for (const id of evictedIds) {
|
|
@@ -16176,23 +16302,42 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16176
16302
|
};
|
|
16177
16303
|
const pollTimer = setInterval(pollCycle, config2.pollInterval);
|
|
16178
16304
|
let shuttingDown = false;
|
|
16305
|
+
let restartRequested = false;
|
|
16306
|
+
const requestRestart = () => {
|
|
16307
|
+
restartRequested = true;
|
|
16308
|
+
shutdown();
|
|
16309
|
+
};
|
|
16179
16310
|
const shutdown = async () => {
|
|
16180
16311
|
if (shuttingDown)
|
|
16181
16312
|
return;
|
|
16182
16313
|
shuttingDown = true;
|
|
16183
|
-
log.info("Shutting down...");
|
|
16314
|
+
log.info(restartRequested ? "Restarting..." : "Shutting down...");
|
|
16184
16315
|
clearInterval(pollTimer);
|
|
16185
|
-
const shutdownMs = Number(process.env.ALOOK_SHUTDOWN_TIMEOUT_MS) || 5000;
|
|
16316
|
+
const shutdownMs = restartRequested ? 30000 : Number(process.env.ALOOK_SHUTDOWN_TIMEOUT_MS) || 5000;
|
|
16186
16317
|
const timeout = setTimeout(() => process.exit(1), shutdownMs);
|
|
16187
16318
|
try {
|
|
16188
16319
|
for (const ws of workspaceStates) {
|
|
16189
16320
|
await client.deregister(ws.token, config2.daemonId);
|
|
16190
16321
|
}
|
|
16191
16322
|
} catch {}
|
|
16192
|
-
clearTimeout(timeout);
|
|
16193
16323
|
releaseDaemonPid(profile);
|
|
16194
|
-
health.server.close()
|
|
16195
|
-
|
|
16324
|
+
health.server.close(() => {
|
|
16325
|
+
if (restartRequested) {
|
|
16326
|
+
const args = ["daemon", "start", "--foreground"];
|
|
16327
|
+
if (profile)
|
|
16328
|
+
args.push("--profile", profile);
|
|
16329
|
+
if (serverUrl)
|
|
16330
|
+
args.push("--server", serverUrl);
|
|
16331
|
+
const child = spawn2("alook", args, {
|
|
16332
|
+
detached: true,
|
|
16333
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
16334
|
+
});
|
|
16335
|
+
child.unref();
|
|
16336
|
+
log.info(`Spawned new daemon (pid=${child.pid})`);
|
|
16337
|
+
}
|
|
16338
|
+
clearTimeout(timeout);
|
|
16339
|
+
process.exit(0);
|
|
16340
|
+
});
|
|
16196
16341
|
};
|
|
16197
16342
|
process.on("SIGTERM", shutdown);
|
|
16198
16343
|
process.on("SIGINT", shutdown);
|
|
@@ -16202,16 +16347,16 @@ function spawnSessionRunner(input) {
|
|
|
16202
16347
|
const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
|
|
16203
16348
|
const logDir = sessionRunnerLogDir();
|
|
16204
16349
|
mkdirSync3(logDir, { recursive: true });
|
|
16205
|
-
const tmpLogPath =
|
|
16350
|
+
const tmpLogPath = join4(logDir, `${input.task.id}.log`);
|
|
16206
16351
|
const fd = openSync(tmpLogPath, "a");
|
|
16207
|
-
const child =
|
|
16352
|
+
const child = spawn2(process.execPath, [sessionRunnerPath, encoded], {
|
|
16208
16353
|
detached: true,
|
|
16209
16354
|
stdio: ["ignore", fd, fd]
|
|
16210
16355
|
});
|
|
16211
16356
|
child.unref();
|
|
16212
16357
|
closeSync(fd);
|
|
16213
16358
|
if (child.pid) {
|
|
16214
|
-
const pidLogPath =
|
|
16359
|
+
const pidLogPath = join4(logDir, `${child.pid}.log`);
|
|
16215
16360
|
renameSync(tmpLogPath, pidLogPath);
|
|
16216
16361
|
}
|
|
16217
16362
|
return child;
|
|
@@ -16286,9 +16431,9 @@ async function startInBackground(profile, serverUrl) {
|
|
|
16286
16431
|
return;
|
|
16287
16432
|
}
|
|
16288
16433
|
const logPath = daemonLogFilePath();
|
|
16289
|
-
mkdirSync4(
|
|
16434
|
+
mkdirSync4(dirname4(logPath), { recursive: true, mode: 448 });
|
|
16290
16435
|
const logFd = openSync2(logPath, "a", 384);
|
|
16291
|
-
const child =
|
|
16436
|
+
const child = spawn3(process.execPath, buildChildArgs(profile, serverUrl), {
|
|
16292
16437
|
detached: true,
|
|
16293
16438
|
stdio: ["ignore", logFd, logFd]
|
|
16294
16439
|
});
|
|
@@ -16396,8 +16541,8 @@ function configCommand() {
|
|
|
16396
16541
|
|
|
16397
16542
|
// commands/email.ts
|
|
16398
16543
|
import { Command as Command5 } from "commander";
|
|
16399
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, readFileSync as
|
|
16400
|
-
import { basename, join as
|
|
16544
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
16545
|
+
import { basename, join as join5 } from "path";
|
|
16401
16546
|
import PostalMime from "postal-mime";
|
|
16402
16547
|
var VALID_STATUSES = ["unread", "read", "archived"];
|
|
16403
16548
|
var EMAIL_DIR = "/tmp/alook-emails";
|
|
@@ -16473,7 +16618,7 @@ function emailCommand() {
|
|
|
16473
16618
|
mkdirSync5(EMAIL_DIR, { recursive: true });
|
|
16474
16619
|
const downloadedPaths = [];
|
|
16475
16620
|
for (const email3 of emails2) {
|
|
16476
|
-
const emailDir =
|
|
16621
|
+
const emailDir = join5(EMAIL_DIR, email3.id);
|
|
16477
16622
|
mkdirSync5(emailDir, { recursive: true });
|
|
16478
16623
|
const metadata = {
|
|
16479
16624
|
id: email3.id,
|
|
@@ -16486,7 +16631,7 @@ function emailCommand() {
|
|
|
16486
16631
|
in_reply_to: email3.in_reply_to || "",
|
|
16487
16632
|
references: email3.references || ""
|
|
16488
16633
|
};
|
|
16489
|
-
const metadataPath =
|
|
16634
|
+
const metadataPath = join5(emailDir, "metadata.json");
|
|
16490
16635
|
writeFileSync3(metadataPath, JSON.stringify(metadata, null, 2));
|
|
16491
16636
|
downloadedPaths.push(metadataPath);
|
|
16492
16637
|
let rawMime;
|
|
@@ -16502,17 +16647,17 @@ function emailCommand() {
|
|
|
16502
16647
|
}
|
|
16503
16648
|
const parsed = await new PostalMime().parse(rawMime);
|
|
16504
16649
|
if (parsed.text) {
|
|
16505
|
-
const bodyPath =
|
|
16650
|
+
const bodyPath = join5(emailDir, "body.txt");
|
|
16506
16651
|
writeFileSync3(bodyPath, parsed.text);
|
|
16507
16652
|
downloadedPaths.push(bodyPath);
|
|
16508
16653
|
}
|
|
16509
16654
|
if (parsed.html) {
|
|
16510
|
-
const htmlPath =
|
|
16655
|
+
const htmlPath = join5(emailDir, "body.html");
|
|
16511
16656
|
writeFileSync3(htmlPath, parsed.html);
|
|
16512
16657
|
downloadedPaths.push(htmlPath);
|
|
16513
16658
|
}
|
|
16514
16659
|
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
16515
|
-
const attDir =
|
|
16660
|
+
const attDir = join5(emailDir, "attachments");
|
|
16516
16661
|
mkdirSync5(attDir, { recursive: true });
|
|
16517
16662
|
const usedFilenames = new Set;
|
|
16518
16663
|
for (let i = 0;i < parsed.attachments.length; i++) {
|
|
@@ -16522,7 +16667,7 @@ function emailCommand() {
|
|
|
16522
16667
|
filename = `${i}-${filename}`;
|
|
16523
16668
|
}
|
|
16524
16669
|
usedFilenames.add(filename);
|
|
16525
|
-
const attPath =
|
|
16670
|
+
const attPath = join5(attDir, filename);
|
|
16526
16671
|
const content = att.content;
|
|
16527
16672
|
let buf;
|
|
16528
16673
|
if (typeof content === "string") {
|
|
@@ -16571,7 +16716,7 @@ function emailCommand() {
|
|
|
16571
16716
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
16572
16717
|
let htmlBody;
|
|
16573
16718
|
try {
|
|
16574
|
-
htmlBody =
|
|
16719
|
+
htmlBody = readFileSync4(opts.bodyFile, "utf-8");
|
|
16575
16720
|
} catch (err) {
|
|
16576
16721
|
console.error(`Error: cannot read body file "${opts.bodyFile}": ${err instanceof Error ? err.message : err}`);
|
|
16577
16722
|
process.exit(1);
|
|
@@ -16587,7 +16732,7 @@ function emailCommand() {
|
|
|
16587
16732
|
let bytes;
|
|
16588
16733
|
let size;
|
|
16589
16734
|
try {
|
|
16590
|
-
bytes =
|
|
16735
|
+
bytes = readFileSync4(path);
|
|
16591
16736
|
size = statSync2(path).size;
|
|
16592
16737
|
} catch (err) {
|
|
16593
16738
|
console.error(`Error: cannot read attachment "${path}": ${err instanceof Error ? err.message : err}`);
|
|
@@ -16846,28 +16991,6 @@ function calendarCommand() {
|
|
|
16846
16991
|
|
|
16847
16992
|
// commands/version.ts
|
|
16848
16993
|
import { Command as Command7 } from "commander";
|
|
16849
|
-
|
|
16850
|
-
// lib/version.ts
|
|
16851
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
16852
|
-
import { join as join5, dirname as dirname4 } from "path";
|
|
16853
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
16854
|
-
function getCurrentVersion() {
|
|
16855
|
-
const __dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
16856
|
-
const candidates = [
|
|
16857
|
-
join5(__dirname2, "..", "package.json"),
|
|
16858
|
-
join5(__dirname2, "..", "..", "package.json")
|
|
16859
|
-
];
|
|
16860
|
-
for (const candidate of candidates) {
|
|
16861
|
-
try {
|
|
16862
|
-
const pkg = JSON.parse(readFileSync4(candidate, "utf-8"));
|
|
16863
|
-
if (typeof pkg.version === "string")
|
|
16864
|
-
return pkg.version;
|
|
16865
|
-
} catch {}
|
|
16866
|
-
}
|
|
16867
|
-
return "unknown";
|
|
16868
|
-
}
|
|
16869
|
-
|
|
16870
|
-
// commands/version.ts
|
|
16871
16994
|
function versionCommand() {
|
|
16872
16995
|
const cmd = new Command7("version").description("Show CLI version").action(() => {
|
|
16873
16996
|
console.log(`alook version ${getCurrentVersion()}`);
|
|
@@ -16875,8 +16998,44 @@ function versionCommand() {
|
|
|
16875
16998
|
return cmd;
|
|
16876
16999
|
}
|
|
16877
17000
|
|
|
17001
|
+
// commands/update.ts
|
|
17002
|
+
import { Command as Command8 } from "commander";
|
|
17003
|
+
function updateCommand() {
|
|
17004
|
+
const cmd = new Command8("update").description("Update CLI to the latest version").action(async () => {
|
|
17005
|
+
const current = getCurrentVersion();
|
|
17006
|
+
console.log(`Current version: ${current}`);
|
|
17007
|
+
const latest = await fetchLatestVersion();
|
|
17008
|
+
if (!latest) {
|
|
17009
|
+
console.error("Failed to fetch latest version from npm registry.");
|
|
17010
|
+
process.exit(1);
|
|
17011
|
+
return;
|
|
17012
|
+
}
|
|
17013
|
+
if (semverGte(current, latest)) {
|
|
17014
|
+
console.log(`Already up to date (v${current}).`);
|
|
17015
|
+
return;
|
|
17016
|
+
}
|
|
17017
|
+
const healthPort = Number(process.env.ALOOK_HEALTH_PORT) || 19514;
|
|
17018
|
+
try {
|
|
17019
|
+
const res = await fetch(`http://127.0.0.1:${healthPort}/health`);
|
|
17020
|
+
if (res.ok) {
|
|
17021
|
+
console.warn("Warning: daemon is running on the old version. After update, restart with: alook daemon restart");
|
|
17022
|
+
}
|
|
17023
|
+
} catch {}
|
|
17024
|
+
console.log(`Updating to v${latest}...`);
|
|
17025
|
+
const result = await runNpmUpdate(latest);
|
|
17026
|
+
if (result.success) {
|
|
17027
|
+
console.log(`Updated successfully: v${current} → v${latest}`);
|
|
17028
|
+
} else {
|
|
17029
|
+
console.error(`Update failed:
|
|
17030
|
+
${result.output}`);
|
|
17031
|
+
process.exit(1);
|
|
17032
|
+
}
|
|
17033
|
+
});
|
|
17034
|
+
return cmd;
|
|
17035
|
+
}
|
|
17036
|
+
|
|
16878
17037
|
// src/index.ts
|
|
16879
|
-
var program = new
|
|
17038
|
+
var program = new Command9;
|
|
16880
17039
|
program.name("alook").description("Alook CLI").option("--server <url>", "Server URL").option("--profile <name>", "Profile name");
|
|
16881
17040
|
program.addCommand(registerCommand());
|
|
16882
17041
|
program.addCommand(statusCommand());
|
|
@@ -16885,4 +17044,5 @@ program.addCommand(emailCommand());
|
|
|
16885
17044
|
program.addCommand(calendarCommand());
|
|
16886
17045
|
program.addCommand(configCommand());
|
|
16887
17046
|
program.addCommand(versionCommand());
|
|
17047
|
+
program.addCommand(updateCommand());
|
|
16888
17048
|
program.parse();
|
package/dist/session-runner.js
CHANGED
|
@@ -13581,6 +13581,7 @@ var ClaimedTaskRowSchema = exports_external.object({
|
|
|
13581
13581
|
result: exports_external.unknown().nullable(),
|
|
13582
13582
|
context: exports_external.unknown().nullable(),
|
|
13583
13583
|
type: exports_external.string().default(TASK_TYPES.USER_DM_MESSAGE),
|
|
13584
|
+
contextKey: exports_external.string().nullable().optional(),
|
|
13584
13585
|
sessionId: exports_external.string().nullable(),
|
|
13585
13586
|
createdAt: exports_external.coerce.date(),
|
|
13586
13587
|
dispatchedAt: exports_external.coerce.date().nullable(),
|
|
@@ -13610,18 +13611,21 @@ var TaskApiBaseSchema = exports_external.object({
|
|
|
13610
13611
|
result: exports_external.unknown().nullable(),
|
|
13611
13612
|
error: exports_external.string().nullable(),
|
|
13612
13613
|
created_at: exports_external.string(),
|
|
13613
|
-
type: exports_external.string()
|
|
13614
|
+
type: exports_external.string(),
|
|
13615
|
+
context_key: exports_external.string().nullable().optional()
|
|
13614
13616
|
});
|
|
13615
13617
|
var TaskApiSchema = TaskApiBaseSchema.extend({
|
|
13616
13618
|
agent: TaskAgentDataApiSchema.nullable().optional()
|
|
13617
13619
|
});
|
|
13618
13620
|
var PollRequestSchema = exports_external.object({
|
|
13619
13621
|
daemon_id: exports_external.string().min(1),
|
|
13620
|
-
max_tasks: exports_external.number().int().min(1).default(1)
|
|
13622
|
+
max_tasks: exports_external.number().int().min(1).default(1),
|
|
13623
|
+
cli_version: exports_external.string().optional()
|
|
13621
13624
|
});
|
|
13622
13625
|
var PollResponseSchema = exports_external.object({
|
|
13623
13626
|
tasks: exports_external.array(TaskApiSchema),
|
|
13624
|
-
evicted: exports_external.boolean().optional()
|
|
13627
|
+
evicted: exports_external.boolean().optional(),
|
|
13628
|
+
pending_update: exports_external.object({ version: exports_external.string() }).optional()
|
|
13625
13629
|
});
|
|
13626
13630
|
var RegisterResponseSchema = exports_external.object({
|
|
13627
13631
|
runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
|
|
@@ -13724,6 +13728,9 @@ var CalendarEventApiSchema = exports_external.object({
|
|
|
13724
13728
|
created_at: exports_external.string(),
|
|
13725
13729
|
updated_at: exports_external.string()
|
|
13726
13730
|
});
|
|
13731
|
+
var AddWhitelistRequestSchema = exports_external.object({
|
|
13732
|
+
email: exports_external.string().email()
|
|
13733
|
+
});
|
|
13727
13734
|
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260418.1_@opentelemetry+api@1.9.1_bun-types@1.3.12_kysely@0.28.16/node_modules/drizzle-orm/entity.js
|
|
13728
13735
|
var entityKind = Symbol.for("drizzle:entityKind");
|
|
13729
13736
|
var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
|
|
@@ -15160,6 +15167,7 @@ var machine = sqliteTable("machine", {
|
|
|
15160
15167
|
deviceInfo: text("device_info").notNull().default(""),
|
|
15161
15168
|
lastSeenAt: text("last_seen_at"),
|
|
15162
15169
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
|
|
15170
|
+
pendingUpdateVersion: text("pending_update_version"),
|
|
15163
15171
|
updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15164
15172
|
}, (t) => [primaryKey({ columns: [t.workspaceId, t.daemonId] })]);
|
|
15165
15173
|
var agentRuntime = sqliteTable("agent_runtime", {
|
|
@@ -15239,6 +15247,7 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
|
|
|
15239
15247
|
conversationId: text("conversation_id").notNull().references(() => conversation.id),
|
|
15240
15248
|
prompt: text("prompt").notNull(),
|
|
15241
15249
|
type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
|
|
15250
|
+
contextKey: text("context_key"),
|
|
15242
15251
|
status: text("status").notNull().default("queued"),
|
|
15243
15252
|
priority: integer2("priority").notNull().default(0),
|
|
15244
15253
|
result: text("result", { mode: "json" }),
|
|
@@ -15377,10 +15386,14 @@ class DaemonClient {
|
|
|
15377
15386
|
daemon_id: daemonId
|
|
15378
15387
|
});
|
|
15379
15388
|
}
|
|
15380
|
-
async poll(token, daemonId, maxTasks) {
|
|
15381
|
-
const raw = await this.request("POST", "/api/daemon/tasks/poll", token, { daemon_id: daemonId, max_tasks: maxTasks });
|
|
15389
|
+
async poll(token, daemonId, maxTasks, cliVersion) {
|
|
15390
|
+
const raw = await this.request("POST", "/api/daemon/tasks/poll", token, { daemon_id: daemonId, max_tasks: maxTasks, ...cliVersion && { cli_version: cliVersion } });
|
|
15382
15391
|
const resp = PollResponseSchema.parse(raw);
|
|
15383
|
-
return {
|
|
15392
|
+
return {
|
|
15393
|
+
tasks: resp.tasks,
|
|
15394
|
+
evicted: resp.evicted ?? false,
|
|
15395
|
+
pending_update: resp.pending_update
|
|
15396
|
+
};
|
|
15384
15397
|
}
|
|
15385
15398
|
startTask(token, taskId) {
|
|
15386
15399
|
return this.request("POST", `/api/daemon/tasks/${taskId}/start`, token);
|
|
@@ -16340,8 +16353,8 @@ var SYSTEM_PROMPT_BODY = `## Memory Management
|
|
|
16340
16353
|
- For SPECIFIC yet LONG rules or pattern, write to experiences/[NAME].md, and add index to ./memory.md for later recall.
|
|
16341
16354
|
### whats is ESSENTIAL and SHORT Memory?
|
|
16342
16355
|
- basic user profile, e.g.:
|
|
16343
|
-
- "user name is
|
|
16344
|
-
- "user is working on
|
|
16356
|
+
- "user name is ..."
|
|
16357
|
+
- "user is working on ..."
|
|
16345
16358
|
- certain local project mapping, e.g.:
|
|
16346
16359
|
- "alook means the project under /user/home/alook/"
|
|
16347
16360
|
- when to read certain stuff, e.g.:
|
|
@@ -16378,6 +16391,7 @@ those json are sorted by datetime in asc order.
|
|
|
16378
16391
|
- When you start a new task, read the last ~10 lines of today's timeline to understand what has been asked and done recently.
|
|
16379
16392
|
- if you don't know the current datetime, obtain the current datetime first.
|
|
16380
16393
|
- When user ask you something you don't have in your current context, try to read the timeline jsonl files for answer (today or previous days).
|
|
16394
|
+
- When access other local projects, make sure you read the CLAUDE.md/AGENTS.md file under the project root dir to understand the requirements.
|
|
16381
16395
|
`;
|
|
16382
16396
|
function buildInstructionContent(task) {
|
|
16383
16397
|
const displayName = task.agent?.name || "Alook Agent";
|
|
@@ -16431,18 +16445,22 @@ To reply to an email, add '--in-reply-to <EMAIL_ID>' to the send command. This s
|
|
|
16431
16445
|
### Calendar
|
|
16432
16446
|
You have your own calendar to setup daily routines and reminders.
|
|
16433
16447
|
Schedule future tasks for yourself. At the scheduled time, a new task is dispatched to you with the event as the prompt (task type 'calendar_event').
|
|
16448
|
+
|
|
16449
|
+
!USE Calendar when you think the tasks are recurring or it should be conducted in the future.
|
|
16450
|
+
!When scheduling calendar events relative to a weekday (e.g. "every Monday"), always run date '+%A' first to confirm today's weekday before calculating the target date
|
|
16434
16451
|
---
|
|
16452
|
+
Keep the event title informative and concise, less than 20 words.
|
|
16453
|
+
Place the event details in description.
|
|
16435
16454
|
Create a one-off event:
|
|
16436
|
-
- Run 'npx @alook/cli calendar set --agent_id ${task.agentId} --event_title "<
|
|
16455
|
+
- Run 'npx @alook/cli calendar set --agent_id ${task.agentId} --event_title "<TASK_TITLE>" --description "<TASK_BODY>" --datetime <YYYY-MM-DDTHH:MM>'
|
|
16437
16456
|
- '--datetime' is LOCAL time, format 'YYYY-MM-DDTHH:MM' (e.g. '2026-04-17T09:30'). Do NOT pass UTC / ISO strings with 'Z'.
|
|
16438
16457
|
- '--event_title' becomes the task prompt when the event fires — write it as the instruction you want future-you to receive.
|
|
16439
|
-
- Optional '--description "<text>"' — longer notes/context shown alongside the event in the web UI. Use it for anything that wouldn't fit cleanly in the title.
|
|
16440
16458
|
|
|
16441
16459
|
Create a repeating event:
|
|
16442
16460
|
- Add '--repeat <interval>' where interval is like '1day', '2hour', '1week', '1month'.
|
|
16443
16461
|
- Optionally add '--repeat_stop_date <YYYY-MM-DD>' to stop the recurrence (local date).
|
|
16444
|
-
- Example: 'npx @alook/cli calendar set --agent_id ${task.agentId} --event_title "
|
|
16445
|
-
|
|
16462
|
+
- Example: 'npx @alook/cli calendar set --agent_id ${task.agentId} --event_title "<REPEAT_TASK_TITLE>" --description "<REPEAT_TASK_BODY>" --datetime 2026-04-18T09:00 --repeat 1day --repeat_stop_date 2026-05-18'
|
|
16463
|
+
---
|
|
16446
16464
|
List upcoming events:
|
|
16447
16465
|
- Run 'npx @alook/cli calendar list --agent_id ${task.agentId}' (defaults: next 30 days, past 0 days).
|
|
16448
16466
|
- Tune the window with '--future_days <N>' and '--past_days <N>'. Add '--json' for machine-readable output.
|
|
@@ -16775,9 +16793,10 @@ function updateEntry(timelineDir, taskId, updater) {
|
|
|
16775
16793
|
}
|
|
16776
16794
|
log.debug(`Timeline updateEntry: task_id ${taskId} not found in last 7 days`);
|
|
16777
16795
|
}
|
|
16778
|
-
function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider) {
|
|
16796
|
+
function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider, contextKey) {
|
|
16779
16797
|
return {
|
|
16780
16798
|
task_id: taskId,
|
|
16799
|
+
context_key: contextKey ?? null,
|
|
16781
16800
|
session_id: sessionId || null,
|
|
16782
16801
|
pid: pid ?? null,
|
|
16783
16802
|
status: "running",
|
|
@@ -16790,7 +16809,9 @@ function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider) {
|
|
|
16790
16809
|
};
|
|
16791
16810
|
}
|
|
16792
16811
|
var DEFAULT_RESUME_MAX_AGE_MS = 3 * 60 * 60 * 1000;
|
|
16793
|
-
|
|
16812
|
+
var EMAIL_RESUME_MAX_AGE_MS = 48 * 60 * 60 * 1000;
|
|
16813
|
+
function findResumableSessionByContextKey(timelineDir, contextKey, provider) {
|
|
16814
|
+
const maxAgeMs = contextKey.startsWith("email:") ? EMAIL_RESUME_MAX_AGE_MS : DEFAULT_RESUME_MAX_AGE_MS;
|
|
16794
16815
|
const now = new Date;
|
|
16795
16816
|
const cutoff = new Date(now.getTime() - maxAgeMs);
|
|
16796
16817
|
const daysToScan = Math.ceil(maxAgeMs / 86400000) + 1;
|
|
@@ -16800,7 +16821,7 @@ function findResumableSessionId(timelineDir, type, provider, maxAgeMs = DEFAULT_
|
|
|
16800
16821
|
}
|
|
16801
16822
|
entries.sort((a, b) => new Date(b.datetime).getTime() - new Date(a.datetime).getTime());
|
|
16802
16823
|
for (const entry of entries) {
|
|
16803
|
-
if (entry.status === "completed" && entry.
|
|
16824
|
+
if (entry.status === "completed" && entry.context_key === contextKey && entry.provider === provider && entry.session_id && new Date(entry.datetime) >= cutoff) {
|
|
16804
16825
|
return entry.session_id;
|
|
16805
16826
|
}
|
|
16806
16827
|
}
|
|
@@ -16838,9 +16859,9 @@ async function runSession(input) {
|
|
|
16838
16859
|
const backend = createBackend(provider, cliPath);
|
|
16839
16860
|
const prompt = buildPrompt(task);
|
|
16840
16861
|
const { workDir, logFile, timelineDir, env } = prepare({ workspacesRoot }, task);
|
|
16841
|
-
const resumeSessionId = task.
|
|
16862
|
+
const resumeSessionId = task.contextKey ? findResumableSessionByContextKey(timelineDir, task.contextKey, provider) ?? undefined : undefined;
|
|
16842
16863
|
if (resumeSessionId) {
|
|
16843
|
-
log.info(`Task ${task.id} resuming session ${resumeSessionId}`);
|
|
16864
|
+
log.info(`Task ${task.id} resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
|
|
16844
16865
|
}
|
|
16845
16866
|
const session2 = backend.execute(prompt, {
|
|
16846
16867
|
cwd: workDir,
|
|
@@ -16851,11 +16872,11 @@ async function runSession(input) {
|
|
|
16851
16872
|
});
|
|
16852
16873
|
const agentPid = session2.pid;
|
|
16853
16874
|
const earlySessionId = await session2.sessionId;
|
|
16854
|
-
await initEntryAsync(timelineDir, createTimelineEntry(task.id, task.prompt, task.type, earlySessionId, process.pid, provider));
|
|
16875
|
+
await initEntryAsync(timelineDir, createTimelineEntry(task.id, task.prompt, task.type, earlySessionId, process.pid, provider, task.contextKey));
|
|
16855
16876
|
const pendingMessages = [];
|
|
16856
16877
|
let seq = 0;
|
|
16857
16878
|
const BATCH_SIZE = Number(process.env.ALOOK_MESSAGE_BATCH_SIZE) || 20;
|
|
16858
|
-
const FLUSH_INTERVAL_MS = Number(process.env.ALOOK_MESSAGE_FLUSH_INTERVAL_MS) ||
|
|
16879
|
+
const FLUSH_INTERVAL_MS = Number(process.env.ALOOK_MESSAGE_FLUSH_INTERVAL_MS) || 100;
|
|
16859
16880
|
const flushMessages = async () => {
|
|
16860
16881
|
if (pendingMessages.length === 0)
|
|
16861
16882
|
return;
|