@alook/cli 0.0.158 → 0.0.160

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
@@ -40,6 +40,50 @@ function fillPool(bytes) {
40
40
  }
41
41
  poolOffset += bytes;
42
42
  }
43
+ function random(bytes) {
44
+ fillPool(bytes |= 0);
45
+ return pool.subarray(poolOffset - bytes, poolOffset);
46
+ }
47
+ function customRandom(alphabet, defaultSize, getRandom) {
48
+ let safeByteCutoff = 256 - 256 % alphabet.length;
49
+ if (safeByteCutoff === 256) {
50
+ let mask = alphabet.length - 1;
51
+ return (size = defaultSize) => {
52
+ if (!size)
53
+ return "";
54
+ let id = "";
55
+ while (true) {
56
+ let bytes = getRandom(size);
57
+ let i = size;
58
+ while (i--) {
59
+ id += alphabet[bytes[i] & mask];
60
+ if (id.length >= size)
61
+ return id;
62
+ }
63
+ }
64
+ };
65
+ }
66
+ let step = Math.ceil(1.6 * 256 * defaultSize / safeByteCutoff);
67
+ return (size = defaultSize) => {
68
+ if (!size)
69
+ return "";
70
+ let id = "";
71
+ while (true) {
72
+ let bytes = getRandom(step);
73
+ let i = step;
74
+ while (i--) {
75
+ if (bytes[i] < safeByteCutoff) {
76
+ id += alphabet[bytes[i] % alphabet.length];
77
+ if (id.length >= size)
78
+ return id;
79
+ }
80
+ }
81
+ }
82
+ };
83
+ }
84
+ function customAlphabet(alphabet, size = 21) {
85
+ return customRandom(alphabet, size, random);
86
+ }
43
87
  function nanoid3(size = 21) {
44
88
  fillPool(size |= 0);
45
89
  let id = "";
@@ -14506,6 +14550,13 @@ function date4(params) {
14506
14550
 
14507
14551
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
14508
14552
  config(en_default());
14553
+ // ../shared/src/utils/slug.ts
14554
+ init_nanoid();
14555
+ var slugId = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz");
14556
+ function sanitizeSlug(input) {
14557
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
14558
+ }
14559
+
14509
14560
  // ../shared/src/lib/community-name.ts
14510
14561
  var FORBIDDEN_NAME_CHARS = /[#@\x00-\x1f\x7f-\x9f]/;
14511
14562
  function validateCommunityName(name) {
@@ -14933,11 +14984,11 @@ var UpdateMemberRequestSchema = exports_external.object({
14933
14984
  });
14934
14985
  var CreateWorkspaceRequestSchema = exports_external.object({
14935
14986
  name: exports_external.string().min(1, "name is required"),
14936
- slug: exports_external.string().optional().default("")
14987
+ slug: exports_external.string().optional().default("").transform(sanitizeSlug)
14937
14988
  });
14938
14989
  var UpdateWorkspaceRequestSchema = exports_external.object({
14939
14990
  name: exports_external.string().min(1, "name is required").max(100).trim().optional(),
14940
- slug: exports_external.string().min(1, "slug is required").max(100).trim().toLowerCase().optional()
14991
+ slug: exports_external.string().min(1, "slug is required").trim().toLowerCase().transform(sanitizeSlug).optional()
14941
14992
  });
14942
14993
  var DeleteWorkspaceRequestSchema = exports_external.object({
14943
14994
  confirm_name: exports_external.string().min(1, "confirm_name is required")
@@ -15189,6 +15240,11 @@ var CommunityAgentChannelMemberRequestSchema = exports_external.object({
15189
15240
  var CommunityAgentJoinServerRequestSchema = exports_external.object({
15190
15241
  invite: exports_external.string().min(1)
15191
15242
  });
15243
+ var CommunityAgentReactAddRequestSchema = exports_external.object({
15244
+ channel: exports_external.string().min(1),
15245
+ seq: CommunityAgentPositiveSeqSchema,
15246
+ emoji: exports_external.string().min(1)
15247
+ });
15192
15248
  var AuditLogCliInvocationPayloadSchema = exports_external.object({
15193
15249
  subcommand: exports_external.string().min(1)
15194
15250
  });
@@ -15201,15 +15257,28 @@ var AuditLogThinkingPayloadSchema = exports_external.object({
15201
15257
  truncated: exports_external.boolean(),
15202
15258
  chars: exports_external.number().int().nonnegative()
15203
15259
  });
15260
+ var AuditLogWakeTriggerPayloadSchema = exports_external.object({
15261
+ messageId: exports_external.string().min(1),
15262
+ channel: exports_external.string().min(1),
15263
+ seq: CommunityAgentPositiveSeqSchema,
15264
+ senderId: exports_external.string().min(1),
15265
+ senderHandle: exports_external.string().min(1),
15266
+ reason: exports_external.enum(["unread", "mention"])
15267
+ });
15268
+ var AuditLogSessionResetPayloadSchema = exports_external.object({});
15204
15269
  var BotAuditEventSchema = exports_external.discriminatedUnion("kind", [
15205
15270
  exports_external.object({ kind: exports_external.literal("cli_invocation"), payload: AuditLogCliInvocationPayloadSchema }),
15206
15271
  exports_external.object({ kind: exports_external.literal("tool_call"), payload: AuditLogToolCallPayloadSchema }),
15207
- exports_external.object({ kind: exports_external.literal("thinking"), payload: AuditLogThinkingPayloadSchema })
15272
+ exports_external.object({ kind: exports_external.literal("thinking"), payload: AuditLogThinkingPayloadSchema }),
15273
+ exports_external.object({ kind: exports_external.literal("wake_trigger"), payload: AuditLogWakeTriggerPayloadSchema }),
15274
+ exports_external.object({ kind: exports_external.literal("session_reset"), payload: AuditLogSessionResetPayloadSchema })
15208
15275
  ]);
15209
15276
  var BotAuditEventKindSchema = exports_external.enum([
15210
15277
  "cli_invocation",
15211
15278
  "tool_call",
15212
- "thinking"
15279
+ "thinking",
15280
+ "wake_trigger",
15281
+ "session_reset"
15213
15282
  ]);
15214
15283
  var HostBotAuditEventFrameSchema = exports_external.object({
15215
15284
  type: exports_external.literal("bot_audit_event"),
@@ -17223,7 +17292,8 @@ var communityChannel = sqliteTable("community_channel", {
17223
17292
  }, (t) => [
17224
17293
  index("idx_channel_server_position").on(t.serverId, t.position),
17225
17294
  index("idx_channel_server_last_message").on(t.serverId, t.lastMessageAt),
17226
- index("idx_channel_parent").on(t.parentChannelId)
17295
+ index("idx_channel_parent").on(t.parentChannelId),
17296
+ uniqueIndex("idx_channel_server_name").on(t.serverId, t.name).where(sql`parent_channel_id IS NULL`)
17227
17297
  ]);
17228
17298
  var communityChannelMember = sqliteTable("community_channel_member", {
17229
17299
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
@@ -17291,7 +17361,8 @@ var communityServerMember = sqliteTable("community_server_member", {
17291
17361
  }, (t) => [
17292
17362
  unique("uq_server_member_server_user").on(t.serverId, t.userId),
17293
17363
  index("idx_server_member_user").on(t.userId),
17294
- index("idx_server_member_user_rail_order").on(t.userId, t.railOrder)
17364
+ index("idx_server_member_user_rail_order").on(t.userId, t.railOrder),
17365
+ index("idx_server_member_server_joined").on(t.serverId, t.joinedAt)
17295
17366
  ]);
17296
17367
  var communityServerFolder = sqliteTable("community_server_folder", {
17297
17368
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
@@ -17316,7 +17387,7 @@ var communityServerInvite = sqliteTable("community_server_invite", {
17316
17387
  uses: integer2("uses").default(0),
17317
17388
  expiresAt: text("expires_at"),
17318
17389
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
17319
- });
17390
+ }, (t) => [index("idx_server_invite_server").on(t.serverId)]);
17320
17391
  var communityFriendship = sqliteTable("community_friendship", {
17321
17392
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
17322
17393
  requesterId: text("requester_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -17541,6 +17612,7 @@ var listedMessageProjection = {
17541
17612
  replyToId: communityMessage.replyToId,
17542
17613
  embeds: communityMessage.embeds,
17543
17614
  flags: communityMessage.flags,
17615
+ seq: communityMessage.seq,
17544
17616
  createdAt: communityMessage.createdAt,
17545
17617
  channelId: communityMessage.channelId,
17546
17618
  dmConversationId: communityMessage.dmConversationId,
@@ -17824,6 +17896,39 @@ import { dirname as dirname4 } from "path";
17824
17896
  import { readFileSync, writeFileSync, mkdirSync } from "fs";
17825
17897
  import { join } from "path";
17826
17898
  import { homedir } from "os";
17899
+ function workspaceStatus(ws) {
17900
+ return ws.status ?? (ws.id ? "active" : "deleted");
17901
+ }
17902
+ function activeWorkspaces(workspaces) {
17903
+ return (workspaces || []).filter((ws) => workspaceStatus(ws) === "active" && !!ws.id);
17904
+ }
17905
+ function markWorkspaceActive(watched, fields) {
17906
+ const existing = watched.find((w) => w.id === fields.id);
17907
+ if (existing) {
17908
+ existing.status = "active";
17909
+ existing.name = fields.name;
17910
+ if (fields.token !== undefined)
17911
+ existing.token = fields.token;
17912
+ if (fields.agent_ids !== undefined)
17913
+ existing.agent_ids = fields.agent_ids;
17914
+ } else {
17915
+ watched.push({
17916
+ id: fields.id,
17917
+ name: fields.name,
17918
+ token: fields.token ?? "",
17919
+ status: "active",
17920
+ agent_ids: fields.agent_ids ?? []
17921
+ });
17922
+ }
17923
+ return watched;
17924
+ }
17925
+ function markWorkspaceDeletedInList(watched, workspaceId) {
17926
+ const entry = watched.find((w) => w.id === workspaceId);
17927
+ if (!entry)
17928
+ return false;
17929
+ entry.status = "deleted";
17930
+ return true;
17931
+ }
17827
17932
  function configDir() {
17828
17933
  return process.env.ALOOK_PROJECT_ROOT || join(homedir(), ".alook");
17829
17934
  }
@@ -17850,7 +17955,7 @@ function loadCLIConfigForProfile(profile) {
17850
17955
  };
17851
17956
  for (const ws of result.watched_workspaces) {
17852
17957
  if (!ws.status)
17853
- ws.status = ws.id ? "active" : "deleted";
17958
+ ws.status = workspaceStatus(ws);
17854
17959
  }
17855
17960
  return result;
17856
17961
  }
@@ -22315,6 +22420,11 @@ class DaemonWsClient {
22315
22420
  this.opts.onConnected();
22316
22421
  return;
22317
22422
  }
22423
+ if (msg.type === "error" && msg.code === "AUTH_REJECTED") {
22424
+ log10.warn("machine token rejected by server (AUTH_REJECTED)", { reason: msg.reason });
22425
+ this.opts.onAuthRejected?.(msg.reason);
22426
+ return;
22427
+ }
22318
22428
  const parsed = DaemonPushMessageSchema.safeParse(msg);
22319
22429
  if (!parsed.success) {
22320
22430
  log10.warn("invalid push message", { err: parsed.error.message });
@@ -22325,13 +22435,16 @@ class DaemonWsClient {
22325
22435
  log10.debug("message parse error", { err: String(err) });
22326
22436
  }
22327
22437
  });
22328
- this.ws.addEventListener("error", () => {
22329
- log10.debug("ws error");
22438
+ this.ws.addEventListener("error", (event) => {
22439
+ const err = event;
22440
+ log10.warn("ws error", { err: String(err?.message ?? err?.error ?? "unknown") });
22330
22441
  });
22331
- this.ws.addEventListener("close", () => {
22442
+ this.ws.addEventListener("close", (event) => {
22443
+ const { code, reason } = event;
22332
22444
  const wasConnected = this.connected;
22333
22445
  this.connected = false;
22334
22446
  this.stopHeartbeat();
22447
+ log10.info("ws closed", { code, reason, wasConnected });
22335
22448
  if (wasConnected) {
22336
22449
  this.opts.onDisconnected();
22337
22450
  }
@@ -22364,7 +22477,7 @@ class DaemonWsClient {
22364
22477
  const delay = Math.min(this.reconnectDelay, WS_RECONNECT_MAX);
22365
22478
  this.reconnectDelay = Math.min(delay * 2, WS_RECONNECT_MAX);
22366
22479
  const jitter = Math.random() * 500;
22367
- log10.debug("reconnecting", { delayMs: Math.round(delay + jitter) });
22480
+ log10.info("reconnecting", { delayMs: Math.round(delay + jitter) });
22368
22481
  this.reconnectTimer = setTimeout(() => {
22369
22482
  this.reconnectTimer = null;
22370
22483
  this.connect();
@@ -23061,6 +23174,7 @@ function isValidMarker(data) {
23061
23174
  }
23062
23175
  var MARKER_STALE_MS = 24 * 60 * 60 * 1000;
23063
23176
  var TMP_STALE_MS = 60 * 60 * 1000;
23177
+ var WS_AUTH_401_THRESHOLD = 5;
23064
23178
  async function reconcilePendingCompletions(workspacesRoot) {
23065
23179
  const dir = join12(workspacesRoot, ".pending_completions");
23066
23180
  let entries;
@@ -23167,8 +23281,7 @@ async function startDaemon(profile, serverUrl) {
23167
23281
  }
23168
23282
  }
23169
23283
  const cliConfig = loadCLIConfigForProfile(profile);
23170
- const allEntries = cliConfig.watched_workspaces || [];
23171
- const workspaces = allEntries.filter((ws) => ws.status !== "deleted" && !!ws.id);
23284
+ const workspaces = activeWorkspaces(cliConfig.watched_workspaces);
23172
23285
  if (workspaces.length === 0) {
23173
23286
  log13.info("No workspaces configured — daemon starting in standby mode. Register a workspace to begin.");
23174
23287
  }
@@ -23204,6 +23317,7 @@ async function startDaemon(profile, serverUrl) {
23204
23317
  const workspaceStates = [];
23205
23318
  const runtimeIndex = new Map;
23206
23319
  let hadWorkspaces = workspaces.length > 0;
23320
+ const consecutive401 = new Map;
23207
23321
  for (const ws of workspaces) {
23208
23322
  const runtimes = providers.map((p) => ({
23209
23323
  type: p.type,
@@ -23267,7 +23381,7 @@ async function startDaemon(profile, serverUrl) {
23267
23381
  }
23268
23382
  } catch {}
23269
23383
  }
23270
- function evictWorkspace(workspaceId) {
23384
+ function markWorkspaceDeleted(workspaceId, reason) {
23271
23385
  const idx = workspaceStates.findIndex((ws2) => ws2.workspaceId === workspaceId);
23272
23386
  if (idx === -1)
23273
23387
  return;
@@ -23276,13 +23390,15 @@ async function startDaemon(profile, serverUrl) {
23276
23390
  runtimeIndex.delete(rid);
23277
23391
  }
23278
23392
  workspaceStates.splice(idx, 1);
23393
+ consecutive401.delete(workspaceId);
23279
23394
  health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
23280
23395
  try {
23281
23396
  const cfg = loadCLIConfigForProfile(profile);
23282
- cfg.watched_workspaces = (cfg.watched_workspaces || []).filter((w) => w.id !== workspaceId);
23283
- saveCLIConfigForProfile(profile, cfg);
23397
+ if (markWorkspaceDeletedInList(cfg.watched_workspaces || [], workspaceId)) {
23398
+ saveCLIConfigForProfile(profile, cfg);
23399
+ }
23284
23400
  } catch {}
23285
- log13.info(`Workspace ${workspaceId} deleted server-sideremoved from config`);
23401
+ log13.info(`Workspace ${workspaceId} removed from polling ${reason}`);
23286
23402
  }
23287
23403
  const pollCycle = async () => {
23288
23404
  let remaining = config2.maxConcurrentTasks - activeTasks.size;
@@ -23290,7 +23406,7 @@ async function startDaemon(profile, serverUrl) {
23290
23406
  return;
23291
23407
  const N = workspaceStates.length;
23292
23408
  const staggerMs = N > 1 ? Math.floor(config2.pollInterval / N) : 0;
23293
- const evictedIds = [];
23409
+ const toRemove = new Map;
23294
23410
  for (let i = 0;i < N; i++) {
23295
23411
  if (remaining <= 0)
23296
23412
  break;
@@ -23300,8 +23416,9 @@ async function startDaemon(profile, serverUrl) {
23300
23416
  }
23301
23417
  try {
23302
23418
  const { tasks: apiTasks, evicted, pending_update, pending_rescan, file_requests, meetings } = await client.poll(ws.token, config2.daemonId, remaining, config2.cliVersion);
23419
+ consecutive401.delete(ws.workspaceId);
23303
23420
  if (evicted) {
23304
- evictedIds.push(ws.workspaceId);
23421
+ toRemove.set(ws.workspaceId, "server evicted");
23305
23422
  continue;
23306
23423
  }
23307
23424
  if (pending_update && !isUpdating() && pending_update.version !== config2.cliVersion) {
@@ -23309,8 +23426,8 @@ async function startDaemon(profile, serverUrl) {
23309
23426
  }
23310
23427
  if (pending_rescan) {
23311
23428
  log13.info("Rescan requested — restarting daemon to re-detect runtimes");
23312
- for (const id of evictedIds) {
23313
- evictWorkspace(id);
23429
+ for (const [id, reason] of toRemove) {
23430
+ markWorkspaceDeleted(id, reason);
23314
23431
  }
23315
23432
  requestRestart();
23316
23433
  return;
@@ -23350,14 +23467,26 @@ async function startDaemon(profile, serverUrl) {
23350
23467
  }
23351
23468
  } catch (e) {
23352
23469
  if (e instanceof Error && e.message.startsWith("HTTP 401")) {
23353
- log13.warn(`Workspace ${ws.workspaceId} poll returned 401 — will retry next cycle`);
23470
+ const n = (consecutive401.get(ws.workspaceId) ?? 0) + 1;
23471
+ consecutive401.set(ws.workspaceId, n);
23472
+ if (n >= WS_AUTH_401_THRESHOLD) {
23473
+ toRemove.set(ws.workspaceId, `poll 401 x${n}`);
23474
+ } else {
23475
+ log13.warn(`Workspace ${ws.workspaceId} poll 401 (${n}/${WS_AUTH_401_THRESHOLD}) — will retry`);
23476
+ }
23354
23477
  } else {
23478
+ consecutive401.delete(ws.workspaceId);
23355
23479
  log13.debug("Poll error", e);
23356
23480
  }
23357
23481
  }
23358
23482
  }
23359
- for (const id of evictedIds) {
23360
- evictWorkspace(id);
23483
+ if (toRemove.size > 0) {
23484
+ const wsTokenWorkspaceDropped = wsWorkspaceId != null && toRemove.has(wsWorkspaceId);
23485
+ for (const [id, reason] of toRemove) {
23486
+ markWorkspaceDeleted(id, reason);
23487
+ }
23488
+ if (wsTokenWorkspaceDropped)
23489
+ rebuildWsClient();
23361
23490
  }
23362
23491
  if (workspaceStates.length === 0 && hadWorkspaces) {
23363
23492
  log13.info("All workspaces evicted — shutting down");
@@ -23373,7 +23502,6 @@ async function startDaemon(profile, serverUrl) {
23373
23502
  }
23374
23503
  };
23375
23504
  const heartbeatTimer = setInterval(heartbeatPing, config2.heartbeatInterval);
23376
- const firstToken = workspaceStates[0]?.token;
23377
23505
  function updatePollInterval(newInterval) {
23378
23506
  clearInterval(pollTimer);
23379
23507
  pollTimer = setInterval(pollCycle, newInterval);
@@ -23429,9 +23557,17 @@ async function startDaemon(profile, serverUrl) {
23429
23557
  });
23430
23558
  }
23431
23559
  break;
23432
- case "daemon.evict":
23433
- evictWorkspace(msg.workspaceId);
23560
+ case "daemon.evict": {
23561
+ const wasWsToken = wsWorkspaceId === msg.workspaceId;
23562
+ markWorkspaceDeleted(msg.workspaceId, "server evicted");
23563
+ if (wasWsToken)
23564
+ rebuildWsClient();
23565
+ if (workspaceStates.length === 0 && hadWorkspaces) {
23566
+ log13.info("All workspaces removed — shutting down");
23567
+ shutdown();
23568
+ }
23434
23569
  break;
23570
+ }
23435
23571
  case "daemon.update":
23436
23572
  if (!isUpdating() && msg.version !== config2.cliVersion) {
23437
23573
  handleCliUpdate(msg.version, () => requestRestart(), profile);
@@ -23474,11 +23610,9 @@ async function startDaemon(profile, serverUrl) {
23474
23610
  }
23475
23611
  }
23476
23612
  }
23477
- const wsToken = firstToken;
23478
- let wsClient = wsToken ? new DaemonWsClient({
23479
- serverURL: config2.serverURL,
23480
- daemonId: config2.daemonId,
23481
- machineToken: wsToken,
23613
+ let wsClient = null;
23614
+ let wsWorkspaceId;
23615
+ const wsCallbacks = {
23482
23616
  onMessage: handleWsPush,
23483
23617
  onConnected: () => {
23484
23618
  log13.info("WS connected — switching to low-frequency poll");
@@ -23487,9 +23621,57 @@ async function startDaemon(profile, serverUrl) {
23487
23621
  onDisconnected: () => {
23488
23622
  log13.info("WS disconnected — reverting to high-frequency poll");
23489
23623
  updatePollInterval(config2.pollInterval);
23624
+ },
23625
+ onAuthRejected: (reason) => {
23626
+ const rejectedWorkspaceId = wsWorkspaceId;
23627
+ const rejectedToken = workspaceStates.find((ws) => ws.workspaceId === rejectedWorkspaceId)?.token;
23628
+ wsClient?.close();
23629
+ wsClient = null;
23630
+ wsWorkspaceId = undefined;
23631
+ if (rejectedWorkspaceId != null && rejectedToken) {
23632
+ confirmAuthRejection(rejectedWorkspaceId, rejectedToken, reason);
23633
+ } else {
23634
+ rebuildWsClient();
23635
+ }
23490
23636
  }
23491
- }) : null;
23492
- wsClient?.connect();
23637
+ };
23638
+ async function confirmAuthRejection(workspaceId, token, reason) {
23639
+ let confirmedDead = false;
23640
+ try {
23641
+ await client.poll(token, config2.daemonId, 0, config2.cliVersion);
23642
+ log13.info(`Workspace ${workspaceId} WS auth rejection not confirmed by poll — keeping (likely transient)`);
23643
+ } catch (e) {
23644
+ if (e instanceof Error && e.message.startsWith("HTTP 401")) {
23645
+ confirmedDead = true;
23646
+ markWorkspaceDeleted(workspaceId, `WS auth rejected${reason ? ` (${reason})` : ""} — confirmed by poll 401`);
23647
+ } else {
23648
+ log13.debug("Confirm-poll error (transient) — keeping workspace", e);
23649
+ }
23650
+ }
23651
+ rebuildWsClient();
23652
+ if (confirmedDead && workspaceStates.length === 0 && hadWorkspaces) {
23653
+ log13.info("All workspaces removed — shutting down");
23654
+ shutdown();
23655
+ }
23656
+ }
23657
+ function rebuildWsClient() {
23658
+ wsClient?.close();
23659
+ const first = workspaceStates[0];
23660
+ if (!first) {
23661
+ wsClient = null;
23662
+ wsWorkspaceId = undefined;
23663
+ return;
23664
+ }
23665
+ wsWorkspaceId = first.workspaceId;
23666
+ wsClient = new DaemonWsClient({
23667
+ serverURL: config2.serverURL,
23668
+ daemonId: config2.daemonId,
23669
+ machineToken: first.token,
23670
+ ...wsCallbacks
23671
+ });
23672
+ wsClient.connect();
23673
+ }
23674
+ rebuildWsClient();
23493
23675
  const sweepTick = async () => {
23494
23676
  for (const ws of workspaceStates) {
23495
23677
  client.sweep(ws.token, config2.daemonId).catch((e) => {
@@ -23575,7 +23757,7 @@ async function startDaemon(profile, serverUrl) {
23575
23757
  log13.info("SIGHUP received — reloading config...");
23576
23758
  try {
23577
23759
  const freshConfig = loadCLIConfigForProfile(profile);
23578
- const freshWorkspaces = (freshConfig.watched_workspaces || []).filter((ws) => ws.status !== "deleted" && !!ws.id);
23760
+ const freshWorkspaces = activeWorkspaces(freshConfig.watched_workspaces);
23579
23761
  const existingIds = new Set(workspaceStates.map((ws) => ws.workspaceId));
23580
23762
  const newWorkspaces = freshWorkspaces.filter((ws) => ws.token && !existingIds.has(ws.id));
23581
23763
  for (const ws of newWorkspaces) {
@@ -23608,22 +23790,7 @@ async function startDaemon(profile, serverUrl) {
23608
23790
  hadWorkspaces = true;
23609
23791
  health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
23610
23792
  if (!wsClient && workspaceStates.length > 0) {
23611
- const token = workspaceStates[0].token;
23612
- wsClient = new DaemonWsClient({
23613
- serverURL: config2.serverURL,
23614
- daemonId: config2.daemonId,
23615
- machineToken: token,
23616
- onMessage: handleWsPush,
23617
- onConnected: () => {
23618
- log13.info("WS connected — switching to low-frequency poll");
23619
- updatePollInterval(config2.wsPollInterval);
23620
- },
23621
- onDisconnected: () => {
23622
- log13.info("WS disconnected — reverting to high-frequency poll");
23623
- updatePollInterval(config2.pollInterval);
23624
- }
23625
- });
23626
- wsClient.connect();
23793
+ rebuildWsClient();
23627
23794
  log13.info("WS push client initialized after SIGHUP reload");
23628
23795
  }
23629
23796
  log13.info(`Reload complete — now polling ${workspaceStates.length} workspace(s)`);
@@ -24125,13 +24292,12 @@ async function activateAndSave(opts) {
24125
24292
  agentIds = agents.map((a) => a.id);
24126
24293
  } catch {}
24127
24294
  const existing = loadCLIConfigForProfile(profile);
24128
- const watched = existing.watched_workspaces || [];
24129
- const idx = watched.findIndex((w) => w.id === ws.id);
24130
- if (idx >= 0) {
24131
- watched[idx] = { id: ws.id, name: ws.name, token, status: "active", agent_ids: agentIds };
24132
- } else {
24133
- watched.push({ id: ws.id, name: ws.name, token, status: "active", agent_ids: agentIds });
24134
- }
24295
+ const watched = markWorkspaceActive(existing.watched_workspaces || [], {
24296
+ id: ws.id,
24297
+ name: ws.name,
24298
+ token,
24299
+ agent_ids: agentIds
24300
+ });
24135
24301
  saveCLIConfigForProfile(profile, {
24136
24302
  server_url: serverUrl,
24137
24303
  watched_workspaces: watched
@@ -24236,17 +24402,11 @@ function syncWorkspacesToConfig(serverWorkspaces, profile, sessionToken) {
24236
24402
  const watched = cfg.watched_workspaces || [];
24237
24403
  const serverIds = new Set(serverWorkspaces.map((w) => w.id));
24238
24404
  for (const sw of serverWorkspaces) {
24239
- const existing = watched.find((w) => w.id === sw.id);
24240
- if (existing) {
24241
- existing.status = "active";
24242
- existing.name = sw.name;
24243
- } else {
24244
- watched.push({ id: sw.id, name: sw.name, token: "", status: "active", agent_ids: [] });
24245
- }
24405
+ markWorkspaceActive(watched, { id: sw.id, name: sw.name });
24246
24406
  }
24247
24407
  for (const w of watched) {
24248
24408
  if (w.id && !serverIds.has(w.id)) {
24249
- w.status = "deleted";
24409
+ markWorkspaceDeletedInList(watched, w.id);
24250
24410
  }
24251
24411
  }
24252
24412
  saveCLIConfigForProfile(profile, {
@@ -24352,7 +24512,7 @@ if (process.argv.includes("--__login-poll")) {
24352
24512
  async function checkExistingAuth(serverUrl, profile) {
24353
24513
  const config2 = loadCLIConfigForProfile(profile);
24354
24514
  const sessionToken = config2.session_token;
24355
- const workspaces = config2.watched_workspaces || [];
24515
+ const workspaces = activeWorkspaces(config2.watched_workspaces);
24356
24516
  const ws = workspaces[0];
24357
24517
  const authToken = sessionToken || ws?.token;
24358
24518
  if (!authToken) {
@@ -24366,7 +24526,7 @@ async function checkExistingAuth(serverUrl, profile) {
24366
24526
  return { valid: false };
24367
24527
  }
24368
24528
  const serverWorkspaces = await res.json();
24369
- const hasValidWorkspace = workspaces.some((w) => w.id && w.status !== "deleted");
24529
+ const hasValidWorkspace = workspaces.length > 0;
24370
24530
  if (!hasValidWorkspace && serverWorkspaces.length > 0) {
24371
24531
  syncWorkspacesToConfig(serverWorkspaces, profile);
24372
24532
  }
@@ -24467,7 +24627,7 @@ function statusCommand() {
24467
24627
  const cmd = new Command3("status").description("Show registration status").action((_opts, command) => {
24468
24628
  const profile = command.parent?.opts().profile;
24469
24629
  const cfg = loadCLIConfigForProfile(profile);
24470
- const ws = cfg.watched_workspaces?.[0];
24630
+ const ws = activeWorkspaces(cfg.watched_workspaces)[0];
24471
24631
  if (!ws?.token) {
24472
24632
  console.log("Not registered");
24473
24633
  console.log(`Run '${cmdPrefix()} register --token <token>' to register.`);
@@ -24692,7 +24852,7 @@ function resolveClientOptsPartial(command, opts = {}) {
24692
24852
  console.error("Error: no server URL configured. Set ALOOK_SERVER_URL or run register.");
24693
24853
  process.exit(1);
24694
24854
  }
24695
- const workspaces = cfg.watched_workspaces || [];
24855
+ const workspaces = activeWorkspaces(cfg.watched_workspaces);
24696
24856
  let ws;
24697
24857
  const envWorkspaceId = process.env.ALOOK_WORKSPACE_ID;
24698
24858
  if (opts.workspace) {
@@ -25748,9 +25908,6 @@ function syncCommand() {
25748
25908
  // commands/workspace.ts
25749
25909
  import { Command as Command13 } from "commander";
25750
25910
  import { readFileSync as readFileSync15 } from "fs";
25751
- function slugify3(name) {
25752
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
25753
- }
25754
25911
  function sleep4(ms) {
25755
25912
  return new Promise((resolve2) => setTimeout(resolve2, ms));
25756
25913
  }
@@ -25772,7 +25929,7 @@ async function resolveWorkspaceId(client, configName) {
25772
25929
  }
25773
25930
  const wsName = configName || "Personal";
25774
25931
  try {
25775
- const newWs = await client.postJSON("/api/workspaces", { name: wsName, slug: slugify3(wsName) });
25932
+ const newWs = await client.postJSON("/api/workspaces", { name: wsName, slug: sanitizeSlug(wsName) });
25776
25933
  console.log(`Created workspace: ${newWs.name} (${newWs.id})`);
25777
25934
  return { workspaceId: newWs.id, created: true };
25778
25935
  } catch (err) {
@@ -25844,15 +26001,11 @@ function workspaceCommand() {
25844
26001
  const res = await wsClient.postJSON("/api/studios", payload2);
25845
26002
  try {
25846
26003
  const freshCfg = loadCLIConfigForProfile(parentOpts.profile);
25847
- const watched = freshCfg.watched_workspaces || [];
25848
- const existing = watched.find((w) => w.id === targetWorkspaceId);
25849
- if (existing) {
25850
- existing.status = "active";
25851
- existing.name = res.workspace.name;
25852
- } else {
25853
- watched.push({ id: targetWorkspaceId, name: res.workspace.name, token, status: "active", agent_ids: [] });
25854
- }
25855
- freshCfg.watched_workspaces = watched;
26004
+ freshCfg.watched_workspaces = markWorkspaceActive(freshCfg.watched_workspaces || [], {
26005
+ id: targetWorkspaceId,
26006
+ name: res.workspace.name,
26007
+ token
26008
+ });
25856
26009
  saveCLIConfigForProfile(parentOpts.profile, freshCfg);
25857
26010
  } catch {}
25858
26011
  if (opts.json)
@@ -25898,7 +26051,7 @@ Workspace initialized: ${res.studio.name || res.workspace.name}`);
25898
26051
  if (agents.length > 0) {
25899
26052
  console.log("Current workspace has existing agents. Creating a new workspace...");
25900
26053
  const wsName = opts.name || config2.name || "New Workspace";
25901
- const newWs = await targetClient.postJSON("/api/workspaces", { name: wsName, slug: slugify3(wsName) });
26054
+ const newWs = await targetClient.postJSON("/api/workspaces", { name: wsName, slug: sanitizeSlug(wsName) });
25902
26055
  targetWorkspaceId = newWs.id;
25903
26056
  targetClient = new APIClient(serverUrl, token, targetWorkspaceId);
25904
26057
  console.log(`Created workspace: ${newWs.name} (${newWs.id})`);
@@ -37,6 +37,50 @@ function fillPool(bytes) {
37
37
  }
38
38
  poolOffset += bytes;
39
39
  }
40
+ function random(bytes) {
41
+ fillPool(bytes |= 0);
42
+ return pool.subarray(poolOffset - bytes, poolOffset);
43
+ }
44
+ function customRandom(alphabet, defaultSize, getRandom) {
45
+ let safeByteCutoff = 256 - 256 % alphabet.length;
46
+ if (safeByteCutoff === 256) {
47
+ let mask = alphabet.length - 1;
48
+ return (size = defaultSize) => {
49
+ if (!size)
50
+ return "";
51
+ let id = "";
52
+ while (true) {
53
+ let bytes = getRandom(size);
54
+ let i = size;
55
+ while (i--) {
56
+ id += alphabet[bytes[i] & mask];
57
+ if (id.length >= size)
58
+ return id;
59
+ }
60
+ }
61
+ };
62
+ }
63
+ let step = Math.ceil(1.6 * 256 * defaultSize / safeByteCutoff);
64
+ return (size = defaultSize) => {
65
+ if (!size)
66
+ return "";
67
+ let id = "";
68
+ while (true) {
69
+ let bytes = getRandom(step);
70
+ let i = step;
71
+ while (i--) {
72
+ if (bytes[i] < safeByteCutoff) {
73
+ id += alphabet[bytes[i] % alphabet.length];
74
+ if (id.length >= size)
75
+ return id;
76
+ }
77
+ }
78
+ }
79
+ };
80
+ }
81
+ function customAlphabet(alphabet, size = 21) {
82
+ return customRandom(alphabet, size, random);
83
+ }
40
84
  function nanoid3(size = 21) {
41
85
  fillPool(size |= 0);
42
86
  let id = "";
@@ -14411,6 +14455,13 @@ function date4(params) {
14411
14455
 
14412
14456
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
14413
14457
  config(en_default());
14458
+ // ../shared/src/utils/slug.ts
14459
+ init_nanoid();
14460
+ var slugId = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz");
14461
+ function sanitizeSlug(input) {
14462
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
14463
+ }
14464
+
14414
14465
  // ../shared/src/lib/community-name.ts
14415
14466
  var FORBIDDEN_NAME_CHARS = /[#@\x00-\x1f\x7f-\x9f]/;
14416
14467
  function validateCommunityName(name) {
@@ -14838,11 +14889,11 @@ var UpdateMemberRequestSchema = exports_external.object({
14838
14889
  });
14839
14890
  var CreateWorkspaceRequestSchema = exports_external.object({
14840
14891
  name: exports_external.string().min(1, "name is required"),
14841
- slug: exports_external.string().optional().default("")
14892
+ slug: exports_external.string().optional().default("").transform(sanitizeSlug)
14842
14893
  });
14843
14894
  var UpdateWorkspaceRequestSchema = exports_external.object({
14844
14895
  name: exports_external.string().min(1, "name is required").max(100).trim().optional(),
14845
- slug: exports_external.string().min(1, "slug is required").max(100).trim().toLowerCase().optional()
14896
+ slug: exports_external.string().min(1, "slug is required").trim().toLowerCase().transform(sanitizeSlug).optional()
14846
14897
  });
14847
14898
  var DeleteWorkspaceRequestSchema = exports_external.object({
14848
14899
  confirm_name: exports_external.string().min(1, "confirm_name is required")
@@ -15094,6 +15145,11 @@ var CommunityAgentChannelMemberRequestSchema = exports_external.object({
15094
15145
  var CommunityAgentJoinServerRequestSchema = exports_external.object({
15095
15146
  invite: exports_external.string().min(1)
15096
15147
  });
15148
+ var CommunityAgentReactAddRequestSchema = exports_external.object({
15149
+ channel: exports_external.string().min(1),
15150
+ seq: CommunityAgentPositiveSeqSchema,
15151
+ emoji: exports_external.string().min(1)
15152
+ });
15097
15153
  var AuditLogCliInvocationPayloadSchema = exports_external.object({
15098
15154
  subcommand: exports_external.string().min(1)
15099
15155
  });
@@ -15106,15 +15162,28 @@ var AuditLogThinkingPayloadSchema = exports_external.object({
15106
15162
  truncated: exports_external.boolean(),
15107
15163
  chars: exports_external.number().int().nonnegative()
15108
15164
  });
15165
+ var AuditLogWakeTriggerPayloadSchema = exports_external.object({
15166
+ messageId: exports_external.string().min(1),
15167
+ channel: exports_external.string().min(1),
15168
+ seq: CommunityAgentPositiveSeqSchema,
15169
+ senderId: exports_external.string().min(1),
15170
+ senderHandle: exports_external.string().min(1),
15171
+ reason: exports_external.enum(["unread", "mention"])
15172
+ });
15173
+ var AuditLogSessionResetPayloadSchema = exports_external.object({});
15109
15174
  var BotAuditEventSchema = exports_external.discriminatedUnion("kind", [
15110
15175
  exports_external.object({ kind: exports_external.literal("cli_invocation"), payload: AuditLogCliInvocationPayloadSchema }),
15111
15176
  exports_external.object({ kind: exports_external.literal("tool_call"), payload: AuditLogToolCallPayloadSchema }),
15112
- exports_external.object({ kind: exports_external.literal("thinking"), payload: AuditLogThinkingPayloadSchema })
15177
+ exports_external.object({ kind: exports_external.literal("thinking"), payload: AuditLogThinkingPayloadSchema }),
15178
+ exports_external.object({ kind: exports_external.literal("wake_trigger"), payload: AuditLogWakeTriggerPayloadSchema }),
15179
+ exports_external.object({ kind: exports_external.literal("session_reset"), payload: AuditLogSessionResetPayloadSchema })
15113
15180
  ]);
15114
15181
  var BotAuditEventKindSchema = exports_external.enum([
15115
15182
  "cli_invocation",
15116
15183
  "tool_call",
15117
- "thinking"
15184
+ "thinking",
15185
+ "wake_trigger",
15186
+ "session_reset"
15118
15187
  ]);
15119
15188
  var HostBotAuditEventFrameSchema = exports_external.object({
15120
15189
  type: exports_external.literal("bot_audit_event"),
@@ -17128,7 +17197,8 @@ var communityChannel = sqliteTable("community_channel", {
17128
17197
  }, (t) => [
17129
17198
  index("idx_channel_server_position").on(t.serverId, t.position),
17130
17199
  index("idx_channel_server_last_message").on(t.serverId, t.lastMessageAt),
17131
- index("idx_channel_parent").on(t.parentChannelId)
17200
+ index("idx_channel_parent").on(t.parentChannelId),
17201
+ uniqueIndex("idx_channel_server_name").on(t.serverId, t.name).where(sql`parent_channel_id IS NULL`)
17132
17202
  ]);
17133
17203
  var communityChannelMember = sqliteTable("community_channel_member", {
17134
17204
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
@@ -17196,7 +17266,8 @@ var communityServerMember = sqliteTable("community_server_member", {
17196
17266
  }, (t) => [
17197
17267
  unique("uq_server_member_server_user").on(t.serverId, t.userId),
17198
17268
  index("idx_server_member_user").on(t.userId),
17199
- index("idx_server_member_user_rail_order").on(t.userId, t.railOrder)
17269
+ index("idx_server_member_user_rail_order").on(t.userId, t.railOrder),
17270
+ index("idx_server_member_server_joined").on(t.serverId, t.joinedAt)
17200
17271
  ]);
17201
17272
  var communityServerFolder = sqliteTable("community_server_folder", {
17202
17273
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
@@ -17221,7 +17292,7 @@ var communityServerInvite = sqliteTable("community_server_invite", {
17221
17292
  uses: integer2("uses").default(0),
17222
17293
  expiresAt: text("expires_at"),
17223
17294
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
17224
- });
17295
+ }, (t) => [index("idx_server_invite_server").on(t.serverId)]);
17225
17296
  var communityFriendship = sqliteTable("community_friendship", {
17226
17297
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
17227
17298
  requesterId: text("requester_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -17446,6 +17517,7 @@ var listedMessageProjection = {
17446
17517
  replyToId: communityMessage.replyToId,
17447
17518
  embeds: communityMessage.embeds,
17448
17519
  flags: communityMessage.flags,
17520
+ seq: communityMessage.seq,
17449
17521
  createdAt: communityMessage.createdAt,
17450
17522
  channelId: communityMessage.channelId,
17451
17523
  dmConversationId: communityMessage.dmConversationId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.158",
3
+ "version": "0.0.160",
4
4
  "description": "Alook CLI — Enable Your Person Colleague",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",