@alook/cli 0.0.15 → 0.0.16
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 +466 -54
- package/dist/session-runner.js +159 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -297,14 +297,30 @@ function statusCommand() {
|
|
|
297
297
|
// commands/daemon.ts
|
|
298
298
|
import { Command as Command3 } from "commander";
|
|
299
299
|
import { spawn as spawn3 } from "child_process";
|
|
300
|
-
import { openSync as openSync2, closeSync as closeSync2, mkdirSync as
|
|
300
|
+
import { openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync6 } from "fs";
|
|
301
301
|
import { dirname as dirname4 } from "path";
|
|
302
302
|
|
|
303
303
|
// ../shared/src/constants.ts
|
|
304
|
+
var TaskStatus = {
|
|
305
|
+
QUEUED: "queued",
|
|
306
|
+
DISPATCHED: "dispatched",
|
|
307
|
+
RUNNING: "running",
|
|
308
|
+
COMPLETED: "completed",
|
|
309
|
+
FAILED: "failed",
|
|
310
|
+
CANCELLED: "cancelled",
|
|
311
|
+
SUPERSEDED: "superseded"
|
|
312
|
+
};
|
|
313
|
+
var TERMINAL_TASK_STATUSES = [
|
|
314
|
+
TaskStatus.COMPLETED,
|
|
315
|
+
TaskStatus.FAILED,
|
|
316
|
+
TaskStatus.CANCELLED,
|
|
317
|
+
TaskStatus.SUPERSEDED
|
|
318
|
+
];
|
|
304
319
|
var TASK_TYPES = {
|
|
305
320
|
USER_DM_MESSAGE: "user_dm_message",
|
|
306
321
|
EMAIL_NOTIFICATION: "email_notification",
|
|
307
|
-
CALENDAR_EVENT: "calendar_event"
|
|
322
|
+
CALENDAR_EVENT: "calendar_event",
|
|
323
|
+
KILL_TASK: "kill_task"
|
|
308
324
|
};
|
|
309
325
|
var POLL_INTERVAL_MS = Number(process.env.POLL_INTERVAL_MS) || 3000;
|
|
310
326
|
var OFFLINE_THRESHOLD_MS = Number(process.env.OFFLINE_THRESHOLD_MS) || 9000;
|
|
@@ -13851,7 +13867,8 @@ var TaskStatusSchema = exports_external.enum([
|
|
|
13851
13867
|
"running",
|
|
13852
13868
|
"completed",
|
|
13853
13869
|
"failed",
|
|
13854
|
-
"cancelled"
|
|
13870
|
+
"cancelled",
|
|
13871
|
+
"superseded"
|
|
13855
13872
|
]);
|
|
13856
13873
|
var ClaimedTaskRowSchema = exports_external.object({
|
|
13857
13874
|
id: exports_external.string(),
|
|
@@ -14032,8 +14049,9 @@ var UpdateAgentRequestSchema = exports_external.object({
|
|
|
14032
14049
|
description: exports_external.string().optional(),
|
|
14033
14050
|
instructions: exports_external.string().optional(),
|
|
14034
14051
|
runtime_id: exports_external.string().min(1).optional(),
|
|
14035
|
-
runtime_config: RuntimeConfigSchema
|
|
14036
|
-
|
|
14052
|
+
runtime_config: RuntimeConfigSchema,
|
|
14053
|
+
visibility: exports_external.enum(["public", "private"]).optional()
|
|
14054
|
+
}).refine((v) => v.name !== undefined || v.description !== undefined || v.instructions !== undefined || v.runtime_id !== undefined || v.runtime_config !== undefined || v.visibility !== undefined, { message: "at least one field is required" });
|
|
14037
14055
|
var CreateConversationRequestSchema = exports_external.object({
|
|
14038
14056
|
agent_id: exports_external.string().min(1, "agent_id is required")
|
|
14039
14057
|
});
|
|
@@ -14125,6 +14143,16 @@ var CreateWorkspaceRequestSchema = exports_external.object({
|
|
|
14125
14143
|
name: exports_external.string().min(1, "name is required"),
|
|
14126
14144
|
slug: exports_external.string().min(1, "slug is required")
|
|
14127
14145
|
});
|
|
14146
|
+
var UpdateWorkspaceRequestSchema = exports_external.object({
|
|
14147
|
+
name: exports_external.string().min(1, "name is required").max(100).trim().optional(),
|
|
14148
|
+
slug: exports_external.string().min(1, "slug is required").max(100).trim().toLowerCase().optional()
|
|
14149
|
+
});
|
|
14150
|
+
var DeleteWorkspaceRequestSchema = exports_external.object({
|
|
14151
|
+
confirm_name: exports_external.string().min(1, "confirm_name is required")
|
|
14152
|
+
});
|
|
14153
|
+
var GrantAgentAccessRequestSchema = exports_external.object({
|
|
14154
|
+
user_id: exports_external.string().min(1, "user_id is required")
|
|
14155
|
+
});
|
|
14128
14156
|
// ../../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
|
|
14129
14157
|
var entityKind = Symbol.for("drizzle:entityKind");
|
|
14130
14158
|
var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
|
|
@@ -15556,6 +15584,48 @@ var member = sqliteTable("member", {
|
|
|
15556
15584
|
globalInstruction: text("global_instruction").notNull().default(""),
|
|
15557
15585
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15558
15586
|
}, (t) => [unique("member_workspace_user").on(t.workspaceId, t.userId)]);
|
|
15587
|
+
var workspaceInvite = sqliteTable("workspace_invite", {
|
|
15588
|
+
id: text("id").primaryKey().$defaultFn(() => "inv_" + nanoid3()),
|
|
15589
|
+
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
15590
|
+
token: text("token").unique().notNull().$defaultFn(() => nanoid3(32)),
|
|
15591
|
+
createdBy: text("created_by").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
15592
|
+
usedBy: text("used_by").references(() => user.id, { onDelete: "set null" }),
|
|
15593
|
+
usedAt: text("used_at"),
|
|
15594
|
+
expiresAt: text("expires_at").notNull(),
|
|
15595
|
+
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15596
|
+
}, (t) => [
|
|
15597
|
+
index("idx_workspace_invite_token").on(t.token),
|
|
15598
|
+
index("idx_workspace_invite_workspace").on(t.workspaceId)
|
|
15599
|
+
]);
|
|
15600
|
+
var agentAccess = sqliteTable("agent_access", {
|
|
15601
|
+
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15602
|
+
agentId: text("agent_id").notNull(),
|
|
15603
|
+
workspaceId: text("workspace_id").notNull(),
|
|
15604
|
+
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
15605
|
+
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15606
|
+
}, (t) => [
|
|
15607
|
+
unique("agent_access_agent_ws_user").on(t.agentId, t.workspaceId, t.userId),
|
|
15608
|
+
index("idx_agent_access_agent_ws").on(t.agentId, t.workspaceId),
|
|
15609
|
+
index("idx_agent_access_user").on(t.userId),
|
|
15610
|
+
foreignKey({
|
|
15611
|
+
columns: [t.agentId, t.workspaceId],
|
|
15612
|
+
foreignColumns: [agent.id, agent.workspaceId]
|
|
15613
|
+
}).onDelete("cascade")
|
|
15614
|
+
]);
|
|
15615
|
+
var agentPin = sqliteTable("agent_pin", {
|
|
15616
|
+
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15617
|
+
agentId: text("agent_id").notNull(),
|
|
15618
|
+
workspaceId: text("workspace_id").notNull(),
|
|
15619
|
+
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
15620
|
+
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15621
|
+
}, (t) => [
|
|
15622
|
+
unique("agent_pin_agent_ws_user").on(t.agentId, t.workspaceId, t.userId),
|
|
15623
|
+
index("idx_agent_pin_ws_user").on(t.workspaceId, t.userId),
|
|
15624
|
+
foreignKey({
|
|
15625
|
+
columns: [t.agentId, t.workspaceId],
|
|
15626
|
+
foreignColumns: [agent.id, agent.workspaceId]
|
|
15627
|
+
}).onDelete("cascade")
|
|
15628
|
+
]);
|
|
15559
15629
|
var machine = sqliteTable("machine", {
|
|
15560
15630
|
daemonId: text("daemon_id").notNull(),
|
|
15561
15631
|
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
@@ -15693,6 +15763,7 @@ var emails = sqliteTable("emails", {
|
|
|
15693
15763
|
htmlBody: text("html_body").notNull().default(""),
|
|
15694
15764
|
attachments: text("attachments").notNull().default("[]"),
|
|
15695
15765
|
status: text("status").notNull().default("unread"),
|
|
15766
|
+
direction: text("direction").notNull().default("inbound"),
|
|
15696
15767
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15697
15768
|
}, (t) => [
|
|
15698
15769
|
foreignKey({
|
|
@@ -15883,6 +15954,9 @@ class DaemonClient {
|
|
|
15883
15954
|
error: error48
|
|
15884
15955
|
});
|
|
15885
15956
|
}
|
|
15957
|
+
supersedeTask(token, taskId) {
|
|
15958
|
+
return this.request("POST", `/api/daemon/tasks/${taskId}/supersede`, token);
|
|
15959
|
+
}
|
|
15886
15960
|
async getArtifactMeta(token, artifactId, workspaceId) {
|
|
15887
15961
|
return this.request("GET", `/api/artifacts/${artifactId}?workspace_id=${encodeURIComponent(workspaceId)}`, token);
|
|
15888
15962
|
}
|
|
@@ -16316,13 +16390,172 @@ async function handleCliUpdate(version3, onSuccess, profile) {
|
|
|
16316
16390
|
}
|
|
16317
16391
|
}
|
|
16318
16392
|
|
|
16393
|
+
// daemon/execenv/timeline.ts
|
|
16394
|
+
import { appendFileSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync } from "fs";
|
|
16395
|
+
import { join as join4 } from "path";
|
|
16396
|
+
|
|
16397
|
+
// daemon/execenv/filelock.ts
|
|
16398
|
+
import { mkdirSync as mkdirSync3, rmdirSync, statSync } from "fs";
|
|
16399
|
+
var DEFAULT_STALE_MS = 3600000;
|
|
16400
|
+
function acquireLock(lockPath, staleMs = DEFAULT_STALE_MS) {
|
|
16401
|
+
try {
|
|
16402
|
+
mkdirSync3(lockPath);
|
|
16403
|
+
return true;
|
|
16404
|
+
} catch {
|
|
16405
|
+
try {
|
|
16406
|
+
const stat = statSync(lockPath);
|
|
16407
|
+
if (Date.now() - stat.mtimeMs > staleMs) {
|
|
16408
|
+
rmdirSync(lockPath);
|
|
16409
|
+
try {
|
|
16410
|
+
mkdirSync3(lockPath);
|
|
16411
|
+
return true;
|
|
16412
|
+
} catch {
|
|
16413
|
+
return false;
|
|
16414
|
+
}
|
|
16415
|
+
}
|
|
16416
|
+
} catch {
|
|
16417
|
+
try {
|
|
16418
|
+
mkdirSync3(lockPath);
|
|
16419
|
+
return true;
|
|
16420
|
+
} catch {
|
|
16421
|
+
return false;
|
|
16422
|
+
}
|
|
16423
|
+
}
|
|
16424
|
+
return false;
|
|
16425
|
+
}
|
|
16426
|
+
}
|
|
16427
|
+
function releaseLock(lockPath) {
|
|
16428
|
+
try {
|
|
16429
|
+
rmdirSync(lockPath);
|
|
16430
|
+
} catch {}
|
|
16431
|
+
}
|
|
16432
|
+
|
|
16433
|
+
// daemon/execenv/timeline.ts
|
|
16434
|
+
function readJsonl(filePath) {
|
|
16435
|
+
let content;
|
|
16436
|
+
try {
|
|
16437
|
+
content = readFileSync5(filePath, "utf-8");
|
|
16438
|
+
} catch {
|
|
16439
|
+
return [];
|
|
16440
|
+
}
|
|
16441
|
+
const entries = [];
|
|
16442
|
+
for (const line of content.trimEnd().split(`
|
|
16443
|
+
`)) {
|
|
16444
|
+
if (!line)
|
|
16445
|
+
continue;
|
|
16446
|
+
try {
|
|
16447
|
+
entries.push(JSON.parse(line));
|
|
16448
|
+
} catch {}
|
|
16449
|
+
}
|
|
16450
|
+
return entries;
|
|
16451
|
+
}
|
|
16452
|
+
function filenameForDate(date5) {
|
|
16453
|
+
const y = date5.getFullYear();
|
|
16454
|
+
const m = String(date5.getMonth() + 1).padStart(2, "0");
|
|
16455
|
+
const d = String(date5.getDate()).padStart(2, "0");
|
|
16456
|
+
return `${y}-${m}-${d}.jsonl`;
|
|
16457
|
+
}
|
|
16458
|
+
function recentFilenames(maxDays) {
|
|
16459
|
+
const filenames = [];
|
|
16460
|
+
const now = new Date;
|
|
16461
|
+
for (let i = 0;i < maxDays; i++) {
|
|
16462
|
+
const d = new Date(now);
|
|
16463
|
+
d.setDate(d.getDate() - i);
|
|
16464
|
+
filenames.push(filenameForDate(d));
|
|
16465
|
+
}
|
|
16466
|
+
return filenames;
|
|
16467
|
+
}
|
|
16468
|
+
var DEFAULT_RESUME_MAX_AGE_MS = 3 * 60 * 60 * 1000;
|
|
16469
|
+
var EMAIL_RESUME_MAX_AGE_MS = 48 * 60 * 60 * 1000;
|
|
16470
|
+
function findRunningPidByTaskId(timelineDir, taskId) {
|
|
16471
|
+
for (const filename of recentFilenames(7)) {
|
|
16472
|
+
const entries = readJsonl(join4(timelineDir, filename));
|
|
16473
|
+
for (const entry of entries) {
|
|
16474
|
+
if (entry.task_id === taskId && entry.status === "running" && entry.pid != null) {
|
|
16475
|
+
return entry.pid;
|
|
16476
|
+
}
|
|
16477
|
+
}
|
|
16478
|
+
}
|
|
16479
|
+
return null;
|
|
16480
|
+
}
|
|
16481
|
+
function findRunningEntryByContextKey(timelineDir, contextKey, provider) {
|
|
16482
|
+
for (const filename of recentFilenames(7)) {
|
|
16483
|
+
const dayEntries = readJsonl(join4(timelineDir, filename));
|
|
16484
|
+
for (let i = dayEntries.length - 1;i >= 0; i--) {
|
|
16485
|
+
const entry = dayEntries[i];
|
|
16486
|
+
if (entry.status === "running" && entry.context_key === contextKey && entry.provider === provider) {
|
|
16487
|
+
return entry;
|
|
16488
|
+
}
|
|
16489
|
+
}
|
|
16490
|
+
}
|
|
16491
|
+
return null;
|
|
16492
|
+
}
|
|
16493
|
+
|
|
16494
|
+
// daemon/execenv/steering.ts
|
|
16495
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync3, readdirSync, statSync as statSync2 } from "fs";
|
|
16496
|
+
import { join as join5 } from "path";
|
|
16497
|
+
var INTENT_DIR_NAME = ".kill_intents";
|
|
16498
|
+
var STEERING_LOCK_DIR = ".steering_locks";
|
|
16499
|
+
var INTENT_STALE_MS = 10 * 60 * 1000;
|
|
16500
|
+
function intentFilePath(baseDir, taskId) {
|
|
16501
|
+
return join5(baseDir, INTENT_DIR_NAME, `${taskId}.json`);
|
|
16502
|
+
}
|
|
16503
|
+
function intentDirPath(baseDir) {
|
|
16504
|
+
return join5(baseDir, INTENT_DIR_NAME);
|
|
16505
|
+
}
|
|
16506
|
+
function steeringLockPath(baseDir, contextKey) {
|
|
16507
|
+
const safeKey = contextKey.replace(/[^a-zA-Z0-9_:-]/g, "_");
|
|
16508
|
+
return join5(baseDir, STEERING_LOCK_DIR, safeKey);
|
|
16509
|
+
}
|
|
16510
|
+
function writeKillIntent(baseDir, intent) {
|
|
16511
|
+
const dir = intentDirPath(baseDir);
|
|
16512
|
+
try {
|
|
16513
|
+
mkdirSync4(dir, { recursive: true });
|
|
16514
|
+
} catch {}
|
|
16515
|
+
const filePath = intentFilePath(baseDir, intent.targetTaskId);
|
|
16516
|
+
writeFileSync5(filePath, JSON.stringify(intent));
|
|
16517
|
+
}
|
|
16518
|
+
function cleanupStaleIntents(baseDir) {
|
|
16519
|
+
const dir = intentDirPath(baseDir);
|
|
16520
|
+
let files;
|
|
16521
|
+
try {
|
|
16522
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
16523
|
+
} catch {
|
|
16524
|
+
return;
|
|
16525
|
+
}
|
|
16526
|
+
const now = Date.now();
|
|
16527
|
+
for (const file2 of files) {
|
|
16528
|
+
const filePath = join5(dir, file2);
|
|
16529
|
+
try {
|
|
16530
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
16531
|
+
const intent = JSON.parse(content);
|
|
16532
|
+
const stat = statSync2(filePath);
|
|
16533
|
+
if (now - stat.mtimeMs > INTENT_STALE_MS) {
|
|
16534
|
+
unlinkSync3(filePath);
|
|
16535
|
+
log.debug(`Cleaned up stale kill intent for task ${intent.targetTaskId}`);
|
|
16536
|
+
}
|
|
16537
|
+
} catch {}
|
|
16538
|
+
}
|
|
16539
|
+
}
|
|
16540
|
+
function acquireSteeringLock(baseDir, contextKey) {
|
|
16541
|
+
const lockPath = steeringLockPath(baseDir, contextKey);
|
|
16542
|
+
try {
|
|
16543
|
+
mkdirSync4(join5(baseDir, STEERING_LOCK_DIR), { recursive: true });
|
|
16544
|
+
} catch {}
|
|
16545
|
+
return acquireLock(lockPath, 60000);
|
|
16546
|
+
}
|
|
16547
|
+
function releaseSteeringLock(baseDir, contextKey) {
|
|
16548
|
+
const lockPath = steeringLockPath(baseDir, contextKey);
|
|
16549
|
+
releaseLock(lockPath);
|
|
16550
|
+
}
|
|
16551
|
+
|
|
16319
16552
|
// daemon/daemon.ts
|
|
16320
|
-
import { existsSync, mkdirSync as
|
|
16553
|
+
import { existsSync, mkdirSync as mkdirSync5, openSync, closeSync, readdirSync as readdirSync2, statSync as statSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
16321
16554
|
import { execSync as execSync3, spawn as spawn2 } from "child_process";
|
|
16322
16555
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
16323
|
-
import { dirname as dirname3, join as
|
|
16556
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
16324
16557
|
var _dir = dirname3(fileURLToPath2(import.meta.url));
|
|
16325
|
-
var sessionRunnerPath = existsSync(
|
|
16558
|
+
var sessionRunnerPath = existsSync(join6(_dir, "session-runner.js")) ? join6(_dir, "session-runner.js") : join6(_dir, "session-runner.ts");
|
|
16326
16559
|
function isCommandAvailable2(cmd) {
|
|
16327
16560
|
try {
|
|
16328
16561
|
const check2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
@@ -16332,21 +16565,21 @@ function isCommandAvailable2(cmd) {
|
|
|
16332
16565
|
return false;
|
|
16333
16566
|
}
|
|
16334
16567
|
}
|
|
16335
|
-
var MAX_SESSION_RUNNER_LOGS =
|
|
16568
|
+
var MAX_SESSION_RUNNER_LOGS = 500;
|
|
16336
16569
|
function pruneSessionRunnerLogs() {
|
|
16337
16570
|
const logDir = sessionRunnerLogDir();
|
|
16338
16571
|
let entries;
|
|
16339
16572
|
try {
|
|
16340
|
-
entries =
|
|
16573
|
+
entries = readdirSync2(logDir).filter((f) => f.endsWith(".log"));
|
|
16341
16574
|
} catch {
|
|
16342
16575
|
return;
|
|
16343
16576
|
}
|
|
16344
16577
|
if (entries.length <= MAX_SESSION_RUNNER_LOGS)
|
|
16345
16578
|
return;
|
|
16346
16579
|
const withMtime = entries.map((name) => {
|
|
16347
|
-
const full =
|
|
16580
|
+
const full = join6(logDir, name);
|
|
16348
16581
|
try {
|
|
16349
|
-
return { name, mtime:
|
|
16582
|
+
return { name, mtime: statSync3(full).mtimeMs };
|
|
16350
16583
|
} catch {
|
|
16351
16584
|
return { name, mtime: 0 };
|
|
16352
16585
|
}
|
|
@@ -16354,7 +16587,7 @@ function pruneSessionRunnerLogs() {
|
|
|
16354
16587
|
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
16355
16588
|
for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
|
|
16356
16589
|
try {
|
|
16357
|
-
|
|
16590
|
+
unlinkSync4(join6(logDir, entry.name));
|
|
16358
16591
|
} catch {}
|
|
16359
16592
|
}
|
|
16360
16593
|
}
|
|
@@ -16433,12 +16666,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16433
16666
|
});
|
|
16434
16667
|
} catch (e) {
|
|
16435
16668
|
if (e instanceof Error && e.message.startsWith("HTTP 401")) {
|
|
16436
|
-
log.warn(`Workspace ${ws.id} token invalid —
|
|
16437
|
-
try {
|
|
16438
|
-
const cfg = loadCLIConfigForProfile(profile);
|
|
16439
|
-
cfg.watched_workspaces = (cfg.watched_workspaces || []).filter((w) => w.id !== ws.id);
|
|
16440
|
-
saveCLIConfigForProfile(profile, cfg);
|
|
16441
|
-
} catch {}
|
|
16669
|
+
log.warn(`Workspace ${ws.id} token invalid — skipping (run '${cmdPrefix()} register --token <token>' to fix)`);
|
|
16442
16670
|
} else {
|
|
16443
16671
|
log.error(`Failed to register workspace ${ws.id}, skipping`, e);
|
|
16444
16672
|
}
|
|
@@ -16497,7 +16725,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16497
16725
|
cfg.watched_workspaces = (cfg.watched_workspaces || []).filter((w) => w.id !== workspaceId);
|
|
16498
16726
|
saveCLIConfigForProfile(profile, cfg);
|
|
16499
16727
|
} catch {}
|
|
16500
|
-
log.info(`Workspace ${workspaceId}
|
|
16728
|
+
log.info(`Workspace ${workspaceId} deleted server-side — removed from config`);
|
|
16501
16729
|
}
|
|
16502
16730
|
const pollCycle = async () => {
|
|
16503
16731
|
let remaining = config2.maxConcurrentTasks - activeTasks.size;
|
|
@@ -16534,7 +16762,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16534
16762
|
}
|
|
16535
16763
|
} catch (e) {
|
|
16536
16764
|
if (e instanceof Error && e.message.startsWith("HTTP 401")) {
|
|
16537
|
-
|
|
16765
|
+
log.warn(`Workspace ${ws.workspaceId} poll returned 401 — will retry next cycle`);
|
|
16538
16766
|
} else {
|
|
16539
16767
|
log.debug("Poll error", e);
|
|
16540
16768
|
}
|
|
@@ -16571,17 +16799,28 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16571
16799
|
releaseDaemonPid(profile);
|
|
16572
16800
|
health.server.close(() => {
|
|
16573
16801
|
if (restartRequested) {
|
|
16574
|
-
const
|
|
16802
|
+
const entry = process.argv[1];
|
|
16803
|
+
const args = [entry, "daemon", "start", "--foreground"];
|
|
16575
16804
|
if (profile)
|
|
16576
16805
|
args.push("--profile", profile);
|
|
16577
16806
|
if (serverUrl)
|
|
16578
16807
|
args.push("--server", serverUrl);
|
|
16579
|
-
const
|
|
16808
|
+
const logPath = daemonLogFilePath();
|
|
16809
|
+
let logFd;
|
|
16810
|
+
try {
|
|
16811
|
+
mkdirSync5(dirname3(logPath), { recursive: true, mode: 448 });
|
|
16812
|
+
logFd = openSync(logPath, "a", 384);
|
|
16813
|
+
} catch (e) {
|
|
16814
|
+
log.error(`Failed to open daemon log file ${logPath}`, e);
|
|
16815
|
+
}
|
|
16816
|
+
const child = spawn2(process.execPath, args, {
|
|
16580
16817
|
detached: true,
|
|
16581
|
-
stdio: ["ignore", "ignore", "ignore"]
|
|
16818
|
+
stdio: logFd != null ? ["ignore", logFd, logFd] : ["ignore", "ignore", "ignore"]
|
|
16582
16819
|
});
|
|
16583
16820
|
child.unref();
|
|
16584
|
-
|
|
16821
|
+
if (logFd != null)
|
|
16822
|
+
closeSync(logFd);
|
|
16823
|
+
log.info(`Spawned new daemon (pid=${child.pid}), logs: ${logPath}`);
|
|
16585
16824
|
}
|
|
16586
16825
|
clearTimeout(timeout);
|
|
16587
16826
|
process.exit(0);
|
|
@@ -16593,21 +16832,62 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16593
16832
|
}
|
|
16594
16833
|
function spawnSessionRunner(input) {
|
|
16595
16834
|
const logDir = sessionRunnerLogDir();
|
|
16596
|
-
|
|
16597
|
-
const logFilePath =
|
|
16835
|
+
mkdirSync5(logDir, { recursive: true });
|
|
16836
|
+
const logFilePath = join6(logDir, `${input.task.id}.log`);
|
|
16598
16837
|
input.logFilePath = logFilePath;
|
|
16599
16838
|
const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
|
|
16600
|
-
|
|
16839
|
+
let fd;
|
|
16840
|
+
try {
|
|
16841
|
+
fd = openSync(logFilePath, "a");
|
|
16842
|
+
} catch (e) {
|
|
16843
|
+
log.error(`Failed to open log file ${logFilePath}`, e);
|
|
16844
|
+
}
|
|
16601
16845
|
const child = spawn2(process.execPath, [sessionRunnerPath, encoded], {
|
|
16602
16846
|
detached: true,
|
|
16603
|
-
stdio: ["ignore", fd, fd]
|
|
16847
|
+
stdio: fd != null ? ["ignore", fd, fd] : ["ignore", "ignore", "ignore"]
|
|
16604
16848
|
});
|
|
16605
16849
|
child.unref();
|
|
16606
|
-
|
|
16850
|
+
if (fd != null)
|
|
16851
|
+
closeSync(fd);
|
|
16607
16852
|
return child;
|
|
16608
16853
|
}
|
|
16609
16854
|
async function handleTask(client, config2, runtimeIndex, task, token, activeTasks) {
|
|
16610
16855
|
log.info(`Task ${task.id} claimed agent=${task.agentId}`);
|
|
16856
|
+
if (task.type === TASK_TYPES.KILL_TASK) {
|
|
16857
|
+
const targetTaskId = task.context?.target_task_id;
|
|
16858
|
+
if (!targetTaskId) {
|
|
16859
|
+
await client.failTask(token, task.id, "missing target_task_id in context");
|
|
16860
|
+
activeTasks.delete(task.id);
|
|
16861
|
+
return;
|
|
16862
|
+
}
|
|
16863
|
+
const agentBaseDir = join6(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
16864
|
+
const timelineDir = join6(agentBaseDir, ".context_timeline");
|
|
16865
|
+
const pid = findRunningPidByTaskId(timelineDir, targetTaskId);
|
|
16866
|
+
if (pid != null) {
|
|
16867
|
+
writeKillIntent(agentBaseDir, {
|
|
16868
|
+
reason: "cancelled",
|
|
16869
|
+
targetTaskId,
|
|
16870
|
+
expectedPid: pid
|
|
16871
|
+
});
|
|
16872
|
+
try {
|
|
16873
|
+
process.kill(pid, "SIGTERM");
|
|
16874
|
+
await client.failTask(token, task.id, "killed");
|
|
16875
|
+
log.info(`Kill task ${task.id}: sent SIGTERM to pid=${pid} for target=${targetTaskId}`);
|
|
16876
|
+
} catch (e) {
|
|
16877
|
+
if (e?.code === "ESRCH") {
|
|
16878
|
+
await client.failTask(token, task.id, "target process already exited");
|
|
16879
|
+
log.info(`Kill task ${task.id}: target pid=${pid} already exited`);
|
|
16880
|
+
} else {
|
|
16881
|
+
await client.failTask(token, task.id, `kill failed: ${e}`);
|
|
16882
|
+
}
|
|
16883
|
+
}
|
|
16884
|
+
} else {
|
|
16885
|
+
await client.failTask(token, task.id, "target not found in timeline");
|
|
16886
|
+
log.info(`Kill task ${task.id}: target ${targetTaskId} not found in timeline`);
|
|
16887
|
+
}
|
|
16888
|
+
activeTasks.delete(task.id);
|
|
16889
|
+
return;
|
|
16890
|
+
}
|
|
16611
16891
|
try {
|
|
16612
16892
|
await client.startTask(token, task.id);
|
|
16613
16893
|
} catch (e) {
|
|
@@ -16622,6 +16902,60 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
16622
16902
|
return;
|
|
16623
16903
|
}
|
|
16624
16904
|
const provider = runtimeData.provider;
|
|
16905
|
+
if (task.contextKey) {
|
|
16906
|
+
const agentBaseDir = join6(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
16907
|
+
cleanupStaleIntents(agentBaseDir);
|
|
16908
|
+
const timelineDir = join6(agentBaseDir, ".context_timeline");
|
|
16909
|
+
const lockAcquired = acquireSteeringLock(agentBaseDir, task.contextKey);
|
|
16910
|
+
if (!lockAcquired) {
|
|
16911
|
+
log.warn(`Steering lock contention for context_key=${task.contextKey}, proceeding without steering`);
|
|
16912
|
+
} else {
|
|
16913
|
+
try {
|
|
16914
|
+
const predecessor = findRunningEntryByContextKey(timelineDir, task.contextKey, provider);
|
|
16915
|
+
if (predecessor && predecessor.task_id !== task.id) {
|
|
16916
|
+
log.info(`Steering: task ${task.id} supersedes predecessor ${predecessor.task_id} (context_key=${task.contextKey})`);
|
|
16917
|
+
if (predecessor.pid != null) {
|
|
16918
|
+
writeKillIntent(agentBaseDir, {
|
|
16919
|
+
reason: "superseded",
|
|
16920
|
+
targetTaskId: predecessor.task_id,
|
|
16921
|
+
expectedPid: predecessor.pid,
|
|
16922
|
+
successorTaskId: task.id
|
|
16923
|
+
});
|
|
16924
|
+
try {
|
|
16925
|
+
process.kill(predecessor.pid, "SIGTERM");
|
|
16926
|
+
log.info(`Steering: sent SIGTERM to predecessor pid=${predecessor.pid}`);
|
|
16927
|
+
} catch (e) {
|
|
16928
|
+
if (e?.code === "ESRCH") {
|
|
16929
|
+
log.info(`Steering: predecessor pid=${predecessor.pid} already exited`);
|
|
16930
|
+
} else {
|
|
16931
|
+
log.warn(`Steering: kill failed for pid=${predecessor.pid}`, e);
|
|
16932
|
+
}
|
|
16933
|
+
}
|
|
16934
|
+
const waitStart = Date.now();
|
|
16935
|
+
const MAX_WAIT_MS = 15000;
|
|
16936
|
+
const POLL_MS = 200;
|
|
16937
|
+
while (Date.now() - waitStart < MAX_WAIT_MS) {
|
|
16938
|
+
const stillRunning = findRunningPidByTaskId(timelineDir, predecessor.task_id);
|
|
16939
|
+
if (stillRunning == null)
|
|
16940
|
+
break;
|
|
16941
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
16942
|
+
}
|
|
16943
|
+
if (findRunningPidByTaskId(timelineDir, predecessor.task_id) != null) {
|
|
16944
|
+
log.warn(`Steering: predecessor pid=${predecessor.pid} did not exit within ${MAX_WAIT_MS}ms, proceeding anyway`);
|
|
16945
|
+
}
|
|
16946
|
+
}
|
|
16947
|
+
try {
|
|
16948
|
+
await client.supersedeTask(token, predecessor.task_id);
|
|
16949
|
+
log.info(`Steering: predecessor ${predecessor.task_id} marked superseded`);
|
|
16950
|
+
} catch (e) {
|
|
16951
|
+
log.warn(`Steering: failed to mark predecessor superseded server-side`, e);
|
|
16952
|
+
}
|
|
16953
|
+
}
|
|
16954
|
+
} finally {
|
|
16955
|
+
releaseSteeringLock(agentBaseDir, task.contextKey);
|
|
16956
|
+
}
|
|
16957
|
+
}
|
|
16958
|
+
}
|
|
16625
16959
|
const cliPath = provider === "claude" ? config2.claudePath : provider === "codex" ? config2.codexPath : config2.opencodePath;
|
|
16626
16960
|
const configModel = provider === "claude" ? config2.claudeModel : provider === "codex" ? config2.codexModel : config2.opencodeModel;
|
|
16627
16961
|
const agentModel = task.agent?.runtimeConfig?.model;
|
|
@@ -16676,7 +17010,7 @@ async function startInBackground(profile, serverUrl) {
|
|
|
16676
17010
|
return;
|
|
16677
17011
|
}
|
|
16678
17012
|
const logPath = daemonLogFilePath();
|
|
16679
|
-
|
|
17013
|
+
mkdirSync6(dirname4(logPath), { recursive: true, mode: 448 });
|
|
16680
17014
|
const logFd = openSync2(logPath, "a", 384);
|
|
16681
17015
|
const child = spawn3(process.execPath, buildChildArgs(profile, serverUrl), {
|
|
16682
17016
|
detached: true,
|
|
@@ -16767,6 +17101,14 @@ function daemonCommand() {
|
|
|
16767
17101
|
import { Command as Command4 } from "commander";
|
|
16768
17102
|
|
|
16769
17103
|
// lib/output.ts
|
|
17104
|
+
function printTable(headers, rows) {
|
|
17105
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] || "").length)));
|
|
17106
|
+
const header = headers.map((h, i) => h.padEnd(widths[i])).join(" ");
|
|
17107
|
+
const separator = widths.map((w) => "-".repeat(w)).join(" ");
|
|
17108
|
+
console.log(header);
|
|
17109
|
+
console.log(separator);
|
|
17110
|
+
rows.forEach((r) => console.log(r.map((c, i) => (c || "").padEnd(widths[i])).join(" ")));
|
|
17111
|
+
}
|
|
16770
17112
|
function printJSON(data) {
|
|
16771
17113
|
console.log(JSON.stringify(data, null, 2));
|
|
16772
17114
|
}
|
|
@@ -16786,8 +17128,8 @@ function configCommand() {
|
|
|
16786
17128
|
|
|
16787
17129
|
// commands/email.ts
|
|
16788
17130
|
import { Command as Command5 } from "commander";
|
|
16789
|
-
import { writeFileSync as
|
|
16790
|
-
import { basename, join as
|
|
17131
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
17132
|
+
import { basename, join as join7 } from "path";
|
|
16791
17133
|
import PostalMime from "postal-mime";
|
|
16792
17134
|
var VALID_STATUSES = ["unread", "read", "archived"];
|
|
16793
17135
|
var EMAIL_BASE = "/tmp/alook-emails";
|
|
@@ -16818,7 +17160,10 @@ function collectRepeated(value, previous) {
|
|
|
16818
17160
|
return previous.concat([value]);
|
|
16819
17161
|
}
|
|
16820
17162
|
function resolveClientOpts(command, opts) {
|
|
16821
|
-
|
|
17163
|
+
let root = command;
|
|
17164
|
+
while (root.parent)
|
|
17165
|
+
root = root.parent;
|
|
17166
|
+
const parentOpts = root.opts() || {};
|
|
16822
17167
|
const profile = parentOpts.profile;
|
|
16823
17168
|
const cfg = loadCLIConfigForProfile(profile);
|
|
16824
17169
|
const serverUrl = parentOpts.server || cfg.server_url;
|
|
@@ -16847,7 +17192,7 @@ function emailCommand() {
|
|
|
16847
17192
|
console.error(`Error: invalid status "${opts.status}", must be one of: ${VALID_STATUSES.join(", ")}`);
|
|
16848
17193
|
process.exit(1);
|
|
16849
17194
|
}
|
|
16850
|
-
const emailDir_base =
|
|
17195
|
+
const emailDir_base = join7(EMAIL_BASE, workspaceId, opts.agent_id);
|
|
16851
17196
|
try {
|
|
16852
17197
|
let query = `/api/email?agentId=${opts.agent_id}`;
|
|
16853
17198
|
if (opts.status)
|
|
@@ -16861,11 +17206,11 @@ function emailCommand() {
|
|
|
16861
17206
|
printJSON(emails2);
|
|
16862
17207
|
return;
|
|
16863
17208
|
}
|
|
16864
|
-
|
|
17209
|
+
mkdirSync7(emailDir_base, { recursive: true });
|
|
16865
17210
|
const downloadedPaths = [];
|
|
16866
17211
|
for (const email3 of emails2) {
|
|
16867
|
-
const emailDir =
|
|
16868
|
-
|
|
17212
|
+
const emailDir = join7(emailDir_base, email3.id);
|
|
17213
|
+
mkdirSync7(emailDir, { recursive: true });
|
|
16869
17214
|
const metadata = {
|
|
16870
17215
|
id: email3.id,
|
|
16871
17216
|
from: email3.from_email,
|
|
@@ -16877,8 +17222,8 @@ function emailCommand() {
|
|
|
16877
17222
|
in_reply_to: email3.in_reply_to || "",
|
|
16878
17223
|
references: email3.references || ""
|
|
16879
17224
|
};
|
|
16880
|
-
const metadataPath =
|
|
16881
|
-
|
|
17225
|
+
const metadataPath = join7(emailDir, "metadata.json");
|
|
17226
|
+
writeFileSync6(metadataPath, JSON.stringify(metadata, null, 2));
|
|
16882
17227
|
downloadedPaths.push(metadataPath);
|
|
16883
17228
|
let rawMime;
|
|
16884
17229
|
try {
|
|
@@ -16893,18 +17238,18 @@ function emailCommand() {
|
|
|
16893
17238
|
}
|
|
16894
17239
|
const parsed = await new PostalMime().parse(rawMime);
|
|
16895
17240
|
if (parsed.text) {
|
|
16896
|
-
const bodyPath =
|
|
16897
|
-
|
|
17241
|
+
const bodyPath = join7(emailDir, "body.txt");
|
|
17242
|
+
writeFileSync6(bodyPath, parsed.text);
|
|
16898
17243
|
downloadedPaths.push(bodyPath);
|
|
16899
17244
|
}
|
|
16900
17245
|
if (parsed.html) {
|
|
16901
|
-
const htmlPath =
|
|
16902
|
-
|
|
17246
|
+
const htmlPath = join7(emailDir, "body.html");
|
|
17247
|
+
writeFileSync6(htmlPath, parsed.html);
|
|
16903
17248
|
downloadedPaths.push(htmlPath);
|
|
16904
17249
|
}
|
|
16905
17250
|
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
16906
|
-
const attDir =
|
|
16907
|
-
|
|
17251
|
+
const attDir = join7(emailDir, "attachments");
|
|
17252
|
+
mkdirSync7(attDir, { recursive: true });
|
|
16908
17253
|
const usedFilenames = new Set;
|
|
16909
17254
|
for (let i = 0;i < parsed.attachments.length; i++) {
|
|
16910
17255
|
const att = parsed.attachments[i];
|
|
@@ -16913,7 +17258,7 @@ function emailCommand() {
|
|
|
16913
17258
|
filename = `${i}-${filename}`;
|
|
16914
17259
|
}
|
|
16915
17260
|
usedFilenames.add(filename);
|
|
16916
|
-
const attPath =
|
|
17261
|
+
const attPath = join7(attDir, filename);
|
|
16917
17262
|
const content = att.content;
|
|
16918
17263
|
let buf;
|
|
16919
17264
|
if (typeof content === "string") {
|
|
@@ -16923,7 +17268,7 @@ function emailCommand() {
|
|
|
16923
17268
|
} else {
|
|
16924
17269
|
buf = Buffer.from(content);
|
|
16925
17270
|
}
|
|
16926
|
-
|
|
17271
|
+
writeFileSync6(attPath, buf);
|
|
16927
17272
|
downloadedPaths.push(attPath);
|
|
16928
17273
|
}
|
|
16929
17274
|
}
|
|
@@ -16962,7 +17307,7 @@ function emailCommand() {
|
|
|
16962
17307
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
16963
17308
|
let htmlBody;
|
|
16964
17309
|
try {
|
|
16965
|
-
htmlBody =
|
|
17310
|
+
htmlBody = readFileSync7(opts.bodyFile, "utf-8");
|
|
16966
17311
|
} catch (err) {
|
|
16967
17312
|
console.error(`Error: cannot read body file "${opts.bodyFile}": ${err instanceof Error ? err.message : err}`);
|
|
16968
17313
|
process.exit(1);
|
|
@@ -16978,8 +17323,8 @@ function emailCommand() {
|
|
|
16978
17323
|
let bytes;
|
|
16979
17324
|
let size;
|
|
16980
17325
|
try {
|
|
16981
|
-
bytes =
|
|
16982
|
-
size =
|
|
17326
|
+
bytes = readFileSync7(path);
|
|
17327
|
+
size = statSync4(path).size;
|
|
16983
17328
|
} catch (err) {
|
|
16984
17329
|
console.error(`Error: cannot read attachment "${path}": ${err instanceof Error ? err.message : err}`);
|
|
16985
17330
|
process.exit(1);
|
|
@@ -17024,6 +17369,73 @@ function emailCommand() {
|
|
|
17024
17369
|
process.exit(1);
|
|
17025
17370
|
}
|
|
17026
17371
|
});
|
|
17372
|
+
const whitelistCmd = new Command5("whitelist").description("Manage email whitelist (allowed senders)");
|
|
17373
|
+
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) => {
|
|
17374
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
|
17375
|
+
workspace: opts.workspace,
|
|
17376
|
+
agentId: opts.agent_id
|
|
17377
|
+
});
|
|
17378
|
+
const client = new APIClient(serverUrl, token, workspaceId);
|
|
17379
|
+
try {
|
|
17380
|
+
const entries = await client.getJSON(`/api/agents/${opts.agent_id}/whitelist`);
|
|
17381
|
+
if (!entries.length) {
|
|
17382
|
+
console.log("No whitelisted emails.");
|
|
17383
|
+
return;
|
|
17384
|
+
}
|
|
17385
|
+
if (opts.json) {
|
|
17386
|
+
printJSON(entries);
|
|
17387
|
+
return;
|
|
17388
|
+
}
|
|
17389
|
+
printTable(["ID", "EMAIL", "CREATED AT"], entries.map((e) => [e.id, e.email, e.created_at]));
|
|
17390
|
+
} catch (err) {
|
|
17391
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
17392
|
+
process.exit(1);
|
|
17393
|
+
}
|
|
17394
|
+
});
|
|
17395
|
+
whitelistCmd.command("add").description("Add an email to the whitelist").requiredOption("--agent_id <id>", "Agent ID").option("--workspace <id>", "Workspace ID").argument("<email>", "Email address to whitelist").action(async (email3, opts, command) => {
|
|
17396
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
|
17397
|
+
workspace: opts.workspace,
|
|
17398
|
+
agentId: opts.agent_id
|
|
17399
|
+
});
|
|
17400
|
+
const client = new APIClient(serverUrl, token, workspaceId);
|
|
17401
|
+
try {
|
|
17402
|
+
const entry = await client.postJSON(`/api/agents/${opts.agent_id}/whitelist`, { email: email3.toLowerCase() });
|
|
17403
|
+
console.log(`Added ${entry.email} to whitelist (id: ${entry.id})`);
|
|
17404
|
+
} catch (err) {
|
|
17405
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17406
|
+
if (msg.includes("409")) {
|
|
17407
|
+
console.error(`Error: ${email3.toLowerCase()} is already whitelisted`);
|
|
17408
|
+
} else {
|
|
17409
|
+
console.error(`Error: ${msg}`);
|
|
17410
|
+
}
|
|
17411
|
+
process.exit(1);
|
|
17412
|
+
}
|
|
17413
|
+
});
|
|
17414
|
+
whitelistCmd.command("delete").description("Remove an email from the whitelist").requiredOption("--agent_id <id>", "Agent ID").option("--workspace <id>", "Workspace ID").argument("<email>", "Email address to remove").action(async (email3, opts, command) => {
|
|
17415
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
|
17416
|
+
workspace: opts.workspace,
|
|
17417
|
+
agentId: opts.agent_id
|
|
17418
|
+
});
|
|
17419
|
+
const client = new APIClient(serverUrl, token, workspaceId);
|
|
17420
|
+
const normalizedEmail = email3.toLowerCase();
|
|
17421
|
+
try {
|
|
17422
|
+
const entries = await client.getJSON(`/api/agents/${opts.agent_id}/whitelist`);
|
|
17423
|
+
const entry = entries.find((e) => e.email === normalizedEmail);
|
|
17424
|
+
if (!entry) {
|
|
17425
|
+
console.error(`Error: ${normalizedEmail} is not in the whitelist`);
|
|
17426
|
+
process.exit(1);
|
|
17427
|
+
}
|
|
17428
|
+
await client.deleteJSON(`/api/agents/${opts.agent_id}/whitelist/${entry.id}`);
|
|
17429
|
+
console.log(`Removed ${normalizedEmail} from whitelist`);
|
|
17430
|
+
} catch (err) {
|
|
17431
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17432
|
+
if (msg === "__exit__")
|
|
17433
|
+
throw err;
|
|
17434
|
+
console.error(`Error: ${msg}`);
|
|
17435
|
+
process.exit(1);
|
|
17436
|
+
}
|
|
17437
|
+
});
|
|
17438
|
+
cmd.addCommand(whitelistCmd);
|
|
17027
17439
|
return cmd;
|
|
17028
17440
|
}
|
|
17029
17441
|
|
|
@@ -17283,7 +17695,7 @@ ${result.output}`);
|
|
|
17283
17695
|
|
|
17284
17696
|
// commands/sync.ts
|
|
17285
17697
|
import { Command as Command9 } from "commander";
|
|
17286
|
-
import { readFileSync as
|
|
17698
|
+
import { readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
17287
17699
|
import { basename as basename2 } from "path";
|
|
17288
17700
|
var MIME_BY_EXT2 = {
|
|
17289
17701
|
".pdf": "application/pdf",
|
|
@@ -17332,9 +17744,9 @@ function syncCommand() {
|
|
|
17332
17744
|
let bytes;
|
|
17333
17745
|
let size;
|
|
17334
17746
|
try {
|
|
17335
|
-
const stat =
|
|
17747
|
+
const stat = statSync5(opts.file);
|
|
17336
17748
|
size = stat.size;
|
|
17337
|
-
bytes =
|
|
17749
|
+
bytes = readFileSync8(opts.file);
|
|
17338
17750
|
} catch (err) {
|
|
17339
17751
|
console.error(`Error: cannot read file "${opts.file}": ${err.message}`);
|
|
17340
17752
|
process.exit(1);
|
package/dist/session-runner.js
CHANGED
|
@@ -18,10 +18,26 @@ import { mkdir, writeFile, rm } from "fs/promises";
|
|
|
18
18
|
import path from "path";
|
|
19
19
|
|
|
20
20
|
// ../shared/src/constants.ts
|
|
21
|
+
var TaskStatus = {
|
|
22
|
+
QUEUED: "queued",
|
|
23
|
+
DISPATCHED: "dispatched",
|
|
24
|
+
RUNNING: "running",
|
|
25
|
+
COMPLETED: "completed",
|
|
26
|
+
FAILED: "failed",
|
|
27
|
+
CANCELLED: "cancelled",
|
|
28
|
+
SUPERSEDED: "superseded"
|
|
29
|
+
};
|
|
30
|
+
var TERMINAL_TASK_STATUSES = [
|
|
31
|
+
TaskStatus.COMPLETED,
|
|
32
|
+
TaskStatus.FAILED,
|
|
33
|
+
TaskStatus.CANCELLED,
|
|
34
|
+
TaskStatus.SUPERSEDED
|
|
35
|
+
];
|
|
21
36
|
var TASK_TYPES = {
|
|
22
37
|
USER_DM_MESSAGE: "user_dm_message",
|
|
23
38
|
EMAIL_NOTIFICATION: "email_notification",
|
|
24
|
-
CALENDAR_EVENT: "calendar_event"
|
|
39
|
+
CALENDAR_EVENT: "calendar_event",
|
|
40
|
+
KILL_TASK: "kill_task"
|
|
25
41
|
};
|
|
26
42
|
var POLL_INTERVAL_MS = Number(process.env.POLL_INTERVAL_MS) || 3000;
|
|
27
43
|
var OFFLINE_THRESHOLD_MS = Number(process.env.OFFLINE_THRESHOLD_MS) || 9000;
|
|
@@ -13568,7 +13584,8 @@ var TaskStatusSchema = exports_external.enum([
|
|
|
13568
13584
|
"running",
|
|
13569
13585
|
"completed",
|
|
13570
13586
|
"failed",
|
|
13571
|
-
"cancelled"
|
|
13587
|
+
"cancelled",
|
|
13588
|
+
"superseded"
|
|
13572
13589
|
]);
|
|
13573
13590
|
var ClaimedTaskRowSchema = exports_external.object({
|
|
13574
13591
|
id: exports_external.string(),
|
|
@@ -13749,8 +13766,9 @@ var UpdateAgentRequestSchema = exports_external.object({
|
|
|
13749
13766
|
description: exports_external.string().optional(),
|
|
13750
13767
|
instructions: exports_external.string().optional(),
|
|
13751
13768
|
runtime_id: exports_external.string().min(1).optional(),
|
|
13752
|
-
runtime_config: RuntimeConfigSchema
|
|
13753
|
-
|
|
13769
|
+
runtime_config: RuntimeConfigSchema,
|
|
13770
|
+
visibility: exports_external.enum(["public", "private"]).optional()
|
|
13771
|
+
}).refine((v) => v.name !== undefined || v.description !== undefined || v.instructions !== undefined || v.runtime_id !== undefined || v.runtime_config !== undefined || v.visibility !== undefined, { message: "at least one field is required" });
|
|
13754
13772
|
var CreateConversationRequestSchema = exports_external.object({
|
|
13755
13773
|
agent_id: exports_external.string().min(1, "agent_id is required")
|
|
13756
13774
|
});
|
|
@@ -13842,6 +13860,16 @@ var CreateWorkspaceRequestSchema = exports_external.object({
|
|
|
13842
13860
|
name: exports_external.string().min(1, "name is required"),
|
|
13843
13861
|
slug: exports_external.string().min(1, "slug is required")
|
|
13844
13862
|
});
|
|
13863
|
+
var UpdateWorkspaceRequestSchema = exports_external.object({
|
|
13864
|
+
name: exports_external.string().min(1, "name is required").max(100).trim().optional(),
|
|
13865
|
+
slug: exports_external.string().min(1, "slug is required").max(100).trim().toLowerCase().optional()
|
|
13866
|
+
});
|
|
13867
|
+
var DeleteWorkspaceRequestSchema = exports_external.object({
|
|
13868
|
+
confirm_name: exports_external.string().min(1, "confirm_name is required")
|
|
13869
|
+
});
|
|
13870
|
+
var GrantAgentAccessRequestSchema = exports_external.object({
|
|
13871
|
+
user_id: exports_external.string().min(1, "user_id is required")
|
|
13872
|
+
});
|
|
13845
13873
|
// ../../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
|
|
13846
13874
|
var entityKind = Symbol.for("drizzle:entityKind");
|
|
13847
13875
|
var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
|
|
@@ -15273,6 +15301,48 @@ var member = sqliteTable("member", {
|
|
|
15273
15301
|
globalInstruction: text("global_instruction").notNull().default(""),
|
|
15274
15302
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15275
15303
|
}, (t) => [unique("member_workspace_user").on(t.workspaceId, t.userId)]);
|
|
15304
|
+
var workspaceInvite = sqliteTable("workspace_invite", {
|
|
15305
|
+
id: text("id").primaryKey().$defaultFn(() => "inv_" + nanoid3()),
|
|
15306
|
+
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
15307
|
+
token: text("token").unique().notNull().$defaultFn(() => nanoid3(32)),
|
|
15308
|
+
createdBy: text("created_by").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
15309
|
+
usedBy: text("used_by").references(() => user.id, { onDelete: "set null" }),
|
|
15310
|
+
usedAt: text("used_at"),
|
|
15311
|
+
expiresAt: text("expires_at").notNull(),
|
|
15312
|
+
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15313
|
+
}, (t) => [
|
|
15314
|
+
index("idx_workspace_invite_token").on(t.token),
|
|
15315
|
+
index("idx_workspace_invite_workspace").on(t.workspaceId)
|
|
15316
|
+
]);
|
|
15317
|
+
var agentAccess = sqliteTable("agent_access", {
|
|
15318
|
+
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15319
|
+
agentId: text("agent_id").notNull(),
|
|
15320
|
+
workspaceId: text("workspace_id").notNull(),
|
|
15321
|
+
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
15322
|
+
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15323
|
+
}, (t) => [
|
|
15324
|
+
unique("agent_access_agent_ws_user").on(t.agentId, t.workspaceId, t.userId),
|
|
15325
|
+
index("idx_agent_access_agent_ws").on(t.agentId, t.workspaceId),
|
|
15326
|
+
index("idx_agent_access_user").on(t.userId),
|
|
15327
|
+
foreignKey({
|
|
15328
|
+
columns: [t.agentId, t.workspaceId],
|
|
15329
|
+
foreignColumns: [agent.id, agent.workspaceId]
|
|
15330
|
+
}).onDelete("cascade")
|
|
15331
|
+
]);
|
|
15332
|
+
var agentPin = sqliteTable("agent_pin", {
|
|
15333
|
+
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15334
|
+
agentId: text("agent_id").notNull(),
|
|
15335
|
+
workspaceId: text("workspace_id").notNull(),
|
|
15336
|
+
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
15337
|
+
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15338
|
+
}, (t) => [
|
|
15339
|
+
unique("agent_pin_agent_ws_user").on(t.agentId, t.workspaceId, t.userId),
|
|
15340
|
+
index("idx_agent_pin_ws_user").on(t.workspaceId, t.userId),
|
|
15341
|
+
foreignKey({
|
|
15342
|
+
columns: [t.agentId, t.workspaceId],
|
|
15343
|
+
foreignColumns: [agent.id, agent.workspaceId]
|
|
15344
|
+
}).onDelete("cascade")
|
|
15345
|
+
]);
|
|
15276
15346
|
var machine = sqliteTable("machine", {
|
|
15277
15347
|
daemonId: text("daemon_id").notNull(),
|
|
15278
15348
|
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
@@ -15410,6 +15480,7 @@ var emails = sqliteTable("emails", {
|
|
|
15410
15480
|
htmlBody: text("html_body").notNull().default(""),
|
|
15411
15481
|
attachments: text("attachments").notNull().default("[]"),
|
|
15412
15482
|
status: text("status").notNull().default("unread"),
|
|
15483
|
+
direction: text("direction").notNull().default("inbound"),
|
|
15413
15484
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15414
15485
|
}, (t) => [
|
|
15415
15486
|
foreignKey({
|
|
@@ -15589,6 +15660,9 @@ class DaemonClient {
|
|
|
15589
15660
|
error: error48
|
|
15590
15661
|
});
|
|
15591
15662
|
}
|
|
15663
|
+
supersedeTask(token, taskId) {
|
|
15664
|
+
return this.request("POST", `/api/daemon/tasks/${taskId}/supersede`, token);
|
|
15665
|
+
}
|
|
15592
15666
|
async getArtifactMeta(token, artifactId, workspaceId) {
|
|
15593
15667
|
return this.request("GET", `/api/artifacts/${artifactId}?workspace_id=${encodeURIComponent(workspaceId)}`, token);
|
|
15594
15668
|
}
|
|
@@ -16581,6 +16655,7 @@ Your current context is only a fraction of the full timeline of what's your have
|
|
|
16581
16655
|
The full context timeline is inside './.context_timeline/YYYY-MM-DD.jsonl'.
|
|
16582
16656
|
Each line of a timeline JSONL is a JSON object with these fields:
|
|
16583
16657
|
- "task_id" — unique task identifier
|
|
16658
|
+
- "context_key" — thread identifier, the same context key meaning those tasks are in the same thread.
|
|
16584
16659
|
- "session_id" — agent session identifier (null until completion)
|
|
16585
16660
|
- "pid" — daemon process ID (present while running, null when done)
|
|
16586
16661
|
- "status" — "running", "completed", or "failed"
|
|
@@ -16589,18 +16664,21 @@ Each line of a timeline JSONL is a JSON object with these fields:
|
|
|
16589
16664
|
- "prompt" — what the user asked
|
|
16590
16665
|
- "agent_responses" — assistant text outputs during execution
|
|
16591
16666
|
- "errmsg" — error message (null unless status is "failed")
|
|
16667
|
+
- "detailed_log" — the running log for this task, including the actually task agent messages with tool-calls and failed logs.
|
|
16592
16668
|
those json are sorted by datetime in asc order.
|
|
16593
16669
|
|
|
16594
16670
|
## BE HUMAN
|
|
16595
16671
|
- When you recall the timelines, don't say "I find ... in my timeline jsonl". Just say "I can recall..."
|
|
16596
16672
|
- User don't know about the context timeline jsonl, this is your secret weapon.
|
|
16597
16673
|
- Act and response like you're human and actually have memory, not a file reading machine.
|
|
16674
|
+
- Don't report your agent id and workspace id when not asked, it will break the image that you're a human colleague.
|
|
16598
16675
|
|
|
16599
16676
|
## RULES
|
|
16600
16677
|
- Read @memory.md(if exists) before your action.
|
|
16601
16678
|
- When you start a new task, read the last ~10 lines of today's timeline to understand what has been asked and done recently.
|
|
16602
16679
|
- if you don't know the current datetime, obtain the current datetime first.
|
|
16603
16680
|
- 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).
|
|
16681
|
+
- Use grep tool to search in the context timeline jsonls if you have clean and focus keywords to recall.
|
|
16604
16682
|
- When access other local projects, make sure you read the CLAUDE.md/AGENTS.md file under the project root dir to understand the requirements.
|
|
16605
16683
|
`;
|
|
16606
16684
|
function buildInstructionContent(task) {
|
|
@@ -16634,7 +16712,7 @@ ${task.agent?.userEmail ? `Your owner's email address is '${task.agent.userEmail
|
|
|
16634
16712
|
|
|
16635
16713
|
### Emails
|
|
16636
16714
|
---
|
|
16637
|
-
Run 'npx @alook/cli email pull --agent_id ${task.agentId} --
|
|
16715
|
+
Run 'npx @alook/cli email pull --agent_id ${task.agentId} --status unread' to download unread emails to '/tmp/alook-emails/${task.workspaceId}/${task.agentId}/'.
|
|
16638
16716
|
Each email is saved to '/tmp/alook-emails/${task.workspaceId}/${task.agentId}/<emailId>/' with:
|
|
16639
16717
|
- 'metadata.json' — sender, recipient, subject, date, status, message_id, in_reply_to, references
|
|
16640
16718
|
- 'body.txt' — plain text body
|
|
@@ -16642,22 +16720,31 @@ Each email is saved to '/tmp/alook-emails/${task.workspaceId}/${task.agentId}/<e
|
|
|
16642
16720
|
- 'attachments/' — extracted attachment files (if any)
|
|
16643
16721
|
---
|
|
16644
16722
|
Before starting to process an email, mark it as read:
|
|
16645
|
-
- Run 'npx @alook/cli email set --agent_id ${task.agentId} --
|
|
16723
|
+
- Run 'npx @alook/cli email set --agent_id ${task.agentId} --email_id <EMAIL_ID> --status read'
|
|
16646
16724
|
---
|
|
16647
16725
|
|
|
16648
16726
|
#### Sending a new email
|
|
16649
16727
|
Write the HTML body to a file first, then send it. The body is forwarded as-is (HTML).
|
|
16650
|
-
- Run 'npx @alook/cli email send --agent_id ${task.agentId} --
|
|
16728
|
+
- Run 'npx @alook/cli email send --agent_id ${task.agentId} --to <ADDRESS> --subject "<SUBJECT>" --body-file <PATH_TO_HTML>'
|
|
16651
16729
|
- To send from a specific mailbox, add '--from <YOUR_EMAIL_ADDRESS>'. Without '--from', the default Alook address is used.
|
|
16652
16730
|
- Attach files with '--attachment <PATH>' — repeat the flag for multiple attachments. Each file is uploaded before sending.
|
|
16653
|
-
- Example: 'npx @alook/cli email send --agent_id ${task.agentId} --
|
|
16731
|
+
- Example: 'npx @alook/cli email send --agent_id ${task.agentId} --to foo@bar.com --subject "Weekly report" --body-file /tmp/body.html --from alice@company.com --attachment /tmp/report.pdf'
|
|
16654
16732
|
|
|
16655
16733
|
#### Replying to an email
|
|
16656
16734
|
To reply to an email, add '--in-reply-to <EMAIL_ID>' to the send command. This sets the correct email threading headers so the recipient's email client groups the reply into the same conversation thread.
|
|
16657
16735
|
- Use 'Re: <original subject>' as the subject.
|
|
16658
16736
|
- Quote the original email body in your reply (wrap it in a blockquote).
|
|
16659
16737
|
- The <EMAIL_ID> is the Alook email id from metadata.json (not the message_id header).
|
|
16660
|
-
- Example: 'npx @alook/cli email send --agent_id ${task.agentId} --
|
|
16738
|
+
- Example: 'npx @alook/cli email send --agent_id ${task.agentId} --to sender@example.com --subject "Re: Bug report" --body-file /tmp/reply.html --in-reply-to <EMAIL_ID>'
|
|
16739
|
+
Tips:
|
|
16740
|
+
- 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.
|
|
16741
|
+
---
|
|
16742
|
+
|
|
16743
|
+
#### Email Whitelist (Allowed Senders)
|
|
16744
|
+
Manage which email addresses are allowed to send you emails.
|
|
16745
|
+
- List: 'npx @alook/cli email whitelist list --agent_id ${task.agentId}' (add '--json' for machine-readable output)
|
|
16746
|
+
- Add: 'npx @alook/cli email whitelist add --agent_id ${task.agentId} <EMAIL_ADDRESS>'
|
|
16747
|
+
- Remove: 'npx @alook/cli email whitelist delete --agent_id ${task.agentId} <EMAIL_ADDRESS>'
|
|
16661
16748
|
---
|
|
16662
16749
|
`;
|
|
16663
16750
|
}
|
|
@@ -16667,6 +16754,8 @@ Upload files for your owner to review in the app.
|
|
|
16667
16754
|
- Your current conversation id is available via env var: $ALOOK_CONVERSATION_ID
|
|
16668
16755
|
- Run 'npx @alook/cli sync upload-artifact --agent_id ${task.agentId} --conversation_id $ALOOK_CONVERSATION_ID --file <PATH>'
|
|
16669
16756
|
- Use this after generating plans, reports, or any file the owner should review.
|
|
16757
|
+
- You response will be rendered in remote server, so don't output link format with local path in your response (cause user can click it and jump to nowheres)
|
|
16758
|
+
- If you think user may need to know any file detail, use upload-artifact tool to send the file to user.
|
|
16670
16759
|
---
|
|
16671
16760
|
|
|
16672
16761
|
### Attachments
|
|
@@ -17058,7 +17147,7 @@ function findResumableSessionByContextKey(timelineDir, contextKey, provider) {
|
|
|
17058
17147
|
}
|
|
17059
17148
|
entries.sort((a, b) => new Date(b.datetime).getTime() - new Date(a.datetime).getTime());
|
|
17060
17149
|
for (const entry of entries) {
|
|
17061
|
-
if (entry.status
|
|
17150
|
+
if (entry.status !== "running" && entry.context_key === contextKey && entry.provider === provider && entry.session_id && new Date(entry.datetime) >= cutoff) {
|
|
17062
17151
|
return entry.session_id;
|
|
17063
17152
|
}
|
|
17064
17153
|
}
|
|
@@ -17082,9 +17171,37 @@ function prepare(config2, task) {
|
|
|
17082
17171
|
return { workDir, timelineDir, env };
|
|
17083
17172
|
}
|
|
17084
17173
|
|
|
17174
|
+
// daemon/execenv/steering.ts
|
|
17175
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3, unlinkSync as unlinkSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
17176
|
+
import { join as join4 } from "path";
|
|
17177
|
+
var INTENT_DIR_NAME = ".kill_intents";
|
|
17178
|
+
var INTENT_STALE_MS = 10 * 60 * 1000;
|
|
17179
|
+
function intentFilePath(baseDir, taskId) {
|
|
17180
|
+
return join4(baseDir, INTENT_DIR_NAME, `${taskId}.json`);
|
|
17181
|
+
}
|
|
17182
|
+
function readKillIntent(baseDir, taskId) {
|
|
17183
|
+
const filePath = intentFilePath(baseDir, taskId);
|
|
17184
|
+
try {
|
|
17185
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
17186
|
+
return JSON.parse(content);
|
|
17187
|
+
} catch {
|
|
17188
|
+
return null;
|
|
17189
|
+
}
|
|
17190
|
+
}
|
|
17191
|
+
function clearKillIntent(baseDir, taskId) {
|
|
17192
|
+
const filePath = intentFilePath(baseDir, taskId);
|
|
17193
|
+
try {
|
|
17194
|
+
unlinkSync2(filePath);
|
|
17195
|
+
} catch {}
|
|
17196
|
+
}
|
|
17197
|
+
|
|
17085
17198
|
// daemon/prompt.ts
|
|
17199
|
+
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.";
|
|
17086
17200
|
function buildPrompt(task, attachments) {
|
|
17087
17201
|
const obj = { type: task.type, instruction: task.prompt };
|
|
17202
|
+
if (task.type === "email_notification") {
|
|
17203
|
+
obj.notice = EMAIL_NOTICE;
|
|
17204
|
+
}
|
|
17088
17205
|
if (attachments && attachments.length > 0) {
|
|
17089
17206
|
obj.attachments = attachments.map((a) => ({
|
|
17090
17207
|
path: a.path,
|
|
@@ -17178,6 +17295,7 @@ async function runSession(input) {
|
|
|
17178
17295
|
};
|
|
17179
17296
|
const flushTimer = setInterval(flushMessages, FLUSH_INTERVAL_MS);
|
|
17180
17297
|
let killed = false;
|
|
17298
|
+
const agentBaseDir = path.dirname(timelineDir);
|
|
17181
17299
|
const onKill = async () => {
|
|
17182
17300
|
if (killed)
|
|
17183
17301
|
return;
|
|
@@ -17193,14 +17311,37 @@ async function runSession(input) {
|
|
|
17193
17311
|
await flushMessages();
|
|
17194
17312
|
} catch {}
|
|
17195
17313
|
await cleanupAttachments(task.id);
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17314
|
+
const intent = readKillIntent(agentBaseDir, task.id);
|
|
17315
|
+
clearKillIntent(agentBaseDir, task.id);
|
|
17316
|
+
if (intent?.reason === "superseded") {
|
|
17317
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17318
|
+
entry.pid = null;
|
|
17319
|
+
entry.status = "superseded";
|
|
17320
|
+
entry.successor_task_id = intent.successorTaskId ?? null;
|
|
17321
|
+
entry.supersede_reason = "superseded by newer task";
|
|
17322
|
+
});
|
|
17323
|
+
try {
|
|
17324
|
+
await client.supersedeTask(token, task.id);
|
|
17325
|
+
} catch {}
|
|
17326
|
+
} else if (intent?.reason === "cancelled") {
|
|
17327
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17328
|
+
entry.pid = null;
|
|
17329
|
+
entry.status = "cancelled";
|
|
17330
|
+
entry.errmsg = "cancelled by user";
|
|
17331
|
+
});
|
|
17332
|
+
try {
|
|
17333
|
+
await client.failTask(token, task.id, "cancelled by user");
|
|
17334
|
+
} catch {}
|
|
17335
|
+
} else {
|
|
17336
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
17337
|
+
entry.pid = null;
|
|
17338
|
+
entry.status = "killed";
|
|
17339
|
+
entry.errmsg = "killed by signal";
|
|
17340
|
+
});
|
|
17341
|
+
try {
|
|
17342
|
+
await client.failTask(token, task.id, "killed by signal");
|
|
17343
|
+
} catch {}
|
|
17344
|
+
}
|
|
17204
17345
|
process.exit(1);
|
|
17205
17346
|
};
|
|
17206
17347
|
process.on("SIGTERM", onKill);
|