openmates 0.27.0-alpha.29 → 0.27.0-alpha.30

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.
@@ -10054,6 +10054,24 @@ function getClientMessagesVersionForSync(cached) {
10054
10054
  const messagesVersion = typeof cached.details.messages_v === "number" ? cached.details.messages_v : 0;
10055
10055
  return Math.min(messagesVersion, cached.messages.length);
10056
10056
  }
10057
+ function mergeConcurrentChatMessages(cache, latest) {
10058
+ const byId = new Map(latest?.chats.map((chat) => [String(chat.details.id), chat]));
10059
+ const chatHashes = new Set(cache.chats.map((chat) => computeSHA256(String(chat.details.id))));
10060
+ const embedIds = new Set(cache.embeds.map((embed) => String(embed.embed_id ?? embed.id)));
10061
+ const addedEmbeds = (latest?.embeds ?? []).filter((embed) => !embedIds.has(String(embed.embed_id ?? embed.id)) && (!embed.hashed_chat_id || chatHashes.has(String(embed.hashed_chat_id))));
10062
+ const keyIds = new Set(cache.embedKeys.map((key) => String(key.id ?? key.encrypted_embed_key)));
10063
+ const embedHashes = new Set([...cache.embeds, ...addedEmbeds].map((embed) => computeSHA256(String(embed.embed_id ?? embed.id))));
10064
+ return {
10065
+ ...cache,
10066
+ ...latest && "tuiContinueCiphertext" in latest ? { tuiContinueCiphertext: latest.tuiContinueCiphertext } : {},
10067
+ embeds: [...cache.embeds, ...addedEmbeds],
10068
+ embedKeys: [...cache.embedKeys, ...(latest?.embedKeys ?? []).filter((key) => !keyIds.has(String(key.id ?? key.encrypted_embed_key)) && embedHashes.has(String(key.hashed_embed_id)))],
10069
+ chats: cache.chats.map((chat) => {
10070
+ const newer = byId.get(String(chat.details.id));
10071
+ return newer && newer.details.messages_v === chat.details.messages_v && newer.details.encrypted_chat_key === chat.details.encrypted_chat_key && newer.messages.length > chat.messages.length ? { ...chat, messages: newer.messages } : chat;
10072
+ })
10073
+ };
10074
+ }
10057
10075
  function findCliMessageBoundaryIndex(messages, messageId) {
10058
10076
  const index = messages.findIndex((message) => message.id === messageId || message.id.startsWith(messageId));
10059
10077
  if (index < 0) {
@@ -11894,7 +11912,7 @@ var OpenMatesClient = class _OpenMatesClient {
11894
11912
  if (!userId) {
11895
11913
  throw new Error("Could not resolve current user id for local connected-account connector.");
11896
11914
  }
11897
- const { createHash: createHash30 } = await import("crypto");
11915
+ const { createHash: createHash31 } = await import("crypto");
11898
11916
  const accountId = randomUUID9();
11899
11917
  const masterKey = this.getMasterKeyBytes();
11900
11918
  const encryptLocalConnectorValue = async (value) => {
@@ -11903,9 +11921,9 @@ var OpenMatesClient = class _OpenMatesClient {
11903
11921
  };
11904
11922
  const payload = {
11905
11923
  id: accountId,
11906
- hashed_user_id: createHash30("sha256").update(userId).digest("hex"),
11924
+ hashed_user_id: createHash31("sha256").update(userId).digest("hex"),
11907
11925
  encrypted_provider_type: await encryptLocalConnectorValue(input.provider_id),
11908
- provider_type_hash: createHash30("sha256").update(input.provider_id).digest("hex"),
11926
+ provider_type_hash: createHash31("sha256").update(input.provider_id).digest("hex"),
11909
11927
  encrypted_account_label: await encryptLocalConnectorValue(input.label),
11910
11928
  encrypted_capabilities: await encryptLocalConnectorValue(input.capabilities),
11911
11929
  encrypted_app_permissions: await encryptLocalConnectorValue({
@@ -12783,7 +12801,7 @@ var OpenMatesClient = class _OpenMatesClient {
12783
12801
  const cache = await this.ensureSynced(options.forceRefresh === true, [], options, false, options.onSyncedChats ? async (synced) => {
12784
12802
  const syncedPage = await this.decryptChatListPage(synced, limit, page, options, teamId, masterKey);
12785
12803
  options.signal?.throwIfAborted();
12786
- saveSyncCache({ ...synced, syncedAt: 0 }, teamId);
12804
+ saveSyncCache(mergeConcurrentChatMessages({ ...synced, syncedAt: 0 }, loadSyncCache(teamId)), teamId);
12787
12805
  options.onSyncedChats?.(syncedPage);
12788
12806
  } : void 0, options.signal);
12789
12807
  return this.decryptChatListPage(cache, limit, page, options, teamId, masterKey);
@@ -13148,6 +13166,15 @@ var OpenMatesClient = class _OpenMatesClient {
13148
13166
  }
13149
13167
  return drafts;
13150
13168
  }
13169
+ async getCachedDraft(chatId, options = {}) {
13170
+ const teamId = this.resolveTeamContext(options);
13171
+ const masterKey = Buffer.from(this.getMasterKeyBytes());
13172
+ if (teamId && !loadLocalTeamKey(this.requireSession().hashedEmail, teamId)) return null;
13173
+ const chat = loadSyncCache(teamId)?.chats.find((entry) => String(entry.details.id ?? "") === chatId);
13174
+ const draft = chat ? await this.decryptCachedDraft(chat) : null;
13175
+ if (!this.hasSession() || teamId !== this.resolveTeamContext(options) || !masterKey.equals(Buffer.from(this.getMasterKeyBytes()))) throw new Error("Chat workspace changed");
13176
+ return draft;
13177
+ }
13151
13178
  async getDraft(chatId, forceRefresh = false) {
13152
13179
  if (forceRefresh) {
13153
13180
  let response;
@@ -13479,24 +13506,71 @@ var OpenMatesClient = class _OpenMatesClient {
13479
13506
  /** Get decrypted messages for a chat resolved by UUID, prefix or title. */
13480
13507
  async getChatMessages(query, options = {}) {
13481
13508
  const teamId = this.resolveTeamContext(options);
13482
- let cache = await this.ensureSynced(true, [], options);
13483
- const masterKey = this.getMasterKeyBytes();
13484
- const wrappingKey = await this.getChatWrappingKey(teamId, masterKey);
13485
- let found = await this.resolveCachedChatForQuery(query, cache, wrappingKey, teamId);
13509
+ const masterKey = Buffer.from(this.getMasterKeyBytes());
13510
+ const localCache = options.preferCache ? loadSyncCache(teamId) : null;
13511
+ let cache = localCache ?? (options.preferCache ? {
13512
+ syncedAt: 0,
13513
+ totalChatCount: 0,
13514
+ loadedChatCount: 0,
13515
+ chats: [],
13516
+ embeds: [],
13517
+ embedKeys: []
13518
+ } : await this.ensureSynced(true, [], options));
13519
+ const current = () => {
13520
+ if (!this.hasSession() || teamId !== this.resolveTeamContext(options) || !masterKey.equals(Buffer.from(this.getMasterKeyBytes()))) throw new Error("Chat workspace changed");
13521
+ };
13522
+ const localTeamKey = options.preferCache && teamId ? loadLocalTeamKey(this.requireSession().hashedEmail, teamId) : null;
13523
+ if (options.preferCache && teamId && !localTeamKey) throw new Error("Team key unavailable offline. Reconnect to refresh Team access.");
13524
+ const wrappingKey = localTeamKey ? base64ToBytes(localTeamKey) : await this.getChatWrappingKey(teamId, masterKey);
13525
+ let found;
13526
+ let fetchedMetadata = false;
13527
+ try {
13528
+ found = await this.resolveCachedChatForQuery(query, cache, wrappingKey, teamId);
13529
+ } catch (error) {
13530
+ if (!options.preferCache || !(error instanceof Error) || !/^Chat '.*' not found\./.test(error.message)) throw error;
13531
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(query)) throw error;
13532
+ const response = await this.http.post(
13533
+ this.appendTeamQuery("/v1/chats/metadata/batch", options),
13534
+ { chat_ids: [query] },
13535
+ this.getCliRequestHeaders()
13536
+ );
13537
+ current();
13538
+ if (!response.ok) throw new Error(`Chat metadata failed with HTTP ${response.status}`);
13539
+ const details = response.data.chats.find((chat) => String(chat.id ?? "") === query);
13540
+ if (!details) throw error;
13541
+ found = { details, messages: [] };
13542
+ fetchedMetadata = true;
13543
+ cache = { ...cache, chats: [...cache.chats, found], chatKeyWrappers: [
13544
+ ...cache.chatKeyWrappers ?? [],
13545
+ ...Array.isArray(details.chat_key_wrappers) ? details.chat_key_wrappers : []
13546
+ ] };
13547
+ }
13486
13548
  const foundChatId = String(found.details.id ?? "");
13487
- if (foundChatId && getClientMessagesVersionForSync(found) === 0) {
13549
+ if (!options.preferCache && foundChatId && getClientMessagesVersionForSync(found) === 0) {
13488
13550
  cache = await this.ensureSynced(true, [foundChatId], options, true);
13489
13551
  found = cache.chats.find((c) => String(c.details.id ?? "") === foundChatId) ?? found;
13490
13552
  }
13491
13553
  const chatItem = await this.decryptChatListItem(found, wrappingKey, cache, teamId);
13492
13554
  const chatKeyBytes = await this.resolveChatKey(cache, found, wrappingKey, teamId);
13555
+ if (!chatKeyBytes && (found.details.encrypted_chat_key || found.messages.length || Number(found.details.messages_v) > 0)) throw new Error("Chat key unavailable. Use /refresh to retry.");
13556
+ const persistMessages = (messages2) => {
13557
+ current();
13558
+ options.signal?.throwIfAborted();
13559
+ const latest = loadSyncCache(teamId) ?? cache;
13560
+ const row = latest.chats.find((chat) => String(chat.details.id) === foundChatId);
13561
+ if (row && (row.details.messages_v !== found.details.messages_v || row.details.encrypted_chat_key !== found.details.encrypted_chat_key)) return;
13562
+ if (!row && localCache?.chats.some((chat) => String(chat.details.id) === foundChatId)) return;
13563
+ const merged = { ...latest, chats: row ? latest.chats.map((chat) => chat === row ? { ...row, messages: messages2 } : chat) : [...latest.chats, { ...found, messages: messages2 }] };
13564
+ saveSyncCache(mergeConcurrentChatMessages(merged, latest), teamId);
13565
+ };
13493
13566
  let rawMessages = found.messages;
13567
+ let historyIncomplete = rawMessages.length > 0 && Number(found.details.messages_v) > rawMessages.length;
13494
13568
  const hasServerMessages = typeof found.details.messages_v === "number" && found.details.messages_v > 0;
13495
- if (rawMessages.length === 0 && hasServerMessages && foundChatId) {
13569
+ if (rawMessages.length === 0 && (hasServerMessages || fetchedMetadata) && foundChatId) {
13496
13570
  const pages = [];
13497
13571
  let cursor = null;
13498
13572
  let hasMoreBefore = true;
13499
- for (let page = 0; page < CLI_MESSAGE_HISTORY_MAX_PAGES && hasMoreBefore; page += 1) {
13573
+ for (let page = 0; page < Math.max(1, Math.min(CLI_MESSAGE_HISTORY_MAX_PAGES, options.maxHistoryPages ?? CLI_MESSAGE_HISTORY_MAX_PAGES)) && hasMoreBefore; page += 1) {
13500
13574
  const queryParams = new URLSearchParams({
13501
13575
  direction: cursor ? "before" : "latest",
13502
13576
  limit: String(CLI_MESSAGE_HISTORY_PAGE_LIMIT),
@@ -13506,21 +13580,58 @@ var OpenMatesClient = class _OpenMatesClient {
13506
13580
  queryParams.set("before_timestamp", String(cursor.created_at));
13507
13581
  queryParams.set("before_message_id", cursor.message_id);
13508
13582
  }
13509
- const response = await this.http.get(
13510
- this.appendTeamQuery(`/v1/chats/${encodeURIComponent(foundChatId)}/messages/window?${queryParams.toString()}`, { teamId }),
13511
- this.getCliRequestHeaders()
13512
- );
13583
+ options.signal?.throwIfAborted();
13584
+ let response;
13585
+ try {
13586
+ response = await this.http.get(
13587
+ this.appendTeamQuery(`/v1/chats/${encodeURIComponent(foundChatId)}/messages/window?${queryParams.toString()}`, { teamId }),
13588
+ this.getCliRequestHeaders()
13589
+ );
13590
+ } catch (error) {
13591
+ if (!options.preferCache || !pages.length) throw error;
13592
+ historyIncomplete = true;
13593
+ break;
13594
+ }
13595
+ current();
13596
+ options.signal?.throwIfAborted();
13513
13597
  if (!response.ok) throw new Error(`Chat messages failed with HTTP ${response.status}`);
13514
13598
  const pageMessages = Array.isArray(response.data.messages) ? normalizeSyncedMessages(response.data.messages) : [];
13515
13599
  if (pageMessages.length === 0) break;
13600
+ if (page === 0 && options.onMessages) {
13601
+ const latest = await this.decryptRawChatMessages(pageMessages, chatItem, chatKeyBytes, cache.embeds);
13602
+ current();
13603
+ if (options.preferCache) persistMessages(pageMessages);
13604
+ options.onMessages({ chat: chatItem, messages: latest });
13605
+ }
13516
13606
  pages.unshift(pageMessages);
13517
13607
  cursor = response.data.start_cursor ?? null;
13518
13608
  hasMoreBefore = response.data.has_more_before === true && cursor !== null;
13519
13609
  }
13520
13610
  rawMessages = pages.flat();
13611
+ historyIncomplete ||= hasMoreBefore;
13521
13612
  }
13522
13613
  const messages = await this.decryptRawChatMessages(rawMessages, chatItem, chatKeyBytes, cache.embeds);
13523
- return { chat: chatItem, messages };
13614
+ current();
13615
+ if (options.preferCache && rawMessages !== found.messages) persistMessages(rawMessages);
13616
+ return { chat: chatItem, messages, ...historyIncomplete ? { historyIncomplete: true } : {} };
13617
+ }
13618
+ /** Warm the latest recent windows in the background; only ciphertext reaches disk. */
13619
+ async cacheRecentChatMessages(options = {}) {
13620
+ const teamId = this.resolveTeamContext(options), masterKey = Buffer.from(this.getMasterKeyBytes());
13621
+ const recent2 = (loadSyncCache(teamId)?.chats ?? []).sort((a, b) => Number(b.details.last_edited_overall_timestamp ?? 0) - Number(a.details.last_edited_overall_timestamp ?? 0)).slice(0, 20).filter((chat) => Number(chat.details.messages_v) > 0 && chat.messages.length === 0);
13622
+ let next = 0;
13623
+ await Promise.all(Array.from({ length: Math.min(3, recent2.length) }, async () => {
13624
+ while (next < recent2.length) {
13625
+ options.signal?.throwIfAborted();
13626
+ if (!this.hasSession() || teamId !== this.resolveTeamContext(options) || !masterKey.equals(Buffer.from(this.getMasterKeyBytes()))) throw new Error("Chat workspace changed");
13627
+ const chat = recent2[next++];
13628
+ try {
13629
+ await this.getChatMessages(String(chat.details.id), { ...options, preferCache: true, maxHistoryPages: 1 });
13630
+ } catch {
13631
+ options.signal?.throwIfAborted();
13632
+ }
13633
+ }
13634
+ }));
13524
13635
  }
13525
13636
  async generateExistingAssistantSpeech(query, selection, options = {}) {
13526
13637
  const { chat, messages } = await this.getChatMessages(query, options);
@@ -13793,23 +13904,81 @@ var OpenMatesClient = class _OpenMatesClient {
13793
13904
  *
13794
13905
  * hashed_embed_id in the key table = SHA-256(embed.embed_id).
13795
13906
  */
13796
- async getEmbed(embedIdOrShort) {
13797
- const cache = await this.ensureSynced();
13798
- const masterKey = this.getMasterKeyBytes();
13799
- const embed = cache.embeds.find(
13907
+ async getEmbed(embedIdOrShort, options = {}) {
13908
+ const teamId = this.resolveTeamContext(options), masterKey = Buffer.from(this.getMasterKeyBytes());
13909
+ const current = () => {
13910
+ if (!this.hasSession() || teamId !== this.resolveTeamContext(options) || !masterKey.equals(Buffer.from(this.getMasterKeyBytes()))) throw new Error("Embed workspace changed");
13911
+ };
13912
+ let cache = options.preferCache ? loadSyncCache(teamId) : await this.ensureSynced(false, [], options);
13913
+ cache ??= { syncedAt: 0, totalChatCount: 0, loadedChatCount: 0, chats: [], embeds: [], embedKeys: [] };
13914
+ const localTeamKey = options.preferCache && teamId ? loadLocalTeamKey(this.requireSession().hashedEmail, teamId) : null;
13915
+ if (options.preferCache && teamId && !localTeamKey) throw new Error("Team key unavailable offline.");
13916
+ const wrappingKey = localTeamKey ? base64ToBytes(localTeamKey) : await this.getChatWrappingKey(teamId, masterKey);
13917
+ let embed = cache.embeds.find(
13800
13918
  (e) => String(e.embed_id ?? "").startsWith(embedIdOrShort) || String(e.id ?? "").startsWith(embedIdOrShort)
13801
13919
  );
13920
+ let fetched = false;
13921
+ const fetchedIds = /* @__PURE__ */ new Set(), fetchedKeyIds = /* @__PURE__ */ new Set();
13922
+ const wrapperId = (key) => `${key.hashed_embed_id}:${key.key_type}:${key.hashed_chat_id ?? ""}`;
13923
+ const targetId = embed ? String(embed.embed_id ?? embed.id) : embedIdOrShort;
13924
+ const cachedKey = options.preferCache && embed ? await this.resolveEmbedKey(cache, wrappingKey, embed, targetId, computeSHA256(targetId)) : null;
13925
+ if ((!embed || !cachedKey) && options.preferCache && /^[0-9a-f-]{36}$/i.test(targetId)) {
13926
+ if (teamId && !options.chatId) throw new Error("Open the original Team chat to load this embed.");
13927
+ const { ws } = await this.openWsClient({ taskUpdateJobs: false });
13928
+ try {
13929
+ let wanted = targetId;
13930
+ const visited = /* @__PURE__ */ new Set();
13931
+ while (wanted && !visited.has(wanted) && visited.size < 8) {
13932
+ visited.add(wanted);
13933
+ const pending = ws.waitForMessage("send_embed_data", (payload) => {
13934
+ const row2 = payload, nested = row2.payload ?? row2;
13935
+ return nested.embed_id === wanted;
13936
+ }, 1e4);
13937
+ await ws.sendAsync("request_embed", { embed_id: wanted, ...teamId ? { team_id: teamId, chat_id: options.chatId } : {} });
13938
+ const envelope = (await pending).payload, row = envelope.payload ?? envelope;
13939
+ current();
13940
+ if (row.already_encrypted !== true || typeof row.content !== "string") throw new Error("Saved embed ciphertext is unavailable. Open its original chat and /refresh.");
13941
+ const encrypted = {
13942
+ embed_id: wanted,
13943
+ encrypted_type: row.type,
13944
+ encrypted_content: row.content,
13945
+ encrypted_text_preview: row.encrypted_text_preview,
13946
+ created_at: row.createdAt,
13947
+ updated_at: row.updatedAt,
13948
+ hashed_chat_id: typeof row.chat_id === "string" ? /^[0-9a-f]{64}$/i.test(row.chat_id) ? row.chat_id : computeSHA256(row.chat_id) : void 0,
13949
+ parent_embed_id: row.parent_embed_id,
13950
+ status: row.status
13951
+ };
13952
+ const wrappers = Array.isArray(row.embed_keys) ? row.embed_keys : [];
13953
+ fetchedIds.add(wanted);
13954
+ for (const wrapper of wrappers) fetchedKeyIds.add(wrapperId(wrapper));
13955
+ cache = {
13956
+ ...cache,
13957
+ embeds: [...cache.embeds.filter((e) => String(e.embed_id ?? e.id) !== wanted), encrypted],
13958
+ embedKeys: [...cache.embedKeys.filter((key) => !wrappers.some((wrapper) => wrapperId(wrapper) === wrapperId(key))), ...wrappers]
13959
+ };
13960
+ fetched = true;
13961
+ const parent = typeof row.parent_embed_id === "string" ? row.parent_embed_id : "";
13962
+ const parentEmbed2 = cache.embeds.find((e) => String(e.embed_id ?? e.id) === parent);
13963
+ const parentKey = parentEmbed2 ? await this.resolveEmbedKey(cache, wrappingKey, parentEmbed2, parent, computeSHA256(parent)) : null;
13964
+ wanted = parent && !parentKey ? parent : "";
13965
+ }
13966
+ embed = cache.embeds.find((e) => String(e.embed_id ?? e.id) === targetId);
13967
+ } finally {
13968
+ ws.close();
13969
+ }
13970
+ }
13802
13971
  if (!embed) {
13803
13972
  throw new Error(
13804
13973
  `Embed '${embedIdOrShort}' not found in local cache. Run 'openmates chats list' to sync first.`
13805
13974
  );
13806
13975
  }
13807
13976
  const embedId = String(embed.embed_id ?? embed.id ?? "");
13808
- const { createHash: createHash30 } = await import("crypto");
13809
- const hashedEmbedId = createHash30("sha256").update(embedId).digest("hex");
13977
+ const { createHash: createHash31 } = await import("crypto");
13978
+ const hashedEmbedId = createHash31("sha256").update(embedId).digest("hex");
13810
13979
  const embedKeyBytes = await this.resolveEmbedKey(
13811
13980
  cache,
13812
- masterKey,
13981
+ wrappingKey,
13813
13982
  embed,
13814
13983
  embedId,
13815
13984
  hashedEmbedId
@@ -13824,7 +13993,19 @@ var OpenMatesClient = class _OpenMatesClient {
13824
13993
  let content = null;
13825
13994
  const rawContent = await decryptField2("encrypted_content");
13826
13995
  if (rawContent) content = parseEmbedContentObject(rawContent);
13827
- content = await this.refreshRemotionVideoCreateContent(embedId, content);
13996
+ if (options.preferCache && (!embedKeyBytes || rawContent === null)) throw new Error("Saved embed key or content is unavailable. Use /refresh to retry.");
13997
+ if (!options.preferCache) content = await this.refreshRemotionVideoCreateContent(embedId, content);
13998
+ if (options.preferCache) {
13999
+ current();
14000
+ if (fetched) {
14001
+ const latest = loadSyncCache(teamId) ?? cache;
14002
+ saveSyncCache({
14003
+ ...latest,
14004
+ embeds: [...latest.embeds.filter((e) => !fetchedIds.has(String(e.embed_id ?? e.id))), ...cache.embeds.filter((e) => fetchedIds.has(String(e.embed_id ?? e.id)))],
14005
+ embedKeys: [...latest.embedKeys.filter((key) => !fetchedKeyIds.has(wrapperId(key))), ...cache.embedKeys.filter((key) => fetchedKeyIds.has(wrapperId(key)))]
14006
+ }, teamId);
14007
+ }
14008
+ }
13828
14009
  const strVal = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
13829
14010
  const resolvedType = type ?? strVal(content?.type) ?? null;
13830
14011
  const resolvedAppId = typeof embed.app_id === "string" ? embed.app_id : strVal(content?.app_id) ?? null;
@@ -13908,7 +14089,7 @@ var OpenMatesClient = class _OpenMatesClient {
13908
14089
  async resolveEmbedKey(cache, masterKey, embed, embedId, hashedEmbedId, visited = /* @__PURE__ */ new Set()) {
13909
14090
  if (visited.has(embedId)) return null;
13910
14091
  visited.add(embedId);
13911
- const { createHash: createHash30 } = await import("crypto");
14092
+ const { createHash: createHash31 } = await import("crypto");
13912
14093
  const masterKeyEntry = cache.embedKeys.find(
13913
14094
  (ek) => ek.hashed_embed_id === hashedEmbedId && String(ek.key_type) === "master"
13914
14095
  );
@@ -13925,23 +14106,14 @@ var OpenMatesClient = class _OpenMatesClient {
13925
14106
  if (chatKeyEntry && typeof chatKeyEntry.encrypted_embed_key === "string") {
13926
14107
  const hashedChatId = String(chatKeyEntry.hashed_chat_id ?? "");
13927
14108
  const owningChat = cache.chats.find((c) => {
13928
- const chatHash = createHash30("sha256").update(String(c.details.id ?? "")).digest("hex");
14109
+ const chatHash = createHash31("sha256").update(String(c.details.id ?? "")).digest("hex");
13929
14110
  return chatHash === hashedChatId;
13930
14111
  });
13931
14112
  if (owningChat) {
13932
- const encChatKey = typeof owningChat.details.encrypted_chat_key === "string" ? owningChat.details.encrypted_chat_key : null;
13933
- if (encChatKey) {
13934
- const chatKeyBytes = await decryptBytesWithAesGcm(
13935
- encChatKey,
13936
- masterKey
13937
- );
13938
- if (chatKeyBytes) {
13939
- const key = await decryptBytesWithAesGcm(
13940
- chatKeyEntry.encrypted_embed_key,
13941
- chatKeyBytes
13942
- );
13943
- if (key) return key;
13944
- }
14113
+ const chatKeyBytes = await this.resolveChatKey(cache, owningChat, masterKey, this.resolveTeamContext());
14114
+ if (chatKeyBytes) {
14115
+ const key = await decryptBytesWithAesGcm(chatKeyEntry.encrypted_embed_key, chatKeyBytes);
14116
+ if (key) return key;
13945
14117
  }
13946
14118
  }
13947
14119
  }
@@ -13954,7 +14126,7 @@ var OpenMatesClient = class _OpenMatesClient {
13954
14126
  const parentFullId = String(
13955
14127
  parentEmbed2.embed_id ?? parentEmbed2.id ?? ""
13956
14128
  );
13957
- const parentHashedId = createHash30("sha256").update(parentFullId).digest("hex");
14129
+ const parentHashedId = createHash31("sha256").update(parentFullId).digest("hex");
13958
14130
  const parentKey = await this.resolveEmbedKey(
13959
14131
  cache,
13960
14132
  masterKey,
@@ -19324,6 +19496,35 @@ var OpenMatesClient = class _OpenMatesClient {
19324
19496
  );
19325
19497
  return legacy.data?.app_settings_memories ?? [];
19326
19498
  }
19499
+ async getCachedContinueItems() {
19500
+ const teamId = this.resolveTeamContext(), masterKey = Buffer.from(this.getMasterKeyBytes());
19501
+ const teamKey = teamId ? loadLocalTeamKey(this.requireSession().hashedEmail, teamId) : null;
19502
+ if (teamId && !teamKey) return null;
19503
+ const cache = loadSyncCache(teamId);
19504
+ if (!cache?.tuiContinueCiphertext) return null;
19505
+ const plain = await decryptWithAesGcmCombined(cache.tuiContinueCiphertext, teamKey ? base64ToBytes(teamKey) : masterKey);
19506
+ if (!this.hasSession() || this.resolveTeamContext() !== teamId || !masterKey.equals(Buffer.from(this.getMasterKeyBytes()))) throw new Error("Home workspace changed");
19507
+ if (!plain) return null;
19508
+ const data = JSON.parse(plain);
19509
+ return Array.isArray(data.memories) && Array.isArray(data.reminders) ? data : null;
19510
+ }
19511
+ async getContinueItems() {
19512
+ const teamId = this.resolveTeamContext(), masterKey = Buffer.from(this.getMasterKeyBytes());
19513
+ const [memories, response] = await Promise.all([teamId ? Promise.resolve([]) : this.listMemories({ personal: true }), this.settingsGet("/v1/settings/reminders?include_recent_fired=true&upcoming_hours=24&recent_hours=12")]);
19514
+ const reminderData = response;
19515
+ const teamChats = new Set(loadSyncCache(teamId)?.chats.map((chat) => String(chat.details.id)) ?? []);
19516
+ const reminders = reminderData.success && Array.isArray(reminderData.reminders) ? reminderData.reminders : [];
19517
+ const data = {
19518
+ memories: memories.filter((memory) => typeof memory.data.embed_id === "string"),
19519
+ reminders: teamId ? reminders.filter((reminder) => teamChats.has(String(reminder.target_chat_id))) : reminders
19520
+ };
19521
+ const wrappingKey = await this.getChatWrappingKey(teamId, masterKey);
19522
+ const tuiContinueCiphertext = await encryptWithAesGcmCombined(JSON.stringify(data), wrappingKey);
19523
+ if (!this.hasSession() || this.resolveTeamContext() !== teamId || !masterKey.equals(Buffer.from(this.getMasterKeyBytes()))) throw new Error("Home workspace changed");
19524
+ const cache = loadSyncCache(teamId) ?? { syncedAt: 0, totalChatCount: 0, loadedChatCount: 0, chats: [], embeds: [], embedKeys: [] };
19525
+ saveSyncCache({ ...cache, tuiContinueCiphertext }, teamId);
19526
+ return data;
19527
+ }
19327
19528
  async listMemories(options = {}) {
19328
19529
  const masterKey = this.getMasterKeyBytes();
19329
19530
  const teamId = this.resolveTeamContext(options);
@@ -19674,7 +19875,7 @@ Required: ${schema.required.join(", ")}`
19674
19875
  const session = this.requireSession();
19675
19876
  const masterKey = base64ToBytes(session.masterKeyExportedB64);
19676
19877
  const cache = await this.ensureSynced();
19677
- const { createHash: createHash30 } = await import("crypto");
19878
+ const { createHash: createHash31 } = await import("crypto");
19678
19879
  const embed = cache.embeds.find(
19679
19880
  (e) => String(e.embed_id ?? "").startsWith(embedIdOrShort) || String(e.id ?? "").startsWith(embedIdOrShort)
19680
19881
  );
@@ -19682,7 +19883,7 @@ Required: ${schema.required.join(", ")}`
19682
19883
  throw new Error(`Embed '${embedIdOrShort}' not found in local cache.`);
19683
19884
  }
19684
19885
  const embedId = String(embed.embed_id ?? embed.id ?? "");
19685
- const hashedEmbedId = createHash30("sha256").update(embedId).digest("hex");
19886
+ const hashedEmbedId = createHash31("sha256").update(embedId).digest("hex");
19686
19887
  const embedKeyBytes = await this.resolveEmbedKey(
19687
19888
  cache,
19688
19889
  masterKey,
@@ -19849,8 +20050,8 @@ Required: ${schema.required.join(", ")}`
19849
20050
  if (!embed) {
19850
20051
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
19851
20052
  }
19852
- const { createHash: createHash30 } = await import("crypto");
19853
- const hashedEmbedId = createHash30("sha256").update(embedId).digest("hex");
20053
+ const { createHash: createHash31 } = await import("crypto");
20054
+ const hashedEmbedId = createHash31("sha256").update(embedId).digest("hex");
19854
20055
  const embedKey = await this.resolveEmbedKey(
19855
20056
  cache,
19856
20057
  masterKey,
@@ -19981,8 +20182,8 @@ Required: ${schema.required.join(", ")}`
19981
20182
  if (!embed) {
19982
20183
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
19983
20184
  }
19984
- const { createHash: createHash30 } = await import("crypto");
19985
- const hashedEmbedId = createHash30("sha256").update(embedId).digest("hex");
20185
+ const { createHash: createHash31 } = await import("crypto");
20186
+ const hashedEmbedId = createHash31("sha256").update(embedId).digest("hex");
19986
20187
  const embedKey = await this.resolveEmbedKey(
19987
20188
  cache,
19988
20189
  masterKey,
@@ -20308,6 +20509,7 @@ Required: ${schema.required.join(", ")}`
20308
20509
  async ensureSynced(forceRefresh = false, refreshChatIds = [], options = {}, includeMessageContentRefresh = false, onSyncedCache, signal) {
20309
20510
  signal?.throwIfAborted();
20310
20511
  const teamId = this.resolveTeamContext(options);
20512
+ const syncMasterKey = Buffer.from(this.getMasterKeyBytes());
20311
20513
  const refreshChatIdSet = new Set(refreshChatIds.filter(Boolean));
20312
20514
  if (!forceRefresh && refreshChatIdSet.size === 0 && isSyncCacheFresh(3e5, teamId)) {
20313
20515
  const cache2 = loadSyncCache(teamId);
@@ -20509,9 +20711,9 @@ Required: ${schema.required.join(", ")}`
20509
20711
  chats.splice(0, chats.length, ...reconciledChats);
20510
20712
  if (totalChatCount === 0) totalChatCount = chats.length;
20511
20713
  if (reconciliation.deleted_chat_ids?.length) {
20512
- const { createHash: createHash30 } = await import("crypto");
20714
+ const { createHash: createHash31 } = await import("crypto");
20513
20715
  const deletedHashes = new Set(
20514
- reconciliation.deleted_chat_ids.map((id) => createHash30("sha256").update(id).digest("hex"))
20716
+ reconciliation.deleted_chat_ids.map((id) => createHash31("sha256").update(id).digest("hex"))
20515
20717
  );
20516
20718
  const keptEmbeds = embeds.filter((embed) => !deletedHashes.has(String(embed.hashed_chat_id ?? "")));
20517
20719
  const keptKeys = embedKeys.filter((key) => !deletedHashes.has(String(key.hashed_chat_id ?? "")));
@@ -20600,8 +20802,10 @@ Required: ${schema.required.join(", ")}`
20600
20802
  chatKeyWrappers,
20601
20803
  newChatSuggestions
20602
20804
  };
20603
- saveSyncCache(cache, teamId);
20604
- return cache;
20805
+ if (!this.hasSession() || teamId !== this.resolveTeamContext(options) || !syncMasterKey.equals(Buffer.from(this.getMasterKeyBytes()))) throw new Error("Chat workspace changed");
20806
+ const mergedCache = mergeConcurrentChatMessages(cache, loadSyncCache(teamId));
20807
+ saveSyncCache(mergedCache, teamId);
20808
+ return mergedCache;
20605
20809
  }
20606
20810
  /**
20607
20811
  * Claim and finish reconnect workflow deliveries using only normal client
@@ -31860,7 +32064,7 @@ async function flushPendingTaskActivities(client2, teamId, stateDir2 = resolveSt
31860
32064
  import { existsSync as existsSync24, readFileSync as readFileSync22, realpathSync as realpathSync12, readdirSync as readdirSync13, statSync as statSync11, writeFileSync as writeFileSync15 } from "fs";
31861
32065
  import { fileURLToPath as fileURLToPath4 } from "url";
31862
32066
  import { basename as basename9, dirname as dirname17, join as join28 } from "path";
31863
- import { createHash as createHash29, randomBytes as randomBytes13, randomUUID as randomUUID23 } from "crypto";
32067
+ import { createHash as createHash30, randomBytes as randomBytes13, randomUUID as randomUUID23 } from "crypto";
31864
32068
  import { arch as arch2, platform as platform2 } from "os";
31865
32069
  import { parse as parseYaml, parseDocument as parseYamlDocument, stringify as stringifyYaml } from "yaml";
31866
32070
 
@@ -32972,6 +33176,120 @@ function prepareUrlEmbeds(message) {
32972
33176
 
32973
33177
  // src/embedRenderers.ts
32974
33178
  import qrcode2 from "qrcode-terminal";
33179
+
33180
+ // src/tuiText.ts
33181
+ var segments = new Intl.Segmenter(void 0, { granularity: "grapheme" });
33182
+ function stripAnsi(value) {
33183
+ return value.replace(/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\))/g, "");
33184
+ }
33185
+ function terminalText(value) {
33186
+ return stripAnsi(value).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "").replace(/\t/g, " ");
33187
+ }
33188
+ function graphemeWidth(value) {
33189
+ if (/^[\p{Mark}\p{Cf}]+$/u.test(value)) return 0;
33190
+ const cp = value.codePointAt(0) ?? 0;
33191
+ if (/\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20e3/u.test(value)) return 2;
33192
+ return cp >= 4352 && (cp <= 4447 || cp === 9001 || cp === 9002 || cp >= 11904 && cp <= 42191 && cp !== 12351 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65040 && cp <= 65049 || cp >= 65072 && cp <= 65135 || cp >= 65281 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 131072 && cp <= 262141) ? 2 : 1;
33193
+ }
33194
+ function cells(value) {
33195
+ return [...segments.segment(stripAnsi(value))].reduce((n, item) => n + graphemeWidth(item.segment), 0);
33196
+ }
33197
+ function truncateCells(value, width, ellipsis = "\u2026") {
33198
+ const clean = terminalText(value).replace(/\n/g, " ");
33199
+ if (cells(clean) <= width) return clean;
33200
+ const available = Math.max(0, width - cells(ellipsis));
33201
+ let result = "", used = 0;
33202
+ for (const { segment } of segments.segment(clean)) {
33203
+ const size = graphemeWidth(segment);
33204
+ if (used + size > available) break;
33205
+ result += segment;
33206
+ used += size;
33207
+ }
33208
+ return width > 0 ? result + (cells(ellipsis) <= width ? ellipsis : "") : "";
33209
+ }
33210
+ function padCells(value, width) {
33211
+ const trimmed = truncateCells(value, width);
33212
+ return trimmed + " ".repeat(Math.max(0, width - cells(trimmed)));
33213
+ }
33214
+ function sliceCells(value, start, width) {
33215
+ start = Math.max(0, Math.floor(start));
33216
+ width = Math.max(0, Math.floor(width));
33217
+ const end = start + width;
33218
+ let position = 0, result = "";
33219
+ for (const { segment } of segments.segment(terminalText(value).replace(/\n/g, " "))) {
33220
+ const size = graphemeWidth(segment), next = position + size;
33221
+ if (next > start && position < end) {
33222
+ result += position >= start && next <= end ? segment : " ".repeat(Math.min(end, next) - Math.max(start, position));
33223
+ }
33224
+ position = next;
33225
+ if (position >= end) break;
33226
+ }
33227
+ return result + " ".repeat(Math.max(0, width - cells(result)));
33228
+ }
33229
+ function wrapCells(value, width) {
33230
+ width = Math.max(1, width);
33231
+ const result = [];
33232
+ for (const line of terminalText(value).split("\n")) {
33233
+ let text7 = "", used = 0;
33234
+ for (const { segment } of segments.segment(line)) {
33235
+ const size = graphemeWidth(segment);
33236
+ if (size > width) {
33237
+ if (text7) result.push(text7);
33238
+ result.push("?");
33239
+ text7 = "";
33240
+ used = 0;
33241
+ continue;
33242
+ }
33243
+ if (used + size > width) {
33244
+ result.push(text7);
33245
+ text7 = "";
33246
+ used = 0;
33247
+ }
33248
+ text7 += segment;
33249
+ used += size;
33250
+ }
33251
+ result.push(text7);
33252
+ }
33253
+ return result;
33254
+ }
33255
+ function eraseGrapheme(value) {
33256
+ const items = [...segments.segment(value)];
33257
+ return items.length ? value.slice(0, items[items.length - 1].index) : "";
33258
+ }
33259
+ function moveGraphemeCursor(value, cursor, direction) {
33260
+ const boundaries = [...segments.segment(value)].map((item) => item.index);
33261
+ boundaries.push(value.length);
33262
+ return direction < 0 ? boundaries.filter((index) => index < cursor).at(-1) ?? 0 : boundaries.find((index) => index > cursor) ?? value.length;
33263
+ }
33264
+ var lineText = (line) => typeof line === "string" ? line : line.text;
33265
+ var rgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
33266
+ function colorSequence(color, mode, background) {
33267
+ if (mode === "none") return "";
33268
+ if (mode === "truecolor") return `\x1B[${background ? 48 : 38};2;${color.join(";")}m`;
33269
+ if (mode === "ansi256") {
33270
+ const level = (v) => Math.round(v / 255 * 5);
33271
+ return `\x1B[${background ? 48 : 38};5;${16 + 36 * level(color[0]) + 6 * level(color[1]) + level(color[2])}m`;
33272
+ }
33273
+ const [r, g, b] = color;
33274
+ const index = (r > 100 ? 1 : 0) + (g > 100 ? 2 : 0) + (b > 100 ? 4 : 0);
33275
+ return `\x1B[${(background ? 40 : 30) + index}m`;
33276
+ }
33277
+ function foreground(value, color, mode, bold = false) {
33278
+ return mode === "none" ? value : `${bold ? "\x1B[1m" : ""}${colorSequence(rgb(color), mode, false)}${value}\x1B[0m`;
33279
+ }
33280
+ function backgroundLine(value, width, color, mode, bold = false) {
33281
+ const line = padCells(value, width);
33282
+ if (mode === "none") return line;
33283
+ const white = mode === "ansi16" ? "\x1B[97m" : colorSequence([255, 255, 255], mode, false);
33284
+ return `${bold ? "\x1B[1m" : ""}${white}${colorSequence(rgb(color), mode, true)}${line}\x1B[0m`;
33285
+ }
33286
+
33287
+ // src/embedRenderers.ts
33288
+ var stdoutWriter = (chunk) => {
33289
+ process.stdout.write(chunk);
33290
+ };
33291
+ var writeEmbedLine = (write, text7 = "") => write(`${text7}
33292
+ `);
32975
33293
  var str = (v) => typeof v === "string" && v.length > 0 ? v : null;
32976
33294
  var DIRECT_TYPES = /* @__PURE__ */ new Set([
32977
33295
  "code",
@@ -33087,7 +33405,7 @@ function formatFare(value) {
33087
33405
  function trunc(s, max) {
33088
33406
  return s.length > max ? s.slice(0, max) + "\u2026" : s;
33089
33407
  }
33090
- function stripAnsi(value) {
33408
+ function stripAnsi2(value) {
33091
33409
  return value.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
33092
33410
  }
33093
33411
  function extractTextLines(value) {
@@ -33125,7 +33443,7 @@ function formatEmbedPreviewLines(embed, maxContentLines = 8) {
33125
33443
  const skill = embed.skillId ?? str(c.skill_id) ?? "";
33126
33444
  const label3 = skill ? `${app}/${skill}` : app || DIRECT_TYPE_LABELS[resolvedType] || resolvedType;
33127
33445
  const title = str(c.title) ?? str(c.name) ?? str(c.query) ?? embed.textPreview ?? "";
33128
- const status = stripAnsi(statusIcon(str(c.status)) || statusIcon("finished"));
33446
+ const status = stripAnsi2(statusIcon(str(c.status)) || statusIcon("finished"));
33129
33447
  const lines = [`\u250C\u2500 ${status} ${label3}${title ? ` \xB7 ${trunc(title, 56)}` : ""}`];
33130
33448
  const fileNames = extractFileNames(c);
33131
33449
  if (fileNames.length > 0) {
@@ -33268,50 +33586,50 @@ async function renderEmbedPreview(embed, client2) {
33268
33586
  process.stdout.write(`\x1B[2m\u2514\u2500 openmates embeds show ${shortId}\x1B[0m
33269
33587
  `);
33270
33588
  }
33271
- async function renderEmbedFullscreen(embed, client2) {
33589
+ async function renderEmbedFullscreen(embed, client2, write = stdoutWriter, createShareLinks = true) {
33272
33590
  const c = embed.content ?? {};
33273
33591
  const resolvedType = embed.type ?? str(c.type) ?? "";
33274
33592
  if (DIRECT_TYPES.has(resolvedType)) {
33275
33593
  const typeLabel = DIRECT_TYPE_LABELS[resolvedType] ?? resolvedType;
33276
- process.stdout.write(
33594
+ write(
33277
33595
  `\x1B[1m${typeLabel}\x1B[0m \x1B[2m${embed.embedId.slice(0, 8)}\x1B[0m
33278
33596
  `
33279
33597
  );
33280
33598
  if (embed.createdAt)
33281
- process.stdout.write(
33599
+ write(
33282
33600
  `\x1B[2mCreated:\x1B[0m ${formatTs(embed.createdAt)}
33283
33601
  `
33284
33602
  );
33285
- process.stdout.write("\n");
33286
- renderDirectTypeFullscreen(embed, c);
33603
+ write("\n");
33604
+ renderDirectTypeFullscreen(embed, c, write);
33287
33605
  return;
33288
33606
  }
33289
33607
  const app = embed.appId ?? str(embed.content?.app_id) ?? "";
33290
33608
  const skill = embed.skillId ?? str(embed.content?.skill_id) ?? "";
33291
33609
  const label3 = skill ? `${app}/${skill}` : app || "embed";
33292
33610
  const status = str(c.status);
33293
- process.stdout.write(`\x1B[1m${label3}\x1B[0m`);
33294
- if (status) process.stdout.write(` ${statusLabel(status)}`);
33295
- process.stdout.write(` \x1B[2m${embed.embedId.slice(0, 8)}\x1B[0m
33611
+ write(`\x1B[1m${label3}\x1B[0m`);
33612
+ if (status) write(` ${statusLabel(status)}`);
33613
+ write(` \x1B[2m${embed.embedId.slice(0, 8)}\x1B[0m
33296
33614
  `);
33297
33615
  const query = str(c.query) ?? str(c.search_query) ?? str(c.question);
33298
33616
  const provider = str(c.provider);
33299
- if (query) process.stdout.write(`\x1B[2mQuery:\x1B[0m ${query}
33617
+ if (query) write(`\x1B[2mQuery:\x1B[0m ${query}
33300
33618
  `);
33301
- if (provider) process.stdout.write(`\x1B[2mProvider:\x1B[0m ${provider}
33619
+ if (provider) write(`\x1B[2mProvider:\x1B[0m ${provider}
33302
33620
  `);
33303
33621
  if (embed.createdAt)
33304
- process.stdout.write(
33622
+ write(
33305
33623
  `\x1B[2mCreated:\x1B[0m ${formatTs(embed.createdAt)}
33306
33624
  `
33307
33625
  );
33308
33626
  const error = str(c.error) ?? str(c.error_message);
33309
33627
  if (error) {
33310
- process.stdout.write(`
33628
+ write(`
33311
33629
  \x1B[31mError:\x1B[0m ${error}
33312
33630
  `);
33313
33631
  }
33314
- process.stdout.write("\n");
33632
+ write("\n");
33315
33633
  const key = `${app}/${skill}`;
33316
33634
  switch (key) {
33317
33635
  case "web/search":
@@ -33319,91 +33637,98 @@ async function renderEmbedFullscreen(embed, client2) {
33319
33637
  case "shopping/search_products":
33320
33638
  case "images/search":
33321
33639
  case "mail/search":
33322
- await renderSearchFullscreen(c, client2);
33640
+ await renderSearchFullscreen(c, client2, write);
33323
33641
  break;
33324
33642
  case "events/search":
33325
- await renderEventsSearchFullscreen(c, client2);
33643
+ await renderEventsSearchFullscreen(c, client2, write);
33326
33644
  break;
33327
33645
  case "videos/search":
33328
- await renderVideosSearchFullscreen(c, client2);
33646
+ await renderVideosSearchFullscreen(c, client2, write);
33329
33647
  break;
33330
33648
  case "maps/search":
33331
- renderMapsSearchFullscreen(c);
33649
+ renderMapsSearchFullscreen(c, write);
33332
33650
  break;
33333
33651
  case "travel/search_connections":
33334
- await renderTravelConnectionsFullscreen(c, client2);
33652
+ await renderTravelConnectionsFullscreen(c, client2, write);
33335
33653
  break;
33336
33654
  case "travel/search_stays":
33337
- await renderTravelStaysFullscreen(c, client2);
33655
+ await renderTravelStaysFullscreen(c, client2, write);
33338
33656
  break;
33339
33657
  case "travel/price_calendar":
33340
- renderTravelPriceCalendarFullscreen(c);
33658
+ renderTravelPriceCalendarFullscreen(c, write);
33341
33659
  break;
33342
33660
  case "travel/get_flight":
33343
- renderTravelFlightFullscreen(c);
33661
+ renderTravelFlightFullscreen(c, write);
33344
33662
  break;
33345
33663
  case "code/get_docs":
33346
- renderCodeDocsFullscreen(c);
33664
+ renderCodeDocsFullscreen(c, write);
33347
33665
  break;
33348
33666
  case "web/read":
33349
- renderWebReadFullscreen(c);
33667
+ renderWebReadFullscreen(c, write);
33350
33668
  break;
33351
33669
  case "math/calculate":
33352
- renderMathCalculateFullscreen(c);
33670
+ renderMathCalculateFullscreen(c, write);
33353
33671
  break;
33354
33672
  case "reminder/set-reminder":
33355
33673
  case "reminder/list-reminders":
33356
33674
  case "reminder/cancel-reminder":
33357
- renderReminderFullscreen(c);
33675
+ renderReminderFullscreen(c, write);
33358
33676
  break;
33359
33677
  case "images/generate":
33360
33678
  case "images/generate_draft":
33361
- renderImageGenerateFullscreen(c);
33679
+ renderImageGenerateFullscreen(c, write);
33362
33680
  break;
33363
33681
  case "videos/get_transcript":
33364
- renderVideoTranscriptFullscreen(c);
33682
+ renderVideoTranscriptFullscreen(c, write);
33365
33683
  break;
33366
33684
  case "videos/create":
33367
- await renderRemotionCreateFullscreen(embed, c, client2);
33685
+ await renderRemotionCreateFullscreen(embed, c, client2, write, createShareLinks);
33368
33686
  break;
33369
33687
  case "health/search_appointments":
33370
- await renderHealthSearchFullscreen(c, client2);
33688
+ await renderHealthSearchFullscreen(c, client2, write);
33371
33689
  break;
33372
33690
  case "audio/transcribe":
33373
- renderAudioTranscribeFullscreen(c);
33691
+ renderAudioTranscribeFullscreen(c, write);
33374
33692
  break;
33375
33693
  default:
33376
- renderDirectTypeFullscreen(embed, c);
33694
+ renderDirectTypeFullscreen(embed, c, write);
33377
33695
  break;
33378
33696
  }
33379
33697
  }
33698
+ async function formatEmbedFullscreenLines(embed, client2) {
33699
+ let output = "";
33700
+ await renderEmbedFullscreen(embed, client2, (chunk) => {
33701
+ output += chunk;
33702
+ }, false);
33703
+ return terminalText(output).trimEnd().split("\n");
33704
+ }
33380
33705
  async function renderSearchPreview(c, ln, _client) {
33381
33706
  const count = resolveResultCount(c);
33382
33707
  if (count !== null) ln(`\x1B[2m+ ${count} results\x1B[0m`);
33383
33708
  else if (str(c.status) === "finished") ln("\x1B[2mNo results\x1B[0m");
33384
33709
  }
33385
- async function renderSearchFullscreen(c, client2) {
33710
+ async function renderSearchFullscreen(c, client2, write) {
33386
33711
  const results = await resolveChildResults(c, client2);
33387
33712
  if (results.length === 0) {
33388
- console.log("No results found.");
33713
+ writeEmbedLine(write, "No results found.");
33389
33714
  return;
33390
33715
  }
33391
- console.log(`${results.length} results:
33716
+ writeEmbedLine(write, `${results.length} results:
33392
33717
  `);
33393
33718
  for (const r of results) {
33394
33719
  const title = str(r.title) ?? str(r.name) ?? "";
33395
33720
  const url = str(r.url) ?? str(r.link) ?? "";
33396
33721
  const desc = str(r.description) ?? str(r.snippet) ?? str(r.summary) ?? "";
33397
33722
  const age = str(r.page_age);
33398
- if (title) process.stdout.write(` \x1B[1m${title}\x1B[0m
33723
+ if (title) write(` \x1B[1m${title}\x1B[0m
33399
33724
  `);
33400
- if (url) process.stdout.write(` \x1B[2m${url}\x1B[0m
33725
+ if (url) write(` \x1B[2m${url}\x1B[0m
33401
33726
  `);
33402
- if (age) process.stdout.write(` \x1B[2m${age}\x1B[0m
33727
+ if (age) write(` \x1B[2m${age}\x1B[0m
33403
33728
  `);
33404
- if (desc) process.stdout.write(` ${trunc(desc, 300)}
33729
+ if (desc) write(` ${desc}
33405
33730
  `);
33406
- process.stdout.write(` \x1B[2m${"\u2500".repeat(40)}\x1B[0m
33731
+ write(` \x1B[2m${"\u2500".repeat(40)}\x1B[0m
33407
33732
  `);
33408
33733
  }
33409
33734
  }
@@ -33412,13 +33737,13 @@ async function renderEventsSearchPreview(c, ln, _client) {
33412
33737
  if (count !== null) ln(`\x1B[2m+ ${count} events\x1B[0m`);
33413
33738
  else if (str(c.status) === "finished") ln("\x1B[2mNo events found\x1B[0m");
33414
33739
  }
33415
- async function renderEventsSearchFullscreen(c, client2) {
33740
+ async function renderEventsSearchFullscreen(c, client2, write) {
33416
33741
  const results = await resolveChildResults(c, client2);
33417
33742
  if (results.length === 0) {
33418
- console.log("No events found.");
33743
+ writeEmbedLine(write, "No events found.");
33419
33744
  return;
33420
33745
  }
33421
- console.log(`${results.length} events:
33746
+ writeEmbedLine(write, `${results.length} events:
33422
33747
  `);
33423
33748
  for (const r of results) {
33424
33749
  const name = str(r.name) ?? str(r.title) ?? "";
@@ -33427,19 +33752,19 @@ async function renderEventsSearchFullscreen(c, client2) {
33427
33752
  const url = str(r.url) ?? str(r.link) ?? "";
33428
33753
  const desc = str(r.description) ?? str(r.summary) ?? "";
33429
33754
  const going = typeof r.going_count === "number" ? r.going_count : null;
33430
- if (name) process.stdout.write(` \x1B[1m${name}\x1B[0m
33755
+ if (name) write(` \x1B[1m${name}\x1B[0m
33431
33756
  `);
33432
- if (date) process.stdout.write(` \x1B[2m${date}\x1B[0m`);
33433
- if (venue) process.stdout.write(` \x1B[2m@ ${venue}\x1B[0m`);
33434
- if (date || venue) process.stdout.write("\n");
33757
+ if (date) write(` \x1B[2m${date}\x1B[0m`);
33758
+ if (venue) write(` \x1B[2m@ ${venue}\x1B[0m`);
33759
+ if (date || venue) write("\n");
33435
33760
  if (going !== null)
33436
- process.stdout.write(` \x1B[2m${going} going\x1B[0m
33761
+ write(` \x1B[2m${going} going\x1B[0m
33437
33762
  `);
33438
- if (desc) process.stdout.write(` ${trunc(desc, 200)}
33763
+ if (desc) write(` ${desc}
33439
33764
  `);
33440
- if (url) process.stdout.write(` \x1B[2m${url}\x1B[0m
33765
+ if (url) write(` \x1B[2m${url}\x1B[0m
33441
33766
  `);
33442
- process.stdout.write(` \x1B[2m${"\u2500".repeat(40)}\x1B[0m
33767
+ write(` \x1B[2m${"\u2500".repeat(40)}\x1B[0m
33443
33768
  `);
33444
33769
  }
33445
33770
  }
@@ -33448,30 +33773,30 @@ async function renderVideosSearchPreview(c, ln, _client) {
33448
33773
  if (count !== null) ln(`\x1B[2m+ ${count} videos\x1B[0m`);
33449
33774
  else if (str(c.status) === "finished") ln("\x1B[2mNo videos found\x1B[0m");
33450
33775
  }
33451
- async function renderVideosSearchFullscreen(c, client2) {
33776
+ async function renderVideosSearchFullscreen(c, client2, write) {
33452
33777
  const results = await resolveChildResults(c, client2);
33453
33778
  if (results.length === 0) {
33454
- console.log("No videos found.");
33779
+ writeEmbedLine(write, "No videos found.");
33455
33780
  return;
33456
33781
  }
33457
- console.log(`${results.length} videos:
33782
+ writeEmbedLine(write, `${results.length} videos:
33458
33783
  `);
33459
33784
  for (const r of results) {
33460
33785
  const title = str(r.title) ?? "";
33461
33786
  const channel = str(r.channel) ?? str(r.author) ?? "";
33462
33787
  const duration = str(r.duration) ?? "";
33463
33788
  const url = str(r.url) ?? str(r.link) ?? "";
33464
- if (title) process.stdout.write(` \x1B[1m${title}\x1B[0m
33789
+ if (title) write(` \x1B[1m${title}\x1B[0m
33465
33790
  `);
33466
33791
  if (channel || duration) {
33467
- process.stdout.write(
33792
+ write(
33468
33793
  ` \x1B[2m${channel}${duration ? ` ${duration}` : ""}\x1B[0m
33469
33794
  `
33470
33795
  );
33471
33796
  }
33472
- if (url) process.stdout.write(` \x1B[2m${url}\x1B[0m
33797
+ if (url) write(` \x1B[2m${url}\x1B[0m
33473
33798
  `);
33474
- console.log();
33799
+ writeEmbedLine(write);
33475
33800
  }
33476
33801
  }
33477
33802
  async function renderRemotionCreatePreview(embed, c, ln, client2) {
@@ -33488,38 +33813,38 @@ async function renderRemotionCreatePreview(embed, c, ln, client2) {
33488
33813
  await renderRemotionShareLink(embed.embedId, client2, ln);
33489
33814
  }
33490
33815
  }
33491
- async function renderRemotionCreateFullscreen(embed, c, client2) {
33816
+ async function renderRemotionCreateFullscreen(embed, c, client2, write, createShareLinks) {
33492
33817
  const meta = remotionMeta(c);
33493
- process.stdout.write(`\x1B[1m${meta.filename}\x1B[0m
33818
+ write(`\x1B[1m${meta.filename}\x1B[0m
33494
33819
  `);
33495
- process.stdout.write(`${meta.statusText} \x1B[2mv${meta.sourceVersion} \xB7 ${meta.durationSeconds}s \xB7 ${meta.width}x${meta.height}\x1B[0m
33820
+ write(`${meta.statusText} \x1B[2mv${meta.sourceVersion} \xB7 ${meta.durationSeconds}s \xB7 ${meta.width}x${meta.height}\x1B[0m
33496
33821
 
33497
33822
  `);
33498
33823
  if (meta.layers.length > 0) {
33499
- process.stdout.write("Timeline:\n");
33824
+ write("Timeline:\n");
33500
33825
  for (const layer of meta.layers) {
33501
- process.stdout.write(` - ${layer}
33826
+ write(` - ${layer}
33502
33827
  `);
33503
33828
  }
33504
- process.stdout.write("\n");
33829
+ write("\n");
33505
33830
  }
33506
33831
  if (meta.source) {
33507
- process.stdout.write("Source:\n");
33508
- process.stdout.write("```tsx\n");
33509
- process.stdout.write(`${meta.source.trim()}
33832
+ write("Source:\n");
33833
+ write("```tsx\n");
33834
+ write(`${meta.source.trim()}
33510
33835
  `);
33511
- process.stdout.write("```\n\n");
33836
+ write("```\n\n");
33512
33837
  }
33513
33838
  if (meta.error) {
33514
- process.stdout.write(`\x1B[31mError:\x1B[0m ${meta.error}
33839
+ write(`\x1B[31mError:\x1B[0m ${meta.error}
33515
33840
 
33516
33841
  `);
33517
33842
  }
33518
- if (meta.status === "finished") {
33519
- await renderRemotionShareLink(embed.embedId, client2, (line) => process.stdout.write(`${line}
33843
+ if (createShareLinks && meta.status === "finished") {
33844
+ await renderRemotionShareLink(embed.embedId, client2, (line) => write(`${line}
33520
33845
  `));
33521
- } else {
33522
- process.stdout.write("Run again after rendering finishes to get the rendered video link and QR code.\n");
33846
+ } else if (meta.status !== "finished") {
33847
+ write("Run again after rendering finishes to get the rendered video link and QR code.\n");
33523
33848
  }
33524
33849
  }
33525
33850
  async function renderRemotionShareLink(embedId, client2, ln) {
@@ -33603,26 +33928,26 @@ function renderMapsSearchPreview(c, ln) {
33603
33928
  ln("\x1B[2mNo places found\x1B[0m");
33604
33929
  }
33605
33930
  }
33606
- function renderMapsSearchFullscreen(c) {
33931
+ function renderMapsSearchFullscreen(c, write) {
33607
33932
  const results = c.results;
33608
33933
  if (!Array.isArray(results) || results.length === 0) {
33609
- console.log("No places found.");
33934
+ writeEmbedLine(write, "No places found.");
33610
33935
  return;
33611
33936
  }
33612
- console.log(`${results.length} places:
33937
+ writeEmbedLine(write, `${results.length} places:
33613
33938
  `);
33614
33939
  for (const r of results) {
33615
33940
  const name = str(r.displayName) ?? str(r.name) ?? "";
33616
33941
  const address = str(r.formattedAddress) ?? str(r.address) ?? "";
33617
33942
  const rating = typeof r.rating === "number" ? `\u2605 ${r.rating}` : "";
33618
33943
  if (name)
33619
- process.stdout.write(
33944
+ write(
33620
33945
  ` \x1B[1m${name}\x1B[0m${rating ? ` ${rating}` : ""}
33621
33946
  `
33622
33947
  );
33623
- if (address) process.stdout.write(` \x1B[2m${address}\x1B[0m
33948
+ if (address) write(` \x1B[2m${address}\x1B[0m
33624
33949
  `);
33625
- console.log();
33950
+ writeEmbedLine(write);
33626
33951
  }
33627
33952
  }
33628
33953
  async function renderTravelConnectionsPreview(c, ln, _client) {
@@ -33649,13 +33974,13 @@ async function renderTravelConnectionsPreview(c, ln, _client) {
33649
33974
  }
33650
33975
  }
33651
33976
  }
33652
- async function renderTravelConnectionsFullscreen(c, client2) {
33977
+ async function renderTravelConnectionsFullscreen(c, client2, write) {
33653
33978
  const results = await resolveChildResults(c, client2);
33654
33979
  if (results.length === 0) {
33655
- console.log("No connections found.");
33980
+ writeEmbedLine(write, "No connections found.");
33656
33981
  return;
33657
33982
  }
33658
- console.log(`${results.length} connections:
33983
+ writeEmbedLine(write, `${results.length} connections:
33659
33984
  `);
33660
33985
  for (const r of results) {
33661
33986
  const origin = str(r.origin) ?? "";
@@ -33667,33 +33992,33 @@ async function renderTravelConnectionsFullscreen(c, client2) {
33667
33992
  const stops = typeof r.stops === "number" ? r.stops === 0 ? "Direct" : `${r.stops} stops` : "";
33668
33993
  const carriers = Array.isArray(r.carriers) ? r.carriers.join(", ") : str(r.carrier) ?? "";
33669
33994
  if (origin && dest)
33670
- process.stdout.write(` \x1B[1m${origin} \u2192 ${dest}\x1B[0m
33995
+ write(` \x1B[1m${origin} \u2192 ${dest}\x1B[0m
33671
33996
  `);
33672
33997
  if (dep && arr)
33673
- process.stdout.write(
33998
+ write(
33674
33999
  ` ${dep} \u2013 ${arr}${duration ? ` (${duration})` : ""}
33675
34000
  `
33676
34001
  );
33677
34002
  if (price || stops || carriers) {
33678
- process.stdout.write(
34003
+ write(
33679
34004
  ` \x1B[2m${[price, stops, carriers].filter(Boolean).join(" \xB7 ")}\x1B[0m
33680
34005
  `
33681
34006
  );
33682
34007
  }
33683
- console.log();
34008
+ writeEmbedLine(write);
33684
34009
  }
33685
34010
  }
33686
34011
  async function renderTravelStaysPreview(c, ln, _client) {
33687
34012
  const count = resolveResultCount(c);
33688
34013
  if (count !== null) ln(`\x1B[2m${count} stays\x1B[0m`);
33689
34014
  }
33690
- async function renderTravelStaysFullscreen(c, client2) {
34015
+ async function renderTravelStaysFullscreen(c, client2, write) {
33691
34016
  const results = await resolveChildResults(c, client2);
33692
34017
  if (results.length === 0) {
33693
- console.log("No stays found.");
34018
+ writeEmbedLine(write, "No stays found.");
33694
34019
  return;
33695
34020
  }
33696
- console.log(`${results.length} stays:
34021
+ writeEmbedLine(write, `${results.length} stays:
33697
34022
  `);
33698
34023
  for (const r of results) {
33699
34024
  const name = str(r.name) ?? str(r.hotel_name) ?? "";
@@ -33701,15 +34026,15 @@ async function renderTravelStaysFullscreen(c, client2) {
33701
34026
  const rating = typeof r.rating === "number" ? `\u2605 ${r.rating}` : "";
33702
34027
  const address = str(r.address) ?? "";
33703
34028
  if (name)
33704
- process.stdout.write(
34029
+ write(
33705
34030
  ` \x1B[1m${name}\x1B[0m${rating ? ` ${rating}` : ""}
33706
34031
  `
33707
34032
  );
33708
- if (price) process.stdout.write(` ${price}
34033
+ if (price) write(` ${price}
33709
34034
  `);
33710
- if (address) process.stdout.write(` \x1B[2m${address}\x1B[0m
34035
+ if (address) write(` \x1B[2m${address}\x1B[0m
33711
34036
  `);
33712
- console.log();
34037
+ writeEmbedLine(write);
33713
34038
  }
33714
34039
  }
33715
34040
  function renderTravelPriceCalendarPreview(c, ln) {
@@ -33722,24 +34047,24 @@ function renderTravelPriceCalendarPreview(c, ln) {
33722
34047
  ln(`\x1B[2mFrom ${currency} ${cheapest}\x1B[0m`);
33723
34048
  }
33724
34049
  }
33725
- function renderTravelPriceCalendarFullscreen(c) {
34050
+ function renderTravelPriceCalendarFullscreen(c, write) {
33726
34051
  const prices = c.prices;
33727
34052
  if (!Array.isArray(prices) || prices.length === 0) {
33728
- console.log("No price data available.");
34053
+ writeEmbedLine(write, "No price data available.");
33729
34054
  return;
33730
34055
  }
33731
34056
  const currency = str(c.currency) ?? "EUR";
33732
- console.log(`Price calendar (${prices.length} dates):
34057
+ writeEmbedLine(write, `Price calendar (${prices.length} dates):
33733
34058
  `);
33734
34059
  for (const p of prices.slice(0, 14)) {
33735
34060
  const date = str(p.date) ?? "";
33736
34061
  const price = p.price ?? p.amount;
33737
34062
  if (date && price !== void 0) {
33738
- process.stdout.write(` ${date} ${currency} ${price}
34063
+ write(` ${date} ${currency} ${price}
33739
34064
  `);
33740
34065
  }
33741
34066
  }
33742
- if (prices.length > 14) console.log(` ... and ${prices.length - 14} more`);
34067
+ if (prices.length > 14) writeEmbedLine(write, ` ... and ${prices.length - 14} more`);
33743
34068
  }
33744
34069
  function renderTravelFlightPreview(c, ln) {
33745
34070
  const flightNumber = str(c.flight_number) ?? str(c.callsign) ?? "";
@@ -33752,7 +34077,7 @@ function renderTravelFlightPreview(c, ln) {
33752
34077
  const status = str(c.flight_status);
33753
34078
  if (status) ln(`\x1B[2mStatus: ${status}\x1B[0m`);
33754
34079
  }
33755
- function renderTravelFlightFullscreen(c) {
34080
+ function renderTravelFlightFullscreen(c, write) {
33756
34081
  const fields = [
33757
34082
  ["Flight", c.flight_number ?? c.callsign],
33758
34083
  ["Airline", c.airline],
@@ -33769,7 +34094,7 @@ function renderTravelFlightFullscreen(c) {
33769
34094
  ];
33770
34095
  for (const [label3, value] of fields) {
33771
34096
  if (value !== null && value !== void 0) {
33772
- process.stdout.write(` \x1B[2m${label3.padEnd(14)}\x1B[0m ${value}
34097
+ write(` \x1B[2m${label3.padEnd(14)}\x1B[0m ${value}
33773
34098
  `);
33774
34099
  }
33775
34100
  }
@@ -33782,14 +34107,14 @@ function renderCodeDocsPreview(c, ln) {
33782
34107
  if (libId) ln(`\x1B[2mLibrary: ${String(libId)}\x1B[0m`);
33783
34108
  if (wordCount) ln(`\x1B[2m${String(wordCount)} words\x1B[0m`);
33784
34109
  }
33785
- function renderCodeDocsFullscreen(c) {
34110
+ function renderCodeDocsFullscreen(c, write) {
33786
34111
  const results = c.results;
33787
34112
  const first = Array.isArray(results) ? results[0] : null;
33788
34113
  const docs = str(first?.documentation) ?? str(c.documentation) ?? "";
33789
34114
  if (docs) {
33790
- console.log(docs);
34115
+ writeEmbedLine(write, docs);
33791
34116
  } else {
33792
- console.log("No documentation content.");
34117
+ writeEmbedLine(write, "No documentation content.");
33793
34118
  }
33794
34119
  }
33795
34120
  function renderWebReadPreview(c, ln) {
@@ -33798,17 +34123,17 @@ function renderWebReadPreview(c, ln) {
33798
34123
  if (url) ln(`\x1B[2m${trunc(url, 60)}\x1B[0m`);
33799
34124
  if (resultCount) ln(`\x1B[2m${resultCount} results\x1B[0m`);
33800
34125
  }
33801
- function renderWebReadFullscreen(c) {
34126
+ function renderWebReadFullscreen(c, write) {
33802
34127
  const url = str(c.url);
33803
- if (url) process.stdout.write(`\x1B[2mURL:\x1B[0m ${url}
34128
+ if (url) write(`\x1B[2mURL:\x1B[0m ${url}
33804
34129
 
33805
34130
  `);
33806
34131
  const results = c.results;
33807
34132
  if (Array.isArray(results)) {
33808
34133
  for (const r of results) {
33809
34134
  const content = str(r.content) ?? str(r.text) ?? "";
33810
- if (content) console.log(content);
33811
- console.log();
34135
+ if (content) writeEmbedLine(write, content);
34136
+ writeEmbedLine(write);
33812
34137
  }
33813
34138
  }
33814
34139
  }
@@ -33824,26 +34149,26 @@ function renderMathCalculatePreview(c, ln) {
33824
34149
  else if (result) ln(trunc(result, 80));
33825
34150
  }
33826
34151
  }
33827
- function renderMathCalculateFullscreen(c) {
34152
+ function renderMathCalculateFullscreen(c, write) {
33828
34153
  const results = c.results;
33829
34154
  const title = str(c.title);
33830
- if (title) process.stdout.write(` \x1B[2mTitle:\x1B[0m ${title}
34155
+ if (title) write(` \x1B[2mTitle:\x1B[0m ${title}
33831
34156
  `);
33832
34157
  if (!Array.isArray(results) || results.length === 0) {
33833
- console.log("No calculation results.");
34158
+ writeEmbedLine(write, "No calculation results.");
33834
34159
  return;
33835
34160
  }
33836
34161
  for (const r of results) {
33837
34162
  const resultTitle = str(r.title);
33838
34163
  const expr = str(r.expression) ?? str(r.input) ?? "";
33839
34164
  const result = str(r.result) ?? str(r.output) ?? "";
33840
- if (resultTitle && resultTitle !== title) process.stdout.write(` \x1B[2mTitle:\x1B[0m ${resultTitle}
34165
+ if (resultTitle && resultTitle !== title) write(` \x1B[2mTitle:\x1B[0m ${resultTitle}
33841
34166
  `);
33842
- if (expr) process.stdout.write(` \x1B[2mExpression:\x1B[0m ${expr}
34167
+ if (expr) write(` \x1B[2mExpression:\x1B[0m ${expr}
33843
34168
  `);
33844
- if (result) process.stdout.write(` \x1B[1mResult:\x1B[0m ${result}
34169
+ if (result) write(` \x1B[1mResult:\x1B[0m ${result}
33845
34170
  `);
33846
- console.log();
34171
+ writeEmbedLine(write);
33847
34172
  }
33848
34173
  }
33849
34174
  function renderReminderPreview(c, ln) {
@@ -33853,7 +34178,7 @@ function renderReminderPreview(c, ln) {
33853
34178
  if (time) ln(`\x1B[2m\u{1F551} ${time}\x1B[0m`);
33854
34179
  if (c.is_repeating === true) ln("\x1B[2mRepeating\x1B[0m");
33855
34180
  }
33856
- function renderReminderFullscreen(c) {
34181
+ function renderReminderFullscreen(c, write) {
33857
34182
  const fields = [
33858
34183
  ["Message", c.prompt ?? c.message ?? c.reminder_text],
33859
34184
  ["Time", c.trigger_at_formatted ?? c.trigger_at],
@@ -33869,7 +34194,7 @@ function renderReminderFullscreen(c) {
33869
34194
  ];
33870
34195
  for (const [label3, value] of fields) {
33871
34196
  if (value !== null && value !== void 0) {
33872
- process.stdout.write(` \x1B[2m${label3.padEnd(14)}\x1B[0m ${value}
34197
+ write(` \x1B[2m${label3.padEnd(14)}\x1B[0m ${value}
33873
34198
  `);
33874
34199
  }
33875
34200
  }
@@ -33881,7 +34206,7 @@ function renderImageGeneratePreview(c, ln) {
33881
34206
  if (prompt) ln(trunc(prompt, 60));
33882
34207
  ln("\x1B[2m[image]\x1B[0m");
33883
34208
  }
33884
- function renderImageGenerateFullscreen(c) {
34209
+ function renderImageGenerateFullscreen(c, write) {
33885
34210
  const fields = [
33886
34211
  ["Model", c.model],
33887
34212
  ["Prompt", c.prompt],
@@ -33890,11 +34215,11 @@ function renderImageGenerateFullscreen(c) {
33890
34215
  ];
33891
34216
  for (const [label3, value] of fields) {
33892
34217
  if (value !== null && value !== void 0) {
33893
- process.stdout.write(` \x1B[2m${label3.padEnd(14)}\x1B[0m ${value}
34218
+ write(` \x1B[2m${label3.padEnd(14)}\x1B[0m ${value}
33894
34219
  `);
33895
34220
  }
33896
34221
  }
33897
- console.log("\n [Images are encrypted \u2014 view in web app]");
34222
+ writeEmbedLine(write, "\n [Images are encrypted \u2014 view in web app]");
33898
34223
  }
33899
34224
  function renderVideoTranscriptPreview(c, ln) {
33900
34225
  const title = str(c.title) ?? str(c.video_title) ?? "";
@@ -33902,17 +34227,17 @@ function renderVideoTranscriptPreview(c, ln) {
33902
34227
  if (title) ln(trunc(title, 60));
33903
34228
  if (channel) ln(`\x1B[2m${channel}\x1B[0m`);
33904
34229
  }
33905
- function renderVideoTranscriptFullscreen(c) {
34230
+ function renderVideoTranscriptFullscreen(c, write) {
33906
34231
  const title = str(c.title) ?? str(c.video_title);
33907
34232
  const url = str(c.url) ?? str(c.video_url);
33908
- if (title) process.stdout.write(`\x1B[1m${title}\x1B[0m
34233
+ if (title) write(`\x1B[1m${title}\x1B[0m
33909
34234
  `);
33910
- if (url) process.stdout.write(`\x1B[2m${url}\x1B[0m
34235
+ if (url) write(`\x1B[2m${url}\x1B[0m
33911
34236
  `);
33912
- console.log();
34237
+ writeEmbedLine(write);
33913
34238
  const transcript = str(c.transcript) ?? str(c.text) ?? "";
33914
- if (transcript) console.log(transcript);
33915
- else console.log("No transcript available.");
34239
+ if (transcript) writeEmbedLine(write, transcript);
34240
+ else writeEmbedLine(write, "No transcript available.");
33916
34241
  }
33917
34242
  async function renderHealthSearchPreview(c, ln, _client) {
33918
34243
  const count = resolveResultCount(c);
@@ -33920,26 +34245,26 @@ async function renderHealthSearchPreview(c, ln, _client) {
33920
34245
  else if (str(c.status) === "finished")
33921
34246
  ln("\x1B[2mNo appointments found\x1B[0m");
33922
34247
  }
33923
- async function renderHealthSearchFullscreen(c, client2) {
34248
+ async function renderHealthSearchFullscreen(c, client2, write) {
33924
34249
  const results = await resolveChildResults(c, client2);
33925
34250
  if (results.length === 0) {
33926
- console.log("No appointments found.");
34251
+ writeEmbedLine(write, "No appointments found.");
33927
34252
  return;
33928
34253
  }
33929
- console.log(`${results.length} appointments:
34254
+ writeEmbedLine(write, `${results.length} appointments:
33930
34255
  `);
33931
34256
  for (const r of results) {
33932
34257
  const slotDt = str(r.slot_datetime) ?? str(r.next_slot) ?? str(r.date) ?? "";
33933
34258
  const name = str(r.name) ?? str(r.doctor_name) ?? str(r.title) ?? "";
33934
34259
  const speciality = str(r.speciality) ?? "";
33935
34260
  const address = str(r.address) ?? "";
33936
- if (slotDt) process.stdout.write(` \x1B[1m${slotDt}\x1B[0m
34261
+ if (slotDt) write(` \x1B[1m${slotDt}\x1B[0m
33937
34262
  `);
33938
- if (name) process.stdout.write(` ${name}${speciality ? ` \xB7 ${speciality}` : ""}
34263
+ if (name) write(` ${name}${speciality ? ` \xB7 ${speciality}` : ""}
33939
34264
  `);
33940
- if (address) process.stdout.write(` \x1B[2m${address}\x1B[0m
34265
+ if (address) write(` \x1B[2m${address}\x1B[0m
33941
34266
  `);
33942
- console.log();
34267
+ writeEmbedLine(write);
33943
34268
  }
33944
34269
  }
33945
34270
  function renderAudioTranscribePreview(c, ln) {
@@ -33950,10 +34275,10 @@ function renderAudioTranscribePreview(c, ln) {
33950
34275
  const text7 = str(c.text) ?? str(c.transcript) ?? "";
33951
34276
  if (text7) ln(trunc(text7, 60));
33952
34277
  }
33953
- function renderAudioTranscribeFullscreen(c) {
34278
+ function renderAudioTranscribeFullscreen(c, write) {
33954
34279
  const text7 = str(c.text) ?? str(c.transcript) ?? "";
33955
- if (text7) console.log(text7);
33956
- else console.log("No transcript available.");
34280
+ if (text7) writeEmbedLine(write, text7);
34281
+ else writeEmbedLine(write, "No transcript available.");
33957
34282
  }
33958
34283
  function renderByDirectType(embed, c, ln) {
33959
34284
  const type = embed.type ?? str(c.type) ?? "";
@@ -34148,7 +34473,7 @@ function renderByDirectType(embed, c, ln) {
34148
34473
  }
34149
34474
  }
34150
34475
  }
34151
- function renderDirectTypeFullscreen(embed, c) {
34476
+ function renderDirectTypeFullscreen(embed, c, write) {
34152
34477
  const type = embed.type ?? str(c.type) ?? "";
34153
34478
  switch (type) {
34154
34479
  case "code":
@@ -34156,12 +34481,12 @@ function renderDirectTypeFullscreen(embed, c) {
34156
34481
  const lang = str(c.language);
34157
34482
  const filename = str(c.filename);
34158
34483
  const code = str(c.code) ?? str(c.content) ?? "";
34159
- if (filename) process.stdout.write(`\x1B[2mFile:\x1B[0m ${filename}
34484
+ if (filename) write(`\x1B[2mFile:\x1B[0m ${filename}
34160
34485
  `);
34161
- if (lang) process.stdout.write(`\x1B[2mLanguage:\x1B[0m ${lang}
34486
+ if (lang) write(`\x1B[2mLanguage:\x1B[0m ${lang}
34162
34487
  `);
34163
- console.log();
34164
- if (code) console.log(code);
34488
+ writeEmbedLine(write);
34489
+ if (code) writeEmbedLine(write, code);
34165
34490
  break;
34166
34491
  }
34167
34492
  case "code-application":
@@ -34169,11 +34494,11 @@ function renderDirectTypeFullscreen(embed, c) {
34169
34494
  const name = str(c.name) ?? str(c.title);
34170
34495
  const framework = str(c.framework);
34171
34496
  const runtime = str(c.runtime);
34172
- if (name) process.stdout.write(`\x1B[1m${name}\x1B[0m
34497
+ if (name) write(`\x1B[1m${name}\x1B[0m
34173
34498
  `);
34174
- if (framework) process.stdout.write(`\x1B[2mFramework:\x1B[0m ${framework}
34499
+ if (framework) write(`\x1B[2mFramework:\x1B[0m ${framework}
34175
34500
  `);
34176
- if (runtime) process.stdout.write(`\x1B[2mRuntime:\x1B[0m ${runtime}
34501
+ if (runtime) write(`\x1B[2mRuntime:\x1B[0m ${runtime}
34177
34502
  `);
34178
34503
  break;
34179
34504
  }
@@ -34181,24 +34506,24 @@ function renderDirectTypeFullscreen(embed, c) {
34181
34506
  case "doc": {
34182
34507
  const title = str(c.title);
34183
34508
  const html = str(c.html) ?? "";
34184
- if (title) process.stdout.write(`\x1B[1m${title}\x1B[0m
34509
+ if (title) write(`\x1B[1m${title}\x1B[0m
34185
34510
 
34186
34511
  `);
34187
34512
  if (html) {
34188
34513
  const text7 = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
34189
- console.log(text7);
34514
+ writeEmbedLine(write, text7);
34190
34515
  }
34191
34516
  break;
34192
34517
  }
34193
34518
  case "document": {
34194
34519
  const title = str(c.title);
34195
34520
  const html = str(c.html) ?? "";
34196
- if (title) process.stdout.write(`\x1B[1m${title}\x1B[0m
34521
+ if (title) write(`\x1B[1m${title}\x1B[0m
34197
34522
 
34198
34523
  `);
34199
34524
  if (html) {
34200
34525
  const text7 = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
34201
- console.log(text7);
34526
+ writeEmbedLine(write, text7);
34202
34527
  }
34203
34528
  break;
34204
34529
  }
@@ -34206,23 +34531,23 @@ function renderDirectTypeFullscreen(embed, c) {
34206
34531
  case "sheet": {
34207
34532
  const title = str(c.title);
34208
34533
  const table = str(c.table) ?? str(c.content) ?? "";
34209
- if (title) process.stdout.write(`\x1B[1m${title}\x1B[0m
34534
+ if (title) write(`\x1B[1m${title}\x1B[0m
34210
34535
 
34211
34536
  `);
34212
- if (table) console.log(table);
34537
+ if (table) writeEmbedLine(write, table);
34213
34538
  break;
34214
34539
  }
34215
34540
  case "pdf": {
34216
34541
  const filename = str(c.filename);
34217
- if (filename) process.stdout.write(`\x1B[2mFile:\x1B[0m ${filename}
34542
+ if (filename) write(`\x1B[2mFile:\x1B[0m ${filename}
34218
34543
  `);
34219
34544
  const results = c.results;
34220
34545
  if (Array.isArray(results)) {
34221
34546
  for (const r of results) {
34222
34547
  const content = str(r.content) ?? str(r.text) ?? "";
34223
34548
  if (content) {
34224
- console.log();
34225
- console.log(content);
34549
+ writeEmbedLine(write);
34550
+ writeEmbedLine(write, content);
34226
34551
  }
34227
34552
  }
34228
34553
  }
@@ -34233,15 +34558,15 @@ function renderDirectTypeFullscreen(embed, c) {
34233
34558
  const url = str(c.url);
34234
34559
  const desc = str(c.description) ?? str(c.snippet) ?? "";
34235
34560
  const age = str(c.page_age);
34236
- if (title) process.stdout.write(`\x1B[1m${title}\x1B[0m
34561
+ if (title) write(`\x1B[1m${title}\x1B[0m
34237
34562
  `);
34238
- if (url) process.stdout.write(`\x1B[2m${url}\x1B[0m
34563
+ if (url) write(`\x1B[2m${url}\x1B[0m
34239
34564
  `);
34240
- if (age) process.stdout.write(`\x1B[2mAge: ${age}\x1B[0m
34565
+ if (age) write(`\x1B[2mAge: ${age}\x1B[0m
34241
34566
  `);
34242
34567
  if (desc) {
34243
- console.log();
34244
- console.log(desc);
34568
+ writeEmbedLine(write);
34569
+ writeEmbedLine(write, desc);
34245
34570
  }
34246
34571
  break;
34247
34572
  }
@@ -34251,18 +34576,18 @@ function renderDirectTypeFullscreen(embed, c) {
34251
34576
  const channel = str(c.channel) ?? str(c.author) ?? "";
34252
34577
  const duration = str(c.duration) ?? "";
34253
34578
  const desc = str(c.description) ?? str(c.snippet) ?? "";
34254
- if (title) process.stdout.write(`\x1B[1m${title}\x1B[0m
34579
+ if (title) write(`\x1B[1m${title}\x1B[0m
34255
34580
  `);
34256
- if (url) process.stdout.write(`\x1B[2m${url}\x1B[0m
34581
+ if (url) write(`\x1B[2m${url}\x1B[0m
34257
34582
  `);
34258
34583
  if (channel)
34259
- process.stdout.write(
34584
+ write(
34260
34585
  `\x1B[2mChannel:\x1B[0m ${channel}${duration ? ` \x1B[2m(${duration})\x1B[0m` : ""}
34261
34586
  `
34262
34587
  );
34263
34588
  if (desc) {
34264
- console.log();
34265
- console.log(desc);
34589
+ writeEmbedLine(write);
34590
+ writeEmbedLine(write, desc);
34266
34591
  }
34267
34592
  break;
34268
34593
  }
@@ -34270,13 +34595,13 @@ function renderDirectTypeFullscreen(embed, c) {
34270
34595
  const subject = str(c.subject);
34271
34596
  const receiver = str(c.receiver);
34272
34597
  const content = str(c.content) ?? "";
34273
- if (subject) process.stdout.write(`\x1B[1m${subject}\x1B[0m
34598
+ if (subject) write(`\x1B[1m${subject}\x1B[0m
34274
34599
  `);
34275
- if (receiver) process.stdout.write(`\x1B[2mTo: ${receiver}\x1B[0m
34600
+ if (receiver) write(`\x1B[2mTo: ${receiver}\x1B[0m
34276
34601
  `);
34277
34602
  if (content) {
34278
- console.log();
34279
- console.log(content);
34603
+ writeEmbedLine(write);
34604
+ writeEmbedLine(write, content);
34280
34605
  }
34281
34606
  break;
34282
34607
  }
@@ -34284,30 +34609,52 @@ function renderDirectTypeFullscreen(embed, c) {
34284
34609
  case "mindmaps-mindmap": {
34285
34610
  const document = mindMapDocumentFromContent(c);
34286
34611
  const title = str(c.title) ?? str(document?.title) ?? "Mind Map";
34287
- process.stdout.write(`\x1B[1m${title}\x1B[0m
34612
+ write(`\x1B[1m${title}\x1B[0m
34288
34613
  `);
34289
34614
  if (!document) {
34290
- console.log("Invalid mind map JSON");
34615
+ writeEmbedLine(write, "Invalid mind map JSON");
34291
34616
  break;
34292
34617
  }
34293
- console.log(`${document.nodes.length} nodes \xB7 ${document.edges?.length ?? 0} edges
34618
+ writeEmbedLine(write, `${document.nodes.length} nodes \xB7 ${document.edges?.length ?? 0} edges
34294
34619
  `);
34295
- console.log(mindMapOutline(document));
34296
- console.log("\n```openmates_mindmap");
34297
- console.log(JSON.stringify(document, null, 2));
34298
- console.log("```");
34620
+ writeEmbedLine(write, mindMapOutline(document));
34621
+ writeEmbedLine(write, "\n```openmates_mindmap");
34622
+ writeEmbedLine(write, JSON.stringify(document, null, 2));
34623
+ writeEmbedLine(write, "```");
34624
+ break;
34625
+ }
34626
+ case "events-event": {
34627
+ const venue = isRecord2(c.venue) ? c.venue : {};
34628
+ const organizer = isRecord2(c.organizer) ? c.organizer : {};
34629
+ const fee = isRecord2(c.fee) ? c.fee : {};
34630
+ const field = (label3, value) => {
34631
+ if (value !== null && value !== void 0 && String(value).trim()) {
34632
+ writeEmbedLine(write, label3);
34633
+ writeEmbedLine(write, String(value));
34634
+ writeEmbedLine(write);
34635
+ }
34636
+ };
34637
+ writeEmbedLine(write, str(c.title) ?? str(c.name) ?? "Event");
34638
+ writeEmbedLine(write);
34639
+ field("When", [c.date_start ?? c.start_time ?? c.date, c.date_end, c.timezone].filter(Boolean).join(" \xB7 "));
34640
+ field("Location", c.event_type === "online" ? "Online event" : [venue.name ?? c.venue_name, venue.address ?? c.venue_address, venue.city ?? c.venue_city, venue.country ?? c.venue_country, str(c.location)].filter(Boolean).join(", "));
34641
+ field("Organizer", organizer.name ?? c.organizer_name);
34642
+ field("Description", c.description);
34643
+ field("Admission", c.is_paid === false ? "Free" : [fee.amount ?? c.fee_amount, fee.currency ?? c.fee_currency].filter((v) => v !== void 0 && v !== null).join(" "));
34644
+ field("Attendees", c.rsvp_count);
34645
+ field("Event page", c.url);
34299
34646
  break;
34300
34647
  }
34301
34648
  default: {
34302
34649
  for (const [k, v] of Object.entries(c)) {
34303
34650
  if (v === null || v === void 0 || k.startsWith("_")) continue;
34304
34651
  if (typeof v === "object") {
34305
- process.stdout.write(
34652
+ write(
34306
34653
  ` \x1B[2m${k.padEnd(20)}\x1B[0m ${JSON.stringify(v)}
34307
34654
  `
34308
34655
  );
34309
34656
  } else {
34310
- process.stdout.write(
34657
+ write(
34311
34658
  ` \x1B[2m${k.padEnd(20)}\x1B[0m ${String(v)}
34312
34659
  `
34313
34660
  );
@@ -87100,113 +87447,6 @@ function formValue(form, name) {
87100
87447
  return form.fields.find((field) => field.name === name)?.value ?? "";
87101
87448
  }
87102
87449
 
87103
- // src/tuiText.ts
87104
- var segments = new Intl.Segmenter(void 0, { granularity: "grapheme" });
87105
- function stripAnsi2(value) {
87106
- return value.replace(/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\))/g, "");
87107
- }
87108
- function terminalText(value) {
87109
- return stripAnsi2(value).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "").replace(/\t/g, " ");
87110
- }
87111
- function graphemeWidth(value) {
87112
- if (/^[\p{Mark}\p{Cf}]+$/u.test(value)) return 0;
87113
- const cp = value.codePointAt(0) ?? 0;
87114
- if (/\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20e3/u.test(value)) return 2;
87115
- return cp >= 4352 && (cp <= 4447 || cp === 9001 || cp === 9002 || cp >= 11904 && cp <= 42191 && cp !== 12351 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65040 && cp <= 65049 || cp >= 65072 && cp <= 65135 || cp >= 65281 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 131072 && cp <= 262141) ? 2 : 1;
87116
- }
87117
- function cells(value) {
87118
- return [...segments.segment(stripAnsi2(value))].reduce((n, item) => n + graphemeWidth(item.segment), 0);
87119
- }
87120
- function truncateCells(value, width, ellipsis = "\u2026") {
87121
- const clean = terminalText(value).replace(/\n/g, " ");
87122
- if (cells(clean) <= width) return clean;
87123
- const available = Math.max(0, width - cells(ellipsis));
87124
- let result = "", used = 0;
87125
- for (const { segment } of segments.segment(clean)) {
87126
- const size = graphemeWidth(segment);
87127
- if (used + size > available) break;
87128
- result += segment;
87129
- used += size;
87130
- }
87131
- return width > 0 ? result + (cells(ellipsis) <= width ? ellipsis : "") : "";
87132
- }
87133
- function padCells(value, width) {
87134
- const trimmed = truncateCells(value, width);
87135
- return trimmed + " ".repeat(Math.max(0, width - cells(trimmed)));
87136
- }
87137
- function sliceCells(value, start, width) {
87138
- start = Math.max(0, Math.floor(start));
87139
- width = Math.max(0, Math.floor(width));
87140
- const end = start + width;
87141
- let position = 0, result = "";
87142
- for (const { segment } of segments.segment(terminalText(value).replace(/\n/g, " "))) {
87143
- const size = graphemeWidth(segment), next = position + size;
87144
- if (next > start && position < end) {
87145
- result += position >= start && next <= end ? segment : " ".repeat(Math.min(end, next) - Math.max(start, position));
87146
- }
87147
- position = next;
87148
- if (position >= end) break;
87149
- }
87150
- return result + " ".repeat(Math.max(0, width - cells(result)));
87151
- }
87152
- function wrapCells(value, width) {
87153
- width = Math.max(1, width);
87154
- const result = [];
87155
- for (const line of terminalText(value).split("\n")) {
87156
- let text7 = "", used = 0;
87157
- for (const { segment } of segments.segment(line)) {
87158
- const size = graphemeWidth(segment);
87159
- if (size > width) {
87160
- if (text7) result.push(text7);
87161
- result.push("?");
87162
- text7 = "";
87163
- used = 0;
87164
- continue;
87165
- }
87166
- if (used + size > width) {
87167
- result.push(text7);
87168
- text7 = "";
87169
- used = 0;
87170
- }
87171
- text7 += segment;
87172
- used += size;
87173
- }
87174
- result.push(text7);
87175
- }
87176
- return result;
87177
- }
87178
- function eraseGrapheme(value) {
87179
- const items = [...segments.segment(value)];
87180
- return items.length ? value.slice(0, items[items.length - 1].index) : "";
87181
- }
87182
- function moveGraphemeCursor(value, cursor, direction) {
87183
- const boundaries = [...segments.segment(value)].map((item) => item.index);
87184
- boundaries.push(value.length);
87185
- return direction < 0 ? boundaries.filter((index) => index < cursor).at(-1) ?? 0 : boundaries.find((index) => index > cursor) ?? value.length;
87186
- }
87187
- var lineText = (line) => typeof line === "string" ? line : line.text;
87188
- var rgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
87189
- function colorSequence(color, mode, background) {
87190
- if (mode === "none") return "";
87191
- if (mode === "truecolor") return `\x1B[${background ? 48 : 38};2;${color.join(";")}m`;
87192
- if (mode === "ansi256") {
87193
- const level = (v) => Math.round(v / 255 * 5);
87194
- return `\x1B[${background ? 48 : 38};5;${16 + 36 * level(color[0]) + 6 * level(color[1]) + level(color[2])}m`;
87195
- }
87196
- const [r, g, b] = color;
87197
- const index = (r > 100 ? 1 : 0) + (g > 100 ? 2 : 0) + (b > 100 ? 4 : 0);
87198
- return `\x1B[${(background ? 40 : 30) + index}m`;
87199
- }
87200
- function foreground(value, color, mode, bold = false) {
87201
- return mode === "none" ? value : `${bold ? "\x1B[1m" : ""}${colorSequence(rgb(color), mode, false)}${value}\x1B[0m`;
87202
- }
87203
- function backgroundLine(value, width, color, mode, bold = false) {
87204
- const line = padCells(value, width);
87205
- if (mode === "none") return line;
87206
- const white = mode === "ansi16" ? "\x1B[97m" : colorSequence([255, 255, 255], mode, false);
87207
- return `${bold ? "\x1B[1m" : ""}${white}${colorSequence(rgb(color), mode, true)}${line}\x1B[0m`;
87208
- }
87209
-
87210
87450
  // src/tuiProjectsWorkspace.ts
87211
87451
  function contextFor(client2, project) {
87212
87452
  const teamId = project ? project.teamId : client2.getActiveTeamId();
@@ -88431,7 +88671,7 @@ async function refreshTuiChatSidebar(state, client2, render, includeProjects = f
88431
88671
  render();
88432
88672
  }
88433
88673
  } catch {
88434
- if (current()) {
88674
+ if (current() && !state.status) {
88435
88675
  state.status = "Running chat status unavailable.";
88436
88676
  render();
88437
88677
  }
@@ -88724,6 +88964,9 @@ function centeredCarouselText(text7, width) {
88724
88964
  return " ".repeat(Math.max(0, Math.floor((width - cells(value)) / 2))) + value;
88725
88965
  }
88726
88966
 
88967
+ // src/tuiHome.ts
88968
+ import { createHash as createHash21 } from "crypto";
88969
+
88727
88970
  // ../workspaceInspirationDefaults.ts
88728
88971
  function getWorkspaceInspirations(surface) {
88729
88972
  const now = Math.floor(Date.now() / 1e3);
@@ -88924,7 +89167,235 @@ function getWorkspaceInspirations(surface) {
88924
89167
  ];
88925
89168
  }
88926
89169
 
89170
+ // ../ui/src/services/continueCarouselService.ts
89171
+ var HOUR_MS = 60 * 60 * 1e3;
89172
+ var DAY_MS = 24 * HOUR_MS;
89173
+ var UPCOMING_WINDOW_MS = 24 * HOUR_MS;
89174
+ var RECENT_WINDOW_MS = 12 * HOUR_MS;
89175
+ function asString(value) {
89176
+ return typeof value === "string" ? value.trim() : "";
89177
+ }
89178
+ function parseTime(value) {
89179
+ const raw = asString(value);
89180
+ if (!raw) return null;
89181
+ const parsed = new Date(raw).getTime();
89182
+ return Number.isNaN(parsed) ? null : parsed;
89183
+ }
89184
+ function parseDateOnlyEnd(value) {
89185
+ const raw = asString(value);
89186
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(raw)) return null;
89187
+ const parsed = (/* @__PURE__ */ new Date(`${raw}T23:59:59.999`)).getTime();
89188
+ return Number.isNaN(parsed) ? null : parsed;
89189
+ }
89190
+ function formatRelativeLabel(prefix, timestamp, nowMs) {
89191
+ const diffMs = timestamp - nowMs;
89192
+ const absMinutes = Math.max(1, Math.round(Math.abs(diffMs) / 6e4));
89193
+ if (absMinutes < 60) {
89194
+ return diffMs >= 0 ? `${prefix} in ${absMinutes}m` : `${prefix} ${absMinutes}m ago`;
89195
+ }
89196
+ const hours = Math.max(1, Math.round(absMinutes / 60));
89197
+ if (hours < 24) {
89198
+ return diffMs >= 0 ? `${prefix} in ${hours}h` : `${prefix} ${hours}h ago`;
89199
+ }
89200
+ const days = Math.max(1, Math.round(hours / 24));
89201
+ return diffMs >= 0 ? `${prefix} in ${days}d` : `${prefix} ${days}d ago`;
89202
+ }
89203
+ function formatDateOnlyLabel(prefix, timestamp, nowMs) {
89204
+ const targetDate = new Date(timestamp);
89205
+ const currentDate = new Date(nowMs);
89206
+ targetDate.setHours(0, 0, 0, 0);
89207
+ currentDate.setHours(0, 0, 0, 0);
89208
+ const dayDifference = Math.round((targetDate.getTime() - currentDate.getTime()) / DAY_MS);
89209
+ if (dayDifference === 0) return `${prefix} today`;
89210
+ if (dayDifference === 1) return `${prefix} tomorrow`;
89211
+ if (dayDifference === -1) return `${prefix} yesterday`;
89212
+ return dayDifference > 0 ? `${prefix} in ${dayDifference}d` : `${prefix} ${Math.abs(dayDifference)}d ago`;
89213
+ }
89214
+ function buildReminderPriority(reminder, nowMs = Date.now()) {
89215
+ const triggerMs = reminder.trigger_at * 1e3;
89216
+ if (!Number.isFinite(triggerMs) || triggerMs <= 0) return null;
89217
+ if (triggerMs <= nowMs) {
89218
+ if (nowMs - triggerMs > RECENT_WINDOW_MS && reminder.status !== "pending") return null;
89219
+ return {
89220
+ reason: "reminder_due",
89221
+ label: formatRelativeLabel("Reminder", triggerMs, nowMs),
89222
+ timestamp: triggerMs
89223
+ };
89224
+ }
89225
+ if (triggerMs - nowMs > UPCOMING_WINDOW_MS) return null;
89226
+ return {
89227
+ reason: "reminder_soon",
89228
+ label: formatRelativeLabel("Reminder", triggerMs, nowMs),
89229
+ timestamp: triggerMs
89230
+ };
89231
+ }
89232
+ function getSavedItemTimeWindow(itemValue) {
89233
+ const exactStartMs = parseTime(itemValue.date_start) ?? parseTime(itemValue.departure) ?? parseTime(itemValue.start_time) ?? parseTime(itemValue.starts_at) ?? parseTime(itemValue.datetime) ?? parseTime(itemValue.appointment_time);
89234
+ const dateOnlyStartMs = exactStartMs === null ? parseDateOnlyEnd(itemValue.date) : null;
89235
+ const startMs = exactStartMs ?? dateOnlyStartMs ?? parseTime(itemValue.available_from);
89236
+ const endMs = parseTime(itemValue.date_end) ?? parseTime(itemValue.arrival) ?? parseTime(itemValue.end_time) ?? parseTime(itemValue.ends_at) ?? parseTime(itemValue.checkout) ?? parseTime(itemValue.available_until);
89237
+ return { startMs, endMs, startIsDateOnly: dateOnlyStartMs !== null };
89238
+ }
89239
+ function getSavedItemPriority(itemValue, nowMs, itemLabel) {
89240
+ const { startMs, endMs, startIsDateOnly } = getSavedItemTimeWindow(itemValue);
89241
+ if (endMs && endMs >= nowMs && (!startMs || startMs <= nowMs)) {
89242
+ return {
89243
+ reason: "ongoing_saved_item",
89244
+ label: `${itemLabel} ongoing`,
89245
+ timestamp: endMs
89246
+ };
89247
+ }
89248
+ if (!startMs) return null;
89249
+ const startedRecently = startMs <= nowMs && nowMs - startMs <= RECENT_WINDOW_MS;
89250
+ const startsSoon = startMs > nowMs && startMs - nowMs <= UPCOMING_WINDOW_MS;
89251
+ if (!startedRecently && !startsSoon) return null;
89252
+ return {
89253
+ reason: "upcoming_saved_item",
89254
+ label: startIsDateOnly ? formatDateOnlyLabel(itemLabel, startMs, nowMs) : formatRelativeLabel(itemLabel, startMs, nowMs),
89255
+ timestamp: startMs
89256
+ };
89257
+ }
89258
+ function getSavedItemPresentation(appId, groupName, itemValue) {
89259
+ if (appId === "travel" || groupName.includes("connection")) {
89260
+ return {
89261
+ icon: "plane",
89262
+ category: "travel",
89263
+ itemLabel: "Trip",
89264
+ summary: [asString(itemValue.origin), asString(itemValue.destination), asString(itemValue.notes)].filter(Boolean).join(" \xB7 ") || null
89265
+ };
89266
+ }
89267
+ if (appId === "events" || groupName.includes("event")) {
89268
+ return {
89269
+ icon: "calendar-days",
89270
+ category: "events",
89271
+ itemLabel: "Event",
89272
+ summary: [asString(itemValue.location), asString(itemValue.provider)].filter(Boolean).join(" \xB7 ") || null
89273
+ };
89274
+ }
89275
+ if (appId === "health") {
89276
+ return {
89277
+ icon: "stethoscope",
89278
+ category: "health",
89279
+ itemLabel: "Appointment",
89280
+ summary: asString(itemValue.notes) || null
89281
+ };
89282
+ }
89283
+ if (appId === "home") {
89284
+ return {
89285
+ icon: "house",
89286
+ category: "home",
89287
+ itemLabel: "Listing",
89288
+ summary: [asString(itemValue.location), asString(itemValue.notes)].filter(Boolean).join(" \xB7 ") || null
89289
+ };
89290
+ }
89291
+ return {
89292
+ icon: "bookmark",
89293
+ category: null,
89294
+ itemLabel: "Saved item",
89295
+ summary: asString(itemValue.notes) || null
89296
+ };
89297
+ }
89298
+ function getReminderByTargetChatId(reminders, nowMs = Date.now()) {
89299
+ const byChat = /* @__PURE__ */ new Map();
89300
+ for (const reminder of reminders) {
89301
+ const chatId = asString(reminder.target_chat_id);
89302
+ if (!chatId) continue;
89303
+ const priority = buildReminderPriority(reminder, nowMs);
89304
+ if (!priority) continue;
89305
+ const existing = byChat.get(chatId);
89306
+ if (!existing || compareContinuePriority(priority, existing.priority, nowMs) < 0) {
89307
+ byChat.set(chatId, { reminder, priority });
89308
+ }
89309
+ }
89310
+ return byChat;
89311
+ }
89312
+ function getReminderByTargetEmbedId(reminders, nowMs = Date.now()) {
89313
+ const byEmbed = /* @__PURE__ */ new Map();
89314
+ for (const reminder of reminders) {
89315
+ const embedId = asString(reminder.target_embed_id);
89316
+ if (!embedId) continue;
89317
+ const priority = buildReminderPriority(reminder, nowMs);
89318
+ if (!priority) continue;
89319
+ const existing = byEmbed.get(embedId);
89320
+ if (!existing || compareContinuePriority(priority, existing.priority, nowMs) < 0) {
89321
+ byEmbed.set(embedId, { reminder, priority });
89322
+ }
89323
+ }
89324
+ return byEmbed;
89325
+ }
89326
+ function getSavedEmbedContinueCandidates(state, remindersByEmbedId = /* @__PURE__ */ new Map(), nowMs = Date.now()) {
89327
+ const candidates = [];
89328
+ for (const [appId, groups] of Array.from(state.entriesByApp.entries())) {
89329
+ for (const [groupName, entries] of Object.entries(groups)) {
89330
+ for (const entry of entries) {
89331
+ const itemValue = entry.item_value || {};
89332
+ const embedId = asString(itemValue.embed_id);
89333
+ if (!embedId) continue;
89334
+ const presentation = getSavedItemPresentation(appId, groupName, itemValue);
89335
+ const reminderMatch = remindersByEmbedId.get(embedId);
89336
+ const { startMs, endMs } = getSavedItemTimeWindow(itemValue);
89337
+ const priority = startMs || endMs ? getSavedItemPriority(itemValue, nowMs, presentation.itemLabel) : reminderMatch?.priority;
89338
+ if (!priority) continue;
89339
+ candidates.push({
89340
+ kind: "embed",
89341
+ embedId,
89342
+ appId,
89343
+ title: asString(itemValue.title) || asString(itemValue.name) || presentation.itemLabel,
89344
+ summary: presentation.summary,
89345
+ icon: presentation.icon,
89346
+ category: presentation.category,
89347
+ itemValue,
89348
+ priority
89349
+ });
89350
+ }
89351
+ }
89352
+ }
89353
+ return sortContinuePriorityItems(candidates, nowMs);
89354
+ }
89355
+ function priorityRank(priority) {
89356
+ switch (priority.reason) {
89357
+ case "reminder_due":
89358
+ return 0;
89359
+ case "reminder_soon":
89360
+ return 1;
89361
+ case "ongoing_saved_item":
89362
+ return 2;
89363
+ case "upcoming_saved_item":
89364
+ return 3;
89365
+ }
89366
+ }
89367
+ function compareContinuePriority(a, b, nowMs = Date.now()) {
89368
+ const rankDiff = priorityRank(a) - priorityRank(b);
89369
+ if (rankDiff !== 0) return rankDiff;
89370
+ if (a.reason === "reminder_due") {
89371
+ return Math.abs(nowMs - a.timestamp) - Math.abs(nowMs - b.timestamp);
89372
+ }
89373
+ return Math.abs(a.timestamp - nowMs) - Math.abs(b.timestamp - nowMs);
89374
+ }
89375
+ function sortContinuePriorityItems(items, nowMs = Date.now()) {
89376
+ return [...items].sort((a, b) => compareContinuePriority(a.priority, b.priority, nowMs));
89377
+ }
89378
+
88927
89379
  // src/tuiHome.ts
89380
+ function homeContinueItems(state, now = Date.now()) {
89381
+ const chats = homeChatItems(state);
89382
+ if (!state.signedIn || !state.continueData) return chats.map((chat) => ({ kind: "chat", chat }));
89383
+ const entriesByApp = /* @__PURE__ */ new Map();
89384
+ for (const memory of state.continueData.memories) {
89385
+ const groups = entriesByApp.get(memory.app_id) ?? {};
89386
+ const group = memory.item_type;
89387
+ (groups[group] ??= []).push({ id: memory.id, item_key: memory.item_key_hash, item_value: memory.data, settings_group: group });
89388
+ entriesByApp.set(memory.app_id, groups);
89389
+ }
89390
+ const reminders = state.continueData.reminders;
89391
+ const reminderChats = getReminderByTargetChatId(reminders, now);
89392
+ const priorityChats = chats.filter((chat) => reminderChats.has(chat.id) && !chat.parentId && !chat.isSubChat && chat.id !== state.activeChatId).map((chat) => ({ kind: "chat", chat, priority: reminderChats.get(chat.id).priority }));
89393
+ const embeds = getSavedEmbedContinueCandidates({ entriesByApp }, getReminderByTargetEmbedId(reminders, now), now).filter((item) => `${item.title} ${item.summary ?? ""} ${item.priority.label}`.toLowerCase().includes(state.filter.toLowerCase()));
89394
+ const priority = sortContinuePriorityItems([...priorityChats, ...embeds], now).slice(0, 10);
89395
+ const promoted = new Set(priority.filter((item) => item.kind === "chat").map((item) => item.chat.id));
89396
+ return [...priority, ...chats.filter((chat) => !promoted.has(chat.id)).map((chat) => ({ kind: "chat", chat }))];
89397
+ }
89398
+ var homeItemId = (item) => item?.kind === "embed" ? `embed:${item.embedId}` : item ? `chat:${item.chat.id}` : void 0;
88928
89399
  var BLUE = { start: "#4867cd", end: "#5a85eb" };
88929
89400
  var prompts = {
88930
89401
  chats: "What do you need help with?",
@@ -88949,12 +89420,12 @@ function currentInspiration(state) {
88949
89420
  }
88950
89421
  function updateHomeChats(state, chats) {
88951
89422
  const preserveSelection = isWorkspaceHome(state) && state.workspace === "chats";
88952
- const selectedId = preserveSelection ? homeChatItems(state)[state.selectedIndex]?.id : void 0;
89423
+ const selectedId = preserveSelection ? homeItemId(homeContinueItems(state)[state.selectedIndex]) : void 0;
88953
89424
  updateTuiChatSidebar(state, () => {
88954
89425
  state.recentChats = chats;
88955
89426
  });
88956
89427
  if (preserveSelection) {
88957
- const items = homeChatItems(state), index = items.findIndex((chat) => chat.id === selectedId);
89428
+ const items = homeContinueItems(state), index = items.findIndex((item) => homeItemId(item) === selectedId);
88958
89429
  state.selectedIndex = index >= 0 ? index : Math.max(0, Math.min(state.selectedIndex, items.length - 1));
88959
89430
  }
88960
89431
  }
@@ -88963,7 +89434,26 @@ async function loadHomeData(state, client2, render) {
88963
89434
  const controller = new AbortController();
88964
89435
  state.homeAbortController = controller;
88965
89436
  const request = ++state.homeLoadVersion, signedIn = state.signedIn;
88966
- const current = () => !controller.signal.aborted && request === state.homeLoadVersion && state.signedIn === signedIn;
89437
+ const teamId = typeof client2.getActiveTeamId === "function" ? client2.getActiveTeamId() : null;
89438
+ const masterKey = signedIn && typeof client2.getMasterKeyBytes === "function" ? Buffer.from(client2.getMasterKeyBytes()) : null;
89439
+ const contextKey = `${teamId ?? "personal"}:${masterKey ? createHash21("sha256").update(masterKey).digest("hex") : signedIn}`;
89440
+ if (state.homeContextKey && state.homeContextKey !== contextKey) {
89441
+ state.continueData = null;
89442
+ state.recentChats = [];
89443
+ state.activityChats = [];
89444
+ state.sidebarLinkedChats = [];
89445
+ state.chatSidebarProjects = [];
89446
+ }
89447
+ state.homeContextKey = contextKey;
89448
+ const current = () => {
89449
+ if (controller.signal.aborted || request !== state.homeLoadVersion || state.signedIn !== signedIn) return false;
89450
+ if (typeof client2.getActiveTeamId === "function" && client2.getActiveTeamId() !== teamId) return false;
89451
+ try {
89452
+ return !masterKey || (typeof client2.hasSession !== "function" || client2.hasSession()) && masterKey.equals(Buffer.from(client2.getMasterKeyBytes()));
89453
+ } catch {
89454
+ return false;
89455
+ }
89456
+ };
88967
89457
  state.homeLoading = true;
88968
89458
  state.homeError = null;
88969
89459
  state.homeChatsLoading = signedIn && homeChatItems(state).length === 0;
@@ -88978,7 +89468,58 @@ async function loadHomeData(state, client2, render) {
88978
89468
  state.homeChatsLoading = homeChatItems(state).length === 0;
88979
89469
  render();
88980
89470
  }
89471
+ const updateContinue = (data) => {
89472
+ const selected = homeItemId(homeContinueItems(state)[state.selectedIndex]);
89473
+ state.continueData = data;
89474
+ if (state.homeSelectionMoved || state.selectedIndex > 0) {
89475
+ const index = homeContinueItems(state).findIndex((item) => homeItemId(item) === selected);
89476
+ state.selectedIndex = index < 0 ? 0 : index;
89477
+ } else state.selectedIndex = 0;
89478
+ render();
89479
+ };
89480
+ if (signedIn && typeof client2.getCachedContinueItems === "function") {
89481
+ try {
89482
+ const cached = await client2.getCachedContinueItems();
89483
+ if (!current()) return;
89484
+ if (cached) updateContinue(cached);
89485
+ } catch {
89486
+ if (!current()) return;
89487
+ }
89488
+ }
88981
89489
  const work = [];
89490
+ let continueRefresh;
89491
+ const refreshContinue = () => {
89492
+ if (!continueRefresh && signedIn && typeof client2.getContinueItems === "function") {
89493
+ continueRefresh = client2.getContinueItems().then((data) => {
89494
+ if (!current()) return;
89495
+ updateContinue(data);
89496
+ if (typeof client2.getEmbed !== "function") return;
89497
+ const saved = homeContinueItems(state).filter((item) => item.kind === "embed").slice(0, 10);
89498
+ let next = 0;
89499
+ void Promise.all(Array.from({ length: Math.min(3, saved.length) }, async () => {
89500
+ while (current() && next < saved.length) {
89501
+ const item = saved[next++];
89502
+ try {
89503
+ await client2.getEmbed(item.embedId, { preferCache: true });
89504
+ } catch {
89505
+ if (!current()) return;
89506
+ }
89507
+ }
89508
+ }));
89509
+ }).catch(() => {
89510
+ });
89511
+ }
89512
+ return continueRefresh ?? Promise.resolve();
89513
+ };
89514
+ if (!teamId) work.push(refreshContinue());
89515
+ let warming;
89516
+ const warmRecent = () => {
89517
+ if (!current() || warming || typeof client2.cacheRecentChatMessages !== "function") return;
89518
+ warming = client2.cacheRecentChatMessages({ signal: controller.signal }).catch(() => {
89519
+ }).finally(() => {
89520
+ warming = void 0;
89521
+ });
89522
+ };
88982
89523
  if (typeof client2.getDailyInspirations === "function") work.push(client2.getDailyInspirations().then((list) => {
88983
89524
  if (current()) {
88984
89525
  state.inspirations = list;
@@ -89003,10 +89544,15 @@ async function loadHomeData(state, client2, render) {
89003
89544
  updateHomeChats(state, page.chats);
89004
89545
  state.homeChatsLoading = false;
89005
89546
  render();
89547
+ warmRecent();
89548
+ if (teamId) void refreshContinue();
89006
89549
  }
89007
- } }).then((page) => {
89550
+ } }).then(async (page) => {
89008
89551
  if (current()) {
89009
89552
  updateHomeChats(state, page.chats);
89553
+ warmRecent();
89554
+ if (teamId) await refreshContinue();
89555
+ if (!current()) return;
89010
89556
  if (page.pendingRecoveryOutputs) state.status = "Some saved AI outputs are pending recovery. Use /refresh to retry.";
89011
89557
  render();
89012
89558
  }
@@ -89031,6 +89577,19 @@ async function loadHomeData(state, client2, render) {
89031
89577
  render();
89032
89578
  }
89033
89579
  }
89580
+ function startHomeSync(state, client2, render, closed) {
89581
+ let refreshing = false;
89582
+ const timer = setInterval(() => {
89583
+ if (closed() || state.startup || !state.signedIn || state.homeLoading || refreshing) return;
89584
+ refreshing = true;
89585
+ void loadHomeData(state, client2, render).catch(() => {
89586
+ }).finally(() => {
89587
+ refreshing = false;
89588
+ });
89589
+ }, 6e4);
89590
+ timer.unref?.();
89591
+ return () => clearInterval(timer);
89592
+ }
89034
89593
  var centered2 = centeredCarouselText;
89035
89594
  function homeHeader(state, width, height) {
89036
89595
  const inspiration = currentInspiration(state), rows2 = Math.max(4, Math.min(8, Math.floor(height / 5))), result = [];
@@ -89048,25 +89607,34 @@ function homeHeader(state, width, height) {
89048
89607
  return result;
89049
89608
  }
89050
89609
  function renderHomeChatCards(state, width, height) {
89051
- const chats = homeChatItems(state), result = homeHeader(state, width, height);
89610
+ const items = homeContinueItems(state), result = homeHeader(state, width, height);
89052
89611
  const activeCount = runningTuiChatGroups(state).length;
89053
89612
  if (activeCount) result.push(centered2(`${activeCount} ${activeCount === 1 ? "chat" : "chats"} active\u2026 /active`, width), "");
89054
- if (!chats.length) {
89613
+ if (!items.length) {
89055
89614
  result.push(centered2(state.homeChatsLoading ? "Loading your recent chats\u2026" : state.homeError || "Start a chat below. Your recent chats will appear here.", width), "");
89056
89615
  return result;
89057
89616
  }
89058
89617
  result.push(centered2(state.signedIn ? "Continue where you left off" : "Explore example chats", width), "");
89059
- const selected = Math.max(0, Math.min(chats.length - 1, state.selectedIndex));
89060
- result.push(...renderCardCarousel(chats.map((chat) => ({
89061
- title: chat.title || chat.draftPreview || "Untitled chat",
89062
- description: chat.hasDraft ? "Draft" : chat.summary || "Continue this conversation",
89063
- footer: chat.category?.replaceAll("_", " ") || "Chat",
89064
- background: (chat.category && CATEGORY_GRADIENTS[chat.category] || BLUE).start
89065
- })), width, selected, state.focus === "content"));
89618
+ const selected = Math.max(0, Math.min(items.length - 1, state.selectedIndex));
89619
+ result.push(...renderCardCarousel(items.map((item) => {
89620
+ if (item.kind === "embed") return {
89621
+ title: item.title,
89622
+ description: item.summary || "Open saved item",
89623
+ footer: `${item.priority.label} \xB7 Saved`,
89624
+ background: (item.category && CATEGORY_GRADIENTS[item.category] || BLUE).start
89625
+ };
89626
+ const chat = item.chat;
89627
+ return {
89628
+ title: chat.title || chat.draftPreview || "Untitled chat",
89629
+ description: chat.hasDraft ? "Draft" : chat.summary || "Continue this conversation",
89630
+ footer: item.priority?.label || chat.category?.replaceAll("_", " ") || "Chat",
89631
+ background: (chat.category && CATEGORY_GRADIENTS[chat.category] || BLUE).start
89632
+ };
89633
+ }), width, selected, state.focus === "content"));
89066
89634
  result.push(
89067
89635
  "",
89068
- centered2(`${selected > 0 ? "\u2039" : " "} Chat ${selected + 1} of ${chats.length} ${selected < chats.length - 1 ? "\u203A" : " "}`, width),
89069
- centered2("\u2190/\u2192 choose chat \xB7 Enter open \xB7 Tab write", width),
89636
+ centered2(`${selected > 0 ? "\u2039" : " "} ${items.some((item) => item.kind === "embed") ? "Item" : "Chat"} ${selected + 1} of ${items.length} ${selected < items.length - 1 ? "\u203A" : " "}`, width),
89637
+ centered2("\u2190/\u2192 choose \xB7 Enter open \xB7 Tab write", width),
89070
89638
  "",
89071
89639
  centered2("/search Search chats \xB7 Ctrl+N New chat", width)
89072
89640
  );
@@ -89810,6 +90378,12 @@ function createInitialTuiState() {
89810
90378
  homeError: null,
89811
90379
  homeLoadVersion: 0,
89812
90380
  homeShowAll: false,
90381
+ homeSelectionMoved: false,
90382
+ homeContextKey: null,
90383
+ continueData: null,
90384
+ detailEmbed: null,
90385
+ embedOrigin: null,
90386
+ embedChoices: [],
89813
90387
  apps: [],
89814
90388
  activeApp: null,
89815
90389
  activeAppSkill: null,
@@ -90049,8 +90623,12 @@ function renderScreenBody(state, width, height) {
90049
90623
  return state.activeProject ? state.projectTab === "tasks" ? [...blueHero(renderProjectIdentity(state.activeProject, { width }), renderProjectIdentity(state.activeProject, { width }).length), ...renderProjectTabs("tasks", width), "", ...renderTaskBoard2(state.tasks, { width, selectedTaskId: filterTasks(state.tasks, state.filter)[state.selectedIndex]?.taskId, query: state.filter })] : blueHero(renderProjectDetail(state.activeProject, { width, tab: state.projectTab, files: state.projectFiles, selectedFileId: filteredProjectFiles(state.projectFiles, state.filter)[state.selectedIndex]?.id, query: state.filter, folderId: state.projectFolderId ?? void 0, sourceId: state.projectSourceId ?? void 0, path: state.projectPath }), renderProjectIdentity(state.activeProject, { width }).length) : ["Projects", "Loading project\u2026"];
90050
90624
  case "status":
90051
90625
  return renderStatus(state, width);
90052
- case "embed":
90053
- return [state.detailTitle || "Embed", "", ...state.detailLines].flatMap((line) => wrap(line, width));
90626
+ case "embed": {
90627
+ const app = state.detailEmbed?.appId ?? "", gradient = APP_GRADIENTS[app] ?? PRIMARY_GRADIENT;
90628
+ const title = state.detailTitle || "Embed";
90629
+ const header2 = [title, state.detailEmbed ? `${state.detailEmbed.type?.replaceAll("-", " ") ?? "Saved item"} \xB7 ${state.detailEmbed.embedId.slice(0, 8)}` : "Saved embeds"];
90630
+ return [...coloredHero(header2, header2.length, gradient), "", ...(state.embedChoices.length ? state.embedChoices.map((id, index) => `${index === state.selectedIndex ? ">" : " "} Embed ${index + 1} \xB7 ${id.slice(0, 8)} \xB7 Enter open`) : state.detailLines).flatMap((line) => wrap(line, width)), "", "\u2191/\u2193 scroll \xB7 PgUp/PgDn page \xB7 Esc back"];
90631
+ }
90054
90632
  case "workflows":
90055
90633
  return [...homeHeader(state, width, height), ...homeCards(state.workflows.filter((w) => w.title.toLowerCase().includes(state.filter.toLowerCase())).flatMap((w, i) => [...renderWorkflowPreviewCard(w, { width: Math.min(width, 88), selected: state.focus === "content" && i === state.selectedIndex }), ""]), width, []), "Show my workflows \xB7 /search Search"];
90056
90634
  case "workflow":
@@ -90230,7 +90808,7 @@ function renderChat(state, width) {
90230
90808
  }
90231
90809
  function renderChatHeader(state, width) {
90232
90810
  const chat = state.screen === "example" ? state.activeExample?.chat : state.activeChat;
90233
- const title = state.headerState === "loading" && !chat?.title ? "Creating new chat\u2026" : state.headerState === "error" ? state.headerError || "Could not send message" : chat?.title || (state.messages.length ? "Chat" : "New chat");
90811
+ const title = state.headerState === "loading" && !chat?.title ? state.status === "Loading chat\u2026" ? "Loading chat\u2026" : "Creating new chat\u2026" : state.headerState === "error" ? state.headerError || "Could not send message" : chat?.title || (state.messages.length ? "Chat" : "New chat");
90234
90812
  const category = chat?.category?.replaceAll("_", " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
90235
90813
  const summary = chat?.summary || (state.messages.length ? "" : "What would you like to work on?");
90236
90814
  const badges = state.screen === "example" ? "Example chat" : state.input && !state.messages.length ? "Draft" : "";
@@ -90521,6 +91099,7 @@ function route(state, workspace, screen) {
90521
91099
  state.selectedIndex = 0;
90522
91100
  state.sidebarIndex = 0;
90523
91101
  state.homeShowAll = false;
91102
+ state.homeSelectionMoved = false;
90524
91103
  state.status = null;
90525
91104
  state.form = null;
90526
91105
  state.workflowEdit = null;
@@ -90553,23 +91132,45 @@ async function recent(context) {
90553
91132
  async function openSavedChat(context, id) {
90554
91133
  const { state, client: client2, render } = context;
90555
91134
  const request = route(state, "chats", "chat");
90556
- state.status = "Loading chat\u2026";
90557
- render();
90558
- const result = await client2.getChatMessages(id);
90559
- if (state.routeVersion !== request) return;
90560
- state.activeChatId = result.chat.id;
90561
- state.activeChat = result.chat;
91135
+ state.activeChatId = id;
91136
+ state.activeChat = [...state.recentChats, ...state.sidebarLinkedChats, ...state.activityChats].find((chat) => chat.id === id) ?? null;
90562
91137
  state.activeExample = null;
90563
- state.selectedProjectId = null;
90564
- state.messages = result.messages.map((m) => ({ id: m.id, role: m.role === "user" ? "user" : m.role === "system" ? "system" : "assistant", content: m.content, title: m.senderName, embedIds: m.embedIds }));
90565
- state.projectFocusPending = null;
90566
- state.headerState = "ready";
90567
- state.headerError = null;
90568
- const remoteDraft = state.drafts[id] === void 0 && typeof client2.getDraft === "function" ? await client2.getDraft(result.chat.id) : null;
90569
- if (state.routeVersion !== request) return;
90570
- state.input = state.drafts[result.chat.id] ?? remoteDraft?.markdown ?? "";
90571
- state.status = null;
91138
+ state.messages = [];
91139
+ state.headerState = state.activeChat ? "ready" : "loading";
91140
+ state.status = "Loading chat\u2026";
90572
91141
  render();
91142
+ let published = false;
91143
+ const publish = (result2, pending = false) => {
91144
+ if (state.routeVersion !== request) return;
91145
+ state.activeChatId = result2.chat.id;
91146
+ state.activeChat = result2.chat;
91147
+ state.activeExample = null;
91148
+ state.selectedProjectId = null;
91149
+ state.messages = result2.messages.map((m) => ({ id: m.id, role: m.role === "user" ? "user" : m.role === "system" ? "system" : "assistant", content: m.content, title: m.senderName, embedIds: m.embedIds }));
91150
+ state.projectFocusPending = null;
91151
+ state.headerState = "ready";
91152
+ state.headerError = null;
91153
+ if (!published) state.input = state.drafts[result2.chat.id] ?? "";
91154
+ state.status = pending ? "Loading older messages\u2026" : result2.historyIncomplete ? "Showing cached recent messages. Older history is unavailable offline." : null;
91155
+ render();
91156
+ if (!published && state.drafts[result2.chat.id] === void 0) {
91157
+ const inputAtOpen = state.input;
91158
+ const draft = typeof client2.getCachedDraft === "function" ? client2.getCachedDraft(result2.chat.id) : typeof client2.getDraft === "function" ? client2.getDraft(result2.chat.id) : Promise.resolve(null);
91159
+ void draft.then((remoteDraft) => {
91160
+ if (state.routeVersion !== request || state.input !== inputAtOpen || state.drafts[result2.chat.id] !== void 0) return;
91161
+ state.input = remoteDraft?.markdown ?? "";
91162
+ render();
91163
+ }).catch(() => {
91164
+ if (state.routeVersion === request && !state.status) {
91165
+ state.status = "Saved draft unavailable.";
91166
+ render();
91167
+ }
91168
+ });
91169
+ }
91170
+ published = true;
91171
+ };
91172
+ const result = await client2.getChatMessages(id, { preferCache: true, onMessages: (latest) => publish(latest, true) });
91173
+ publish(result);
90573
91174
  }
90574
91175
  async function openProject(context, id) {
90575
91176
  const { state, client: client2, render } = context;
@@ -90978,7 +91579,8 @@ async function handleWorkspaceCommand(context, command) {
90978
91579
  case "/chat-create-project": {
90979
91580
  if (state.chatProjectBusy) return true;
90980
91581
  if (!state.signedIn) throw new Error("Sign in to organize chats.");
90981
- const selected = state.focus === "sidebar" ? tuiChatSidebarRows(state)[state.sidebarIndex]?.chatId : ["start", "chats"].includes(state.screen) ? homeChatItems(state)[state.selectedIndex]?.id : state.activeChatId;
91582
+ const homeItem = homeContinueItems(state)[state.selectedIndex];
91583
+ const selected = state.focus === "sidebar" ? tuiChatSidebarRows(state)[state.sidebarIndex]?.chatId : ["start", "chats"].includes(state.screen) ? homeItem?.kind === "chat" ? homeItem.chat.id : void 0 : state.activeChatId;
90982
91584
  const ids = arg ? arg.split(/\s+/).filter(Boolean) : selected ? [selected] : [];
90983
91585
  if (!ids.length) throw new Error("Choose a saved chat first.");
90984
91586
  await refreshTuiChatSidebar(state, client2, render, true);
@@ -91114,29 +91716,47 @@ async function handleWorkspaceCommand(context, command) {
91114
91716
  return true;
91115
91717
  }
91116
91718
  case "/embed": {
91719
+ if (state.screen !== "embed") state.embedOrigin = { screen: state.screen, workspace: state.workspace, focus: state.focus, selectedIndex: state.selectedIndex, scrollOffset: state.scrollOffset, filter: state.filter, input: state.input };
91117
91720
  const request = route(state, state.workspace, "embed");
91118
91721
  state.detailTitle = "Embeds";
91722
+ state.detailEmbed = null;
91723
+ state.focus = "content";
91724
+ state.detailLines = ["Loading saved embed\u2026"];
91725
+ render();
91119
91726
  if (!arg) {
91120
91727
  const ids = [...new Set(state.messages.flatMap((message) => message.embedIds ?? []))];
91728
+ state.embedChoices = ids;
91121
91729
  state.detailLines = ids.length ? ids.map((id) => `/embed ${id}`) : ["No saved embeds in this chat yet."];
91122
91730
  } else {
91123
- const example = state.activeExample?.embeds?.find((embed2) => embed2.embed_id === arg);
91124
- const content = example ? parseEmbedContentObject(example.content) : null;
91125
- const embed = example && content ? {
91126
- id: arg,
91127
- embedId: arg,
91128
- type: example.type,
91129
- content,
91130
- textPreview: null,
91131
- appId: typeof content.app_id === "string" ? content.app_id : null,
91132
- skillId: typeof content.skill_id === "string" ? content.skill_id : null,
91133
- createdAt: null
91134
- } : await client2.getEmbed(arg);
91135
- if (request !== state.routeVersion) return true;
91136
- state.detailTitle = embed.textPreview || (embed.appId ? `${embed.appId}${embed.skillId ? `/${embed.skillId}` : ""}` : embed.type?.replaceAll("_", " ")) || "Embed";
91137
- const body = embed.content;
91138
- const text7 = ["code", "markdown", "content", "text", "transcript"].map((key) => body?.[key]).find((value) => typeof value === "string");
91139
- state.detailLines = typeof text7 === "string" ? text7.split("\n") : formatEmbedPreviewLines(embed, 100);
91731
+ try {
91732
+ const example = state.activeExample?.embeds?.find((embed2) => embed2.embed_id === arg);
91733
+ const content = example ? parseEmbedContentObject(example.content) : null;
91734
+ const embed = example && content ? {
91735
+ id: arg,
91736
+ embedId: arg,
91737
+ type: example.type,
91738
+ content,
91739
+ textPreview: null,
91740
+ appId: typeof content.app_id === "string" ? content.app_id : null,
91741
+ skillId: typeof content.skill_id === "string" ? content.skill_id : null,
91742
+ createdAt: null
91743
+ } : await client2.getEmbed(arg, { preferCache: true, chatId: state.activeChatId ?? void 0 });
91744
+ if (request !== state.routeVersion) return true;
91745
+ state.detailTitle = embed.textPreview || (embed.appId ? `${embed.appId}${embed.skillId ? `/${embed.skillId}` : ""}` : embed.type?.replaceAll("_", " ")) || "Embed";
91746
+ state.detailEmbed = embed;
91747
+ state.embedChoices = [];
91748
+ const cachedClient = Object.create(client2);
91749
+ cachedClient.getEmbed = (id, options) => client2.getEmbed(id, { ...options, preferCache: true, chatId: state.activeChatId ?? void 0 });
91750
+ const lines = await formatEmbedFullscreenLines(embed, cachedClient);
91751
+ if (request !== state.routeVersion) return true;
91752
+ state.detailLines = lines;
91753
+ } catch (error) {
91754
+ if (request !== state.routeVersion) return true;
91755
+ state.detailTitle = "Saved embed unavailable";
91756
+ state.detailLines = [error instanceof Error ? error.message : String(error), "", "Reconnect and use /refresh to retry."];
91757
+ state.detailEmbed = null;
91758
+ state.status = null;
91759
+ }
91140
91760
  }
91141
91761
  render();
91142
91762
  return true;
@@ -91343,6 +91963,11 @@ async function handleWorkspaceKey(context, chunk, key) {
91343
91963
  render();
91344
91964
  return true;
91345
91965
  }
91966
+ if (state.screen === "embed" && state.embedChoices.length && state.focus === "content" && ["up", "down"].includes(key.name ?? "")) {
91967
+ state.selectedIndex = Math.max(0, Math.min(state.embedChoices.length - 1, state.selectedIndex + (key.name === "up" ? -1 : 1)));
91968
+ render();
91969
+ return true;
91970
+ }
91346
91971
  if (key.ctrl && key.name === "b") {
91347
91972
  await handleWorkspaceCommand(context, "/sidebar");
91348
91973
  return true;
@@ -91430,8 +92055,24 @@ async function handleWorkspaceKey(context, chunk, key) {
91430
92055
  state.focus = "content";
91431
92056
  state.scrollOffset = 0;
91432
92057
  } else if (state.screen === "embed") {
91433
- state.screen = state.workspace === "projects" ? "project" : "chat";
91434
- state.focus = state.workspace === "chats" ? "composer" : "content";
92058
+ const origin = state.embedOrigin;
92059
+ state.embedOrigin = null;
92060
+ state.detailEmbed = null;
92061
+ state.embedChoices = [];
92062
+ if (origin) {
92063
+ state.screen = origin.screen;
92064
+ state.workspace = origin.workspace;
92065
+ state.focus = origin.focus;
92066
+ state.selectedIndex = origin.selectedIndex;
92067
+ state.scrollOffset = origin.scrollOffset;
92068
+ state.filter = origin.filter;
92069
+ state.input = origin.input;
92070
+ state.inputCursor = null;
92071
+ } else {
92072
+ state.screen = state.workspace === "projects" ? "project" : "chats";
92073
+ state.focus = "content";
92074
+ }
92075
+ ++state.routeVersion;
91435
92076
  } else {
91436
92077
  state.input = "";
91437
92078
  state.focus = state.workspace === "chats" ? "composer" : "content";
@@ -91459,7 +92100,8 @@ async function handleWorkspaceKey(context, chunk, key) {
91459
92100
  return true;
91460
92101
  }
91461
92102
  if (carouselHome && state.focus === "content" && (key.name === "left" || key.name === "right")) {
91462
- const count = chatHome ? homeChatItems(state).length : homeTuiApps(state.apps, state.filter, state.homeShowAll).length;
92103
+ const count = chatHome ? homeContinueItems(state).length : homeTuiApps(state.apps, state.filter, state.homeShowAll).length;
92104
+ state.homeSelectionMoved = true;
91463
92105
  state.selectedIndex = Math.max(0, Math.min(Math.max(0, count - 1), state.selectedIndex + (key.name === "left" ? -1 : 1)));
91464
92106
  state.followSelection = true;
91465
92107
  render();
@@ -91676,7 +92318,7 @@ async function handleWorkspaceKey(context, chunk, key) {
91676
92318
  return true;
91677
92319
  }
91678
92320
  if (state.focus === "content" && (key.name === "up" || key.name === "down") && ["start", "tasks", "projects", "project", "chats"].includes(state.screen)) {
91679
- const count = state.screen === "tasks" || state.screen === "project" && state.projectTab === "tasks" ? filterTasks(state.tasks, state.filter, state.taskStatusFilter || void 0).length : state.screen === "projects" ? filteredProjects(state.projects, state.filter).length : state.screen === "project" ? filteredProjectFiles(state.projectFiles, state.filter).length : homeChatItems(state).length;
92321
+ const count = state.screen === "tasks" || state.screen === "project" && state.projectTab === "tasks" ? filterTasks(state.tasks, state.filter, state.taskStatusFilter || void 0).length : state.screen === "projects" ? filteredProjects(state.projects, state.filter).length : state.screen === "project" ? filteredProjectFiles(state.projectFiles, state.filter).length : homeContinueItems(state).length;
91680
92322
  state.selectedIndex = Math.max(0, Math.min(count - 1, state.selectedIndex + (key.name === "up" ? -1 : 1)));
91681
92323
  render();
91682
92324
  return true;
@@ -91693,13 +92335,19 @@ async function handleWorkspaceKey(context, chunk, key) {
91693
92335
  return true;
91694
92336
  }
91695
92337
  if (state.screen === "chats" || state.screen === "start") {
91696
- const chat = homeChatItems(state)[state.selectedIndex];
91697
- if (chat) {
92338
+ const item = homeContinueItems(state)[state.selectedIndex];
92339
+ if (item?.kind === "embed") await context.command(`/embed ${item.embedId}`);
92340
+ else if (item) {
92341
+ const chat = item.chat;
91698
92342
  if (chat.source === "example") await context.command(`/example ${chat.slug || chat.id}`);
91699
92343
  else await openSavedChat(context, chat.id);
91700
92344
  }
91701
92345
  return true;
91702
92346
  }
92347
+ if (state.screen === "embed" && state.embedChoices.length) {
92348
+ await context.command(`/embed ${state.embedChoices[state.selectedIndex]}`);
92349
+ return true;
92350
+ }
91703
92351
  }
91704
92352
  if (state.focus === "composer") {
91705
92353
  if (key.name === "return" && state.workspace !== "chats") {
@@ -91928,10 +92576,12 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
91928
92576
  terminal.render(renderTuiFrame(state, terminal.width, terminal.height, { colorMode: terminal.colorMode, ascii: terminal.ascii }));
91929
92577
  }, 16);
91930
92578
  };
92579
+ const stopHomeSync = startHomeSync(state, client2, render, () => closed);
91931
92580
  const finish = (result2) => {
91932
92581
  closed = true;
91933
92582
  state.homeAbortController?.abort();
91934
92583
  Object.values(state.chatContextAuthoringControls).forEach((control) => control.stop());
92584
+ stopHomeSync();
91935
92585
  stopActivity?.();
91936
92586
  clearTimeout(activityTimer);
91937
92587
  clearInterval(activityPoll);
@@ -92818,7 +93468,7 @@ function clamp(value, min, max) {
92818
93468
  }
92819
93469
 
92820
93470
  // src/codexTaskBridge.ts
92821
- import { createHash as createHash21 } from "crypto";
93471
+ import { createHash as createHash22 } from "crypto";
92822
93472
  import { spawn as spawn3 } from "child_process";
92823
93473
  import { existsSync as existsSync13, readFileSync as readFileSync15, readdirSync as readdirSync7 } from "fs";
92824
93474
  import { isAbsolute as isAbsolute4, join as join18, resolve as resolve11 } from "path";
@@ -92861,7 +93511,7 @@ async function flushCodexChatDeletions(client2, teamId, stateRoot, notice) {
92861
93511
  const session = client2.getSession();
92862
93512
  const scope2 = [session.apiUrl, session.hashedEmail, teamId || "personal"];
92863
93513
  const account = JSON.stringify(scope2);
92864
- const directory = join18(stateRoot, "codex-chat-deletions", createHash21("sha256").update(account).digest("hex"));
93514
+ const directory = join18(stateRoot, "codex-chat-deletions", createHash22("sha256").update(account).digest("hex"));
92865
93515
  if (!existsSync13(directory)) return;
92866
93516
  const release2 = await lockfile4.lock(directory, { retries: 0, stale: 24e4 });
92867
93517
  try {
@@ -92895,14 +93545,14 @@ async function flushCodexChatDeletions(client2, teamId, stateRoot, notice) {
92895
93545
  }
92896
93546
 
92897
93547
  // src/taskCommandDelivery.ts
92898
- import { createHash as createHash22 } from "crypto";
93548
+ import { createHash as createHash23 } from "crypto";
92899
93549
  import { existsSync as existsSync14, readFileSync as readFileSync16, readdirSync as readdirSync8 } from "fs";
92900
93550
  import { join as join19 } from "path";
92901
93551
  import lockfile5 from "proper-lockfile";
92902
93552
  async function flushTaskCommands(client2, teamId, root = resolveStateDir(), onNotice) {
92903
93553
  const session = client2.getSession();
92904
93554
  const scope2 = [session.apiUrl, session.hashedEmail, teamId || "personal"];
92905
- const directory = join19(root, "task-command-delivery", createHash22("sha256").update(JSON.stringify(scope2)).digest("hex"));
93555
+ const directory = join19(root, "task-command-delivery", createHash23("sha256").update(JSON.stringify(scope2)).digest("hex"));
92906
93556
  if (!existsSync14(directory)) return;
92907
93557
  const release2 = await lockfile5.lock(directory, { retries: 0, stale: 24e4 });
92908
93558
  try {
@@ -93012,7 +93662,7 @@ import {
93012
93662
  writeFileSync as writeFileSync13
93013
93663
  } from "fs";
93014
93664
  import { spawn as spawn7 } from "child_process";
93015
- import { createHash as createHash28 } from "crypto";
93665
+ import { createHash as createHash29 } from "crypto";
93016
93666
  import { extname as extname4, join as join26, resolve as resolve17, relative as relative11 } from "path";
93017
93667
 
93018
93668
  // src/projectSourceRootPolicy.ts
@@ -93047,7 +93697,7 @@ function isEqualOrInside(parent, candidate) {
93047
93697
  }
93048
93698
 
93049
93699
  // src/projectPathPolicy.ts
93050
- import { createHash as createHash23, randomBytes as randomBytes11 } from "crypto";
93700
+ import { createHash as createHash24, randomBytes as randomBytes11 } from "crypto";
93051
93701
  import {
93052
93702
  chmodSync as chmodSync8,
93053
93703
  closeSync as closeSync5,
@@ -93328,7 +93978,7 @@ function isInside2(root, candidate) {
93328
93978
  return relation === "" || !relation.startsWith("..") && !isAbsolute5(relation);
93329
93979
  }
93330
93980
  function sha2562(value) {
93331
- return createHash23("sha256").update(value).digest("hex");
93981
+ return createHash24("sha256").update(value).digest("hex");
93332
93982
  }
93333
93983
  function stableJson2(value) {
93334
93984
  return JSON.stringify(value, (_key, item) => {
@@ -93338,7 +93988,7 @@ function stableJson2(value) {
93338
93988
  }
93339
93989
 
93340
93990
  // src/remoteFileWrites.ts
93341
- import { createHash as createHash24, randomBytes as randomBytes12 } from "crypto";
93991
+ import { createHash as createHash25, randomBytes as randomBytes12 } from "crypto";
93342
93992
  import {
93343
93993
  chmodSync as chmodSync9,
93344
93994
  closeSync as closeSync7,
@@ -93603,7 +94253,7 @@ var MAX_FILE_BYTES2 = 200 * 1024;
93603
94253
  var LOCK_ROOT = join22(tmpdir5(), `openmates-remote-file-locks-${typeof process.getuid === "function" ? process.getuid() : "user"}`);
93604
94254
  var DEFAULT_JOURNAL_ROOT = join22(tmpdir5(), `openmates-remote-file-journal-${typeof process.getuid === "function" ? process.getuid() : "user"}`);
93605
94255
  function sha256ProjectFile(content) {
93606
- return createHash24("sha256").update(content).digest("hex");
94256
+ return createHash25("sha256").update(content).digest("hex");
93607
94257
  }
93608
94258
  async function executeRemoteFileMutation(options) {
93609
94259
  const mutation = validateMutation(options.mutation);
@@ -94291,13 +94941,13 @@ import { existsSync as existsSync21, realpathSync as realpathSync10, statSync as
94291
94941
  import { basename as basename8, dirname as dirname15, relative as relative10, resolve as resolve16 } from "path";
94292
94942
 
94293
94943
  // src/remoteCommandRuntime.ts
94294
- import { createHash as createHash26, randomUUID as randomUUID21 } from "crypto";
94944
+ import { createHash as createHash27, randomUUID as randomUUID21 } from "crypto";
94295
94945
  import { spawn as spawn6, spawnSync as spawnSync5 } from "child_process";
94296
94946
  import { existsSync as existsSync19, lstatSync as lstatSync9, readdirSync as readdirSync11, realpathSync as realpathSync7, statSync as statSync6 } from "fs";
94297
94947
  import { isAbsolute as isAbsolute8, join as join23, relative as relative7, resolve as resolve15, sep } from "path";
94298
94948
 
94299
94949
  // src/remoteCommandAppArmor.ts
94300
- import { createHash as createHash25 } from "crypto";
94950
+ import { createHash as createHash26 } from "crypto";
94301
94951
  import { spawn as spawn5 } from "child_process";
94302
94952
  import { lstatSync as lstatSync8, readFileSync as readFileSync18, realpathSync as realpathSync6, statSync as statSync5 } from "fs";
94303
94953
  import { isAbsolute as isAbsolute7, relative as relative6 } from "path";
@@ -94514,7 +95164,7 @@ function hasControl2(value) {
94514
95164
  });
94515
95165
  }
94516
95166
  function sha2563(value) {
94517
- return createHash25("sha256").update(value).digest("hex");
95167
+ return createHash26("sha256").update(value).digest("hex");
94518
95168
  }
94519
95169
 
94520
95170
  // src/remoteCommandRuntime.ts
@@ -95355,7 +96005,7 @@ function stableJson4(value) {
95355
96005
  return JSON.stringify(value);
95356
96006
  }
95357
96007
  function sha2564(value) {
95358
- return createHash26("sha256").update(value).digest("hex");
96008
+ return createHash27("sha256").update(value).digest("hex");
95359
96009
  }
95360
96010
  function deepFreeze(value) {
95361
96011
  if (value && typeof value === "object") {
@@ -95633,7 +96283,7 @@ function capabilityError(message) {
95633
96283
  }
95634
96284
 
95635
96285
  // src/remoteCommandResourceGrants.ts
95636
- import { createHash as createHash27 } from "crypto";
96286
+ import { createHash as createHash28 } from "crypto";
95637
96287
  import { chmodSync as chmodSync11, existsSync as existsSync20, lstatSync as lstatSync10, mkdirSync as mkdirSync18, readFileSync as readFileSync19, realpathSync as realpathSync9, renameSync as renameSync9, statSync as statSync8, writeFileSync as writeFileSync12 } from "fs";
95638
96288
  import { isAbsolute as isAbsolute10, join as join25, relative as relative9 } from "path";
95639
96289
  var STORE_FILE = "remote-command-resource-grants.json";
@@ -95728,7 +96378,7 @@ function resolveRemoteCommandResourceGrants(context) {
95728
96378
  return { writable_targets, network_profiles, credential_profiles };
95729
96379
  }
95730
96380
  function definitionDigest(kind, definition) {
95731
- return createHash27("sha256").update(JSON.stringify(["openmates-command-resource-v1", kind, definition])).digest("hex");
96381
+ return createHash28("sha256").update(JSON.stringify(["openmates-command-resource-v1", kind, definition])).digest("hex");
95732
96382
  }
95733
96383
  function upsert(grant) {
95734
96384
  validateIdentity(grant.project_id, grant.source_id, grant.profile_id);
@@ -96898,7 +97548,7 @@ function readRemoteAccessTextFile(options) {
96898
97548
  truncated,
96899
97549
  sizeBytes: stats.size,
96900
97550
  lineCount: countTextLines(content),
96901
- expected_base: truncated ? null : createHash28("sha256").update(bytes2.subarray(0, bytesRead)).digest("hex")
97551
+ expected_base: truncated ? null : createHash29("sha256").update(bytes2.subarray(0, bytesRead)).digest("hex")
96902
97552
  };
96903
97553
  } catch (error) {
96904
97554
  const code = error.code;
@@ -96948,7 +97598,7 @@ function readRemoteAccessImageChunk(options) {
96948
97598
  mime_type: mimeType,
96949
97599
  size_bytes: stats.size,
96950
97600
  offset: options.offset,
96951
- content_hash: createHash28("sha256").update(bytes2).digest("hex")
97601
+ content_hash: createHash29("sha256").update(bytes2).digest("hex")
96952
97602
  };
96953
97603
  } catch (error) {
96954
97604
  if (error.code === "ELOOP") throw new Error("Remote source path is a symbolic link");
@@ -96996,8 +97646,8 @@ function readRemoteAccessFileChunk(options) {
96996
97646
  content_base64: bytes2.toString("base64"),
96997
97647
  size_bytes: stats.size,
96998
97648
  offset: options.offset,
96999
- file_identity: createHash28("sha256").update(`${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`).digest("hex"),
97000
- chunk_hash: createHash28("sha256").update(bytes2).digest("hex")
97649
+ file_identity: createHash29("sha256").update(`${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`).digest("hex"),
97650
+ chunk_hash: createHash29("sha256").update(bytes2).digest("hex")
97001
97651
  };
97002
97652
  } catch (error) {
97003
97653
  if (error.code === "ELOOP") throw new Error("Remote source path is a symbolic link");
@@ -100197,7 +100847,7 @@ async function handlePlans(client2, subcommand, rest, flags, redactor) {
100197
100847
  if (action === "diff") throw new Error("Revision diffs are reviewed in the authenticated web review page; use submit-for-review to print its URL.");
100198
100848
  if (action !== "submit-for-review") throw new Error("Usage: openmates plans revisions list|submit-for-review|status|diff <plan-id>");
100199
100849
  const snapshot2 = JSON.stringify(planToJson(plan));
100200
- const revision = await client2.submitPlanRevision(plan.planId, { fingerprint: createHash29("sha256").update(snapshot2).digest("hex"), encrypted_snapshot: await encryptPlanAssumptionSources(plan, masterKey, snapshot2), created_at: nowSeconds2() });
100850
+ const revision = await client2.submitPlanRevision(plan.planId, { fingerprint: createHash30("sha256").update(snapshot2).digest("hex"), encrypted_snapshot: await encryptPlanAssumptionSources(plan, masterKey, snapshot2), created_at: nowSeconds2() });
100201
100851
  const reviewUrl = `${deriveAppUrl(client2.apiUrl)}/plans/${plan.planId}/review?revision=${revision.revision_id}`;
100202
100852
  if (flags.json === true) printJson2({ revision, review_url: reviewUrl });
100203
100853
  else console.log(`Plan revision submitted for web review: ${reviewUrl}`);
@@ -101117,7 +101767,7 @@ async function handleProjects(client2, subcommand, rest, flags, redactor) {
101117
101767
  encrypted_settings: await encryptWithAesGcmCombined(JSON.stringify({ default_focus: defaultFocus }), projectKey),
101118
101768
  key_wrappers: wrapping.teamId ? [{
101119
101769
  key_type: "team",
101120
- hashed_team_id: createHash29("sha256").update(wrapping.teamId).digest("hex"),
101770
+ hashed_team_id: createHash30("sha256").update(wrapping.teamId).digest("hex"),
101121
101771
  team_key_epoch: 1,
101122
101772
  encrypted_project_key: await encryptBytesWithAesGcm(projectKey, wrapping.key),
101123
101773
  wrapper_version: 1,
@@ -102499,7 +103149,7 @@ async function createEncryptedRemoteAccessProject(client2, name, rootPath, write
102499
103149
  encrypted_settings: await encryptWithAesGcmCombined(JSON.stringify({ default_focus: defaultFocus }), projectKey),
102500
103150
  key_wrappers: wrapping.teamId ? [{
102501
103151
  key_type: "team",
102502
- hashed_team_id: createHash29("sha256").update(wrapping.teamId).digest("hex"),
103152
+ hashed_team_id: createHash30("sha256").update(wrapping.teamId).digest("hex"),
102503
103153
  team_key_epoch: 1,
102504
103154
  encrypted_project_key: await encryptBytesWithAesGcm(projectKey, wrapping.key),
102505
103155
  wrapper_version: 1,
@@ -103890,7 +104540,7 @@ function normalizeApiKeyLoadedChat(payload) {
103890
104540
  const messages = rawMessages.map((message) => {
103891
104541
  const messageId = String(message.client_message_id ?? message.message_id ?? message.id ?? "");
103892
104542
  const directEmbedIds = Array.isArray(message.embedIds) ? message.embedIds : Array.isArray(message.embed_ids) ? message.embed_ids : [];
103893
- const hashedMessageId = messageId ? createHash29("sha256").update(messageId).digest("hex") : null;
104543
+ const hashedMessageId = messageId ? createHash30("sha256").update(messageId).digest("hex") : null;
103894
104544
  const matchedEmbedIds = hashedMessageId ? rawEmbeds.filter((embed) => embed.hashed_message_id === hashedMessageId && !embed.parent_embed_id).map((embed) => String(embed.embed_id ?? embed.id ?? "")).filter(Boolean) : [];
103895
104545
  return {
103896
104546
  id: messageId,