@alook/cli 0.0.23 → 0.0.24

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
@@ -220,7 +220,6 @@ Usage: ${cmdPrefix()} register --token <token>`);
220
220
  console.error("Error: no workspaces found for this user");
221
221
  process.exit(1);
222
222
  }
223
- const ws = workspaces[0];
224
223
  console.log("Scanning for AI runtimes...");
225
224
  const runtimes = detectRuntimes();
226
225
  if (runtimes.length === 0) {
@@ -247,6 +246,7 @@ Usage: ${cmdPrefix()} register --token <token>`);
247
246
  console.error(`Error: failed to activate: ${err instanceof Error ? err.message : err}`);
248
247
  process.exit(1);
249
248
  }
249
+ const ws = workspaces.find((w) => w.id === activateResp.workspace_id) || workspaces[0];
250
250
  const wsClient = new APIClient(serverUrl, token, ws.id);
251
251
  let agentIds = [];
252
252
  try {
@@ -325,6 +325,18 @@ var TASK_TYPES = {
325
325
  var POLL_INTERVAL_MS = Number(process.env.POLL_INTERVAL_MS) || 3000;
326
326
  var OFFLINE_THRESHOLD_MS = Number(process.env.OFFLINE_THRESHOLD_MS) || 9000;
327
327
  var EVENT_POLL_INTERVAL_MS = Number(process.env.EVENT_POLL_INTERVAL_MS) || 2000;
328
+ var MeetingStatus = {
329
+ PENDING: "pending",
330
+ SCHEDULED: "scheduled",
331
+ JOINING: "joining",
332
+ RECORDING: "recording",
333
+ COMPLETED: "completed",
334
+ FAILED: "failed"
335
+ };
336
+ var TERMINAL_MEETING_STATUSES = [
337
+ MeetingStatus.COMPLETED,
338
+ MeetingStatus.FAILED
339
+ ];
328
340
  var DEV_WEB_URL = process.env.ALOOK_SERVER_URL || "http://localhost:3000";
329
341
  var DEV_WS_DO_URL = process.env.DEV_WS_DO_URL || "http://localhost:8789";
330
342
  var DEV_EMAIL_WORKER_URL = process.env.DEV_EMAIL_WORKER_URL || "http://localhost:8787";
@@ -13931,11 +13943,18 @@ var PollRequestSchema = exports_external.object({
13931
13943
  max_tasks: exports_external.number().int().min(1).default(1),
13932
13944
  cli_version: exports_external.string().optional()
13933
13945
  });
13946
+ var FileRequestItemSchema = exports_external.object({
13947
+ id: exports_external.string(),
13948
+ agent_id: exports_external.string(),
13949
+ request_type: exports_external.enum(["tree", "read"]),
13950
+ path: exports_external.string()
13951
+ });
13934
13952
  var PollResponseSchema = exports_external.object({
13935
13953
  tasks: exports_external.array(TaskApiSchema),
13936
13954
  evicted: exports_external.boolean().optional(),
13937
13955
  pending_update: exports_external.object({ version: exports_external.string() }).optional(),
13938
- pending_rescan: exports_external.boolean().optional()
13956
+ pending_rescan: exports_external.boolean().optional(),
13957
+ file_requests: exports_external.array(FileRequestItemSchema).optional()
13939
13958
  });
13940
13959
  var RegisterResponseSchema = exports_external.object({
13941
13960
  runtimes: exports_external.array(exports_external.object({ id: exports_external.string() }))
@@ -14060,7 +14079,8 @@ var UpdateAgentRequestSchema = exports_external.object({
14060
14079
  visibility: exports_external.enum(["public", "private"]).optional()
14061
14080
  }).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" });
14062
14081
  var CreateConversationRequestSchema = exports_external.object({
14063
- agent_id: exports_external.string().min(1, "agent_id is required")
14082
+ agent_id: exports_external.string().min(1, "agent_id is required"),
14083
+ channel: exports_external.string().optional()
14064
14084
  });
14065
14085
  var CreateMessageRequestSchema = exports_external.object({
14066
14086
  content: exports_external.string().min(1, "content is required")
@@ -14088,6 +14108,13 @@ var SendEmailRequestSchema = exports_external.object({
14088
14108
  var UpdateEmailStatusRequestSchema = exports_external.object({
14089
14109
  status: exports_external.enum(["unread", "read", "archived"])
14090
14110
  });
14111
+ var MeetingInfoSchema = exports_external.object({
14112
+ title: exports_external.string(),
14113
+ meetingUrl: exports_external.string(),
14114
+ startTime: exports_external.string().nullable(),
14115
+ endTime: exports_external.string().nullable(),
14116
+ attendees: exports_external.array(exports_external.object({ name: exports_external.string(), email: exports_external.string() }))
14117
+ });
14091
14118
  var EmailNotifyRequestSchema = exports_external.object({
14092
14119
  agentId: exports_external.string().min(1),
14093
14120
  workspaceId: exports_external.string().min(1),
@@ -14099,7 +14126,8 @@ var EmailNotifyRequestSchema = exports_external.object({
14099
14126
  forwarded: exports_external.boolean().optional().default(false),
14100
14127
  messageId: exports_external.string().optional().default(""),
14101
14128
  inReplyTo: exports_external.string().optional().default(""),
14102
- references: exports_external.string().optional().default("")
14129
+ references: exports_external.string().optional().default(""),
14130
+ meetingInfo: MeetingInfoSchema.nullable().optional()
14103
14131
  });
14104
14132
  var CreateEmailAccountSchema = exports_external.object({
14105
14133
  emailAddress: exports_external.string().email("valid email required"),
@@ -14160,6 +14188,25 @@ var DeleteWorkspaceRequestSchema = exports_external.object({
14160
14188
  var GrantAgentAccessRequestSchema = exports_external.object({
14161
14189
  user_id: exports_external.string().min(1, "user_id is required")
14162
14190
  });
14191
+ var WorkspaceFileBrowseRequestSchema = exports_external.object({
14192
+ request_type: exports_external.enum(["tree", "read"]),
14193
+ path: exports_external.string().default(".")
14194
+ });
14195
+ var WorkspaceFileEntrySchema = exports_external.object({
14196
+ name: exports_external.string(),
14197
+ path: exports_external.string(),
14198
+ isDirectory: exports_external.boolean(),
14199
+ size: exports_external.number(),
14200
+ modifiedAt: exports_external.string()
14201
+ });
14202
+ var WorkspaceFileReportSchema = exports_external.object({
14203
+ request_id: exports_external.string().min(1),
14204
+ entries: exports_external.array(WorkspaceFileEntrySchema).optional(),
14205
+ content: exports_external.string().nullable().optional(),
14206
+ isBinary: exports_external.boolean().optional(),
14207
+ error: exports_external.string().optional(),
14208
+ path: exports_external.string()
14209
+ });
14163
14210
  // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260426.1_@opentelemetry+api@1.9.1_bun-types@1.3.13_kysely@0.28.16/node_modules/drizzle-orm/entity.js
14164
14211
  var entityKind = Symbol.for("drizzle:entityKind");
14165
14212
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -15690,6 +15737,15 @@ var agentWhitelist = sqliteTable("agent_whitelist", {
15690
15737
  foreignColumns: [agent.id, agent.workspaceId]
15691
15738
  }).onDelete("cascade")
15692
15739
  ]);
15740
+ var channel = sqliteTable("channel", {
15741
+ id: text("id").primaryKey().$defaultFn(() => "ch_" + nanoid3()),
15742
+ workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
15743
+ name: text("name").notNull(),
15744
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15745
+ }, (t) => [
15746
+ unique("channel_workspace_name").on(t.workspaceId, t.name),
15747
+ index("idx_channel_workspace").on(t.workspaceId)
15748
+ ]);
15693
15749
  var conversation = sqliteTable("conversation", {
15694
15750
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
15695
15751
  workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
@@ -15697,6 +15753,7 @@ var conversation = sqliteTable("conversation", {
15697
15753
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
15698
15754
  title: text("title").notNull().default(""),
15699
15755
  type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
15756
+ channel: text("channel").notNull().default("default"),
15700
15757
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15701
15758
  }, (t) => [
15702
15759
  foreignKey({
@@ -15850,6 +15907,33 @@ var agentEmailAccount = sqliteTable("agent_email_account", {
15850
15907
  foreignColumns: [agent.id, agent.workspaceId]
15851
15908
  }).onDelete("cascade")
15852
15909
  ]);
15910
+ var meetingSession = sqliteTable("meeting_session", {
15911
+ id: text("id").primaryKey().$defaultFn(() => "ms_" + nanoid3()),
15912
+ agentId: text("agent_id").notNull(),
15913
+ workspaceId: text("workspace_id").notNull(),
15914
+ title: text("title").notNull().default(""),
15915
+ meetingUrl: text("meeting_url").notNull(),
15916
+ status: text("status").notNull().default("scheduled"),
15917
+ fromEmail: text("from_email"),
15918
+ isWhitelisted: integer2("is_whitelisted", { mode: "boolean" }).notNull().default(true),
15919
+ participants: text("participants", { mode: "json" }).$type().notNull().default([]),
15920
+ scheduledAt: text("scheduled_at"),
15921
+ startedAt: text("started_at"),
15922
+ completedAt: text("completed_at"),
15923
+ transcriptR2Key: text("transcript_r2_key"),
15924
+ summary: text("summary"),
15925
+ error: text("error"),
15926
+ workerSessionId: text("worker_session_id"),
15927
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15928
+ updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15929
+ }, (t) => [
15930
+ index("idx_meeting_session_agent_ws").on(t.agentId, t.workspaceId),
15931
+ index("idx_meeting_session_status").on(t.status),
15932
+ foreignKey({
15933
+ columns: [t.agentId, t.workspaceId],
15934
+ foreignColumns: [agent.id, agent.workspaceId]
15935
+ }).onDelete("cascade")
15936
+ ]);
15853
15937
  var machineToken = sqliteTable("machine_token", {
15854
15938
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
15855
15939
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -15860,6 +15944,17 @@ var machineToken = sqliteTable("machine_token", {
15860
15944
  lastUsedAt: text("last_used_at"),
15861
15945
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15862
15946
  }, (t) => [index("idx_machine_token").on(t.token)]);
15947
+ var workspaceFileRequest = sqliteTable("workspace_file_request", {
15948
+ id: text("id").primaryKey().$defaultFn(() => "wfr_" + nanoid3()),
15949
+ workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
15950
+ agentId: text("agent_id").notNull(),
15951
+ requestType: text("request_type").notNull(),
15952
+ path: text("path").notNull().default("."),
15953
+ status: text("status").notNull().default("pending"),
15954
+ result: text("result"),
15955
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15956
+ updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15957
+ }, (t) => [index("idx_wfr_workspace_status").on(t.workspaceId, t.status)]);
15863
15958
  // ../shared/src/db/queries/task.ts
15864
15959
  var DEFAULT_STALE_SECONDS = Number(process.env.ALOOK_STALE_DISPATCH_TIMEOUT_S) || 20;
15865
15960
  var DEFAULT_STALE_RUNNING_SECONDS = Number(process.env.ALOOK_STALE_RUNNING_TIMEOUT_S) || 3600;
@@ -15951,7 +16046,8 @@ class DaemonClient {
15951
16046
  tasks: resp.tasks,
15952
16047
  evicted: resp.evicted ?? false,
15953
16048
  pending_update: resp.pending_update,
15954
- pending_rescan: resp.pending_rescan
16049
+ pending_rescan: resp.pending_rescan,
16050
+ file_requests: resp.file_requests
15955
16051
  };
15956
16052
  }
15957
16053
  startTask(token, taskId) {
@@ -15998,6 +16094,18 @@ class DaemonClient {
15998
16094
  reportMessages(token, taskId, messages) {
15999
16095
  return this.request("POST", `/api/daemon/tasks/${taskId}/messages`, token, { messages });
16000
16096
  }
16097
+ reportFileData(token, body) {
16098
+ return this.request("POST", "/api/daemon/workspace/report", token, body);
16099
+ }
16100
+ async claimMeetings(token, daemonId) {
16101
+ const raw = await this.request("POST", "/api/daemon/meetings/claim", token, { daemon_id: daemonId });
16102
+ return raw.map((m) => ({
16103
+ id: m.id,
16104
+ meetingUrl: m.meeting_url,
16105
+ participants: m.participants,
16106
+ workspaceId: m.workspace_id
16107
+ }));
16108
+ }
16001
16109
  }
16002
16110
 
16003
16111
  // daemon/config.ts
@@ -16562,14 +16670,108 @@ function releaseSteeringLock(baseDir, contextKey) {
16562
16670
  releaseLock(lockPath);
16563
16671
  }
16564
16672
 
16673
+ // daemon/workspace-files.ts
16674
+ import { readdir, stat, readFile } from "fs/promises";
16675
+ import { join as join6, resolve, extname, relative } from "path";
16676
+ var SKIP_DIRS = new Set([".git", "node_modules", ".next", ".wrangler", "__pycache__", ".venv"]);
16677
+ var TEXT_EXTENSIONS = new Set([
16678
+ ".md",
16679
+ ".txt",
16680
+ ".json",
16681
+ ".js",
16682
+ ".ts",
16683
+ ".tsx",
16684
+ ".jsx",
16685
+ ".py",
16686
+ ".rb",
16687
+ ".go",
16688
+ ".rs",
16689
+ ".toml",
16690
+ ".yaml",
16691
+ ".yml",
16692
+ ".html",
16693
+ ".css",
16694
+ ".scss",
16695
+ ".sh",
16696
+ ".bash",
16697
+ ".zsh",
16698
+ ".env",
16699
+ ".cfg",
16700
+ ".ini",
16701
+ ".xml",
16702
+ ".svg",
16703
+ ".sql",
16704
+ ".jsonl",
16705
+ ".log",
16706
+ ".csv"
16707
+ ]);
16708
+ var MAX_FILE_SIZE = 1048576;
16709
+ async function readDirectoryTree(dirPath, basePath) {
16710
+ let entries;
16711
+ try {
16712
+ entries = await readdir(dirPath, { withFileTypes: true });
16713
+ } catch {
16714
+ return [];
16715
+ }
16716
+ entries.sort((a, b) => {
16717
+ if (a.isDirectory() && !b.isDirectory())
16718
+ return -1;
16719
+ if (!a.isDirectory() && b.isDirectory())
16720
+ return 1;
16721
+ return a.name.localeCompare(b.name);
16722
+ });
16723
+ const results = [];
16724
+ for (const entry of entries) {
16725
+ if (entry.name.startsWith(".") && entry.name !== ".context_timeline")
16726
+ continue;
16727
+ if (SKIP_DIRS.has(entry.name))
16728
+ continue;
16729
+ const fullPath = join6(dirPath, entry.name);
16730
+ let info;
16731
+ try {
16732
+ info = await stat(fullPath);
16733
+ } catch {
16734
+ continue;
16735
+ }
16736
+ results.push({
16737
+ name: entry.name,
16738
+ path: relative(basePath, fullPath),
16739
+ isDirectory: entry.isDirectory(),
16740
+ size: entry.isDirectory() ? 0 : info.size,
16741
+ modifiedAt: info.mtime.toISOString()
16742
+ });
16743
+ }
16744
+ return results;
16745
+ }
16746
+ async function readFileContent(filePath) {
16747
+ const info = await stat(filePath);
16748
+ if (info.isDirectory())
16749
+ throw new Error("Cannot read a directory");
16750
+ if (info.size > MAX_FILE_SIZE)
16751
+ throw new Error("File too large (>1MB)");
16752
+ const ext = extname(filePath).toLowerCase();
16753
+ if (ext !== "" && !TEXT_EXTENSIONS.has(ext)) {
16754
+ return { content: null, isBinary: true };
16755
+ }
16756
+ const content = await readFile(filePath, "utf-8");
16757
+ return { content, isBinary: false };
16758
+ }
16759
+ function validatePath(agentWorkdir, requestedPath) {
16760
+ const resolved = resolve(agentWorkdir, requestedPath);
16761
+ if (resolved !== agentWorkdir && !resolved.startsWith(agentWorkdir + "/"))
16762
+ return null;
16763
+ return resolved;
16764
+ }
16765
+
16565
16766
  // daemon/daemon.ts
16566
16767
  import { existsSync, mkdirSync as mkdirSync5, openSync, closeSync, readdirSync as readdirSync2, statSync as statSync3, unlinkSync as unlinkSync4 } from "fs";
16567
- import { readdir, readFile, unlink, stat as fsStat } from "fs/promises";
16768
+ import { readdir as readdir2, readFile as readFile2, unlink, stat as fsStat } from "fs/promises";
16568
16769
  import { execSync as execSync3, spawn as spawn2 } from "child_process";
16569
16770
  import { fileURLToPath as fileURLToPath2 } from "url";
16570
- import { dirname as dirname3, join as join6 } from "path";
16771
+ import { dirname as dirname3, join as join7 } from "path";
16571
16772
  var _dir = dirname3(fileURLToPath2(import.meta.url));
16572
- var sessionRunnerPath = existsSync(join6(_dir, "session-runner.js")) ? join6(_dir, "session-runner.js") : join6(_dir, "session-runner.ts");
16773
+ var sessionRunnerPath = existsSync(join7(_dir, "session-runner.js")) ? join7(_dir, "session-runner.js") : join7(_dir, "session-runner.ts");
16774
+ var meetingRunnerPath = existsSync(join7(_dir, "meeting-runner.js")) ? join7(_dir, "meeting-runner.js") : join7(_dir, "meeting-runner.ts");
16573
16775
  function isCommandAvailable2(cmd) {
16574
16776
  try {
16575
16777
  const check2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
@@ -16591,7 +16793,7 @@ function pruneSessionRunnerLogs() {
16591
16793
  if (entries.length <= MAX_SESSION_RUNNER_LOGS)
16592
16794
  return;
16593
16795
  const withMtime = entries.map((name) => {
16594
- const full = join6(logDir, name);
16796
+ const full = join7(logDir, name);
16595
16797
  try {
16596
16798
  return { name, mtime: statSync3(full).mtimeMs };
16597
16799
  } catch {
@@ -16601,7 +16803,7 @@ function pruneSessionRunnerLogs() {
16601
16803
  withMtime.sort((a, b) => b.mtime - a.mtime);
16602
16804
  for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
16603
16805
  try {
16604
- unlinkSync4(join6(logDir, entry.name));
16806
+ unlinkSync4(join7(logDir, entry.name));
16605
16807
  } catch {}
16606
16808
  }
16607
16809
  }
@@ -16642,10 +16844,10 @@ function isValidMarker(data) {
16642
16844
  var MARKER_STALE_MS = 24 * 60 * 60 * 1000;
16643
16845
  var TMP_STALE_MS = 60 * 60 * 1000;
16644
16846
  async function reconcilePendingCompletions(workspacesRoot) {
16645
- const dir = join6(workspacesRoot, ".pending_completions");
16847
+ const dir = join7(workspacesRoot, ".pending_completions");
16646
16848
  let entries;
16647
16849
  try {
16648
- entries = await readdir(dir);
16850
+ entries = await readdir2(dir);
16649
16851
  } catch {
16650
16852
  return;
16651
16853
  }
@@ -16653,19 +16855,19 @@ async function reconcilePendingCompletions(workspacesRoot) {
16653
16855
  if (!name.endsWith(".tmp"))
16654
16856
  continue;
16655
16857
  try {
16656
- const s = await fsStat(join6(dir, name));
16858
+ const s = await fsStat(join7(dir, name));
16657
16859
  if (Date.now() - s.mtimeMs > TMP_STALE_MS) {
16658
- await unlink(join6(dir, name));
16860
+ await unlink(join7(dir, name));
16659
16861
  }
16660
16862
  } catch {}
16661
16863
  }
16662
16864
  const jsonFiles = entries.filter((f) => f.endsWith(".json"));
16663
16865
  for (const name of jsonFiles) {
16664
- const filePath = join6(dir, name);
16866
+ const filePath = join7(dir, name);
16665
16867
  try {
16666
16868
  let raw;
16667
16869
  try {
16668
- raw = await readFile(filePath, "utf-8");
16870
+ raw = await readFile2(filePath, "utf-8");
16669
16871
  } catch {
16670
16872
  continue;
16671
16873
  }
@@ -16872,7 +17074,7 @@ async function startDaemon(profile, serverUrl) {
16872
17074
  await new Promise((r) => setTimeout(r, staggerMs));
16873
17075
  }
16874
17076
  try {
16875
- const { tasks: apiTasks, evicted, pending_update, pending_rescan } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
17077
+ const { tasks: apiTasks, evicted, pending_update, pending_rescan, file_requests } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
16876
17078
  if (evicted) {
16877
17079
  evictedIds.push(ws.workspaceId);
16878
17080
  continue;
@@ -16894,6 +17096,11 @@ async function startDaemon(profile, serverUrl) {
16894
17096
  activeTasks.delete(task.id);
16895
17097
  });
16896
17098
  }
17099
+ if (file_requests) {
17100
+ for (const req of file_requests) {
17101
+ handleFileRequest(client, config2, ws.workspaceId, req, ws.token).catch((e) => log.debug("File request error", e));
17102
+ }
17103
+ }
16897
17104
  } catch (e) {
16898
17105
  if (e instanceof Error && e.message.startsWith("HTTP 401")) {
16899
17106
  log.warn(`Workspace ${ws.workspaceId} poll returned 401 — will retry next cycle`);
@@ -16905,6 +17112,23 @@ async function startDaemon(profile, serverUrl) {
16905
17112
  for (const id of evictedIds) {
16906
17113
  evictWorkspace(id);
16907
17114
  }
17115
+ for (const ws of workspaceStates) {
17116
+ try {
17117
+ const meetings = await client.claimMeetings(ws.token, config2.daemonId);
17118
+ for (const m of meetings) {
17119
+ spawnMeetingRunner({
17120
+ meetingId: m.id,
17121
+ meetingUrl: m.meetingUrl,
17122
+ participants: m.participants,
17123
+ workspaceId: m.workspaceId,
17124
+ callbackUrl: config2.serverURL,
17125
+ authToken: ws.token
17126
+ });
17127
+ }
17128
+ } catch (e) {
17129
+ log.debug("Meeting claim error", e);
17130
+ }
17131
+ }
16908
17132
  try {
16909
17133
  await reconcilePendingCompletions(config2.workspacesRoot);
16910
17134
  } catch (e) {
@@ -16972,7 +17196,7 @@ async function startDaemon(profile, serverUrl) {
16972
17196
  function spawnSessionRunner(input) {
16973
17197
  const logDir = sessionRunnerLogDir();
16974
17198
  mkdirSync5(logDir, { recursive: true });
16975
- const logFilePath = join6(logDir, `${input.task.id}.log`);
17199
+ const logFilePath = join7(logDir, `${input.task.id}.log`);
16976
17200
  input.logFilePath = logFilePath;
16977
17201
  const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
16978
17202
  let fd;
@@ -16990,6 +17214,50 @@ function spawnSessionRunner(input) {
16990
17214
  closeSync(fd);
16991
17215
  return child;
16992
17216
  }
17217
+ function spawnMeetingRunner(input) {
17218
+ const logDir = sessionRunnerLogDir();
17219
+ mkdirSync5(logDir, { recursive: true });
17220
+ const logFilePath = join7(logDir, `meeting-${input.meetingId}.log`);
17221
+ const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
17222
+ let fd;
17223
+ try {
17224
+ fd = openSync(logFilePath, "a");
17225
+ } catch (e) {
17226
+ log.error(`Failed to open meeting log file ${logFilePath}`, e);
17227
+ }
17228
+ const child = spawn2(process.execPath, [meetingRunnerPath, encoded], {
17229
+ detached: true,
17230
+ stdio: fd != null ? ["ignore", fd, fd] : ["ignore", "ignore", "ignore"]
17231
+ });
17232
+ child.unref();
17233
+ if (fd != null)
17234
+ closeSync(fd);
17235
+ log.info(`Spawned meeting runner for ${input.meetingId} (pid=${child.pid})`);
17236
+ return child;
17237
+ }
17238
+ async function handleFileRequest(client, config2, workspaceId, req, token) {
17239
+ const agentWorkdir = join7(config2.workspacesRoot, workspaceId, req.agent_id, "workdir");
17240
+ const resolved = validatePath(agentWorkdir, req.path);
17241
+ if (!resolved) {
17242
+ await client.reportFileData(token, { request_id: req.id, error: "invalid path", path: req.path });
17243
+ return;
17244
+ }
17245
+ try {
17246
+ if (req.request_type === "tree") {
17247
+ const entries = await readDirectoryTree(resolved, agentWorkdir);
17248
+ await client.reportFileData(token, { request_id: req.id, entries, path: req.path });
17249
+ } else {
17250
+ const { content, isBinary } = await readFileContent(resolved);
17251
+ await client.reportFileData(token, { request_id: req.id, content, isBinary, path: req.path });
17252
+ }
17253
+ } catch (e) {
17254
+ await client.reportFileData(token, {
17255
+ request_id: req.id,
17256
+ error: e instanceof Error ? e.message : String(e),
17257
+ path: req.path
17258
+ });
17259
+ }
17260
+ }
16993
17261
  async function handleTask(client, config2, runtimeIndex, task, token, activeTasks) {
16994
17262
  log.info(`Task ${task.id} claimed agent=${task.agentId}`);
16995
17263
  if (task.type === TASK_TYPES.KILL_TASK) {
@@ -16999,8 +17267,8 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
16999
17267
  activeTasks.delete(task.id);
17000
17268
  return;
17001
17269
  }
17002
- const agentBaseDir = join6(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
17003
- const timelineDir = join6(agentBaseDir, ".context_timeline");
17270
+ const agentBaseDir = join7(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
17271
+ const timelineDir = join7(agentBaseDir, ".context_timeline");
17004
17272
  const pid = findRunningPidByTaskId(timelineDir, targetTaskId);
17005
17273
  if (pid != null) {
17006
17274
  writeKillIntent(agentBaseDir, {
@@ -17042,9 +17310,9 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
17042
17310
  }
17043
17311
  const provider = runtimeData.provider;
17044
17312
  if (task.contextKey) {
17045
- const agentBaseDir = join6(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
17313
+ const agentBaseDir = join7(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
17046
17314
  cleanupStaleIntents(agentBaseDir);
17047
- const timelineDir = join6(agentBaseDir, ".context_timeline");
17315
+ const timelineDir = join7(agentBaseDir, ".context_timeline");
17048
17316
  const lockAcquired = acquireSteeringLock(agentBaseDir, task.contextKey);
17049
17317
  if (!lockAcquired) {
17050
17318
  log.warn(`Steering lock contention for context_key=${task.contextKey}, proceeding without steering`);
@@ -17269,7 +17537,7 @@ function configCommand() {
17269
17537
  // commands/email.ts
17270
17538
  import { Command as Command5 } from "commander";
17271
17539
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
17272
- import { basename, join as join7 } from "path";
17540
+ import { basename, join as join8 } from "path";
17273
17541
  import PostalMime from "postal-mime";
17274
17542
  var VALID_STATUSES = ["unread", "read", "archived"];
17275
17543
  var EMAIL_BASE = "/tmp/alook-emails";
@@ -17311,11 +17579,26 @@ function resolveClientOpts(command, opts) {
17311
17579
  let ws;
17312
17580
  if (opts.workspace) {
17313
17581
  ws = workspaces.find((w) => w.id === opts.workspace);
17582
+ if (!ws) {
17583
+ console.error(`Error: workspace ${opts.workspace} not found in config.`);
17584
+ process.exit(1);
17585
+ }
17314
17586
  } else if (opts.agentId) {
17315
17587
  ws = workspaces.find((w) => w.agent_ids?.includes(opts.agentId));
17316
- }
17317
- if (!ws)
17588
+ if (!ws) {
17589
+ if (workspaces.length === 1) {
17590
+ ws = workspaces[0];
17591
+ } else {
17592
+ console.error(`Error: agent ${opts.agentId} not found in any registered workspace. Use --workspace to specify.`);
17593
+ process.exit(1);
17594
+ }
17595
+ }
17596
+ } else if (workspaces.length === 1) {
17318
17597
  ws = workspaces[0];
17598
+ } else {
17599
+ console.error(`Error: multiple workspaces registered. Use --workspace to specify which one.`);
17600
+ process.exit(1);
17601
+ }
17319
17602
  const token = ws?.token;
17320
17603
  if (!token) {
17321
17604
  console.error(`Error: not registered. Run '${cmdPrefix()} register --token <token>' first.`);
@@ -17332,7 +17615,7 @@ function emailCommand() {
17332
17615
  console.error(`Error: invalid status "${opts.status}", must be one of: ${VALID_STATUSES.join(", ")}`);
17333
17616
  process.exit(1);
17334
17617
  }
17335
- const emailDir_base = join7(EMAIL_BASE, workspaceId, opts.agent_id);
17618
+ const emailDir_base = join8(EMAIL_BASE, workspaceId, opts.agent_id);
17336
17619
  try {
17337
17620
  let query = `/api/email?agentId=${opts.agent_id}`;
17338
17621
  if (opts.status)
@@ -17349,7 +17632,7 @@ function emailCommand() {
17349
17632
  mkdirSync7(emailDir_base, { recursive: true });
17350
17633
  const downloadedPaths = [];
17351
17634
  for (const email3 of emails2) {
17352
- const emailDir = join7(emailDir_base, email3.id);
17635
+ const emailDir = join8(emailDir_base, email3.id);
17353
17636
  mkdirSync7(emailDir, { recursive: true });
17354
17637
  const metadata = {
17355
17638
  id: email3.id,
@@ -17362,7 +17645,7 @@ function emailCommand() {
17362
17645
  in_reply_to: email3.in_reply_to || "",
17363
17646
  references: email3.references || ""
17364
17647
  };
17365
- const metadataPath = join7(emailDir, "metadata.json");
17648
+ const metadataPath = join8(emailDir, "metadata.json");
17366
17649
  writeFileSync6(metadataPath, JSON.stringify(metadata, null, 2));
17367
17650
  downloadedPaths.push(metadataPath);
17368
17651
  let rawMime;
@@ -17378,17 +17661,17 @@ function emailCommand() {
17378
17661
  }
17379
17662
  const parsed = await new PostalMime().parse(rawMime);
17380
17663
  if (parsed.text) {
17381
- const bodyPath = join7(emailDir, "body.txt");
17664
+ const bodyPath = join8(emailDir, "body.txt");
17382
17665
  writeFileSync6(bodyPath, parsed.text);
17383
17666
  downloadedPaths.push(bodyPath);
17384
17667
  }
17385
17668
  if (parsed.html) {
17386
- const htmlPath = join7(emailDir, "body.html");
17669
+ const htmlPath = join8(emailDir, "body.html");
17387
17670
  writeFileSync6(htmlPath, parsed.html);
17388
17671
  downloadedPaths.push(htmlPath);
17389
17672
  }
17390
17673
  if (parsed.attachments && parsed.attachments.length > 0) {
17391
- const attDir = join7(emailDir, "attachments");
17674
+ const attDir = join8(emailDir, "attachments");
17392
17675
  mkdirSync7(attDir, { recursive: true });
17393
17676
  const usedFilenames = new Set;
17394
17677
  for (let i = 0;i < parsed.attachments.length; i++) {
@@ -17398,7 +17681,7 @@ function emailCommand() {
17398
17681
  filename = `${i}-${filename}`;
17399
17682
  }
17400
17683
  usedFilenames.add(filename);
17401
- const attPath = join7(attDir, filename);
17684
+ const attPath = join8(attDir, filename);
17402
17685
  const content = att.content;
17403
17686
  let buf;
17404
17687
  if (typeof content === "string") {
@@ -17884,8 +18167,8 @@ function syncCommand() {
17884
18167
  let bytes;
17885
18168
  let size;
17886
18169
  try {
17887
- const stat = statSync5(opts.file);
17888
- size = stat.size;
18170
+ const stat2 = statSync5(opts.file);
18171
+ size = stat2.size;
17889
18172
  bytes = readFileSync8(opts.file);
17890
18173
  } catch (err) {
17891
18174
  console.error(`Error: cannot read file "${opts.file}": ${err.message}`);