@alook/cli 0.0.40 → 0.0.42

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 Command10 } from "commander";
18
+ import { Command as Command11 } from "commander";
19
19
 
20
20
  // commands/register.ts
21
21
  import { Command } from "commander";
@@ -320,8 +320,29 @@ var TASK_TYPES = {
320
320
  USER_DM_MESSAGE: "user_dm_message",
321
321
  EMAIL_NOTIFICATION: "email_notification",
322
322
  CALENDAR_EVENT: "calendar_event",
323
+ ISSUE_EVENT: "issue_event",
323
324
  KILL_TASK: "kill_task"
324
325
  };
326
+ var IssueStatus = {
327
+ TODO: "todo",
328
+ IN_PROGRESS: "in_progress",
329
+ REVIEW: "review",
330
+ DONE: "done",
331
+ CLOSED: "closed",
332
+ CANCELED: "canceled",
333
+ FAILED: "failed"
334
+ };
335
+ var ACTIVE_ISSUE_STATUSES = [
336
+ IssueStatus.TODO,
337
+ IssueStatus.IN_PROGRESS,
338
+ IssueStatus.REVIEW
339
+ ];
340
+ var TERMINAL_ISSUE_STATUSES = [
341
+ IssueStatus.DONE,
342
+ IssueStatus.CLOSED,
343
+ IssueStatus.CANCELED,
344
+ IssueStatus.FAILED
345
+ ];
325
346
  var POLL_INTERVAL_MS = Number(process.env.POLL_INTERVAL_MS) || 3000;
326
347
  var OFFLINE_THRESHOLD_MS = Number(process.env.OFFLINE_THRESHOLD_MS) || 9000;
327
348
  var EVENT_POLL_INTERVAL_MS = Number(process.env.EVENT_POLL_INTERVAL_MS) || 2000;
@@ -14821,6 +14842,42 @@ var CalendarEventApiSchema = exports_external.object({
14821
14842
  created_at: exports_external.string(),
14822
14843
  updated_at: exports_external.string()
14823
14844
  });
14845
+ var IssueStatusSchema = exports_external.enum([
14846
+ IssueStatus.TODO,
14847
+ IssueStatus.IN_PROGRESS,
14848
+ IssueStatus.REVIEW,
14849
+ IssueStatus.DONE,
14850
+ IssueStatus.CLOSED,
14851
+ IssueStatus.CANCELED,
14852
+ IssueStatus.FAILED
14853
+ ]);
14854
+ var CreateIssueRequestSchema = exports_external.object({
14855
+ agent_id: exports_external.string().min(1, "agent_id is required"),
14856
+ title: exports_external.string().min(1, "title is required").max(200),
14857
+ description: exports_external.string().max(20000).optional().default("")
14858
+ });
14859
+ var UpdateIssueRequestSchema = exports_external.object({
14860
+ title: exports_external.string().min(1).max(200).optional(),
14861
+ description: exports_external.string().max(20000).optional(),
14862
+ status: IssueStatusSchema.optional()
14863
+ }).refine((v) => v.title !== undefined || v.description !== undefined || v.status !== undefined, { message: "at least one field is required" });
14864
+ var CreateIssueCommentRequestSchema = exports_external.object({
14865
+ content: exports_external.string().min(1, "content is required").max(20000)
14866
+ });
14867
+ var IssueApiSchema = exports_external.object({
14868
+ id: exports_external.string(),
14869
+ workspace_id: exports_external.string(),
14870
+ agent_id: exports_external.string(),
14871
+ creator_user_id: exports_external.string(),
14872
+ conversation_id: exports_external.string(),
14873
+ latest_task_id: exports_external.string().nullable(),
14874
+ title: exports_external.string(),
14875
+ description: exports_external.string(),
14876
+ status: IssueStatusSchema,
14877
+ created_at: exports_external.string(),
14878
+ updated_at: exports_external.string(),
14879
+ completed_at: exports_external.string().nullable()
14880
+ });
14824
14881
  var CreateAgentLinkRequestSchema = exports_external.object({
14825
14882
  source_agent_id: exports_external.string().min(1, "source_agent_id is required"),
14826
14883
  target_agent_id: exports_external.string().min(1, "target_agent_id is required"),
@@ -16603,6 +16660,30 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
16603
16660
  foreignColumns: [agent.id, agent.workspaceId]
16604
16661
  }).onDelete("cascade")
16605
16662
  ]);
16663
+ var issue2 = sqliteTable("issue", {
16664
+ id: text("id").primaryKey().$defaultFn(() => "iss_" + nanoid3()),
16665
+ workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
16666
+ agentId: text("agent_id").notNull(),
16667
+ creatorUserId: text("creator_user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
16668
+ conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
16669
+ latestTaskId: text("latest_task_id").references(() => agentTaskQueue.id, {
16670
+ onDelete: "set null"
16671
+ }),
16672
+ title: text("title").notNull(),
16673
+ description: text("description").notNull().default(""),
16674
+ status: text("status").notNull().default("todo"),
16675
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
16676
+ updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString()),
16677
+ completedAt: text("completed_at")
16678
+ }, (t) => [
16679
+ index("idx_issue_workspace_status_agent").on(t.workspaceId, t.status, t.agentId),
16680
+ index("idx_issue_workspace_updated").on(t.workspaceId, t.updatedAt),
16681
+ unique("issue_conversation_unique").on(t.conversationId),
16682
+ foreignKey({
16683
+ columns: [t.agentId, t.workspaceId],
16684
+ foreignColumns: [agent.id, agent.workspaceId]
16685
+ }).onDelete("cascade")
16686
+ ]);
16606
16687
  var taskMessage = sqliteTable("task_message", {
16607
16688
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
16608
16689
  taskId: text("task_id").notNull().references(() => agentTaskQueue.id, { onDelete: "cascade" }),
@@ -17969,7 +18050,11 @@ async function startDaemon(profile, serverUrl) {
17969
18050
  }
17970
18051
  if (pending_rescan) {
17971
18052
  log.info("Rescan requested — restarting daemon to re-detect runtimes");
18053
+ for (const id of evictedIds) {
18054
+ evictWorkspace(id);
18055
+ }
17972
18056
  requestRestart();
18057
+ return;
17973
18058
  }
17974
18059
  for (const apiTask of apiTasks) {
17975
18060
  const task = fromApiTask(apiTask);
@@ -19112,19 +19197,201 @@ function calendarCommand() {
19112
19197
  return cmd;
19113
19198
  }
19114
19199
 
19115
- // commands/version.ts
19200
+ // commands/issue.ts
19116
19201
  import { Command as Command7 } from "commander";
19202
+ import { readFileSync as readFileSync8 } from "fs";
19203
+ var VALID_STATUSES2 = ["todo", "in_progress", "review", "done", "closed", "canceled", "failed"];
19204
+ function resolveClientOpts3(command, agentId) {
19205
+ let root = command;
19206
+ while (root.parent)
19207
+ root = root.parent;
19208
+ const parentOpts = root.opts() || {};
19209
+ const profile = parentOpts.profile;
19210
+ const cfg = loadCLIConfigForProfile(profile);
19211
+ const serverUrl = parentOpts.server || cfg.server_url;
19212
+ const workspaces = cfg.watched_workspaces || [];
19213
+ const ws = workspaces.find((w) => w.agent_ids?.includes(agentId));
19214
+ if (!ws || !ws.token) {
19215
+ console.error(`Error: no registered workspace contains agent ${agentId}. Run '${cmdPrefix()} register --token <token>' first.`);
19216
+ process.exit(1);
19217
+ }
19218
+ return { serverUrl, token: ws.token, workspaceId: ws.id };
19219
+ }
19220
+ function readBody(opts) {
19221
+ if (opts.body && opts.bodyFile) {
19222
+ console.error("Error: --body and --body-file are mutually exclusive");
19223
+ process.exit(1);
19224
+ }
19225
+ if (opts.bodyFile)
19226
+ return readFileSync8(opts.bodyFile, "utf-8");
19227
+ return opts.body ?? "";
19228
+ }
19229
+ function printIssue(issue3) {
19230
+ console.log(`${issue3.id} ${issue3.status.padEnd(11)} ${issue3.title}`);
19231
+ }
19232
+ function printIssueDetail(issue3, messages) {
19233
+ console.log(`id: ${issue3.id}`);
19234
+ console.log(`agent_id: ${issue3.agent_id}`);
19235
+ console.log(`status: ${issue3.status}`);
19236
+ console.log(`conversation_id: ${issue3.conversation_id}`);
19237
+ if (issue3.latest_task_id)
19238
+ console.log(`latest_task_id: ${issue3.latest_task_id}`);
19239
+ console.log(`title: ${issue3.title}`);
19240
+ console.log("description:");
19241
+ console.log(issue3.description || "(no description)");
19242
+ if (messages && messages.length > 0) {
19243
+ console.log(`
19244
+ conversation:`);
19245
+ for (const m of messages) {
19246
+ console.log(`[${m.role}] ${m.content}`);
19247
+ }
19248
+ }
19249
+ }
19250
+ function issueCommand() {
19251
+ const cmd = new Command7("issue").description("Manage assigned issues");
19252
+ cmd.command("create").description("Create and dispatch an issue to an agent").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--title <title>", "Issue title").option("--description <text>", "Issue description").option("--body-file <path>", "Read issue description from a file").option("--json", "Output as JSON").action(async (opts, command) => {
19253
+ const { serverUrl, token, workspaceId } = resolveClientOpts3(command, opts.agent_id);
19254
+ const client = new APIClient(serverUrl, token, workspaceId);
19255
+ const description = readBody({ body: opts.description, bodyFile: opts.bodyFile });
19256
+ try {
19257
+ const res = await client.postJSON("/api/issues", {
19258
+ agent_id: opts.agent_id,
19259
+ title: opts.title,
19260
+ description
19261
+ });
19262
+ if (opts.json)
19263
+ return printJSON(res);
19264
+ console.log(`Created ${res.issue.id} — ${res.issue.title}`);
19265
+ } catch (err) {
19266
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
19267
+ process.exit(1);
19268
+ }
19269
+ });
19270
+ cmd.command("list").description("List issues for an agent").requiredOption("--agent_id <id>", "Agent ID").option("--status <status>", `Filter by status (${VALID_STATUSES2.join(", ")})`).option("--completed", "Show completed/closed/canceled/failed issues").option("--all", "Show all issues").option("--json", "Output as JSON").action(async (opts, command) => {
19271
+ if (opts.status && !VALID_STATUSES2.includes(opts.status)) {
19272
+ console.error(`Error: invalid status "${opts.status}"`);
19273
+ process.exit(1);
19274
+ }
19275
+ const { serverUrl, token, workspaceId } = resolveClientOpts3(command, opts.agent_id);
19276
+ const client = new APIClient(serverUrl, token, workspaceId);
19277
+ const params = new URLSearchParams({ agentId: opts.agent_id });
19278
+ if (opts.status)
19279
+ params.set("status", opts.status);
19280
+ if (!opts.all && !opts.status)
19281
+ params.set("terminal", opts.completed ? "true" : "false");
19282
+ try {
19283
+ const issues = await client.getJSON(`/api/issues?${params}`);
19284
+ if (opts.json)
19285
+ return printJSON(issues);
19286
+ if (issues.length === 0) {
19287
+ console.log("No issues found.");
19288
+ return;
19289
+ }
19290
+ for (const issue3 of issues)
19291
+ printIssue(issue3);
19292
+ } catch (err) {
19293
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
19294
+ process.exit(1);
19295
+ }
19296
+ });
19297
+ cmd.command("pull").description("Show the next active issue for an agent").requiredOption("--agent_id <id>", "Agent ID").option("--json", "Output as JSON").action(async (opts, command) => {
19298
+ const { serverUrl, token, workspaceId } = resolveClientOpts3(command, opts.agent_id);
19299
+ const client = new APIClient(serverUrl, token, workspaceId);
19300
+ try {
19301
+ const issues = await client.getJSON(`/api/issues?agentId=${encodeURIComponent(opts.agent_id)}&terminal=false`);
19302
+ const issue3 = issues[0] ?? null;
19303
+ if (opts.json)
19304
+ return printJSON(issue3);
19305
+ if (!issue3) {
19306
+ console.log("No active issues.");
19307
+ return;
19308
+ }
19309
+ printIssueDetail(issue3);
19310
+ } catch (err) {
19311
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
19312
+ process.exit(1);
19313
+ }
19314
+ });
19315
+ cmd.command("show").description("Show issue details and conversation").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--issue_id <id>", "Issue ID").option("--json", "Output as JSON").action(async (opts, command) => {
19316
+ const { serverUrl, token, workspaceId } = resolveClientOpts3(command, opts.agent_id);
19317
+ const client = new APIClient(serverUrl, token, workspaceId);
19318
+ try {
19319
+ const res = await client.getJSON(`/api/issues/${opts.issue_id}?agentId=${encodeURIComponent(opts.agent_id)}`);
19320
+ if (res.issue.agent_id !== opts.agent_id) {
19321
+ console.error(`Error: issue ${res.issue.id} does not belong to agent ${opts.agent_id}`);
19322
+ process.exit(1);
19323
+ }
19324
+ if (opts.json)
19325
+ return printJSON(res);
19326
+ printIssueDetail(res.issue, res.messages);
19327
+ } catch (err) {
19328
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
19329
+ process.exit(1);
19330
+ }
19331
+ });
19332
+ cmd.command("update").description("Update issue status or text").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--issue_id <id>", "Issue ID").option("--status <status>", `New status (${VALID_STATUSES2.join(", ")})`).option("--title <title>", "New title").option("--description <text>", "New description").option("--body-file <path>", "Read description from a file").option("--json", "Output as JSON").action(async (opts, command) => {
19333
+ if (opts.status && !VALID_STATUSES2.includes(opts.status)) {
19334
+ console.error(`Error: invalid status "${opts.status}"`);
19335
+ process.exit(1);
19336
+ }
19337
+ const description = readBody({ body: opts.description, bodyFile: opts.bodyFile });
19338
+ const body = {};
19339
+ if (opts.status)
19340
+ body.status = opts.status;
19341
+ if (opts.title)
19342
+ body.title = opts.title;
19343
+ if (description)
19344
+ body.description = description;
19345
+ if (Object.keys(body).length === 0) {
19346
+ console.error("Error: pass at least one of --status, --title, --description, --body-file");
19347
+ process.exit(1);
19348
+ }
19349
+ const { serverUrl, token, workspaceId } = resolveClientOpts3(command, opts.agent_id);
19350
+ const client = new APIClient(serverUrl, token, workspaceId);
19351
+ try {
19352
+ const issue3 = await client.patchJSON(`/api/issues/${opts.issue_id}?agentId=${encodeURIComponent(opts.agent_id)}`, body);
19353
+ if (opts.json)
19354
+ return printJSON(issue3);
19355
+ printIssue(issue3);
19356
+ } catch (err) {
19357
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
19358
+ process.exit(1);
19359
+ }
19360
+ });
19361
+ cmd.command("comment").description("Append a comment to an issue").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--issue_id <id>", "Issue ID").option("--body <text>", "Comment text").option("--body-file <path>", "Read comment from a file").option("--json", "Output as JSON").action(async (opts, command) => {
19362
+ const content = readBody({ body: opts.body, bodyFile: opts.bodyFile }).trim();
19363
+ if (!content) {
19364
+ console.error("Error: pass --body or --body-file");
19365
+ process.exit(1);
19366
+ }
19367
+ const { serverUrl, token, workspaceId } = resolveClientOpts3(command, opts.agent_id);
19368
+ const client = new APIClient(serverUrl, token, workspaceId);
19369
+ try {
19370
+ const res = await client.postJSON(`/api/issues/${opts.issue_id}?agentId=${encodeURIComponent(opts.agent_id)}`, { content });
19371
+ if (opts.json)
19372
+ return printJSON(res);
19373
+ console.log(`Commented on ${opts.issue_id}`);
19374
+ } catch (err) {
19375
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
19376
+ process.exit(1);
19377
+ }
19378
+ });
19379
+ return cmd;
19380
+ }
19381
+
19382
+ // commands/version.ts
19383
+ import { Command as Command8 } from "commander";
19117
19384
  function versionCommand() {
19118
- const cmd = new Command7("version").description("Show CLI version").action(() => {
19385
+ const cmd = new Command8("version").description("Show CLI version").action(() => {
19119
19386
  console.log(`alook version ${getCurrentVersion()}`);
19120
19387
  });
19121
19388
  return cmd;
19122
19389
  }
19123
19390
 
19124
19391
  // commands/update.ts
19125
- import { Command as Command8 } from "commander";
19392
+ import { Command as Command9 } from "commander";
19126
19393
  function updateCommand() {
19127
- const cmd = new Command8("update").description("Update CLI to the latest version").action(async () => {
19394
+ const cmd = new Command9("update").description("Update CLI to the latest version").action(async () => {
19128
19395
  const current = getCurrentVersion();
19129
19396
  console.log(`Current version: ${current}`);
19130
19397
  const latest = await fetchLatestVersion();
@@ -19158,8 +19425,8 @@ ${result.output}`);
19158
19425
  }
19159
19426
 
19160
19427
  // commands/sync.ts
19161
- import { Command as Command9 } from "commander";
19162
- import { readFileSync as readFileSync8, statSync as statSync5 } from "fs";
19428
+ import { Command as Command10 } from "commander";
19429
+ import { readFileSync as readFileSync9, statSync as statSync5 } from "fs";
19163
19430
  import { basename as basename2 } from "path";
19164
19431
  var MIME_BY_EXT2 = {
19165
19432
  ".pdf": "application/pdf",
@@ -19187,7 +19454,7 @@ function guessContentType2(filename) {
19187
19454
  const ext = filename.slice(idx).toLowerCase();
19188
19455
  return MIME_BY_EXT2[ext] ?? "application/octet-stream";
19189
19456
  }
19190
- function resolveClientOpts3(command, agentId) {
19457
+ function resolveClientOpts4(command, agentId) {
19191
19458
  const parentOpts = command.parent?.parent?.opts() || {};
19192
19459
  const profile = parentOpts.profile;
19193
19460
  const cfg = loadCLIConfigForProfile(profile);
@@ -19201,16 +19468,16 @@ function resolveClientOpts3(command, agentId) {
19201
19468
  return { serverUrl, token: ws.token, workspaceId: ws.id };
19202
19469
  }
19203
19470
  function syncCommand() {
19204
- const cmd = new Command9("sync").description("File sync utilities");
19471
+ const cmd = new Command10("sync").description("File sync utilities");
19205
19472
  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) => {
19206
- const { serverUrl, token, workspaceId } = resolveClientOpts3(command, opts.agent_id);
19473
+ const { serverUrl, token, workspaceId } = resolveClientOpts4(command, opts.agent_id);
19207
19474
  const client = new APIClient(serverUrl, token, workspaceId);
19208
19475
  let bytes;
19209
19476
  let size;
19210
19477
  try {
19211
19478
  const stat2 = statSync5(opts.file);
19212
19479
  size = stat2.size;
19213
- bytes = readFileSync8(opts.file);
19480
+ bytes = readFileSync9(opts.file);
19214
19481
  } catch (err) {
19215
19482
  console.error(`Error: cannot read file "${opts.file}": ${err.message}`);
19216
19483
  process.exit(1);
@@ -19233,13 +19500,14 @@ function syncCommand() {
19233
19500
  }
19234
19501
 
19235
19502
  // src/index.ts
19236
- var program = new Command10;
19503
+ var program = new Command11;
19237
19504
  program.name("alook").description("Alook CLI").option("--server <url>", "Server URL").option("--profile <name>", "Profile name");
19238
19505
  program.addCommand(registerCommand());
19239
19506
  program.addCommand(statusCommand());
19240
19507
  program.addCommand(daemonCommand());
19241
19508
  program.addCommand(emailCommand());
19242
19509
  program.addCommand(calendarCommand());
19510
+ program.addCommand(issueCommand());
19243
19511
  program.addCommand(configCommand());
19244
19512
  program.addCommand(versionCommand());
19245
19513
  program.addCommand(updateCommand());
@@ -37,8 +37,29 @@ var TASK_TYPES = {
37
37
  USER_DM_MESSAGE: "user_dm_message",
38
38
  EMAIL_NOTIFICATION: "email_notification",
39
39
  CALENDAR_EVENT: "calendar_event",
40
+ ISSUE_EVENT: "issue_event",
40
41
  KILL_TASK: "kill_task"
41
42
  };
43
+ var IssueStatus = {
44
+ TODO: "todo",
45
+ IN_PROGRESS: "in_progress",
46
+ REVIEW: "review",
47
+ DONE: "done",
48
+ CLOSED: "closed",
49
+ CANCELED: "canceled",
50
+ FAILED: "failed"
51
+ };
52
+ var ACTIVE_ISSUE_STATUSES = [
53
+ IssueStatus.TODO,
54
+ IssueStatus.IN_PROGRESS,
55
+ IssueStatus.REVIEW
56
+ ];
57
+ var TERMINAL_ISSUE_STATUSES = [
58
+ IssueStatus.DONE,
59
+ IssueStatus.CLOSED,
60
+ IssueStatus.CANCELED,
61
+ IssueStatus.FAILED
62
+ ];
42
63
  var POLL_INTERVAL_MS = Number(process.env.POLL_INTERVAL_MS) || 3000;
43
64
  var OFFLINE_THRESHOLD_MS = Number(process.env.OFFLINE_THRESHOLD_MS) || 9000;
44
65
  var EVENT_POLL_INTERVAL_MS = Number(process.env.EVENT_POLL_INTERVAL_MS) || 2000;
@@ -14538,6 +14559,42 @@ var CalendarEventApiSchema = exports_external.object({
14538
14559
  created_at: exports_external.string(),
14539
14560
  updated_at: exports_external.string()
14540
14561
  });
14562
+ var IssueStatusSchema = exports_external.enum([
14563
+ IssueStatus.TODO,
14564
+ IssueStatus.IN_PROGRESS,
14565
+ IssueStatus.REVIEW,
14566
+ IssueStatus.DONE,
14567
+ IssueStatus.CLOSED,
14568
+ IssueStatus.CANCELED,
14569
+ IssueStatus.FAILED
14570
+ ]);
14571
+ var CreateIssueRequestSchema = exports_external.object({
14572
+ agent_id: exports_external.string().min(1, "agent_id is required"),
14573
+ title: exports_external.string().min(1, "title is required").max(200),
14574
+ description: exports_external.string().max(20000).optional().default("")
14575
+ });
14576
+ var UpdateIssueRequestSchema = exports_external.object({
14577
+ title: exports_external.string().min(1).max(200).optional(),
14578
+ description: exports_external.string().max(20000).optional(),
14579
+ status: IssueStatusSchema.optional()
14580
+ }).refine((v) => v.title !== undefined || v.description !== undefined || v.status !== undefined, { message: "at least one field is required" });
14581
+ var CreateIssueCommentRequestSchema = exports_external.object({
14582
+ content: exports_external.string().min(1, "content is required").max(20000)
14583
+ });
14584
+ var IssueApiSchema = exports_external.object({
14585
+ id: exports_external.string(),
14586
+ workspace_id: exports_external.string(),
14587
+ agent_id: exports_external.string(),
14588
+ creator_user_id: exports_external.string(),
14589
+ conversation_id: exports_external.string(),
14590
+ latest_task_id: exports_external.string().nullable(),
14591
+ title: exports_external.string(),
14592
+ description: exports_external.string(),
14593
+ status: IssueStatusSchema,
14594
+ created_at: exports_external.string(),
14595
+ updated_at: exports_external.string(),
14596
+ completed_at: exports_external.string().nullable()
14597
+ });
14541
14598
  var CreateAgentLinkRequestSchema = exports_external.object({
14542
14599
  source_agent_id: exports_external.string().min(1, "source_agent_id is required"),
14543
14600
  target_agent_id: exports_external.string().min(1, "target_agent_id is required"),
@@ -16320,6 +16377,30 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
16320
16377
  foreignColumns: [agent.id, agent.workspaceId]
16321
16378
  }).onDelete("cascade")
16322
16379
  ]);
16380
+ var issue2 = sqliteTable("issue", {
16381
+ id: text("id").primaryKey().$defaultFn(() => "iss_" + nanoid3()),
16382
+ workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
16383
+ agentId: text("agent_id").notNull(),
16384
+ creatorUserId: text("creator_user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
16385
+ conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
16386
+ latestTaskId: text("latest_task_id").references(() => agentTaskQueue.id, {
16387
+ onDelete: "set null"
16388
+ }),
16389
+ title: text("title").notNull(),
16390
+ description: text("description").notNull().default(""),
16391
+ status: text("status").notNull().default("todo"),
16392
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
16393
+ updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString()),
16394
+ completedAt: text("completed_at")
16395
+ }, (t) => [
16396
+ index("idx_issue_workspace_status_agent").on(t.workspaceId, t.status, t.agentId),
16397
+ index("idx_issue_workspace_updated").on(t.workspaceId, t.updatedAt),
16398
+ unique("issue_conversation_unique").on(t.conversationId),
16399
+ foreignKey({
16400
+ columns: [t.agentId, t.workspaceId],
16401
+ foreignColumns: [agent.id, agent.workspaceId]
16402
+ }).onDelete("cascade")
16403
+ ]);
16323
16404
  var taskMessage = sqliteTable("task_message", {
16324
16405
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
16325
16406
  taskId: text("task_id").notNull().references(() => agentTaskQueue.id, { onDelete: "cascade" }),
@@ -17265,7 +17346,8 @@ class CodexBackend {
17265
17346
  } else {
17266
17347
  const threadParams = {
17267
17348
  cwd: options.cwd,
17268
- sandbox: "danger-full-access",
17349
+ sandboxPolicy: { type: "dangerFullAccess" },
17350
+ approvalPolicy: "never",
17269
17351
  persistExtendedHistory: true,
17270
17352
  experimentalRawEvents: false
17271
17353
  };
@@ -17278,7 +17360,9 @@ class CodexBackend {
17278
17360
  resolveSessionId(sessionId);
17279
17361
  await sendRpc("turn/start", {
17280
17362
  threadId: sessionId,
17281
- input: [{ type: "text", text: prompt }]
17363
+ input: [{ type: "text", text: prompt }],
17364
+ sandboxPolicy: { type: "dangerFullAccess" },
17365
+ approvalPolicy: "never"
17282
17366
  });
17283
17367
  } catch (err) {
17284
17368
  const errMsg = err instanceof Error ? err.message : "handshake failed";
@@ -17698,8 +17782,9 @@ ${task.agent.instructions}
17698
17782
  ## Your Colleagues
17699
17783
  Below are your direct colleagues. You can reach them via email.
17700
17784
 
17701
- **Important:**
17702
- - When communicating with a colleague on the previous topics, always reply to the existing email thread instead of composing a new email. This keeps the full conversation context visible to your colleague so they can pick up where you left off. For a new topic, you can just start a new email.
17785
+ **Important:**
17786
+ - When communicating with a colleague on the **same topic** as an existing email thread, reply to that thread (use --in-reply-to) to keep context together.
17787
+ - **When starting a NEW topic or task that is unrelated to any previous email thread, you MUST compose a brand new email (do NOT use --in-reply-to). Never hijack an unrelated thread just because you recently emailed that colleague.** Judge by topic/task relevance, not by recency of communication.
17703
17788
  `;
17704
17789
  for (let i = 0;i < task.agent.colleagues.length; i++) {
17705
17790
  const c = task.agent.colleagues[i];
@@ -18234,12 +18319,17 @@ function clearKillIntent(baseDir, taskId) {
18234
18319
  }
18235
18320
 
18236
18321
  // daemon/prompt.ts
18322
+ var DM_RESPONSE_NOTICE = "IMPORTANT: Only your final text response is visible to the user." + " Tool calls, intermediate reasoning, and mid-process outputs are NOT displayed." + " Put all key information, answers, and conclusions in your final response — that is the only thing the user will read.";
18237
18323
  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.";
18324
+ var ISSUE_NOTICE = "This task was triggered by an assigned issue. Use `alook issue show`, `alook issue update`, and `alook issue comment` to inspect and update the issue as you work." + " Move the issue to in_progress when you begin, then to review, done, closed, canceled, or failed when appropriate.";
18238
18325
  function buildDmNotice(name, email3) {
18239
18326
  return `This task was triggered by an incoming email on a conversation with ${name} (${email3}).` + ` ${name} is present in this session — reply to them directly.` + ` If you need to communicate with anyone else, use the email sending tool.`;
18240
18327
  }
18241
18328
  function buildPrompt(task, attachments) {
18242
18329
  const obj = { type: task.type, instruction: task.prompt };
18330
+ if (task.type === "user_dm_message") {
18331
+ obj.notice = DM_RESPONSE_NOTICE;
18332
+ }
18243
18333
  if (task.type === "email_notification") {
18244
18334
  const ctx = task.context;
18245
18335
  const dmUser = ctx?.dmUser;
@@ -18249,6 +18339,9 @@ function buildPrompt(task, attachments) {
18249
18339
  obj.notice = EMAIL_NOTICE;
18250
18340
  }
18251
18341
  }
18342
+ if (task.type === "issue_event") {
18343
+ obj.notice = ISSUE_NOTICE;
18344
+ }
18252
18345
  if (task.sender) {
18253
18346
  obj.sender = {
18254
18347
  name: task.sender.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "description": "Alook CLI — Enable Your Person Colleague",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",