openmates 0.27.0-alpha.52 → 0.27.0-alpha.54

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();
@@ -15955,6 +15988,18 @@ var OpenMatesClient = class _OpenMatesClient {
15955
15988
  remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
15956
15989
  });
15957
15990
  }
15991
+ const queuedAdmission = !params.incognito && shouldWaitForAi ? ws.waitForMessage("message_queued", (payload) => {
15992
+ const event = payload;
15993
+ return event.chat_id === chatId && event.user_message_id === messageId;
15994
+ }, params.responseTimeoutMs ?? 9e4) : null;
15995
+ if (queuedAdmission) void queuedAdmission.catch(() => {
15996
+ });
15997
+ const pausedHandoff = !params.incognito && shouldWaitForAi ? ws.waitForMessage("queued_handoff_paused", (payload) => {
15998
+ const event = payload;
15999
+ return event.chat_id === chatId && (event.message_id === messageId || event.message_id === void 0);
16000
+ }, params.responseTimeoutMs ?? 9e4) : null;
16001
+ if (pausedHandoff) void pausedHandoff.catch(() => {
16002
+ });
15958
16003
  const confirmed = ws.waitForMessage(
15959
16004
  "chat_message_confirmed",
15960
16005
  (payload) => {
@@ -16248,11 +16293,56 @@ var OpenMatesClient = class _OpenMatesClient {
16248
16293
  }
16249
16294
  await this.replayCurrentTurnRecoveryOutputPages(ws, ownerId2, pages, cache, teamId, chatId);
16250
16295
  };
16251
- const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId, {
16296
+ const responsePromise = precollectedResponse ?? ws.collectAiResponse(messageId, chatId, {
16252
16297
  ...streamOpts,
16253
16298
  timeoutMs: params.responseTimeoutMs,
16254
16299
  recoveryTurnId: savedTurnId
16255
- }));
16300
+ });
16301
+ const outcome = await Promise.race([
16302
+ responsePromise.then((response) => ({ kind: "response", response })),
16303
+ ...queuedAdmission ? [queuedAdmission.then(
16304
+ (frame) => ({ kind: "queued", frame }),
16305
+ () => new Promise(() => {
16306
+ })
16307
+ )] : [],
16308
+ ...pausedHandoff ? [pausedHandoff.then(
16309
+ (frame) => ({ kind: "paused", frame }),
16310
+ () => new Promise(() => {
16311
+ })
16312
+ )] : []
16313
+ ]);
16314
+ if (outcome.kind === "paused") {
16315
+ void responsePromise.catch(() => {
16316
+ });
16317
+ throw new Error("Message saved, but AI handoff is paused. No assistant run started; check the chat before retrying.");
16318
+ }
16319
+ if (outcome.kind === "queued") {
16320
+ void responsePromise.catch(() => {
16321
+ });
16322
+ const frame = outcome.frame.payload;
16323
+ return {
16324
+ status: "queued",
16325
+ chatId,
16326
+ messageId: null,
16327
+ userMessageId: messageId,
16328
+ queueMode: frame.queue_mode === "steer" ? "steer" : "follow_up",
16329
+ queueRevision: typeof frame.queue_revision === "number" ? frame.queue_revision : 1,
16330
+ assistant: "",
16331
+ category: null,
16332
+ modelName: null,
16333
+ mateName: null,
16334
+ followUpSuggestions: [],
16335
+ taskProposals: [],
16336
+ taskUpdateProposals: [],
16337
+ taskEvents: [],
16338
+ pendingTaskUpdateJobs: [],
16339
+ subChatEvents: [],
16340
+ appSettingsMemoryRequests: [],
16341
+ tokenUsage: null,
16342
+ promptBudget: null
16343
+ };
16344
+ }
16345
+ const resp = outcome.response;
16256
16346
  assistantMessageId = resp.messageId;
16257
16347
  assistant = resp.content;
16258
16348
  category = resp.category;
@@ -18738,7 +18828,7 @@ var OpenMatesClient = class _OpenMatesClient {
18738
18828
  // -------------------------------------------------------------------------
18739
18829
  async listUserTasks(filters = {}, options = {}) {
18740
18830
  const session = this.requireSession();
18741
- const owner = [
18831
+ const owner2 = [
18742
18832
  session.apiUrl,
18743
18833
  session.hashedEmail,
18744
18834
  session.sessionId,
@@ -18748,7 +18838,7 @@ var OpenMatesClient = class _OpenMatesClient {
18748
18838
  ];
18749
18839
  const current2 = () => {
18750
18840
  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])
18841
+ 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
18842
  throw new Error("Task workspace changed");
18753
18843
  };
18754
18844
  const params = new URLSearchParams();
@@ -18984,8 +19074,8 @@ var OpenMatesClient = class _OpenMatesClient {
18984
19074
  if (task.externalChat && (current2.external_chat_provider !== input.external_chat_provider || current2.external_chat_lookup_hash !== input.external_chat_lookup_hash)) {
18985
19075
  conflict = taskOwnerConflict(task.externalChat);
18986
19076
  } 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 });
19077
+ const owner2 = await this.getChatMetadata(task.primaryChatId, context);
19078
+ conflict = taskOwnerConflict({ provider: "openmates", id: owner2.id, title: owner2.title });
18989
19079
  }
18990
19080
  }
18991
19081
  } catch {
@@ -21226,9 +21316,9 @@ Required: ${schema.required.join(", ")}`
21226
21316
  ...baseSyncPayload,
21227
21317
  ...refreshChatIdSet.size > 0 ? { refresh_chat_ids: Array.from(refreshChatIdSet) } : {}
21228
21318
  });
21229
- const frames2 = await syncFrames;
21319
+ const frames3 = await syncFrames;
21230
21320
  signal?.throwIfAborted();
21231
- if (onSyncedCache && !frames2.some((frame) => frame.type === "phase_2_last_20_chats_ready")) {
21321
+ if (onSyncedCache && !frames3.some((frame) => frame.type === "phase_2_last_20_chats_ready")) {
21232
21322
  throw new Error("Chat metadata sync ended before history arrived.");
21233
21323
  }
21234
21324
  if (includeMessageContentRefresh && refreshChatIdSet.size > 0) {
@@ -21239,9 +21329,9 @@ Required: ${schema.required.join(", ")}`
21239
21329
  });
21240
21330
  const collectedPhase3Frames = await phase3Frames;
21241
21331
  signal?.throwIfAborted();
21242
- frames2.push(...collectedPhase3Frames);
21332
+ frames3.push(...collectedPhase3Frames);
21243
21333
  }
21244
- for (const frame of frames2) {
21334
+ for (const frame of frames3) {
21245
21335
  if (frame.type === "phase_1_last_chat_ready") {
21246
21336
  const p = frame.payload;
21247
21337
  newChatSuggestions = p.new_chat_suggestions ?? [];
@@ -34662,7 +34752,7 @@ function generateQr(value) {
34662
34752
  function remotionMeta(c) {
34663
34753
  const source = str(c.remotion_source) ?? str(c.source) ?? "";
34664
34754
  const fps = firstInt(source, /fps\s*[:=]\s*(\d+)/) ?? 30;
34665
- const frames2 = firstInt(source, /durationInFrames\s*[:=]\s*(\d+)/) ?? 150;
34755
+ const frames3 = firstInt(source, /durationInFrames\s*[:=]\s*(\d+)/) ?? 150;
34666
34756
  const status = str(c.status) ?? "processing";
34667
34757
  return {
34668
34758
  filename: str(c.filename) ?? str(c.title) ?? "Composition.tsx",
@@ -34670,7 +34760,7 @@ function remotionMeta(c) {
34670
34760
  statusText: remotionStatusText(status),
34671
34761
  source,
34672
34762
  sourceVersion: numberValue(c.current_source_version ?? c.source_version) ?? 1,
34673
- durationSeconds: Math.max(1, Math.ceil(frames2 / Math.max(1, fps))),
34763
+ durationSeconds: Math.max(1, Math.ceil(frames3 / Math.max(1, fps))),
34674
34764
  width: firstInt(source, /width\s*[:=]\s*(\d+)/) ?? 1920,
34675
34765
  height: firstInt(source, /height\s*[:=]\s*(\d+)/) ?? 1080,
34676
34766
  layers: remotionLayers(source),
@@ -37197,11 +37287,11 @@ async function writeRuntimeIncidentState(installDir, role, state, effectiveUid =
37197
37287
  const statePath = path.join(stateDir2, `${role}.json`);
37198
37288
  const temporaryPath = `${statePath}.${randomUUID12()}.tmp`;
37199
37289
  await fs.mkdir(stateDir2, { recursive: true, mode: 448 });
37200
- const owner = await runtimeIncidentStateOwner(installDir, effectiveUid);
37290
+ const owner2 = await runtimeIncidentStateOwner(installDir, effectiveUid);
37201
37291
  try {
37202
37292
  await fs.writeFile(temporaryPath, `${JSON.stringify(state, null, 2)}
37203
37293
  `, { flag: "wx", mode: 384 });
37204
- if (owner && (owner.uid !== 0 || owner.gid !== 0)) await fs.chown(temporaryPath, owner.uid, owner.gid);
37294
+ if (owner2 && (owner2.uid !== 0 || owner2.gid !== 0)) await fs.chown(temporaryPath, owner2.uid, owner2.gid);
37205
37295
  await fs.rename(temporaryPath, statePath);
37206
37296
  await fs.chmod(statePath, 384);
37207
37297
  } catch (error) {
@@ -65591,6 +65681,9 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
65591
65681
  generated_by: {
65592
65682
  text: "generated by {model}"
65593
65683
  },
65684
+ using_model: {
65685
+ text: "Using {model}"
65686
+ },
65594
65687
  assistant_speech: {
65595
65688
  confirmation: {
65596
65689
  text: "Confirmation"
@@ -65798,6 +65891,26 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
65798
65891
  text: "Stop queue"
65799
65892
  }
65800
65893
  },
65894
+ response_activity: {
65895
+ using: {
65896
+ text: "Using {apps}"
65897
+ },
65898
+ thinking_using: {
65899
+ text: "Thinking & using {apps}"
65900
+ },
65901
+ response_thinking: {
65902
+ text: "Thinking for this response"
65903
+ },
65904
+ researching: {
65905
+ text: "Researching\u2026"
65906
+ },
65907
+ working: {
65908
+ text: "Working\u2026"
65909
+ },
65910
+ writing: {
65911
+ text: "Writing\u2026"
65912
+ }
65913
+ },
65801
65914
  new_chat_suggestions: {
65802
65915
  quantum_computing: {
65803
65916
  text: "Teach me about quantum computing in simple terms"
@@ -66525,6 +66638,38 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
66525
66638
  text: "Could not start the explanation chat. Please try again."
66526
66639
  }
66527
66640
  },
66641
+ queued_followup: {
66642
+ queued: {
66643
+ text: "Queued"
66644
+ },
66645
+ steering: {
66646
+ text: "Steering current response"
66647
+ },
66648
+ updating: {
66649
+ text: "Updating\u2026"
66650
+ },
66651
+ steer_instead: {
66652
+ text: "Steer instead"
66653
+ },
66654
+ editing: {
66655
+ text: "Editing queued message"
66656
+ },
66657
+ cancel_edit: {
66658
+ text: "Cancel edit"
66659
+ },
66660
+ conflict: {
66661
+ text: "This queued message changed on another device. Reopen it to edit the latest version."
66662
+ },
66663
+ unavailable: {
66664
+ text: "Couldn\u2019t update the queued message. Try again."
66665
+ },
66666
+ steer_deferred: {
66667
+ text: "Couldn\u2019t steer this response. Your message remains queued."
66668
+ },
66669
+ paused: {
66670
+ text: "Saved, but the assistant could not continue with this message."
66671
+ }
66672
+ },
66528
66673
  hidden_chats: {
66529
66674
  unlock: {
66530
66675
  text: "Unlock hidden chats"
@@ -91485,6 +91630,10 @@ var TuiTerminal = class {
91485
91630
  paintedWidth = 0;
91486
91631
  paintedHeight = 0;
91487
91632
  outputBlocked = false;
91633
+ /** Animation ticks must not add work while stdout is awaiting drain. */
91634
+ get isOutputBlocked() {
91635
+ return this.outputBlocked;
91636
+ }
91488
91637
  pendingFrame = null;
91489
91638
  outputDrained = () => {
91490
91639
  this.output.off("drain", this.outputDrained);
@@ -91526,6 +91675,10 @@ var TuiTerminal = class {
91526
91675
  const depth = this.output.getColorDepth?.() ?? 8;
91527
91676
  return depth >= 24 ? "truecolor" : depth >= 8 ? "ansi256" : depth >= 4 ? "ansi16" : "none";
91528
91677
  }
91678
+ /** Explicit terminal preference; GUI OS motion preferences are not available over SSH. */
91679
+ get reducedMotion() {
91680
+ return process.env.OPENMATES_REDUCED_MOTION === "1";
91681
+ }
91529
91682
  get ascii() {
91530
91683
  return process.env.OPENMATES_ASCII === "1" || process.env.TERM === "dumb";
91531
91684
  }
@@ -92636,10 +92789,62 @@ function filterTasks(tasks, query, status) {
92636
92789
  const words = query.toLocaleLowerCase().trim().split(/\s+/).filter(Boolean);
92637
92790
  return sorted(tasks.filter((task) => {
92638
92791
  if (status && task.status !== status) return false;
92792
+ if (!words.length) return true;
92639
92793
  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
92794
  return words.every((word) => haystack.includes(word));
92641
92795
  }));
92642
92796
  }
92797
+ var titleHeightCache = /* @__PURE__ */ new WeakMap();
92798
+ function taskTitleLines(task, width, selected) {
92799
+ return wrapCells(`${selected ? "\u203A " : " "}${task.title || "Untitled task"}`, Math.max(8, width - 2)).slice(0, 2);
92800
+ }
92801
+ function taskTitleHeight(task, width, selected) {
92802
+ let cache = titleHeightCache.get(task);
92803
+ if (!cache || cache.title !== task.title) {
92804
+ cache = { title: task.title, widths: /* @__PURE__ */ new Map() };
92805
+ titleHeightCache.set(task, cache);
92806
+ }
92807
+ let counts = cache.widths.get(width);
92808
+ if (!counts) {
92809
+ if (cache.widths.size >= 3) {
92810
+ const oldest = cache.widths.keys().next().value;
92811
+ if (oldest !== void 0) cache.widths.delete(oldest);
92812
+ }
92813
+ counts = {};
92814
+ cache.widths.set(width, counts);
92815
+ }
92816
+ const key = selected ? "selected" : "unselected";
92817
+ return counts[key] ?? (counts[key] = taskTitleLines(task, width, selected).length);
92818
+ }
92819
+ function taskCardHeight(task, width, selected) {
92820
+ const titleRows = taskTitleHeight(task, width, selected);
92821
+ 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"));
92822
+ }
92823
+ function cardPositions(group, width, selectedTaskId, separatorRows) {
92824
+ let height = 0;
92825
+ const cards = group.map((task, index) => {
92826
+ const start = height;
92827
+ const end = start + taskCardHeight(task, width, task.taskId === selectedTaskId);
92828
+ height = end + separatorRows;
92829
+ return { task, index, start, end };
92830
+ });
92831
+ return { cards, height };
92832
+ }
92833
+ function boardWindows(viewport, fullHeight, stackStart, selected) {
92834
+ const requestedStart = Math.floor(viewport.start);
92835
+ const span = Math.min(fullHeight, Math.max(0, Math.ceil(viewport.end) - requestedStart));
92836
+ const start = Math.min(Math.max(0, requestedStart), Math.max(0, fullHeight - span));
92837
+ const end = start + span;
92838
+ const windows = [{ start, end }];
92839
+ if (selected && viewport.followSelection !== false) {
92840
+ const padding = Math.max(1, end - start);
92841
+ windows.push({ start: Math.max(0, stackStart + selected.start - padding), end: stackStart + selected.end + padding });
92842
+ }
92843
+ return windows;
92844
+ }
92845
+ function intersects(start, end, windows) {
92846
+ return windows.some((window) => start < window.end && end > window.start);
92847
+ }
92643
92848
  function renderTaskBoard2(tasks, options) {
92644
92849
  const width = Math.max(18, options.width);
92645
92850
  const visible = filterTasks(tasks, options.query ?? "");
@@ -92667,14 +92872,40 @@ function renderTaskBoard2(tasks, options) {
92667
92872
  { kind: "select", target: "task", column: TASK_STATUSES.indexOf(status), index: 0 }
92668
92873
  )), colWidth, gap));
92669
92874
  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));
92875
+ if (!options.viewport) {
92876
+ const stacks = shown.map(({ tasks: group }) => group.length ? group.flatMap((task, index) => [...taskCard(
92877
+ task,
92878
+ colWidth,
92879
+ task.taskId === options.selectedTaskId,
92880
+ { kind: "select", target: "task", column: TASK_STATUSES.indexOf(task.status), index, id: task.taskId, activate: true }
92881
+ ), ""]) : [padCells("No tasks here.", colWidth)]);
92882
+ for (let row3 = 0; row3 < Math.max(...stacks.map((stack) => stack.length)); row3++) {
92883
+ lines.push(joinColumns(stacks.map((stack) => stack[row3] ?? ""), colWidth, gap));
92884
+ }
92885
+ } else {
92886
+ const stackStart = lines.length;
92887
+ const positions = shown.map(({ tasks: group }) => cardPositions(group, colWidth, options.selectedTaskId, 1));
92888
+ const selectedCard = positions.flatMap(({ cards }) => cards).find(({ task }) => task.taskId === options.selectedTaskId);
92889
+ const totalRows = Math.max(...positions.map(({ cards, height }) => cards.length ? height : 1));
92890
+ const windows = boardWindows(options.viewport, stackStart + totalRows, stackStart, selectedCard);
92891
+ const stacks = positions.map(({ cards }) => {
92892
+ const rows2 = /* @__PURE__ */ new Map();
92893
+ if (!cards.length && intersects(stackStart, stackStart + 1, windows)) rows2.set(0, padCells("No tasks here.", colWidth));
92894
+ for (const card2 of cards) {
92895
+ if (!intersects(stackStart + card2.start, stackStart + card2.end, windows)) continue;
92896
+ const rendered = taskCard(
92897
+ card2.task,
92898
+ colWidth,
92899
+ card2.task.taskId === options.selectedTaskId,
92900
+ { kind: "select", target: "task", column: TASK_STATUSES.indexOf(card2.task.status), index: card2.index, id: card2.task.taskId, activate: true }
92901
+ );
92902
+ rendered.forEach((line3, offset) => rows2.set(card2.start + offset, line3));
92903
+ }
92904
+ return rows2;
92905
+ });
92906
+ for (let row3 = 0; row3 < totalRows; row3++) {
92907
+ lines.push(intersects(stackStart + row3, stackStart + row3 + 1, windows) ? joinColumns(stacks.map((stack) => stack.get(row3) ?? ""), colWidth, gap) : "");
92908
+ }
92678
92909
  }
92679
92910
  } else {
92680
92911
  const group = columns[focusedIndex]?.tasks ?? [];
@@ -92684,13 +92915,33 @@ function renderTaskBoard2(tasks, options) {
92684
92915
  { kind: "select", target: "task", column: focusedIndex, index: 0 }
92685
92916
  ), "\u2500".repeat(Math.min(width, 52)));
92686
92917
  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
- ));
92918
+ if (!options.viewport) {
92919
+ for (const [index, task] of group.entries()) {
92920
+ lines.push(...taskCard(
92921
+ task,
92922
+ Math.min(width, 52),
92923
+ task.taskId === options.selectedTaskId,
92924
+ { kind: "select", target: "task", column: focusedIndex, index, id: task.taskId, activate: true }
92925
+ ));
92926
+ }
92927
+ } else {
92928
+ const stackStart = lines.length;
92929
+ const { cards, height } = cardPositions(group, Math.min(width, 52), options.selectedTaskId, 0);
92930
+ const windows = boardWindows(options.viewport, stackStart + height, stackStart, cards.find(({ task }) => task.taskId === options.selectedTaskId));
92931
+ const rows2 = Array(height).fill("");
92932
+ for (const card2 of cards) {
92933
+ if (!intersects(stackStart + card2.start, stackStart + card2.end, windows)) continue;
92934
+ const rendered = taskCard(
92935
+ card2.task,
92936
+ Math.min(width, 52),
92937
+ card2.task.taskId === options.selectedTaskId,
92938
+ { kind: "select", target: "task", column: focusedIndex, index: card2.index, id: card2.task.taskId, activate: true }
92939
+ );
92940
+ rendered.forEach((line3, offset) => {
92941
+ rows2[card2.start + offset] = line3;
92942
+ });
92943
+ }
92944
+ lines.push(...rows2);
92694
92945
  }
92695
92946
  }
92696
92947
  return lines;
@@ -92711,7 +92962,7 @@ function taskCard(task, width, selected, action2) {
92711
92962
  if (!selected) return top ? `\u256D${edge}\u256E` : `\u2570${edge}\u256F`;
92712
92963
  return styledLine([{ text: top ? `\u2554${edge}\u2557` : `\u255A${edge}\u255D`, color: STATUS_COLORS[task.status], background: SELECTED_BACKGROUND, bold: true }]);
92713
92964
  };
92714
- const titleLines = wrapCells(`${selected ? "\u203A " : " "}${task.title || "Untitled task"}`, inside).slice(0, 2);
92965
+ const titleLines = taskTitleLines(task, width, selected);
92715
92966
  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
92967
  return [
92717
92968
  border(true),
@@ -94423,7 +94674,7 @@ function settingsChildren(route2, options) {
94423
94674
 
94424
94675
  // src/tuiSettings.ts
94425
94676
  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);
94677
+ var ownerValid = (ctx, owner2) => currentOwner(ctx) === owner2 && ctx.state.owner === owner2 && (ctx.isOwnerCurrent?.() ?? true);
94427
94678
  var asObject = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
94428
94679
  var safeText2 = (value) => terminalText(String(value ?? "")).replace(/\s+/g, " ").trim();
94429
94680
  var isSecret = (key) => /(?:secret|token|password|private.?key|encrypted|ciphertext|api_key$|^key$)/i.test(key);
@@ -94444,10 +94695,10 @@ function settingsWebUrl(ctx) {
94444
94695
  return "https://openmates.org";
94445
94696
  }
94446
94697
  }
94447
- function createTuiSettingsState(owner, options = {}) {
94698
+ function createTuiSettingsState(owner2, options = {}) {
94448
94699
  return {
94449
94700
  route: "main",
94450
- owner,
94701
+ owner: owner2,
94451
94702
  ownerStale: false,
94452
94703
  generation: 0,
94453
94704
  authenticated: options.authenticated ?? true,
@@ -94492,17 +94743,17 @@ function restrictedSession(client2) {
94492
94743
  }
94493
94744
  }
94494
94745
  function syncTuiSettingsOwner(ctx) {
94495
- const owner = currentOwner(ctx);
94496
- if (ctx.isOwnerCurrent?.() === false && owner === ctx.state.owner) {
94746
+ const owner2 = currentOwner(ctx);
94747
+ if (ctx.isOwnerCurrent?.() === false && owner2 === ctx.state.owner) {
94497
94748
  if (!ctx.state.ownerStale) {
94498
- const fresh2 = createTuiSettingsState(owner, { authenticated: ctx.client.hasSession(), restricted: restrictedSession(ctx.client), paymentEnabled: false, webUrl: settingsWebUrl(ctx) });
94749
+ const fresh2 = createTuiSettingsState(owner2, { authenticated: ctx.client.hasSession(), restricted: restrictedSession(ctx.client), paymentEnabled: false, webUrl: settingsWebUrl(ctx) });
94499
94750
  Object.assign(ctx.state, fresh2, { ownerStale: true, generation: ctx.state.generation + 1 });
94500
94751
  ctx.render();
94501
94752
  }
94502
94753
  return true;
94503
94754
  }
94504
- if (ctx.state.owner === owner) return false;
94505
- const fresh = createTuiSettingsState(owner, {
94755
+ if (ctx.state.owner === owner2) return false;
94756
+ const fresh = createTuiSettingsState(owner2, {
94506
94757
  authenticated: ctx.client.hasSession(),
94507
94758
  restricted: restrictedSession(ctx.client),
94508
94759
  paymentEnabled: ctx.state.paymentEnabled,
@@ -94537,14 +94788,14 @@ async function loadPage(ctx, page) {
94537
94788
  const state = ctx.state;
94538
94789
  if (state.busy) return;
94539
94790
  if (state.ownerStale) return;
94540
- const owner = currentOwner(ctx), generation = ++state.generation;
94791
+ const owner2 = currentOwner(ctx), generation = ++state.generation;
94541
94792
  state.busy = true;
94542
94793
  state.loading = true;
94543
94794
  state.error = null;
94544
94795
  ctx.render();
94545
94796
  try {
94546
94797
  const value = await page.load(ctx.client);
94547
- if (!ownerValid(ctx, owner)) {
94798
+ if (!ownerValid(ctx, owner2)) {
94548
94799
  ctx.render();
94549
94800
  return;
94550
94801
  }
@@ -94565,9 +94816,9 @@ async function loadPage(ctx, page) {
94565
94816
  state.isAdmin = info.is_admin === true;
94566
94817
  }
94567
94818
  } catch (error) {
94568
- if (ownerValid(ctx, owner) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94819
+ if (ownerValid(ctx, owner2) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94569
94820
  } finally {
94570
- if (ownerValid(ctx, owner) && state.generation === generation) {
94821
+ if (ownerValid(ctx, owner2) && state.generation === generation) {
94571
94822
  state.busy = false;
94572
94823
  state.loading = false;
94573
94824
  ctx.render();
@@ -94604,10 +94855,10 @@ async function openTuiSettingsPage(state, route2, ctx) {
94604
94855
  await loadPage(ctx, page);
94605
94856
  }
94606
94857
  async function loadProfile(ctx) {
94607
- const state = ctx.state, owner = currentOwner(ctx), generation = state.generation;
94858
+ const state = ctx.state, owner2 = currentOwner(ctx), generation = state.generation;
94608
94859
  try {
94609
94860
  const info = asObject(await ctx.client.whoAmI());
94610
- if (!ownerValid(ctx, owner) || state.generation !== generation) return;
94861
+ if (!ownerValid(ctx, owner2) || state.generation !== generation) return;
94611
94862
  state.profile = { username: safeText2(info.username), email: safeText2(info.email), account: safeText2(info.id), team: safeText2(info.active_team_name) };
94612
94863
  state.isAdmin = info.is_admin === true;
94613
94864
  ctx.render();
@@ -94615,11 +94866,11 @@ async function loadProfile(ctx) {
94615
94866
  }
94616
94867
  }
94617
94868
  async function loadCapabilities(ctx) {
94618
- const state = ctx.state, owner = currentOwner(ctx), generation = state.generation;
94869
+ const state = ctx.state, owner2 = currentOwner(ctx), generation = state.generation;
94619
94870
  if (!state.authenticated || typeof ctx.client.settingsGet !== "function") return;
94620
94871
  try {
94621
94872
  const status = asObject(await ctx.client.settingsGet("server-status"));
94622
- if (!ownerValid(ctx, owner) || state.generation !== generation) return;
94873
+ if (!ownerValid(ctx, owner2) || state.generation !== generation) return;
94623
94874
  state.paymentEnabled = status.is_self_hosted !== true && status.payment_enabled === true;
94624
94875
  state.paymentChecked = true;
94625
94876
  ctx.render();
@@ -94688,7 +94939,7 @@ async function runMutation(ctx, actionId) {
94688
94939
  ctx.render();
94689
94940
  return;
94690
94941
  }
94691
- const owner = currentOwner(ctx), generation = ++state.generation;
94942
+ const owner2 = currentOwner(ctx), generation = ++state.generation;
94692
94943
  state.busy = true;
94693
94944
  state.error = null;
94694
94945
  state.message = null;
@@ -94700,7 +94951,7 @@ async function runMutation(ctx, actionId) {
94700
94951
  ctx.render();
94701
94952
  return;
94702
94953
  }
94703
- if (!ownerValid(ctx, owner) || state.generation !== generation) return;
94954
+ if (!ownerValid(ctx, owner2) || state.generation !== generation) return;
94704
94955
  const response = asObject(result);
94705
94956
  if (response.success === false) throw new Error(safeText2(response.message) || "Operation was not accepted.");
94706
94957
  state.message = action2 ? `${action2.label} completed.` : "Saved.";
@@ -94716,9 +94967,9 @@ async function runMutation(ctx, actionId) {
94716
94967
  return;
94717
94968
  }
94718
94969
  } catch (error) {
94719
- if (ownerValid(ctx, owner) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94970
+ if (ownerValid(ctx, owner2) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94720
94971
  } finally {
94721
- if (ownerValid(ctx, owner) && state.generation === generation) {
94972
+ if (ownerValid(ctx, owner2) && state.generation === generation) {
94722
94973
  state.busy = false;
94723
94974
  ctx.render();
94724
94975
  }
@@ -94788,19 +95039,19 @@ async function handleTuiSettingsCommand(ctx, arg) {
94788
95039
  if (row3.switchTeam) {
94789
95040
  if (ctx.client.getActiveTeamId() === row3.switchTeam) await openTuiSettingsPage(state, "teams/details", ctx);
94790
95041
  else {
94791
- const owner = currentOwner(ctx), generation = ++state.generation;
95042
+ const owner2 = currentOwner(ctx), generation = ++state.generation;
94792
95043
  state.busy = true;
94793
95044
  state.error = null;
94794
95045
  ctx.render();
94795
95046
  try {
94796
95047
  const team = await ctx.client.getTeam(row3.switchTeam);
94797
- if (!ownerValid(ctx, owner) || state.generation !== generation) return true;
95048
+ if (!ownerValid(ctx, owner2) || state.generation !== generation) return true;
94798
95049
  ctx.client.setActiveTeamId(team.team_id ?? row3.switchTeam);
94799
95050
  ctx.render();
94800
95051
  } catch (error) {
94801
- if (ownerValid(ctx, owner) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
95052
+ if (ownerValid(ctx, owner2) && state.generation === generation) state.error = safeText2(error instanceof Error ? error.message : error);
94802
95053
  } finally {
94803
- if (ownerValid(ctx, owner) && state.generation === generation) {
95054
+ if (ownerValid(ctx, owner2) && state.generation === generation) {
94804
95055
  state.busy = false;
94805
95056
  ctx.render();
94806
95057
  }
@@ -95102,13 +95353,13 @@ function renderTuiSettings(state, width) {
95102
95353
  // src/tuiModelSelector.ts
95103
95354
  var currentOwner2 = (ctx) => typeof ctx.owner === "function" ? ctx.owner() : ctx.owner;
95104
95355
  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);
95356
+ 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
95357
  var clean = (value) => terminalText(value).replace(/\s+/g, " ").trim();
95107
95358
  var modelValue = (model) => model.id;
95108
95359
  var rank = { low: 0, medium: 1, high: 2, max: 3 };
95109
- function createTuiModelSelectorState(owner, chatId = null) {
95360
+ function createTuiModelSelectorState(owner2, chatId = null) {
95110
95361
  return {
95111
- owner,
95362
+ owner: owner2,
95112
95363
  chatId,
95113
95364
  generation: 0,
95114
95365
  open: false,
@@ -95125,9 +95376,9 @@ function createTuiModelSelectorState(owner, chatId = null) {
95125
95376
  };
95126
95377
  }
95127
95378
  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 });
95379
+ const owner2 = currentOwner2(ctx), chatId = currentChatId(ctx);
95380
+ if (ctx.state.owner === owner2 && ctx.state.chatId === chatId && (ctx.isOwnerCurrent?.() ?? true)) return false;
95381
+ Object.assign(ctx.state, createTuiModelSelectorState(owner2, chatId), { generation: ctx.state.generation + 1 });
95131
95382
  ctx.render();
95132
95383
  return true;
95133
95384
  }
@@ -95165,14 +95416,14 @@ function tuiModelSelectionForSend(state) {
95165
95416
  }
95166
95417
  async function restoreTuiModelSelection(ctx) {
95167
95418
  syncTuiModelSelectorOwner(ctx);
95168
- const state = ctx.state, owner = currentOwner2(ctx), chatId = currentChatId(ctx), generation = ++state.generation;
95419
+ const state = ctx.state, owner2 = currentOwner2(ctx), chatId = currentChatId(ctx), generation = ++state.generation;
95169
95420
  state.ready = false;
95170
95421
  state.busy = true;
95171
95422
  state.error = null;
95172
95423
  ctx.render();
95173
95424
  try {
95174
95425
  const [catalogResult, selectionResult] = await Promise.allSettled([ctx.loadCatalog(), ctx.restoreSelection()]);
95175
- if (!current(ctx, owner, chatId, generation)) return;
95426
+ if (!current(ctx, owner2, chatId, generation)) return;
95176
95427
  if (catalogResult.status === "fulfilled") {
95177
95428
  state.models = catalogResult.value.filter((model) => model.id.startsWith(`${model.providerId}/`) && model.name && model.providerName);
95178
95429
  state.catalogAuthoritative = catalogResult.value.authoritative !== false;
@@ -95205,13 +95456,13 @@ async function restoreTuiModelSelection(ctx) {
95205
95456
  state.selection = "auto";
95206
95457
  state.error = "The selected model is unavailable. Reset to Auto.";
95207
95458
  await ctx.persistSelection("auto");
95208
- if (!current(ctx, owner, chatId, generation)) return;
95459
+ if (!current(ctx, owner2, chatId, generation)) return;
95209
95460
  }
95210
95461
  state.ready = true;
95211
95462
  } catch (error) {
95212
- if (current(ctx, owner, chatId, generation)) state.error = clean(error instanceof Error ? error.message : String(error));
95463
+ if (current(ctx, owner2, chatId, generation)) state.error = clean(error instanceof Error ? error.message : String(error));
95213
95464
  } finally {
95214
- if (current(ctx, owner, chatId, generation)) {
95465
+ if (current(ctx, owner2, chatId, generation)) {
95215
95466
  state.busy = false;
95216
95467
  ctx.render();
95217
95468
  }
@@ -95322,20 +95573,20 @@ async function handleTuiModelSelectorAction(ctx, action2) {
95322
95573
  const requested = action2.slice(7);
95323
95574
  if (requested !== "auto" && !usableModels(state).some((model) => modelValue(model) === requested)) return true;
95324
95575
  const selection = requested === state.selection && requested !== "auto" ? "auto" : requested;
95325
- const owner = currentOwner2(ctx), chatId = currentChatId(ctx), generation = ++state.generation;
95576
+ const owner2 = currentOwner2(ctx), chatId = currentChatId(ctx), generation = ++state.generation;
95326
95577
  state.busy = true;
95327
95578
  state.error = null;
95328
95579
  ctx.render();
95329
95580
  try {
95330
95581
  const persisted = await ctx.persistSelection(selection);
95331
- if (!current(ctx, owner, chatId, generation)) return true;
95582
+ if (!current(ctx, owner2, chatId, generation)) return true;
95332
95583
  state.selection = persisted;
95333
95584
  if (persisted === "auto") state.ready = true;
95334
95585
  closeTuiModelSelector(ctx);
95335
95586
  } catch (error) {
95336
- if (current(ctx, owner, chatId, generation)) state.error = clean(error instanceof Error ? error.message : String(error));
95587
+ if (current(ctx, owner2, chatId, generation)) state.error = clean(error instanceof Error ? error.message : String(error));
95337
95588
  } finally {
95338
- if (current(ctx, owner, chatId, generation)) {
95589
+ if (current(ctx, owner2, chatId, generation)) {
95339
95590
  state.busy = false;
95340
95591
  ctx.render();
95341
95592
  }
@@ -95484,15 +95735,15 @@ function createTuiModelSelectorShell(args) {
95484
95735
  }
95485
95736
  };
95486
95737
  const sync = () => {
95487
- const owner = ownerId(client2);
95488
- if (!owner || !isTuiAiComposer(state)) {
95738
+ const owner2 = ownerId(client2);
95739
+ if (!owner2 || !isTuiAiComposer(state)) {
95489
95740
  clearSubscription();
95490
95741
  ownerFence = null;
95491
95742
  state.modelSelector = null;
95492
95743
  return;
95493
95744
  }
95494
95745
  const chatId = chatIdForView(state);
95495
- if (state.modelSelector?.owner === owner && state.modelSelector.chatId === chatId && ownerFence?.()) {
95746
+ if (state.modelSelector?.owner === owner2 && state.modelSelector.chatId === chatId && ownerFence?.()) {
95496
95747
  if (!waitingForChat || state.headerState !== "ready") return;
95497
95748
  waitingForChat = false;
95498
95749
  const ctx = context();
@@ -95502,7 +95753,7 @@ function createTuiModelSelectorShell(args) {
95502
95753
  return;
95503
95754
  }
95504
95755
  clearSubscription();
95505
- state.modelSelector = createTuiModelSelectorState(owner, chatId);
95756
+ state.modelSelector = createTuiModelSelectorState(owner2, chatId);
95506
95757
  ownerFence = captureTuiWorkspaceOwner(client2);
95507
95758
  subscribe(chatId);
95508
95759
  const picker = state.modelSelector;
@@ -95577,12 +95828,12 @@ function createTuiModelSelectorShell(args) {
95577
95828
  }
95578
95829
  const ctx = context();
95579
95830
  if (!ctx) return { message, blocked: true };
95580
- const owner = picker.owner, chatId = picker.chatId, generation = picker.generation;
95831
+ const owner2 = picker.owner, chatId = picker.chatId, generation = picker.generation;
95581
95832
  picker.busy = true;
95582
95833
  render();
95583
95834
  try {
95584
95835
  const saved = await ctx.persistSelection(selected.id);
95585
- if (state.modelSelector !== picker || !ownerFence?.() || picker.owner !== owner || picker.chatId !== chatId || picker.generation !== generation)
95836
+ if (state.modelSelector !== picker || !ownerFence?.() || picker.owner !== owner2 || picker.chatId !== chatId || picker.generation !== generation)
95586
95837
  return { message, blocked: true };
95587
95838
  picker.selection = saved;
95588
95839
  picker.ready = true;
@@ -95968,11 +96219,12 @@ function parseTuiMarkdown(content, width, options = {}) {
95968
96219
  index = next;
95969
96220
  continue;
95970
96221
  }
95971
- for (const line3 of rows2.slice(index, Math.min(next + 1, rows2.length)))
96222
+ for (const line3 of options.semanticOnly ? [] : rows2.slice(index, Math.min(next + 1, rows2.length)))
95972
96223
  output.push(...wrapped([{ text: terminalText(line3), color: CODE }], width).map((line4) => ({ type: "line", line: line4 })));
95973
96224
  index = next;
95974
96225
  continue;
95975
96226
  }
96227
+ if (options.semanticOnly) continue;
95976
96228
  const heading = /^\s{0,3}#{1,6}\s+(.+?)\s*#*\s*$/.exec(row3);
95977
96229
  if (heading && output.length && output.at(-1)?.type === "line" && output.at(-1).line !== "") {
95978
96230
  const previous = output.at(-1);
@@ -96245,7 +96497,8 @@ function renderTuiResultsViewLines(data, options = {}) {
96245
96497
 
96246
96498
  // src/tuiChatResults.ts
96247
96499
  function messageResultsViews(content) {
96248
- return parseMessageSegments(content, { preserveCodeFences: true }).flatMap((segment) => segment.type === "text" ? parseTuiMarkdown(segment.value, 160).filter((block) => block.type === "results-view") : []);
96500
+ if (!/embeds_(?:results|map)_view/i.test(content)) return [];
96501
+ return parseMessageSegments(content, { preserveCodeFences: true }).flatMap((segment) => segment.type === "text" ? parseTuiMarkdown(segment.value, 160, { semanticOnly: true }).filter((block) => block.type === "results-view") : []);
96249
96502
  }
96250
96503
  function messageSupplementalEmbedIds(content) {
96251
96504
  const ids = /* @__PURE__ */ new Set();
@@ -97478,7 +97731,8 @@ function renderHeaderDialog(dialog, width, height = 24, state) {
97478
97731
 
97479
97732
  // src/tuiInteractiveQuestions.ts
97480
97733
  function messageQuestions(content) {
97481
- return parseTuiMarkdown(content, 160).flatMap((block) => block.type === "question" ? [block.payload] : []);
97734
+ if (!/interactive_question/i.test(content)) return [];
97735
+ return parseTuiMarkdown(content, 160, { semanticOnly: true }).flatMap((block) => block.type === "question" ? [block.payload] : []);
97482
97736
  }
97483
97737
  function chatQuestions(state) {
97484
97738
  const messages = state.screen === "example" && state.activeExample ? state.activeExample.messages : state.messages;
@@ -97491,7 +97745,8 @@ function chatQuestions(state) {
97491
97745
  const latest = questions.filter((item) => item.payload.id === question.payload.id).at(-1);
97492
97746
  for (let index = messages.length - 1; index > latest.messageIndex; index--) {
97493
97747
  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);
97748
+ if (!/interactive_response/i.test(messages[index].content)) continue;
97749
+ const response = parseTuiMarkdown(messages[index].content, 160, { semanticOnly: true }).find((block) => block.type === "response" && block.payload.id === question.payload.id);
97495
97750
  if (response?.type === "response" && !validateInteractiveQuestionAnswer(question.payload, response.payload)) {
97496
97751
  question.response = formatInteractiveQuestionAnswer(question.payload, response.payload).responsePayload;
97497
97752
  break;
@@ -98456,13 +98711,17 @@ function renderWorkspaceFrame(state, rawWidth, rawHeight, body, options = {}) {
98456
98711
  const marker = content.findIndex((row3) => /^[>›] /.test(lineText(row3).trimStart()));
98457
98712
  start = marker >= bodyHeight - 4 ? Math.max(0, marker - bodyHeight + 6) : 0;
98458
98713
  } 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);
98714
+ const markers = content.map((row3, index) => /(?:^|[│║|])\s*[>›] /.test(lineText(row3)) ? index : -1).filter((index) => index >= 0);
98460
98715
  const marker = state.screen === "workflow" ? markers.at(-1) ?? -1 : markers[0] ?? -1;
98461
98716
  let selectionEnd = marker;
98462
98717
  if (["workflow", "start", "chats", "apps", "projects", "workflows"].includes(state.screen) && marker >= 0) {
98463
98718
  const closing2 = content.findIndex((row3, index) => index > marker && lineText(row3).includes("\u2570"));
98464
98719
  selectionEnd = Math.min(marker + 10, closing2 < 0 ? marker : closing2);
98465
98720
  }
98721
+ if ((state.screen === "tasks" || state.screen === "project" && state.projectTab === "tasks") && marker >= 0) {
98722
+ const closing2 = content.findIndex((row3, index) => index > marker && lineText(row3).includes("\u255A"));
98723
+ selectionEnd = closing2 < 0 ? marker : closing2;
98724
+ }
98466
98725
  if (selectionEnd >= start + viewportHeight) start = Math.min(maxStart, selectionEnd - viewportHeight + 1);
98467
98726
  if (marker >= 0 && marker < start) start = marker;
98468
98727
  }
@@ -98546,7 +98805,9 @@ function renderWorkspaceFrame(state, rawWidth, rawHeight, body, options = {}) {
98546
98805
  const sidebarRow = sidebarWidth ? renderSide(side[i], sidebarWidth, gutter, true) : void 0;
98547
98806
  const settingsColumn2 = gutter + sidebarWidth + paneWidth + 2;
98548
98807
  const settingsRow = settingsWidth ? renderSide(settingsRows[i], settingsWidth, settingsColumn2, false) : void 0;
98549
- rows2.push(place(rendered, sidebarRow, settingsRow));
98808
+ const painted = place(rendered, sidebarRow, settingsRow);
98809
+ rows2.push(painted);
98810
+ options.onBodyLine?.(value, i + 2, rendered, painted);
98550
98811
  }
98551
98812
  if (state.modelSelector?.open && showComposer && aiComposer && !modal && !settingsFullscreen && bodyHeight > 0) {
98552
98813
  const popWidth = Math.max(1, Math.min(46, composerWidth));
@@ -99906,8 +100167,8 @@ function executeTuiAppsSkill(client2, run, confirmation) {
99906
100167
  async function executeConfirmedTuiAppsSkill(client2, run) {
99907
100168
  if (!client2.hasSession()) throw new Error("Sign in to run Apps skills in the terminal.");
99908
100169
  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));
100170
+ const owner2 = record7(await client2.whoAmI());
100171
+ const ownerId2 = string3(owner2.id, string3(owner2.user_id));
99911
100172
  if (!ownerId2) throw new Error("Account identity unavailable; skill was not run.");
99912
100173
  const rootId = randomUUID19();
99913
100174
  const key = randomBytes10(32);
@@ -100233,6 +100494,106 @@ async function handleSettingsKey(context, chunk, key) {
100233
100494
  return handleTuiSettingsKey(ctx, chunk, key);
100234
100495
  }
100235
100496
 
100497
+ // src/tuiStreamingRender.ts
100498
+ var COLORS = ["#ff2d55", "#ff6b2b", "#ffd60a", "#30d158", "#32ade6", "#bf5af2"];
100499
+ var glowLines = /* @__PURE__ */ new WeakSet();
100500
+ 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)(?:$|[-_])/;
100501
+ function tuiResponsePhase(segments2) {
100502
+ const skills = [];
100503
+ for (let i = segments2.length - 1; i >= 0; i--) {
100504
+ const segment = segments2[i];
100505
+ if (segment.type === "text") {
100506
+ if (segment.value.trim()) break;
100507
+ continue;
100508
+ }
100509
+ skills.push(typeof segment.meta?.skill_id === "string" ? segment.meta.skill_id : "");
100510
+ }
100511
+ return skills.length ? skills.some((id) => ACTION_SKILL.test(id.toLowerCase())) ? "Working" : "Researching" : "Working";
100512
+ }
100513
+ function clearTuiSendState(state) {
100514
+ state.isBusy = false;
100515
+ state.isAwaitingAi = false;
100516
+ state.streamingMessage = null;
100517
+ state.aiTaskId = null;
100518
+ state.projectFocusPending = null;
100519
+ }
100520
+ var owner = (state) => JSON.stringify([state.signedIn, state.currentUserHash, state.activeTeamId, state.activeChatId, state.routeVersion]);
100521
+ var frames2 = /* @__PURE__ */ new WeakMap();
100522
+ function renderTuiThinking(mate, width, model, phase = "Thinking") {
100523
+ const status = truncateCells(`\u2726 ${phase}\u2026`, width), statusStart = Math.max(0, Math.floor((width - cells(status)) / 2));
100524
+ const name = truncateCells(mate, Math.max(0, statusStart - 1));
100525
+ const spans = [
100526
+ { text: name, bold: true, color: "#5a85eb" },
100527
+ { text: " ".repeat(Math.max(0, statusStart - cells(name))) },
100528
+ { text: status, bold: true, color: "#80caff" }
100529
+ ];
100530
+ const rows2 = name ? [{ text: spans.map((span) => span.text).join(""), spans }] : [
100531
+ { text: truncateCells(mate, width), bold: true, color: "#5a85eb" },
100532
+ { text: status, bold: true, color: "#80caff" }
100533
+ ];
100534
+ if (model) rows2.push({ text: truncateCells(model, width), color: "#808080" });
100535
+ return rows2;
100536
+ }
100537
+ function renderTuiStreamingGlow(width, phase) {
100538
+ const size = Math.max(1, Math.min(72, width)), inset = Math.max(0, Math.floor((width - size) / 2));
100539
+ const spans = [
100540
+ { text: " ".repeat(inset), color: "#e6e6e6" },
100541
+ ...Array.from({ length: size }, (_, index) => ({
100542
+ text: "\u2581",
100543
+ color: COLORS[(Math.floor(index * COLORS.length / size - phase / 2) % COLORS.length + COLORS.length) % COLORS.length]
100544
+ })),
100545
+ { text: " ".repeat(Math.max(0, width - size - inset)), color: "#e6e6e6" }
100546
+ ];
100547
+ const line3 = { text: spans.map((span) => span.text).join(""), spans };
100548
+ glowLines.add(line3);
100549
+ return line3;
100550
+ }
100551
+ function isTuiStreamingGlow(line3) {
100552
+ return typeof line3 !== "string" && glowLines.has(line3);
100553
+ }
100554
+ function clearTuiStreamingFrame(state) {
100555
+ frames2.delete(state);
100556
+ }
100557
+ function rememberTuiStreamingFrame(state, frame, width, height, bodyWidth, painted, options) {
100558
+ const mode = options.colorMode ?? "none";
100559
+ if (!painted || !state.isAwaitingAi || state.screen !== "chat" || mode === "none" || options.reducedMotion || state.modelSelector?.open) return;
100560
+ const at = painted.line.indexOf(painted.rendered);
100561
+ if (at < 0) return;
100562
+ const prefix = painted.line.slice(0, at), suffix = painted.line.slice(at + painted.rendered.length);
100563
+ const variants = Array.from({ length: 12 }, (_, phase) => {
100564
+ const glow = renderTuiStreamingGlow(bodyWidth, phase);
100565
+ return prefix + (typeof glow === "string" ? glow : glow.spans.map((span) => foreground(span.text, span.color ?? "#e6e6e6", mode)).join("")) + suffix;
100566
+ });
100567
+ frames2.set(state, {
100568
+ owner: owner(state),
100569
+ width,
100570
+ height,
100571
+ mode,
100572
+ ascii: options.ascii ?? false,
100573
+ rows: frame.split("\n"),
100574
+ row: painted.row,
100575
+ variants,
100576
+ messages: state.messages,
100577
+ message: state.streamingMessage,
100578
+ content: state.streamingMessage?.content,
100579
+ thinking: state.streamingMessage?.thinkingContent,
100580
+ thinkingActive: state.streamingMessage?.thinkingActive,
100581
+ expanded: state.streamingMessage?.thinkingExpanded,
100582
+ model: state.streamingMessage?.modelName,
100583
+ scroll: state.scrollOffset,
100584
+ input: state.input,
100585
+ focus: state.focus,
100586
+ sidebar: state.sidebarOpen,
100587
+ settings: state.settings
100588
+ });
100589
+ }
100590
+ function renderTuiStreamingAnimationFrame(state, width, height, options = {}) {
100591
+ const saved = frames2.get(state);
100592
+ 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;
100593
+ saved.rows[saved.row] = saved.variants[(state.streamingPhase % 12 + 12) % 12];
100594
+ return saved.rows.join("\n");
100595
+ }
100596
+
100236
100597
  // src/tuiFullscreenChrome.ts
100237
100598
  function captureTuiView(state) {
100238
100599
  return {
@@ -100248,6 +100609,7 @@ function captureTuiView(state) {
100248
100609
  };
100249
100610
  }
100250
100611
  function restoreTuiView(state, origin) {
100612
+ clearTuiSendState(state);
100251
100613
  Object.assign(state, origin);
100252
100614
  state.inputCursor = origin.inputCursor ?? null;
100253
100615
  state.navigationIndex = ["chats", "apps", "projects", "workflows", "tasks"].indexOf(state.workspace);
@@ -100276,6 +100638,7 @@ function closeTuiFullscreen(state) {
100276
100638
  return true;
100277
100639
  }
100278
100640
  if (state.screen === "chat" || state.screen === "example") {
100641
+ clearTuiSendState(state);
100279
100642
  if (!state.input.startsWith("/")) state.drafts[state.screen === "example" ? `example:${state.activeExample?.chat.id}` : state.activeChatId ?? "new"] = state.input;
100280
100643
  const origin = state.chatOrigin;
100281
100644
  state.chatOrigin = null;
@@ -100504,7 +100867,9 @@ function createInitialTuiState() {
100504
100867
  chatContextAuthoringControls: {},
100505
100868
  status: null,
100506
100869
  isBusy: false,
100507
- isAwaitingAi: false
100870
+ isAwaitingAi: false,
100871
+ streamingMessage: null,
100872
+ streamingPhase: 0
100508
100873
  };
100509
100874
  }
100510
100875
  function programmaticQuickstart() {
@@ -100560,6 +100925,7 @@ function resetEndedTuiSession(state, hasSession) {
100560
100925
  return true;
100561
100926
  }
100562
100927
  function renderTuiFrame(state, width, height, options = {}) {
100928
+ clearTuiStreamingFrame(state);
100563
100929
  if (state.screen !== "chat") clearChatRenderCache(state);
100564
100930
  fenceTuiSettingsView(state);
100565
100931
  beginPointerFrame(state, width, height);
@@ -100569,13 +100935,23 @@ function renderTuiFrame(state, width, height, options = {}) {
100569
100935
  const teamBanner = state.signedIn && state.activeTeamId ? [{ text: teamIdentityLabel(state), color: "#5a85eb", bold: true }] : [];
100570
100936
  const embedHero = state.screen === "embed" ? renderEmbedHero(state, bodyWidth, height) : null;
100571
100937
  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(
100938
+ let glow;
100939
+ const frame = renderWorkspaceFrame(
100573
100940
  state,
100574
100941
  width,
100575
100942
  height,
100576
100943
  [...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 }
100944
+ {
100945
+ ...options,
100946
+ stickyRows: stickyRows + header2.length + teamBanner.length,
100947
+ stickyFallbackRows: embedHero ? header2.length + teamBanner.length : void 0,
100948
+ onBodyLine: (value, row3, rendered, line3) => {
100949
+ if (isTuiStreamingGlow(value)) glow = { row: row3, rendered, line: line3 };
100950
+ }
100951
+ }
100578
100952
  );
100953
+ rememberTuiStreamingFrame(state, frame, width, height, bodyWidth, glow, options);
100954
+ return frame;
100579
100955
  }
100580
100956
  function teamIdentityLabel(state) {
100581
100957
  const name = terminalText(state.activeTeamName?.trim() || "Team").replace(/\s+/g, " ").slice(0, 80);
@@ -100687,7 +101063,7 @@ function renderScreenBody(state, width, height, embedHero) {
100687
101063
  case "projects":
100688
101064
  return [...homeHeader(state, width, height), ...renderProjectCarousel(filteredProjects(state.projects, state.filter), width, state.selectedIndex, state.focus === "content")];
100689
101065
  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"];
101066
+ 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
101067
  case "status":
100692
101068
  return renderStatus(state, width);
100693
101069
  case "results-view": {
@@ -100719,9 +101095,9 @@ function renderScreenBody(state, width, height, embedHero) {
100719
101095
  case "workflow":
100720
101096
  return renderWorkflowDetail(state, width);
100721
101097
  case "tasks":
100722
- return [...homeHeader(state, width, height), ...renderTasks(state, width)];
101098
+ return [...homeHeader(state, width, height), ...renderTasks(state, width, height, homeHeader(state, width, height).length)];
100723
101099
  case "task":
100724
- return renderTaskDetail2(state, width);
101100
+ return renderTaskDetail2(state, width, height);
100725
101101
  case "start":
100726
101102
  default:
100727
101103
  return [...renderHomeChatCards(state, width, height), ...state.signedIn && state.activeTeamId ? ["", teamAiReminder()] : []];
@@ -100761,11 +101137,18 @@ function renderHelp(width) {
100761
101137
  "Outside TUI: openmates --help, openmates chats --help, openmates apps --help"
100762
101138
  ].flatMap((line3) => wrap(line3, width));
100763
101139
  }
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 });
101140
+ function taskViewport(state, height, prefixRows = 0) {
101141
+ return {
101142
+ start: Math.max(0, state.scrollOffset - prefixRows - 4),
101143
+ end: state.scrollOffset + height + 4,
101144
+ followSelection: state.followSelection && state.focus === "content"
101145
+ };
100766
101146
  }
100767
- function renderTaskDetail2(state, width) {
100768
- if (!state.activeTask) return renderTasks(state, width);
101147
+ function renderTasks(state, width, height, prefixRows = 0) {
101148
+ 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) });
101149
+ }
101150
+ function renderTaskDetail2(state, width, height) {
101151
+ if (!state.activeTask) return renderTasks(state, width, height);
100769
101152
  return renderTaskDetails(state.activeTask, {
100770
101153
  width,
100771
101154
  activity: state.taskContext?.activity,
@@ -100868,8 +101251,7 @@ function renderExampleChat(state, width, height) {
100868
101251
  }
100869
101252
  return lines.flatMap((line3) => typeof line3 === "string" ? wrap(line3, width) : [line3]);
100870
101253
  }
100871
- function renderChat(state, width, height) {
100872
- const lines = renderChatHeader(state, width, height);
101254
+ function renderChat(state, width, height, fullHistory = false) {
100873
101255
  const embeds = new Map(Object.entries(state.chatEmbeds));
100874
101256
  const cacheContext = {
100875
101257
  owner: JSON.stringify([state.signedIn, state.currentUserHash, state.activeTeamId, state.activeChatId, state.routeVersion]),
@@ -100896,47 +101278,84 @@ function renderChat(state, width, height) {
100896
101278
  return firstEmbedId;
100897
101279
  };
100898
101280
  let contextIndex2 = 0, viewOffset = 0, questionOffset = 0;
100899
- for (const [index, message] of state.messages.entries()) {
101281
+ const offsets = state.messages.map((message) => {
100900
101282
  const event = message.role === "system" ? parseChatContextContent(message.content) : null;
100901
- if (event) {
100902
- contextIndex2++;
100903
- lines.push(`${chatContextSummary(event)} \xB7 /context ${contextIndex2}`);
101283
+ const offset = { event, contextIndex: contextIndex2 + (event ? 1 : 0), viewOffset, questionOffset };
101284
+ if (event) contextIndex2++;
101285
+ else {
101286
+ viewOffset += messageResultsViews(message.content).length;
101287
+ if (message.role === "assistant") questionOffset += messageQuestions(message.content).length;
101288
+ }
101289
+ return offset;
101290
+ });
101291
+ const footer = [];
101292
+ if (state.projectFocusPending) footer.push("Project access starts after the countdown. /project-focus-reject to cancel.");
101293
+ if (state.signedIn && state.activeTeamId) footer.push(teamAiReminder());
101294
+ if (state.followUpSuggestions.length) footer.push("", "Suggestions", ...state.followUpSuggestions.slice(0, 3).map((s, i) => ` ${i + 1}. ${s}`));
101295
+ if (state.status) footer.push("", state.status);
101296
+ const wrappedFooter = footer.flatMap((line3) => typeof line3 === "string" ? wrap(line3, width) : [line3]);
101297
+ const blocks = [];
101298
+ let rowCount = wrappedFooter.length;
101299
+ if (state.isAwaitingAi && !state.streamingMessage?.content.trim()) {
101300
+ const mate = messageLabel("assistant", state.streamingMessage?.title, state.streamingMessage?.category, state.activeChat);
101301
+ const waiting = [...renderTuiThinking(mate, width, state.streamingMessage?.modelName), ...renderTuiReasoning(state.streamingMessage, width), renderTuiStreamingGlow(width, state.streamingPhase), ""];
101302
+ blocks.push(waiting);
101303
+ rowCount += waiting.length;
101304
+ }
101305
+ const budget = Math.max(0, state.scrollOffset) + height + 1;
101306
+ for (let index = state.messages.length - 1; index >= 0; index--) {
101307
+ const message = state.messages[index], offset = offsets[index], rows2 = [];
101308
+ if (offset.event) {
101309
+ const event = offset.event;
101310
+ rows2.push(`${chatContextSummary(event)} \xB7 /context ${offset.contextIndex}`);
100904
101311
  if (event.type === "project_authoring_recommendation") {
100905
101312
  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}`);
101313
+ 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
101314
  }
100908
- lines.push("");
100909
- continue;
101315
+ } else {
101316
+ if (message === state.streamingMessage && !message.content.trim()) continue;
101317
+ const questionBlocks = message.role === "assistant";
101318
+ const prepared = cachedChatLayout(
101319
+ state,
101320
+ message,
101321
+ message.content,
101322
+ { ...cacheContext, variant: questionBlocks ? "questions" : "literal-questions", cacheable: cacheable.has(index) },
101323
+ () => prepareMessageContent(message.content, width, state, questionBlocks)
101324
+ );
101325
+ const label3 = messageLabel(message.role, message.title, message.category, state.activeChat, message.remoteUser);
101326
+ if (state.isAwaitingAi && message === state.streamingMessage) rows2.push(...renderTuiThinking(label3, width, message.modelName, message.thinkingActive ? "Thinking" : tuiResponsePhase(prepared.segments)));
101327
+ else {
101328
+ rows2.push({ text: label3, color: "#5a85eb", bold: true });
101329
+ if (message.role === "assistant" && message.modelName) rows2.push({ text: message.modelName, color: "#808080" });
101330
+ }
101331
+ if (message.role === "assistant") rows2.push(...renderTuiReasoning(message, width));
101332
+ rows2.push(...renderPreparedMessageContent(
101333
+ prepared,
101334
+ width,
101335
+ embeds,
101336
+ state,
101337
+ questionBlocks ? message.embedIds : void 0,
101338
+ offset.viewOffset,
101339
+ offset.questionOffset,
101340
+ defaultEmbedId
101341
+ ));
101342
+ if (state.isAwaitingAi && message === state.streamingMessage) rows2.push(renderTuiStreamingGlow(width, state.streamingPhase));
100910
101343
  }
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]);
101344
+ rows2.push("");
101345
+ const wrapped2 = rows2.flatMap((line3) => typeof line3 === "string" ? wrap(line3, width) : [line3]);
101346
+ blocks.push(wrapped2);
101347
+ rowCount += wrapped2.length;
101348
+ if (!fullHistory && !(state.followSelection && state.focus === "content") && rowCount >= budget) break;
101349
+ }
101350
+ return [...renderChatHeader(state, width, height), ...blocks.reverse().flat(), ...wrappedFooter];
101351
+ }
101352
+ function renderTuiReasoning(message, width) {
101353
+ if (!message?.thinkingContent?.trim()) return [];
101354
+ const summary = pointerLine(
101355
+ `\u2726 Thinking for this response ${message.thinkingExpanded ? "\u25BE" : "\u25B8"} /thinking`,
101356
+ { kind: "command", command: `/thinking${message.id ? " " + message.id : ""}` }
101357
+ );
101358
+ return [summary, ...message.thinkingExpanded ? wrap(message.thinkingContent, width).map((text7) => ({ text: text7, color: "#a0a0a0" })) : []];
100940
101359
  }
100941
101360
  function teamAiReminder() {
100942
101361
  return "To ask AI, mention @openmates or a configured Mate.";
@@ -100989,17 +101408,13 @@ function relativeTime(timestamp) {
100989
101408
  function renderStatus(state, width) {
100990
101409
  return ["OpenMates", "", state.status ?? "Working..."].flatMap((line3) => wrap(line3, width));
100991
101410
  }
100992
- function prepareMessageContent(content, width, state, questionBlocks = true, countQuestions = false) {
101411
+ function prepareMessageContent(content, width, state, questionBlocks = true) {
100993
101412
  const segments2 = parseMessageSegments(content, { preserveCodeFences: true }).map((segment) => segment.type === "embed" ? segment : {
100994
101413
  type: "text",
100995
101414
  value: segment.value,
100996
101415
  blocks: parseTuiMarkdown(segment.value, width, { resolveEmbedAlias: (id) => state ? aliasForEmbed(state, id) : id, questionBlocks })
100997
101416
  });
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
- };
101417
+ return { segments: segments2 };
101003
101418
  }
101004
101419
  function renderMessageContentStyled(content, width, embeds = /* @__PURE__ */ new Map(), state, extraIds = [], viewOffset = 0, questionOffset = 0, questionBlocks = true) {
101005
101420
  return renderPreparedMessageContent(
@@ -101431,6 +101846,7 @@ function rememberDraft(state) {
101431
101846
  if (state.workspace === "chats" && !state.input.startsWith("/")) state.drafts[chatDraftKey(state)] = state.input;
101432
101847
  }
101433
101848
  function route(state, workspace, screen) {
101849
+ clearTuiSendState(state);
101434
101850
  if (state.settings) closeTuiSettings(state);
101435
101851
  if (state.input) rememberDraft(state);
101436
101852
  state.workspace = workspace;
@@ -101902,6 +102318,18 @@ async function handleWorkspaceCommand(context, command) {
101902
102318
  }
101903
102319
  return true;
101904
102320
  }
102321
+ case "/thinking": {
102322
+ const messages = [...state.messages, ...state.streamingMessage && !state.messages.includes(state.streamingMessage) ? [state.streamingMessage] : []];
102323
+ const message = arg ? messages.find((message2) => message2.id === arg) : messages.findLast((message2) => message2.role === "assistant" && message2.thinkingContent?.trim());
102324
+ if (state.input.trim() === command) {
102325
+ state.input = "";
102326
+ state.inputCursor = null;
102327
+ }
102328
+ if (message?.thinkingContent?.trim()) message.thinkingExpanded = !message.thinkingExpanded;
102329
+ else state.status = "No visible thinking is available for this response.";
102330
+ render();
102331
+ return true;
102332
+ }
101905
102333
  case "/browse":
101906
102334
  state.homeShowAll = !state.homeShowAll;
101907
102335
  state.selectedIndex = 0;
@@ -103384,21 +103812,21 @@ function string4(value) {
103384
103812
  function ownerToken(session, teamId) {
103385
103813
  return createHmac4("sha256", Buffer.from(session.masterKeyExportedB64, "base64")).update(JSON.stringify([session.apiUrl, session.hashedEmail, teamId, session.createdAt])).digest("hex");
103386
103814
  }
103387
- function keyFor(owner, chatId) {
103388
- return createHmac4("sha256", Buffer.from(owner, "hex")).update(chatId).digest("hex");
103815
+ function keyFor(owner2, chatId) {
103816
+ return createHmac4("sha256", Buffer.from(owner2, "hex")).update(chatId).digest("hex");
103389
103817
  }
103390
103818
  function isRecord5(value) {
103391
103819
  const v = object4(value);
103392
103820
  return typeof v.ciphertext === "string" && v.ciphertext.length > 0 && Number.isSafeInteger(v.version) && v.version >= 0 && typeof v.pending === "boolean";
103393
103821
  }
103394
- function readLocal(owner, chatId) {
103822
+ function readLocal(owner2, chatId) {
103395
103823
  try {
103396
103824
  const path2 = join19(resolveStateDir(), FILE);
103397
103825
  const raw = readFileSync17(path2, "utf8");
103398
103826
  if (Buffer.byteLength(raw) > MAX_FILE_BYTES3) return null;
103399
103827
  const envelope = object4(JSON.parse(raw));
103400
103828
  if (envelope.version !== 2) return null;
103401
- const record10 = object4(object4(envelope.owners)[owner])[keyFor(owner, chatId)];
103829
+ const record10 = object4(object4(envelope.owners)[owner2])[keyFor(owner2, chatId)];
103402
103830
  return isRecord5(record10) ? record10 : null;
103403
103831
  } catch {
103404
103832
  return null;
@@ -103407,7 +103835,7 @@ function readLocal(owner, chatId) {
103407
103835
  function sameRecord(left, right) {
103408
103836
  return left === right || !!left && !!right && left.ciphertext === right.ciphertext && left.version === right.version && left.pending === right.pending;
103409
103837
  }
103410
- async function writeLocal(owner, chatId, record10, current2, expected) {
103838
+ async function writeLocal(owner2, chatId, record10, current2, expected) {
103411
103839
  if (!current2()) throw new Error("Chat model preference owner changed.");
103412
103840
  const dir = resolveStateDir();
103413
103841
  mkdirSync16(dir, { recursive: true, mode: 448 });
@@ -103432,11 +103860,11 @@ async function writeLocal(owner, chatId, record10, current2, expected) {
103432
103860
  }
103433
103861
  } catch {
103434
103862
  }
103435
- const chats = owners[owner] ?? {};
103436
- const existing = chats[keyFor(owner, chatId)] ?? null;
103863
+ const chats = owners[owner2] ?? {};
103864
+ const existing = chats[keyFor(owner2, chatId)] ?? null;
103437
103865
  if (expected !== void 0 && !sameRecord(existing, expected)) return existing;
103438
- chats[keyFor(owner, chatId)] = record10;
103439
- owners[owner] = chats;
103866
+ chats[keyFor(owner2, chatId)] = record10;
103867
+ owners[owner2] = chats;
103440
103868
  if (Object.values(owners).reduce((sum, values) => sum + Object.keys(values).length, 0) > MAX_CHATS)
103441
103869
  throw new Error("Chat model preference cache is full.");
103442
103870
  const encoded = JSON.stringify({ version: 2, owners });
@@ -103515,21 +103943,21 @@ function createChatModelPreferences(client2) {
103515
103943
  if (!client2.hasSession()) throw new Error("Sign in to choose a chat model.");
103516
103944
  const session = client2.getSession();
103517
103945
  const teamId = client2.resolveTeamContext();
103518
- const owner = ownerToken(session, teamId);
103946
+ const owner2 = ownerToken(session, teamId);
103519
103947
  const persisted = loadSession();
103520
- if (persisted && ownerToken(persisted, teamId) !== owner)
103948
+ if (persisted && ownerToken(persisted, teamId) !== owner2)
103521
103949
  throw new Error("Chat model preference owner changed.");
103522
103950
  const current2 = () => {
103523
103951
  try {
103524
- if (!client2.hasSession() || ownerToken(client2.getSession(), client2.resolveTeamContext()) !== owner) return false;
103952
+ if (!client2.hasSession() || ownerToken(client2.getSession(), client2.resolveTeamContext()) !== owner2) return false;
103525
103953
  if (!persisted) return true;
103526
103954
  const onDisk = loadSession();
103527
- return !!onDisk && ownerToken(onDisk, teamId) === owner;
103955
+ return !!onDisk && ownerToken(onDisk, teamId) === owner2;
103528
103956
  } catch {
103529
103957
  return false;
103530
103958
  }
103531
103959
  };
103532
- return { owner, teamId, current: current2, masterKey: Buffer.from(session.masterKeyExportedB64, "base64") };
103960
+ return { owner: owner2, teamId, current: current2, masterKey: Buffer.from(session.masterKeyExportedB64, "base64") };
103533
103961
  };
103534
103962
  const checked = (current2) => {
103535
103963
  if (!current2()) throw new Error("Chat model preference owner changed.");
@@ -103539,10 +103967,10 @@ function createChatModelPreferences(client2) {
103539
103967
  if (plaintext === null) throw new Error("Chat model preference could not be decrypted.");
103540
103968
  return parseSelection(plaintext);
103541
103969
  };
103542
- const stateKey = (owner, chatId) => `${owner}:${chatId}`;
103543
- const publish = (owner, chatId, state, current2) => {
103970
+ const stateKey = (owner2, chatId) => `${owner2}:${chatId}`;
103971
+ const publish = (owner2, chatId, state, current2) => {
103544
103972
  checked(current2);
103545
- const key = stateKey(owner, chatId);
103973
+ const key = stateKey(owner2, chatId);
103546
103974
  states2.set(key, state);
103547
103975
  for (const listener of listeners.get(key) ?? []) {
103548
103976
  try {
@@ -103552,18 +103980,18 @@ function createChatModelPreferences(client2) {
103552
103980
  }
103553
103981
  return state;
103554
103982
  };
103555
- const withChat = (owner, chatId, work) => {
103556
- const key = stateKey(owner, chatId);
103983
+ const withChat = (owner2, chatId, work) => {
103984
+ const key = stateKey(owner2, chatId);
103557
103985
  const task = (serial.get(key) ?? Promise.resolve()).then(work);
103558
103986
  serial.set(key, task.catch(() => {
103559
103987
  }));
103560
103988
  return task;
103561
103989
  };
103562
103990
  const flush = async (chatId, context = capture()) => {
103563
- const { owner, teamId, current: current2, masterKey } = context;
103991
+ const { owner: owner2, teamId, current: current2, masterKey } = context;
103564
103992
  checked(current2);
103565
- const local = readLocal(owner, chatId);
103566
- if (!local?.pending) return states2.get(stateKey(owner, chatId)) ?? { selection: "auto", pending: false, restored: false };
103993
+ const local = readLocal(owner2, chatId);
103994
+ if (!local?.pending) return states2.get(stateKey(owner2, chatId)) ?? { selection: "auto", pending: false, restored: false };
103567
103995
  const selection = await decrypt(local, masterKey);
103568
103996
  try {
103569
103997
  for (let attempt = 0; attempt < 3; attempt++) {
@@ -103573,15 +104001,15 @@ function createChatModelPreferences(client2) {
103573
104001
  const accepted = await client2.compareAndSetChatModelPreference(chatId, local.ciphertext, expected, teamId);
103574
104002
  checked(current2);
103575
104003
  if (!accepted) continue;
103576
- await writeLocal(owner, chatId, { ...accepted, pending: false }, current2);
104004
+ await writeLocal(owner2, chatId, { ...accepted, pending: false }, current2);
103577
104005
  const state = { selection, pending: false, restored: true };
103578
- return publish(owner, chatId, state, current2);
104006
+ return publish(owner2, chatId, state, current2);
103579
104007
  }
103580
104008
  throw new Error("Chat model preference conflicted repeatedly.");
103581
104009
  } catch {
103582
104010
  checked(current2);
103583
104011
  const state = { selection, pending: true, restored: true };
103584
- return publish(owner, chatId, state, current2);
104012
+ return publish(owner2, chatId, state, current2);
103585
104013
  }
103586
104014
  };
103587
104015
  return {
@@ -103604,32 +104032,32 @@ function createChatModelPreferences(client2) {
103604
104032
  },
103605
104033
  async restore(chatId) {
103606
104034
  const context = capture();
103607
- const { owner, current: current2, masterKey } = context;
104035
+ const { owner: owner2, current: current2, masterKey } = context;
103608
104036
  checked(current2);
103609
- const local = readLocal(owner, chatId);
104037
+ const local = readLocal(owner2, chatId);
103610
104038
  if (local) {
103611
104039
  const selection = await decrypt(local, masterKey);
103612
104040
  checked(current2);
103613
- const initial = publish(owner, chatId, { selection, pending: local.pending, restored: true }, current2);
103614
- void withChat(owner, chatId, async () => {
104041
+ const initial = publish(owner2, chatId, { selection, pending: local.pending, restored: true }, current2);
104042
+ void withChat(owner2, chatId, async () => {
103615
104043
  if (local.pending) return flush(chatId, context);
103616
104044
  return refreshRemote(chatId, context, local);
103617
104045
  }).catch(() => {
103618
104046
  });
103619
104047
  return initial;
103620
104048
  }
103621
- return withChat(owner, chatId, () => refreshRemote(chatId, context, null));
104049
+ return withChat(owner2, chatId, () => refreshRemote(chatId, context, null));
103622
104050
  },
103623
104051
  async select(chatId, selection) {
103624
104052
  const context = capture();
103625
104053
  return withChat(context.owner, chatId, async () => {
103626
- const { owner, current: current2, masterKey } = context;
104054
+ const { owner: owner2, current: current2, masterKey } = context;
103627
104055
  const serialized = encodeSelection(selection);
103628
104056
  const ciphertext = await encryptWithAesGcmCombined(serialized, masterKey);
103629
104057
  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);
104058
+ const previous = readLocal(owner2, chatId);
104059
+ await writeLocal(owner2, chatId, { ciphertext, version: previous?.version ?? 0, pending: true }, current2);
104060
+ publish(owner2, chatId, { selection, pending: true, restored: true }, current2);
103633
104061
  return flush(chatId, context);
103634
104062
  });
103635
104063
  },
@@ -103662,7 +104090,7 @@ function createChatModelPreferences(client2) {
103662
104090
  }
103663
104091
  };
103664
104092
  async function refreshRemote(chatId, context, local) {
103665
- const { owner, teamId, current: current2, masterKey } = context;
104093
+ const { owner: owner2, teamId, current: current2, masterKey } = context;
103666
104094
  checked(current2);
103667
104095
  let remote = null;
103668
104096
  let confirmed = false;
@@ -103673,7 +104101,7 @@ function createChatModelPreferences(client2) {
103673
104101
  checked(current2);
103674
104102
  if (!local) {
103675
104103
  const state2 = { selection: "auto", pending: false, restored: false };
103676
- return publish(owner, chatId, state2, current2);
104104
+ return publish(owner2, chatId, state2, current2);
103677
104105
  }
103678
104106
  }
103679
104107
  checked(current2);
@@ -103681,21 +104109,21 @@ function createChatModelPreferences(client2) {
103681
104109
  const ciphertext = await encryptWithAesGcmCombined(encodeSelection("auto"), masterKey);
103682
104110
  checked(current2);
103683
104111
  const autoRecord = { ciphertext, version: 0, pending: false };
103684
- const stored = await writeLocal(owner, chatId, autoRecord, current2, local);
104112
+ const stored = await writeLocal(owner2, chatId, autoRecord, current2, local);
103685
104113
  checked(current2);
103686
- if (!stored) return publish(owner, chatId, { selection: "auto", pending: false, restored: false }, current2);
104114
+ if (!stored) return publish(owner2, chatId, { selection: "auto", pending: false, restored: false }, current2);
103687
104115
  if (stored !== autoRecord) {
103688
104116
  const selection2 = await decrypt(stored, masterKey);
103689
- return publish(owner, chatId, { selection: selection2, pending: stored.pending, restored: true }, current2);
104117
+ return publish(owner2, chatId, { selection: selection2, pending: stored.pending, restored: true }, current2);
103690
104118
  }
103691
- return publish(owner, chatId, { selection: "auto", pending: false, restored: true }, current2);
104119
+ return publish(owner2, chatId, { selection: "auto", pending: false, restored: true }, current2);
103692
104120
  }
103693
104121
  const chosen = remote && (!local || remote.version > local.version) ? remote : local;
103694
104122
  const selection = chosen ? await decrypt(chosen, masterKey) : "auto";
103695
104123
  checked(current2);
103696
- if (remote && chosen === remote) await writeLocal(owner, chatId, { ...remote, pending: false }, current2);
104124
+ if (remote && chosen === remote) await writeLocal(owner2, chatId, { ...remote, pending: false }, current2);
103697
104125
  const state = { selection, pending: false, restored: true };
103698
- return publish(owner, chatId, state, current2);
104126
+ return publish(owner2, chatId, state, current2);
103699
104127
  }
103700
104128
  }
103701
104129
 
@@ -103713,6 +104141,7 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
103713
104141
  hydrateExamples(state);
103714
104142
  let resolveResult = null;
103715
104143
  let renderTimer = null;
104144
+ let lastFrameOwner = null;
103716
104145
  let closed = false;
103717
104146
  let stopActivity;
103718
104147
  let activityTimer;
@@ -103745,9 +104174,25 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
103745
104174
  void refreshActivity();
103746
104175
  }, 3e4);
103747
104176
  const activityAnimation = setInterval(() => {
103748
- if (!closed && state.runningChatIds.length) {
103749
- state.activityFrame = (state.activityFrame + 1) % 4;
104177
+ if (closed) return;
104178
+ const sidebarActivity = state.runningChatIds.length > 0;
104179
+ if (sidebarActivity) state.activityFrame = (state.activityFrame + 1) % 4;
104180
+ const streaming = state.screen === "chat" && state.isAwaitingAi && state.streamingMessage && !state.textSelection && !terminal.reducedMotion && !terminal.isOutputBlocked && lastFrameOwner?.();
104181
+ if (streaming) {
104182
+ state.streamingPhase = (state.streamingPhase + 1) % 12;
104183
+ }
104184
+ if (sidebarActivity) {
103750
104185
  render();
104186
+ return;
104187
+ }
104188
+ if (streaming && !renderTimer) {
104189
+ const frame = renderTuiStreamingAnimationFrame(
104190
+ state,
104191
+ terminal.width,
104192
+ terminal.height,
104193
+ { colorMode: terminal.colorMode, ascii: terminal.ascii, reducedMotion: terminal.reducedMotion }
104194
+ );
104195
+ if (frame) terminal.render(frame, tuiComposerCursor(state, terminal.width, terminal.height), state.textSelection);
103751
104196
  }
103752
104197
  }, 250);
103753
104198
  const syncSessionView = () => {
@@ -103825,7 +104270,8 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
103825
104270
  renderTimer = null;
103826
104271
  if (closed) return;
103827
104272
  syncSessionView();
103828
- terminal.render(renderTuiFrame(state, terminal.width, terminal.height, { colorMode: terminal.colorMode, ascii: terminal.ascii }), tuiComposerCursor(state, terminal.width, terminal.height), state.textSelection);
104273
+ lastFrameOwner = client2.hasSession() ? captureTuiWorkspaceOwner(client2) : () => !client2.hasSession();
104274
+ 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
104275
  }, 16);
103830
104276
  };
103831
104277
  teamChatRefresh = createTuiTeamChatRefresh(state, client2, render);
@@ -104094,7 +104540,7 @@ async function handleEnter(params) {
104094
104540
  }
104095
104541
  async function handleCommand(params) {
104096
104542
  const { command, state, client: client2, terminal, render, finish, modelShell } = params;
104097
- client2.clearInteractiveChatViewer();
104543
+ if (command.split(/\s+/, 1)[0] !== "/thinking") client2.clearInteractiveChatViewer();
104098
104544
  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
104545
  const [name, ...parts] = command.split(/\s+/);
104100
104546
  const arg = parts.join(" ");
@@ -104254,17 +104700,66 @@ async function handleCommand(params) {
104254
104700
  async function sendTuiMessage(params) {
104255
104701
  const { state, client: client2, render } = params;
104256
104702
  if (state.isBusy) return;
104703
+ const initialMessage = params.message.trim();
104704
+ if (!initialMessage) return;
104257
104705
  const preparingOwner = client2.hasSession() ? captureTuiWorkspaceOwner(client2) : () => !client2.hasSession();
104258
104706
  const preparingRoute = state.routeVersion;
104259
- const preparingMessages = state.messages;
104707
+ const previousMessages = state.messages;
104260
104708
  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;
104709
+ const previousActiveChat = state.activeChat, previousHeaderState = state.headerState;
104710
+ const previousScreen = state.screen;
104711
+ const sourceExample = previousScreen === "example" ? state.activeExample : null;
104712
+ const history = sourceExample ? buildExampleContinuationHistory(sourceExample) : void 0;
104713
+ const draftKey = sourceExample ? `example:${sourceExample.chat.id}` : preparingChatId ?? "new";
104714
+ const draftBeforeSend = state.drafts[draftKey];
104715
+ if (sourceExample) state.messages = sourceExample.messages.map((m) => ({ role: m.role === "user" ? "user" : "assistant", content: m.content, title: m.senderName }));
104716
+ const messages = state.messages;
104717
+ const userMessage = { id: randomUUID21(), role: "user", content: initialMessage };
104718
+ messages.push(userMessage);
104719
+ state.screen = "chat";
104720
+ state.isBusy = true;
104721
+ state.drafts[draftKey] = "";
104722
+ state.input = "";
104723
+ state.inputCursor = null;
104724
+ state.status = "Preparing message\u2026";
104725
+ render();
104726
+ const ownsPreparation = () => preparingOwner() && state.routeVersion === preparingRoute && state.messages === messages && state.activeChatId === preparingChatId;
104727
+ const restorePreparation = (status) => {
104728
+ if (!ownsPreparation()) return;
104729
+ const index = messages.indexOf(userMessage);
104730
+ if (index >= 0) messages.splice(index, 1);
104731
+ state.messages = previousMessages;
104732
+ state.screen = previousScreen;
104733
+ state.isBusy = false;
104734
+ if (state.input === "" && state.drafts[draftKey] === "") {
104735
+ state.input = initialMessage;
104736
+ state.inputCursor = null;
104737
+ if (draftBeforeSend === void 0) delete state.drafts[draftKey];
104738
+ rememberDraft(state);
104739
+ }
104740
+ if (client2.getActiveTeamId?.()) reconcileTuiTeamSenderLabels(state);
104741
+ state.status = status;
104742
+ render();
104743
+ };
104744
+ let resolved;
104745
+ try {
104746
+ resolved = !params.questionAnswer && params.modelShell ? await params.modelShell.consumeMention(params.message, true) : { message: params.message, blocked: false };
104747
+ } catch (error) {
104748
+ restorePreparation(error instanceof Error ? error.message : String(error));
104749
+ return;
104750
+ }
104751
+ if (!ownsPreparation()) return;
104752
+ if (resolved.blocked) {
104753
+ restorePreparation(null);
104754
+ return;
104755
+ }
104264
104756
  const message = resolved.message.trim();
104265
- if (!message) return;
104757
+ if (!message) {
104758
+ restorePreparation(null);
104759
+ return;
104760
+ }
104761
+ userMessage.content = message;
104266
104762
  client2.clearInteractiveChatViewer();
104267
- state.isBusy = true;
104268
104763
  state.status = "Preparing message\u2026";
104269
104764
  render();
104270
104765
  if (!ownsPreparation()) return;
@@ -104272,15 +104767,7 @@ async function sendTuiMessage(params) {
104272
104767
  try {
104273
104768
  prepared = params.questionAnswer ? { message, preparedEmbeds: [], displayNames: [] } : await prepareTuiMessage(client2, message);
104274
104769
  } 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
- }
104770
+ restorePreparation(error instanceof Error ? error.message : String(error));
104284
104771
  return;
104285
104772
  }
104286
104773
  if (!ownsPreparation()) return;
@@ -104288,34 +104775,16 @@ async function sendTuiMessage(params) {
104288
104775
  const waitForAi = !client2.hasSession() || shouldWaitForTeamAi(prepared.message, sendTeamId);
104289
104776
  const modelSelection = waitForAi && !params.questionAnswer && isTuiAiComposer(state) && client2.hasSession() ? params.modelShell?.selectionForSend() ?? null : "auto";
104290
104777
  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
- }
104778
+ restorePreparation("Model selection is loading. Retry or choose Auto before sending.");
104300
104779
  return;
104301
104780
  }
104302
104781
  const separator = modelSelection.indexOf("/");
104303
104782
  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
104783
  const existingChatId = sourceExample ? null : state.activeChatId;
104311
104784
  const chatId = existingChatId ?? randomUUID21();
104312
104785
  let ownedMessages = messages, ownedChatId = chatId;
104313
104786
  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) }));
104787
+ 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
104788
  state.activeChatId = chatId;
104320
104789
  state.headerState = existingChatId ? "ready" : "loading";
104321
104790
  state.headerError = null;
@@ -104324,8 +104793,8 @@ async function sendTuiMessage(params) {
104324
104793
  state.screen = "chat";
104325
104794
  state.aiTaskId = null;
104326
104795
  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);
104796
+ userMessage.content = prepared.message;
104797
+ userMessage.embedIds = prepared.preparedEmbeds.map((embed) => embed.embedId);
104329
104798
  const privacyCallbacks = {
104330
104799
  onPrivacyPrepared: (safe2) => {
104331
104800
  if (ownsSend()) {
@@ -104340,11 +104809,18 @@ async function sendTuiMessage(params) {
104340
104809
  }
104341
104810
  }
104342
104811
  };
104343
- const assistantMessage = waitForAi ? { role: "assistant", content: "", title: "Assistant" } : null;
104344
- if (assistantMessage) state.messages.push(assistantMessage);
104812
+ const assistantMessage = waitForAi ? { id: randomUUID21(), role: "assistant", content: "", title: "Assistant" } : null;
104813
+ state.streamingMessage = assistantMessage;
104814
+ if (assistantMessage) state.streamingPhase = 0;
104345
104815
  state.isAwaitingAi = waitForAi;
104816
+ const showAssistant = (content) => {
104817
+ if (!assistantMessage || !ownsSend() || !content) return;
104818
+ assistantMessage.content = content;
104819
+ if (!messages.includes(assistantMessage)) messages.push(assistantMessage);
104820
+ };
104346
104821
  render();
104347
104822
  let questionSendError = null;
104823
+ let accepted = false;
104348
104824
  try {
104349
104825
  if (!client2.hasSession()) {
104350
104826
  const result = await client2.sendAnonymousMessage({
@@ -104352,7 +104828,8 @@ async function sendTuiMessage(params) {
104352
104828
  ...privacyCallbacks,
104353
104829
  messageHistory: anonymousHistory
104354
104830
  });
104355
- if (assistantMessage) assistantMessage.content = result.assistant;
104831
+ accepted = true;
104832
+ showAssistant(result.assistant);
104356
104833
  if (ownsSend()) {
104357
104834
  ownedChatId = result.chatId;
104358
104835
  state.activeChatId = result.chatId;
@@ -104385,22 +104862,42 @@ async function sendTuiMessage(params) {
104385
104862
  onStream: (event) => {
104386
104863
  if (!ownsSend()) return;
104387
104864
  if (event.taskId) state.aiTaskId = event.taskId;
104865
+ if (assistantMessage && event.thinkingContent !== void 0) assistantMessage.thinkingContent = event.thinkingContent;
104866
+ if (assistantMessage && event.thinkingActive !== void 0) assistantMessage.thinkingActive = event.thinkingActive;
104867
+ if (assistantMessage && event.modelName) assistantMessage.modelName = event.modelName;
104868
+ if (assistantMessage && event.category) {
104869
+ state.activeChat = {
104870
+ id: chatId,
104871
+ shortId: chatId.slice(0, 8),
104872
+ title: state.activeChat?.title ?? null,
104873
+ summary: state.activeChat?.summary ?? null,
104874
+ updatedAt: null,
104875
+ createdAt: state.activeChat?.createdAt ?? Math.floor(Date.now() / 1e3),
104876
+ category: event.category,
104877
+ mateName: null
104878
+ };
104879
+ }
104388
104880
  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();
104881
+ showAssistant(event.content);
104392
104882
  }
104883
+ render();
104393
104884
  }
104394
104885
  });
104395
- if (assistantMessage) assistantMessage.content = result.assistant;
104886
+ accepted = true;
104887
+ showAssistant(result.assistant);
104396
104888
  if (ownsSend()) {
104397
104889
  ownedChatId = result.chatId;
104398
104890
  state.activeChatId = result.chatId;
104399
104891
  state.followUpSuggestions = result.followUpSuggestions ?? [];
104400
104892
  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
- });
104893
+ if (!existingChatId && params.modelShell) {
104894
+ void Promise.resolve().then(() => ownsSend() ? params.modelShell?.persistCreatedChat(result.chatId, modelSelection) : void 0).catch(() => {
104895
+ });
104896
+ }
104897
+ if (state.screen === "chat" && typeof client2.setInteractiveChatViewer === "function") {
104898
+ void Promise.resolve().then(() => ownsSend() && state.screen === "chat" ? client2.setInteractiveChatViewer(result.chatId) : void 0).catch(() => {
104899
+ });
104900
+ }
104404
104901
  if (result.mateName && assistantMessage) assistantMessage.title = result.mateName;
104405
104902
  if (typeof client2.getChatMetadata === "function") {
104406
104903
  const completedLength = messages.length;
@@ -104425,13 +104922,14 @@ async function sendTuiMessage(params) {
104425
104922
  state.headerState = "ready";
104426
104923
  }
104427
104924
  } catch (error) {
104925
+ if (accepted) return;
104428
104926
  if (params.questionAnswer) {
104429
104927
  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
104928
  if (ownsSend()) {
104929
+ for (const optimistic of [userMessage, assistantMessage].filter((item) => item !== null)) {
104930
+ const index = messages.indexOf(optimistic);
104931
+ if (index >= 0) messages.splice(index, 1);
104932
+ }
104435
104933
  state.messages = previousMessages;
104436
104934
  state.activeChatId = existingChatId;
104437
104935
  state.activeChat = previousActiveChat;
@@ -104441,14 +104939,14 @@ async function sendTuiMessage(params) {
104441
104939
  state.screen = sourceExample ? "example" : "chat";
104442
104940
  state.status = null;
104443
104941
  }
104444
- } else if (assistantMessage) {
104942
+ } else if (assistantMessage && ownsSend()) {
104445
104943
  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
- }
104944
+ showAssistant(error instanceof Error ? error.message : String(error));
104945
+ state.headerState = "error";
104946
+ state.headerError = /credit/i.test(assistantMessage.content) ? "Not enough credits" : assistantMessage.content;
104451
104947
  } else if (ownsSend()) {
104948
+ const index = messages.indexOf(userMessage);
104949
+ if (index >= 0) messages.splice(index, 1);
104452
104950
  state.messages = previousMessages;
104453
104951
  state.activeChatId = existingChatId;
104454
104952
  state.activeChat = previousActiveChat;
@@ -104456,9 +104954,11 @@ async function sendTuiMessage(params) {
104456
104954
  ownedChatId = existingChatId;
104457
104955
  state.headerState = previousHeaderState;
104458
104956
  state.screen = previousScreen;
104459
- state.input = message;
104460
- state.inputCursor = null;
104461
- rememberDraft(state);
104957
+ if (state.input === "" && state.drafts[draftKey] === "") {
104958
+ state.input = message;
104959
+ state.inputCursor = null;
104960
+ rememberDraft(state);
104961
+ }
104462
104962
  state.status = error instanceof Error ? error.message : String(error);
104463
104963
  }
104464
104964
  } finally {
@@ -104466,6 +104966,7 @@ async function sendTuiMessage(params) {
104466
104966
  state.isBusy = false;
104467
104967
  if (sendTeamId) reconcileTuiTeamSenderLabels(state);
104468
104968
  state.isAwaitingAi = false;
104969
+ state.streamingMessage = null;
104469
104970
  state.projectFocusPending = null;
104470
104971
  state.aiTaskId = null;
104471
104972
  registerChatEmbedAliases(state);
@@ -105011,7 +105512,7 @@ async function flushTaskCommands(client2, teamId, root = resolveStateDir(), onNo
105011
105512
  await taskMutationStore(scope2, root).deliver(command.id, async () => {
105012
105513
  if (operation.kind === "create") {
105013
105514
  const linked = operation.link_to_chat !== false;
105014
- const owner = linked ? await readCodexThread(command.thread) : void 0;
105515
+ const owner2 = linked ? await readCodexThread(command.thread) : void 0;
105015
105516
  const input = await buildCreateUserTaskInput(master, {
105016
105517
  taskId: command.task_id,
105017
105518
  title: operation.title ?? "",
@@ -105019,7 +105520,7 @@ async function flushTaskCommands(client2, teamId, root = resolveStateDir(), onNo
105019
105520
  projectIds: [command.project_id],
105020
105521
  status: operation.status,
105021
105522
  assign: linked ? "codex" : "user",
105022
- externalChat: owner ? { provider: "codex", id: owner.id, title: owner.title } : void 0
105523
+ externalChat: owner2 ? { provider: "codex", id: owner2.id, title: owner2.title } : void 0
105023
105524
  });
105024
105525
  return { kind: "create", taskId: command.task_id, input, creator: linked ? "codex" : void 0 };
105025
105526
  }
@@ -111238,8 +111739,8 @@ async function handleTasks(client2, subcommand, rest, flags, redactor) {
111238
111739
  }
111239
111740
  if (process.env.CODEX_THREAD_ID && (task.externalChat?.provider !== "codex" || task.externalChat.id !== process.env.CODEX_THREAD_ID)) {
111240
111741
  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 }));
111742
+ const owner2 = await client2.getChatMetadata(task.primaryChatId, scope2);
111743
+ throw new Error(taskOwnerConflict({ provider: "openmates", id: owner2.id, title: owner2.title }));
111243
111744
  }
111244
111745
  throw new Error(taskOwnerConflict(task.externalChat));
111245
111746
  }
@@ -111255,8 +111756,8 @@ async function handleTasks(client2, subcommand, rest, flags, redactor) {
111255
111756
  if (subcommand === "connect") {
111256
111757
  if (typeof flags.thread !== "string") throw new Error("Usage: openmates tasks connect <task-id> --thread <codex-thread-uuid>");
111257
111758
  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 }));
111759
+ const owner2 = await client2.getChatMetadata(task.primaryChatId, scope2);
111760
+ throw new Error(taskOwnerConflict({ provider: "openmates", id: owner2.id, title: owner2.title }));
111260
111761
  }
111261
111762
  if (task.externalChat) {
111262
111763
  if (task.externalChat.provider === "codex" && task.externalChat.id === flags.thread) {
@@ -120628,6 +121129,7 @@ async function sendMessageStreaming(client2, params, redactor) {
120628
121129
  const onStream = (event) => {
120629
121130
  if (params.json) return;
120630
121131
  if (event.kind === "typing") {
121132
+ if (event.thinkingContent) return;
120631
121133
  const mateName = event.category && MATE_NAMES[event.category] ? MATE_NAMES[event.category] : "Mate";
120632
121134
  process.stdout.write(`\x1B[2m${mateName} is typing...\x1B[0m`);
120633
121135
  typingShown = true;
@@ -121058,6 +121560,10 @@ ${renderRemoteCommandReview(review)}
121058
121560
  const acceptedTaskProposals = params.acceptTaskProposals === true && result.status === "completed" ? await acceptChatTaskProposals(client2, result.chatId, result.taskProposals, `${finalMessage}
121059
121561
 
121060
121562
  ${result.assistant}`) : [];
121563
+ if (result.status === "queued") {
121564
+ if (!params.json) process.stderr.write("Message queued for this chat. It will run after the active response finishes.\n");
121565
+ return { ...result, acceptedTaskProposals: [] };
121566
+ }
121061
121567
  if (result.status === "waiting_for_user") {
121062
121568
  const question = parseInteractiveQuestionBlock(result.assistant);
121063
121569
  if (params.json && question) {