zidane 6.5.1 → 6.5.3

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 (110) hide show
  1. package/dist/{acp-o-LQAjFw.js → acp-CIpD8UIw.js} +4 -4
  2. package/dist/{acp-o-LQAjFw.js.map → acp-CIpD8UIw.js.map} +1 -1
  3. package/dist/acp-cli.js +5 -5
  4. package/dist/acp.d.ts +1 -1
  5. package/dist/acp.d.ts.map +1 -1
  6. package/dist/acp.js +1 -1
  7. package/dist/{agent-BVNkC1h4.js → agent-8kfGIDb_.js} +755 -586
  8. package/dist/agent-8kfGIDb_.js.map +1 -0
  9. package/dist/agent.d.ts +1 -1
  10. package/dist/agent.js +1 -1
  11. package/dist/{anthropic-Dov2mC5e.js → anthropic-_Hn1YiWr.js} +2 -2
  12. package/dist/{anthropic-Dov2mC5e.js.map → anthropic-_Hn1YiWr.js.map} +1 -1
  13. package/dist/{auth-B8ZuBcKC.js → auth-CS_e8ng1.js} +6 -5
  14. package/dist/{auth-B8ZuBcKC.js.map → auth-CS_e8ng1.js.map} +1 -1
  15. package/dist/chat/pure.d.ts +4 -4
  16. package/dist/chat/pure.js +3 -3
  17. package/dist/chat.d.ts +124 -10
  18. package/dist/chat.d.ts.map +1 -1
  19. package/dist/chat.js +6 -6
  20. package/dist/{context-breakdown-BLF10tOO.js → context-segments-1mcqSCM3.js} +110 -2
  21. package/dist/context-segments-1mcqSCM3.js.map +1 -0
  22. package/dist/{context-transport-D-daWOmp.js → context-transport-Bco9Wo0b.js} +3 -3
  23. package/dist/{context-transport-D-daWOmp.js.map → context-transport-Bco9Wo0b.js.map} +1 -1
  24. package/dist/contexts/daytona.d.ts +1 -1
  25. package/dist/contexts/daytona.d.ts.map +1 -1
  26. package/dist/contexts/daytona.js +5 -2
  27. package/dist/contexts/daytona.js.map +1 -1
  28. package/dist/contexts/e2b.d.ts +1 -1
  29. package/dist/eval.d.ts +1 -1
  30. package/dist/extensions.d.ts +1 -1
  31. package/dist/extensions.js +1 -1
  32. package/dist/headless.d.ts +1 -1
  33. package/dist/headless.js +1 -1
  34. package/dist/{index-CWBSrqI6.d.ts → index-BKyWhILQ.d.ts} +179 -7
  35. package/dist/index-BKyWhILQ.d.ts.map +1 -0
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.js +7 -7
  38. package/dist/{login-CR3fDHXh.js → login-D3CKhgbI.js} +2 -2
  39. package/dist/{login-CR3fDHXh.js.map → login-D3CKhgbI.js.map} +1 -1
  40. package/dist/{mcp-C6IkXNOS.js → mcp-D9fqOQo5.js} +3 -3
  41. package/dist/{mcp-C6IkXNOS.js.map → mcp-D9fqOQo5.js.map} +1 -1
  42. package/dist/mcp.d.ts +1 -1
  43. package/dist/mcp.js +1 -1
  44. package/dist/{oauth-bkEOybqm.js → oauth-BYjlFR-J.js} +49 -10
  45. package/dist/oauth-BYjlFR-J.js.map +1 -0
  46. package/dist/output/stream-json.d.ts +1 -1
  47. package/dist/output/terminal.d.ts +1 -1
  48. package/dist/plugins.d.ts +1 -1
  49. package/dist/presets.d.ts +1 -1
  50. package/dist/presets.js +1 -1
  51. package/dist/providers/anthropic.d.ts +1 -1
  52. package/dist/providers/anthropic.js +1 -1
  53. package/dist/providers/arcee.d.ts +1 -1
  54. package/dist/providers/baseten.d.ts +1 -1
  55. package/dist/providers/cerebras.d.ts +1 -1
  56. package/dist/providers/local.d.ts +1 -1
  57. package/dist/providers/openai-compat.d.ts +1 -1
  58. package/dist/providers/openai.d.ts +1 -1
  59. package/dist/providers/openai.js +1 -1
  60. package/dist/providers/openrouter.d.ts +1 -1
  61. package/dist/providers/xai.d.ts +1 -1
  62. package/dist/providers/xai.js +68 -1
  63. package/dist/providers/xai.js.map +1 -0
  64. package/dist/{providers-B6mFe7OD.js → providers-VypZh7rD.js} +3 -3
  65. package/dist/{providers-B6mFe7OD.js.map → providers-VypZh7rD.js.map} +1 -1
  66. package/dist/providers.d.ts +2 -2
  67. package/dist/providers.js +4 -4
  68. package/dist/restate.d.ts +1 -1
  69. package/dist/restate.js +1 -1
  70. package/dist/rolldown-runtime-BhDjJH2R.js +15 -0
  71. package/dist/session/sqlite.d.ts +1 -1
  72. package/dist/session.d.ts +1 -1
  73. package/dist/skills.d.ts +1 -1
  74. package/dist/{stdio-DzUUkkhS.js → stdio-DPpBDPx5.js} +13 -4
  75. package/dist/{stdio-DzUUkkhS.js.map → stdio-DPpBDPx5.js.map} +1 -1
  76. package/dist/{stdio-loader-BILyUGmi.js → stdio-loader-CI4sPFve.js} +11 -6
  77. package/dist/stdio-loader-CI4sPFve.js.map +1 -0
  78. package/dist/{tool-formatters-gFqQbzPY.d.ts → tool-formatters-DMr62Hpy.d.ts} +3 -3
  79. package/dist/{tool-formatters-gFqQbzPY.d.ts.map → tool-formatters-DMr62Hpy.d.ts.map} +1 -1
  80. package/dist/tools/fetch-url.d.ts +1 -1
  81. package/dist/tools/web-search.d.ts +1 -1
  82. package/dist/tools.d.ts +1 -1
  83. package/dist/tools.js +1 -1
  84. package/dist/{transcript-anchors-qCs3wZpu.d.ts → transcript-anchors-BHihk9nf.d.ts} +2 -2
  85. package/dist/{transcript-anchors-qCs3wZpu.d.ts.map → transcript-anchors-BHihk9nf.d.ts.map} +1 -1
  86. package/dist/{transcript-anchors-DEIr0tJC.js → transcript-anchors-uU_KZLsp.js} +161 -36
  87. package/dist/transcript-anchors-uU_KZLsp.js.map +1 -0
  88. package/dist/tui.d.ts +74 -3
  89. package/dist/tui.d.ts.map +1 -1
  90. package/dist/tui.js +823 -81
  91. package/dist/tui.js.map +1 -1
  92. package/dist/{turn-operations-M9-jUyDd.d.ts → turn-operations-BgqVUjtl.d.ts} +3 -3
  93. package/dist/{turn-operations-M9-jUyDd.d.ts.map → turn-operations-BgqVUjtl.d.ts.map} +1 -1
  94. package/dist/{turn-operations-C3IijXMW.js → turn-operations-iGu5J08j.js} +2 -2
  95. package/dist/{turn-operations-C3IijXMW.js.map → turn-operations-iGu5J08j.js.map} +1 -1
  96. package/dist/types.d.ts +1 -1
  97. package/dist/{xai-Du6Wfnlb.js → xai-CfmI5Y5k.js} +10 -67
  98. package/dist/xai-CfmI5Y5k.js.map +1 -0
  99. package/docs/CHAT.md +42 -1
  100. package/docs/INTERACTIONS.md +39 -0
  101. package/docs/RESTATE.md +7 -0
  102. package/docs/TUI.md +28 -2
  103. package/package.json +18 -17
  104. package/dist/agent-BVNkC1h4.js.map +0 -1
  105. package/dist/context-breakdown-BLF10tOO.js.map +0 -1
  106. package/dist/index-CWBSrqI6.d.ts.map +0 -1
  107. package/dist/oauth-bkEOybqm.js.map +0 -1
  108. package/dist/stdio-loader-BILyUGmi.js.map +0 -1
  109. package/dist/transcript-anchors-DEIr0tJC.js.map +0 -1
  110. package/dist/xai-Du6Wfnlb.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,28 +1,29 @@
1
- import { $t as toggleChainEntry, A as redirectUriFromAuthUrl, At as resolveTheme, Bn as TODO_STATUS_GLYPHS, Bt as useDiscovery, Cr as stripSpawnTokensLine, Ct as SETTINGS_TOGGLES, D as formatPathForCwd, Dr as toolResultText, En as discoverAgentsMd, Er as toolCallPreview, Et as useSettings, F as loadMcpToolsCache, G as parentServerName, Gt as resolveStoragePaths, Ht as ConfigProvider, I as mcpToolsCachePath, Jt as chainsEqual, K as createFileMcpCredentialStore, Kt as buildRunModel, L as refreshMcpToolsCatalog, M as runOAuthLogin, N as supportsOAuth, O as fetchOAuthRedirect, Or as updateToolEventOutcomes, Pn as accentColor, Q as getMcpAuthStatus, Rt as createDiscoverySlot, Sr as serverToolResultSummary, St as SETTINGS_INPUTS, T as suggestSafelistEntries, Tn as chatAutoCompactBehavior, Tt as clampFps, U as buildVisibleMcpRows, Ut as useConfig, V as useMcpToolToggleMap, Vt as useDiscoveryOptional, W as indexOfServerRow, Wt as resolveConfig, X as useMcpAuthDispatch, Xn as useActiveTodos, Xt as providerKeySetChanged, Y as McpAuthProvider, Yt as filterChainFallbacks, Z as useMcpAuthState, Zt as restoreChainFallbacks, _ as useSafeModeQueue, _r as lastContextSizeFromTurns, _t as listProjectFiles, a as useSurfaces, an as removeMcpServer, b as getSafelist, br as marginTopFor, bt as SETTINGS_CATEGORIES, c as useStreamBuffer, cn as dedupMcpServersByName, cr as buildPlanSystem, d as discoverProjectSkills, dr as createChatTurnsReadTool, dt as EMPTY_HINTS, en as useCompletion, et as ASK_USER_TOOL, fn as tryOpenBrowser, ft as clipHintsToWidth, g as useSafeModeActions, gn as useUpdateCheck, gr as eventsFromTurns, h as SafeModeProvider, hn as buildUpdateHint, hr as deriveSessionTitle, ht as buildHints, i as useSelectStyle, in as mcpsWritePath, it as createInteractionTools, j as oauthUsesManualCodePaste, kr as usageByModel, kt as resolveChipColor, l as buildSkillsConfig, lr as envSection, lt as useInteractionsActions, m as writeSessionExport, mn as bootTick, mt as truncateTrailing, n as ThemeProvider, nn as hasClaudeCodeSetup, nt as PRESENT_PLAN_TOOL, on as upsertMcpServer, ot as makeRequestInteraction, pr as compactSummaryEvent, pt as hintsLength, qn as pruneTodosByRun, qt as chainPositionOf, r as useColors, rn as importFromClaudeCode, rt as buildResumedToolResultsTurn, s as useTheme, sn as buildMcpServers, sr as buildBuildSystem, st as pendingInteractionsFromTurns, t as computeTurnAnchors, tn as countClaudeSessionFiles, tt as InteractionsProvider, u as defaultSkillScanPaths, un as discoverProjectMcps, ur as chatReferenceTokensForPrompt, ut as useInteractionsQueue, vr as listSessionMeta, vt as useEnabledToggleSet, wr as sumRunCosts, wt as SettingsProvider, x as isOnSafelist, xt as SETTINGS_CHOICES, y as addToSafelist, yr as loadState, yt as DEFAULT_SETTINGS, z as subscribeMcpToolsCache, zt as DiscoveryProvider } from "./transcript-anchors-DEIr0tJC.js";
2
1
  import { C as estimateRebilledCostUsd } from "./openai-compat-CnHeo4Lz.js";
3
- import { $ as installExtensionFromSpec, $t as groupBindings, A as selectFilesFromSession, C as resolveLegacySessionDir, Cn as resolveExtensionConfigValue, D as resolveTasksDir, Dn as OUTPUT_RESERVE_TOKENS, E as resolveSessionDir, Et as isSpawnTool, G as summaryToTurn, It as looksBinary, M as compactConversation, O as buildPostCompactAttachments, On as effectiveContextWindow, Pt as tailTruncate, Qt as formatBindingForDisplay, Rt as createModelSearchTool, S as resolveLegacyPersistDir, T as resolvePersistDir, X as createExtensionPromptBridge, Xt as KEYBINDING_KEY_COL_WIDTH, Z as ExtensionInstallError, Zt as ensureKeybindingsFile, at as createExtensionHud, bt as basic_default, cn as registerExtensionHostModules, dt as filterExtensionRegistry, en as keybindingsPath, f as applyCompactSummaryCutoff, fn as inlineLoadedExtension, ft as filterRegistryPlugins, gn as createDebugLog, hn as scanExtensionDirectories, ht as createExtensionHostRuntimeBridge, it as updateInstalledExtension, lt as composeExtensionPreset, mn as parseExtensionScanPathEnv, mt as partitionExtensionCatalog, on as resolveExtensionKeybindings, ot as discoverAndBuildExtensionRegistry, rt as removeInstalledExtension, t as createAgent, tn as matchesBinding, ut as disposeExtensionRegistry, v as cleanupPersistedSession, w as resolveMcpWarningsDir, wn as sanitizeExtensionConfigActionPatch, x as rehomePersistedStubs, yn as coerceExtensionConfigInput } from "./agent-BVNkC1h4.js";
2
+ import { t as buildContextSegments } from "./context-segments-1mcqSCM3.js";
3
+ import { $ as installExtensionFromSpec, $t as groupBindings, A as selectFilesFromSession, C as resolveLegacySessionDir, Cn as resolveExtensionConfigValue, D as resolveTasksDir, Dn as OUTPUT_RESERVE_TOKENS, E as resolveSessionDir, Et as isSpawnTool, G as summaryToTurn, It as looksBinary, M as compactConversation, O as buildPostCompactAttachments, On as effectiveContextWindow, Pt as tailTruncate, Qt as formatBindingForDisplay, Rt as createModelSearchTool, S as resolveLegacyPersistDir, T as resolvePersistDir, X as createExtensionPromptBridge, Xt as KEYBINDING_KEY_COL_WIDTH, Z as ExtensionInstallError, Zt as ensureKeybindingsFile, at as createExtensionHud, bt as basic_default, cn as registerExtensionHostModules, dt as filterExtensionRegistry, en as keybindingsPath, f as applyCompactSummaryCutoff, fn as inlineLoadedExtension, ft as filterRegistryPlugins, gn as createDebugLog, hn as scanExtensionDirectories, ht as createExtensionHostRuntimeBridge, it as updateInstalledExtension, lt as composeExtensionPreset, mn as parseExtensionScanPathEnv, mt as partitionExtensionCatalog, on as resolveExtensionKeybindings, ot as discoverAndBuildExtensionRegistry, rt as removeInstalledExtension, t as createAgent, tn as matchesBinding, ut as disposeExtensionRegistry, v as cleanupPersistedSession, w as resolveMcpWarningsDir, wn as sanitizeExtensionConfigActionPatch, x as rehomePersistedStubs, yn as coerceExtensionConfigInput } from "./agent-8kfGIDb_.js";
4
4
  import { a as formatDuration, c as previewLine, i as fmtTokens, l as shortId, n as compactPath, o as formatTaskStatus, r as fmtCost, s as formatTaskSummary, t as ageString } from "./format-BF745pLL.js";
5
5
  import { n as appendCapped } from "./output-cap-C9zukcCj.js";
6
6
  import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-DWVt7MvF.js";
7
7
  import { u as errorMessage } from "./errors-BwBxu3zD.js";
8
- import { R as projectUserPaths, c as SkillInstallError, l as installSkillFromSpec, z as findGitRoot } from "./skills-Cp9vnBwd.js";
8
+ import { E as buildCatalog, R as projectUserPaths, c as SkillInstallError, l as installSkillFromSpec, z as findGitRoot } from "./skills-Cp9vnBwd.js";
9
9
  import { n as defaultPluginDataRoot, r as loadAgentPlugin } from "./plugins-2PMTVcp-.js";
10
10
  import { a as createRingBufferSink, i as createLoggingHooks, l as logFileTimestamp, n as createFileSink, o as fanoutSink, r as createLogger, s as formatLogLine, t as consoleSink } from "./logger-DBX9uYOw.js";
11
11
  import { i as sniffImageMediaType } from "./media-sniff-UKijEaiQ.js";
12
- import { _ as hasAuthorizationHeader, h as McpOAuthProvider, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-C6IkXNOS.js";
13
- import { replaceDynamicSection } from "./prompt.js";
12
+ import { _ as hasAuthorizationHeader, h as McpOAuthProvider, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-D9fqOQo5.js";
13
+ import { replaceDynamicSection, splitSystemPrompt } from "./prompt.js";
14
14
  import { a as getExtensionSessionState, c as setExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, t as createSession } from "./session-VpLrNaFQ.js";
15
15
  import { n as flattenTurns, r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
16
16
  import { o as withTimeout } from "./timeout-BDetbuN6.js";
17
17
  import { i as installCacheBreakLogger } from "./cache-telemetry-ehVdWsg2.js";
18
18
  import { a as generateStructuredData, c as shouldAutoGenerateTitle, f as commandBlockToEvent, g as parseCommandInvocation, h as executeCommand, n as patchInlineGhost, p as commandBlocksToContent, r as resolveInlineSuggestion, s as generateSessionTitle, t as createPathAutocompleteProvider } from "./inline-autocomplete-D0MPvBaS.js";
19
19
  import { s as resolveCompletionSubmit } from "./completion-core-BRo86R_L.js";
20
- import { a as ZIDANE_EXTENSION_SKILL_NAME, d as extensionScaffold, f as readSkillEditMarker, h as withZidaneSkillEditSkill, i as SKILL_EDIT_METADATA_KEY, l as editSessionMetadata, m as withZidaneExtensionSkill, o as ZIDANE_SKILL_EDIT_SKILL_NAME, t as loginMcpServer, u as extensionDirForCwd } from "./login-CR3fDHXh.js";
21
- import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning, w as renderDefaultCallbackPage } from "./auth-B8ZuBcKC.js";
20
+ import { a as ZIDANE_EXTENSION_SKILL_NAME, d as extensionScaffold, f as readSkillEditMarker, h as withZidaneSkillEditSkill, i as SKILL_EDIT_METADATA_KEY, l as editSessionMetadata, m as withZidaneExtensionSkill, o as ZIDANE_SKILL_EDIT_SKILL_NAME, t as loginMcpServer, u as extensionDirForCwd } from "./login-D3CKhgbI.js";
21
+ import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning, w as renderDefaultCallbackPage } from "./auth-CS_e8ng1.js";
22
22
  import { createDockerContext } from "./contexts/docker.js";
23
23
  import { createTuiStore } from "./session/sqlite.js";
24
- import { $ as buildContextualDiff, A as createSkillsCompletionProvider, B as EDIT_TOOL_NAMES, C as chunkMarkdownForRender, D as splitMarkdownCodeBlocks, E as normalizeMarkdownCodeFences, G as isVisibleDuringSearch, H as isTurnHighlighted, I as createCommandsCompletionProvider, J as selectableTurnIds, K as promptHistoryFromEvents, L as blendHsl, N as createFilesCompletionProvider, O as stripHtmlCommentsOutsideCode, R as buildLinearRamp, T as codeTokenHasClosingFence, U as isUserBashEvent, V as isEditErrorResult, W as isVisible, X as streamEventSearchText, Y as selectionAfterTurnRemoval, Z as turnSelectionOwnership, _ as turnContextSize, _t as summarizeOutcomes, a as TOOL_DISPLAY, at as filetypeFromPath, b as filterModelCatalog, bt as createChatsCompletionProvider, ct as summarizeEditPayload, d as finalizeStreamingMarkdown, et as buildUnifiedDiff, f as finalizeStreamingMarkdownForOwner, ft as mergeApprovalAndBodyOutcomes, g as revealEarlierTranscriptEvents, gt as stripEditOutcomesAnnotation, h as replaceTranscriptTurnEvents, ht as rewriteMultiEditHeader, i as turnAsText, it as extractEditPayload, j as uniqueSkillNamesFromReferences, mt as resolveApprovalForPayload, n as deleteTurnSafely, o as displayNameFor, ot as previewEditPayload, p as hiddenEarlierTranscriptCount, pt as parseEditOutcomesFromResult, q as retagUserPromptEvents, r as truncateTurnsAt, s as formatToolCall, tt as clipDiffForRender, u as capTranscriptEvents, ut as buildEditOutcomesAnnotation, v as splitPromptSegments, w as closeDanglingCodeFence, y as buildModelCatalog, z as BASH_CALL_ID_PREFIX } from "./turn-operations-C3IijXMW.js";
24
+ import { $ as buildContextualDiff, A as createSkillsCompletionProvider, B as EDIT_TOOL_NAMES, C as chunkMarkdownForRender, D as splitMarkdownCodeBlocks, E as normalizeMarkdownCodeFences, G as isVisibleDuringSearch, H as isTurnHighlighted, I as createCommandsCompletionProvider, J as selectableTurnIds, K as promptHistoryFromEvents, L as blendHsl, N as createFilesCompletionProvider, O as stripHtmlCommentsOutsideCode, R as buildLinearRamp, T as codeTokenHasClosingFence, U as isUserBashEvent, V as isEditErrorResult, W as isVisible, X as streamEventSearchText, Y as selectionAfterTurnRemoval, Z as turnSelectionOwnership, _ as turnContextSize, _t as summarizeOutcomes, a as TOOL_DISPLAY, at as filetypeFromPath, b as filterModelCatalog, bt as createChatsCompletionProvider, ct as summarizeEditPayload, d as finalizeStreamingMarkdown, et as buildUnifiedDiff, f as finalizeStreamingMarkdownForOwner, ft as mergeApprovalAndBodyOutcomes, g as revealEarlierTranscriptEvents, gt as stripEditOutcomesAnnotation, h as replaceTranscriptTurnEvents, ht as rewriteMultiEditHeader, i as turnAsText, it as extractEditPayload, j as uniqueSkillNamesFromReferences, mt as resolveApprovalForPayload, n as deleteTurnSafely, o as displayNameFor, ot as previewEditPayload, p as hiddenEarlierTranscriptCount, pt as parseEditOutcomesFromResult, q as retagUserPromptEvents, r as truncateTurnsAt, s as formatToolCall, tt as clipDiffForRender, u as capTranscriptEvents, ut as buildEditOutcomesAnnotation, v as splitPromptSegments, w as closeDanglingCodeFence, y as buildModelCatalog, z as BASH_CALL_ID_PREFIX } from "./turn-operations-iGu5J08j.js";
25
25
  import { a as searchHighlightRanges, c as searchSessionHistoryIndexAsync, i as highlightSegments, n as buildSessionSearchIndexAsync, r as filterSessionSearchCatalog, u as searchSessionIndexAsync } from "./history-search-C4bk1Wz_.js";
26
+ import { $ as McpAuthProvider, $t as filterChainFallbacks, An as discoverAgentsMd, Ar as toolCallPreview, At as useSettings, B as mcpToolsCachePath, C as addToSafelist, Cr as loadState, Ct as DEFAULT_SETTINGS, Dr as stripSpawnTokensLine, Dt as SETTINGS_TOGGLES, Er as serverToolResultSummary, Et as SETTINGS_INPUTS, F as oauthUsesManualCodePaste, G as useMcpToolToggleMap, Gt as useDiscoveryOptional, Ht as createDiscoverySlot, I as runOAuthLogin, J as indexOfServerRow, Jt as resolveConfig, Kt as ConfigProvider, L as supportsOAuth, M as fetchOAuthRedirect, Mr as updateToolEventOutcomes, Nr as usageByModel, Nt as resolveChipColor, Or as sumRunCosts, Ot as SettingsProvider, P as redirectUriFromAuthUrl, Pt as resolveTheme, Qt as chainsEqual, Rn as accentColor, Sr as listSessionMeta, St as useEnabledToggleSet, T as isOnSafelist, Tt as SETTINGS_CHOICES, U as subscribeMcpToolsCache, Ut as DiscoveryProvider, V as refreshMcpToolsCatalog, Wn as TODO_STATUS_GLYPHS, Wt as useDiscovery, X as createFileMcpCredentialStore, Xt as buildRunModel, Y as parentServerName, Yt as resolveStoragePaths, Zn as pruneTodosByRun, Zt as chainPositionOf, _r as compactSummaryEvent, _t as hintsLength, a as useSurfaces, an as countClaudeSessionFiles, at as InteractionsProvider, b as useSafeModeActions, bn as useUpdateCheck, br as eventsFromTurns, c as discoverSystemMd, cn as mcpsWritePath, ct as createInteractionTools, d as userSystemMdPath, dn as buildMcpServers, dr as buildBuildSystem, dt as pendingInteractionsFromTurns, en as providerKeySetChanged, er as useActiveTodos, et as useMcpAuthDispatch, f as useStreamBuffer, fn as dedupMcpServersByName, fr as buildPlanSystem, gn as tryOpenBrowser, gt as clipHintsToWidth, h as discoverProjectSkills, hr as createChatTurnsReadTool, ht as EMPTY_HINTS, i as useSelectStyle, in as useCompletion, it as ASK_USER_TOOL, j as formatPathForCwd, jr as toolResultText, k as suggestSafelistEntries, kn as chatAutoCompactBehavior, kt as clampFps, l as renderSystemMdSeed, ln as removeMcpServer, m as defaultSkillScanPaths, mn as discoverProjectMcps, mr as chatReferenceTokensForPrompt, mt as useInteractionsQueue, n as ThemeProvider, nt as getMcpAuthStatus, on as hasClaudeCodeSetup, ot as PRESENT_PLAN_TOOL, p as buildSkillsConfig, pr as envSection, pt as useInteractionsActions, q as buildVisibleMcpRows, qt as useConfig, r as useColors, rn as toggleChainEntry, s as useTheme, sn as importFromClaudeCode, st as buildResumedToolResultsTurn, t as computeTurnAnchors, tn as restoreChainFallbacks, tt as useMcpAuthState, u as userScopeOverridesLoad, un as upsertMcpServer, ut as makeRequestInteraction, v as writeSessionExport, vn as bootTick, vt as truncateTrailing, w as getSafelist, wr as marginTopFor, wt as SETTINGS_CATEGORIES, x as useSafeModeQueue, xr as lastContextSizeFromTurns, xt as listProjectFiles, y as SafeModeProvider, yn as buildUpdateHint, yr as deriveSessionTitle, yt as buildHints, z as loadMcpToolsCache } from "./transcript-anchors-uU_KZLsp.js";
26
27
  import { basename, delimiter, dirname, join, relative, resolve } from "node:path";
27
28
  import { Buffer } from "node:buffer";
28
29
  import * as fs from "node:fs";
@@ -2642,7 +2643,7 @@ const NO_SEARCH_MATCHES = {
2642
2643
  * needs a stable id on `StreamEvent` itself, which is a data-model change
2643
2644
  * rather than a rendering one.
2644
2645
  */
2645
- function Transcript({ events, settings, selectedTurnId = null, busy = false, searchQuery, searchPending = false, onLoadEarlier, onSelectTurn, onOpenTurn }) {
2646
+ function Transcript({ events, settings, selectedTurnId = null, busy = false, searchQuery, searchPending = false, interactionPending = false, onLoadEarlier, onSelectTurn, onOpenTurn }) {
2646
2647
  const COLOR = useColors();
2647
2648
  const showThrobber = busy && settings.showThrobber;
2648
2649
  const codeSettle = useRef({
@@ -2766,13 +2767,13 @@ function Transcript({ events, settings, selectedTurnId = null, busy = false, sea
2766
2767
  }, [events]);
2767
2768
  const modal = useModalState();
2768
2769
  useKeyboard(useCallback((key) => {
2769
- if (modal?.isOpen) return;
2770
+ if (modal?.isOpen || interactionPending) return;
2770
2771
  if (key.name !== "pageup" && key.name !== "pagedown") return;
2771
2772
  const scrollbox = scrollboxRef.current;
2772
2773
  if (!scrollbox) return;
2773
2774
  const direction = key.name === "pageup" ? -1 : 1;
2774
2775
  scrollbox.scrollBy(direction, key.shift ? "content" : "viewport");
2775
- }, [modal]));
2776
+ }, [modal, interactionPending]));
2776
2777
  const anchors = useMemo(() => computeTurnAnchors(items), [items]);
2777
2778
  const liveByItem = useMemo(() => {
2778
2779
  const map = /* @__PURE__ */ new Map();
@@ -6005,6 +6006,88 @@ function readToolsByServer(dataDir) {
6005
6006
  return out;
6006
6007
  }
6007
6008
  //#endregion
6009
+ //#region src/tui/editor-command.ts
6010
+ /**
6011
+ * `$EDITOR` / `$VISUAL` command parsing.
6012
+ *
6013
+ * These variables conventionally hold a command LINE, not a bare executable —
6014
+ * `code --wait`, `cursor --wait`, `subl -w`, `emacsclient -nw` are all normal
6015
+ * values. Spawning the whole string as argv[0] looks for a file literally named
6016
+ * `cursor --wait` and fails with ENOENT, so the flags have to be split off.
6017
+ *
6018
+ * Kept separate from `app.tsx` (and free of `node:child_process`) so the
6019
+ * tokenizer is unit-testable without pulling in the TUI.
6020
+ */
6021
+ /**
6022
+ * Split an editor command line into `[executable, ...args]`.
6023
+ *
6024
+ * Whitespace-separated, with single and double quotes honored so an editor
6025
+ * living under a path with spaces still works
6026
+ * (`"/Applications/My App/bin/ed" -w`). Quotes group and are stripped;
6027
+ * backslash escaping is deliberately NOT implemented, because on Windows
6028
+ * backslash is the path separator and treating `C:\tools\ed` as escapes would
6029
+ * corrupt far more command lines than it would rescue.
6030
+ *
6031
+ * Returns `[]` for an empty or whitespace-only spec, so callers can fall back
6032
+ * to the platform opener rather than spawning `''`.
6033
+ */
6034
+ function parseEditorCommand(spec) {
6035
+ const tokens = [];
6036
+ let current = "";
6037
+ let quote = null;
6038
+ let quoted = false;
6039
+ for (const ch of spec) {
6040
+ if (quote) {
6041
+ if (ch === quote) quote = null;
6042
+ else current += ch;
6043
+ continue;
6044
+ }
6045
+ if (ch === "\"" || ch === "'") {
6046
+ quote = ch;
6047
+ quoted = true;
6048
+ continue;
6049
+ }
6050
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
6051
+ if (current !== "" || quoted) {
6052
+ tokens.push(current);
6053
+ current = "";
6054
+ quoted = false;
6055
+ }
6056
+ continue;
6057
+ }
6058
+ current += ch;
6059
+ }
6060
+ if (current !== "" || quoted) tokens.push(current);
6061
+ return tokens;
6062
+ }
6063
+ /**
6064
+ * Re-quote a single argv token for a shell that will re-parse the joined command
6065
+ * line — i.e. `spawn(..., { shell: true })`, which Node uses on Windows so that
6066
+ * `.cmd` / `.bat` wrappers (how `code`, `cursor` and friends ship there) resolve.
6067
+ *
6068
+ * Node does NOT re-quote when `shell` is set: it joins `[cmd, ...args]` with
6069
+ * spaces and hands the string over. Since {@link parseEditorCommand} has already
6070
+ * STRIPPED the user's quotes, handing tokens back unquoted would split
6071
+ * `C:\Program Files\ed.exe` at the space and try to run `C:\Program`. This puts
6072
+ * the quotes back.
6073
+ *
6074
+ * Only tokens that need it are quoted, so a plain `code` stays `code`. A literal
6075
+ * double quote is doubled, which is how `cmd.exe` escapes one inside a quoted
6076
+ * run.
6077
+ *
6078
+ * The test covers `cmd.exe` METACHARACTERS, not just whitespace: `&`, `|`, `^`,
6079
+ * `<`, `>` and friends carry none of the obvious "needs quoting" signals, so a
6080
+ * repo cloned into `C:\work\a&b` would otherwise hand `C:\work\a&b\AGENTS.md`
6081
+ * over bare, and cmd would split it at the `&` and run the tail as a second
6082
+ * command. `%` and `!` are included so a path containing them can't be mistaken
6083
+ * for a variable reference at parse time.
6084
+ */
6085
+ function quoteForShell(token) {
6086
+ if (token === "") return "\"\"";
6087
+ if (!/[\s"&|^<>%()!]/.test(token)) return token;
6088
+ return `"${token.replace(/"/g, "\"\"")}"`;
6089
+ }
6090
+ //#endregion
6008
6091
  //#region src/tui/extension-modal.ts
6009
6092
  /**
6010
6093
  * Resolve a modal contribution to a renderable node. Eager nodes pass through;
@@ -8746,7 +8829,7 @@ function InteractionBlock({ request, onResolve }) {
8746
8829
  onResolve
8747
8830
  });
8748
8831
  }
8749
- function InteractionShell({ title, maxHeight, children }) {
8832
+ function InteractionShell({ title, maxHeight, minHeight, children }) {
8750
8833
  return /* @__PURE__ */ jsx$1("box", {
8751
8834
  title,
8752
8835
  style: {
@@ -8755,14 +8838,48 @@ function InteractionShell({ title, maxHeight, children }) {
8755
8838
  paddingLeft: 1,
8756
8839
  paddingRight: 1,
8757
8840
  flexDirection: "column",
8758
- flexShrink: 0,
8841
+ flexShrink: 1,
8759
8842
  overflow: "hidden",
8760
- ...maxHeight !== void 0 ? { maxHeight } : {}
8843
+ ...maxHeight !== void 0 ? { maxHeight } : {},
8844
+ minHeight: minHeight ?? 0
8761
8845
  },
8762
8846
  children
8763
8847
  });
8764
8848
  }
8765
8849
  /**
8850
+ * Resolve a key event to a page-scroll direction, or `null` when it isn't one.
8851
+ *
8852
+ * `pageup` / `pagedown` only, and modifier-free. `shift` is rejected because
8853
+ * `shift+page*` means jump-to-top/bottom for the transcript — a panel must not
8854
+ * quietly redefine it as a page step. `ctrl+u` / `ctrl+d` are deliberately NOT
8855
+ * used: OpenTUI's `defaultTextareaKeyBindings` maps `ctrl+u` to
8856
+ * delete-to-line-start, `ctrl+d` to delete and `ctrl+shift+d` to delete-line,
8857
+ * and a textarea question is focused on exactly the same condition as these
8858
+ * handlers — so those chords would delete the user's answer AND scroll in one
8859
+ * keypress. The page keys appear nowhere in that table.
8860
+ */
8861
+ function pageScrollDirection(key) {
8862
+ if (key.ctrl || key.meta || key.option || key.shift) return null;
8863
+ if (key.name === "pageup") return -1;
8864
+ if (key.name === "pagedown") return 1;
8865
+ return null;
8866
+ }
8867
+ /**
8868
+ * Scroll one page, keeping a 1-row overlap so the line at the fold stays
8869
+ * visible (standard pager UX).
8870
+ *
8871
+ * Measures `viewport.height`, NOT the renderable's own `height`: the latter
8872
+ * includes anything outside the scrolling viewport, which made the step
8873
+ * overshoot what the user could actually see. `scrollBy` clamps at both ends.
8874
+ */
8875
+ function pageScrollBy(scrollbox, direction) {
8876
+ const page = Math.max(1, scrollbox.viewport.height - 1);
8877
+ scrollbox.scrollBy({
8878
+ x: 0,
8879
+ y: direction * page
8880
+ });
8881
+ }
8882
+ /**
8766
8883
  * The plan BODY half of plan review — pairs with {@link InteractionBlock}
8767
8884
  * (which renders only the title + decision picker for `plan` requests).
8768
8885
  * The chat screen hides the Transcript (same `visible: false` pattern as
@@ -8779,15 +8896,10 @@ function PlanReviewPanel({ request }) {
8779
8896
  const { title, plan, steps } = request.payload;
8780
8897
  useKeyboard((key) => {
8781
8898
  if (!focused) return;
8782
- if (key.ctrl || key.meta || key.option || key.shift) return;
8783
- if (key.name !== "pageup" && key.name !== "pagedown") return;
8899
+ const direction = pageScrollDirection(key);
8784
8900
  const sb = scrollboxRef.current;
8785
- if (!sb) return;
8786
- const page = Math.max(1, sb.viewport.height - 1);
8787
- sb.scrollBy({
8788
- x: 0,
8789
- y: key.name === "pageup" ? -page : page
8790
- });
8901
+ if (direction === null || !sb) return;
8902
+ pageScrollBy(sb, direction);
8791
8903
  });
8792
8904
  return /* @__PURE__ */ jsxs("box", {
8793
8905
  title: " plan ",
@@ -8978,30 +9090,76 @@ function PlanInteractionBlock({ request, onResolve }) {
8978
9090
  }, "pick");
8979
9091
  }
8980
9092
  /**
8981
- * Cap the question-wizard's height to ~60% of the terminal so the
8982
- * transcript above stays readable when the model fires a long batch.
8983
- * The inner scrollbox handles overflow; the active question is
8984
- * scrolled into view on every advance.
9093
+ * Rows the wizard leaves to the transcript above it, and the smallest cap we
9094
+ * will ever impose.
9095
+ *
9096
+ * The cap used to be `'60%'`. A PERCENTAGE `maxHeight` resolves against the
9097
+ * parent's height, and the wizard's parent is a content-sized column whose
9098
+ * height is indefinite — so Yoga had nothing to take 60% OF and the cap
9099
+ * silently did not apply. The shell then grew to whatever the (under-)estimated
9100
+ * content claimed and drew its border there, which is how questions ended up
9101
+ * rendered below a closed frame. Resolving the cap to a ROW COUNT from the
9102
+ * terminal's own dimensions removes that failure mode entirely.
8985
9103
  */
8986
- const QUESTION_WIZARD_MAX_HEIGHT = "60%";
9104
+ const WIZARD_RESERVED_ROWS = 8;
9105
+ const WIZARD_MIN_ROWS = 8;
9106
+ /**
9107
+ * Cap the question wizard's height in ROWS, derived from the terminal.
9108
+ *
9109
+ * Three guards, applied outward:
9110
+ * - **{@link WIZARD_RESERVED_ROWS}** keeps the wizard clear of the top so some
9111
+ * transcript stays visible. This dominates below ~32 rows, so the docstring
9112
+ * claim of "~75%" only actually bites on tall terminals; shorter ones get
9113
+ * `rows − 8`.
9114
+ * - **{@link WIZARD_MIN_ROWS}** floors the result so a short terminal still
9115
+ * gets a usable form rather than a two-line slot.
9116
+ * - **`terminalRows`** ceilings it. Without this the floor wins outright below
9117
+ * 8 rows and the function returns a cap TALLER than the viewport — the exact
9118
+ * class of bug this cap exists to prevent (a frame positioned outside what
9119
+ * the terminal can show). On a terminal that small the wizard takes the
9120
+ * whole screen, which is the honest outcome.
9121
+ */
9122
+ function wizardMaxRows(terminalRows) {
9123
+ const generous = Math.floor(terminalRows * .75);
9124
+ const leaveRoom = terminalRows - WIZARD_RESERVED_ROWS;
9125
+ return Math.min(terminalRows, Math.max(WIZARD_MIN_ROWS, Math.min(generous, leaveRoom)));
9126
+ }
9127
+ /**
9128
+ * Rows a string occupies once soft-wrapped into `width` columns.
9129
+ *
9130
+ * The sizing estimates used to count `\n` only, with a comment waving off
9131
+ * wrapping as drift the scrollbox would absorb. It doesn't: the estimate feeds
9132
+ * the scrollbox's explicit height, so under-counting makes the panel shorter
9133
+ * than its content and the frame lands mid-text. A wrapped markdown intro or a
9134
+ * wrapped choice description is the common case, not an edge one.
9135
+ *
9136
+ * Word-boundary-agnostic on purpose: `ceil(len / width)` stays within a row of
9137
+ * real word wrapping, and being one row over costs nothing.
9138
+ *
9139
+ * Assumes one column per code point, so CJK and wide emoji (two cells each)
9140
+ * under-count by up to half — the same assumption `truncateToCols` makes in
9141
+ * `settings-modal.tsx`. The consequence is a panel sized short of its content,
9142
+ * not content the user cannot reach: the scrollbox measures its own children for
9143
+ * `scrollHeight`, so the tail stays scrollable either way.
9144
+ */
9145
+ function wrappedRows(text, width) {
9146
+ if (width <= 0) return text.split("\n").length;
9147
+ let rows = 0;
9148
+ for (const line of text.split("\n")) rows += Math.max(1, Math.ceil(line.length / width));
9149
+ return rows;
9150
+ }
8987
9151
  /**
8988
9152
  * Rows the intro contributes to the wizard's content-sized height.
8989
9153
  *
8990
- * A line count, plus the one-row bottom margin. Wrapping makes this a lower
8991
- * bound, and long intros are capped: past {@link INTRO_ROWS_CAP} the panel would
8992
- * hit its 60% ceiling anyway and the scrollbox takes over, so counting further
8993
- * buys nothing. Zero when there is no intro, which keeps a bare confirm question
8994
- * at its ~4-row size.
9154
+ * A wrapped line count, plus the one-row bottom margin. Long intros are capped:
9155
+ * past {@link INTRO_ROWS_CAP} the panel is at its {@link wizardMaxRows} ceiling
9156
+ * anyway and the scrollbox takes over, so counting further buys nothing. Zero
9157
+ * when there is no intro, which keeps a bare confirm question at its ~4-row size.
8995
9158
  */
8996
- const INTRO_ROWS_CAP = 12;
8997
- function introRows(intro) {
9159
+ const INTRO_ROWS_CAP = 24;
9160
+ function introRows(intro, width = Number.POSITIVE_INFINITY) {
8998
9161
  if (!intro) return 0;
8999
- let lines = 1;
9000
- for (let i = 0; i < intro.length; i++) {
9001
- if (intro[i] === "\n") lines++;
9002
- if (lines > INTRO_ROWS_CAP) return 13;
9003
- }
9004
- return lines + 1;
9162
+ return Math.min(INTRO_ROWS_CAP, wrappedRows(intro, width)) + 1;
9005
9163
  }
9006
9164
  /**
9007
9165
  * Estimated render height (rows) of one {@link QuestionRow}, mirroring its
@@ -9011,15 +9169,17 @@ function introRows(intro) {
9011
9169
  * confirm pair (2), or the 1-line summary / `waiting…` placeholder.
9012
9170
  *
9013
9171
  * Drives content-sized wizards: a single confirm question claims ~4 rows
9014
- * instead of stretching to the 60% cap (which stays as the overflow
9015
- * ceiling — an under-estimate degrades to scrolling, never clipping the
9016
- * active input out of reach, thanks to scroll-into-view on advance).
9017
- * Multi-line prompts/descriptions are counted by their newlines; soft
9018
- * wrapping is deliberately ignored — the scrollbox absorbs the drift.
9172
+ * instead of stretching to the {@link wizardMaxRows} ceiling, which stays as
9173
+ * the overflow bound — an under-estimate degrades to scrolling, never clipping
9174
+ * the active input out of reach, thanks to scroll-into-view on advance.
9175
+ *
9176
+ * Prompts, descriptions, and each choice's own description are measured with
9177
+ * {@link wrappedRows} at `width`, so a wrapped line is counted as the rows it
9178
+ * actually occupies.
9019
9179
  */
9020
- function estimateQuestionRowHeight(question, status) {
9021
- const lines = (s) => s.split("\n").length;
9022
- let height = lines(question.prompt) + (question.description ? lines(question.description) : 0) + 1;
9180
+ function estimateQuestionRowHeight(question, status, width = Number.POSITIVE_INFINITY) {
9181
+ const textWidth = width === Number.POSITIVE_INFINITY ? width : Math.max(1, width - 5);
9182
+ let height = wrappedRows(question.prompt, textWidth) + (question.description ? wrappedRows(question.description, textWidth) : 0) + 1;
9023
9183
  if (status !== "active") return height + 1;
9024
9184
  switch (question.type) {
9025
9185
  case "text":
@@ -9029,7 +9189,8 @@ function estimateQuestionRowHeight(question, status) {
9029
9189
  height += 6;
9030
9190
  break;
9031
9191
  case "select":
9032
- height += question.choices.length + (question.multi ? 1 : 0);
9192
+ for (const choice of question.choices) height += 1 + (choice.description ? wrappedRows(choice.description, textWidth) : 0);
9193
+ height += question.multi ? 1 : 0;
9033
9194
  break;
9034
9195
  case "confirm":
9035
9196
  height += 2;
@@ -9077,6 +9238,13 @@ function QuestionInteractionBlock({ request, onResolve }) {
9077
9238
  if (!canGoBack) return;
9078
9239
  setActiveIndex((i) => Math.max(0, i - 1));
9079
9240
  });
9241
+ useKeyboard((key) => {
9242
+ if (!focused) return;
9243
+ const direction = pageScrollDirection(key);
9244
+ const scrollbox = scrollboxRef.current;
9245
+ if (direction === null || !scrollbox) return;
9246
+ pageScrollBy(scrollbox, direction);
9247
+ });
9080
9248
  useEffect(() => {
9081
9249
  const scrollbox = scrollboxRef.current;
9082
9250
  if (!scrollbox) return;
@@ -9087,14 +9255,30 @@ function QuestionInteractionBlock({ request, onResolve }) {
9087
9255
  });
9088
9256
  return () => cancelAnimationFrame(handle);
9089
9257
  }, [activeIndex, questions]);
9090
- const contentRows = useMemo(() => introRows(intro) + questions.reduce((sum, q, i) => sum + estimateQuestionRowHeight(q, i < activeIndex ? "done" : i === activeIndex ? "active" : "pending"), 0), [
9258
+ const { width: termCols, height: termRows } = useTerminalDimensions();
9259
+ const maxRows = wizardMaxRows(termRows);
9260
+ const contentCols = Math.max(1, termCols - 4);
9261
+ const measuredRows = useMemo(() => introRows(intro, contentCols) + questions.reduce((sum, q, i) => sum + estimateQuestionRowHeight(q, i < activeIndex ? "done" : i === activeIndex ? "active" : "pending", contentCols), 0), [
9091
9262
  intro,
9092
9263
  questions,
9093
- activeIndex
9264
+ activeIndex,
9265
+ contentCols
9094
9266
  ]);
9267
+ const highWaterRef = useRef({
9268
+ key: "",
9269
+ rows: 0
9270
+ });
9271
+ const highWaterKey = `${request.id}:${contentCols}`;
9272
+ if (highWaterRef.current.key !== highWaterKey) highWaterRef.current = {
9273
+ key: highWaterKey,
9274
+ rows: 0
9275
+ };
9276
+ highWaterRef.current.rows = Math.max(highWaterRef.current.rows, measuredRows);
9277
+ const contentRows = Math.min(highWaterRef.current.rows, maxRows);
9095
9278
  return /* @__PURE__ */ jsxs(InteractionShell, {
9096
9279
  title: questions.length > 1 ? ` answer ${activeIndex + 1}/${questions.length} · esc to abort run ` : " answer · esc to abort run ",
9097
- maxHeight: QUESTION_WIZARD_MAX_HEIGHT,
9280
+ maxHeight: maxRows,
9281
+ minHeight: Math.min(maxRows, contentRows + 2, Math.max(WIZARD_MIN_ROWS, termRows - 2)),
9098
9282
  children: [/* @__PURE__ */ jsxs("scrollbox", {
9099
9283
  ref: scrollboxRef,
9100
9284
  focusable: false,
@@ -11651,6 +11835,7 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
11651
11835
  searchQuery: transcriptSearchActive ? transcriptSearchQuery : void 0,
11652
11836
  searchPending: transcriptSearchSearching,
11653
11837
  busy: !transcriptSearchActive && busy && !pending && !pendingInteraction,
11838
+ interactionPending: pendingInteraction != null,
11654
11839
  onLoadEarlier: transcriptSearchActive ? void 0 : onLoadEarlierTranscript
11655
11840
  })
11656
11841
  }),
@@ -13810,6 +13995,62 @@ function parentExtensionName(row) {
13810
13995
  function indexOfExtensionRow(rows, extName) {
13811
13996
  return rows.findIndex((r) => r.kind === "extension" && r.ext.name === extName);
13812
13997
  }
13998
+ /**
13999
+ * Filter segments by the active query and resolve each one's expanded state.
14000
+ *
14001
+ * Deliberately does NOT auto-expand matches the way the Extensions tab does:
14002
+ * the search corpus includes each segment's full text, so a query typically
14003
+ * matches deep inside a long document — auto-expanding every hit would dump
14004
+ * thousands of lines into the viewport on a keystroke. The user expands the row
14005
+ * they care about.
14006
+ */
14007
+ function buildVisibleInstructionRows(opts) {
14008
+ const { segments, expanded, queryActive, matchSegment } = opts;
14009
+ const out = [];
14010
+ for (const segment of segments) {
14011
+ if (queryActive && !matchSegment(segment)) continue;
14012
+ out.push({
14013
+ segment,
14014
+ expanded: expanded.has(segment.id)
14015
+ });
14016
+ }
14017
+ return out;
14018
+ }
14019
+ /**
14020
+ * Split a segment's text into renderable preview lines, clamped to
14021
+ * {@link INSTRUCTION_PREVIEW_LINE_CAP}.
14022
+ *
14023
+ * `hidden` is how many lines were dropped off the tail, so the view can render
14024
+ * an honest "+N more lines" marker instead of silently truncating. Tabs are
14025
+ * expanded to two spaces because the terminal text renderable doesn't
14026
+ * interpret `\t`, and `\r` is stripped so CRLF files don't leave artefacts at
14027
+ * every line end.
14028
+ */
14029
+ function instructionPreviewLines(text) {
14030
+ const all = text.replace(/\r\n?/g, "\n").replace(/\t/g, " ").split("\n");
14031
+ while (all.length > 0 && all[all.length - 1].trim() === "") all.pop();
14032
+ if (all.length <= 400) return {
14033
+ lines: all,
14034
+ hidden: 0
14035
+ };
14036
+ return {
14037
+ lines: all.slice(0, 400),
14038
+ hidden: all.length - 400
14039
+ };
14040
+ }
14041
+ /**
14042
+ * Short badge for a segment's origin — the "where does this come from" column.
14043
+ * `'file'` is intentionally the only one that reads like a location, since it's
14044
+ * the only origin the user can act on.
14045
+ */
14046
+ function instructionOriginBadge(segment) {
14047
+ switch (segment.origin) {
14048
+ case "file": return "file";
14049
+ case "generated": return "generated";
14050
+ case "mcp": return "mcp server";
14051
+ case "built-in": return "built-in";
14052
+ }
14053
+ }
13813
14054
  //#endregion
13814
14055
  //#region src/tui/logs-view.ts
13815
14056
  /** Level order for the tab's min-level filter (matches the logger's ordering). */
@@ -14606,13 +14847,14 @@ const TAB_LABELS = {
14606
14847
  ...Object.fromEntries(SETTINGS_CATEGORIES.map((c) => [c.id, c.label])),
14607
14848
  keybindings: "Keybindings",
14608
14849
  authentication: "Authentication",
14850
+ instructions: "Instructions",
14609
14851
  skills: "Skills",
14610
14852
  mcps: "MCP servers",
14611
14853
  extensions: "Extensions & Plugins",
14612
14854
  logs: "Logs"
14613
14855
  };
14614
14856
  function isCategoryTab(id) {
14615
- return id !== "skills" && id !== "mcps" && id !== "keybindings" && id !== "authentication" && id !== "extensions" && id !== "logs";
14857
+ return id !== "skills" && id !== "mcps" && id !== "keybindings" && id !== "authentication" && id !== "extensions" && id !== "instructions" && id !== "logs";
14616
14858
  }
14617
14859
  function anchorIdFor(index) {
14618
14860
  return `settings-row-${index}`;
@@ -14889,6 +15131,16 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14889
15131
  const mcpToolToggleMap = useMcpToolToggleMap({ catalogByServer: mcpToolsByServer });
14890
15132
  const [expandedMcps, setExpandedMcps] = useState(() => /* @__PURE__ */ new Set());
14891
15133
  const [expandedExtensions, setExpandedExtensions] = useState(() => /* @__PURE__ */ new Set());
15134
+ const [expandedInstructions, setExpandedInstructions] = useState(() => /* @__PURE__ */ new Set());
15135
+ const [instructionsNotice, setInstructionsNotice] = useState(null);
15136
+ const [instructionSegments, setInstructionSegments] = useState([]);
15137
+ const [instructionsLoading, setInstructionsLoading] = useState(false);
15138
+ const [instructionsError, setInstructionsError] = useState(void 0);
15139
+ const [instructionsLive, setInstructionsLive] = useState(true);
15140
+ const [instructionsRefreshKey, setInstructionsRefreshKey] = useState(0);
15141
+ const [instructionsShadowedSystem, setInstructionsShadowedSystem] = useState(void 0);
15142
+ const [instructionsCanOverride, setInstructionsCanOverride] = useState(true);
15143
+ const [instructionsOverrideBlockedBy, setInstructionsOverrideBlockedBy] = useState(void 0);
14892
15144
  const onRefreshMcpToolsRef = useRef(actions?.onRefreshMcpTools);
14893
15145
  onRefreshMcpToolsRef.current = actions?.onRefreshMcpTools;
14894
15146
  const [mcpToolsRefreshing, setMcpToolsRefreshing] = useState(false);
@@ -14905,6 +15157,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14905
15157
  const tabs = [...SETTINGS_CATEGORIES.map((c) => c.id)];
14906
15158
  if (keybindings) tabs.push("keybindings");
14907
15159
  if (authentication) tabs.push("authentication");
15160
+ if (actions?.getContextSegments) tabs.push("instructions");
14908
15161
  tabs.push("skills", "mcps");
14909
15162
  if (extensionsCatalogProp || extensionsInactiveProp) tabs.push("extensions");
14910
15163
  if (logs) tabs.push("logs");
@@ -14912,6 +15165,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14912
15165
  }, [
14913
15166
  keybindings,
14914
15167
  authentication,
15168
+ actions?.getContextSegments,
14915
15169
  extensionsCatalogProp,
14916
15170
  extensionsInactiveProp,
14917
15171
  logs
@@ -15090,6 +15344,32 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15090
15344
  for (const d of mcpsCatalogLiveRef.current) mcpColdProbedRef.current.add(d.config.name);
15091
15345
  fireMcpToolsRefresh();
15092
15346
  }, [activeTab, fireMcpToolsRefresh]);
15347
+ useEffect(() => {
15348
+ const fetchSegments = actions?.getContextSegments;
15349
+ if (activeTab !== "instructions" || !fetchSegments) return;
15350
+ let cancelled = false;
15351
+ setInstructionsLoading(true);
15352
+ Promise.resolve(fetchSegments()).then((result) => {
15353
+ if (cancelled) return;
15354
+ setInstructionSegments(result.segments);
15355
+ setInstructionsLive(result.live);
15356
+ setInstructionsCanOverride(result.canOverrideSystem);
15357
+ setInstructionsOverrideBlockedBy(result.overrideBlockedBy);
15358
+ setInstructionsShadowedSystem(result.shadowedSystemPath);
15359
+ setInstructionsError(void 0);
15360
+ }).catch((err) => {
15361
+ if (!cancelled) setInstructionsError(errorMessage(err));
15362
+ }).finally(() => {
15363
+ if (!cancelled) setInstructionsLoading(false);
15364
+ });
15365
+ return () => {
15366
+ cancelled = true;
15367
+ };
15368
+ }, [
15369
+ activeTab,
15370
+ actions?.getContextSegments,
15371
+ instructionsRefreshKey
15372
+ ]);
15093
15373
  useEffect(() => {
15094
15374
  if (activeTab !== "mcps") return;
15095
15375
  const cold = mcpsCatalog.filter((d) => mcpsToggle.enabledSet.has(d.config.name) && (mcpToolsByServer[d.config.name]?.length ?? 0) === 0 && !mcpColdProbedRef.current.has(d.config.name));
@@ -15217,12 +15497,24 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15217
15497
  mcpToolsByServer
15218
15498
  ]);
15219
15499
  const filteredProviders = useMemo(() => (authentication?.providers ?? []).filter((p) => matchesTerms(providerCorpus(p), queryTerms)), [authentication?.providers, queryTerms]);
15500
+ const instructionVisibleRows = useMemo(() => buildVisibleInstructionRows({
15501
+ segments: instructionSegments,
15502
+ expanded: expandedInstructions,
15503
+ queryActive,
15504
+ matchSegment: (seg) => matchesTerms(instructionCorpus(seg), queryTerms)
15505
+ }), [
15506
+ instructionSegments,
15507
+ expandedInstructions,
15508
+ queryActive,
15509
+ queryTerms
15510
+ ]);
15220
15511
  const authRowCount = filteredProviders.length;
15221
15512
  const filteredSize = {
15222
15513
  ...Object.fromEntries(SETTINGS_CATEGORIES.map((c) => [c.id, filteredByCategory[c.id].length])),
15223
15514
  skills: filteredSkills.length,
15224
15515
  mcps: mcpVisibleRows.length,
15225
15516
  extensions: extensionVisibleRows.length,
15517
+ instructions: instructionVisibleRows.length,
15226
15518
  keybindings: 0,
15227
15519
  logs: 0,
15228
15520
  authentication: authRowCount
@@ -15320,8 +15612,20 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15320
15612
  });
15321
15613
  }, [extensionVisibleRows]);
15322
15614
  const toggleExpandFocusedExtensionRow = useCallback(() => toggleExpandExtensionRowAt(cursor), [toggleExpandExtensionRowAt, cursor]);
15615
+ const toggleExpandInstructionRowAt = useCallback((index) => {
15616
+ const row = instructionVisibleRows[index];
15617
+ if (!row) return;
15618
+ setExpandedInstructions((prev) => {
15619
+ const next = new Set(prev);
15620
+ if (!next.delete(row.segment.id)) next.add(row.segment.id);
15621
+ return next;
15622
+ });
15623
+ }, [instructionVisibleRows]);
15624
+ const toggleExpandFocusedInstructionRow = useCallback(() => toggleExpandInstructionRowAt(cursor), [toggleExpandInstructionRowAt, cursor]);
15323
15625
  const mcpRowCount = mcpVisibleRows.length;
15324
15626
  const extensionRowCount = extensionVisibleRows.length;
15627
+ const instructionRowCount = instructionVisibleRows.length;
15628
+ const instructionExpandedCount = expandedInstructions.size;
15325
15629
  useEffect(() => {
15326
15630
  if (activeTab === "keybindings" || activeTab === "logs") return;
15327
15631
  const sb = scrollboxRef.current;
@@ -15345,7 +15649,9 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15345
15649
  activeTab,
15346
15650
  query,
15347
15651
  mcpRowCount,
15348
- extensionRowCount
15652
+ extensionRowCount,
15653
+ instructionRowCount,
15654
+ instructionExpandedCount
15349
15655
  ]);
15350
15656
  useEffect(() => {
15351
15657
  if (mcpRowCount === 0) return;
@@ -15367,6 +15673,19 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15367
15673
  };
15368
15674
  });
15369
15675
  }, [extensionRowCount]);
15676
+ useEffect(() => {
15677
+ if (instructionRowCount === 0) return;
15678
+ setCursorByTab((prev) => {
15679
+ if (prev.instructions < instructionRowCount) return prev;
15680
+ return {
15681
+ ...prev,
15682
+ instructions: instructionRowCount - 1
15683
+ };
15684
+ });
15685
+ }, [instructionRowCount]);
15686
+ useEffect(() => {
15687
+ setInstructionsNotice(null);
15688
+ }, [cursor, activeTab]);
15370
15689
  useEffect(() => {
15371
15690
  if (activeTab !== "keybindings") return;
15372
15691
  const sb = scrollboxRef.current;
@@ -15466,6 +15785,22 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15466
15785
  } else if (row.kind === "tool") mcpToolToggleMap[row.serverName]?.toggle(row.tool.name);
15467
15786
  return;
15468
15787
  }
15788
+ if (activeTab === "instructions") {
15789
+ const row = instructionVisibleRows[index];
15790
+ if (!row) return;
15791
+ const { segment } = row;
15792
+ if (segment.id === "system" && !segment.path && actions?.onCreateSystemPrompt) {
15793
+ if (!instructionsCanOverride) {
15794
+ setInstructionsNotice(instructionsOverrideBlockedBy === "scope" ? "Your instruction scope excludes user-scope files, so a SYSTEM.md in your home directory would not be loaded. Change it on the Agent tab. Press tab to read the prompt inline." : "This profile supplies its own system prompt — a SYSTEM.md override would not apply. Press tab to read it inline.");
15795
+ return;
15796
+ }
15797
+ Promise.resolve(actions.onCreateSystemPrompt()).catch((err) => setInstructionsNotice(`Could not create SYSTEM.md: ${errorMessage(err)}`));
15798
+ return;
15799
+ }
15800
+ if (segment.path) Promise.resolve(actions?.onOpenInstructionsFile?.(segment.path)).catch((err) => setInstructionsNotice(`Could not open ${segment.label}: ${errorMessage(err)}`));
15801
+ else setInstructionsNotice(`"${segment.label}" is ${instructionOriginBadge(segment)} — no file to open. Press tab to read it inline.`);
15802
+ return;
15803
+ }
15469
15804
  if (activeTab === "keybindings") {
15470
15805
  actions?.onOpenKeybindings?.();
15471
15806
  return;
@@ -15492,7 +15827,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15492
15827
  fireMcpToolsRefresh,
15493
15828
  mcpToolToggleMap,
15494
15829
  actions,
15495
- filteredProviders
15830
+ filteredProviders,
15831
+ instructionVisibleRows,
15832
+ instructionsCanOverride,
15833
+ instructionsOverrideBlockedBy
15496
15834
  ]);
15497
15835
  /**
15498
15836
  * True while an inline editor or overlay dialog owns the modal's input
@@ -15549,6 +15887,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15549
15887
  activateRowAt(index);
15550
15888
  return;
15551
15889
  }
15890
+ if (activeTab === "instructions") {
15891
+ toggleExpandInstructionRowAt(index);
15892
+ return;
15893
+ }
15552
15894
  if (activeTab === "skills") return;
15553
15895
  if (isCategoryTab(activeTab) && filteredByCategory[activeTab][index]?.kind === "toggle") return;
15554
15896
  activateRowAt(index);
@@ -15560,6 +15902,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15560
15902
  filteredByCategory,
15561
15903
  toggleExpandMcpRowAt,
15562
15904
  toggleExpandExtensionRowAt,
15905
+ toggleExpandInstructionRowAt,
15563
15906
  activateRowAt
15564
15907
  ]);
15565
15908
  /** Tab-strip clicks — same input-capture guard as row clicks. */
@@ -15628,6 +15971,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15628
15971
  toggleExpandFocusedExtensionRow();
15629
15972
  return;
15630
15973
  }
15974
+ if (activeTab === "instructions" && key.name === "tab") {
15975
+ toggleExpandFocusedInstructionRow();
15976
+ return;
15977
+ }
15631
15978
  if (key.name === "return") {
15632
15979
  activateRowAt(cursor);
15633
15980
  return;
@@ -15832,6 +16179,14 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15832
16179
  actions.onRefreshSkills();
15833
16180
  return;
15834
16181
  }
16182
+ if (activeTab === "instructions" && actions?.onRefreshInstructions) {
16183
+ setInstructionsNotice("reloading…");
16184
+ actions.onRefreshInstructions().then((outcome) => {
16185
+ setInstructionsRefreshKey((n) => n + 1);
16186
+ setInstructionsNotice(outcome === "rebuilt" ? "reloaded — agent rebuilt; MCP servers reconnect on the next turn" : outcome === "queued" ? "reloaded — agent rebuild queued until the current run finishes" : "reloaded from disk");
16187
+ }).catch((err) => setInstructionsNotice(`Refresh failed: ${errorMessage(err)}`));
16188
+ return;
16189
+ }
15835
16190
  if (activeTab === "extensions" && actions?.onRefreshPlugins) {
15836
16191
  actions.onRefreshPlugins();
15837
16192
  return;
@@ -15981,6 +16336,21 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15981
16336
  contentCols,
15982
16337
  authState
15983
16338
  }),
16339
+ activeTab === "instructions" && /* @__PURE__ */ jsx$1(InstructionsList, {
16340
+ onRowClick: clickRowAt,
16341
+ rows: instructionVisibleRows,
16342
+ totalCount: instructionSegments.length,
16343
+ loading: instructionsLoading,
16344
+ error: instructionsError,
16345
+ live: instructionsLive,
16346
+ shadowedSystemPath: instructionsShadowedSystem,
16347
+ canOverrideSystem: instructionsCanOverride,
16348
+ overrideBlockedBy: instructionsOverrideBlockedBy,
16349
+ cursor,
16350
+ highlightBg: SURFACE.selection,
16351
+ query,
16352
+ contentCols
16353
+ }),
15984
16354
  activeTab === "keybindings" && keybindings && /* @__PURE__ */ jsx$1(KeybindingsList, {
15985
16355
  bindings: keybindings,
15986
16356
  extensionRows: extensionKeybindings,
@@ -16013,6 +16383,13 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
16013
16383
  style: { flexShrink: 0 },
16014
16384
  children: renderMcpDetailPanel(focusedMcp, focusedMcpStatus, COLOR)
16015
16385
  }),
16386
+ activeTab === "instructions" && instructionsNotice && /* @__PURE__ */ jsx$1("box", {
16387
+ style: { flexShrink: 0 },
16388
+ children: /* @__PURE__ */ jsx$1("text", {
16389
+ fg: COLOR.mute,
16390
+ children: instructionsNotice
16391
+ })
16392
+ }),
16016
16393
  activeTab === "extensions" && extEditNotice && /* @__PURE__ */ jsx$1("box", {
16017
16394
  style: { flexShrink: 0 },
16018
16395
  children: /* @__PURE__ */ jsx$1("text", {
@@ -16125,7 +16502,9 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
16125
16502
  if (row?.kind !== "config" || row.item.type !== "string" || !row.item.secret) return false;
16126
16503
  const stored = settings.extensionConfig?.[row.extName]?.[row.item.key];
16127
16504
  return stored !== void 0 && stored !== row.item.default;
16128
- })()
16505
+ })(),
16506
+ focusedInstructionOpenable: activeTab === "instructions" && instructionVisibleRows[cursor]?.segment.path !== void 0,
16507
+ focusedInstructionCreatable: activeTab === "instructions" && actions?.onCreateSystemPrompt !== void 0 && instructionsCanOverride && instructionVisibleRows[cursor]?.segment.id === "system" && instructionVisibleRows[cursor]?.segment.path === void 0
16129
16508
  })
16130
16509
  }),
16131
16510
  extInstallActive && /* @__PURE__ */ jsx$1(ExtensionInstallDialog, {
@@ -17225,6 +17604,101 @@ function renderEmptyToolsListRow({ serverName, anchorId, focused, bg, COLOR, ref
17225
17604
  })]
17226
17605
  }, `tool-empty:${serverName}`);
17227
17606
  }
17607
+ function InstructionsList({ rows, totalCount, loading, error, live, shadowedSystemPath, canOverrideSystem, overrideBlockedBy, cursor, highlightBg, query, contentCols, onRowClick }) {
17608
+ const COLOR = useColors();
17609
+ if (error) return /* @__PURE__ */ jsx$1("text", {
17610
+ fg: COLOR.error,
17611
+ children: `Could not read context: ${error}`
17612
+ });
17613
+ if (loading && totalCount === 0) return /* @__PURE__ */ jsx$1(EmptyRow, { label: "reading session context…" });
17614
+ if (totalCount === 0) return /* @__PURE__ */ jsx$1(EmptyRow, { label: "this profile loads no default context" });
17615
+ if (rows.length === 0) return /* @__PURE__ */ jsx$1(EmptyRow, { label: `no context matches "${query}"` });
17616
+ const INDENT = " ";
17617
+ const detailCols = Math.max(0, contentCols - 1 - 1 - 4);
17618
+ return /* @__PURE__ */ jsxs("box", {
17619
+ style: { flexDirection: "column" },
17620
+ children: [
17621
+ !live && /* @__PURE__ */ jsx$1("text", {
17622
+ fg: COLOR.mute,
17623
+ children: " No active session — showing what your next one will load. MCP and extension context is added at run time."
17624
+ }),
17625
+ shadowedSystemPath !== void 0 && /* @__PURE__ */ jsx$1("text", {
17626
+ fg: COLOR.mute,
17627
+ children: canOverrideSystem ? ` ${displayPath$1(shadowedSystemPath, homedir())} is shadowed by the project SYSTEM.md and is not loaded.` : overrideBlockedBy === "scope" ? ` ${displayPath$1(shadowedSystemPath, homedir())} is not loaded — your instruction scope excludes it.` : ` ${displayPath$1(shadowedSystemPath, homedir())} is not loaded — this profile supplies its own system prompt.`
17628
+ }),
17629
+ rows.map(({ segment, expanded }, i) => {
17630
+ const focused = i === cursor;
17631
+ const bg = focused ? highlightBg : void 0;
17632
+ const openable = segment.path !== void 0;
17633
+ const secondLine = segment.path ? displayPath$1(segment.path, homedir()) : segment.detail ?? "";
17634
+ const preview = expanded ? instructionPreviewLines(segment.text) : null;
17635
+ return /* @__PURE__ */ jsxs("box", {
17636
+ onMouseDown: onRowClick && ((event) => {
17637
+ event.stopPropagation();
17638
+ event.preventDefault();
17639
+ onRowClick(i, "row");
17640
+ }),
17641
+ id: anchorIdFor(i),
17642
+ style: {
17643
+ flexDirection: "column",
17644
+ flexShrink: 0,
17645
+ paddingLeft: 1,
17646
+ paddingRight: 1,
17647
+ backgroundColor: bg
17648
+ },
17649
+ children: [
17650
+ /* @__PURE__ */ jsxs("text", {
17651
+ wrapMode: "none",
17652
+ children: [
17653
+ /* @__PURE__ */ jsx$1("span", {
17654
+ fg: focused ? COLOR.brand : COLOR.mute,
17655
+ children: focused ? "▶ " : " "
17656
+ }),
17657
+ /* @__PURE__ */ jsx$1("span", {
17658
+ fg: COLOR.mute,
17659
+ children: expanded ? "▾ " : "▸ "
17660
+ }),
17661
+ /* @__PURE__ */ jsx$1("span", {
17662
+ fg: focused ? COLOR.brand : COLOR.dim,
17663
+ children: segment.label
17664
+ }),
17665
+ /* @__PURE__ */ jsxs("span", {
17666
+ fg: COLOR.mute,
17667
+ children: [" · ", instructionOriginBadge(segment)]
17668
+ }),
17669
+ /* @__PURE__ */ jsxs("span", {
17670
+ fg: COLOR.mute,
17671
+ children: [" · ~", fmtTokens(segment.tokens)]
17672
+ })
17673
+ ]
17674
+ }),
17675
+ secondLine !== "" && /* @__PURE__ */ jsx$1("text", {
17676
+ wrapMode: "none",
17677
+ fg: openable ? COLOR.model : COLOR.mute,
17678
+ children: `${INDENT}${truncateToCols(secondLine, detailCols)}`
17679
+ }),
17680
+ preview && /* @__PURE__ */ jsxs("box", {
17681
+ style: {
17682
+ flexDirection: "column",
17683
+ paddingLeft: 4,
17684
+ paddingTop: 1
17685
+ },
17686
+ children: [preview.lines.map((line, n) => /* @__PURE__ */ jsx$1("text", {
17687
+ wrapMode: "none",
17688
+ fg: COLOR.dim,
17689
+ children: line === "" ? " " : truncateToCols(line, detailCols)
17690
+ }, n)), preview.hidden > 0 && /* @__PURE__ */ jsx$1("text", {
17691
+ wrapMode: "none",
17692
+ fg: COLOR.mute,
17693
+ children: `… +${preview.hidden} more line${preview.hidden === 1 ? "" : "s"}${openable ? " — press ↵ to open the file" : ""}`
17694
+ })]
17695
+ })
17696
+ ]
17697
+ }, segment.id);
17698
+ })
17699
+ ]
17700
+ });
17701
+ }
17228
17702
  function KeybindingsList({ bindings, extensionRows, query }) {
17229
17703
  const sections = useMemo(() => groupBindings(bindings, extensionRows), [bindings, extensionRows]);
17230
17704
  const filteredSections = useMemo(() => {
@@ -17713,7 +18187,7 @@ function renderMcpErrors(errors, home, warnColor, dimColor) {
17713
18187
  }, err.path))
17714
18188
  });
17715
18189
  }
17716
- function Hints({ activeTab, authorizing = false, focusedMcp, focusedMcpRow, focusedMcpStatus, canRefreshSkills, canRefreshMcps, canRefreshPlugins, canEditExtension, canCreateExtension, canUpdateExtension, canRemoveExtension, canInstallExtension, canInstallSkill, canEditSkill, canAddMcp, canRemoveSkill, canRemoveMcp, focusedExtensionExpandable, focusedSecretClearable = false }) {
18190
+ function Hints({ activeTab, authorizing = false, focusedMcp, focusedMcpRow, focusedMcpStatus, canRefreshSkills, canRefreshMcps, canRefreshPlugins, canEditExtension, canCreateExtension, canUpdateExtension, canRemoveExtension, canInstallExtension, canInstallSkill, canEditSkill, canAddMcp, canRemoveSkill, canRemoveMcp, focusedExtensionExpandable, focusedSecretClearable = false, focusedInstructionOpenable = false, focusedInstructionCreatable = false }) {
17717
18191
  const COLOR = useColors();
17718
18192
  const onServerRow = focusedMcpRow?.kind === "server";
17719
18193
  const showLogin = activeTab === "mcps" && onServerRow && !!focusedMcp && !!focusedMcpStatus && canLogin$1(focusedMcp, focusedMcpStatus);
@@ -17737,8 +18211,15 @@ function Hints({ activeTab, authorizing = false, focusedMcp, focusedMcpRow, focu
17737
18211
  label: "type to filter"
17738
18212
  }] : [{
17739
18213
  key: "↵",
17740
- label: isCategoryTab(activeTab) ? "toggle/cycle/select" : activeTab === "keybindings" ? "edit file" : activeTab === "authentication" ? "switch / re-authenticate" : "toggle"
18214
+ label: isCategoryTab(activeTab) ? "toggle/cycle/select" : activeTab === "keybindings" ? "edit file" : activeTab === "authentication" ? "switch / re-authenticate" : activeTab === "instructions" ? focusedInstructionOpenable ? "open file" : focusedInstructionCreatable ? "create SYSTEM.md override" : "no file to open" : "toggle"
17741
18215
  }],
18216
+ ...activeTab === "instructions" ? [{
18217
+ key: "tab",
18218
+ label: "read inline"
18219
+ }, {
18220
+ key: "ctrl+r",
18221
+ label: "reload"
18222
+ }] : [],
17742
18223
  ...activeTab === "mcps" && focusedMcpRow ? [{
17743
18224
  key: "tab",
17744
18225
  label: focusedMcpRow.kind === "server" ? "expand/collapse" : "collapse"
@@ -17904,6 +18385,21 @@ function keybindingCorpus(row) {
17904
18385
  if (row.kind === "extension") return `${row.spec} ${row.label} ${row.description} ${row.id}`.toLowerCase();
17905
18386
  return `${row.spec} ${row.def.label} ${row.def.description} ${row.def.action}`.toLowerCase();
17906
18387
  }
18388
+ /**
18389
+ * Search corpus for an Instructions row — label, origin, detail, path AND the
18390
+ * segment's full text. Including the body is the point: it turns the shared
18391
+ * search box into "which instruction source told the agent this?", which is
18392
+ * the question this tab exists to answer.
18393
+ */
18394
+ function instructionCorpus(segment) {
18395
+ return [
18396
+ segment.label,
18397
+ instructionOriginBadge(segment),
18398
+ segment.detail ?? "",
18399
+ segment.path ?? "",
18400
+ segment.text
18401
+ ].join(" ").toLowerCase();
18402
+ }
17907
18403
  function providerCorpus(p) {
17908
18404
  return `${p.label} ${p.key} ${p.methods.map((m) => `${m.source} ${m.detail}`).join(" ")}`.toLowerCase();
17909
18405
  }
@@ -18820,29 +19316,45 @@ const debugLog = createDebugLog("tui");
18820
19316
  * spawned process is detached + unref'd so closing the TUI doesn't
18821
19317
  * kill the editor (and vice versa).
18822
19318
  *
18823
- * Resolves once the spawn is dispatched — we don't wait for the
18824
- * editor to exit. The user is expected to save and either restart
19319
+ * Resolves once the spawn is DISPATCHED (the `'spawn'` event) — we don't wait
19320
+ * for the editor to exit. The user is expected to save and either restart
18825
19321
  * the TUI (for changes that need to be picked up at launch, like
18826
19322
  * keybindings) or simply continue working.
19323
+ *
19324
+ * Rejects when the spawn fails. `child_process.spawn` reports ENOENT through an
19325
+ * `'error'` EVENT rather than a throw, so a missing binary used to surface as an
19326
+ * unhandled emitter error — crashing into OpenTUI's error console right past
19327
+ * every caller's `try`/`catch`. Funnelling both outcomes into the promise is
19328
+ * what makes those guards effective.
18827
19329
  */
18828
19330
  async function launchEditor(path) {
18829
19331
  const envEditor = (process.env.VISUAL ?? process.env.EDITOR ?? "").trim();
19332
+ const win32 = process.platform === "win32";
19333
+ const q = win32 ? quoteForShell : (s) => s;
18830
19334
  const [cmd, args] = (() => {
18831
- if (envEditor) return [envEditor, [path]];
19335
+ const [bin, ...editorArgs] = parseEditorCommand(envEditor);
19336
+ if (bin) return [q(bin), [...editorArgs.map(q), q(path)]];
18832
19337
  if (process.platform === "darwin") return ["open", [path]];
18833
- if (process.platform === "win32") return ["cmd", [
19338
+ if (win32) return ["cmd", [
18834
19339
  "/c",
18835
19340
  "start",
18836
19341
  "\"\"",
18837
- path
19342
+ q(path)
18838
19343
  ]];
18839
19344
  return ["xdg-open", [path]];
18840
19345
  })();
18841
- spawn(cmd, args, {
18842
- detached: true,
18843
- stdio: "ignore",
18844
- shell: process.platform === "win32"
18845
- }).unref();
19346
+ await new Promise((resolve, reject) => {
19347
+ const child = spawn(cmd, args, {
19348
+ detached: true,
19349
+ stdio: "ignore",
19350
+ shell: win32
19351
+ });
19352
+ child.once("error", reject);
19353
+ child.once("spawn", () => {
19354
+ child.unref();
19355
+ resolve();
19356
+ });
19357
+ });
18846
19358
  }
18847
19359
  /**
18848
19360
  * Session-metadata key under which the TUI persists the user's "pinned"
@@ -19056,6 +19568,29 @@ function AppShell() {
19056
19568
  useEffect(() => {
19057
19569
  userInstructionsScopeRef.current = settings.userInstructionsScope;
19058
19570
  }, [settings.userInstructionsScope]);
19571
+ const sessionInstructionFilesRef = useRef([]);
19572
+ /**
19573
+ * Path of the `SYSTEM.md` override the CURRENT session's prompt was built
19574
+ * from, or `null` when the built-in doctrine is in force. Same activation-time
19575
+ * semantics as `sessionInstructionFilesRef`: what the live prompt came from,
19576
+ * not what's on disk right now.
19577
+ */
19578
+ const sessionSystemFileRef = useRef(null);
19579
+ /**
19580
+ * Path of the project-scope override whose warning is already in the visible
19581
+ * transcript, so a rebuild that changes nothing stays quiet. Reset by
19582
+ * `activateSession` because `teardown` clears the transcript the note lived in.
19583
+ */
19584
+ const announcedSystemOverrideRef = useRef(null);
19585
+ /**
19586
+ * The rendered AGENTS.md block this session's prompt was built with, or `null`
19587
+ * when none loaded. Threaded into `getContextSegments` so the base-prompt
19588
+ * segment can subtract it: the agent receives the block as an opaque substring
19589
+ * of the system string and can't find it on its own, and without this the
19590
+ * instruction text would be reported twice — once inside the system prompt
19591
+ * segment, once as its own file rows.
19592
+ */
19593
+ const sessionRulesBlockRef = useRef(null);
19059
19594
  const subagentModelsRef = useRef(settings.subagentModels);
19060
19595
  useEffect(() => {
19061
19596
  subagentModelsRef.current = settings.subagentModels;
@@ -20118,6 +20653,110 @@ function AppShell() {
20118
20653
  return d ? getContextWindow(d, entry.model) ?? void 0 : void 0;
20119
20654
  });
20120
20655
  }, [providerRegistry]);
20656
+ /**
20657
+ * The host-composed half of a session's context: the instruction files
20658
+ * discovered for the current cwd + scope, and the system prompt they get
20659
+ * rendered into.
20660
+ *
20661
+ * Extracted from `buildAgent` so session activation and the Settings →
20662
+ * Instructions tab share ONE composition path. The tab has to answer "what
20663
+ * does my context contain" on screens where no agent exists yet (home,
20664
+ * sessions list, model picker — settings opens everywhere but `auth`), and a
20665
+ * second copy of this logic would drift from what sessions actually load.
20666
+ *
20667
+ * Cheap and side-effect-free: a handful of `existsSync` + small reads plus
20668
+ * pure string composition, safe to call on every settings open.
20669
+ */
20670
+ /**
20671
+ * Warn in the transcript when a PROJECT-scope `SYSTEM.md` is in force.
20672
+ *
20673
+ * Such a file replaces the built-in doctrine — including the destructive-action
20674
+ * and git fragments — so one that merely exists at the root of a checkout can
20675
+ * silently drop "destructive actions need explicit confirmation" and "never
20676
+ * force-push without confirmation". `AGENTS.md` can only APPEND, and renders
20677
+ * under a header marking it as user-authored guidance; deleting doctrine is a
20678
+ * different risk profile from adding to it. The user-scope file stays quiet:
20679
+ * they wrote it themselves, in their own home directory.
20680
+ *
20681
+ * Called from EVERY path that installs a prompt — session activation and
20682
+ * `rebuildActiveAgent` (which serves the Instructions tab's `ctrl+r`, both its
20683
+ * immediate and its deferred-until-idle branch, plus post-login and
20684
+ * extension-swap rebuilds). Announcing only on activation meant that dropping a
20685
+ * `SYSTEM.md` into a checkout and hitting `ctrl+r` swapped the safety guidance
20686
+ * out in silence.
20687
+ *
20688
+ * De-duped on the announced path so an unchanged rebuild doesn't repeat itself.
20689
+ */
20690
+ const announceProjectSystemOverride = useCallback(() => {
20691
+ const override = sessionSystemFileRef.current;
20692
+ if (override?.source !== "project") {
20693
+ announcedSystemOverrideRef.current = null;
20694
+ return;
20695
+ }
20696
+ if (announcedSystemOverrideRef.current === override.path) return;
20697
+ announcedSystemOverrideRef.current = override.path;
20698
+ stream.appendImmediate({
20699
+ kind: "info",
20700
+ text: `\u26A0 ${override.path} is replacing the built-in system prompt, including the destructive-action and git safety guidance. Settings \u2192 Instructions to read it.`
20701
+ });
20702
+ }, [stream]);
20703
+ const composeHostContext = useCallback((profile, allowInteraction) => {
20704
+ const actualCwd = cwdRef.current;
20705
+ const agentsMd = discoverAgentsMd({
20706
+ cwd: actualCwd,
20707
+ prefix: config.prefix,
20708
+ scope: userInstructionsScopeRef.current,
20709
+ ruler: importRulerConfigsRef.current !== false
20710
+ });
20711
+ const systemMd = discoverSystemMd({
20712
+ cwd: actualCwd,
20713
+ prefix: config.prefix,
20714
+ scope: userInstructionsScopeRef.current
20715
+ });
20716
+ const supportsOverride = profile.id === "build" || profile.id === "plan";
20717
+ const envOpts = {
20718
+ cwd: actualCwd,
20719
+ ...projectDir !== actualCwd ? { projectRoot: projectDir } : {},
20720
+ allowInteraction,
20721
+ ...agentsMd.block ? { userInstructions: agentsMd.block } : {},
20722
+ ...systemMd.file ? { systemOverride: systemMd.file.contents } : {}
20723
+ };
20724
+ const build = profile.id === "build" ? buildBuildSystem : profile.id === "plan" ? buildPlanSystem : null;
20725
+ const system = build ? build(envOpts) : null;
20726
+ return {
20727
+ agentsMd,
20728
+ /** The override actually in force, or `null` when this profile ignores overrides. */
20729
+ systemFile: supportsOverride ? systemMd.file : null,
20730
+ /**
20731
+ * Overrides that exist on disk but aren't loaded. On a profile that can't
20732
+ * accept one, that's EVERY discovered file — not just the precedence
20733
+ * losers — so the tab can explain the silence.
20734
+ */
20735
+ shadowedSystemFiles: supportsOverride ? systemMd.shadowed : [...systemMd.file ? [systemMd.file] : [], ...systemMd.shadowed],
20736
+ supportsOverride,
20737
+ envOpts,
20738
+ system,
20739
+ /**
20740
+ * The BUILT-IN prompt: no AGENTS.md block, and deliberately no active
20741
+ * `SYSTEM.md` either — the correct seed for a new override.
20742
+ *
20743
+ * Seeding from `system` would bake the instructions into the override and
20744
+ * the next session would append them again (the override replaces the
20745
+ * doctrine; `userInstructions` still composes below it). Clearing
20746
+ * `systemOverride` too means this always answers "what would the built-in
20747
+ * doctrine be", so a future "reset my override" or "diff against default"
20748
+ * caller can't be handed a copy of the override it was trying to replace.
20749
+ *
20750
+ * A thunk so the extra composition only happens on the create path, not
20751
+ * on every session activation.
20752
+ */
20753
+ doctrineOnlySystem: () => build ? build({
20754
+ ...envOpts,
20755
+ userInstructions: void 0,
20756
+ systemOverride: void 0
20757
+ }) : null
20758
+ };
20759
+ }, [config.prefix, projectDir]);
20121
20760
  const buildAgent = useCallback((session, key) => {
20122
20761
  const descriptor = providerRegistry[key];
20123
20762
  if (!descriptor) throw new Error(`No provider registered for key "${key}"`);
@@ -20178,19 +20817,13 @@ function AppShell() {
20178
20817
  const allowInteraction = allowInteractionRef.current !== false;
20179
20818
  const interactionTools = allowInteraction ? createInteractionTools({ requestInteraction: makeRequestInteraction(interactions) }) : {};
20180
20819
  const actualCwd = cwdRef.current;
20181
- const agentsMd = discoverAgentsMd({
20182
- cwd: actualCwd,
20183
- prefix: config.prefix,
20184
- scope: userInstructionsScopeRef.current,
20185
- ruler: importRulerConfigsRef.current !== false
20186
- });
20187
- const envOpts = {
20188
- cwd: actualCwd,
20189
- ...projectDir !== actualCwd ? { projectRoot: projectDir } : {},
20190
- allowInteraction,
20191
- ...agentsMd.block ? { userInstructions: agentsMd.block } : {}
20192
- };
20193
- const builtInSystem = profile.id === "build" ? buildBuildSystem(envOpts) : profile.id === "plan" ? buildPlanSystem(envOpts) : null;
20820
+ const { agentsMd, systemFile, system: builtInSystem } = composeHostContext(profile, allowInteraction);
20821
+ sessionInstructionFilesRef.current = agentsMd.files;
20822
+ sessionRulesBlockRef.current = agentsMd.block;
20823
+ sessionSystemFileRef.current = systemFile ? {
20824
+ path: systemFile.path,
20825
+ source: systemFile.source
20826
+ } : null;
20194
20827
  const persistDir = resolvePersistDir({
20195
20828
  userDir: cacheDir,
20196
20829
  sessionId: session.id
@@ -21168,6 +21801,8 @@ function AppShell() {
21168
21801
  sessionRef.current = session;
21169
21802
  agentRef.current = buildAgent(session, key);
21170
21803
  if (extensionRegistrySwapKeyRef.current === null || extensionRegistrySwapKeyRef.current === extensionRegistry) pendingAgentRebuildRef.current = false;
21804
+ announcedSystemOverrideRef.current = null;
21805
+ announceProjectSystemOverride();
21171
21806
  const persistedPins = readPinnedSkills(session.metadata["zidane.activeSkills"]);
21172
21807
  setActiveSkillNames(persistedPins);
21173
21808
  setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
@@ -22823,6 +23458,8 @@ function AppShell() {
22823
23458
  const mcpLoginAbortsRef = useRef(/* @__PURE__ */ new Map());
22824
23459
  const buildAgentRef = useRef(buildAgent);
22825
23460
  buildAgentRef.current = buildAgent;
23461
+ const announceProjectSystemOverrideRef = useRef(announceProjectSystemOverride);
23462
+ announceProjectSystemOverrideRef.current = announceProjectSystemOverride;
22826
23463
  /**
22827
23464
  * Rebuild the agent for the active session — used post-login so the
22828
23465
  * newly-authenticated MCP server actually connects. The current agent's
@@ -22843,6 +23480,7 @@ function AppShell() {
22843
23480
  const staleWarnLog = warnLogRef.current;
22844
23481
  const fresh = buildAgentRef.current(session, p.provider.key);
22845
23482
  agentRef.current = fresh;
23483
+ announceProjectSystemOverrideRef.current();
22846
23484
  if (stale) await stale.destroy().catch((err) => debugLog("rebuildActiveAgent: destroy failed", err));
22847
23485
  if (staleWarnLog) await staleWarnLog.close().catch((err) => debugLog("rebuildActiveAgent: warnLog.close failed", err));
22848
23486
  }, []);
@@ -23012,6 +23650,106 @@ function AppShell() {
23012
23650
  }
23013
23651
  })();
23014
23652
  }, [modal, config.paths.userDir]);
23653
+ const onReadContextSegments = useCallback(async () => {
23654
+ const { shadowedSystemFiles, supportsOverride } = composeHostContext(pickedAgentRef.current, allowInteractionRef.current !== false);
23655
+ const shadowedSystemPath = shadowedSystemFiles[0]?.path;
23656
+ const scopeLoadsUserOverride = userScopeOverridesLoad(userInstructionsScopeRef.current);
23657
+ const canOverrideSystem = supportsOverride && scopeLoadsUserOverride;
23658
+ const overrideBlockedBy = !supportsOverride ? "profile" : !scopeLoadsUserOverride ? "scope" : void 0;
23659
+ const agent = agentRef.current;
23660
+ if (agent) return {
23661
+ segments: await agent.getContextSegments({
23662
+ rulesFiles: sessionInstructionFilesRef.current,
23663
+ ...sessionRulesBlockRef.current ? { rulesBlock: sessionRulesBlockRef.current } : {},
23664
+ ...sessionSystemFileRef.current ? { systemPath: sessionSystemFileRef.current.path } : {}
23665
+ }) ?? [],
23666
+ live: true,
23667
+ canOverrideSystem,
23668
+ ...overrideBlockedBy ? { overrideBlockedBy } : {},
23669
+ ...shadowedSystemPath ? { shadowedSystemPath } : {}
23670
+ };
23671
+ const { agentsMd, systemFile, system } = composeHostContext(pickedAgentRef.current, allowInteractionRef.current !== false);
23672
+ const enabled = enabledSkillsRef.current;
23673
+ const sessionSkills = enabled === void 0 ? [...skillsCatalogRef.current] : skillsCatalogRef.current.filter((s) => enabled.includes(s.name));
23674
+ return {
23675
+ segments: buildContextSegments({
23676
+ ...system ? { baseSystem: system } : {},
23677
+ ...systemFile ? { systemPath: systemFile.path } : {},
23678
+ ...agentsMd.block ? { rulesBlock: agentsMd.block } : {},
23679
+ rulesFiles: agentsMd.files,
23680
+ skillsCatalog: buildCatalog(sessionSkills)
23681
+ }),
23682
+ live: false,
23683
+ canOverrideSystem,
23684
+ ...overrideBlockedBy ? { overrideBlockedBy } : {},
23685
+ ...shadowedSystemPath ? { shadowedSystemPath } : {}
23686
+ };
23687
+ }, [composeHostContext]);
23688
+ const onOpenInstructionsFile = useCallback(async (path) => {
23689
+ await launchEditor(path);
23690
+ modal.close();
23691
+ }, [modal]);
23692
+ /**
23693
+ * Create `~/.{prefix}/SYSTEM.md` and open it — the `↵` action on the System
23694
+ * prompt row when no override exists yet.
23695
+ *
23696
+ * Seeded with the doctrine currently in effect (see `renderSystemMdSeed`), so
23697
+ * creating the file changes nothing until the user edits it. An empty seed
23698
+ * would strip every line of doctrine the instant the file appeared.
23699
+ *
23700
+ * Idempotent: if the file already exists we open it rather than clobbering the
23701
+ * user's text. `composeHostContext` gives us the exact prompt the next session
23702
+ * would use as the seed source, and we take only the STATIC half so the
23703
+ * per-turn `<env>` block never gets frozen into the file.
23704
+ */
23705
+ const onCreateSystemPrompt = useCallback(async () => {
23706
+ const path = userSystemMdPath({ prefix: config.prefix });
23707
+ if (!existsSync(path)) {
23708
+ const { doctrineOnlySystem, supportsOverride } = composeHostContext(pickedAgentRef.current, allowInteractionRef.current !== false);
23709
+ if (!supportsOverride) throw new Error(`the "${pickedAgentRef.current.id}" profile supplies its own system prompt, so a SYSTEM.md override would not apply`);
23710
+ if (!userScopeOverridesLoad(userInstructionsScopeRef.current)) throw new Error(`userInstructionsScope is "${userInstructionsScopeRef.current}", so a SYSTEM.md in your home directory would not be loaded`);
23711
+ const doctrine = splitSystemPrompt(doctrineOnlySystem() ?? "").static;
23712
+ if (doctrine.trim().length === 0) throw new Error("could not compose a system prompt to seed the override from");
23713
+ mkdirSync(dirname(path), { recursive: true });
23714
+ writeFileSync(path, renderSystemMdSeed(doctrine, userInstructionsScopeRef.current), "utf8");
23715
+ }
23716
+ await launchEditor(path);
23717
+ modal.close();
23718
+ }, [
23719
+ modal,
23720
+ config.prefix,
23721
+ composeHostContext
23722
+ ]);
23723
+ /**
23724
+ * `ctrl+r` on the Instructions tab: re-read the instruction files from disk
23725
+ * and make the live agent actually use them.
23726
+ *
23727
+ * Re-discovery is implicit — `composeHostContext` hits the filesystem on every
23728
+ * call, so the modal's own re-fetch picks up new files. The work here is
23729
+ * rebuilding the agent so a session that's already running switches to the new
23730
+ * prompt without a TUI restart.
23731
+ *
23732
+ * Deferred while a run is in flight: swapping the system prompt underneath a
23733
+ * streaming turn is not safe, so we raise the same `pendingAgentRebuild` flag
23734
+ * that MCP-settings changes and extension-registry swaps use and let
23735
+ * `drainFollowUps` apply it once the turn ends.
23736
+ *
23737
+ * Returns which of the two happened so the tab can say so — a rebuild that
23738
+ * silently didn't take effect yet is worse than no rebuild.
23739
+ *
23740
+ * Side effect worth surfacing: `rebuildActiveAgent` destroys the stale agent,
23741
+ * which closes its MCP connections. The fresh agent reconnects lazily on its
23742
+ * next run, so the turn after a reload pays that cost. The tab's notice says so.
23743
+ */
23744
+ const onRefreshInstructions = useCallback(async () => {
23745
+ if (!agentRef.current || !sessionRef.current) return "no-session";
23746
+ if (runningRef.current) {
23747
+ pendingAgentRebuildRef.current = true;
23748
+ return "queued";
23749
+ }
23750
+ await rebuildActiveAgent();
23751
+ return "rebuilt";
23752
+ }, [rebuildActiveAgent]);
23015
23753
  const hasMultipleAgents = useMemo(() => Object.keys(liveAgentRegistry).length > 1, [liveAgentRegistry]);
23016
23754
  const turnIds = useMemo(() => selectableTurnIds(events, settings), [events, settings]);
23017
23755
  useEffect(() => {
@@ -23688,6 +24426,10 @@ function AppShell() {
23688
24426
  actions: {
23689
24427
  onConfigureProvider,
23690
24428
  onOpenKeybindings: onOpenKeybindingsFile,
24429
+ getContextSegments: onReadContextSegments,
24430
+ onOpenInstructionsFile,
24431
+ onCreateSystemPrompt,
24432
+ onRefreshInstructions,
23691
24433
  onLoginMcp,
23692
24434
  onLogoutMcp,
23693
24435
  onCancelLoginMcp,