@alook/cli 0.0.23 → 0.0.25

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() }))
@@ -13944,7 +13963,6 @@ var DaemonRuntimeItemSchema = exports_external.object({
13944
13963
  type: exports_external.string().optional(),
13945
13964
  provider: exports_external.string().optional(),
13946
13965
  runtime_mode: exports_external.string().optional(),
13947
- name: exports_external.string().optional(),
13948
13966
  version: exports_external.string().optional(),
13949
13967
  status: exports_external.string().optional(),
13950
13968
  model: exports_external.string().optional()
@@ -14060,7 +14078,8 @@ var UpdateAgentRequestSchema = exports_external.object({
14060
14078
  visibility: exports_external.enum(["public", "private"]).optional()
14061
14079
  }).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
14080
  var CreateConversationRequestSchema = exports_external.object({
14063
- agent_id: exports_external.string().min(1, "agent_id is required")
14081
+ agent_id: exports_external.string().min(1, "agent_id is required"),
14082
+ channel: exports_external.string().optional()
14064
14083
  });
14065
14084
  var CreateMessageRequestSchema = exports_external.object({
14066
14085
  content: exports_external.string().min(1, "content is required")
@@ -14088,6 +14107,13 @@ var SendEmailRequestSchema = exports_external.object({
14088
14107
  var UpdateEmailStatusRequestSchema = exports_external.object({
14089
14108
  status: exports_external.enum(["unread", "read", "archived"])
14090
14109
  });
14110
+ var MeetingInfoSchema = exports_external.object({
14111
+ title: exports_external.string(),
14112
+ meetingUrl: exports_external.string(),
14113
+ startTime: exports_external.string().nullable(),
14114
+ endTime: exports_external.string().nullable(),
14115
+ attendees: exports_external.array(exports_external.object({ name: exports_external.string(), email: exports_external.string() }))
14116
+ });
14091
14117
  var EmailNotifyRequestSchema = exports_external.object({
14092
14118
  agentId: exports_external.string().min(1),
14093
14119
  workspaceId: exports_external.string().min(1),
@@ -14099,7 +14125,8 @@ var EmailNotifyRequestSchema = exports_external.object({
14099
14125
  forwarded: exports_external.boolean().optional().default(false),
14100
14126
  messageId: exports_external.string().optional().default(""),
14101
14127
  inReplyTo: exports_external.string().optional().default(""),
14102
- references: exports_external.string().optional().default("")
14128
+ references: exports_external.string().optional().default(""),
14129
+ meetingInfo: MeetingInfoSchema.nullable().optional()
14103
14130
  });
14104
14131
  var CreateEmailAccountSchema = exports_external.object({
14105
14132
  emailAddress: exports_external.string().email("valid email required"),
@@ -14160,6 +14187,25 @@ var DeleteWorkspaceRequestSchema = exports_external.object({
14160
14187
  var GrantAgentAccessRequestSchema = exports_external.object({
14161
14188
  user_id: exports_external.string().min(1, "user_id is required")
14162
14189
  });
14190
+ var WorkspaceFileBrowseRequestSchema = exports_external.object({
14191
+ request_type: exports_external.enum(["tree", "read"]),
14192
+ path: exports_external.string().default(".")
14193
+ });
14194
+ var WorkspaceFileEntrySchema = exports_external.object({
14195
+ name: exports_external.string(),
14196
+ path: exports_external.string(),
14197
+ isDirectory: exports_external.boolean(),
14198
+ size: exports_external.number(),
14199
+ modifiedAt: exports_external.string()
14200
+ });
14201
+ var WorkspaceFileReportSchema = exports_external.object({
14202
+ request_id: exports_external.string().min(1),
14203
+ entries: exports_external.array(WorkspaceFileEntrySchema).optional(),
14204
+ content: exports_external.string().nullable().optional(),
14205
+ isBinary: exports_external.boolean().optional(),
14206
+ error: exports_external.string().optional(),
14207
+ path: exports_external.string()
14208
+ });
14163
14209
  // ../../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
14210
  var entityKind = Symbol.for("drizzle:entityKind");
14165
14211
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -15647,7 +15693,6 @@ var agentRuntime = sqliteTable("agent_runtime", {
15647
15693
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
15648
15694
  workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
15649
15695
  daemonId: text("daemon_id").notNull(),
15650
- name: text("name").notNull().default(""),
15651
15696
  runtimeMode: text("runtime_mode").notNull().default("local"),
15652
15697
  provider: text("provider").notNull(),
15653
15698
  deviceInfo: text("device_info").notNull().default(""),
@@ -15690,6 +15735,15 @@ var agentWhitelist = sqliteTable("agent_whitelist", {
15690
15735
  foreignColumns: [agent.id, agent.workspaceId]
15691
15736
  }).onDelete("cascade")
15692
15737
  ]);
15738
+ var channel = sqliteTable("channel", {
15739
+ id: text("id").primaryKey().$defaultFn(() => "ch_" + nanoid3()),
15740
+ workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
15741
+ name: text("name").notNull(),
15742
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15743
+ }, (t) => [
15744
+ unique("channel_workspace_name").on(t.workspaceId, t.name),
15745
+ index("idx_channel_workspace").on(t.workspaceId)
15746
+ ]);
15693
15747
  var conversation = sqliteTable("conversation", {
15694
15748
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
15695
15749
  workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
@@ -15697,6 +15751,7 @@ var conversation = sqliteTable("conversation", {
15697
15751
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
15698
15752
  title: text("title").notNull().default(""),
15699
15753
  type: text("type").notNull().default(TASK_TYPES.USER_DM_MESSAGE),
15754
+ channel: text("channel").notNull().default("default"),
15700
15755
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15701
15756
  }, (t) => [
15702
15757
  foreignKey({
@@ -15850,6 +15905,33 @@ var agentEmailAccount = sqliteTable("agent_email_account", {
15850
15905
  foreignColumns: [agent.id, agent.workspaceId]
15851
15906
  }).onDelete("cascade")
15852
15907
  ]);
15908
+ var meetingSession = sqliteTable("meeting_session", {
15909
+ id: text("id").primaryKey().$defaultFn(() => "ms_" + nanoid3()),
15910
+ agentId: text("agent_id").notNull(),
15911
+ workspaceId: text("workspace_id").notNull(),
15912
+ title: text("title").notNull().default(""),
15913
+ meetingUrl: text("meeting_url").notNull(),
15914
+ status: text("status").notNull().default("scheduled"),
15915
+ fromEmail: text("from_email"),
15916
+ isWhitelisted: integer2("is_whitelisted", { mode: "boolean" }).notNull().default(true),
15917
+ participants: text("participants", { mode: "json" }).$type().notNull().default([]),
15918
+ scheduledAt: text("scheduled_at"),
15919
+ startedAt: text("started_at"),
15920
+ completedAt: text("completed_at"),
15921
+ transcriptR2Key: text("transcript_r2_key"),
15922
+ summary: text("summary"),
15923
+ error: text("error"),
15924
+ workerSessionId: text("worker_session_id"),
15925
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15926
+ updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15927
+ }, (t) => [
15928
+ index("idx_meeting_session_agent_ws").on(t.agentId, t.workspaceId),
15929
+ index("idx_meeting_session_status").on(t.status),
15930
+ foreignKey({
15931
+ columns: [t.agentId, t.workspaceId],
15932
+ foreignColumns: [agent.id, agent.workspaceId]
15933
+ }).onDelete("cascade")
15934
+ ]);
15853
15935
  var machineToken = sqliteTable("machine_token", {
15854
15936
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
15855
15937
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -15860,6 +15942,17 @@ var machineToken = sqliteTable("machine_token", {
15860
15942
  lastUsedAt: text("last_used_at"),
15861
15943
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15862
15944
  }, (t) => [index("idx_machine_token").on(t.token)]);
15945
+ var workspaceFileRequest = sqliteTable("workspace_file_request", {
15946
+ id: text("id").primaryKey().$defaultFn(() => "wfr_" + nanoid3()),
15947
+ workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
15948
+ agentId: text("agent_id").notNull(),
15949
+ requestType: text("request_type").notNull(),
15950
+ path: text("path").notNull().default("."),
15951
+ status: text("status").notNull().default("pending"),
15952
+ result: text("result"),
15953
+ createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString()),
15954
+ updatedAt: text("updated_at").notNull().$defaultFn(() => new Date().toISOString())
15955
+ }, (t) => [index("idx_wfr_workspace_status").on(t.workspaceId, t.status)]);
15863
15956
  // ../shared/src/db/queries/task.ts
15864
15957
  var DEFAULT_STALE_SECONDS = Number(process.env.ALOOK_STALE_DISPATCH_TIMEOUT_S) || 20;
15865
15958
  var DEFAULT_STALE_RUNNING_SECONDS = Number(process.env.ALOOK_STALE_RUNNING_TIMEOUT_S) || 3600;
@@ -15951,7 +16044,8 @@ class DaemonClient {
15951
16044
  tasks: resp.tasks,
15952
16045
  evicted: resp.evicted ?? false,
15953
16046
  pending_update: resp.pending_update,
15954
- pending_rescan: resp.pending_rescan
16047
+ pending_rescan: resp.pending_rescan,
16048
+ file_requests: resp.file_requests
15955
16049
  };
15956
16050
  }
15957
16051
  startTask(token, taskId) {
@@ -15998,6 +16092,18 @@ class DaemonClient {
15998
16092
  reportMessages(token, taskId, messages) {
15999
16093
  return this.request("POST", `/api/daemon/tasks/${taskId}/messages`, token, { messages });
16000
16094
  }
16095
+ reportFileData(token, body) {
16096
+ return this.request("POST", "/api/daemon/workspace/report", token, body);
16097
+ }
16098
+ async claimMeetings(token, daemonId) {
16099
+ const raw = await this.request("POST", "/api/daemon/meetings/claim", token, { daemon_id: daemonId });
16100
+ return raw.map((m) => ({
16101
+ id: m.id,
16102
+ meetingUrl: m.meeting_url,
16103
+ participants: m.participants,
16104
+ workspaceId: m.workspace_id
16105
+ }));
16106
+ }
16001
16107
  }
16002
16108
 
16003
16109
  // daemon/config.ts
@@ -16099,7 +16205,6 @@ function loadDaemonConfig(profile) {
16099
16205
  maxConcurrentTasks: parseInt(process.env.ALOOK_DAEMON_MAX_CONCURRENT_TASKS || "20"),
16100
16206
  daemonId,
16101
16207
  deviceName: process.env.ALOOK_DAEMON_DEVICE_NAME || h,
16102
- runtimeName: process.env.ALOOK_AGENT_RUNTIME_NAME || "Local Agent",
16103
16208
  workspacesRoot,
16104
16209
  cliVersion: getCurrentVersion()
16105
16210
  };
@@ -16562,14 +16667,108 @@ function releaseSteeringLock(baseDir, contextKey) {
16562
16667
  releaseLock(lockPath);
16563
16668
  }
16564
16669
 
16670
+ // daemon/workspace-files.ts
16671
+ import { readdir, stat, readFile } from "fs/promises";
16672
+ import { join as join6, resolve, extname, relative } from "path";
16673
+ var SKIP_DIRS = new Set([".git", "node_modules", ".next", ".wrangler", "__pycache__", ".venv"]);
16674
+ var TEXT_EXTENSIONS = new Set([
16675
+ ".md",
16676
+ ".txt",
16677
+ ".json",
16678
+ ".js",
16679
+ ".ts",
16680
+ ".tsx",
16681
+ ".jsx",
16682
+ ".py",
16683
+ ".rb",
16684
+ ".go",
16685
+ ".rs",
16686
+ ".toml",
16687
+ ".yaml",
16688
+ ".yml",
16689
+ ".html",
16690
+ ".css",
16691
+ ".scss",
16692
+ ".sh",
16693
+ ".bash",
16694
+ ".zsh",
16695
+ ".env",
16696
+ ".cfg",
16697
+ ".ini",
16698
+ ".xml",
16699
+ ".svg",
16700
+ ".sql",
16701
+ ".jsonl",
16702
+ ".log",
16703
+ ".csv"
16704
+ ]);
16705
+ var MAX_FILE_SIZE = 1048576;
16706
+ async function readDirectoryTree(dirPath, basePath) {
16707
+ let entries;
16708
+ try {
16709
+ entries = await readdir(dirPath, { withFileTypes: true });
16710
+ } catch {
16711
+ return [];
16712
+ }
16713
+ entries.sort((a, b) => {
16714
+ if (a.isDirectory() && !b.isDirectory())
16715
+ return -1;
16716
+ if (!a.isDirectory() && b.isDirectory())
16717
+ return 1;
16718
+ return a.name.localeCompare(b.name);
16719
+ });
16720
+ const results = [];
16721
+ for (const entry of entries) {
16722
+ if (entry.name.startsWith(".") && entry.name !== ".context_timeline")
16723
+ continue;
16724
+ if (SKIP_DIRS.has(entry.name))
16725
+ continue;
16726
+ const fullPath = join6(dirPath, entry.name);
16727
+ let info;
16728
+ try {
16729
+ info = await stat(fullPath);
16730
+ } catch {
16731
+ continue;
16732
+ }
16733
+ results.push({
16734
+ name: entry.name,
16735
+ path: relative(basePath, fullPath),
16736
+ isDirectory: entry.isDirectory(),
16737
+ size: entry.isDirectory() ? 0 : info.size,
16738
+ modifiedAt: info.mtime.toISOString()
16739
+ });
16740
+ }
16741
+ return results;
16742
+ }
16743
+ async function readFileContent(filePath) {
16744
+ const info = await stat(filePath);
16745
+ if (info.isDirectory())
16746
+ throw new Error("Cannot read a directory");
16747
+ if (info.size > MAX_FILE_SIZE)
16748
+ throw new Error("File too large (>1MB)");
16749
+ const ext = extname(filePath).toLowerCase();
16750
+ if (ext !== "" && !TEXT_EXTENSIONS.has(ext)) {
16751
+ return { content: null, isBinary: true };
16752
+ }
16753
+ const content = await readFile(filePath, "utf-8");
16754
+ return { content, isBinary: false };
16755
+ }
16756
+ function validatePath(agentWorkdir, requestedPath) {
16757
+ const resolved = resolve(agentWorkdir, requestedPath);
16758
+ if (resolved !== agentWorkdir && !resolved.startsWith(agentWorkdir + "/"))
16759
+ return null;
16760
+ return resolved;
16761
+ }
16762
+
16565
16763
  // daemon/daemon.ts
16566
16764
  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";
16765
+ import { readdir as readdir2, readFile as readFile2, unlink, stat as fsStat } from "fs/promises";
16568
16766
  import { execSync as execSync3, spawn as spawn2 } from "child_process";
16569
16767
  import { fileURLToPath as fileURLToPath2 } from "url";
16570
- import { dirname as dirname3, join as join6 } from "path";
16768
+ import { dirname as dirname3, join as join7 } from "path";
16571
16769
  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");
16770
+ var sessionRunnerPath = existsSync(join7(_dir, "session-runner.js")) ? join7(_dir, "session-runner.js") : join7(_dir, "session-runner.ts");
16771
+ var meetingRunnerPath = existsSync(join7(_dir, "meeting-runner.js")) ? join7(_dir, "meeting-runner.js") : join7(_dir, "meeting-runner.ts");
16573
16772
  function isCommandAvailable2(cmd) {
16574
16773
  try {
16575
16774
  const check2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
@@ -16591,7 +16790,7 @@ function pruneSessionRunnerLogs() {
16591
16790
  if (entries.length <= MAX_SESSION_RUNNER_LOGS)
16592
16791
  return;
16593
16792
  const withMtime = entries.map((name) => {
16594
- const full = join6(logDir, name);
16793
+ const full = join7(logDir, name);
16595
16794
  try {
16596
16795
  return { name, mtime: statSync3(full).mtimeMs };
16597
16796
  } catch {
@@ -16601,7 +16800,7 @@ function pruneSessionRunnerLogs() {
16601
16800
  withMtime.sort((a, b) => b.mtime - a.mtime);
16602
16801
  for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
16603
16802
  try {
16604
- unlinkSync4(join6(logDir, entry.name));
16803
+ unlinkSync4(join7(logDir, entry.name));
16605
16804
  } catch {}
16606
16805
  }
16607
16806
  }
@@ -16642,10 +16841,10 @@ function isValidMarker(data) {
16642
16841
  var MARKER_STALE_MS = 24 * 60 * 60 * 1000;
16643
16842
  var TMP_STALE_MS = 60 * 60 * 1000;
16644
16843
  async function reconcilePendingCompletions(workspacesRoot) {
16645
- const dir = join6(workspacesRoot, ".pending_completions");
16844
+ const dir = join7(workspacesRoot, ".pending_completions");
16646
16845
  let entries;
16647
16846
  try {
16648
- entries = await readdir(dir);
16847
+ entries = await readdir2(dir);
16649
16848
  } catch {
16650
16849
  return;
16651
16850
  }
@@ -16653,19 +16852,19 @@ async function reconcilePendingCompletions(workspacesRoot) {
16653
16852
  if (!name.endsWith(".tmp"))
16654
16853
  continue;
16655
16854
  try {
16656
- const s = await fsStat(join6(dir, name));
16855
+ const s = await fsStat(join7(dir, name));
16657
16856
  if (Date.now() - s.mtimeMs > TMP_STALE_MS) {
16658
- await unlink(join6(dir, name));
16857
+ await unlink(join7(dir, name));
16659
16858
  }
16660
16859
  } catch {}
16661
16860
  }
16662
16861
  const jsonFiles = entries.filter((f) => f.endsWith(".json"));
16663
16862
  for (const name of jsonFiles) {
16664
- const filePath = join6(dir, name);
16863
+ const filePath = join7(dir, name);
16665
16864
  try {
16666
16865
  let raw;
16667
16866
  try {
16668
- raw = await readFile(filePath, "utf-8");
16867
+ raw = await readFile2(filePath, "utf-8");
16669
16868
  } catch {
16670
16869
  continue;
16671
16870
  }
@@ -16780,7 +16979,6 @@ async function startDaemon(profile, serverUrl) {
16780
16979
  const runtimeIndex = new Map;
16781
16980
  for (const ws of workspaces) {
16782
16981
  const runtimes = providers.map((p) => ({
16783
- name: config2.runtimeName || `${p.type} (${config2.deviceName})`,
16784
16982
  type: p.type,
16785
16983
  version: p.version
16786
16984
  }));
@@ -16872,7 +17070,7 @@ async function startDaemon(profile, serverUrl) {
16872
17070
  await new Promise((r) => setTimeout(r, staggerMs));
16873
17071
  }
16874
17072
  try {
16875
- const { tasks: apiTasks, evicted, pending_update, pending_rescan } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
17073
+ const { tasks: apiTasks, evicted, pending_update, pending_rescan, file_requests } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
16876
17074
  if (evicted) {
16877
17075
  evictedIds.push(ws.workspaceId);
16878
17076
  continue;
@@ -16894,6 +17092,11 @@ async function startDaemon(profile, serverUrl) {
16894
17092
  activeTasks.delete(task.id);
16895
17093
  });
16896
17094
  }
17095
+ if (file_requests) {
17096
+ for (const req of file_requests) {
17097
+ handleFileRequest(client, config2, ws.workspaceId, req, ws.token).catch((e) => log.debug("File request error", e));
17098
+ }
17099
+ }
16897
17100
  } catch (e) {
16898
17101
  if (e instanceof Error && e.message.startsWith("HTTP 401")) {
16899
17102
  log.warn(`Workspace ${ws.workspaceId} poll returned 401 — will retry next cycle`);
@@ -16905,6 +17108,23 @@ async function startDaemon(profile, serverUrl) {
16905
17108
  for (const id of evictedIds) {
16906
17109
  evictWorkspace(id);
16907
17110
  }
17111
+ for (const ws of workspaceStates) {
17112
+ try {
17113
+ const meetings = await client.claimMeetings(ws.token, config2.daemonId);
17114
+ for (const m of meetings) {
17115
+ spawnMeetingRunner({
17116
+ meetingId: m.id,
17117
+ meetingUrl: m.meetingUrl,
17118
+ participants: m.participants,
17119
+ workspaceId: m.workspaceId,
17120
+ callbackUrl: config2.serverURL,
17121
+ authToken: ws.token
17122
+ });
17123
+ }
17124
+ } catch (e) {
17125
+ log.debug("Meeting claim error", e);
17126
+ }
17127
+ }
16908
17128
  try {
16909
17129
  await reconcilePendingCompletions(config2.workspacesRoot);
16910
17130
  } catch (e) {
@@ -16972,7 +17192,7 @@ async function startDaemon(profile, serverUrl) {
16972
17192
  function spawnSessionRunner(input) {
16973
17193
  const logDir = sessionRunnerLogDir();
16974
17194
  mkdirSync5(logDir, { recursive: true });
16975
- const logFilePath = join6(logDir, `${input.task.id}.log`);
17195
+ const logFilePath = join7(logDir, `${input.task.id}.log`);
16976
17196
  input.logFilePath = logFilePath;
16977
17197
  const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
16978
17198
  let fd;
@@ -16990,6 +17210,50 @@ function spawnSessionRunner(input) {
16990
17210
  closeSync(fd);
16991
17211
  return child;
16992
17212
  }
17213
+ function spawnMeetingRunner(input) {
17214
+ const logDir = sessionRunnerLogDir();
17215
+ mkdirSync5(logDir, { recursive: true });
17216
+ const logFilePath = join7(logDir, `meeting-${input.meetingId}.log`);
17217
+ const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
17218
+ let fd;
17219
+ try {
17220
+ fd = openSync(logFilePath, "a");
17221
+ } catch (e) {
17222
+ log.error(`Failed to open meeting log file ${logFilePath}`, e);
17223
+ }
17224
+ const child = spawn2(process.execPath, [meetingRunnerPath, encoded], {
17225
+ detached: true,
17226
+ stdio: fd != null ? ["ignore", fd, fd] : ["ignore", "ignore", "ignore"]
17227
+ });
17228
+ child.unref();
17229
+ if (fd != null)
17230
+ closeSync(fd);
17231
+ log.info(`Spawned meeting runner for ${input.meetingId} (pid=${child.pid})`);
17232
+ return child;
17233
+ }
17234
+ async function handleFileRequest(client, config2, workspaceId, req, token) {
17235
+ const agentWorkdir = join7(config2.workspacesRoot, workspaceId, req.agent_id, "workdir");
17236
+ const resolved = validatePath(agentWorkdir, req.path);
17237
+ if (!resolved) {
17238
+ await client.reportFileData(token, { request_id: req.id, error: "invalid path", path: req.path });
17239
+ return;
17240
+ }
17241
+ try {
17242
+ if (req.request_type === "tree") {
17243
+ const entries = await readDirectoryTree(resolved, agentWorkdir);
17244
+ await client.reportFileData(token, { request_id: req.id, entries, path: req.path });
17245
+ } else {
17246
+ const { content, isBinary } = await readFileContent(resolved);
17247
+ await client.reportFileData(token, { request_id: req.id, content, isBinary, path: req.path });
17248
+ }
17249
+ } catch (e) {
17250
+ await client.reportFileData(token, {
17251
+ request_id: req.id,
17252
+ error: e instanceof Error ? e.message : String(e),
17253
+ path: req.path
17254
+ });
17255
+ }
17256
+ }
16993
17257
  async function handleTask(client, config2, runtimeIndex, task, token, activeTasks) {
16994
17258
  log.info(`Task ${task.id} claimed agent=${task.agentId}`);
16995
17259
  if (task.type === TASK_TYPES.KILL_TASK) {
@@ -16999,8 +17263,8 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
16999
17263
  activeTasks.delete(task.id);
17000
17264
  return;
17001
17265
  }
17002
- const agentBaseDir = join6(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
17003
- const timelineDir = join6(agentBaseDir, ".context_timeline");
17266
+ const agentBaseDir = join7(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
17267
+ const timelineDir = join7(agentBaseDir, ".context_timeline");
17004
17268
  const pid = findRunningPidByTaskId(timelineDir, targetTaskId);
17005
17269
  if (pid != null) {
17006
17270
  writeKillIntent(agentBaseDir, {
@@ -17042,9 +17306,9 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
17042
17306
  }
17043
17307
  const provider = runtimeData.provider;
17044
17308
  if (task.contextKey) {
17045
- const agentBaseDir = join6(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
17309
+ const agentBaseDir = join7(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
17046
17310
  cleanupStaleIntents(agentBaseDir);
17047
- const timelineDir = join6(agentBaseDir, ".context_timeline");
17311
+ const timelineDir = join7(agentBaseDir, ".context_timeline");
17048
17312
  const lockAcquired = acquireSteeringLock(agentBaseDir, task.contextKey);
17049
17313
  if (!lockAcquired) {
17050
17314
  log.warn(`Steering lock contention for context_key=${task.contextKey}, proceeding without steering`);
@@ -17269,7 +17533,7 @@ function configCommand() {
17269
17533
  // commands/email.ts
17270
17534
  import { Command as Command5 } from "commander";
17271
17535
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
17272
- import { basename, join as join7 } from "path";
17536
+ import { basename, join as join8 } from "path";
17273
17537
  import PostalMime from "postal-mime";
17274
17538
  var VALID_STATUSES = ["unread", "read", "archived"];
17275
17539
  var EMAIL_BASE = "/tmp/alook-emails";
@@ -17311,11 +17575,26 @@ function resolveClientOpts(command, opts) {
17311
17575
  let ws;
17312
17576
  if (opts.workspace) {
17313
17577
  ws = workspaces.find((w) => w.id === opts.workspace);
17578
+ if (!ws) {
17579
+ console.error(`Error: workspace ${opts.workspace} not found in config.`);
17580
+ process.exit(1);
17581
+ }
17314
17582
  } else if (opts.agentId) {
17315
17583
  ws = workspaces.find((w) => w.agent_ids?.includes(opts.agentId));
17316
- }
17317
- if (!ws)
17584
+ if (!ws) {
17585
+ if (workspaces.length === 1) {
17586
+ ws = workspaces[0];
17587
+ } else {
17588
+ console.error(`Error: agent ${opts.agentId} not found in any registered workspace. Use --workspace to specify.`);
17589
+ process.exit(1);
17590
+ }
17591
+ }
17592
+ } else if (workspaces.length === 1) {
17318
17593
  ws = workspaces[0];
17594
+ } else {
17595
+ console.error(`Error: multiple workspaces registered. Use --workspace to specify which one.`);
17596
+ process.exit(1);
17597
+ }
17319
17598
  const token = ws?.token;
17320
17599
  if (!token) {
17321
17600
  console.error(`Error: not registered. Run '${cmdPrefix()} register --token <token>' first.`);
@@ -17332,7 +17611,7 @@ function emailCommand() {
17332
17611
  console.error(`Error: invalid status "${opts.status}", must be one of: ${VALID_STATUSES.join(", ")}`);
17333
17612
  process.exit(1);
17334
17613
  }
17335
- const emailDir_base = join7(EMAIL_BASE, workspaceId, opts.agent_id);
17614
+ const emailDir_base = join8(EMAIL_BASE, workspaceId, opts.agent_id);
17336
17615
  try {
17337
17616
  let query = `/api/email?agentId=${opts.agent_id}`;
17338
17617
  if (opts.status)
@@ -17349,7 +17628,7 @@ function emailCommand() {
17349
17628
  mkdirSync7(emailDir_base, { recursive: true });
17350
17629
  const downloadedPaths = [];
17351
17630
  for (const email3 of emails2) {
17352
- const emailDir = join7(emailDir_base, email3.id);
17631
+ const emailDir = join8(emailDir_base, email3.id);
17353
17632
  mkdirSync7(emailDir, { recursive: true });
17354
17633
  const metadata = {
17355
17634
  id: email3.id,
@@ -17362,7 +17641,7 @@ function emailCommand() {
17362
17641
  in_reply_to: email3.in_reply_to || "",
17363
17642
  references: email3.references || ""
17364
17643
  };
17365
- const metadataPath = join7(emailDir, "metadata.json");
17644
+ const metadataPath = join8(emailDir, "metadata.json");
17366
17645
  writeFileSync6(metadataPath, JSON.stringify(metadata, null, 2));
17367
17646
  downloadedPaths.push(metadataPath);
17368
17647
  let rawMime;
@@ -17378,17 +17657,17 @@ function emailCommand() {
17378
17657
  }
17379
17658
  const parsed = await new PostalMime().parse(rawMime);
17380
17659
  if (parsed.text) {
17381
- const bodyPath = join7(emailDir, "body.txt");
17660
+ const bodyPath = join8(emailDir, "body.txt");
17382
17661
  writeFileSync6(bodyPath, parsed.text);
17383
17662
  downloadedPaths.push(bodyPath);
17384
17663
  }
17385
17664
  if (parsed.html) {
17386
- const htmlPath = join7(emailDir, "body.html");
17665
+ const htmlPath = join8(emailDir, "body.html");
17387
17666
  writeFileSync6(htmlPath, parsed.html);
17388
17667
  downloadedPaths.push(htmlPath);
17389
17668
  }
17390
17669
  if (parsed.attachments && parsed.attachments.length > 0) {
17391
- const attDir = join7(emailDir, "attachments");
17670
+ const attDir = join8(emailDir, "attachments");
17392
17671
  mkdirSync7(attDir, { recursive: true });
17393
17672
  const usedFilenames = new Set;
17394
17673
  for (let i = 0;i < parsed.attachments.length; i++) {
@@ -17398,7 +17677,7 @@ function emailCommand() {
17398
17677
  filename = `${i}-${filename}`;
17399
17678
  }
17400
17679
  usedFilenames.add(filename);
17401
- const attPath = join7(attDir, filename);
17680
+ const attPath = join8(attDir, filename);
17402
17681
  const content = att.content;
17403
17682
  let buf;
17404
17683
  if (typeof content === "string") {
@@ -17884,8 +18163,8 @@ function syncCommand() {
17884
18163
  let bytes;
17885
18164
  let size;
17886
18165
  try {
17887
- const stat = statSync5(opts.file);
17888
- size = stat.size;
18166
+ const stat2 = statSync5(opts.file);
18167
+ size = stat2.size;
17889
18168
  bytes = readFileSync8(opts.file);
17890
18169
  } catch (err) {
17891
18170
  console.error(`Error: cannot read file "${opts.file}": ${err.message}`);