zidane 7.0.20 → 7.0.23

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.
Files changed (81) hide show
  1. package/dist/acp-cli.js +2 -2
  2. package/dist/acp.d.ts +1 -1
  3. package/dist/agent.d.ts +1 -1
  4. package/dist/{anthropic-DATE-m9f.js → anthropic-BSbDiW0J.js} +34 -4
  5. package/dist/anthropic-BSbDiW0J.js.map +1 -0
  6. package/dist/{approvals-CzrFKdu-.js → approvals-Deg8JhMI.js} +209 -67
  7. package/dist/approvals-Deg8JhMI.js.map +1 -0
  8. package/dist/{auth-DD4KYv1w.js → auth-Nq-tSMb9.js} +52 -34
  9. package/dist/{auth-DD4KYv1w.js.map → auth-Nq-tSMb9.js.map} +1 -1
  10. package/dist/chat/pure.d.ts +3 -3
  11. package/dist/chat.d.ts +5 -5
  12. package/dist/chat.js +2 -2
  13. package/dist/contexts/daytona.d.ts +1 -1
  14. package/dist/contexts/e2b.d.ts +1 -1
  15. package/dist/daemon-cli.js +1 -1
  16. package/dist/eval.d.ts +1 -1
  17. package/dist/extensions.d.ts +1 -1
  18. package/dist/headless.d.ts +1 -1
  19. package/dist/{index-CvGkUoNk.d.ts → index-C4mY0oKE.d.ts} +3 -21
  20. package/dist/index-C4mY0oKE.d.ts.map +1 -0
  21. package/dist/{index-CGjexO2a.d.ts → index-DQn4tuAA.d.ts} +7 -5
  22. package/dist/index-DQn4tuAA.d.ts.map +1 -0
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +3 -3
  25. package/dist/{ipc-gce-02nV.js → ipc-DlfrDGKv.js} +2 -2
  26. package/dist/{ipc-gce-02nV.js.map → ipc-DlfrDGKv.js.map} +1 -1
  27. package/dist/ipc.d.ts +5 -5
  28. package/dist/ipc.d.ts.map +1 -1
  29. package/dist/ipc.js +3 -3
  30. package/dist/mcp.d.ts +1 -1
  31. package/dist/{oauth-D-LZ4ic-.js → oauth-CmtlmCzi.js} +142 -210
  32. package/dist/oauth-CmtlmCzi.js.map +1 -0
  33. package/dist/output/stream-json.d.ts +1 -1
  34. package/dist/output/terminal.d.ts +1 -1
  35. package/dist/plugins.d.ts +1 -1
  36. package/dist/presets.d.ts +1 -1
  37. package/dist/providers/anthropic.d.ts +1 -1
  38. package/dist/providers/anthropic.js +1 -1
  39. package/dist/providers/arcee.d.ts +1 -1
  40. package/dist/providers/baseten.d.ts +1 -1
  41. package/dist/providers/cerebras.d.ts +1 -1
  42. package/dist/providers/local.d.ts +1 -1
  43. package/dist/providers/openai-compat.d.ts +1 -1
  44. package/dist/providers/openai.d.ts +1 -1
  45. package/dist/providers/openai.js +1 -1
  46. package/dist/providers/openrouter.d.ts +1 -1
  47. package/dist/providers/xai.d.ts +1 -1
  48. package/dist/providers/xai.js +1 -1
  49. package/dist/{providers-Bjdvmpfu.js → providers-ZucqgwU7.js} +2 -2
  50. package/dist/{providers-Bjdvmpfu.js.map → providers-ZucqgwU7.js.map} +1 -1
  51. package/dist/providers.d.ts +1 -1
  52. package/dist/providers.js +3 -3
  53. package/dist/restate.d.ts +1 -1
  54. package/dist/{server-C3FOQvIu.js → server-DW-sKF3g.js} +19 -6
  55. package/dist/server-DW-sKF3g.js.map +1 -0
  56. package/dist/session/sqlite.d.ts +1 -1
  57. package/dist/session.d.ts +1 -1
  58. package/dist/skills.d.ts +1 -1
  59. package/dist/{tool-formatters-BTNnkola.d.ts → tool-formatters-GZHxP824.d.ts} +2 -2
  60. package/dist/{tool-formatters-BTNnkola.d.ts.map → tool-formatters-GZHxP824.d.ts.map} +1 -1
  61. package/dist/tools/fetch-url.d.ts +1 -1
  62. package/dist/tools/web-search.d.ts +1 -1
  63. package/dist/tools.d.ts +1 -1
  64. package/dist/{transcript-anchors-DzY3KZ54.d.ts → transcript-anchors-CY50AXdF.d.ts} +2 -2
  65. package/dist/{transcript-anchors-DzY3KZ54.d.ts.map → transcript-anchors-CY50AXdF.d.ts.map} +1 -1
  66. package/dist/tui.d.ts +3 -3
  67. package/dist/tui.d.ts.map +1 -1
  68. package/dist/tui.js +506 -379
  69. package/dist/tui.js.map +1 -1
  70. package/dist/{turn-operations-DRED3NeH.d.ts → turn-operations-CAmNETlu.d.ts} +3 -3
  71. package/dist/{turn-operations-DRED3NeH.d.ts.map → turn-operations-CAmNETlu.d.ts.map} +1 -1
  72. package/dist/types.d.ts +1 -1
  73. package/docs/CHAT.md +8 -0
  74. package/docs/IPC.md +4 -2
  75. package/package.json +1 -1
  76. package/dist/anthropic-DATE-m9f.js.map +0 -1
  77. package/dist/approvals-CzrFKdu-.js.map +0 -1
  78. package/dist/index-CGjexO2a.d.ts.map +0 -1
  79. package/dist/index-CvGkUoNk.d.ts.map +0 -1
  80. package/dist/oauth-D-LZ4ic-.js.map +0 -1
  81. package/dist/server-C3FOQvIu.js.map +0 -1
package/dist/tui.js CHANGED
@@ -20,9 +20,9 @@ import { s as resolveCompletionSubmit } from "./completion-core-BRo86R_L.js";
20
20
  import { n as JsonRpcRemoteError, t as JsonRpcConnectionClosedError } from "./json-rpc-rSWMUh9b.js";
21
21
  import { $ as reorderKeyed, $t as formatTokenCount, A as chunkMarkdownForRender, B as streamingLabel, C as usageTone, Ct as retagUserPromptEvents, Dt as streamEventSearchText, E as filterModelCatalog, Et as selectionAfterTurnRemoval, F as stripHtmlCommentsOutsideCode, G as chainPositionOf, Gt as shortSessionId, H as DAEMON_HEALTH_STYLE, Ht as sessionEventExcerpt, I as cacheBreakChip, It as isMainSession, J as filterChainFallbacks, Jt as fitRunReceiptCells, K as chainsEqual, Kt as truncateTitle, L as compactingLabel, M as codeTokenHasClosingFence, Mt as SESSION_EVENT_STATUS_STYLE, N as normalizeMarkdownCodeFences, O as parseCustomModelQuery, Ot as turnSelectionOwnership, P as splitMarkdownCodeBlocks, Q as providerKeySetChanged, Qt as formatRunReceiptLine, R as liveSessionCost, S as resetLabel, Sn as summarizeOutcomes, T as buildModelCatalog, Tn as createChatsCompletionProvider, Tt as selectableTurnIds, U as buildRunModel, Vt as sessionDirLabel, Wt as sessionEventMeta, Y as pickChainPrimary, Yt as formatCostUsd, Zt as formatRunReceiptCells, _ as turnContextSize, _n as mergeApprovalAndBodyOutcomes, _t as isTurnHighlighted, a as TOOL_DISPLAY, an as buildUnifiedDiff, at as createSkillsCompletionProvider, b as formatUsagePercent, bn as rewriteMultiEditHeader, bt as isVisibleDuringSearch, ct as createFilesCompletionProvider, d as finalizeStreamingMarkdown, dn as previewEditPayload, dt as createCommandsCompletionProvider, et as restoreChainFallbacks, f as finalizeStreamingMarkdownForOwner, ft as blendHsl, g as revealEarlierTranscriptEvents, gt as isEditErrorResult, h as replaceTranscriptTurnEvents, hn as buildEditOutcomesAnnotation, ht as EDIT_TOOL_NAMES, i as turnAsText, in as buildContextualDiff, j as closeDanglingCodeFence, ln as extractEditPayload, mt as BASH_CALL_ID_PREFIX, n as deleteTurnSafely, nn as runReceiptFromRun, nt as toggleChainEntry, o as displayNameFor, on as clipDiffForRender, ot as uniqueSkillNamesFromReferences, p as hiddenEarlierTranscriptCount, pn as summarizeEditPayload, pt as buildLinearRamp, r as truncateTurnsAt, s as formatToolCall, tn as hasRunReceipt, u as capTranscriptEvents, un as filetypeFromPath, v as formatBalance, vn as parseEditOutcomesFromResult, vt as isUserBashEvent, w as splitPromptSegments, wt as retagUserPromptEventsFromRefs, x as mostConstrainedWindow, xn as stripEditOutcomesAnnotation, xt as promptHistoryFromEvents, yn as resolveApprovalForPayload, yt as isVisible } from "./turn-operations-BgqpLlOG.js";
22
22
  import { a as searchHighlightRanges, c as searchSessionHistoryIndexAsync, i as highlightSegments, n as buildSessionSearchIndexAsync, r as filterSessionSearchCatalog, u as searchSessionIndexAsync } from "./history-search-C4bk1Wz_.js";
23
- import { $ as supportsOAuth, $n as tryOpenBrowser, A as useSafeModeQueue, An as createDiscoverySlot, At as makeRequestInteraction, Ci as sumRunCosts, Ct as InteractionsProvider, Di as updateToolEventOutcomes, Dr as accentColor, Dt as createInteractionTools, Ei as toolResultText, Et as createAutomaticInteractionResponse, Fn as useConfig, G as usageChecksOptedOut, Gn as mcpsWritePath, Gr as setTodosForRun, H as fetchAllProviderUsage, Hn as countClaudeSessionFiles, Hr as pruneTodosByRun, In as resolveConfig, Ir as applyTodosSnapshot, J as fetchOAuthRedirect, Jn as buildMcpServers, Jt as buildPluginContributions, Kn as removeMcpServer, Kr as todoProgressLabel, Ln as resolveStoragePaths, M as addToSafelist, Mn as useDiscovery, N as getSafelist, Nn as useDiscoveryOptional, Nt as useInteractionsActions, O as SafeModeProvider, Oi as usageByModel, On as parseEditorCommand, P as isOnSafelist, Pn as ConfigProvider, Pr as TODO_STATUS_GLYPHS, Pt as useInteractionsQueue, Q as runOAuthLogin, R as suggestSafelistEntries, Si as stripSpawnTokensLine, St as HOST_INTERACTION_ID_PREFIX, Ti as toolCallPreview, Tt as buildResumedToolResultsTurn, U as fetchProviderUsage, Un as hasClaudeCodeSetup, Ut as createBuiltinCommands, V as clearProviderUsageCache, Vn as useCompletion, Vr as pickActiveRunId, W as providerUsageSupported, Wn as importFromClaudeCode, Wr as selectActiveTodos, Wt as createMainSession, X as redirectUriFromAuthUrl, Xt as parsePluginScanPathEnv, Yn as dedupMcpServersByName, Yt as discoverProjectPlugins, Z as oauthUsesManualCodePaste, Zn as discoverProjectMcps, Zt as attachMcpWarnLog, _ as buildSpawnEndEvent, _i as loadState, _r as groupArtifacts, _t as useMcpAuthState, ai as buildPlanSystem, at as subscribeMcpToolsCache, b as defaultSkillScanPaths, bn as resolveTheme, bt as ASK_USER_TOOL, c as useSelectStyle, ci as createChatTurnsReadTool, cn as DEFAULT_SETTINGS, d as useTheme, dn as SETTINGS_INPUTS, dt as parentServerName, en as EMPTY_HINTS, er as tryRevealPath, f as discoverSystemMd, fi as deriveSessionTitle, fn as SETTINGS_TOGGLES, ft as createFileMcpCredentialStore, g as useStreamBuffer, gi as listSessionMeta, gn as useSettings, gr as chatAutoCompactBehavior, gt as useMcpAuthDispatch, h as userSystemMdPath, hi as lastContextSizeFromTurns, ht as McpAuthProvider, ii as buildBuildSystem, in as buildHints, ir as useUpdateCheck, jn as DiscoveryProvider, jt as pendingInteractionsFromTurns, k as useSafeModeActions, kn as quoteForShell, kt as isModelInteraction, l as useSurfaces, ln as SETTINGS_CATEGORIES, lt as buildVisibleMcpRows, m as userScopeOverridesLoad, mi as getSessionMeta, mn as clampFps, nn as hintsLength, nr as bootTick, nt as mcpToolsCachePath, o as ThemeProvider, oi as envSection, on as listProjectFiles, p as renderSystemMdSeed, pi as eventsFromTurns, pn as SettingsProvider, q as formatPathForCwd, qn as upsertMcpServer, qr as useActiveTodos, qt as runEndNotice, r as computeTurnAnchors, rn as truncateTrailing, rr as buildUpdateHint, rt as refreshMcpToolsCatalog, s as useColors, si as chatReferenceTokensForPrompt, sn as useEnabledToggleSet, st as useMcpToolToggleMap, tn as clipHintsToWidth, tt as loadMcpToolsCache, ui as compactSummaryEvent, un as SETTINGS_CHOICES, ut as indexOfServerRow, v as buildSpawnStartEvent, vi as marginTopFor, vt as getMcpAuthStatus, w as writeSessionExport, wt as PRESENT_PLAN_TOOL, x as discoverProjectSkills, xi as serverToolResultSummary, y as buildSkillsConfig, yi as noticeTurnContent, yn as resolveChipColor, yr as discoverAgentsMd, zn as createArtifactsCompletionProvider } from "./approvals-CzrFKdu-.js";
24
- import { E as renderDefaultCallbackPage, T as restoreModelOptions, b as modelSupportsReasoning, c as setPreferredCredential, g as getContextWindow, h as enabledModelOptions, l as setProviderCredential, n as applyApiKeyEnv, t as detectAuth, w as piIdOf, y as modelOptionsFor } from "./auth-DD4KYv1w.js";
25
- import { a as connectIpcClient, d as defaultSocketPath, m as selfDaemonSpawnCommand, o as connectOrSpawnDaemon, p as probeSocket } from "./server-C3FOQvIu.js";
23
+ import { $ as supportsOAuth, $n as tryOpenBrowser, A as useSafeModeQueue, An as createDiscoverySlot, At as makeRequestInteraction, Ci as sumRunCosts, Ct as InteractionsProvider, Di as updateToolEventOutcomes, Dr as accentColor, Dt as createInteractionTools, Ei as toolResultText, Et as createAutomaticInteractionResponse, Fn as useConfig, G as usageChecksOptedOut, Gn as mcpsWritePath, Gr as setTodosForRun, H as fetchAllProviderUsage, Hn as countClaudeSessionFiles, Hr as pruneTodosByRun, In as resolveConfig, Ir as applyTodosSnapshot, J as fetchOAuthRedirect, Jn as buildMcpServers, Jt as buildPluginContributions, Kn as removeMcpServer, Kr as todoProgressLabel, Ln as resolveStoragePaths, M as addToSafelist, Mn as useDiscovery, N as getSafelist, Nn as useDiscoveryOptional, Nt as useInteractionsActions, O as SafeModeProvider, Oi as usageByModel, On as parseEditorCommand, P as isOnSafelist, Pn as ConfigProvider, Pr as TODO_STATUS_GLYPHS, Pt as useInteractionsQueue, Q as runOAuthLogin, R as suggestSafelistEntries, Si as stripSpawnTokensLine, St as HOST_INTERACTION_ID_PREFIX, Ti as toolCallPreview, Tt as buildResumedToolResultsTurn, U as fetchProviderUsage, Un as hasClaudeCodeSetup, Ut as createBuiltinCommands, V as clearProviderUsageCache, Vn as useCompletion, Vr as pickActiveRunId, W as providerUsageSupported, Wn as importFromClaudeCode, Wr as selectActiveTodos, Wt as createMainSession, X as redirectUriFromAuthUrl, Xt as parsePluginScanPathEnv, Yn as dedupMcpServersByName, Yt as discoverProjectPlugins, Z as oauthUsesManualCodePaste, Zn as discoverProjectMcps, Zt as attachMcpWarnLog, _ as buildSpawnEndEvent, _i as loadState, _r as groupArtifacts, _t as useMcpAuthState, ai as buildPlanSystem, at as subscribeMcpToolsCache, b as defaultSkillScanPaths, bn as resolveTheme, bt as ASK_USER_TOOL, c as useSelectStyle, ci as createChatTurnsReadTool, cn as DEFAULT_SETTINGS, d as useTheme, dn as SETTINGS_INPUTS, dt as parentServerName, en as EMPTY_HINTS, er as tryRevealPath, f as discoverSystemMd, fi as deriveSessionTitle, fn as SETTINGS_TOGGLES, ft as createFileMcpCredentialStore, g as useStreamBuffer, gi as listSessionMeta, gn as useSettings, gr as chatAutoCompactBehavior, gt as useMcpAuthDispatch, h as userSystemMdPath, hi as lastContextSizeFromTurns, ht as McpAuthProvider, ii as buildBuildSystem, in as buildHints, ir as useUpdateCheck, jn as DiscoveryProvider, jt as pendingInteractionsFromTurns, k as useSafeModeActions, kn as quoteForShell, kt as isModelInteraction, l as useSurfaces, ln as SETTINGS_CATEGORIES, lt as buildVisibleMcpRows, m as userScopeOverridesLoad, mi as getSessionMeta, mn as clampFps, nn as hintsLength, nr as bootTick, nt as mcpToolsCachePath, o as ThemeProvider, oi as envSection, on as listProjectFiles, p as renderSystemMdSeed, pi as eventsFromTurns, pn as SettingsProvider, q as formatPathForCwd, qn as upsertMcpServer, qr as useActiveTodos, qt as runEndNotice, r as computeTurnAnchors, rn as truncateTrailing, rr as buildUpdateHint, rt as refreshMcpToolsCatalog, s as useColors, si as chatReferenceTokensForPrompt, sn as useEnabledToggleSet, st as useMcpToolToggleMap, tn as clipHintsToWidth, tt as loadMcpToolsCache, ui as compactSummaryEvent, un as SETTINGS_CHOICES, ut as indexOfServerRow, v as buildSpawnStartEvent, vi as marginTopFor, vt as getMcpAuthStatus, w as writeSessionExport, wt as PRESENT_PLAN_TOOL, x as discoverProjectSkills, xi as serverToolResultSummary, y as buildSkillsConfig, yi as noticeTurnContent, yn as resolveChipColor, yr as discoverAgentsMd, zn as createArtifactsCompletionProvider } from "./approvals-Deg8JhMI.js";
24
+ import { E as renderDefaultCallbackPage, T as restoreModelOptions, b as modelSupportsReasoning, c as setPreferredCredential, g as getContextWindow, h as enabledModelOptions, l as setProviderCredential, n as applyApiKeyEnv, t as detectAuth, w as piIdOf, y as modelOptionsFor } from "./auth-Nq-tSMb9.js";
25
+ import { a as connectIpcClient, d as defaultSocketPath, m as selfDaemonSpawnCommand, o as connectOrSpawnDaemon, p as probeSocket } from "./server-DW-sKF3g.js";
26
26
  import { createDockerContext } from "./contexts/docker.js";
27
27
  import { createTuiStore } from "./session/sqlite.js";
28
28
  import { _ as buildVisibleAuthRows, a as editTodo, b as parentProviderKey, c as NOTICE_GLYPH, f as validateMcpForm, g as instructionPreviewLines, h as instructionOriginBadge, i as cycleTodoStatus, l as MCP_TRANSPORTS, m as buildVisibleInstructionRows, n as buildTodoRetryMessage, r as clearTodos, s as removeTodo, t as addTodo, v as indexOfProviderRow, x as selectableSources } from "./todo-edits-Dw2oO2ON.js";
@@ -7659,7 +7659,7 @@ function OptionRow({ option, isEnabled, isFocused, highlightBg }) {
7659
7659
  * away from. Without it such an entry is invisible and re-persists forever.
7660
7660
  */
7661
7661
  /** Visible rows in the windowed list. Keeps the modal a stable size on long catalogs. */
7662
- const VISIBLE_ROWS = 10;
7662
+ const VISIBLE_ROWS = 14;
7663
7663
  /**
7664
7664
  * Promote selected entries in SELECTION order, then append every unselected
7665
7665
  * row in its original catalog order. Pure/non-mutating and collision-safe:
@@ -7835,7 +7835,7 @@ function ModelPickerModal({ providers, modelsFor, current, onPick, chain, onAppl
7835
7835
  if (providers.length === 0) return /* @__PURE__ */ jsx$1(EmptyProvidersState, {});
7836
7836
  return /* @__PURE__ */ jsxs(Modal, {
7837
7837
  title: chainMode ? "select models · order is fallback priority" : "select model",
7838
- maxWidth: 100,
7838
+ maxWidth: 140,
7839
7839
  onClose: handleClose,
7840
7840
  children: [
7841
7841
  /* @__PURE__ */ jsx$1("box", {
@@ -8210,12 +8210,12 @@ const METHOD_NOT_FOUND = -32601;
8210
8210
  * Version-skewed daemons exist by construction (a daemon process is frozen
8211
8211
  * at spawn time while windows come from newer builds mid-development).
8212
8212
  */
8213
- async function optional(call) {
8213
+ async function optional(call, allowClosed = true) {
8214
8214
  try {
8215
8215
  return await call();
8216
8216
  } catch (err) {
8217
8217
  if (err instanceof JsonRpcRemoteError && err.code === METHOD_NOT_FOUND) return null;
8218
- if (err instanceof JsonRpcConnectionClosedError) return null;
8218
+ if (allowClosed && err instanceof JsonRpcConnectionClosedError) return null;
8219
8219
  throw err;
8220
8220
  }
8221
8221
  }
@@ -8227,12 +8227,15 @@ async function connectDaemonSession(options) {
8227
8227
  spawnCommand: options.spawnCommand ?? selfDaemonSpawnCommand(),
8228
8228
  ...options.appVersion ? { appVersion: options.appVersion } : {},
8229
8229
  onClose: () => {
8230
- if (!disposed) callbacks.onDisconnect?.();
8230
+ if (!disposed) {
8231
+ disposed = true;
8232
+ callbacks.onDisconnect?.();
8233
+ }
8231
8234
  }
8232
8235
  });
8233
8236
  let running = false;
8234
- /** A `run:start` observed via signal wins over the initial RPC probe. */
8235
- let sawRunStart = false;
8237
+ /** Either live run-state signal wins over the initial RPC probe. */
8238
+ let sawRunState = false;
8236
8239
  /** Sequence for `suggest.resolve` — see `resolveSuggestion`. */
8237
8240
  let suggestSeq = 0;
8238
8241
  /**
@@ -8275,6 +8278,7 @@ async function connectDaemonSession(options) {
8275
8278
  });
8276
8279
  };
8277
8280
  const unsubscribe = client.onSignal((signal) => {
8281
+ if (disposed) return;
8278
8282
  switch (signal.kind) {
8279
8283
  case "approval:request":
8280
8284
  surfaceApproval(signal.request);
@@ -8341,10 +8345,11 @@ async function connectDaemonSession(options) {
8341
8345
  break;
8342
8346
  case "run:start":
8343
8347
  running = true;
8344
- sawRunStart = true;
8348
+ sawRunState = true;
8345
8349
  callbacks.onRunState(true);
8346
8350
  break;
8347
8351
  case "run:end":
8352
+ sawRunState = true;
8348
8353
  running = false;
8349
8354
  callbacks.onRunState(false, {
8350
8355
  ok: signal.ok,
@@ -8439,25 +8444,34 @@ async function connectDaemonSession(options) {
8439
8444
  default: break;
8440
8445
  }
8441
8446
  });
8447
+ const bootstrapRequest = async (method, params) => {
8448
+ if (disposed) throw new JsonRpcConnectionClosedError();
8449
+ const result = await client.request(method, params);
8450
+ if (disposed) throw new JsonRpcConnectionClosedError();
8451
+ return result;
8452
+ };
8442
8453
  try {
8443
- const initialEvents = await client.request("sessions.loadEvents", { sessionId });
8454
+ const initialEvents = await bootstrapRequest("sessions.loadEvents", { sessionId });
8444
8455
  callbacks.onInitialTranscript(initialEvents);
8445
8456
  transcriptReady = true;
8446
8457
  for (const event of bufferedEvents.splice(0)) callbacks.onStreamEvent(event);
8447
- if (!sawRunStart) {
8448
- running = await client.request("chat.isRunning", { sessionId });
8449
- if (running) callbacks.onRunState(true);
8458
+ if (!sawRunState) {
8459
+ const snapshot = await bootstrapRequest("chat.isRunning", { sessionId });
8460
+ if (!sawRunState) {
8461
+ running = snapshot;
8462
+ callbacks.onRunState(running);
8463
+ }
8450
8464
  }
8451
- for (const request of await client.request("approvals.listPending", {})) surfaceApproval(request);
8452
- await client.request("interactions.rehydrate", { sessionId });
8453
- for (const pending of await client.request("interactions.list", { sessionId })) surfaceInteraction(pending.request);
8465
+ for (const request of await bootstrapRequest("approvals.listPending", {})) surfaceApproval(request);
8466
+ await bootstrapRequest("interactions.rehydrate", { sessionId });
8467
+ for (const pending of await bootstrapRequest("interactions.list", { sessionId })) surfaceInteraction(pending.request);
8454
8468
  client.notify("presence.watch", { sessionId });
8455
8469
  const [presence, queued, draft, systemMd, todos] = await Promise.all([
8456
- optional(() => client.request("presence.list", {})),
8457
- optional(() => client.request("queue.list", { sessionId })),
8458
- optional(() => client.request("drafts.get", { sessionId })),
8459
- optional(() => client.request("sessions.systemMd", { sessionId })),
8460
- optional(() => client.request("todos.get", { sessionId }))
8470
+ optional(() => bootstrapRequest("presence.list", {}), false),
8471
+ optional(() => bootstrapRequest("queue.list", { sessionId }), false),
8472
+ optional(() => bootstrapRequest("drafts.get", { sessionId }), false),
8473
+ optional(() => bootstrapRequest("sessions.systemMd", { sessionId }), false),
8474
+ optional(() => bootstrapRequest("todos.get", { sessionId }), false)
8461
8475
  ]);
8462
8476
  if (presence) callbacks.onPresenceChanged?.(presence);
8463
8477
  if (queued && queued.length > 0) callbacks.onQueueChanged?.(queued);
@@ -8470,6 +8484,7 @@ async function connectDaemonSession(options) {
8470
8484
  callbacks.onSystemMd(systemMd?.file ?? null, systemMd?.shadowed ?? []);
8471
8485
  if (todos) callbacks.onTodosChanged?.(todos);
8472
8486
  } catch (err) {
8487
+ disposed = true;
8473
8488
  unsubscribe();
8474
8489
  client.close();
8475
8490
  throw err;
@@ -8649,6 +8664,89 @@ async function connectDaemonSession(options) {
8649
8664
  };
8650
8665
  }
8651
8666
  //#endregion
8667
+ //#region src/tui/runtime/session-connection.ts
8668
+ function guardConnectionCallbacks(attempt, callbacks) {
8669
+ const guarded = { ...callbacks };
8670
+ for (const key of Object.keys(callbacks)) guarded[key] = attempt.guard(callbacks[key]);
8671
+ return guarded;
8672
+ }
8673
+ /** Owns one activation and its transport attempts; absence of a controller is never local mode. */
8674
+ var SessionConnection = class {
8675
+ schedule;
8676
+ generation = 0;
8677
+ cancelRetry;
8678
+ controller;
8679
+ canSend = false;
8680
+ constructor(schedule = (run, delay) => {
8681
+ const timer = setTimeout(run, delay);
8682
+ return () => clearTimeout(timer);
8683
+ }) {
8684
+ this.schedule = schedule;
8685
+ }
8686
+ dispose() {
8687
+ this.generation++;
8688
+ this.canSend = false;
8689
+ this.cancelRetry?.();
8690
+ this.cancelRetry = void 0;
8691
+ const controller = this.controller;
8692
+ this.controller = void 0;
8693
+ controller?.dispose();
8694
+ }
8695
+ begin() {
8696
+ this.dispose();
8697
+ const generation = this.generation;
8698
+ const isCurrent = () => generation === this.generation;
8699
+ return {
8700
+ isCurrent,
8701
+ localReady: () => {
8702
+ if (isCurrent()) this.canSend = true;
8703
+ },
8704
+ connect: async (options) => {
8705
+ let failures = 0;
8706
+ const run = async () => {
8707
+ if (!isCurrent()) return;
8708
+ let live = true;
8709
+ const current = () => isCurrent() && live;
8710
+ const disconnect = (error) => {
8711
+ if (!current()) return;
8712
+ live = false;
8713
+ this.canSend = false;
8714
+ const controller = this.controller;
8715
+ this.controller = void 0;
8716
+ controller?.dispose();
8717
+ options.cleanup?.();
8718
+ options.unavailable(error);
8719
+ this.cancelRetry = this.schedule(() => {
8720
+ this.cancelRetry = void 0;
8721
+ run();
8722
+ }, Math.min(1500 * 2 ** Math.min(failures++, 3), 12e3));
8723
+ };
8724
+ try {
8725
+ const controller = await options.open({
8726
+ isCurrent: current,
8727
+ guard: (callback) => (...args) => {
8728
+ if (current()) callback(...args);
8729
+ },
8730
+ disconnect
8731
+ });
8732
+ if (!current()) {
8733
+ controller.dispose();
8734
+ return;
8735
+ }
8736
+ this.controller = controller;
8737
+ failures = 0;
8738
+ options.ready(controller);
8739
+ this.canSend = true;
8740
+ } catch (error) {
8741
+ disconnect(error);
8742
+ }
8743
+ };
8744
+ await run();
8745
+ }
8746
+ };
8747
+ }
8748
+ };
8749
+ //#endregion
8652
8750
  //#region src/chat/external-editor.ts
8653
8751
  /**
8654
8752
  * External-editor handoff — open a piece of text in the user's `$EDITOR`,
@@ -22504,6 +22602,7 @@ function AppShell() {
22504
22602
  */
22505
22603
  const activateSessionRef = useRef(async () => {});
22506
22604
  const activationGenerationRef = useRef(0);
22605
+ const connectionRef = useRef(new SessionConnection());
22507
22606
  useEffect(() => {
22508
22607
  const daemon = daemonRef.current;
22509
22608
  if (!daemon || !picked) return;
@@ -24303,14 +24402,14 @@ function AppShell() {
24303
24402
  refreshSessionsRef.current = refreshSessions;
24304
24403
  refreshSessionsForArtifactsRef.current = refreshSessions;
24305
24404
  const teardown = useCallback(async () => {
24405
+ const teardownGeneration = ++activationGenerationRef.current;
24306
24406
  try {
24307
- daemonRef.current?.dispose();
24407
+ connectionRef.current.dispose();
24308
24408
  } catch (err) {
24309
24409
  debugLog("teardown: daemon dispose failed", err);
24310
24410
  }
24311
24411
  daemonRef.current = null;
24312
24412
  setDaemonConnected(false);
24313
- activationGenerationRef.current += 1;
24314
24413
  setRemoteDraft(null);
24315
24414
  setRemoteDraftAttachments(null);
24316
24415
  setRemoteSuggest(null);
@@ -24348,10 +24447,12 @@ function AppShell() {
24348
24447
  transcriptRevealHoldRef.current = false;
24349
24448
  stream.reset();
24350
24449
  await agentRef.current?.destroy().catch((err) => debugLog("agent.destroy failed", err));
24450
+ if (activationGenerationRef.current !== teardownGeneration) return;
24351
24451
  agentRef.current = null;
24352
24452
  sessionRef.current = null;
24353
24453
  if (warnLogRef.current) {
24354
24454
  await warnLogRef.current.close().catch((err) => debugLog("teardown: warnLog.close failed", err));
24455
+ if (activationGenerationRef.current !== teardownGeneration) return;
24355
24456
  warnLogRef.current = null;
24356
24457
  }
24357
24458
  setCompacting(false);
@@ -24477,10 +24578,12 @@ function AppShell() {
24477
24578
  }
24478
24579
  }, [stream, autoCompactBehaviorFor]);
24479
24580
  const activateSession = useCallback(async (id, key, opts) => {
24480
- await teardown();
24481
- activationGenerationRef.current += 1;
24482
- const activationGeneration = activationGenerationRef.current;
24581
+ const cleanup = teardown();
24582
+ const activation = connectionRef.current.begin();
24583
+ await cleanup;
24584
+ if (!activation.isCurrent()) return;
24483
24585
  const loaded = id ? await loadSession(store, id) : null;
24586
+ if (!activation.isCurrent()) return;
24484
24587
  const freshProfile = loaded ? null : defaultAgentProfile();
24485
24588
  const session = loaded ?? await createSession({
24486
24589
  store,
@@ -24489,6 +24592,7 @@ function AppShell() {
24489
24592
  ...id ? { id } : {},
24490
24593
  ...opts?.metadata ? { metadata: opts.metadata } : {}
24491
24594
  });
24595
+ if (!activation.isCurrent()) return;
24492
24596
  if (freshProfile && freshProfile.id !== pickedAgentRef.current.id) {
24493
24597
  pickedAgentRef.current = freshProfile;
24494
24598
  setPickedAgent(freshProfile);
@@ -24506,370 +24610,372 @@ function AppShell() {
24506
24610
  stampedLegacyAgent = true;
24507
24611
  }
24508
24612
  }
24509
- if (settingsRef.current.useDaemon && !isDaemonDisabledByEnv()) try {
24510
- agentRef.current = null;
24511
- if (!loaded && typeof session.metadata.cwd !== "string") {
24512
- session.setMeta("cwd", cwdRef.current ?? process.cwd());
24513
- await session.save();
24514
- }
24515
- const activePick = pickedRef.current;
24516
- let seedModelViaRpc = false;
24517
- if (activePick) {
24518
- if (typeof session.metadata.providerKey !== "string") if (loaded) seedModelViaRpc = true;
24519
- else {
24520
- session.setMeta("providerKey", activePick.provider.key);
24521
- session.setMeta("modelId", activePick.model);
24522
- if (activePick.effort) session.setMeta("effort", activePick.effort);
24523
- if (activePick.modelOptions) session.setMeta("modelOptions", activePick.modelOptions);
24524
- await session.save();
24525
- }
24526
- lastPushedModelRef.current = JSON.stringify([
24527
- activePick.provider.key,
24528
- activePick.model,
24529
- activePick.effort ?? null,
24530
- activePick.modelOptions ?? null
24531
- ]);
24532
- } else lastPushedModelRef.current = null;
24533
- const sessionId = session.id;
24534
- const refreshHeaderFromStore = () => {
24535
- store.load(sessionId).then((data) => {
24536
- if (!data || sessionRef.current?.id !== sessionId) return;
24537
- setCurrentSession({
24538
- id: data.id,
24539
- title: deriveSessionTitle(data.turns, data.metadata),
24540
- turnCount: data.turns.length,
24541
- userMessageCount: data.turns.reduce((n, t) => t.role === "user" ? n + 1 : n, 0),
24542
- runCount: data.runs.length,
24543
- updatedAt: data.updatedAt,
24544
- ...data.projectRoot ? { projectRoot: data.projectRoot } : {}
24545
- });
24546
- }).catch(() => void 0);
24547
- };
24548
- let controllerHandle = null;
24549
- const reflectRemoteModel = () => {
24550
- store.load(sessionId).then((data) => {
24551
- if (!data || sessionRef.current?.id !== sessionId) return;
24552
- const providerKey = data.metadata.providerKey;
24553
- const modelId = data.metadata.modelId;
24554
- if (typeof providerKey !== "string" || typeof modelId !== "string") return;
24555
- const auth = availableProvidersRef.current.find((p) => p.key === providerKey);
24556
- if (!auth) return;
24557
- const next = makePicked(auth, modelId);
24558
- if (!next) return;
24559
- const effort = data.metadata.effort;
24560
- const modelOptions = data.metadata.modelOptions;
24561
- const merged = {
24562
- ...next,
24563
- ...typeof effort === "string" ? { effort } : {},
24564
- ...modelOptions && typeof modelOptions === "object" ? { modelOptions } : {}
24565
- };
24566
- const tuple = JSON.stringify([
24567
- merged.provider.key,
24568
- merged.model,
24569
- merged.effort ?? null,
24570
- merged.modelOptions ?? null
24571
- ]);
24572
- if (lastPushedModelRef.current === tuple) return;
24573
- lastPushedModelRef.current = tuple;
24574
- setPicked(merged);
24575
- }).catch(() => void 0);
24576
- };
24577
- const reflectRemoteAgent = () => {
24578
- (store.getMeta ? store.getMeta(sessionId) : store.load(sessionId)).then((meta) => {
24579
- if (!meta || sessionRef.current?.id !== sessionId) return;
24580
- const remote = meta.agentId;
24581
- if (!remote || remote === pickedAgentRef.current.id) return;
24582
- sessionRef.current.setAgentId(remote);
24583
- const profile = liveAgentRegistryRef.current[remote];
24584
- if (!profile) return;
24585
- pickedAgentRef.current = profile;
24586
- setPickedAgent(profile);
24587
- }).catch(() => void 0);
24588
- };
24589
- const resyncTranscript = () => {
24590
- controllerHandle?.fetchTranscript().then(async (canonical) => {
24591
- if (!canonical || sessionRef.current?.id !== sessionId || runningRef.current) return;
24592
- await streamRef.current?.whenDrained();
24593
- if (sessionRef.current?.id !== sessionId || runningRef.current) return;
24594
- streamRef.current?.reset();
24595
- setEvents(capTranscriptEvents(canonical));
24596
- }).catch(() => void 0);
24597
- };
24598
- const controller = await connectDaemonSession({
24599
- sessionId,
24600
- appVersion: "tui",
24601
- callbacks: {
24602
- onRetry: ({ attempt, max, source }) => setRetryInfo({
24603
- attempt,
24604
- max,
24605
- source
24606
- }),
24607
- onRetryCleared: (source) => clearRetryInfo(source),
24608
- onPromptTurns: (refs) => {
24609
- stream.flushAndUpdate((events) => retagUserPromptEventsFromRefs(events, refs));
24610
- },
24611
- onCompactionProgress: (tokens) => setCompactionTokens(tokens),
24612
- onStreamProgress: (progress) => setStreamProgress(progress),
24613
- onModelFallback: (from, to) => {
24614
- setActiveFallback({
24615
- provider: to.provider,
24616
- model: to.model
24617
- });
24618
- stream.appendImmediate({
24619
- kind: "info",
24620
- text: `model fallback: ${from.provider}/${from.model} failed, continuing on ${to.provider}/${to.model}`
24621
- });
24622
- },
24623
- onCacheBreak: (info) => {
24624
- const providerKey = pickedRef.current?.provider.key;
24625
- const apply = (costUsd) => {
24626
- setActiveCacheBreak(costUsd !== void 0 ? {
24627
- ...info,
24628
- costUsd
24629
- } : info);
24630
- };
24631
- if (!providerKey) {
24632
- apply(void 0);
24633
- return;
24634
- }
24635
- estimateRebilledCostUsd(providerKey, info.model, info.rebilledTokens).then(apply).catch(() => apply(void 0));
24636
- },
24637
- onSystemMd: (file) => {
24638
- sessionSystemFileRef.current = file;
24639
- announceProjectSystemOverride();
24640
- },
24641
- onSettingsChanged: (remote) => {
24642
- const local = settingsRef.current;
24643
- const entries = Object.entries(remote).filter(([key, value]) => key in DEFAULT_SETTINGS && JSON.stringify(local[key]) !== JSON.stringify(value));
24644
- if (entries.length === 0) return;
24645
- suppressSettingsPublishRef.current = true;
24646
- for (const [key, value] of entries) setSetting(key, value);
24647
- },
24648
- onSessionsChanged: (op, changedId) => {
24649
- if (op === "update" && changedId === sessionId) {
24650
- refreshHeaderFromStore();
24651
- resyncTranscript();
24652
- reflectRemoteModel();
24653
- reflectRemoteAgent();
24654
- }
24655
- },
24656
- onDraftChanged: (text, shell) => {
24657
- draftSeqRef.current += 1;
24658
- setRemoteDraft({
24659
- seq: draftSeqRef.current,
24660
- text,
24661
- ...shell ? { shell: true } : {}
24662
- });
24663
- },
24664
- onSuggestChanged: ({ basis, caret, suggestion, candidates }) => {
24665
- remoteSuggestSeqRef.current += 1;
24666
- setRemoteSuggest({
24667
- seq: remoteSuggestSeqRef.current,
24668
- basis,
24669
- caret,
24670
- suggestion,
24671
- ...candidates ? { candidates } : {}
24672
- });
24673
- },
24674
- onSearchChanged: ({ active, query }) => {
24675
- setTranscriptSearchMode(active);
24676
- setTranscriptSearchQuery(query);
24677
- if (!active) setSelectedTurnId(null);
24678
- remoteSearchSeqRef.current += 1;
24679
- setRemoteSearchQuery(active ? {
24680
- seq: remoteSearchSeqRef.current,
24681
- query
24682
- } : null);
24683
- },
24684
- onDraftAttachmentsChanged: (attachments) => {
24685
- draftAttachmentsSeqRef.current += 1;
24686
- setRemoteDraftAttachments({
24687
- seq: draftAttachmentsSeqRef.current,
24688
- attachments: attachments.map((att) => ({
24689
- name: att.name ?? "attachment",
24690
- mediaType: att.mediaType,
24691
- content: Buffer.from(att.data, "base64")
24692
- }))
24693
- });
24694
- },
24695
- onSkillsChanged: (active) => {
24696
- setActiveSkillNames(new Set(active));
24697
- },
24698
- onMcpAuth: ({ name, reason }) => {
24699
- dispatchAuthRef.current({
24700
- type: "auth-required",
24701
- name,
24702
- reason: reason === "auto-promoted" ? "auto-promoted" : "no-tokens"
24703
- });
24704
- },
24705
- onHudState: (state) => {
24706
- setRemoteHudState(state);
24707
- },
24708
- onHudNotify: (n) => {
24709
- const allow = enabledExtensionsRef.current;
24710
- if (allow !== void 0 && !allow.includes(n.extension)) return;
24711
- stream.appendImmediate({
24712
- kind: n.tone === "error" ? "error" : "info",
24713
- text: `${n.extension}: ${n.tone === "warn" ? "⚠ " : ""}${n.text}`
24714
- });
24715
- },
24716
- onExtensionSignal: (envelope) => {
24717
- getTuiBus()?.deliver(envelope);
24718
- },
24719
- onTurnEnd: ({ usage, childId }) => {
24720
- if (childId === void 0) setStreamProgress(null);
24721
- if (childId !== void 0) stream.flushAndUpdate((prev) => finalizeStreamingMarkdownForOwner(prev, childId));
24722
- if (!usage) return;
24723
- if (childId === void 0) {
24724
- lastFinishReasonRef.current = usage.finishReason;
24725
- const tokens = turnContextSize(usage);
24726
- if (tokens > 0) {
24727
- setLastInputTokens(tokens);
24728
- lastInputTokensRef.current = tokens;
24729
- }
24730
- }
24731
- const turnCost = usage.cost ?? 0;
24732
- if (turnCost > 0) setSessionCost((prev) => prev + turnCost);
24733
- },
24734
- onArtifactRecorded: () => notifyArtifactsChanged(),
24735
- onArtifactChanged: () => notifyArtifactsChanged(),
24736
- onTodosChanged: (snapshot) => {
24737
- todosApplySeqRef.current += 1;
24738
- const seq = todosApplySeqRef.current;
24739
- const open = sessionRef.current;
24740
- if (open?.id === sessionId && pickActiveRunId(open) === snapshot.runId) {
24741
- applyTodosSnapshot(open, snapshot);
24742
- notifyTodosChanged();
24743
- return;
24613
+ const useDaemon = settingsRef.current.useDaemon && !isDaemonDisabledByEnv();
24614
+ if (useDaemon) {
24615
+ setDaemonHealth("connecting");
24616
+ await activation.connect({
24617
+ open: async (attempt) => {
24618
+ agentRef.current = null;
24619
+ if (!loaded && typeof session.metadata.cwd !== "string") {
24620
+ session.setMeta("cwd", cwdRef.current ?? process.cwd());
24621
+ await session.save();
24622
+ if (!attempt.isCurrent()) throw new Error("activation superseded");
24623
+ }
24624
+ const activePick = pickedRef.current;
24625
+ let seedModelViaRpc = false;
24626
+ if (activePick) {
24627
+ if (typeof session.metadata.providerKey !== "string") if (loaded) seedModelViaRpc = true;
24628
+ else {
24629
+ session.setMeta("providerKey", activePick.provider.key);
24630
+ session.setMeta("modelId", activePick.model);
24631
+ if (activePick.effort) session.setMeta("effort", activePick.effort);
24632
+ if (activePick.modelOptions) session.setMeta("modelOptions", activePick.modelOptions);
24633
+ await session.save();
24634
+ if (!attempt.isCurrent()) throw new Error("activation superseded");
24744
24635
  }
24745
- loadSession(store, sessionId).then((fresh) => {
24746
- if (!fresh || sessionRef.current?.id !== sessionId) return;
24747
- if (seq !== todosApplySeqRef.current) return;
24748
- applyTodosSnapshot(fresh, snapshot);
24749
- sessionRef.current = fresh;
24750
- refreshHeaderFromStore();
24751
- notifyTodosChanged();
24636
+ lastPushedModelRef.current = JSON.stringify([
24637
+ activePick.provider.key,
24638
+ activePick.model,
24639
+ activePick.effort ?? null,
24640
+ activePick.modelOptions ?? null
24641
+ ]);
24642
+ } else lastPushedModelRef.current = null;
24643
+ if (!attempt.isCurrent()) throw new Error("activation superseded");
24644
+ const sessionId = session.id;
24645
+ const refreshHeaderFromStore = () => {
24646
+ store.load(sessionId).then((data) => {
24647
+ if (!attempt.isCurrent() || !data || sessionRef.current?.id !== sessionId) return;
24648
+ setCurrentSession({
24649
+ id: data.id,
24650
+ title: deriveSessionTitle(data.turns, data.metadata),
24651
+ turnCount: data.turns.length,
24652
+ userMessageCount: data.turns.reduce((n, t) => t.role === "user" ? n + 1 : n, 0),
24653
+ runCount: data.runs.length,
24654
+ updatedAt: data.updatedAt,
24655
+ ...data.projectRoot ? { projectRoot: data.projectRoot } : {}
24656
+ });
24752
24657
  }).catch(() => void 0);
24753
- },
24754
- onWorkChanged: (items) => {
24755
- const mapped = items.map((item) => ({
24756
- kind: item.kind,
24757
- callId: item.id,
24758
- tool: item.label,
24759
- startedAt: item.startedAt
24760
- }));
24761
- setInFlightTools(mapped.filter((m) => m.kind === "tool"));
24762
- setBackgroundTasks(mapped.filter((m) => m.kind === "task"));
24763
- },
24764
- onQueueChanged: (items) => {
24765
- daemonQueueIdsRef.current = items.map((item) => item.id);
24766
- const mirrored = items.map((item) => ({
24767
- prompt: item.text,
24768
- references: [],
24769
- attachments: []
24770
- }));
24771
- messageQueueRef.current = mirrored;
24772
- setMessageQueue(mirrored);
24773
- setQueueSelectionIndex((prev) => prev == null ? prev : mirrored.length === 0 ? null : Math.min(prev, mirrored.length - 1));
24774
- },
24775
- onInitialTranscript: (initial) => {
24776
- setEvents(capTranscriptEvents(initial));
24777
- },
24778
- onStreamEvent: (event) => {
24779
- const source = {
24780
- ...event.turnId ? { turnId: event.turnId } : {},
24781
- ...event.childId ? { childId: event.childId } : {},
24782
- ...typeof event.depth === "number" ? { depth: event.depth } : {}
24783
- };
24784
- if (event.kind === "markdown" && event.streaming) stream.queueStreamDelta("markdown", event.text, source);
24785
- else if (event.kind === "thinking") stream.queueStreamDelta("thinking", event.text, source);
24786
- else stream.appendImmediate(event);
24787
- },
24788
- onRunState: (running, info) => {
24789
- runningRef.current = running;
24790
- setBusy(running);
24791
- if (running) return;
24792
- stream.flushAndUpdate(finalizeStreamingMarkdown);
24793
- if (info && !info.ok && info.error) stream.appendImmediate({
24794
- kind: "error",
24795
- text: info.error
24796
- });
24797
- refreshHeaderFromStore();
24798
- resyncTranscript();
24799
- },
24800
- onApproval: (request, resolve) => {
24801
- requestApproval(request.tool, request.input, request.originator, request.id).then(resolve);
24802
- },
24803
- onApprovalResolved: (requestId) => {
24804
- dropApprovalByCorrelation(requestId);
24805
- },
24806
- onInteraction: (request, actions) => {
24807
- interactions.enqueue({
24808
- request,
24809
- resolve: actions.respond,
24810
- cancel: () => actions.cancel()
24811
- });
24812
- },
24813
- onInteractionResolved: (requestId) => {
24814
- interactions.dropById(requestId);
24815
- },
24816
- onCompacting: (active, info) => {
24817
- setCompacting(active);
24818
- if (!active && info?.ok && info.effectiveTokens !== void 0) {
24819
- setLastInputTokens(info.effectiveTokens);
24820
- lastInputTokensRef.current = info.effectiveTokens;
24821
- }
24822
- },
24823
- onDisconnect: () => {
24824
- runningRef.current = false;
24825
- setBusy(false);
24826
- setDaemonHealth("connecting");
24827
- stream.appendImmediate({
24828
- kind: "info",
24829
- text: "daemon connection lost, reconnecting"
24830
- });
24831
- setTimeout(() => {
24832
- if (sessionRef.current?.id === sessionId && settingsRef.current.useDaemon && daemonRef.current === controllerHandle) activateSessionRef.current(sessionId, pickedRef.current?.provider.key ?? "anthropic").catch(() => {
24658
+ };
24659
+ let controllerHandle = null;
24660
+ const reflectRemoteModel = () => {
24661
+ store.load(sessionId).then((data) => {
24662
+ if (!attempt.isCurrent() || !data || sessionRef.current?.id !== sessionId) return;
24663
+ const providerKey = data.metadata.providerKey;
24664
+ const modelId = data.metadata.modelId;
24665
+ if (typeof providerKey !== "string" || typeof modelId !== "string") return;
24666
+ const auth = availableProvidersRef.current.find((p) => p.key === providerKey);
24667
+ if (!auth) return;
24668
+ const next = makePicked(auth, modelId);
24669
+ if (!next) return;
24670
+ const effort = data.metadata.effort;
24671
+ const modelOptions = data.metadata.modelOptions;
24672
+ const merged = {
24673
+ ...next,
24674
+ ...typeof effort === "string" ? { effort } : {},
24675
+ ...modelOptions && typeof modelOptions === "object" ? { modelOptions } : {}
24676
+ };
24677
+ const tuple = JSON.stringify([
24678
+ merged.provider.key,
24679
+ merged.model,
24680
+ merged.effort ?? null,
24681
+ merged.modelOptions ?? null
24682
+ ]);
24683
+ if (lastPushedModelRef.current === tuple) return;
24684
+ lastPushedModelRef.current = tuple;
24685
+ setPicked(merged);
24686
+ }).catch(() => void 0);
24687
+ };
24688
+ const reflectRemoteAgent = () => {
24689
+ (store.getMeta ? store.getMeta(sessionId) : store.load(sessionId)).then((meta) => {
24690
+ if (!attempt.isCurrent() || !meta || sessionRef.current?.id !== sessionId) return;
24691
+ const remote = meta.agentId;
24692
+ if (!remote || remote === pickedAgentRef.current.id) return;
24693
+ sessionRef.current.setAgentId(remote);
24694
+ const profile = liveAgentRegistryRef.current[remote];
24695
+ if (!profile) return;
24696
+ pickedAgentRef.current = profile;
24697
+ setPickedAgent(profile);
24698
+ }).catch(() => void 0);
24699
+ };
24700
+ const resyncTranscript = () => {
24701
+ controllerHandle?.fetchTranscript().then(async (canonical) => {
24702
+ if (!attempt.isCurrent() || !canonical || sessionRef.current?.id !== sessionId || runningRef.current) return;
24703
+ await streamRef.current?.whenDrained();
24704
+ if (!attempt.isCurrent() || sessionRef.current?.id !== sessionId || runningRef.current) return;
24705
+ streamRef.current?.reset();
24706
+ setEvents(capTranscriptEvents(canonical));
24707
+ }).catch(() => void 0);
24708
+ };
24709
+ const controller = await connectDaemonSession({
24710
+ sessionId,
24711
+ appVersion: "tui",
24712
+ callbacks: guardConnectionCallbacks(attempt, {
24713
+ onRetry: ({ attempt, max, source }) => setRetryInfo({
24714
+ attempt,
24715
+ max,
24716
+ source
24717
+ }),
24718
+ onRetryCleared: (source) => clearRetryInfo(source),
24719
+ onPromptTurns: (refs) => {
24720
+ stream.flushAndUpdate((events) => retagUserPromptEventsFromRefs(events, refs));
24721
+ },
24722
+ onCompactionProgress: (tokens) => setCompactionTokens(tokens),
24723
+ onStreamProgress: (progress) => setStreamProgress(progress),
24724
+ onModelFallback: (from, to) => {
24725
+ setActiveFallback({
24726
+ provider: to.provider,
24727
+ model: to.model
24728
+ });
24833
24729
  stream.appendImmediate({
24730
+ kind: "info",
24731
+ text: `model fallback: ${from.provider}/${from.model} failed, continuing on ${to.provider}/${to.model}`
24732
+ });
24733
+ },
24734
+ onCacheBreak: (info) => {
24735
+ const providerKey = pickedRef.current?.provider.key;
24736
+ const apply = (costUsd) => {
24737
+ if (!attempt.isCurrent()) return;
24738
+ setActiveCacheBreak(costUsd !== void 0 ? {
24739
+ ...info,
24740
+ costUsd
24741
+ } : info);
24742
+ };
24743
+ if (!providerKey) {
24744
+ apply(void 0);
24745
+ return;
24746
+ }
24747
+ estimateRebilledCostUsd(providerKey, info.model, info.rebilledTokens).then(apply).catch(() => apply(void 0));
24748
+ },
24749
+ onSystemMd: (file) => {
24750
+ sessionSystemFileRef.current = file;
24751
+ announceProjectSystemOverride();
24752
+ },
24753
+ onSettingsChanged: (remote) => {
24754
+ const local = settingsRef.current;
24755
+ const entries = Object.entries(remote).filter(([key, value]) => key in DEFAULT_SETTINGS && JSON.stringify(local[key]) !== JSON.stringify(value));
24756
+ if (entries.length === 0) return;
24757
+ suppressSettingsPublishRef.current = true;
24758
+ for (const [key, value] of entries) setSetting(key, value);
24759
+ },
24760
+ onSessionsChanged: (op, changedId) => {
24761
+ if (op === "update" && changedId === sessionId) {
24762
+ refreshHeaderFromStore();
24763
+ resyncTranscript();
24764
+ reflectRemoteModel();
24765
+ reflectRemoteAgent();
24766
+ }
24767
+ },
24768
+ onDraftChanged: (text, shell) => {
24769
+ draftSeqRef.current += 1;
24770
+ setRemoteDraft({
24771
+ seq: draftSeqRef.current,
24772
+ text,
24773
+ ...shell ? { shell: true } : {}
24774
+ });
24775
+ },
24776
+ onSuggestChanged: ({ basis, caret, suggestion, candidates }) => {
24777
+ remoteSuggestSeqRef.current += 1;
24778
+ setRemoteSuggest({
24779
+ seq: remoteSuggestSeqRef.current,
24780
+ basis,
24781
+ caret,
24782
+ suggestion,
24783
+ ...candidates ? { candidates } : {}
24784
+ });
24785
+ },
24786
+ onSearchChanged: ({ active, query }) => {
24787
+ setTranscriptSearchMode(active);
24788
+ setTranscriptSearchQuery(query);
24789
+ if (!active) setSelectedTurnId(null);
24790
+ remoteSearchSeqRef.current += 1;
24791
+ setRemoteSearchQuery(active ? {
24792
+ seq: remoteSearchSeqRef.current,
24793
+ query
24794
+ } : null);
24795
+ },
24796
+ onDraftAttachmentsChanged: (attachments) => {
24797
+ draftAttachmentsSeqRef.current += 1;
24798
+ setRemoteDraftAttachments({
24799
+ seq: draftAttachmentsSeqRef.current,
24800
+ attachments: attachments.map((att) => ({
24801
+ name: att.name ?? "attachment",
24802
+ mediaType: att.mediaType,
24803
+ content: Buffer.from(att.data, "base64")
24804
+ }))
24805
+ });
24806
+ },
24807
+ onSkillsChanged: (active) => {
24808
+ setActiveSkillNames(new Set(active));
24809
+ },
24810
+ onMcpAuth: ({ name, reason }) => {
24811
+ dispatchAuthRef.current({
24812
+ type: "auth-required",
24813
+ name,
24814
+ reason: reason === "auto-promoted" ? "auto-promoted" : "no-tokens"
24815
+ });
24816
+ },
24817
+ onHudState: (state) => {
24818
+ setRemoteHudState(state);
24819
+ },
24820
+ onHudNotify: (n) => {
24821
+ const allow = enabledExtensionsRef.current;
24822
+ if (allow !== void 0 && !allow.includes(n.extension)) return;
24823
+ stream.appendImmediate({
24824
+ kind: n.tone === "error" ? "error" : "info",
24825
+ text: `${n.extension}: ${n.tone === "warn" ? "⚠ " : ""}${n.text}`
24826
+ });
24827
+ },
24828
+ onExtensionSignal: (envelope) => {
24829
+ getTuiBus()?.deliver(envelope);
24830
+ },
24831
+ onTurnEnd: ({ usage, childId }) => {
24832
+ if (childId === void 0) setStreamProgress(null);
24833
+ if (childId !== void 0) stream.flushAndUpdate((prev) => finalizeStreamingMarkdownForOwner(prev, childId));
24834
+ if (!usage) return;
24835
+ if (childId === void 0) {
24836
+ lastFinishReasonRef.current = usage.finishReason;
24837
+ const tokens = turnContextSize(usage);
24838
+ if (tokens > 0) {
24839
+ setLastInputTokens(tokens);
24840
+ lastInputTokensRef.current = tokens;
24841
+ }
24842
+ }
24843
+ const turnCost = usage.cost ?? 0;
24844
+ if (turnCost > 0) setSessionCost((prev) => prev + turnCost);
24845
+ },
24846
+ onArtifactRecorded: () => notifyArtifactsChanged(),
24847
+ onArtifactChanged: () => notifyArtifactsChanged(),
24848
+ onTodosChanged: (snapshot) => {
24849
+ todosApplySeqRef.current += 1;
24850
+ const seq = todosApplySeqRef.current;
24851
+ const open = sessionRef.current;
24852
+ if (open?.id === sessionId && pickActiveRunId(open) === snapshot.runId) {
24853
+ applyTodosSnapshot(open, snapshot);
24854
+ notifyTodosChanged();
24855
+ return;
24856
+ }
24857
+ loadSession(store, sessionId).then((fresh) => {
24858
+ if (!attempt.isCurrent() || !fresh || sessionRef.current?.id !== sessionId) return;
24859
+ if (seq !== todosApplySeqRef.current) return;
24860
+ applyTodosSnapshot(fresh, snapshot);
24861
+ sessionRef.current = fresh;
24862
+ refreshHeaderFromStore();
24863
+ notifyTodosChanged();
24864
+ }).catch(() => void 0);
24865
+ },
24866
+ onWorkChanged: (items) => {
24867
+ const mapped = items.map((item) => ({
24868
+ kind: item.kind,
24869
+ callId: item.id,
24870
+ tool: item.label,
24871
+ startedAt: item.startedAt
24872
+ }));
24873
+ setInFlightTools(mapped.filter((m) => m.kind === "tool"));
24874
+ setBackgroundTasks(mapped.filter((m) => m.kind === "task"));
24875
+ },
24876
+ onQueueChanged: (items) => {
24877
+ daemonQueueIdsRef.current = items.map((item) => item.id);
24878
+ const mirrored = items.map((item) => ({
24879
+ prompt: item.text,
24880
+ references: [],
24881
+ attachments: []
24882
+ }));
24883
+ messageQueueRef.current = mirrored;
24884
+ setMessageQueue(mirrored);
24885
+ setQueueSelectionIndex((prev) => prev == null ? prev : mirrored.length === 0 ? null : Math.min(prev, mirrored.length - 1));
24886
+ },
24887
+ onInitialTranscript: (initial) => {
24888
+ setEvents(capTranscriptEvents(initial));
24889
+ },
24890
+ onStreamEvent: (event) => {
24891
+ const source = {
24892
+ ...event.turnId ? { turnId: event.turnId } : {},
24893
+ ...event.childId ? { childId: event.childId } : {},
24894
+ ...typeof event.depth === "number" ? { depth: event.depth } : {}
24895
+ };
24896
+ if (event.kind === "markdown" && event.streaming) stream.queueStreamDelta("markdown", event.text, source);
24897
+ else if (event.kind === "thinking") stream.queueStreamDelta("thinking", event.text, source);
24898
+ else stream.appendImmediate(event);
24899
+ },
24900
+ onRunState: (running, info) => {
24901
+ runningRef.current = running;
24902
+ setBusy(running);
24903
+ if (running) return;
24904
+ stream.flushAndUpdate(finalizeStreamingMarkdown);
24905
+ if (info && !info.ok && info.error) stream.appendImmediate({
24834
24906
  kind: "error",
24835
- text: "reconnect failed. ctrl+r or reopen the session to retry"
24907
+ text: info.error
24836
24908
  });
24837
- });
24838
- }, 1500);
24839
- }
24909
+ refreshHeaderFromStore();
24910
+ resyncTranscript();
24911
+ },
24912
+ onApproval: (request, resolve) => {
24913
+ requestApproval(request.tool, request.input, request.originator, request.id).then(attempt.guard(resolve));
24914
+ },
24915
+ onApprovalResolved: (requestId) => {
24916
+ dropApprovalByCorrelation(requestId);
24917
+ },
24918
+ onInteraction: (request, actions) => {
24919
+ interactions.enqueue({
24920
+ request,
24921
+ resolve: attempt.guard(actions.respond),
24922
+ cancel: attempt.guard(() => actions.cancel())
24923
+ });
24924
+ },
24925
+ onInteractionResolved: (requestId) => {
24926
+ interactions.dropById(requestId);
24927
+ },
24928
+ onCompacting: (active, info) => {
24929
+ setCompacting(active);
24930
+ if (!active && info?.ok && info.effectiveTokens !== void 0) {
24931
+ setLastInputTokens(info.effectiveTokens);
24932
+ lastInputTokensRef.current = info.effectiveTokens;
24933
+ }
24934
+ },
24935
+ onDisconnect: attempt.disconnect
24936
+ })
24937
+ });
24938
+ if (!attempt.isCurrent()) return controller;
24939
+ controllerHandle = controller;
24940
+ daemonRef.current = controller;
24941
+ setDaemonConnected(true);
24942
+ if (stampedLegacyAgent) controller.setAgentId(pickedAgentRef.current.id);
24943
+ if (seedModelViaRpc && activePick) controller.applyModel({
24944
+ providerKey: activePick.provider.key,
24945
+ modelId: activePick.model,
24946
+ ...activePick.effort ? { effort: activePick.effort } : {},
24947
+ ...activePick.modelOptions ? { modelOptions: activePick.modelOptions } : {}
24948
+ });
24949
+ return controller;
24950
+ },
24951
+ ready: () => {},
24952
+ cleanup: () => {
24953
+ denyAll();
24954
+ interactions.cancelAll("daemon disconnected");
24955
+ },
24956
+ unavailable: (err) => {
24957
+ debugLog("daemon unavailable — retrying attachment", err);
24958
+ daemonRef.current = null;
24959
+ runningRef.current = false;
24960
+ setBusy(false);
24961
+ setDaemonHealth("connecting");
24962
+ stream.appendImmediate({
24963
+ kind: "info",
24964
+ text: "daemon unavailable, reconnecting; sends disabled until reattached. Disable “Use daemon runtime” explicitly to run locally."
24965
+ });
24840
24966
  }
24841
24967
  });
24842
- if (activationGenerationRef.current !== activationGeneration) {
24843
- controller.dispose();
24844
- return;
24845
- }
24846
- controllerHandle = controller;
24847
- daemonRef.current = controller;
24848
- setDaemonConnected(true);
24849
- if (stampedLegacyAgent) controller.setAgentId(pickedAgentRef.current.id);
24850
- if (seedModelViaRpc && activePick) controller.applyModel({
24851
- providerKey: activePick.provider.key,
24852
- modelId: activePick.model,
24853
- ...activePick.effort ? { effort: activePick.effort } : {},
24854
- ...activePick.modelOptions ? { modelOptions: activePick.modelOptions } : {}
24855
- });
24856
- } catch (err) {
24857
- debugLog("daemon activation failed — falling back to in-process runtime", err);
24858
- daemonRef.current = null;
24859
- setDaemonConnected(false);
24968
+ } else {
24860
24969
  agentRef.current = buildAgent(session, key);
24861
- stream.appendImmediate({
24862
- kind: "info",
24863
- text: `daemon unavailable (${errorMessage(err)}), running this session in-process. Turn off “Use daemon runtime” to silence this.`
24864
- });
24970
+ activation.localReady();
24865
24971
  }
24866
- else agentRef.current = buildAgent(session, key);
24972
+ if (!activation.isCurrent()) return;
24867
24973
  if (extensionRegistrySwapKeyRef.current === null || extensionRegistrySwapKeyRef.current === extensionRegistry) pendingAgentRebuildRef.current = false;
24868
24974
  announcedSystemOverrideRef.current = null;
24869
24975
  announceProjectSystemOverride();
24870
24976
  const persistedPins = readPinnedSkills(session.metadata["zidane.activeSkills"]);
24871
24977
  setActiveSkillNames(persistedPins);
24872
- if (!daemonRef.current) setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
24978
+ if (!useDaemon) setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
24873
24979
  setActiveFallback(null);
24874
24980
  const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
24875
24981
  setLastInputTokens(replayedTokens);
@@ -24891,7 +24997,7 @@ function AppShell() {
24891
24997
  lastProvider: key,
24892
24998
  lastSessionId: session.id
24893
24999
  });
24894
- const pending = daemonRef.current ? [] : pendingInteractionsFromTurns(session.turns);
25000
+ const pending = useDaemon ? [] : pendingInteractionsFromTurns(session.turns);
24895
25001
  if (pending.length > 0) {
24896
25002
  const collected = /* @__PURE__ */ new Map();
24897
25003
  const automatic = interactionModeRef.current === "skip";
@@ -25407,6 +25513,7 @@ function AppShell() {
25407
25513
  });
25408
25514
  }, []);
25409
25515
  const dropSelectedQueuedMessage = useCallback(() => {
25516
+ if (!connectionRef.current.canSend) return;
25410
25517
  const idx = queueSelectionIndexRef.current;
25411
25518
  if (idx == null) return;
25412
25519
  const queue = messageQueueRef.current;
@@ -25438,6 +25545,7 @@ function AppShell() {
25438
25545
  * it up as a regular prompt, so the message is never silently dropped.
25439
25546
  */
25440
25547
  const pushSelectedQueuedMessage = useCallback(() => {
25548
+ if (!connectionRef.current.canSend) return;
25441
25549
  const idx = queueSelectionIndexRef.current;
25442
25550
  if (idx == null) return;
25443
25551
  if (daemonRef.current) {
@@ -25848,6 +25956,7 @@ function AppShell() {
25848
25956
  * `agent.run` so the single-flight agent is never re-entered.
25849
25957
  */
25850
25958
  const runCommand = useCallback(async (invocation, attachments = []) => {
25959
+ if (!connectionRef.current.canSend) return;
25851
25960
  const session = sessionRef.current;
25852
25961
  const agent = agentRef.current;
25853
25962
  if (!session) return;
@@ -26227,6 +26336,7 @@ function AppShell() {
26227
26336
  * path as `/` commands (`commandAbortRef`).
26228
26337
  */
26229
26338
  const runBashCommand = useCallback(async (command) => {
26339
+ if (!connectionRef.current.canSend) return;
26230
26340
  const session = sessionRef.current;
26231
26341
  if (!session) return;
26232
26342
  const controller = new AbortController();
@@ -26321,6 +26431,7 @@ function AppShell() {
26321
26431
  }, [stream, cwd]);
26322
26432
  /** Route a submitted prompt to the command executor when it's a `/cmd`, otherwise to the model. */
26323
26433
  const runPromptOrCommand = useCallback(async (prompt, references, attachments, opts) => {
26434
+ if (!connectionRef.current.canSend) return;
26324
26435
  if (!opts?.wake) {
26325
26436
  const invocation = parseCommandInvocation(prompt, commandsRef.current);
26326
26437
  if (invocation) {
@@ -26384,6 +26495,20 @@ function AppShell() {
26384
26495
  }
26385
26496
  }, [runSingleMessage, runPromptOrCommand]);
26386
26497
  const onSubmitPrompt = useCallback((prompt, references, attachments) => {
26498
+ if (!connectionRef.current.canSend) {
26499
+ const shell = parseBashInvocation(prompt);
26500
+ setRemoteDraft({
26501
+ seq: ++draftSeqRef.current,
26502
+ text: shell ?? prompt,
26503
+ ...shell !== null ? { shell: true } : {}
26504
+ });
26505
+ setRemoteDraftAttachments({
26506
+ seq: ++draftAttachmentsSeqRef.current,
26507
+ attachments: [...attachments]
26508
+ });
26509
+ return;
26510
+ }
26511
+ const submitGeneration = activationGenerationRef.current;
26387
26512
  if (!prompt.trim() && attachments.length === 0) return;
26388
26513
  const agent = agentRef.current;
26389
26514
  const daemon = daemonRef.current;
@@ -26397,6 +26522,7 @@ function AppShell() {
26397
26522
  let effectivePrompt = prompt;
26398
26523
  if (parseCommandInvocation(prompt, commandsRef.current) === null && parseBashInvocation(prompt) === null) for (const contribution of extensionRegistry.promptTransforms) try {
26399
26524
  const out = await contribution.value(effectivePrompt);
26525
+ if (submitGeneration !== activationGenerationRef.current || !connectionRef.current.canSend || daemon !== daemonRef.current) return;
26400
26526
  if (out === void 0) continue;
26401
26527
  if (typeof out === "string") {
26402
26528
  effectivePrompt = out;
@@ -26413,6 +26539,7 @@ function AppShell() {
26413
26539
  debugLog(`prompt transform (${contribution.extensionName}) threw`, err);
26414
26540
  }
26415
26541
  const resolvedReferences = await resolveCompletionSubmit(effectivePrompt, completionProviders, { cursor: effectivePrompt.length }).catch(() => references);
26542
+ if (submitGeneration !== activationGenerationRef.current || !connectionRef.current.canSend || daemon !== daemonRef.current) return;
26416
26543
  if (daemon) {
26417
26544
  if (!wasRunning) runningRef.current = false;
26418
26545
  const commandInvocation = parseCommandInvocation(effectivePrompt, commandsRef.current);