openmates 0.27.0-alpha.52 → 0.27.0-alpha.53

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.
@@ -755,9 +755,9 @@ async function resumeCodexTask(context) {
755
755
  child.once("exit", (code) => code === 0 ? resolve20() : reject(new Error(`Codex resume exited with status ${code ?? "interrupted"}.`)));
756
756
  });
757
757
  }
758
- function taskOwnerConflict(owner) {
759
- const title = JSON.stringify(owner.title || "Untitled chat");
760
- const location = owner.provider === "openmates" ? `OpenMates chat ID: ${owner.id}. View: openmates chats show ${owner.id}` : `Codex chat ID: ${owner.id}`;
758
+ function taskOwnerConflict(owner2) {
759
+ const title = JSON.stringify(owner2.title || "Untitled chat");
760
+ const location = owner2.provider === "openmates" ? `OpenMates chat ID: ${owner2.id}. View: openmates chats show ${owner2.id}` : `Codex chat ID: ${owner2.id}`;
761
761
  return `Task is already linked to ${title}. ${location}. Release that link before claiming the task in another chat.`;
762
762
  }
763
763
  function assertCodexClaimCaller(threadId, currentThread = process.env.CODEX_THREAD_ID) {
@@ -5438,6 +5438,7 @@ var OpenMatesWsClient = class {
5438
5438
  const asyncEmbedWaitMs = options?.asyncEmbedWaitMs ?? DEFAULT_ASYNC_EMBED_WAIT_MS;
5439
5439
  return new Promise((resolve20, reject) => {
5440
5440
  let latestContent = "";
5441
+ let thinkingContent = "";
5441
5442
  let messageId = null;
5442
5443
  let taskId = null;
5443
5444
  let category = null;
@@ -5779,6 +5780,15 @@ var OpenMatesWsClient = class {
5779
5780
  }
5780
5781
  resetTimeout(timeoutMs);
5781
5782
  };
5783
+ const belongsToResponseTurn = (p) => {
5784
+ if (p.chat_id !== chatId) return false;
5785
+ const ids = [p.user_message_id, p.userMessageId].filter((id) => id !== void 0);
5786
+ if (ids.length === 0 || ids.every((id) => id === userMessageId)) return true;
5787
+ if (awaitingSubChatsCompletion && p.is_sub_chat_continuation === true) return true;
5788
+ if (awaitingFocusModeContinuation && p.is_focus_mode_continuation === true) return true;
5789
+ if (p.is_async_skill_continuation === true && p.original_user_message_id === userMessageId && typeof p.async_skill_task_id === "string" && (pendingProjectOperations.has(p.async_skill_task_id) || awaitingUnidentifiedAsyncContinuation)) return true;
5790
+ return Boolean(options?.recoveryTurnId && p.recovery_turn_id === options.recoveryTurnId);
5791
+ };
5782
5792
  const onMessage = (rawData) => {
5783
5793
  try {
5784
5794
  const parsed = JSON.parse(rawData.toString());
@@ -5901,9 +5911,25 @@ var OpenMatesWsClient = class {
5901
5911
  else scheduleResolve(latestContent);
5902
5912
  return;
5903
5913
  }
5914
+ if (type === "thinking_chunk" || type === "thinking_complete") {
5915
+ if (p.chat_id !== chatId || !taskId || p.task_id !== taskId) return;
5916
+ if (type === "thinking_chunk") {
5917
+ if (typeof p.content !== "string" || !p.content) return;
5918
+ thinkingContent += p.content;
5919
+ }
5920
+ onStream?.({
5921
+ kind: "typing",
5922
+ content: latestContent,
5923
+ category,
5924
+ modelName,
5925
+ taskId,
5926
+ thinkingContent,
5927
+ thinkingActive: type === "thinking_chunk"
5928
+ });
5929
+ return;
5930
+ }
5904
5931
  if (type === "ai_message_update") {
5905
- const msgId = p.user_message_id ?? p.userMessageId;
5906
- if (msgId !== userMessageId && p.chat_id !== chatId) return;
5932
+ if (!belongsToResponseTurn(p)) return;
5907
5933
  beginProjectContinuation(p);
5908
5934
  if (activeProjectContinuation && (p.is_async_skill_continuation !== true || p.async_skill_task_id !== activeProjectContinuation)) return;
5909
5935
  if (p.is_focus_mode_continuation === true) beginFocusModeContinuation();
@@ -5926,7 +5952,9 @@ var OpenMatesWsClient = class {
5926
5952
  content: latestContent,
5927
5953
  category,
5928
5954
  modelName,
5929
- taskId: taskId ?? void 0
5955
+ taskId: taskId ?? void 0,
5956
+ thinkingContent,
5957
+ thinkingActive: false
5930
5958
  });
5931
5959
  scheduleResolve(latestContent);
5932
5960
  } else {
@@ -5935,15 +5963,15 @@ var OpenMatesWsClient = class {
5935
5963
  content: latestContent,
5936
5964
  category,
5937
5965
  modelName,
5938
- taskId: taskId ?? void 0
5966
+ taskId: taskId ?? void 0,
5967
+ thinkingContent,
5968
+ thinkingActive: false
5939
5969
  });
5940
5970
  }
5941
5971
  return;
5942
5972
  }
5943
5973
  if (type === "ai_background_response_completed") {
5944
- const msgId = p.user_message_id ?? p.userMessageId;
5945
- if (msgId && msgId !== userMessageId && p.chat_id !== chatId) return;
5946
- if (!msgId && p.chat_id !== chatId) return;
5974
+ if (!belongsToResponseTurn(p)) return;
5947
5975
  beginProjectContinuation(p);
5948
5976
  if (activeProjectContinuation && (p.is_async_skill_continuation !== true || p.async_skill_task_id !== activeProjectContinuation)) return;
5949
5977
  if (p.is_focus_mode_continuation === true) beginFocusModeContinuation();
@@ -5958,7 +5986,7 @@ var OpenMatesWsClient = class {
5958
5986
  awaitingUnidentifiedAsyncContinuation = true;
5959
5987
  resetTimeout(Math.max(timeoutMs, 20 * 6e4 + 5e3));
5960
5988
  }
5961
- onStream?.({ kind: "done", content, category, modelName, taskId: taskId ?? void 0 });
5989
+ onStream?.({ kind: "done", content, category, modelName, taskId: taskId ?? void 0, thinkingContent, thinkingActive: false });
5962
5990
  scheduleResolve(content);
5963
5991
  return;
5964
5992
  }
@@ -5978,14 +6006,17 @@ var OpenMatesWsClient = class {
5978
6006
  if (typeof message.model_name === "string" && message.model_name) {
5979
6007
  modelName = message.model_name;
5980
6008
  }
5981
- onStream?.({ kind: "done", content, category, modelName, taskId: taskId ?? void 0 });
6009
+ onStream?.({ kind: "done", content, category, modelName, taskId: taskId ?? void 0, thinkingContent, thinkingActive: false });
5982
6010
  scheduleResolve(content);
5983
6011
  return;
5984
6012
  }
5985
6013
  if (type === "ai_typing_started") {
5986
- if (p.chat_id !== chatId) return;
6014
+ if (p.chat_id !== chatId || p.user_message_id !== void 0 && p.user_message_id !== userMessageId || p.userMessageId !== void 0 && p.userMessageId !== userMessageId) return;
5987
6015
  beginProjectContinuation(p);
5988
6016
  capture(p);
6017
+ if (typeof p.task_id !== "string" && typeof p.message_id === "string" && p.message_id) {
6018
+ taskId = p.message_id;
6019
+ }
5989
6020
  if (typeof p.title === "string" && p.title.trim()) generatedTitle = p.title.trim();
5990
6021
  if (Array.isArray(p.icon_names)) {
5991
6022
  generatedIcon = p.icon_names.find((icon) => typeof icon === "string" && icon.trim().length > 0) ?? generatedIcon;
@@ -5995,7 +6026,9 @@ var OpenMatesWsClient = class {
5995
6026
  content: "",
5996
6027
  category,
5997
6028
  modelName,
5998
- taskId: taskId ?? void 0
6029
+ taskId: taskId ?? void 0,
6030
+ thinkingContent,
6031
+ thinkingActive: false
5999
6032
  });
6000
6033
  return;
6001
6034
  }
@@ -11493,15 +11526,15 @@ var OpenMatesClient = class _OpenMatesClient {
11493
11526
  return `${path2}${separator}team_id=${encodeURIComponent(teamId)}`;
11494
11527
  }
11495
11528
  async listTeams() {
11496
- const owner = this.captureTeamAccountOwner();
11529
+ const owner2 = this.captureTeamAccountOwner();
11497
11530
  const response = await this.http.get("/v1/teams", this.getCliRequestHeaders());
11498
11531
  if (!response.ok) throw new Error(`Team list failed with HTTP ${response.status}`);
11499
- if (!owner.isCurrent()) throw new Error("Account changed while loading Teams.");
11532
+ if (!owner2.isCurrent()) throw new Error("Account changed while loading Teams.");
11500
11533
  const teams = response.data.teams ?? [];
11501
- await Promise.all(teams.map((team) => this.cacheTeamKeyFromRecord(team, owner.isCurrent)));
11502
- if (!owner.isCurrent()) throw new Error("Account changed while loading Teams.");
11534
+ await Promise.all(teams.map((team) => this.cacheTeamKeyFromRecord(team, owner2.isCurrent)));
11535
+ if (!owner2.isCurrent()) throw new Error("Account changed while loading Teams.");
11503
11536
  const teamIds = teams.map((team) => team.team_id).filter((teamId) => typeof teamId === "string" && teamId.length > 0);
11504
- pruneLocalTeamArtifacts(owner.hashedEmail, teamIds);
11537
+ pruneLocalTeamArtifacts(owner2.hashedEmail, teamIds);
11505
11538
  if (this.session?.activeTeamId && !teamIds.includes(this.session.activeTeamId)) {
11506
11539
  this.setActiveTeamId(null);
11507
11540
  }
@@ -11557,12 +11590,12 @@ var OpenMatesClient = class _OpenMatesClient {
11557
11590
  return response.data.team;
11558
11591
  }
11559
11592
  async getTeam(teamId) {
11560
- const owner = this.captureTeamAccountOwner();
11593
+ const owner2 = this.captureTeamAccountOwner();
11561
11594
  const response = await this.http.get(`/v1/teams/${encodeURIComponent(teamId)}`, this.getCliRequestHeaders());
11562
11595
  if (!response.ok || !response.data.team) throw new Error(`Team get failed with HTTP ${response.status}`);
11563
- if (!owner.isCurrent()) throw new Error("Account changed while loading Team.");
11564
- await this.cacheTeamKeyFromRecord(response.data.team, owner.isCurrent);
11565
- if (!owner.isCurrent()) throw new Error("Account changed while loading Team.");
11596
+ if (!owner2.isCurrent()) throw new Error("Account changed while loading Team.");
11597
+ await this.cacheTeamKeyFromRecord(response.data.team, owner2.isCurrent);
11598
+ if (!owner2.isCurrent()) throw new Error("Account changed while loading Team.");
11566
11599
  return response.data.team;
11567
11600
  }
11568
11601
  /** Team names and descriptions are decrypted only on this device. */
@@ -13171,7 +13204,7 @@ var OpenMatesClient = class _OpenMatesClient {
13171
13204
  /** Observe lifecycle metadata without claiming or collecting AI responses. */
13172
13205
  async observeChatActivity(onChange) {
13173
13206
  const session = this.requireSession();
13174
- const owner = [
13207
+ const owner2 = [
13175
13208
  session.apiUrl,
13176
13209
  session.hashedEmail,
13177
13210
  session.sessionId,
@@ -13180,7 +13213,7 @@ var OpenMatesClient = class _OpenMatesClient {
13180
13213
  ];
13181
13214
  const ownerCurrent = () => {
13182
13215
  const current2 = this.session;
13183
- return !!current2 && current2.apiUrl === owner[0] && current2.hashedEmail === owner[1] && current2.sessionId === owner[2] && current2.createdAt === owner[3] && current2.masterKeyExportedB64 === owner[4];
13216
+ return !!current2 && current2.apiUrl === owner2[0] && current2.hashedEmail === owner2[1] && current2.sessionId === owner2[2] && current2.createdAt === owner2[3] && current2.masterKeyExportedB64 === owner2[4];
13184
13217
  };
13185
13218
  const types = ["ai_task_initiated", "ai_typing_started", "ai_typing_ended", "ai_background_response_completed", "ai_task_cancel_requested", "ai_task_error", "pending_ai_response", "team_chat_message_created"];
13186
13219
  let stopped = false;
@@ -14185,7 +14218,7 @@ var OpenMatesClient = class _OpenMatesClient {
14185
14218
  async getChatMessagesWindow(query, options = {}) {
14186
14219
  const teamId = this.resolveTeamContext(options);
14187
14220
  const session = this.requireSession();
14188
- const owner = [
14221
+ const owner2 = [
14189
14222
  session.apiUrl,
14190
14223
  session.hashedEmail,
14191
14224
  session.sessionId,
@@ -14196,13 +14229,13 @@ var OpenMatesClient = class _OpenMatesClient {
14196
14229
  const selectedTeamId = this.getActiveTeamId();
14197
14230
  const current2 = () => {
14198
14231
  const active = this.session;
14199
- if (!active || active.apiUrl !== owner[0] || active.hashedEmail !== owner[1] || active.sessionId !== owner[2] || active.createdAt !== owner[3] || active.masterKeyExportedB64 !== owner[4] || options.preferCache && this.getActiveTeamId() !== selectedTeamId)
14232
+ if (!active || active.apiUrl !== owner2[0] || active.hashedEmail !== owner2[1] || active.sessionId !== owner2[2] || active.createdAt !== owner2[3] || active.masterKeyExportedB64 !== owner2[4] || options.preferCache && this.getActiveTeamId() !== selectedTeamId)
14200
14233
  throw new Error("Chat workspace changed");
14201
14234
  };
14202
14235
  const cache = options.preferCache ? loadSyncCache(teamId) : await this.ensureSynced(false, [], options);
14203
14236
  current2();
14204
14237
  if (!cache) throw new Error("Chat cache unavailable. Open the chat to refresh it.");
14205
- const localTeamKey = options.preferCache && teamId ? loadLocalTeamKey(owner[1], teamId) : null;
14238
+ const localTeamKey = options.preferCache && teamId ? loadLocalTeamKey(owner2[1], teamId) : null;
14206
14239
  if (options.preferCache && teamId && !localTeamKey) throw new Error("Team key unavailable offline. Reconnect to refresh Team access.");
14207
14240
  const wrappingKey = localTeamKey ? base64ToBytes(localTeamKey) : await this.getChatWrappingKey(teamId, masterKey);
14208
14241
  current2();
@@ -18738,7 +18771,7 @@ var OpenMatesClient = class _OpenMatesClient {
18738
18771
  // -------------------------------------------------------------------------
18739
18772
  async listUserTasks(filters = {}, options = {}) {
18740
18773
  const session = this.requireSession();
18741
- const owner = [
18774
+ const owner2 = [
18742
18775
  session.apiUrl,
18743
18776
  session.hashedEmail,
18744
18777
  session.sessionId,
@@ -18748,7 +18781,7 @@ var OpenMatesClient = class _OpenMatesClient {
18748
18781
  ];
18749
18782
  const current2 = () => {
18750
18783
  const active = this.session;
18751
- if (!active || active.apiUrl !== owner[0] || active.hashedEmail !== owner[1] || active.sessionId !== owner[2] || active.createdAt !== owner[3] || active.masterKeyExportedB64 !== owner[4] || active.activeTeamId !== owner[5])
18784
+ if (!active || active.apiUrl !== owner2[0] || active.hashedEmail !== owner2[1] || active.sessionId !== owner2[2] || active.createdAt !== owner2[3] || active.masterKeyExportedB64 !== owner2[4] || active.activeTeamId !== owner2[5])
18752
18785
  throw new Error("Task workspace changed");
18753
18786
  };
18754
18787
  const params = new URLSearchParams();
@@ -18984,8 +19017,8 @@ var OpenMatesClient = class _OpenMatesClient {
18984
19017
  if (task.externalChat && (current2.external_chat_provider !== input.external_chat_provider || current2.external_chat_lookup_hash !== input.external_chat_lookup_hash)) {
18985
19018
  conflict = taskOwnerConflict(task.externalChat);
18986
19019
  } else if (task.primaryChatId && task.primaryChatId !== input.primary_chat_id) {
18987
- const owner = await this.getChatMetadata(task.primaryChatId, context);
18988
- conflict = taskOwnerConflict({ provider: "openmates", id: owner.id, title: owner.title });
19020
+ const owner2 = await this.getChatMetadata(task.primaryChatId, context);
19021
+ conflict = taskOwnerConflict({ provider: "openmates", id: owner2.id, title: owner2.title });
18989
19022
  }
18990
19023
  }
18991
19024
  } catch {
@@ -21226,9 +21259,9 @@ Required: ${schema.required.join(", ")}`
21226
21259
  ...baseSyncPayload,
21227
21260
  ...refreshChatIdSet.size > 0 ? { refresh_chat_ids: Array.from(refreshChatIdSet) } : {}
21228
21261
  });
21229
- const frames2 = await syncFrames;
21262
+ const frames3 = await syncFrames;
21230
21263
  signal?.throwIfAborted();
21231
- if (onSyncedCache && !frames2.some((frame) => frame.type === "phase_2_last_20_chats_ready")) {
21264
+ if (onSyncedCache && !frames3.some((frame) => frame.type === "phase_2_last_20_chats_ready")) {
21232
21265
  throw new Error("Chat metadata sync ended before history arrived.");
21233
21266
  }
21234
21267
  if (includeMessageContentRefresh && refreshChatIdSet.size > 0) {
@@ -21239,9 +21272,9 @@ Required: ${schema.required.join(", ")}`
21239
21272
  });
21240
21273
  const collectedPhase3Frames = await phase3Frames;
21241
21274
  signal?.throwIfAborted();
21242
- frames2.push(...collectedPhase3Frames);
21275
+ frames3.push(...collectedPhase3Frames);
21243
21276
  }
21244
- for (const frame of frames2) {
21277
+ for (const frame of frames3) {
21245
21278
  if (frame.type === "phase_1_last_chat_ready") {
21246
21279
  const p = frame.payload;
21247
21280
  newChatSuggestions = p.new_chat_suggestions ?? [];
@@ -34662,7 +34695,7 @@ function generateQr(value) {
34662
34695
  function remotionMeta(c) {
34663
34696
  const source = str(c.remotion_source) ?? str(c.source) ?? "";
34664
34697
  const fps = firstInt(source, /fps\s*[:=]\s*(\d+)/) ?? 30;
34665
- const frames2 = firstInt(source, /durationInFrames\s*[:=]\s*(\d+)/) ?? 150;
34698
+ const frames3 = firstInt(source, /durationInFrames\s*[:=]\s*(\d+)/) ?? 150;
34666
34699
  const status = str(c.status) ?? "processing";
34667
34700
  return {
34668
34701
  filename: str(c.filename) ?? str(c.title) ?? "Composition.tsx",
@@ -34670,7 +34703,7 @@ function remotionMeta(c) {
34670
34703
  statusText: remotionStatusText(status),
34671
34704
  source,
34672
34705
  sourceVersion: numberValue(c.current_source_version ?? c.source_version) ?? 1,
34673
- durationSeconds: Math.max(1, Math.ceil(frames2 / Math.max(1, fps))),
34706
+ durationSeconds: Math.max(1, Math.ceil(frames3 / Math.max(1, fps))),
34674
34707
  width: firstInt(source, /width\s*[:=]\s*(\d+)/) ?? 1920,
34675
34708
  height: firstInt(source, /height\s*[:=]\s*(\d+)/) ?? 1080,
34676
34709
  layers: remotionLayers(source),
@@ -37197,11 +37230,11 @@ async function writeRuntimeIncidentState(installDir, role, state, effectiveUid =
37197
37230
  const statePath = path.join(stateDir2, `${role}.json`);
37198
37231
  const temporaryPath = `${statePath}.${randomUUID12()}.tmp`;
37199
37232
  await fs.mkdir(stateDir2, { recursive: true, mode: 448 });
37200
- const owner = await runtimeIncidentStateOwner(installDir, effectiveUid);
37233
+ const owner2 = await runtimeIncidentStateOwner(installDir, effectiveUid);
37201
37234
  try {
37202
37235
  await fs.writeFile(temporaryPath, `${JSON.stringify(state, null, 2)}
37203
37236
  `, { flag: "wx", mode: 384 });
37204
- if (owner && (owner.uid !== 0 || owner.gid !== 0)) await fs.chown(temporaryPath, owner.uid, owner.gid);
37237
+ if (owner2 && (owner2.uid !== 0 || owner2.gid !== 0)) await fs.chown(temporaryPath, owner2.uid, owner2.gid);
37205
37238
  await fs.rename(temporaryPath, statePath);
37206
37239
  await fs.chmod(statePath, 384);
37207
37240
  } catch (error) {
@@ -91485,6 +91518,10 @@ var TuiTerminal = class {
91485
91518
  paintedWidth = 0;
91486
91519
  paintedHeight = 0;
91487
91520
  outputBlocked = false;
91521
+ /** Animation ticks must not add work while stdout is awaiting drain. */
91522
+ get isOutputBlocked() {
91523
+ return this.outputBlocked;
91524
+ }
91488
91525
  pendingFrame = null;
91489
91526
  outputDrained = () => {
91490
91527
  this.output.off("drain", this.outputDrained);
@@ -91526,6 +91563,10 @@ var TuiTerminal = class {
91526
91563
  const depth = this.output.getColorDepth?.() ?? 8;
91527
91564
  return depth >= 24 ? "truecolor" : depth >= 8 ? "ansi256" : depth >= 4 ? "ansi16" : "none";
91528
91565
  }
91566
+ /** Explicit terminal preference; GUI OS motion preferences are not available over SSH. */
91567
+ get reducedMotion() {
91568
+ return process.env.OPENMATES_REDUCED_MOTION === "1";
91569
+ }
91529
91570
  get ascii() {
91530
91571
  return process.env.OPENMATES_ASCII === "1" || process.env.TERM === "dumb";
91531
91572
  }
@@ -92636,10 +92677,62 @@ function filterTasks(tasks, query, status) {
92636
92677
  const words = query.toLocaleLowerCase().trim().split(/\s+/).filter(Boolean);
92637
92678
  return sorted(tasks.filter((task) => {
92638
92679
  if (status && task.status !== status) return false;
92680
+ if (!words.length) return true;
92639
92681
  const haystack = [task.shortId, task.slug, task.title, task.description, task.labels.join(" "), task.tags.join(" "), task.status, task.assigneeIdentity ?? "", task.assigneeHash ?? "", task.queueState].join(" ").toLocaleLowerCase();
92640
92682
  return words.every((word) => haystack.includes(word));
92641
92683
  }));
92642
92684
  }
92685
+ var titleHeightCache = /* @__PURE__ */ new WeakMap();
92686
+ function taskTitleLines(task, width, selected) {
92687
+ return wrapCells(`${selected ? "\u203A " : " "}${task.title || "Untitled task"}`, Math.max(8, width - 2)).slice(0, 2);
92688
+ }
92689
+ function taskTitleHeight(task, width, selected) {
92690
+ let cache = titleHeightCache.get(task);
92691
+ if (!cache || cache.title !== task.title) {
92692
+ cache = { title: task.title, widths: /* @__PURE__ */ new Map() };
92693
+ titleHeightCache.set(task, cache);
92694
+ }
92695
+ let counts = cache.widths.get(width);
92696
+ if (!counts) {
92697
+ if (cache.widths.size >= 3) {
92698
+ const oldest = cache.widths.keys().next().value;
92699
+ if (oldest !== void 0) cache.widths.delete(oldest);
92700
+ }
92701
+ counts = {};
92702
+ cache.widths.set(width, counts);
92703
+ }
92704
+ const key = selected ? "selected" : "unselected";
92705
+ return counts[key] ?? (counts[key] = taskTitleLines(task, width, selected).length);
92706
+ }
92707
+ function taskCardHeight(task, width, selected) {
92708
+ const titleRows = taskTitleHeight(task, width, selected);
92709
+ return 2 + titleRows + 1 + 1 + Number(task.linkedProjectIds.length > 0) + Number(Boolean(task.dueAt)) + Number(task.priority > 1) + Number(Boolean(task.queueState && task.queueState !== "none"));
92710
+ }
92711
+ function cardPositions(group, width, selectedTaskId, separatorRows) {
92712
+ let height = 0;
92713
+ const cards = group.map((task, index) => {
92714
+ const start = height;
92715
+ const end = start + taskCardHeight(task, width, task.taskId === selectedTaskId);
92716
+ height = end + separatorRows;
92717
+ return { task, index, start, end };
92718
+ });
92719
+ return { cards, height };
92720
+ }
92721
+ function boardWindows(viewport, fullHeight, stackStart, selected) {
92722
+ const requestedStart = Math.floor(viewport.start);
92723
+ const span = Math.min(fullHeight, Math.max(0, Math.ceil(viewport.end) - requestedStart));
92724
+ const start = Math.min(Math.max(0, requestedStart), Math.max(0, fullHeight - span));
92725
+ const end = start + span;
92726
+ const windows = [{ start, end }];
92727
+ if (selected && viewport.followSelection !== false) {
92728
+ const padding = Math.max(1, end - start);
92729
+ windows.push({ start: Math.max(0, stackStart + selected.start - padding), end: stackStart + selected.end + padding });
92730
+ }
92731
+ return windows;
92732
+ }
92733
+ function intersects(start, end, windows) {
92734
+ return windows.some((window) => start < window.end && end > window.start);
92735
+ }
92643
92736
  function renderTaskBoard2(tasks, options) {
92644
92737
  const width = Math.max(18, options.width);
92645
92738
  const visible = filterTasks(tasks, options.query ?? "");
@@ -92667,14 +92760,40 @@ function renderTaskBoard2(tasks, options) {
92667
92760
  { kind: "select", target: "task", column: TASK_STATUSES.indexOf(status), index: 0 }
92668
92761
  )), colWidth, gap));
92669
92762
  lines.push(shown.map(() => "\u2500".repeat(colWidth)).join(gap));
92670
- const stacks = shown.map(({ tasks: group }) => group.length ? group.flatMap((task, index) => [...taskCard(
92671
- task,
92672
- colWidth,
92673
- task.taskId === options.selectedTaskId,
92674
- { kind: "select", target: "task", column: TASK_STATUSES.indexOf(task.status), index, id: task.taskId, activate: true }
92675
- ), ""]) : [padCells("No tasks here.", colWidth)]);
92676
- for (let row3 = 0; row3 < Math.max(...stacks.map((stack) => stack.length)); row3++) {
92677
- lines.push(joinColumns(stacks.map((stack) => stack[row3] ?? ""), colWidth, gap));
92763
+ if (!options.viewport) {
92764
+ const stacks = shown.map(({ tasks: group }) => group.length ? group.flatMap((task, index) => [...taskCard(
92765
+ task,
92766
+ colWidth,
92767
+ task.taskId === options.selectedTaskId,
92768
+ { kind: "select", target: "task", column: TASK_STATUSES.indexOf(task.status), index, id: task.taskId, activate: true }
92769
+ ), ""]) : [padCells("No tasks here.", colWidth)]);
92770
+ for (let row3 = 0; row3 < Math.max(...stacks.map((stack) => stack.length)); row3++) {
92771
+ lines.push(joinColumns(stacks.map((stack) => stack[row3] ?? ""), colWidth, gap));
92772
+ }
92773
+ } else {
92774
+ const stackStart = lines.length;
92775
+ const positions = shown.map(({ tasks: group }) => cardPositions(group, colWidth, options.selectedTaskId, 1));
92776
+ const selectedCard = positions.flatMap(({ cards }) => cards).find(({ task }) => task.taskId === options.selectedTaskId);
92777
+ const totalRows = Math.max(...positions.map(({ cards, height }) => cards.length ? height : 1));
92778
+ const windows = boardWindows(options.viewport, stackStart + totalRows, stackStart, selectedCard);
92779
+ const stacks = positions.map(({ cards }) => {
92780
+ const rows2 = /* @__PURE__ */ new Map();
92781
+ if (!cards.length && intersects(stackStart, stackStart + 1, windows)) rows2.set(0, padCells("No tasks here.", colWidth));
92782
+ for (const card2 of cards) {
92783
+ if (!intersects(stackStart + card2.start, stackStart + card2.end, windows)) continue;
92784
+ const rendered = taskCard(
92785
+ card2.task,
92786
+ colWidth,
92787
+ card2.task.taskId === options.selectedTaskId,
92788
+ { kind: "select", target: "task", column: TASK_STATUSES.indexOf(card2.task.status), index: card2.index, id: card2.task.taskId, activate: true }
92789
+ );
92790
+ rendered.forEach((line3, offset) => rows2.set(card2.start + offset, line3));
92791
+ }
92792
+ return rows2;
92793
+ });
92794
+ for (let row3 = 0; row3 < totalRows; row3++) {
92795
+ lines.push(intersects(stackStart + row3, stackStart + row3 + 1, windows) ? joinColumns(stacks.map((stack) => stack.get(row3) ?? ""), colWidth, gap) : "");
92796
+ }
92678
92797
  }
92679
92798
  } else {
92680
92799
  const group = columns[focusedIndex]?.tasks ?? [];
@@ -92684,13 +92803,33 @@ function renderTaskBoard2(tasks, options) {
92684
92803
  { kind: "select", target: "task", column: focusedIndex, index: 0 }
92685
92804
  ), "\u2500".repeat(Math.min(width, 52)));
92686
92805
  if (!group.length) lines.push(" No tasks here.");
92687
- for (const [index, task] of group.entries()) {
92688
- lines.push(...taskCard(
92689
- task,
92690
- Math.min(width, 52),
92691
- task.taskId === options.selectedTaskId,
92692
- { kind: "select", target: "task", column: focusedIndex, index, id: task.taskId, activate: true }
92693
- ));
92806
+ if (!options.viewport) {
92807
+ for (const [index, task] of group.entries()) {
92808
+ lines.push(...taskCard(
92809
+ task,
92810
+ Math.min(width, 52),
92811
+ task.taskId === options.selectedTaskId,
92812
+ { kind: "select", target: "task", column: focusedIndex, index, id: task.taskId, activate: true }
92813
+ ));
92814
+ }
92815
+ } else {
92816
+ const stackStart = lines.length;
92817
+ const { cards, height } = cardPositions(group, Math.min(width, 52), options.selectedTaskId, 0);
92818
+ const windows = boardWindows(options.viewport, stackStart + height, stackStart, cards.find(({ task }) => task.taskId === options.selectedTaskId));
92819
+ const rows2 = Array(height).fill("");
92820
+ for (const card2 of cards) {
92821
+ if (!intersects(stackStart + card2.start, stackStart + card2.end, windows)) continue;
92822
+ const rendered = taskCard(
92823
+ card2.task,
92824
+ Math.min(width, 52),
92825
+ card2.task.taskId === options.selectedTaskId,
92826
+ { kind: "select", target: "task", column: focusedIndex, index: card2.index, id: card2.task.taskId, activate: true }
92827
+ );
92828
+ rendered.forEach((line3, offset) => {
92829
+ rows2[card2.start + offset] = line3;
92830
+ });
92831
+ }
92832
+ lines.push(...rows2);
92694
92833
  }
92695
92834
  }
92696
92835
  return lines;
@@ -92711,7 +92850,7 @@ function taskCard(task, width, selected, action2) {
92711
92850
  if (!selected) return top ? `\u256D${edge}\u256E` : `\u2570${edge}\u256F`;
92712
92851
  return styledLine([{ text: top ? `\u2554${edge}\u2557` : `\u255A${edge}\u255D`, color: STATUS_COLORS[task.status], background: SELECTED_BACKGROUND, bold: true }]);
92713
92852
  };
92714
- const titleLines = wrapCells(`${selected ? "\u203A " : " "}${task.title || "Untitled task"}`, inside).slice(0, 2);
92853
+ const titleLines = taskTitleLines(task, width, selected);
92715
92854
  const metadata = [task.linkedProjectIds.length ? "Project" : "", assignee(task), task.dueAt ? `Due ${new Date(task.dueAt * 1e3).toISOString().slice(0, 10)}` : "", task.priority > 1 ? task.priorityLevel : ""].filter(Boolean);
92716
92855
  return [
92717
92856
  border(true),
@@ -94423,7 +94562,7 @@ function settingsChildren(route2, options) {
94423
94562
 
94424
94563
  // src/tuiSettings.ts
94425
94564
  var currentOwner = (ctx) => typeof ctx.owner === "function" ? ctx.owner() : ctx.owner;
94426
- var ownerValid = (ctx, owner) => currentOwner(ctx) === owner && ctx.state.owner === owner && (ctx.isOwnerCurrent?.() ?? true);
94565
+ var ownerValid = (ctx, owner2) => currentOwner(ctx) === owner2 && ctx.state.owner === owner2 && (ctx.isOwnerCurrent?.() ?? true);
94427
94566
  var asObject = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
94428
94567
  var safeText2 = (value) => terminalText(String(value ?? "")).replace(/\s+/g, " ").trim();
94429
94568
  var isSecret = (key) => /(?:secret|token|password|private.?key|encrypted|ciphertext|api_key$|^key$)/i.test(key);
@@ -94444,10 +94583,10 @@ function settingsWebUrl(ctx) {
94444
94583
  return "https://openmates.org";
94445
94584
  }
94446
94585
  }
94447
- function createTuiSettingsState(owner, options = {}) {
94586
+ function createTuiSettingsState(owner2, options = {}) {
94448
94587
  return {
94449
94588
  route: "main",
94450
- owner,
94589
+ owner: owner2,
94451
94590
  ownerStale: false,
94452
94591
  generation: 0,
94453
94592
  authenticated: options.authenticated ?? true,
@@ -94492,17 +94631,17 @@ function restrictedSession(client2) {
94492
94631
  }
94493
94632
  }
94494
94633
  function syncTuiSettingsOwner(ctx) {
94495
- const owner = currentOwner(ctx);
94496
- if (ctx.isOwnerCurrent?.() === false && owner === ctx.state.owner) {
94634
+ const owner2 = currentOwner(ctx);
94635
+ if (ctx.isOwnerCurrent?.() === false && owner2 === ctx.state.owner) {
94497
94636
  if (!ctx.state.ownerStale) {
94498
- const fresh2 = createTuiSettingsState(owner, { authenticated: ctx.client.hasSession(), restricted: restrictedSession(ctx.client), paymentEnabled: false, webUrl: settingsWebUrl(ctx) });
94637
+ const fresh2 = createTuiSettingsState(owner2, { authenticated: ctx.client.hasSession(), restricted: restrictedSession(ctx.client), paymentEnabled: false, webUrl: settingsWebUrl(ctx) });
94499
94638
  Object.assign(ctx.state, fresh2, { ownerStale: true, generation: ctx.state.generation + 1 });
94500
94639
  ctx.render();
94501
94640
  }
94502
94641
  return true;
94503
94642
  }
94504
- if (ctx.state.owner === owner) return false;
94505
- const fresh = createTuiSettingsState(owner, {
94643
+ if (ctx.state.owner === owner2) return false;
94644
+ const fresh = createTuiSettingsState(owner2, {
94506
94645
  authenticated: ctx.client.hasSession(),
94507
94646
  restricted: restrictedSession(ctx.client),
94508
94647
  paymentEnabled: ctx.state.paymentEnabled,
@@ -94537,14 +94676,14 @@ async function loadPage(ctx, page) {
94537
94676
  const state = ctx.state;
94538
94677
  if (state.busy) return;
94539
94678
  if (state.ownerStale) return;
94540
- const owner = currentOwner(ctx), generation = ++state.generation;
94679
+ const owner2 = currentOwner(ctx), generation = ++state.generation;
94541
94680
  state.busy = true;
94542
94681
  state.loading = true;
94543
94682
  state.error = null;
94544
94683
  ctx.render();
94545
94684
  try {
94546
94685
  const value = await page.load(ctx.client);
94547
- if (!ownerValid(ctx, owner)) {
94686
+ if (!ownerValid(ctx, owner2)) {
94548
94687
  ctx.render();
94549
94688
  return;
94550
94689
  }
@@ -94565,9 +94704,9 @@ async function loadPage(ctx, page) {
94565
94704
  state.isAdmin = info.is_admin === true;
94566
94705
  }
94567
94706
  } catch (error) {
94568
- if (ownerValid(ctx, owner) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94707
+ if (ownerValid(ctx, owner2) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94569
94708
  } finally {
94570
- if (ownerValid(ctx, owner) && state.generation === generation) {
94709
+ if (ownerValid(ctx, owner2) && state.generation === generation) {
94571
94710
  state.busy = false;
94572
94711
  state.loading = false;
94573
94712
  ctx.render();
@@ -94604,10 +94743,10 @@ async function openTuiSettingsPage(state, route2, ctx) {
94604
94743
  await loadPage(ctx, page);
94605
94744
  }
94606
94745
  async function loadProfile(ctx) {
94607
- const state = ctx.state, owner = currentOwner(ctx), generation = state.generation;
94746
+ const state = ctx.state, owner2 = currentOwner(ctx), generation = state.generation;
94608
94747
  try {
94609
94748
  const info = asObject(await ctx.client.whoAmI());
94610
- if (!ownerValid(ctx, owner) || state.generation !== generation) return;
94749
+ if (!ownerValid(ctx, owner2) || state.generation !== generation) return;
94611
94750
  state.profile = { username: safeText2(info.username), email: safeText2(info.email), account: safeText2(info.id), team: safeText2(info.active_team_name) };
94612
94751
  state.isAdmin = info.is_admin === true;
94613
94752
  ctx.render();
@@ -94615,11 +94754,11 @@ async function loadProfile(ctx) {
94615
94754
  }
94616
94755
  }
94617
94756
  async function loadCapabilities(ctx) {
94618
- const state = ctx.state, owner = currentOwner(ctx), generation = state.generation;
94757
+ const state = ctx.state, owner2 = currentOwner(ctx), generation = state.generation;
94619
94758
  if (!state.authenticated || typeof ctx.client.settingsGet !== "function") return;
94620
94759
  try {
94621
94760
  const status = asObject(await ctx.client.settingsGet("server-status"));
94622
- if (!ownerValid(ctx, owner) || state.generation !== generation) return;
94761
+ if (!ownerValid(ctx, owner2) || state.generation !== generation) return;
94623
94762
  state.paymentEnabled = status.is_self_hosted !== true && status.payment_enabled === true;
94624
94763
  state.paymentChecked = true;
94625
94764
  ctx.render();
@@ -94688,7 +94827,7 @@ async function runMutation(ctx, actionId) {
94688
94827
  ctx.render();
94689
94828
  return;
94690
94829
  }
94691
- const owner = currentOwner(ctx), generation = ++state.generation;
94830
+ const owner2 = currentOwner(ctx), generation = ++state.generation;
94692
94831
  state.busy = true;
94693
94832
  state.error = null;
94694
94833
  state.message = null;
@@ -94700,7 +94839,7 @@ async function runMutation(ctx, actionId) {
94700
94839
  ctx.render();
94701
94840
  return;
94702
94841
  }
94703
- if (!ownerValid(ctx, owner) || state.generation !== generation) return;
94842
+ if (!ownerValid(ctx, owner2) || state.generation !== generation) return;
94704
94843
  const response = asObject(result);
94705
94844
  if (response.success === false) throw new Error(safeText2(response.message) || "Operation was not accepted.");
94706
94845
  state.message = action2 ? `${action2.label} completed.` : "Saved.";
@@ -94716,9 +94855,9 @@ async function runMutation(ctx, actionId) {
94716
94855
  return;
94717
94856
  }
94718
94857
  } catch (error) {
94719
- if (ownerValid(ctx, owner) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94858
+ if (ownerValid(ctx, owner2) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94720
94859
  } finally {
94721
- if (ownerValid(ctx, owner) && state.generation === generation) {
94860
+ if (ownerValid(ctx, owner2) && state.generation === generation) {
94722
94861
  state.busy = false;
94723
94862
  ctx.render();
94724
94863
  }
@@ -94788,19 +94927,19 @@ async function handleTuiSettingsCommand(ctx, arg) {
94788
94927
  if (row3.switchTeam) {
94789
94928
  if (ctx.client.getActiveTeamId() === row3.switchTeam) await openTuiSettingsPage(state, "teams/details", ctx);
94790
94929
  else {
94791
- const owner = currentOwner(ctx), generation = ++state.generation;
94930
+ const owner2 = currentOwner(ctx), generation = ++state.generation;
94792
94931
  state.busy = true;
94793
94932
  state.error = null;
94794
94933
  ctx.render();
94795
94934
  try {
94796
94935
  const team = await ctx.client.getTeam(row3.switchTeam);
94797
- if (!ownerValid(ctx, owner) || state.generation !== generation) return true;
94936
+ if (!ownerValid(ctx, owner2) || state.generation !== generation) return true;
94798
94937
  ctx.client.setActiveTeamId(team.team_id ?? row3.switchTeam);
94799
94938
  ctx.render();
94800
94939
  } catch (error) {
94801
- if (ownerValid(ctx, owner) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94940
+ if (ownerValid(ctx, owner2) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94802
94941
  } finally {
94803
- if (ownerValid(ctx, owner) && state.generation === generation) {
94942
+ if (ownerValid(ctx, owner2) && state.generation === generation) {
94804
94943
  state.busy = false;
94805
94944
  ctx.render();
94806
94945
  }
@@ -95102,13 +95241,13 @@ function renderTuiSettings(state, width) {
95102
95241
  // src/tuiModelSelector.ts
95103
95242
  var currentOwner2 = (ctx) => typeof ctx.owner === "function" ? ctx.owner() : ctx.owner;
95104
95243
  var currentChatId = (ctx) => typeof ctx.chatId === "function" ? ctx.chatId() : ctx.chatId;
95105
- var current = (ctx, owner, chatId, generation) => ctx.state.owner === owner && ctx.state.chatId === chatId && currentOwner2(ctx) === owner && currentChatId(ctx) === chatId && ctx.state.generation === generation && (ctx.isOwnerCurrent?.() ?? true);
95244
+ var current = (ctx, owner2, chatId, generation) => ctx.state.owner === owner2 && ctx.state.chatId === chatId && currentOwner2(ctx) === owner2 && currentChatId(ctx) === chatId && ctx.state.generation === generation && (ctx.isOwnerCurrent?.() ?? true);
95106
95245
  var clean = (value) => terminalText(value).replace(/\s+/g, " ").trim();
95107
95246
  var modelValue = (model) => model.id;
95108
95247
  var rank = { low: 0, medium: 1, high: 2, max: 3 };
95109
- function createTuiModelSelectorState(owner, chatId = null) {
95248
+ function createTuiModelSelectorState(owner2, chatId = null) {
95110
95249
  return {
95111
- owner,
95250
+ owner: owner2,
95112
95251
  chatId,
95113
95252
  generation: 0,
95114
95253
  open: false,
@@ -95125,9 +95264,9 @@ function createTuiModelSelectorState(owner, chatId = null) {
95125
95264
  };
95126
95265
  }
95127
95266
  function syncTuiModelSelectorOwner(ctx) {
95128
- const owner = currentOwner2(ctx), chatId = currentChatId(ctx);
95129
- if (ctx.state.owner === owner && ctx.state.chatId === chatId && (ctx.isOwnerCurrent?.() ?? true)) return false;
95130
- Object.assign(ctx.state, createTuiModelSelectorState(owner, chatId), { generation: ctx.state.generation + 1 });
95267
+ const owner2 = currentOwner2(ctx), chatId = currentChatId(ctx);
95268
+ if (ctx.state.owner === owner2 && ctx.state.chatId === chatId && (ctx.isOwnerCurrent?.() ?? true)) return false;
95269
+ Object.assign(ctx.state, createTuiModelSelectorState(owner2, chatId), { generation: ctx.state.generation + 1 });
95131
95270
  ctx.render();
95132
95271
  return true;
95133
95272
  }
@@ -95165,14 +95304,14 @@ function tuiModelSelectionForSend(state) {
95165
95304
  }
95166
95305
  async function restoreTuiModelSelection(ctx) {
95167
95306
  syncTuiModelSelectorOwner(ctx);
95168
- const state = ctx.state, owner = currentOwner2(ctx), chatId = currentChatId(ctx), generation = ++state.generation;
95307
+ const state = ctx.state, owner2 = currentOwner2(ctx), chatId = currentChatId(ctx), generation = ++state.generation;
95169
95308
  state.ready = false;
95170
95309
  state.busy = true;
95171
95310
  state.error = null;
95172
95311
  ctx.render();
95173
95312
  try {
95174
95313
  const [catalogResult, selectionResult] = await Promise.allSettled([ctx.loadCatalog(), ctx.restoreSelection()]);
95175
- if (!current(ctx, owner, chatId, generation)) return;
95314
+ if (!current(ctx, owner2, chatId, generation)) return;
95176
95315
  if (catalogResult.status === "fulfilled") {
95177
95316
  state.models = catalogResult.value.filter((model) => model.id.startsWith(`${model.providerId}/`) && model.name && model.providerName);
95178
95317
  state.catalogAuthoritative = catalogResult.value.authoritative !== false;
@@ -95205,13 +95344,13 @@ async function restoreTuiModelSelection(ctx) {
95205
95344
  state.selection = "auto";
95206
95345
  state.error = "The selected model is unavailable. Reset to Auto.";
95207
95346
  await ctx.persistSelection("auto");
95208
- if (!current(ctx, owner, chatId, generation)) return;
95347
+ if (!current(ctx, owner2, chatId, generation)) return;
95209
95348
  }
95210
95349
  state.ready = true;
95211
95350
  } catch (error) {
95212
- if (current(ctx, owner, chatId, generation)) state.error = clean(error instanceof Error ? error.message : String(error));
95351
+ if (current(ctx, owner2, chatId, generation)) state.error = clean(error instanceof Error ? error.message : String(error));
95213
95352
  } finally {
95214
- if (current(ctx, owner, chatId, generation)) {
95353
+ if (current(ctx, owner2, chatId, generation)) {
95215
95354
  state.busy = false;
95216
95355
  ctx.render();
95217
95356
  }
@@ -95322,20 +95461,20 @@ async function handleTuiModelSelectorAction(ctx, action2) {
95322
95461
  const requested = action2.slice(7);
95323
95462
  if (requested !== "auto" && !usableModels(state).some((model) => modelValue(model) === requested)) return true;
95324
95463
  const selection = requested === state.selection && requested !== "auto" ? "auto" : requested;
95325
- const owner = currentOwner2(ctx), chatId = currentChatId(ctx), generation = ++state.generation;
95464
+ const owner2 = currentOwner2(ctx), chatId = currentChatId(ctx), generation = ++state.generation;
95326
95465
  state.busy = true;
95327
95466
  state.error = null;
95328
95467
  ctx.render();
95329
95468
  try {
95330
95469
  const persisted = await ctx.persistSelection(selection);
95331
- if (!current(ctx, owner, chatId, generation)) return true;
95470
+ if (!current(ctx, owner2, chatId, generation)) return true;
95332
95471
  state.selection = persisted;
95333
95472
  if (persisted === "auto") state.ready = true;
95334
95473
  closeTuiModelSelector(ctx);
95335
95474
  } catch (error) {
95336
- if (current(ctx, owner, chatId, generation)) state.error = clean(error instanceof Error ? error.message : String(error));
95475
+ if (current(ctx, owner2, chatId, generation)) state.error = clean(error instanceof Error ? error.message : String(error));
95337
95476
  } finally {
95338
- if (current(ctx, owner, chatId, generation)) {
95477
+ if (current(ctx, owner2, chatId, generation)) {
95339
95478
  state.busy = false;
95340
95479
  ctx.render();
95341
95480
  }
@@ -95484,15 +95623,15 @@ function createTuiModelSelectorShell(args) {
95484
95623
  }
95485
95624
  };
95486
95625
  const sync = () => {
95487
- const owner = ownerId(client2);
95488
- if (!owner || !isTuiAiComposer(state)) {
95626
+ const owner2 = ownerId(client2);
95627
+ if (!owner2 || !isTuiAiComposer(state)) {
95489
95628
  clearSubscription();
95490
95629
  ownerFence = null;
95491
95630
  state.modelSelector = null;
95492
95631
  return;
95493
95632
  }
95494
95633
  const chatId = chatIdForView(state);
95495
- if (state.modelSelector?.owner === owner && state.modelSelector.chatId === chatId && ownerFence?.()) {
95634
+ if (state.modelSelector?.owner === owner2 && state.modelSelector.chatId === chatId && ownerFence?.()) {
95496
95635
  if (!waitingForChat || state.headerState !== "ready") return;
95497
95636
  waitingForChat = false;
95498
95637
  const ctx = context();
@@ -95502,7 +95641,7 @@ function createTuiModelSelectorShell(args) {
95502
95641
  return;
95503
95642
  }
95504
95643
  clearSubscription();
95505
- state.modelSelector = createTuiModelSelectorState(owner, chatId);
95644
+ state.modelSelector = createTuiModelSelectorState(owner2, chatId);
95506
95645
  ownerFence = captureTuiWorkspaceOwner(client2);
95507
95646
  subscribe(chatId);
95508
95647
  const picker = state.modelSelector;
@@ -95577,12 +95716,12 @@ function createTuiModelSelectorShell(args) {
95577
95716
  }
95578
95717
  const ctx = context();
95579
95718
  if (!ctx) return { message, blocked: true };
95580
- const owner = picker.owner, chatId = picker.chatId, generation = picker.generation;
95719
+ const owner2 = picker.owner, chatId = picker.chatId, generation = picker.generation;
95581
95720
  picker.busy = true;
95582
95721
  render();
95583
95722
  try {
95584
95723
  const saved = await ctx.persistSelection(selected.id);
95585
- if (state.modelSelector !== picker || !ownerFence?.() || picker.owner !== owner || picker.chatId !== chatId || picker.generation !== generation)
95724
+ if (state.modelSelector !== picker || !ownerFence?.() || picker.owner !== owner2 || picker.chatId !== chatId || picker.generation !== generation)
95586
95725
  return { message, blocked: true };
95587
95726
  picker.selection = saved;
95588
95727
  picker.ready = true;
@@ -95968,11 +96107,12 @@ function parseTuiMarkdown(content, width, options = {}) {
95968
96107
  index = next;
95969
96108
  continue;
95970
96109
  }
95971
- for (const line3 of rows2.slice(index, Math.min(next + 1, rows2.length)))
96110
+ for (const line3 of options.semanticOnly ? [] : rows2.slice(index, Math.min(next + 1, rows2.length)))
95972
96111
  output.push(...wrapped([{ text: terminalText(line3), color: CODE }], width).map((line4) => ({ type: "line", line: line4 })));
95973
96112
  index = next;
95974
96113
  continue;
95975
96114
  }
96115
+ if (options.semanticOnly) continue;
95976
96116
  const heading = /^\s{0,3}#{1,6}\s+(.+?)\s*#*\s*$/.exec(row3);
95977
96117
  if (heading && output.length && output.at(-1)?.type === "line" && output.at(-1).line !== "") {
95978
96118
  const previous = output.at(-1);
@@ -96245,7 +96385,8 @@ function renderTuiResultsViewLines(data, options = {}) {
96245
96385
 
96246
96386
  // src/tuiChatResults.ts
96247
96387
  function messageResultsViews(content) {
96248
- return parseMessageSegments(content, { preserveCodeFences: true }).flatMap((segment) => segment.type === "text" ? parseTuiMarkdown(segment.value, 160).filter((block) => block.type === "results-view") : []);
96388
+ if (!/embeds_(?:results|map)_view/i.test(content)) return [];
96389
+ return parseMessageSegments(content, { preserveCodeFences: true }).flatMap((segment) => segment.type === "text" ? parseTuiMarkdown(segment.value, 160, { semanticOnly: true }).filter((block) => block.type === "results-view") : []);
96249
96390
  }
96250
96391
  function messageSupplementalEmbedIds(content) {
96251
96392
  const ids = /* @__PURE__ */ new Set();
@@ -97478,7 +97619,8 @@ function renderHeaderDialog(dialog, width, height = 24, state) {
97478
97619
 
97479
97620
  // src/tuiInteractiveQuestions.ts
97480
97621
  function messageQuestions(content) {
97481
- return parseTuiMarkdown(content, 160).flatMap((block) => block.type === "question" ? [block.payload] : []);
97622
+ if (!/interactive_question/i.test(content)) return [];
97623
+ return parseTuiMarkdown(content, 160, { semanticOnly: true }).flatMap((block) => block.type === "question" ? [block.payload] : []);
97482
97624
  }
97483
97625
  function chatQuestions(state) {
97484
97626
  const messages = state.screen === "example" && state.activeExample ? state.activeExample.messages : state.messages;
@@ -97491,7 +97633,8 @@ function chatQuestions(state) {
97491
97633
  const latest = questions.filter((item) => item.payload.id === question.payload.id).at(-1);
97492
97634
  for (let index = messages.length - 1; index > latest.messageIndex; index--) {
97493
97635
  if (messages[index].role !== "user") continue;
97494
- const response = parseTuiMarkdown(messages[index].content, 160).find((block) => block.type === "response" && block.payload.id === question.payload.id);
97636
+ if (!/interactive_response/i.test(messages[index].content)) continue;
97637
+ const response = parseTuiMarkdown(messages[index].content, 160, { semanticOnly: true }).find((block) => block.type === "response" && block.payload.id === question.payload.id);
97495
97638
  if (response?.type === "response" && !validateInteractiveQuestionAnswer(question.payload, response.payload)) {
97496
97639
  question.response = formatInteractiveQuestionAnswer(question.payload, response.payload).responsePayload;
97497
97640
  break;
@@ -98456,13 +98599,17 @@ function renderWorkspaceFrame(state, rawWidth, rawHeight, body, options = {}) {
98456
98599
  const marker = content.findIndex((row3) => /^[>›] /.test(lineText(row3).trimStart()));
98457
98600
  start = marker >= bodyHeight - 4 ? Math.max(0, marker - bodyHeight + 6) : 0;
98458
98601
  } else if (state.followSelection && ["start", "tasks", "projects", "project", "chat", "chats", "example", "examples", "workflow", "workflows", "apps", "app", "app-skill"].includes(state.screen) && state.focus === "content") {
98459
- const markers = content.map((row3, index) => /(?:^|[│|])\s*[>›] /.test(lineText(row3)) ? index : -1).filter((index) => index >= 0);
98602
+ const markers = content.map((row3, index) => /(?:^|[│║|])\s*[>›] /.test(lineText(row3)) ? index : -1).filter((index) => index >= 0);
98460
98603
  const marker = state.screen === "workflow" ? markers.at(-1) ?? -1 : markers[0] ?? -1;
98461
98604
  let selectionEnd = marker;
98462
98605
  if (["workflow", "start", "chats", "apps", "projects", "workflows"].includes(state.screen) && marker >= 0) {
98463
98606
  const closing2 = content.findIndex((row3, index) => index > marker && lineText(row3).includes("\u2570"));
98464
98607
  selectionEnd = Math.min(marker + 10, closing2 < 0 ? marker : closing2);
98465
98608
  }
98609
+ if ((state.screen === "tasks" || state.screen === "project" && state.projectTab === "tasks") && marker >= 0) {
98610
+ const closing2 = content.findIndex((row3, index) => index > marker && lineText(row3).includes("\u255A"));
98611
+ selectionEnd = closing2 < 0 ? marker : closing2;
98612
+ }
98466
98613
  if (selectionEnd >= start + viewportHeight) start = Math.min(maxStart, selectionEnd - viewportHeight + 1);
98467
98614
  if (marker >= 0 && marker < start) start = marker;
98468
98615
  }
@@ -98546,7 +98693,9 @@ function renderWorkspaceFrame(state, rawWidth, rawHeight, body, options = {}) {
98546
98693
  const sidebarRow = sidebarWidth ? renderSide(side[i], sidebarWidth, gutter, true) : void 0;
98547
98694
  const settingsColumn2 = gutter + sidebarWidth + paneWidth + 2;
98548
98695
  const settingsRow = settingsWidth ? renderSide(settingsRows[i], settingsWidth, settingsColumn2, false) : void 0;
98549
- rows2.push(place(rendered, sidebarRow, settingsRow));
98696
+ const painted = place(rendered, sidebarRow, settingsRow);
98697
+ rows2.push(painted);
98698
+ options.onBodyLine?.(value, i + 2, rendered, painted);
98550
98699
  }
98551
98700
  if (state.modelSelector?.open && showComposer && aiComposer && !modal && !settingsFullscreen && bodyHeight > 0) {
98552
98701
  const popWidth = Math.max(1, Math.min(46, composerWidth));
@@ -99906,8 +100055,8 @@ function executeTuiAppsSkill(client2, run, confirmation) {
99906
100055
  async function executeConfirmedTuiAppsSkill(client2, run) {
99907
100056
  if (!client2.hasSession()) throw new Error("Sign in to run Apps skills in the terminal.");
99908
100057
  if (client2.getActiveTeamId()) throw new Error("Switch to Personal to run Apps skills in the terminal.");
99909
- const owner = record7(await client2.whoAmI());
99910
- const ownerId2 = string3(owner.id, string3(owner.user_id));
100058
+ const owner2 = record7(await client2.whoAmI());
100059
+ const ownerId2 = string3(owner2.id, string3(owner2.user_id));
99911
100060
  if (!ownerId2) throw new Error("Account identity unavailable; skill was not run.");
99912
100061
  const rootId = randomUUID19();
99913
100062
  const key = randomBytes10(32);
@@ -100233,6 +100382,106 @@ async function handleSettingsKey(context, chunk, key) {
100233
100382
  return handleTuiSettingsKey(ctx, chunk, key);
100234
100383
  }
100235
100384
 
100385
+ // src/tuiStreamingRender.ts
100386
+ var COLORS = ["#ff2d55", "#ff6b2b", "#ffd60a", "#30d158", "#32ade6", "#bf5af2"];
100387
+ var glowLines = /* @__PURE__ */ new WeakSet();
100388
+ var ACTION_SKILL = /^(?:add|cancel|clean|create|delete|edit|generate|keep-temporary|modify|remove|run|schedule|send|set|share|speak|transcribe|translate|update|vectorize|write)(?:$|[-_])/;
100389
+ function tuiResponsePhase(segments2) {
100390
+ const skills = [];
100391
+ for (let i = segments2.length - 1; i >= 0; i--) {
100392
+ const segment = segments2[i];
100393
+ if (segment.type === "text") {
100394
+ if (segment.value.trim()) break;
100395
+ continue;
100396
+ }
100397
+ skills.push(typeof segment.meta?.skill_id === "string" ? segment.meta.skill_id : "");
100398
+ }
100399
+ return skills.length ? skills.some((id) => ACTION_SKILL.test(id.toLowerCase())) ? "Working" : "Researching" : "Working";
100400
+ }
100401
+ function clearTuiSendState(state) {
100402
+ state.isBusy = false;
100403
+ state.isAwaitingAi = false;
100404
+ state.streamingMessage = null;
100405
+ state.aiTaskId = null;
100406
+ state.projectFocusPending = null;
100407
+ }
100408
+ var owner = (state) => JSON.stringify([state.signedIn, state.currentUserHash, state.activeTeamId, state.activeChatId, state.routeVersion]);
100409
+ var frames2 = /* @__PURE__ */ new WeakMap();
100410
+ function renderTuiThinking(mate, width, model, phase = "Thinking") {
100411
+ const status = truncateCells(`\u2726 ${phase}\u2026`, width), statusStart = Math.max(0, Math.floor((width - cells(status)) / 2));
100412
+ const name = truncateCells(mate, Math.max(0, statusStart - 1));
100413
+ const spans = [
100414
+ { text: name, bold: true, color: "#5a85eb" },
100415
+ { text: " ".repeat(Math.max(0, statusStart - cells(name))) },
100416
+ { text: status, bold: true, color: "#80caff" }
100417
+ ];
100418
+ const rows2 = name ? [{ text: spans.map((span) => span.text).join(""), spans }] : [
100419
+ { text: truncateCells(mate, width), bold: true, color: "#5a85eb" },
100420
+ { text: status, bold: true, color: "#80caff" }
100421
+ ];
100422
+ if (model) rows2.push({ text: truncateCells(model, width), color: "#808080" });
100423
+ return rows2;
100424
+ }
100425
+ function renderTuiStreamingGlow(width, phase) {
100426
+ const size = Math.max(1, Math.min(72, width)), inset = Math.max(0, Math.floor((width - size) / 2));
100427
+ const spans = [
100428
+ { text: " ".repeat(inset), color: "#e6e6e6" },
100429
+ ...Array.from({ length: size }, (_, index) => ({
100430
+ text: "\u2581",
100431
+ color: COLORS[(Math.floor(index * COLORS.length / size - phase / 2) % COLORS.length + COLORS.length) % COLORS.length]
100432
+ })),
100433
+ { text: " ".repeat(Math.max(0, width - size - inset)), color: "#e6e6e6" }
100434
+ ];
100435
+ const line3 = { text: spans.map((span) => span.text).join(""), spans };
100436
+ glowLines.add(line3);
100437
+ return line3;
100438
+ }
100439
+ function isTuiStreamingGlow(line3) {
100440
+ return typeof line3 !== "string" && glowLines.has(line3);
100441
+ }
100442
+ function clearTuiStreamingFrame(state) {
100443
+ frames2.delete(state);
100444
+ }
100445
+ function rememberTuiStreamingFrame(state, frame, width, height, bodyWidth, painted, options) {
100446
+ const mode = options.colorMode ?? "none";
100447
+ if (!painted || !state.isAwaitingAi || state.screen !== "chat" || mode === "none" || options.reducedMotion || state.modelSelector?.open) return;
100448
+ const at = painted.line.indexOf(painted.rendered);
100449
+ if (at < 0) return;
100450
+ const prefix = painted.line.slice(0, at), suffix = painted.line.slice(at + painted.rendered.length);
100451
+ const variants = Array.from({ length: 12 }, (_, phase) => {
100452
+ const glow = renderTuiStreamingGlow(bodyWidth, phase);
100453
+ return prefix + (typeof glow === "string" ? glow : glow.spans.map((span) => foreground(span.text, span.color ?? "#e6e6e6", mode)).join("")) + suffix;
100454
+ });
100455
+ frames2.set(state, {
100456
+ owner: owner(state),
100457
+ width,
100458
+ height,
100459
+ mode,
100460
+ ascii: options.ascii ?? false,
100461
+ rows: frame.split("\n"),
100462
+ row: painted.row,
100463
+ variants,
100464
+ messages: state.messages,
100465
+ message: state.streamingMessage,
100466
+ content: state.streamingMessage?.content,
100467
+ thinking: state.streamingMessage?.thinkingContent,
100468
+ thinkingActive: state.streamingMessage?.thinkingActive,
100469
+ expanded: state.streamingMessage?.thinkingExpanded,
100470
+ model: state.streamingMessage?.modelName,
100471
+ scroll: state.scrollOffset,
100472
+ input: state.input,
100473
+ focus: state.focus,
100474
+ sidebar: state.sidebarOpen,
100475
+ settings: state.settings
100476
+ });
100477
+ }
100478
+ function renderTuiStreamingAnimationFrame(state, width, height, options = {}) {
100479
+ const saved = frames2.get(state);
100480
+ if (options.reducedMotion || !saved || !state.isAwaitingAi || state.screen !== "chat" || state.textSelection || state.form || state.chrome || state.questionEditor || state.paletteOpen || state.modelSelector?.open || saved.owner !== owner(state) || saved.width !== width || saved.height !== height || saved.mode !== (options.colorMode ?? "none") || saved.ascii !== (options.ascii ?? false) || saved.messages !== state.messages || saved.message !== state.streamingMessage || saved.content !== state.streamingMessage?.content || saved.thinking !== state.streamingMessage?.thinkingContent || saved.thinkingActive !== state.streamingMessage?.thinkingActive || saved.expanded !== state.streamingMessage?.thinkingExpanded || saved.model !== state.streamingMessage?.modelName || saved.scroll !== state.scrollOffset || saved.input !== state.input || saved.focus !== state.focus || saved.sidebar !== state.sidebarOpen || saved.settings !== state.settings) return null;
100481
+ saved.rows[saved.row] = saved.variants[(state.streamingPhase % 12 + 12) % 12];
100482
+ return saved.rows.join("\n");
100483
+ }
100484
+
100236
100485
  // src/tuiFullscreenChrome.ts
100237
100486
  function captureTuiView(state) {
100238
100487
  return {
@@ -100248,6 +100497,7 @@ function captureTuiView(state) {
100248
100497
  };
100249
100498
  }
100250
100499
  function restoreTuiView(state, origin) {
100500
+ clearTuiSendState(state);
100251
100501
  Object.assign(state, origin);
100252
100502
  state.inputCursor = origin.inputCursor ?? null;
100253
100503
  state.navigationIndex = ["chats", "apps", "projects", "workflows", "tasks"].indexOf(state.workspace);
@@ -100276,6 +100526,7 @@ function closeTuiFullscreen(state) {
100276
100526
  return true;
100277
100527
  }
100278
100528
  if (state.screen === "chat" || state.screen === "example") {
100529
+ clearTuiSendState(state);
100279
100530
  if (!state.input.startsWith("/")) state.drafts[state.screen === "example" ? `example:${state.activeExample?.chat.id}` : state.activeChatId ?? "new"] = state.input;
100280
100531
  const origin = state.chatOrigin;
100281
100532
  state.chatOrigin = null;
@@ -100504,7 +100755,9 @@ function createInitialTuiState() {
100504
100755
  chatContextAuthoringControls: {},
100505
100756
  status: null,
100506
100757
  isBusy: false,
100507
- isAwaitingAi: false
100758
+ isAwaitingAi: false,
100759
+ streamingMessage: null,
100760
+ streamingPhase: 0
100508
100761
  };
100509
100762
  }
100510
100763
  function programmaticQuickstart() {
@@ -100560,6 +100813,7 @@ function resetEndedTuiSession(state, hasSession) {
100560
100813
  return true;
100561
100814
  }
100562
100815
  function renderTuiFrame(state, width, height, options = {}) {
100816
+ clearTuiStreamingFrame(state);
100563
100817
  if (state.screen !== "chat") clearChatRenderCache(state);
100564
100818
  fenceTuiSettingsView(state);
100565
100819
  beginPointerFrame(state, width, height);
@@ -100569,13 +100823,23 @@ function renderTuiFrame(state, width, height, options = {}) {
100569
100823
  const teamBanner = state.signedIn && state.activeTeamId ? [{ text: teamIdentityLabel(state), color: "#5a85eb", bold: true }] : [];
100570
100824
  const embedHero = state.screen === "embed" ? renderEmbedHero(state, bodyWidth, height) : null;
100571
100825
  const stickyRows = state.screen === "embed" ? embedHero.length + 1 : state.screen === "results-view" ? 2 : state.screen === "project" && state.activeProject ? renderProjectIdentity(state.activeProject, { width: bodyWidth }).length + renderProjectTabs(state.projectTab, bodyWidth).length : state.screen === "app" && state.activeApp ? renderTuiAppIdentity(state.activeApp, bodyWidth).length + renderTuiAppTabs(state.appTab, bodyWidth).length : state.screen === "app-skill" && state.activeAppSkill ? renderTuiAppsSkillIdentity(state.activeAppSkill, bodyWidth).length + renderTuiAppsSkillTabs(state.appSkillTab, bodyWidth).length : state.screen === "workflow" && state.activeWorkflow ? renderWorkflowIdentity(state.activeWorkflow, { width: bodyWidth, run: state.workflowTab === "runs" ? state.workflowRuns[state.selectedWorkflowRunIndex] : void 0 }).length + (bodyWidth < 36 ? 7 : 4) : 0;
100572
- return renderWorkspaceFrame(
100826
+ let glow;
100827
+ const frame = renderWorkspaceFrame(
100573
100828
  state,
100574
100829
  width,
100575
100830
  height,
100576
100831
  [...header2, ...teamBanner, ...renderBody(state, bodyWidth, height, embedHero ?? void 0)],
100577
- { ...options, stickyRows: stickyRows + header2.length + teamBanner.length, stickyFallbackRows: embedHero ? header2.length + teamBanner.length : void 0 }
100832
+ {
100833
+ ...options,
100834
+ stickyRows: stickyRows + header2.length + teamBanner.length,
100835
+ stickyFallbackRows: embedHero ? header2.length + teamBanner.length : void 0,
100836
+ onBodyLine: (value, row3, rendered, line3) => {
100837
+ if (isTuiStreamingGlow(value)) glow = { row: row3, rendered, line: line3 };
100838
+ }
100839
+ }
100578
100840
  );
100841
+ rememberTuiStreamingFrame(state, frame, width, height, bodyWidth, glow, options);
100842
+ return frame;
100579
100843
  }
100580
100844
  function teamIdentityLabel(state) {
100581
100845
  const name = terminalText(state.activeTeamName?.trim() || "Team").replace(/\s+/g, " ").slice(0, 80);
@@ -100687,7 +100951,7 @@ function renderScreenBody(state, width, height, embedHero) {
100687
100951
  case "projects":
100688
100952
  return [...homeHeader(state, width, height), ...renderProjectCarousel(filteredProjects(state.projects, state.filter), width, state.selectedIndex, state.focus === "content")];
100689
100953
  case "project":
100690
- return state.activeProject ? state.projectTab === "tasks" ? [...renderProjectIdentity(state.activeProject, { width }), ...renderProjectPointerTabs("tasks", width), "", ...renderTaskBoard2(state.tasks, { width, selectedTaskId: filterTasks(state.tasks, state.filter)[state.selectedIndex]?.taskId, query: state.filter })] : 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 }) : ["Projects", "Loading project\u2026"];
100954
+ return state.activeProject ? state.projectTab === "tasks" ? [...renderProjectIdentity(state.activeProject, { width }), ...renderProjectPointerTabs("tasks", width), "", ...renderTaskBoard2(state.tasks, { width, selectedTaskId: filterTasks(state.tasks, state.filter)[state.selectedIndex]?.taskId, query: state.filter, viewport: taskViewport(state, height, renderProjectIdentity(state.activeProject, { width }).length + renderProjectPointerTabs("tasks", width).length + 1) })] : 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 }) : ["Projects", "Loading project\u2026"];
100691
100955
  case "status":
100692
100956
  return renderStatus(state, width);
100693
100957
  case "results-view": {
@@ -100719,9 +100983,9 @@ function renderScreenBody(state, width, height, embedHero) {
100719
100983
  case "workflow":
100720
100984
  return renderWorkflowDetail(state, width);
100721
100985
  case "tasks":
100722
- return [...homeHeader(state, width, height), ...renderTasks(state, width)];
100986
+ return [...homeHeader(state, width, height), ...renderTasks(state, width, height, homeHeader(state, width, height).length)];
100723
100987
  case "task":
100724
- return renderTaskDetail2(state, width);
100988
+ return renderTaskDetail2(state, width, height);
100725
100989
  case "start":
100726
100990
  default:
100727
100991
  return [...renderHomeChatCards(state, width, height), ...state.signedIn && state.activeTeamId ? ["", teamAiReminder()] : []];
@@ -100761,11 +101025,18 @@ function renderHelp(width) {
100761
101025
  "Outside TUI: openmates --help, openmates chats --help, openmates apps --help"
100762
101026
  ].flatMap((line3) => wrap(line3, width));
100763
101027
  }
100764
- function renderTasks(state, width) {
100765
- return renderTaskBoard2(state.tasks, { width, selectedTaskId: filterTasks(state.tasks, state.filter, state.taskStatusFilter || void 0)[state.selectedIndex]?.taskId, query: state.filter, status: state.taskStatusFilter || void 0 });
101028
+ function taskViewport(state, height, prefixRows = 0) {
101029
+ return {
101030
+ start: Math.max(0, state.scrollOffset - prefixRows - 4),
101031
+ end: state.scrollOffset + height + 4,
101032
+ followSelection: state.followSelection && state.focus === "content"
101033
+ };
101034
+ }
101035
+ function renderTasks(state, width, height, prefixRows = 0) {
101036
+ return renderTaskBoard2(state.tasks, { width, selectedTaskId: filterTasks(state.tasks, state.filter, state.taskStatusFilter || void 0)[state.selectedIndex]?.taskId, query: state.filter, status: state.taskStatusFilter || void 0, viewport: taskViewport(state, height, prefixRows) });
100766
101037
  }
100767
- function renderTaskDetail2(state, width) {
100768
- if (!state.activeTask) return renderTasks(state, width);
101038
+ function renderTaskDetail2(state, width, height) {
101039
+ if (!state.activeTask) return renderTasks(state, width, height);
100769
101040
  return renderTaskDetails(state.activeTask, {
100770
101041
  width,
100771
101042
  activity: state.taskContext?.activity,
@@ -100868,8 +101139,7 @@ function renderExampleChat(state, width, height) {
100868
101139
  }
100869
101140
  return lines.flatMap((line3) => typeof line3 === "string" ? wrap(line3, width) : [line3]);
100870
101141
  }
100871
- function renderChat(state, width, height) {
100872
- const lines = renderChatHeader(state, width, height);
101142
+ function renderChat(state, width, height, fullHistory = false) {
100873
101143
  const embeds = new Map(Object.entries(state.chatEmbeds));
100874
101144
  const cacheContext = {
100875
101145
  owner: JSON.stringify([state.signedIn, state.currentUserHash, state.activeTeamId, state.activeChatId, state.routeVersion]),
@@ -100896,47 +101166,84 @@ function renderChat(state, width, height) {
100896
101166
  return firstEmbedId;
100897
101167
  };
100898
101168
  let contextIndex2 = 0, viewOffset = 0, questionOffset = 0;
100899
- for (const [index, message] of state.messages.entries()) {
101169
+ const offsets = state.messages.map((message) => {
100900
101170
  const event = message.role === "system" ? parseChatContextContent(message.content) : null;
100901
- if (event) {
100902
- contextIndex2++;
100903
- lines.push(`${chatContextSummary(event)} \xB7 /context ${contextIndex2}`);
101171
+ const offset = { event, contextIndex: contextIndex2 + (event ? 1 : 0), viewOffset, questionOffset };
101172
+ if (event) contextIndex2++;
101173
+ else {
101174
+ viewOffset += messageResultsViews(message.content).length;
101175
+ if (message.role === "assistant") questionOffset += messageQuestions(message.content).length;
101176
+ }
101177
+ return offset;
101178
+ });
101179
+ const footer = [];
101180
+ if (state.projectFocusPending) footer.push("Project access starts after the countdown. /project-focus-reject to cancel.");
101181
+ if (state.signedIn && state.activeTeamId) footer.push(teamAiReminder());
101182
+ if (state.followUpSuggestions.length) footer.push("", "Suggestions", ...state.followUpSuggestions.slice(0, 3).map((s, i) => ` ${i + 1}. ${s}`));
101183
+ if (state.status) footer.push("", state.status);
101184
+ const wrappedFooter = footer.flatMap((line3) => typeof line3 === "string" ? wrap(line3, width) : [line3]);
101185
+ const blocks = [];
101186
+ let rowCount = wrappedFooter.length;
101187
+ if (state.isAwaitingAi && !state.streamingMessage?.content.trim()) {
101188
+ const mate = messageLabel("assistant", state.streamingMessage?.title, state.streamingMessage?.category, state.activeChat);
101189
+ const waiting = [...renderTuiThinking(mate, width, state.streamingMessage?.modelName), ...renderTuiReasoning(state.streamingMessage, width), renderTuiStreamingGlow(width, state.streamingPhase), ""];
101190
+ blocks.push(waiting);
101191
+ rowCount += waiting.length;
101192
+ }
101193
+ const budget = Math.max(0, state.scrollOffset) + height + 1;
101194
+ for (let index = state.messages.length - 1; index >= 0; index--) {
101195
+ const message = state.messages[index], offset = offsets[index], rows2 = [];
101196
+ if (offset.event) {
101197
+ const event = offset.event;
101198
+ rows2.push(`${chatContextSummary(event)} \xB7 /context ${offset.contextIndex}`);
100904
101199
  if (event.type === "project_authoring_recommendation") {
100905
101200
  const job = state.chatContextAuthoringJobs[event.event_id];
100906
- lines.push(job ? `Authoring: ${job.status} \xB7 ${job.status === "needs_write_approval" ? "/authoring-save" : "/authoring-refresh"} ${contextIndex2}` : `[${event.action === "create" ? "Create" : "Update"} ${event.kind === "focus" ? "Focus" : "Workflow"}] /focus-author ${contextIndex2}`);
101201
+ rows2.push(job ? `Authoring: ${job.status} \xB7 ${job.status === "needs_write_approval" ? "/authoring-save" : "/authoring-refresh"} ${offset.contextIndex}` : `[${event.action === "create" ? "Create" : "Update"} ${event.kind === "focus" ? "Focus" : "Workflow"}] /focus-author ${offset.contextIndex}`);
100907
101202
  }
100908
- lines.push("");
100909
- continue;
101203
+ } else {
101204
+ if (message === state.streamingMessage && !message.content.trim()) continue;
101205
+ const questionBlocks = message.role === "assistant";
101206
+ const prepared = cachedChatLayout(
101207
+ state,
101208
+ message,
101209
+ message.content,
101210
+ { ...cacheContext, variant: questionBlocks ? "questions" : "literal-questions", cacheable: cacheable.has(index) },
101211
+ () => prepareMessageContent(message.content, width, state, questionBlocks)
101212
+ );
101213
+ const label3 = messageLabel(message.role, message.title, message.category, state.activeChat, message.remoteUser);
101214
+ if (state.isAwaitingAi && message === state.streamingMessage) rows2.push(...renderTuiThinking(label3, width, message.modelName, message.thinkingActive ? "Thinking" : tuiResponsePhase(prepared.segments)));
101215
+ else {
101216
+ rows2.push({ text: label3, color: "#5a85eb", bold: true });
101217
+ if (message.role === "assistant" && message.modelName) rows2.push({ text: message.modelName, color: "#808080" });
101218
+ }
101219
+ if (message.role === "assistant") rows2.push(...renderTuiReasoning(message, width));
101220
+ rows2.push(...renderPreparedMessageContent(
101221
+ prepared,
101222
+ width,
101223
+ embeds,
101224
+ state,
101225
+ questionBlocks ? message.embedIds : void 0,
101226
+ offset.viewOffset,
101227
+ offset.questionOffset,
101228
+ defaultEmbedId
101229
+ ));
101230
+ if (state.isAwaitingAi && message === state.streamingMessage) rows2.push(renderTuiStreamingGlow(width, state.streamingPhase));
100910
101231
  }
100911
- lines.push({ text: messageLabel(message.role, message.title, message.category, state.activeChat, message.remoteUser), color: "#5a85eb", bold: true });
100912
- const questionBlocks = message.role === "assistant";
100913
- const prepared = cachedChatLayout(
100914
- state,
100915
- message,
100916
- message.content,
100917
- { ...cacheContext, variant: questionBlocks ? "questions" : "literal-questions", cacheable: cacheable.has(index) },
100918
- () => prepareMessageContent(message.content, width, state, questionBlocks, true)
100919
- );
100920
- lines.push(...renderPreparedMessageContent(
100921
- prepared,
100922
- width,
100923
- embeds,
100924
- state,
100925
- questionBlocks ? message.embedIds : void 0,
100926
- viewOffset,
100927
- questionOffset,
100928
- defaultEmbedId
100929
- ));
100930
- viewOffset += prepared.viewCount;
100931
- if (questionBlocks) questionOffset += prepared.questionCount;
100932
- lines.push("");
100933
- }
100934
- if (state.projectFocusPending) lines.push("Project access starts after the countdown. /project-focus-reject to cancel.");
100935
- if (state.isAwaitingAi) lines.push(`${messageLabel("assistant", null, null, state.activeChat)} is typing...`);
100936
- if (state.signedIn && state.activeTeamId) lines.push(teamAiReminder());
100937
- if (state.followUpSuggestions.length) lines.push("", "Suggestions", ...state.followUpSuggestions.slice(0, 3).map((s, i) => ` ${i + 1}. ${s}`));
100938
- if (state.status) lines.push("", state.status);
100939
- return lines.flatMap((line3) => typeof line3 === "string" ? wrap(line3, width) : [line3]);
101232
+ rows2.push("");
101233
+ const wrapped2 = rows2.flatMap((line3) => typeof line3 === "string" ? wrap(line3, width) : [line3]);
101234
+ blocks.push(wrapped2);
101235
+ rowCount += wrapped2.length;
101236
+ if (!fullHistory && !(state.followSelection && state.focus === "content") && rowCount >= budget) break;
101237
+ }
101238
+ return [...renderChatHeader(state, width, height), ...blocks.reverse().flat(), ...wrappedFooter];
101239
+ }
101240
+ function renderTuiReasoning(message, width) {
101241
+ if (!message?.thinkingContent?.trim()) return [];
101242
+ const summary = pointerLine(
101243
+ `\u2726 Thinking for this response ${message.thinkingExpanded ? "\u25BE" : "\u25B8"} /thinking`,
101244
+ { kind: "command", command: `/thinking${message.id ? " " + message.id : ""}` }
101245
+ );
101246
+ return [summary, ...message.thinkingExpanded ? wrap(message.thinkingContent, width).map((text7) => ({ text: text7, color: "#a0a0a0" })) : []];
100940
101247
  }
100941
101248
  function teamAiReminder() {
100942
101249
  return "To ask AI, mention @openmates or a configured Mate.";
@@ -100989,17 +101296,13 @@ function relativeTime(timestamp) {
100989
101296
  function renderStatus(state, width) {
100990
101297
  return ["OpenMates", "", state.status ?? "Working..."].flatMap((line3) => wrap(line3, width));
100991
101298
  }
100992
- function prepareMessageContent(content, width, state, questionBlocks = true, countQuestions = false) {
101299
+ function prepareMessageContent(content, width, state, questionBlocks = true) {
100993
101300
  const segments2 = parseMessageSegments(content, { preserveCodeFences: true }).map((segment) => segment.type === "embed" ? segment : {
100994
101301
  type: "text",
100995
101302
  value: segment.value,
100996
101303
  blocks: parseTuiMarkdown(segment.value, width, { resolveEmbedAlias: (id) => state ? aliasForEmbed(state, id) : id, questionBlocks })
100997
101304
  });
100998
- return {
100999
- segments: segments2,
101000
- viewCount: segments2.reduce((count, segment) => count + (segment.type === "text" ? segment.blocks.filter((block) => block.type === "results-view").length : 0), 0),
101001
- questionCount: countQuestions && questionBlocks ? messageQuestions(content).length : 0
101002
- };
101305
+ return { segments: segments2 };
101003
101306
  }
101004
101307
  function renderMessageContentStyled(content, width, embeds = /* @__PURE__ */ new Map(), state, extraIds = [], viewOffset = 0, questionOffset = 0, questionBlocks = true) {
101005
101308
  return renderPreparedMessageContent(
@@ -101431,6 +101734,7 @@ function rememberDraft(state) {
101431
101734
  if (state.workspace === "chats" && !state.input.startsWith("/")) state.drafts[chatDraftKey(state)] = state.input;
101432
101735
  }
101433
101736
  function route(state, workspace, screen) {
101737
+ clearTuiSendState(state);
101434
101738
  if (state.settings) closeTuiSettings(state);
101435
101739
  if (state.input) rememberDraft(state);
101436
101740
  state.workspace = workspace;
@@ -101902,6 +102206,18 @@ async function handleWorkspaceCommand(context, command) {
101902
102206
  }
101903
102207
  return true;
101904
102208
  }
102209
+ case "/thinking": {
102210
+ const messages = [...state.messages, ...state.streamingMessage && !state.messages.includes(state.streamingMessage) ? [state.streamingMessage] : []];
102211
+ const message = arg ? messages.find((message2) => message2.id === arg) : messages.findLast((message2) => message2.role === "assistant" && message2.thinkingContent?.trim());
102212
+ if (state.input.trim() === command) {
102213
+ state.input = "";
102214
+ state.inputCursor = null;
102215
+ }
102216
+ if (message?.thinkingContent?.trim()) message.thinkingExpanded = !message.thinkingExpanded;
102217
+ else state.status = "No visible thinking is available for this response.";
102218
+ render();
102219
+ return true;
102220
+ }
101905
102221
  case "/browse":
101906
102222
  state.homeShowAll = !state.homeShowAll;
101907
102223
  state.selectedIndex = 0;
@@ -103384,21 +103700,21 @@ function string4(value) {
103384
103700
  function ownerToken(session, teamId) {
103385
103701
  return createHmac4("sha256", Buffer.from(session.masterKeyExportedB64, "base64")).update(JSON.stringify([session.apiUrl, session.hashedEmail, teamId, session.createdAt])).digest("hex");
103386
103702
  }
103387
- function keyFor(owner, chatId) {
103388
- return createHmac4("sha256", Buffer.from(owner, "hex")).update(chatId).digest("hex");
103703
+ function keyFor(owner2, chatId) {
103704
+ return createHmac4("sha256", Buffer.from(owner2, "hex")).update(chatId).digest("hex");
103389
103705
  }
103390
103706
  function isRecord5(value) {
103391
103707
  const v = object4(value);
103392
103708
  return typeof v.ciphertext === "string" && v.ciphertext.length > 0 && Number.isSafeInteger(v.version) && v.version >= 0 && typeof v.pending === "boolean";
103393
103709
  }
103394
- function readLocal(owner, chatId) {
103710
+ function readLocal(owner2, chatId) {
103395
103711
  try {
103396
103712
  const path2 = join19(resolveStateDir(), FILE);
103397
103713
  const raw = readFileSync17(path2, "utf8");
103398
103714
  if (Buffer.byteLength(raw) > MAX_FILE_BYTES3) return null;
103399
103715
  const envelope = object4(JSON.parse(raw));
103400
103716
  if (envelope.version !== 2) return null;
103401
- const record10 = object4(object4(envelope.owners)[owner])[keyFor(owner, chatId)];
103717
+ const record10 = object4(object4(envelope.owners)[owner2])[keyFor(owner2, chatId)];
103402
103718
  return isRecord5(record10) ? record10 : null;
103403
103719
  } catch {
103404
103720
  return null;
@@ -103407,7 +103723,7 @@ function readLocal(owner, chatId) {
103407
103723
  function sameRecord(left, right) {
103408
103724
  return left === right || !!left && !!right && left.ciphertext === right.ciphertext && left.version === right.version && left.pending === right.pending;
103409
103725
  }
103410
- async function writeLocal(owner, chatId, record10, current2, expected) {
103726
+ async function writeLocal(owner2, chatId, record10, current2, expected) {
103411
103727
  if (!current2()) throw new Error("Chat model preference owner changed.");
103412
103728
  const dir = resolveStateDir();
103413
103729
  mkdirSync16(dir, { recursive: true, mode: 448 });
@@ -103432,11 +103748,11 @@ async function writeLocal(owner, chatId, record10, current2, expected) {
103432
103748
  }
103433
103749
  } catch {
103434
103750
  }
103435
- const chats = owners[owner] ?? {};
103436
- const existing = chats[keyFor(owner, chatId)] ?? null;
103751
+ const chats = owners[owner2] ?? {};
103752
+ const existing = chats[keyFor(owner2, chatId)] ?? null;
103437
103753
  if (expected !== void 0 && !sameRecord(existing, expected)) return existing;
103438
- chats[keyFor(owner, chatId)] = record10;
103439
- owners[owner] = chats;
103754
+ chats[keyFor(owner2, chatId)] = record10;
103755
+ owners[owner2] = chats;
103440
103756
  if (Object.values(owners).reduce((sum, values) => sum + Object.keys(values).length, 0) > MAX_CHATS)
103441
103757
  throw new Error("Chat model preference cache is full.");
103442
103758
  const encoded = JSON.stringify({ version: 2, owners });
@@ -103515,21 +103831,21 @@ function createChatModelPreferences(client2) {
103515
103831
  if (!client2.hasSession()) throw new Error("Sign in to choose a chat model.");
103516
103832
  const session = client2.getSession();
103517
103833
  const teamId = client2.resolveTeamContext();
103518
- const owner = ownerToken(session, teamId);
103834
+ const owner2 = ownerToken(session, teamId);
103519
103835
  const persisted = loadSession();
103520
- if (persisted && ownerToken(persisted, teamId) !== owner)
103836
+ if (persisted && ownerToken(persisted, teamId) !== owner2)
103521
103837
  throw new Error("Chat model preference owner changed.");
103522
103838
  const current2 = () => {
103523
103839
  try {
103524
- if (!client2.hasSession() || ownerToken(client2.getSession(), client2.resolveTeamContext()) !== owner) return false;
103840
+ if (!client2.hasSession() || ownerToken(client2.getSession(), client2.resolveTeamContext()) !== owner2) return false;
103525
103841
  if (!persisted) return true;
103526
103842
  const onDisk = loadSession();
103527
- return !!onDisk && ownerToken(onDisk, teamId) === owner;
103843
+ return !!onDisk && ownerToken(onDisk, teamId) === owner2;
103528
103844
  } catch {
103529
103845
  return false;
103530
103846
  }
103531
103847
  };
103532
- return { owner, teamId, current: current2, masterKey: Buffer.from(session.masterKeyExportedB64, "base64") };
103848
+ return { owner: owner2, teamId, current: current2, masterKey: Buffer.from(session.masterKeyExportedB64, "base64") };
103533
103849
  };
103534
103850
  const checked = (current2) => {
103535
103851
  if (!current2()) throw new Error("Chat model preference owner changed.");
@@ -103539,10 +103855,10 @@ function createChatModelPreferences(client2) {
103539
103855
  if (plaintext === null) throw new Error("Chat model preference could not be decrypted.");
103540
103856
  return parseSelection(plaintext);
103541
103857
  };
103542
- const stateKey = (owner, chatId) => `${owner}:${chatId}`;
103543
- const publish = (owner, chatId, state, current2) => {
103858
+ const stateKey = (owner2, chatId) => `${owner2}:${chatId}`;
103859
+ const publish = (owner2, chatId, state, current2) => {
103544
103860
  checked(current2);
103545
- const key = stateKey(owner, chatId);
103861
+ const key = stateKey(owner2, chatId);
103546
103862
  states2.set(key, state);
103547
103863
  for (const listener of listeners.get(key) ?? []) {
103548
103864
  try {
@@ -103552,18 +103868,18 @@ function createChatModelPreferences(client2) {
103552
103868
  }
103553
103869
  return state;
103554
103870
  };
103555
- const withChat = (owner, chatId, work) => {
103556
- const key = stateKey(owner, chatId);
103871
+ const withChat = (owner2, chatId, work) => {
103872
+ const key = stateKey(owner2, chatId);
103557
103873
  const task = (serial.get(key) ?? Promise.resolve()).then(work);
103558
103874
  serial.set(key, task.catch(() => {
103559
103875
  }));
103560
103876
  return task;
103561
103877
  };
103562
103878
  const flush = async (chatId, context = capture()) => {
103563
- const { owner, teamId, current: current2, masterKey } = context;
103879
+ const { owner: owner2, teamId, current: current2, masterKey } = context;
103564
103880
  checked(current2);
103565
- const local = readLocal(owner, chatId);
103566
- if (!local?.pending) return states2.get(stateKey(owner, chatId)) ?? { selection: "auto", pending: false, restored: false };
103881
+ const local = readLocal(owner2, chatId);
103882
+ if (!local?.pending) return states2.get(stateKey(owner2, chatId)) ?? { selection: "auto", pending: false, restored: false };
103567
103883
  const selection = await decrypt(local, masterKey);
103568
103884
  try {
103569
103885
  for (let attempt = 0; attempt < 3; attempt++) {
@@ -103573,15 +103889,15 @@ function createChatModelPreferences(client2) {
103573
103889
  const accepted = await client2.compareAndSetChatModelPreference(chatId, local.ciphertext, expected, teamId);
103574
103890
  checked(current2);
103575
103891
  if (!accepted) continue;
103576
- await writeLocal(owner, chatId, { ...accepted, pending: false }, current2);
103892
+ await writeLocal(owner2, chatId, { ...accepted, pending: false }, current2);
103577
103893
  const state = { selection, pending: false, restored: true };
103578
- return publish(owner, chatId, state, current2);
103894
+ return publish(owner2, chatId, state, current2);
103579
103895
  }
103580
103896
  throw new Error("Chat model preference conflicted repeatedly.");
103581
103897
  } catch {
103582
103898
  checked(current2);
103583
103899
  const state = { selection, pending: true, restored: true };
103584
- return publish(owner, chatId, state, current2);
103900
+ return publish(owner2, chatId, state, current2);
103585
103901
  }
103586
103902
  };
103587
103903
  return {
@@ -103604,32 +103920,32 @@ function createChatModelPreferences(client2) {
103604
103920
  },
103605
103921
  async restore(chatId) {
103606
103922
  const context = capture();
103607
- const { owner, current: current2, masterKey } = context;
103923
+ const { owner: owner2, current: current2, masterKey } = context;
103608
103924
  checked(current2);
103609
- const local = readLocal(owner, chatId);
103925
+ const local = readLocal(owner2, chatId);
103610
103926
  if (local) {
103611
103927
  const selection = await decrypt(local, masterKey);
103612
103928
  checked(current2);
103613
- const initial = publish(owner, chatId, { selection, pending: local.pending, restored: true }, current2);
103614
- void withChat(owner, chatId, async () => {
103929
+ const initial = publish(owner2, chatId, { selection, pending: local.pending, restored: true }, current2);
103930
+ void withChat(owner2, chatId, async () => {
103615
103931
  if (local.pending) return flush(chatId, context);
103616
103932
  return refreshRemote(chatId, context, local);
103617
103933
  }).catch(() => {
103618
103934
  });
103619
103935
  return initial;
103620
103936
  }
103621
- return withChat(owner, chatId, () => refreshRemote(chatId, context, null));
103937
+ return withChat(owner2, chatId, () => refreshRemote(chatId, context, null));
103622
103938
  },
103623
103939
  async select(chatId, selection) {
103624
103940
  const context = capture();
103625
103941
  return withChat(context.owner, chatId, async () => {
103626
- const { owner, current: current2, masterKey } = context;
103942
+ const { owner: owner2, current: current2, masterKey } = context;
103627
103943
  const serialized = encodeSelection(selection);
103628
103944
  const ciphertext = await encryptWithAesGcmCombined(serialized, masterKey);
103629
103945
  checked(current2);
103630
- const previous = readLocal(owner, chatId);
103631
- await writeLocal(owner, chatId, { ciphertext, version: previous?.version ?? 0, pending: true }, current2);
103632
- publish(owner, chatId, { selection, pending: true, restored: true }, current2);
103946
+ const previous = readLocal(owner2, chatId);
103947
+ await writeLocal(owner2, chatId, { ciphertext, version: previous?.version ?? 0, pending: true }, current2);
103948
+ publish(owner2, chatId, { selection, pending: true, restored: true }, current2);
103633
103949
  return flush(chatId, context);
103634
103950
  });
103635
103951
  },
@@ -103662,7 +103978,7 @@ function createChatModelPreferences(client2) {
103662
103978
  }
103663
103979
  };
103664
103980
  async function refreshRemote(chatId, context, local) {
103665
- const { owner, teamId, current: current2, masterKey } = context;
103981
+ const { owner: owner2, teamId, current: current2, masterKey } = context;
103666
103982
  checked(current2);
103667
103983
  let remote = null;
103668
103984
  let confirmed = false;
@@ -103673,7 +103989,7 @@ function createChatModelPreferences(client2) {
103673
103989
  checked(current2);
103674
103990
  if (!local) {
103675
103991
  const state2 = { selection: "auto", pending: false, restored: false };
103676
- return publish(owner, chatId, state2, current2);
103992
+ return publish(owner2, chatId, state2, current2);
103677
103993
  }
103678
103994
  }
103679
103995
  checked(current2);
@@ -103681,21 +103997,21 @@ function createChatModelPreferences(client2) {
103681
103997
  const ciphertext = await encryptWithAesGcmCombined(encodeSelection("auto"), masterKey);
103682
103998
  checked(current2);
103683
103999
  const autoRecord = { ciphertext, version: 0, pending: false };
103684
- const stored = await writeLocal(owner, chatId, autoRecord, current2, local);
104000
+ const stored = await writeLocal(owner2, chatId, autoRecord, current2, local);
103685
104001
  checked(current2);
103686
- if (!stored) return publish(owner, chatId, { selection: "auto", pending: false, restored: false }, current2);
104002
+ if (!stored) return publish(owner2, chatId, { selection: "auto", pending: false, restored: false }, current2);
103687
104003
  if (stored !== autoRecord) {
103688
104004
  const selection2 = await decrypt(stored, masterKey);
103689
- return publish(owner, chatId, { selection: selection2, pending: stored.pending, restored: true }, current2);
104005
+ return publish(owner2, chatId, { selection: selection2, pending: stored.pending, restored: true }, current2);
103690
104006
  }
103691
- return publish(owner, chatId, { selection: "auto", pending: false, restored: true }, current2);
104007
+ return publish(owner2, chatId, { selection: "auto", pending: false, restored: true }, current2);
103692
104008
  }
103693
104009
  const chosen = remote && (!local || remote.version > local.version) ? remote : local;
103694
104010
  const selection = chosen ? await decrypt(chosen, masterKey) : "auto";
103695
104011
  checked(current2);
103696
- if (remote && chosen === remote) await writeLocal(owner, chatId, { ...remote, pending: false }, current2);
104012
+ if (remote && chosen === remote) await writeLocal(owner2, chatId, { ...remote, pending: false }, current2);
103697
104013
  const state = { selection, pending: false, restored: true };
103698
- return publish(owner, chatId, state, current2);
104014
+ return publish(owner2, chatId, state, current2);
103699
104015
  }
103700
104016
  }
103701
104017
 
@@ -103713,6 +104029,7 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
103713
104029
  hydrateExamples(state);
103714
104030
  let resolveResult = null;
103715
104031
  let renderTimer = null;
104032
+ let lastFrameOwner = null;
103716
104033
  let closed = false;
103717
104034
  let stopActivity;
103718
104035
  let activityTimer;
@@ -103745,9 +104062,25 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
103745
104062
  void refreshActivity();
103746
104063
  }, 3e4);
103747
104064
  const activityAnimation = setInterval(() => {
103748
- if (!closed && state.runningChatIds.length) {
103749
- state.activityFrame = (state.activityFrame + 1) % 4;
104065
+ if (closed) return;
104066
+ const sidebarActivity = state.runningChatIds.length > 0;
104067
+ if (sidebarActivity) state.activityFrame = (state.activityFrame + 1) % 4;
104068
+ const streaming = state.screen === "chat" && state.isAwaitingAi && state.streamingMessage && !state.textSelection && !terminal.reducedMotion && !terminal.isOutputBlocked && lastFrameOwner?.();
104069
+ if (streaming) {
104070
+ state.streamingPhase = (state.streamingPhase + 1) % 12;
104071
+ }
104072
+ if (sidebarActivity) {
103750
104073
  render();
104074
+ return;
104075
+ }
104076
+ if (streaming && !renderTimer) {
104077
+ const frame = renderTuiStreamingAnimationFrame(
104078
+ state,
104079
+ terminal.width,
104080
+ terminal.height,
104081
+ { colorMode: terminal.colorMode, ascii: terminal.ascii, reducedMotion: terminal.reducedMotion }
104082
+ );
104083
+ if (frame) terminal.render(frame, tuiComposerCursor(state, terminal.width, terminal.height), state.textSelection);
103751
104084
  }
103752
104085
  }, 250);
103753
104086
  const syncSessionView = () => {
@@ -103825,7 +104158,8 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
103825
104158
  renderTimer = null;
103826
104159
  if (closed) return;
103827
104160
  syncSessionView();
103828
- terminal.render(renderTuiFrame(state, terminal.width, terminal.height, { colorMode: terminal.colorMode, ascii: terminal.ascii }), tuiComposerCursor(state, terminal.width, terminal.height), state.textSelection);
104161
+ lastFrameOwner = client2.hasSession() ? captureTuiWorkspaceOwner(client2) : () => !client2.hasSession();
104162
+ terminal.render(renderTuiFrame(state, terminal.width, terminal.height, { colorMode: terminal.colorMode, ascii: terminal.ascii, reducedMotion: terminal.reducedMotion }), tuiComposerCursor(state, terminal.width, terminal.height), state.textSelection);
103829
104163
  }, 16);
103830
104164
  };
103831
104165
  teamChatRefresh = createTuiTeamChatRefresh(state, client2, render);
@@ -104094,7 +104428,7 @@ async function handleEnter(params) {
104094
104428
  }
104095
104429
  async function handleCommand(params) {
104096
104430
  const { command, state, client: client2, terminal, render, finish, modelShell } = params;
104097
- client2.clearInteractiveChatViewer();
104431
+ if (command.split(/\s+/, 1)[0] !== "/thinking") client2.clearInteractiveChatViewer();
104098
104432
  if (await handleWorkspaceCommand({ state, client: client2, terminal, render, command: (next) => handleCommand({ ...params, command: next }), send: (message, options) => sendTuiMessage({ message, state, client: client2, render, modelShell, questionAnswer: options?.questionAnswer }), modelShell }, command)) return;
104099
104433
  const [name, ...parts] = command.split(/\s+/);
104100
104434
  const arg = parts.join(" ");
@@ -104254,17 +104588,66 @@ async function handleCommand(params) {
104254
104588
  async function sendTuiMessage(params) {
104255
104589
  const { state, client: client2, render } = params;
104256
104590
  if (state.isBusy) return;
104591
+ const initialMessage = params.message.trim();
104592
+ if (!initialMessage) return;
104257
104593
  const preparingOwner = client2.hasSession() ? captureTuiWorkspaceOwner(client2) : () => !client2.hasSession();
104258
104594
  const preparingRoute = state.routeVersion;
104259
- const preparingMessages = state.messages;
104595
+ const previousMessages = state.messages;
104260
104596
  const preparingChatId = state.activeChatId;
104261
- const ownsPreparation = () => preparingOwner() && state.routeVersion === preparingRoute && state.messages === preparingMessages && state.activeChatId === preparingChatId;
104262
- const resolved = !params.questionAnswer && params.modelShell ? await params.modelShell.consumeMention(params.message, true) : { message: params.message, blocked: false };
104263
- if (resolved.blocked || !ownsPreparation() || state.isBusy) return;
104597
+ const previousActiveChat = state.activeChat, previousHeaderState = state.headerState;
104598
+ const previousScreen = state.screen;
104599
+ const sourceExample = previousScreen === "example" ? state.activeExample : null;
104600
+ const history = sourceExample ? buildExampleContinuationHistory(sourceExample) : void 0;
104601
+ const draftKey = sourceExample ? `example:${sourceExample.chat.id}` : preparingChatId ?? "new";
104602
+ const draftBeforeSend = state.drafts[draftKey];
104603
+ if (sourceExample) state.messages = sourceExample.messages.map((m) => ({ role: m.role === "user" ? "user" : "assistant", content: m.content, title: m.senderName }));
104604
+ const messages = state.messages;
104605
+ const userMessage = { id: randomUUID21(), role: "user", content: initialMessage };
104606
+ messages.push(userMessage);
104607
+ state.screen = "chat";
104608
+ state.isBusy = true;
104609
+ state.drafts[draftKey] = "";
104610
+ state.input = "";
104611
+ state.inputCursor = null;
104612
+ state.status = "Preparing message\u2026";
104613
+ render();
104614
+ const ownsPreparation = () => preparingOwner() && state.routeVersion === preparingRoute && state.messages === messages && state.activeChatId === preparingChatId;
104615
+ const restorePreparation = (status) => {
104616
+ if (!ownsPreparation()) return;
104617
+ const index = messages.indexOf(userMessage);
104618
+ if (index >= 0) messages.splice(index, 1);
104619
+ state.messages = previousMessages;
104620
+ state.screen = previousScreen;
104621
+ state.isBusy = false;
104622
+ if (state.input === "" && state.drafts[draftKey] === "") {
104623
+ state.input = initialMessage;
104624
+ state.inputCursor = null;
104625
+ if (draftBeforeSend === void 0) delete state.drafts[draftKey];
104626
+ rememberDraft(state);
104627
+ }
104628
+ if (client2.getActiveTeamId?.()) reconcileTuiTeamSenderLabels(state);
104629
+ state.status = status;
104630
+ render();
104631
+ };
104632
+ let resolved;
104633
+ try {
104634
+ resolved = !params.questionAnswer && params.modelShell ? await params.modelShell.consumeMention(params.message, true) : { message: params.message, blocked: false };
104635
+ } catch (error) {
104636
+ restorePreparation(error instanceof Error ? error.message : String(error));
104637
+ return;
104638
+ }
104639
+ if (!ownsPreparation()) return;
104640
+ if (resolved.blocked) {
104641
+ restorePreparation(null);
104642
+ return;
104643
+ }
104264
104644
  const message = resolved.message.trim();
104265
- if (!message) return;
104645
+ if (!message) {
104646
+ restorePreparation(null);
104647
+ return;
104648
+ }
104649
+ userMessage.content = message;
104266
104650
  client2.clearInteractiveChatViewer();
104267
- state.isBusy = true;
104268
104651
  state.status = "Preparing message\u2026";
104269
104652
  render();
104270
104653
  if (!ownsPreparation()) return;
@@ -104272,15 +104655,7 @@ async function sendTuiMessage(params) {
104272
104655
  try {
104273
104656
  prepared = params.questionAnswer ? { message, preparedEmbeds: [], displayNames: [] } : await prepareTuiMessage(client2, message);
104274
104657
  } catch (error) {
104275
- if (ownsPreparation()) {
104276
- state.isBusy = false;
104277
- if (client2.getActiveTeamId?.()) reconcileTuiTeamSenderLabels(state);
104278
- state.input = message;
104279
- state.inputCursor = null;
104280
- rememberDraft(state);
104281
- state.status = error instanceof Error ? error.message : String(error);
104282
- render();
104283
- }
104658
+ restorePreparation(error instanceof Error ? error.message : String(error));
104284
104659
  return;
104285
104660
  }
104286
104661
  if (!ownsPreparation()) return;
@@ -104288,34 +104663,16 @@ async function sendTuiMessage(params) {
104288
104663
  const waitForAi = !client2.hasSession() || shouldWaitForTeamAi(prepared.message, sendTeamId);
104289
104664
  const modelSelection = waitForAi && !params.questionAnswer && isTuiAiComposer(state) && client2.hasSession() ? params.modelShell?.selectionForSend() ?? null : "auto";
104290
104665
  if (modelSelection === null) {
104291
- if (ownsPreparation()) {
104292
- state.isBusy = false;
104293
- state.input = message;
104294
- state.inputCursor = null;
104295
- rememberDraft(state);
104296
- if (sendTeamId) reconcileTuiTeamSenderLabels(state);
104297
- state.status = "Model selection is loading. Retry or choose Auto before sending.";
104298
- render();
104299
- }
104666
+ restorePreparation("Model selection is loading. Retry or choose Auto before sending.");
104300
104667
  return;
104301
104668
  }
104302
104669
  const separator = modelSelection.indexOf("/");
104303
104670
  const modelDirective = separator > 0 ? `@ai-model:${modelSelection.slice(separator + 1)}:${modelSelection.slice(0, separator)} ` : "";
104304
- const previousMessages = state.messages, previousActiveChat = state.activeChat, previousHeaderState = state.headerState;
104305
- const previousScreen = state.screen;
104306
- const sourceExample = state.screen === "example" ? state.activeExample : null;
104307
- const history = sourceExample ? buildExampleContinuationHistory(sourceExample) : void 0;
104308
- if (sourceExample) state.messages = sourceExample.messages.map((m) => ({ role: m.role === "user" ? "user" : "assistant", content: m.content, title: m.senderName }));
104309
- const messages = state.messages;
104310
104671
  const existingChatId = sourceExample ? null : state.activeChatId;
104311
104672
  const chatId = existingChatId ?? randomUUID21();
104312
104673
  let ownedMessages = messages, ownedChatId = chatId;
104313
104674
  const ownsSend = () => preparingOwner() && state.routeVersion === preparingRoute && state.messages === ownedMessages && state.activeChatId === ownedChatId;
104314
- const draftKey = sourceExample ? `example:${sourceExample.chat.id}` : existingChatId ?? "new";
104315
- state.drafts[draftKey] = "";
104316
- state.input = "";
104317
- state.inputCursor = null;
104318
- const anonymousHistory = history ?? messages.filter((m) => m.role !== "system").map((m) => ({ message_id: randomUUID21(), role: m.role, content: m.content, sender_name: m.title ?? (m.role === "user" ? "User" : "Assistant"), created_at: Math.floor(Date.now() / 1e3) }));
104675
+ const anonymousHistory = history ?? messages.filter((m) => m !== userMessage && m.role !== "system").map((m) => ({ message_id: randomUUID21(), role: m.role, content: m.content, sender_name: m.title ?? (m.role === "user" ? "User" : "Assistant"), created_at: Math.floor(Date.now() / 1e3) }));
104319
104676
  state.activeChatId = chatId;
104320
104677
  state.headerState = existingChatId ? "ready" : "loading";
104321
104678
  state.headerError = null;
@@ -104324,8 +104681,8 @@ async function sendTuiMessage(params) {
104324
104681
  state.screen = "chat";
104325
104682
  state.aiTaskId = null;
104326
104683
  state.status = prepared.displayNames.length ? `Attached: ${prepared.displayNames.join(", ")}` : null;
104327
- const userMessage = { role: "user", content: prepared.message, embedIds: prepared.preparedEmbeds.map((embed) => embed.embedId) };
104328
- state.messages.push(userMessage);
104684
+ userMessage.content = prepared.message;
104685
+ userMessage.embedIds = prepared.preparedEmbeds.map((embed) => embed.embedId);
104329
104686
  const privacyCallbacks = {
104330
104687
  onPrivacyPrepared: (safe2) => {
104331
104688
  if (ownsSend()) {
@@ -104340,11 +104697,18 @@ async function sendTuiMessage(params) {
104340
104697
  }
104341
104698
  }
104342
104699
  };
104343
- const assistantMessage = waitForAi ? { role: "assistant", content: "", title: "Assistant" } : null;
104344
- if (assistantMessage) state.messages.push(assistantMessage);
104700
+ const assistantMessage = waitForAi ? { id: randomUUID21(), role: "assistant", content: "", title: "Assistant" } : null;
104701
+ state.streamingMessage = assistantMessage;
104702
+ if (assistantMessage) state.streamingPhase = 0;
104345
104703
  state.isAwaitingAi = waitForAi;
104704
+ const showAssistant = (content) => {
104705
+ if (!assistantMessage || !ownsSend() || !content) return;
104706
+ assistantMessage.content = content;
104707
+ if (!messages.includes(assistantMessage)) messages.push(assistantMessage);
104708
+ };
104346
104709
  render();
104347
104710
  let questionSendError = null;
104711
+ let accepted = false;
104348
104712
  try {
104349
104713
  if (!client2.hasSession()) {
104350
104714
  const result = await client2.sendAnonymousMessage({
@@ -104352,7 +104716,8 @@ async function sendTuiMessage(params) {
104352
104716
  ...privacyCallbacks,
104353
104717
  messageHistory: anonymousHistory
104354
104718
  });
104355
- if (assistantMessage) assistantMessage.content = result.assistant;
104719
+ accepted = true;
104720
+ showAssistant(result.assistant);
104356
104721
  if (ownsSend()) {
104357
104722
  ownedChatId = result.chatId;
104358
104723
  state.activeChatId = result.chatId;
@@ -104385,22 +104750,42 @@ async function sendTuiMessage(params) {
104385
104750
  onStream: (event) => {
104386
104751
  if (!ownsSend()) return;
104387
104752
  if (event.taskId) state.aiTaskId = event.taskId;
104753
+ if (assistantMessage && event.thinkingContent !== void 0) assistantMessage.thinkingContent = event.thinkingContent;
104754
+ if (assistantMessage && event.thinkingActive !== void 0) assistantMessage.thinkingActive = event.thinkingActive;
104755
+ if (assistantMessage && event.modelName) assistantMessage.modelName = event.modelName;
104756
+ if (assistantMessage && event.category) {
104757
+ state.activeChat = {
104758
+ id: chatId,
104759
+ shortId: chatId.slice(0, 8),
104760
+ title: state.activeChat?.title ?? null,
104761
+ summary: state.activeChat?.summary ?? null,
104762
+ updatedAt: null,
104763
+ createdAt: state.activeChat?.createdAt ?? Math.floor(Date.now() / 1e3),
104764
+ category: event.category,
104765
+ mateName: null
104766
+ };
104767
+ }
104388
104768
  if (assistantMessage && (event.kind === "chunk" || event.kind === "done")) {
104389
- assistantMessage.content = event.content;
104390
- if (event.category) state.activeChat = { id: chatId, shortId: chatId.slice(0, 8), title: state.activeChat?.title ?? null, summary: state.activeChat?.summary ?? null, updatedAt: null, createdAt: state.activeChat?.createdAt ?? Math.floor(Date.now() / 1e3), category: event.category, mateName: null };
104391
- render();
104769
+ showAssistant(event.content);
104392
104770
  }
104771
+ render();
104393
104772
  }
104394
104773
  });
104395
- if (assistantMessage) assistantMessage.content = result.assistant;
104774
+ accepted = true;
104775
+ showAssistant(result.assistant);
104396
104776
  if (ownsSend()) {
104397
104777
  ownedChatId = result.chatId;
104398
104778
  state.activeChatId = result.chatId;
104399
104779
  state.followUpSuggestions = result.followUpSuggestions ?? [];
104400
104780
  if (result.userMessageId) userMessage.id = result.userMessageId;
104401
- if (!existingChatId) void params.modelShell?.persistCreatedChat(result.chatId, modelSelection);
104402
- if (state.screen === "chat") void client2.setInteractiveChatViewer(result.chatId).catch(() => {
104403
- });
104781
+ if (!existingChatId && params.modelShell) {
104782
+ void Promise.resolve().then(() => ownsSend() ? params.modelShell?.persistCreatedChat(result.chatId, modelSelection) : void 0).catch(() => {
104783
+ });
104784
+ }
104785
+ if (state.screen === "chat" && typeof client2.setInteractiveChatViewer === "function") {
104786
+ void Promise.resolve().then(() => ownsSend() && state.screen === "chat" ? client2.setInteractiveChatViewer(result.chatId) : void 0).catch(() => {
104787
+ });
104788
+ }
104404
104789
  if (result.mateName && assistantMessage) assistantMessage.title = result.mateName;
104405
104790
  if (typeof client2.getChatMetadata === "function") {
104406
104791
  const completedLength = messages.length;
@@ -104425,13 +104810,14 @@ async function sendTuiMessage(params) {
104425
104810
  state.headerState = "ready";
104426
104811
  }
104427
104812
  } catch (error) {
104813
+ if (accepted) return;
104428
104814
  if (params.questionAnswer) {
104429
104815
  questionSendError = error instanceof Error ? error : new Error(String(error));
104430
- for (const optimistic of [userMessage, assistantMessage].filter((item) => item !== null)) {
104431
- const index = messages.indexOf(optimistic);
104432
- if (index >= 0) messages.splice(index, 1);
104433
- }
104434
104816
  if (ownsSend()) {
104817
+ for (const optimistic of [userMessage, assistantMessage].filter((item) => item !== null)) {
104818
+ const index = messages.indexOf(optimistic);
104819
+ if (index >= 0) messages.splice(index, 1);
104820
+ }
104435
104821
  state.messages = previousMessages;
104436
104822
  state.activeChatId = existingChatId;
104437
104823
  state.activeChat = previousActiveChat;
@@ -104441,14 +104827,14 @@ async function sendTuiMessage(params) {
104441
104827
  state.screen = sourceExample ? "example" : "chat";
104442
104828
  state.status = null;
104443
104829
  }
104444
- } else if (assistantMessage) {
104830
+ } else if (assistantMessage && ownsSend()) {
104445
104831
  assistantMessage.title = "Error";
104446
- assistantMessage.content = error instanceof Error ? error.message : String(error);
104447
- if (ownsSend()) {
104448
- state.headerState = "error";
104449
- state.headerError = /credit/i.test(assistantMessage.content) ? "Not enough credits" : assistantMessage.content;
104450
- }
104832
+ showAssistant(error instanceof Error ? error.message : String(error));
104833
+ state.headerState = "error";
104834
+ state.headerError = /credit/i.test(assistantMessage.content) ? "Not enough credits" : assistantMessage.content;
104451
104835
  } else if (ownsSend()) {
104836
+ const index = messages.indexOf(userMessage);
104837
+ if (index >= 0) messages.splice(index, 1);
104452
104838
  state.messages = previousMessages;
104453
104839
  state.activeChatId = existingChatId;
104454
104840
  state.activeChat = previousActiveChat;
@@ -104456,9 +104842,11 @@ async function sendTuiMessage(params) {
104456
104842
  ownedChatId = existingChatId;
104457
104843
  state.headerState = previousHeaderState;
104458
104844
  state.screen = previousScreen;
104459
- state.input = message;
104460
- state.inputCursor = null;
104461
- rememberDraft(state);
104845
+ if (state.input === "" && state.drafts[draftKey] === "") {
104846
+ state.input = message;
104847
+ state.inputCursor = null;
104848
+ rememberDraft(state);
104849
+ }
104462
104850
  state.status = error instanceof Error ? error.message : String(error);
104463
104851
  }
104464
104852
  } finally {
@@ -104466,6 +104854,7 @@ async function sendTuiMessage(params) {
104466
104854
  state.isBusy = false;
104467
104855
  if (sendTeamId) reconcileTuiTeamSenderLabels(state);
104468
104856
  state.isAwaitingAi = false;
104857
+ state.streamingMessage = null;
104469
104858
  state.projectFocusPending = null;
104470
104859
  state.aiTaskId = null;
104471
104860
  registerChatEmbedAliases(state);
@@ -105011,7 +105400,7 @@ async function flushTaskCommands(client2, teamId, root = resolveStateDir(), onNo
105011
105400
  await taskMutationStore(scope2, root).deliver(command.id, async () => {
105012
105401
  if (operation.kind === "create") {
105013
105402
  const linked = operation.link_to_chat !== false;
105014
- const owner = linked ? await readCodexThread(command.thread) : void 0;
105403
+ const owner2 = linked ? await readCodexThread(command.thread) : void 0;
105015
105404
  const input = await buildCreateUserTaskInput(master, {
105016
105405
  taskId: command.task_id,
105017
105406
  title: operation.title ?? "",
@@ -105019,7 +105408,7 @@ async function flushTaskCommands(client2, teamId, root = resolveStateDir(), onNo
105019
105408
  projectIds: [command.project_id],
105020
105409
  status: operation.status,
105021
105410
  assign: linked ? "codex" : "user",
105022
- externalChat: owner ? { provider: "codex", id: owner.id, title: owner.title } : void 0
105411
+ externalChat: owner2 ? { provider: "codex", id: owner2.id, title: owner2.title } : void 0
105023
105412
  });
105024
105413
  return { kind: "create", taskId: command.task_id, input, creator: linked ? "codex" : void 0 };
105025
105414
  }
@@ -111238,8 +111627,8 @@ async function handleTasks(client2, subcommand, rest, flags, redactor) {
111238
111627
  }
111239
111628
  if (process.env.CODEX_THREAD_ID && (task.externalChat?.provider !== "codex" || task.externalChat.id !== process.env.CODEX_THREAD_ID)) {
111240
111629
  if (task.primaryChatId) {
111241
- const owner = await client2.getChatMetadata(task.primaryChatId, scope2);
111242
- throw new Error(taskOwnerConflict({ provider: "openmates", id: owner.id, title: owner.title }));
111630
+ const owner2 = await client2.getChatMetadata(task.primaryChatId, scope2);
111631
+ throw new Error(taskOwnerConflict({ provider: "openmates", id: owner2.id, title: owner2.title }));
111243
111632
  }
111244
111633
  throw new Error(taskOwnerConflict(task.externalChat));
111245
111634
  }
@@ -111255,8 +111644,8 @@ async function handleTasks(client2, subcommand, rest, flags, redactor) {
111255
111644
  if (subcommand === "connect") {
111256
111645
  if (typeof flags.thread !== "string") throw new Error("Usage: openmates tasks connect <task-id> --thread <codex-thread-uuid>");
111257
111646
  if (task.primaryChatId) {
111258
- const owner = await client2.getChatMetadata(task.primaryChatId, scope2);
111259
- throw new Error(taskOwnerConflict({ provider: "openmates", id: owner.id, title: owner.title }));
111647
+ const owner2 = await client2.getChatMetadata(task.primaryChatId, scope2);
111648
+ throw new Error(taskOwnerConflict({ provider: "openmates", id: owner2.id, title: owner2.title }));
111260
111649
  }
111261
111650
  if (task.externalChat) {
111262
111651
  if (task.externalChat.provider === "codex" && task.externalChat.id === flags.thread) {
@@ -120628,6 +121017,7 @@ async function sendMessageStreaming(client2, params, redactor) {
120628
121017
  const onStream = (event) => {
120629
121018
  if (params.json) return;
120630
121019
  if (event.kind === "typing") {
121020
+ if (event.thinkingContent) return;
120631
121021
  const mateName = event.category && MATE_NAMES[event.category] ? MATE_NAMES[event.category] : "Mate";
120632
121022
  process.stdout.write(`\x1B[2m${mateName} is typing...\x1B[0m`);
120633
121023
  typingShown = true;