@alook/cli 0.0.9 → 0.0.10

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 CHANGED
@@ -15,7 +15,7 @@ var __export = (target, all) => {
15
15
  };
16
16
 
17
17
  // src/index.ts
18
- import { Command as Command9 } from "commander";
18
+ import { Command as Command10 } from "commander";
19
19
 
20
20
  // commands/register.ts
21
21
  import { Command } from "commander";
@@ -13896,7 +13896,8 @@ var TaskApiBaseSchema = exports_external.object({
13896
13896
  error: exports_external.string().nullable(),
13897
13897
  created_at: exports_external.string(),
13898
13898
  type: exports_external.string(),
13899
- context_key: exports_external.string().nullable().optional()
13899
+ context_key: exports_external.string().nullable().optional(),
13900
+ context: exports_external.unknown().nullable().optional()
13900
13901
  });
13901
13902
  var TaskApiSchema = TaskApiBaseSchema.extend({
13902
13903
  agent: TaskAgentDataApiSchema.nullable().optional()
@@ -14015,6 +14016,64 @@ var CalendarEventApiSchema = exports_external.object({
14015
14016
  var AddWhitelistRequestSchema = exports_external.object({
14016
14017
  email: exports_external.string().email()
14017
14018
  });
14019
+ var RuntimeConfigSchema = exports_external.object({ model: exports_external.string().max(100).optional() }).passthrough().optional();
14020
+ var CreateAgentRequestSchema = exports_external.object({
14021
+ name: exports_external.string().min(1, "name is required"),
14022
+ description: exports_external.string().optional().default(""),
14023
+ instructions: exports_external.string().optional().default(""),
14024
+ runtime_id: exports_external.string().min(1, "runtime_id is required"),
14025
+ runtime_config: RuntimeConfigSchema,
14026
+ max_concurrent_tasks: exports_external.number().int().optional(),
14027
+ email_handle: exports_external.string().optional()
14028
+ });
14029
+ var UpdateAgentRequestSchema = exports_external.object({
14030
+ name: exports_external.string().min(1).optional(),
14031
+ description: exports_external.string().optional(),
14032
+ instructions: exports_external.string().optional(),
14033
+ runtime_id: exports_external.string().min(1).optional(),
14034
+ runtime_config: RuntimeConfigSchema
14035
+ }).refine((v) => v.name !== undefined || v.description !== undefined || v.instructions !== undefined || v.runtime_id !== undefined || v.runtime_config !== undefined, { message: "at least one field is required" });
14036
+ var CreateConversationRequestSchema = exports_external.object({
14037
+ agent_id: exports_external.string().min(1, "agent_id is required")
14038
+ });
14039
+ var CreateMessageRequestSchema = exports_external.object({
14040
+ content: exports_external.string().min(1, "content is required")
14041
+ });
14042
+ var EmailAttachmentSchema = exports_external.object({
14043
+ key: exports_external.string().min(1),
14044
+ filename: exports_external.string().min(1),
14045
+ size: exports_external.number().int().nonnegative().optional(),
14046
+ contentType: exports_external.string().min(1)
14047
+ });
14048
+ var SendEmailRequestSchema = exports_external.object({
14049
+ agentId: exports_external.string().min(1, "agentId is required"),
14050
+ to: exports_external.string().min(1, "to is required"),
14051
+ subject: exports_external.string().min(1, "subject is required"),
14052
+ htmlBody: exports_external.string().default(""),
14053
+ inReplyTo: exports_external.string().optional(),
14054
+ references: exports_external.string().optional(),
14055
+ attachments: exports_external.array(EmailAttachmentSchema).optional()
14056
+ });
14057
+ var UpdateEmailStatusRequestSchema = exports_external.object({
14058
+ status: exports_external.enum(["unread", "read", "archived"])
14059
+ });
14060
+ var EmailNotifyRequestSchema = exports_external.object({
14061
+ agentId: exports_external.string().min(1),
14062
+ workspaceId: exports_external.string().min(1),
14063
+ r2Key: exports_external.string().min(1),
14064
+ from: exports_external.string().min(1),
14065
+ to: exports_external.string().optional(),
14066
+ subject: exports_external.string().min(1),
14067
+ isWhitelisted: exports_external.boolean(),
14068
+ forwarded: exports_external.boolean().optional().default(false),
14069
+ messageId: exports_external.string().optional().default(""),
14070
+ inReplyTo: exports_external.string().optional().default(""),
14071
+ references: exports_external.string().optional().default("")
14072
+ });
14073
+ var CreateWorkspaceRequestSchema = exports_external.object({
14074
+ name: exports_external.string().min(1, "name is required"),
14075
+ slug: exports_external.string().min(1, "slug is required")
14076
+ });
14018
14077
  // ../../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
14019
14078
  var entityKind = Symbol.for("drizzle:entityKind");
14020
14079
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -15521,6 +15580,7 @@ var message = sqliteTable("message", {
15521
15580
  role: text("role").notNull(),
15522
15581
  content: text("content").notNull().default(""),
15523
15582
  taskId: text("task_id"),
15583
+ attachmentIds: text("attachment_ids"),
15524
15584
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15525
15585
  });
15526
15586
  var agentTaskQueue = sqliteTable("agent_task_queue", {
@@ -15605,6 +15665,24 @@ var calendarEvent = sqliteTable("calendar_event", {
15605
15665
  foreignColumns: [agent.id, agent.workspaceId]
15606
15666
  }).onDelete("cascade")
15607
15667
  ]);
15668
+ var artifact = sqliteTable("artifact", {
15669
+ id: text("id").primaryKey().$defaultFn(() => "art_" + nanoid3()),
15670
+ conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
15671
+ agentId: text("agent_id").notNull(),
15672
+ workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
15673
+ filename: text("filename").notNull(),
15674
+ contentType: text("content_type").notNull().default("application/octet-stream"),
15675
+ size: integer2("size").notNull(),
15676
+ r2Key: text("r2_key").notNull(),
15677
+ source: text("source").notNull().default("agent"),
15678
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15679
+ }, (t) => [
15680
+ index("idx_artifact_conversation").on(t.conversationId),
15681
+ foreignKey({
15682
+ columns: [t.agentId, t.workspaceId],
15683
+ foreignColumns: [agent.id, agent.workspaceId]
15684
+ }).onDelete("cascade")
15685
+ ]);
15608
15686
  var machineToken = sqliteTable("machine_token", {
15609
15687
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
15610
15688
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -15701,6 +15779,16 @@ class DaemonClient {
15701
15779
  error: error48
15702
15780
  });
15703
15781
  }
15782
+ async getArtifactMeta(token, artifactId, workspaceId) {
15783
+ return this.request("GET", `/api/artifacts/${artifactId}?workspace_id=${encodeURIComponent(workspaceId)}`, token);
15784
+ }
15785
+ async downloadArtifact(token, artifactId, workspaceId) {
15786
+ const res = await fetch(`${this.baseURL}/api/artifacts/${artifactId}/content?workspace_id=${encodeURIComponent(workspaceId)}`, { headers: { Authorization: `Bearer ${token}` } });
15787
+ if (!res.ok) {
15788
+ throw new Error(`artifact download failed: HTTP ${res.status}`);
15789
+ }
15790
+ return res.arrayBuffer();
15791
+ }
15704
15792
  reportMessages(token, taskId, messages) {
15705
15793
  return this.request("POST", `/api/daemon/tasks/${taskId}/messages`, token, { messages });
15706
15794
  }
@@ -15871,6 +15959,7 @@ function fromApiTask(api2) {
15871
15959
  priority: api2.priority,
15872
15960
  type: api2.type,
15873
15961
  contextKey: api2.context_key ?? null,
15962
+ context: api2.context ?? undefined,
15874
15963
  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,
15875
15964
  repos: undefined,
15876
15965
  createdAt: api2.created_at
@@ -17034,8 +17123,83 @@ ${result.output}`);
17034
17123
  return cmd;
17035
17124
  }
17036
17125
 
17126
+ // commands/sync.ts
17127
+ import { Command as Command9 } from "commander";
17128
+ import { readFileSync as readFileSync5, statSync as statSync3 } from "fs";
17129
+ import { basename as basename2 } from "path";
17130
+ var MIME_BY_EXT2 = {
17131
+ ".pdf": "application/pdf",
17132
+ ".png": "image/png",
17133
+ ".jpg": "image/jpeg",
17134
+ ".jpeg": "image/jpeg",
17135
+ ".gif": "image/gif",
17136
+ ".txt": "text/plain",
17137
+ ".html": "text/html",
17138
+ ".json": "application/json",
17139
+ ".csv": "text/csv",
17140
+ ".md": "text/markdown",
17141
+ ".ts": "text/typescript",
17142
+ ".js": "text/javascript",
17143
+ ".yaml": "text/yaml",
17144
+ ".yml": "text/yaml",
17145
+ ".svg": "image/svg+xml",
17146
+ ".xml": "application/xml",
17147
+ ".zip": "application/zip"
17148
+ };
17149
+ function guessContentType2(filename) {
17150
+ const idx = filename.lastIndexOf(".");
17151
+ if (idx < 0)
17152
+ return "application/octet-stream";
17153
+ const ext = filename.slice(idx).toLowerCase();
17154
+ return MIME_BY_EXT2[ext] ?? "application/octet-stream";
17155
+ }
17156
+ function resolveClientOpts3(command, agentId) {
17157
+ const parentOpts = command.parent?.parent?.opts() || {};
17158
+ const profile = parentOpts.profile;
17159
+ const cfg = loadCLIConfigForProfile(profile);
17160
+ const serverUrl = parentOpts.server || cfg.server_url;
17161
+ const workspaces = cfg.watched_workspaces || [];
17162
+ const ws = workspaces.find((w) => w.agent_ids?.includes(agentId));
17163
+ if (!ws || !ws.token) {
17164
+ console.error(`Error: no registered workspace contains agent ${agentId}. Run '${cmdPrefix()} register --token <token>' first.`);
17165
+ process.exit(1);
17166
+ }
17167
+ return { serverUrl, token: ws.token, workspaceId: ws.id };
17168
+ }
17169
+ function syncCommand() {
17170
+ const cmd = new Command9("sync").description("File sync utilities");
17171
+ cmd.command("upload-artifact").description("Upload a file artifact to a conversation").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--conversation_id <id>", "Conversation ID").requiredOption("--file <path>", "Path to file to upload").action(async (opts, command) => {
17172
+ const { serverUrl, token, workspaceId } = resolveClientOpts3(command, opts.agent_id);
17173
+ const client = new APIClient(serverUrl, token, workspaceId);
17174
+ let bytes;
17175
+ let size;
17176
+ try {
17177
+ const stat = statSync3(opts.file);
17178
+ size = stat.size;
17179
+ bytes = readFileSync5(opts.file);
17180
+ } catch (err) {
17181
+ console.error(`Error: cannot read file "${opts.file}": ${err.message}`);
17182
+ process.exit(1);
17183
+ }
17184
+ const filename = basename2(opts.file);
17185
+ const contentType = guessContentType2(filename);
17186
+ const form = new FormData;
17187
+ form.append("file", new Blob([new Uint8Array(bytes)], { type: contentType }), filename);
17188
+ form.append("agent_id", opts.agent_id);
17189
+ form.append("conversation_id", opts.conversation_id);
17190
+ try {
17191
+ const result = await client.postMultipart("/api/artifacts/upload", form);
17192
+ printJSON(result);
17193
+ } catch (err) {
17194
+ console.error(`Error uploading artifact: ${err.message}`);
17195
+ process.exit(1);
17196
+ }
17197
+ });
17198
+ return cmd;
17199
+ }
17200
+
17037
17201
  // src/index.ts
17038
- var program = new Command9;
17202
+ var program = new Command10;
17039
17203
  program.name("alook").description("Alook CLI").option("--server <url>", "Server URL").option("--profile <name>", "Profile name");
17040
17204
  program.addCommand(registerCommand());
17041
17205
  program.addCommand(statusCommand());
@@ -17045,4 +17209,5 @@ program.addCommand(calendarCommand());
17045
17209
  program.addCommand(configCommand());
17046
17210
  program.addCommand(versionCommand());
17047
17211
  program.addCommand(updateCommand());
17212
+ program.addCommand(syncCommand());
17048
17213
  program.parse();
@@ -15,6 +15,8 @@ var __export = (target, all) => {
15
15
 
16
16
  // daemon/session-runner.ts
17
17
  import { createWriteStream } from "fs";
18
+ import { mkdir, writeFile, rm } from "fs/promises";
19
+ import path from "path";
18
20
 
19
21
  // ../shared/src/constants.ts
20
22
  var TASK_TYPES = {
@@ -13612,7 +13614,8 @@ var TaskApiBaseSchema = exports_external.object({
13612
13614
  error: exports_external.string().nullable(),
13613
13615
  created_at: exports_external.string(),
13614
13616
  type: exports_external.string(),
13615
- context_key: exports_external.string().nullable().optional()
13617
+ context_key: exports_external.string().nullable().optional(),
13618
+ context: exports_external.unknown().nullable().optional()
13616
13619
  });
13617
13620
  var TaskApiSchema = TaskApiBaseSchema.extend({
13618
13621
  agent: TaskAgentDataApiSchema.nullable().optional()
@@ -13731,6 +13734,64 @@ var CalendarEventApiSchema = exports_external.object({
13731
13734
  var AddWhitelistRequestSchema = exports_external.object({
13732
13735
  email: exports_external.string().email()
13733
13736
  });
13737
+ var RuntimeConfigSchema = exports_external.object({ model: exports_external.string().max(100).optional() }).passthrough().optional();
13738
+ var CreateAgentRequestSchema = exports_external.object({
13739
+ name: exports_external.string().min(1, "name is required"),
13740
+ description: exports_external.string().optional().default(""),
13741
+ instructions: exports_external.string().optional().default(""),
13742
+ runtime_id: exports_external.string().min(1, "runtime_id is required"),
13743
+ runtime_config: RuntimeConfigSchema,
13744
+ max_concurrent_tasks: exports_external.number().int().optional(),
13745
+ email_handle: exports_external.string().optional()
13746
+ });
13747
+ var UpdateAgentRequestSchema = exports_external.object({
13748
+ name: exports_external.string().min(1).optional(),
13749
+ description: exports_external.string().optional(),
13750
+ instructions: exports_external.string().optional(),
13751
+ runtime_id: exports_external.string().min(1).optional(),
13752
+ runtime_config: RuntimeConfigSchema
13753
+ }).refine((v) => v.name !== undefined || v.description !== undefined || v.instructions !== undefined || v.runtime_id !== undefined || v.runtime_config !== undefined, { message: "at least one field is required" });
13754
+ var CreateConversationRequestSchema = exports_external.object({
13755
+ agent_id: exports_external.string().min(1, "agent_id is required")
13756
+ });
13757
+ var CreateMessageRequestSchema = exports_external.object({
13758
+ content: exports_external.string().min(1, "content is required")
13759
+ });
13760
+ var EmailAttachmentSchema = exports_external.object({
13761
+ key: exports_external.string().min(1),
13762
+ filename: exports_external.string().min(1),
13763
+ size: exports_external.number().int().nonnegative().optional(),
13764
+ contentType: exports_external.string().min(1)
13765
+ });
13766
+ var SendEmailRequestSchema = exports_external.object({
13767
+ agentId: exports_external.string().min(1, "agentId is required"),
13768
+ to: exports_external.string().min(1, "to is required"),
13769
+ subject: exports_external.string().min(1, "subject is required"),
13770
+ htmlBody: exports_external.string().default(""),
13771
+ inReplyTo: exports_external.string().optional(),
13772
+ references: exports_external.string().optional(),
13773
+ attachments: exports_external.array(EmailAttachmentSchema).optional()
13774
+ });
13775
+ var UpdateEmailStatusRequestSchema = exports_external.object({
13776
+ status: exports_external.enum(["unread", "read", "archived"])
13777
+ });
13778
+ var EmailNotifyRequestSchema = exports_external.object({
13779
+ agentId: exports_external.string().min(1),
13780
+ workspaceId: exports_external.string().min(1),
13781
+ r2Key: exports_external.string().min(1),
13782
+ from: exports_external.string().min(1),
13783
+ to: exports_external.string().optional(),
13784
+ subject: exports_external.string().min(1),
13785
+ isWhitelisted: exports_external.boolean(),
13786
+ forwarded: exports_external.boolean().optional().default(false),
13787
+ messageId: exports_external.string().optional().default(""),
13788
+ inReplyTo: exports_external.string().optional().default(""),
13789
+ references: exports_external.string().optional().default("")
13790
+ });
13791
+ var CreateWorkspaceRequestSchema = exports_external.object({
13792
+ name: exports_external.string().min(1, "name is required"),
13793
+ slug: exports_external.string().min(1, "slug is required")
13794
+ });
13734
13795
  // ../../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
13735
13796
  var entityKind = Symbol.for("drizzle:entityKind");
13736
13797
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -15237,6 +15298,7 @@ var message = sqliteTable("message", {
15237
15298
  role: text("role").notNull(),
15238
15299
  content: text("content").notNull().default(""),
15239
15300
  taskId: text("task_id"),
15301
+ attachmentIds: text("attachment_ids"),
15240
15302
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15241
15303
  });
15242
15304
  var agentTaskQueue = sqliteTable("agent_task_queue", {
@@ -15321,6 +15383,24 @@ var calendarEvent = sqliteTable("calendar_event", {
15321
15383
  foreignColumns: [agent.id, agent.workspaceId]
15322
15384
  }).onDelete("cascade")
15323
15385
  ]);
15386
+ var artifact = sqliteTable("artifact", {
15387
+ id: text("id").primaryKey().$defaultFn(() => "art_" + nanoid3()),
15388
+ conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
15389
+ agentId: text("agent_id").notNull(),
15390
+ workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
15391
+ filename: text("filename").notNull(),
15392
+ contentType: text("content_type").notNull().default("application/octet-stream"),
15393
+ size: integer2("size").notNull(),
15394
+ r2Key: text("r2_key").notNull(),
15395
+ source: text("source").notNull().default("agent"),
15396
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15397
+ }, (t) => [
15398
+ index("idx_artifact_conversation").on(t.conversationId),
15399
+ foreignKey({
15400
+ columns: [t.agentId, t.workspaceId],
15401
+ foreignColumns: [agent.id, agent.workspaceId]
15402
+ }).onDelete("cascade")
15403
+ ]);
15324
15404
  var machineToken = sqliteTable("machine_token", {
15325
15405
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
15326
15406
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -15406,6 +15486,16 @@ class DaemonClient {
15406
15486
  error: error48
15407
15487
  });
15408
15488
  }
15489
+ async getArtifactMeta(token, artifactId, workspaceId) {
15490
+ return this.request("GET", `/api/artifacts/${artifactId}?workspace_id=${encodeURIComponent(workspaceId)}`, token);
15491
+ }
15492
+ async downloadArtifact(token, artifactId, workspaceId) {
15493
+ const res = await fetch(`${this.baseURL}/api/artifacts/${artifactId}/content?workspace_id=${encodeURIComponent(workspaceId)}`, { headers: { Authorization: `Bearer ${token}` } });
15494
+ if (!res.ok) {
15495
+ throw new Error(`artifact download failed: HTTP ${res.status}`);
15496
+ }
15497
+ return res.arrayBuffer();
15498
+ }
15409
15499
  reportMessages(token, taskId, messages) {
15410
15500
  return this.request("POST", `/api/daemon/tasks/${taskId}/messages`, token, { messages });
15411
15501
  }
@@ -16442,6 +16532,19 @@ To reply to an email, add '--in-reply-to <EMAIL_ID>' to the send command. This s
16442
16532
  `;
16443
16533
  }
16444
16534
  content += `
16535
+ ### Artifacts
16536
+ Upload files for your owner to review in the app.
16537
+ - Your current conversation id is available via env var: $ALOOK_CONVERSATION_ID
16538
+ - Run 'npx @alook/cli sync upload-artifact --agent_id ${task.agentId} --conversation_id $ALOOK_CONVERSATION_ID --file <PATH>'
16539
+ - Use this after generating plans, reports, or any file the owner should review.
16540
+ ---
16541
+
16542
+ ### Attachments
16543
+ When your task includes attachments, their local paths are listed in the prompt JSON under "attachments".
16544
+ Use your Read tool to open them. Images and PDFs are read visually.
16545
+ ---
16546
+ `;
16547
+ content += `
16445
16548
  ### Calendar
16446
16549
  You have your own calendar to setup daily routines and reminders.
16447
16550
  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').
@@ -16848,17 +16951,65 @@ function prepare(config2, task) {
16848
16951
  }
16849
16952
 
16850
16953
  // daemon/prompt.ts
16851
- function buildPrompt(task) {
16852
- return JSON.stringify({ type: task.type, instruction: task.prompt });
16954
+ function buildPrompt(task, attachments) {
16955
+ const obj = { type: task.type, instruction: task.prompt };
16956
+ if (attachments && attachments.length > 0) {
16957
+ obj.attachments = attachments.map((a) => ({
16958
+ path: a.path,
16959
+ content_type: a.content_type,
16960
+ filename: a.filename
16961
+ }));
16962
+ }
16963
+ return JSON.stringify(obj);
16853
16964
  }
16854
16965
 
16855
16966
  // daemon/session-runner.ts
16967
+ var ATTACHMENTS_BASE = "/tmp/alook-attachments";
16968
+ function sanitizeFilename(name) {
16969
+ return path.basename(name).replace(/[/\\]/g, "_").replace(/\.\./g, "_").slice(0, 255) || "file";
16970
+ }
16971
+ async function cleanupAttachments(taskId) {
16972
+ try {
16973
+ await rm(path.join(ATTACHMENTS_BASE, taskId), { recursive: true, force: true });
16974
+ } catch {}
16975
+ }
16976
+ async function downloadAttachments(client, token, workspaceId, taskId, attachmentIds) {
16977
+ const dir = path.join(ATTACHMENTS_BASE, taskId);
16978
+ await mkdir(dir, { recursive: true });
16979
+ const attachments = [];
16980
+ for (const artId of attachmentIds) {
16981
+ const meta3 = await client.getArtifactMeta(token, artId, workspaceId);
16982
+ const content = await client.downloadArtifact(token, artId, workspaceId);
16983
+ const filename = sanitizeFilename(meta3.filename);
16984
+ const localPath = path.join(dir, `${artId}_${filename}`);
16985
+ await writeFile(localPath, Buffer.from(content));
16986
+ attachments.push({
16987
+ path: localPath,
16988
+ content_type: meta3.content_type,
16989
+ filename: meta3.filename
16990
+ });
16991
+ }
16992
+ return attachments;
16993
+ }
16856
16994
  async function runSession(input) {
16857
16995
  const { task, provider, cliPath, model, serverURL, token, workspacesRoot, agentTimeout } = input;
16858
16996
  const client = new DaemonClient(serverURL);
16859
16997
  const backend = createBackend(provider, cliPath);
16860
- const prompt = buildPrompt(task);
16861
16998
  const { workDir, logFile, timelineDir, env } = prepare({ workspacesRoot }, task);
16999
+ const attachmentIds = task.context?.attachment_ids ?? [];
17000
+ let attachments;
17001
+ if (attachmentIds.length > 0) {
17002
+ try {
17003
+ attachments = await downloadAttachments(client, token, task.workspaceId, task.id, attachmentIds);
17004
+ } catch (e) {
17005
+ await cleanupAttachments(task.id);
17006
+ const errMsg = `failed to download attachments: ${e}`;
17007
+ log.error(`Task ${task.id} ${errMsg}`);
17008
+ await client.failTask(token, task.id, errMsg);
17009
+ return;
17010
+ }
17011
+ }
17012
+ const prompt = buildPrompt(task, attachments);
16862
17013
  const resumeSessionId = task.contextKey ? findResumableSessionByContextKey(timelineDir, task.contextKey, provider) ?? undefined : undefined;
16863
17014
  if (resumeSessionId) {
16864
17015
  log.info(`Task ${task.id} resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
@@ -16917,6 +17068,7 @@ async function runSession(input) {
16917
17068
  await flushMessages();
16918
17069
  } catch {}
16919
17070
  logStream?.end();
17071
+ await cleanupAttachments(task.id);
16920
17072
  updateEntry(timelineDir, task.id, (entry) => {
16921
17073
  entry.pid = null;
16922
17074
  entry.status = "killed";
@@ -16979,6 +17131,7 @@ async function runSession(input) {
16979
17131
  process.removeListener("SIGINT", onKill);
16980
17132
  if (killed)
16981
17133
  return;
17134
+ await cleanupAttachments(task.id);
16982
17135
  if (result.status === "completed") {
16983
17136
  updateEntry(timelineDir, task.id, (entry) => {
16984
17137
  entry.session_id = result.sessionId || null;
@@ -17024,6 +17177,7 @@ async function main() {
17024
17177
  await runSession(input);
17025
17178
  } catch (e) {
17026
17179
  log.error(`session-runner: unhandled error for task ${input.task.id}`, e);
17180
+ await cleanupAttachments(input.task.id);
17027
17181
  try {
17028
17182
  await client.failTask(input.token, input.task.id, `session-runner crash: ${e}`);
17029
17183
  } catch {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "Alook CLI — register and run always-on AI coding agents.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",