zidane 6.5.0 → 6.5.2

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 (125) hide show
  1. package/dist/{acp-B-AOovHz.js → acp-565X8nVP.js} +6 -7
  2. package/dist/{acp-B-AOovHz.js.map → acp-565X8nVP.js.map} +1 -1
  3. package/dist/acp-cli.js +6 -6
  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-SQ6rxfpT.js → agent-CvO7UOxv.js} +1427 -610
  8. package/dist/agent-CvO7UOxv.js.map +1 -0
  9. package/dist/agent.d.ts +1 -1
  10. package/dist/agent.js +1 -2
  11. package/dist/{anthropic-D3lkw5mv.js → anthropic-Dov2mC5e.js} +3 -3
  12. package/dist/{anthropic-D3lkw5mv.js.map → anthropic-Dov2mC5e.js.map} +1 -1
  13. package/dist/{auth-BzYbbdN_.js → auth-B8ZuBcKC.js} +5 -5
  14. package/dist/{auth-BzYbbdN_.js.map → auth-B8ZuBcKC.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 +114 -5
  18. package/dist/chat.d.ts.map +1 -1
  19. package/dist/chat.js +6 -6
  20. package/dist/context-segments-1mcqSCM3.js +494 -0
  21. package/dist/context-segments-1mcqSCM3.js.map +1 -0
  22. package/dist/{context-transport-CDBatvQc.js → context-transport-ClvadrlH.js} +3 -3
  23. package/dist/{context-transport-CDBatvQc.js.map → context-transport-ClvadrlH.js.map} +1 -1
  24. package/dist/contexts/daytona.d.ts +1 -1
  25. package/dist/contexts/e2b.d.ts +1 -1
  26. package/dist/eval.d.ts +1 -1
  27. package/dist/eval.js +1 -1
  28. package/dist/extensions.d.ts +1 -1
  29. package/dist/extensions.js +1 -1
  30. package/dist/headless.d.ts +1 -1
  31. package/dist/headless.js +3 -3
  32. package/dist/{index-CWBSrqI6.d.ts → index-DfERTrql.d.ts} +178 -6
  33. package/dist/index-DfERTrql.d.ts.map +1 -0
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.js +9 -10
  36. package/dist/index.js.map +1 -1
  37. package/dist/{login-XZDS4K2o.js → login-Cl6ML2XN.js} +2 -2
  38. package/dist/{login-XZDS4K2o.js.map → login-Cl6ML2XN.js.map} +1 -1
  39. package/dist/{mcp-D27rP47I.js → mcp-DdntU47g.js} +3 -3
  40. package/dist/{mcp-D27rP47I.js.map → mcp-DdntU47g.js.map} +1 -1
  41. package/dist/mcp.d.ts +1 -1
  42. package/dist/mcp.js +1 -1
  43. package/dist/{messages-C01kKGj0.js → messages-C_H9soi5.js} +2 -3
  44. package/dist/{messages-C01kKGj0.js.map → messages-C_H9soi5.js.map} +1 -1
  45. package/dist/{openai-compat-B3c_hzwf.js → openai-compat-CnHeo4Lz.js} +216 -3
  46. package/dist/openai-compat-CnHeo4Lz.js.map +1 -0
  47. package/dist/output/stream-json.d.ts +1 -1
  48. package/dist/output/terminal.d.ts +1 -1
  49. package/dist/plugins.d.ts +1 -1
  50. package/dist/presets.d.ts +1 -1
  51. package/dist/presets.js +1 -1
  52. package/dist/providers/anthropic.d.ts +1 -1
  53. package/dist/providers/anthropic.js +1 -1
  54. package/dist/providers/arcee.d.ts +1 -1
  55. package/dist/providers/arcee.js +1 -1
  56. package/dist/providers/baseten.d.ts +1 -1
  57. package/dist/providers/baseten.js +1 -1
  58. package/dist/providers/cerebras.d.ts +1 -1
  59. package/dist/providers/cerebras.js +1 -1
  60. package/dist/providers/local.d.ts +1 -1
  61. package/dist/providers/local.js +1 -1
  62. package/dist/providers/openai-compat.d.ts +1 -1
  63. package/dist/providers/openai-compat.js +1 -1
  64. package/dist/providers/openai.d.ts +1 -1
  65. package/dist/providers/openai.js +2 -2
  66. package/dist/providers/openrouter.d.ts +1 -1
  67. package/dist/providers/openrouter.js +1 -1
  68. package/dist/providers/xai.d.ts +1 -1
  69. package/dist/providers/xai.js +1 -1
  70. package/dist/{providers-z1vU0b2Q.js → providers-B6mFe7OD.js} +3 -3
  71. package/dist/{providers-z1vU0b2Q.js.map → providers-B6mFe7OD.js.map} +1 -1
  72. package/dist/providers.d.ts +2 -2
  73. package/dist/providers.js +4 -4
  74. package/dist/restate.d.ts +178 -2
  75. package/dist/restate.d.ts.map +1 -1
  76. package/dist/restate.js +458 -4
  77. package/dist/restate.js.map +1 -1
  78. package/dist/session/sqlite.d.ts +1 -1
  79. package/dist/{session-cr9VdoEO.js → session-VpLrNaFQ.js} +2 -2
  80. package/dist/{session-cr9VdoEO.js.map → session-VpLrNaFQ.js.map} +1 -1
  81. package/dist/session.d.ts +1 -1
  82. package/dist/session.js +2 -2
  83. package/dist/skills.d.ts +1 -1
  84. package/dist/{stdio-O-m9WVkE.js → stdio-BQeCMmuK.js} +2 -2
  85. package/dist/{stdio-O-m9WVkE.js.map → stdio-BQeCMmuK.js.map} +1 -1
  86. package/dist/{stdio-loader-BSGQmG4L.js → stdio-loader-BN1l1ZAG.js} +2 -2
  87. package/dist/{stdio-loader-BSGQmG4L.js.map → stdio-loader-BN1l1ZAG.js.map} +1 -1
  88. package/dist/{tool-formatters-gFqQbzPY.d.ts → tool-formatters-B2gBbKaS.d.ts} +2 -2
  89. package/dist/{tool-formatters-gFqQbzPY.d.ts.map → tool-formatters-B2gBbKaS.d.ts.map} +1 -1
  90. package/dist/tools/fetch-url.d.ts +1 -1
  91. package/dist/tools/web-search.d.ts +1 -1
  92. package/dist/tools.d.ts +1 -1
  93. package/dist/tools.js +1 -2
  94. package/dist/{transcript-anchors-qCs3wZpu.d.ts → transcript-anchors-CeIxhKP7.d.ts} +2 -2
  95. package/dist/{transcript-anchors-qCs3wZpu.d.ts.map → transcript-anchors-CeIxhKP7.d.ts.map} +1 -1
  96. package/dist/{transcript-anchors-D4IxJifV.js → transcript-anchors-CmFKUw8M.js} +139 -19
  97. package/dist/transcript-anchors-CmFKUw8M.js.map +1 -0
  98. package/dist/tui.d.ts +74 -3
  99. package/dist/tui.d.ts.map +1 -1
  100. package/dist/tui.js +819 -83
  101. package/dist/tui.js.map +1 -1
  102. package/dist/{turn-operations-M9-jUyDd.d.ts → turn-operations-Dyv3Z-Ff.d.ts} +3 -3
  103. package/dist/{turn-operations-M9-jUyDd.d.ts.map → turn-operations-Dyv3Z-Ff.d.ts.map} +1 -1
  104. package/dist/{turn-operations-CAvi8NYt.js → turn-operations-iGu5J08j.js} +2 -2
  105. package/dist/{turn-operations-CAvi8NYt.js.map → turn-operations-iGu5J08j.js.map} +1 -1
  106. package/dist/types.d.ts +1 -1
  107. package/dist/{xai-BPwMQSuF.js → xai-Du6Wfnlb.js} +2 -2
  108. package/dist/{xai-BPwMQSuF.js.map → xai-Du6Wfnlb.js.map} +1 -1
  109. package/docs/CHAT.md +42 -1
  110. package/docs/INTERACTIONS.md +39 -0
  111. package/docs/RESTATE.md +102 -3
  112. package/docs/TUI.md +28 -2
  113. package/package.json +1 -1
  114. package/dist/agent-SQ6rxfpT.js.map +0 -1
  115. package/dist/aliasing-lVFbm81n.js +0 -217
  116. package/dist/aliasing-lVFbm81n.js.map +0 -1
  117. package/dist/context-breakdown-Dzo25N45.js +0 -155
  118. package/dist/context-breakdown-Dzo25N45.js.map +0 -1
  119. package/dist/edit-utils-EGosADZq.js +0 -234
  120. package/dist/edit-utils-EGosADZq.js.map +0 -1
  121. package/dist/index-CWBSrqI6.d.ts.map +0 -1
  122. package/dist/openai-compat-B3c_hzwf.js.map +0 -1
  123. package/dist/tool-state-DTs-3ZHH.js +0 -653
  124. package/dist/tool-state-DTs-3ZHH.js.map +0 -1
  125. package/dist/transcript-anchors-D4IxJifV.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,27 +1,28 @@
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-D4IxJifV.js";
2
- import { $ as removeInstalledExtension, C as resolveTasksDir, Cn as effectiveContextWindow, E as selectFilesFromSession, Gt as KEYBINDING_KEY_COL_WIDTH, Jt as groupBindings, K as createExtensionPromptBridge, Kt as ensureKeybindingsFile, Nt as createModelSearchTool, O as compactConversation, S as resolveSessionDir, Sn as OUTPUT_RESERVE_TOKENS, St as isSpawnTool, V as summaryToTurn, Xt as matchesBinding, Y as installExtensionFromSpec, Yt as keybindingsPath, _ as rehomePersistedStubs, at as composeExtensionPreset, b as resolveMcpWarningsDir, c as applyCompactSummaryCutoff, ct as filterRegistryPlugins, dn as createDebugLog, dt as createExtensionHostRuntimeBridge, et as updateInstalledExtension, gt as basic_default, jt as looksBinary, kt as tailTruncate, ln as parseExtensionScanPathEnv, m as cleanupPersistedSession, mn as coerceExtensionConfigInput, nt as discoverAndBuildExtensionRegistry, ot as disposeExtensionRegistry, q as ExtensionInstallError, qt as formatBindingForDisplay, rn as registerExtensionHostModules, sn as inlineLoadedExtension, st as filterExtensionRegistry, t as createAgent, tn as resolveExtensionKeybindings, tt as createExtensionHud, un as scanExtensionDirectories, ut as partitionExtensionCatalog, v as resolveLegacyPersistDir, vn as resolveExtensionConfigValue, w as buildPostCompactAttachments, x as resolvePersistDir, y as resolveLegacySessionDir, yn as sanitizeExtensionConfigActionPatch } from "./agent-SQ6rxfpT.js";
1
+ 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-CmFKUw8M.js";
2
+ import { C as estimateRebilledCostUsd } from "./openai-compat-CnHeo4Lz.js";
3
+ import { t as buildContextSegments } from "./context-segments-1mcqSCM3.js";
4
+ 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-CvO7UOxv.js";
3
5
  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";
4
6
  import { n as appendCapped } from "./output-cap-C9zukcCj.js";
5
7
  import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-DWVt7MvF.js";
6
8
  import { u as errorMessage } from "./errors-BwBxu3zD.js";
7
- import { R as projectUserPaths, c as SkillInstallError, l as installSkillFromSpec, z as findGitRoot } from "./skills-Cp9vnBwd.js";
9
+ import { E as buildCatalog, R as projectUserPaths, c as SkillInstallError, l as installSkillFromSpec, z as findGitRoot } from "./skills-Cp9vnBwd.js";
8
10
  import { n as defaultPluginDataRoot, r as loadAgentPlugin } from "./plugins-2PMTVcp-.js";
9
11
  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";
10
12
  import { i as sniffImageMediaType } from "./media-sniff-UKijEaiQ.js";
11
- import { _ as hasAuthorizationHeader, h as McpOAuthProvider, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-D27rP47I.js";
12
- import { C as estimateRebilledCostUsd } from "./openai-compat-B3c_hzwf.js";
13
- import { replaceDynamicSection } from "./prompt.js";
14
- import { a as getExtensionSessionState, c as setExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, t as createSession } from "./session-cr9VdoEO.js";
13
+ import { _ as hasAuthorizationHeader, h as McpOAuthProvider, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-DdntU47g.js";
14
+ import { replaceDynamicSection, splitSystemPrompt } from "./prompt.js";
15
+ import { a as getExtensionSessionState, c as setExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, t as createSession } from "./session-VpLrNaFQ.js";
15
16
  import { n as flattenTurns, r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
16
17
  import { o as withTimeout } from "./timeout-BDetbuN6.js";
17
18
  import { i as installCacheBreakLogger } from "./cache-telemetry-ehVdWsg2.js";
18
19
  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
20
  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-XZDS4K2o.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-BzYbbdN_.js";
21
+ 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-Cl6ML2XN.js";
22
+ 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";
22
23
  import { createDockerContext } from "./contexts/docker.js";
23
24
  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-CAvi8NYt.js";
25
+ 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
26
  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
27
  import { basename, delimiter, dirname, join, relative, resolve } from "node:path";
27
28
  import { Buffer } from "node:buffer";
@@ -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,70 @@ 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
+ * Deliberately generous (~75%, closer to the plan panel) because a batch of
9110
+ * questions IS the interaction while it's up — the transcript above can wait.
9111
+ * Two guards keep it usable at the extremes: never below
9112
+ * {@link WIZARD_MIN_ROWS} (a 16-row terminal still gets a usable form), and
9113
+ * never within {@link WIZARD_RESERVED_ROWS} of the top (so the user keeps some
9114
+ * transcript context on a tall terminal).
9115
+ */
9116
+ function wizardMaxRows(terminalRows) {
9117
+ const generous = Math.floor(terminalRows * .75);
9118
+ const leaveRoom = terminalRows - WIZARD_RESERVED_ROWS;
9119
+ return Math.max(WIZARD_MIN_ROWS, Math.min(generous, leaveRoom));
9120
+ }
9121
+ /**
9122
+ * Rows a string occupies once soft-wrapped into `width` columns.
9123
+ *
9124
+ * The sizing estimates used to count `\n` only, with a comment waving off
9125
+ * wrapping as drift the scrollbox would absorb. It doesn't: the estimate feeds
9126
+ * the scrollbox's explicit height, so under-counting makes the panel shorter
9127
+ * than its content and the frame lands mid-text. A wrapped markdown intro or a
9128
+ * wrapped choice description is the common case, not an edge one.
9129
+ *
9130
+ * Word-boundary-agnostic on purpose: `ceil(len / width)` stays within a row of
9131
+ * real word wrapping, and being one row over costs nothing.
9132
+ *
9133
+ * Assumes one column per code point, so CJK and wide emoji (two cells each)
9134
+ * under-count by up to half — the same assumption `truncateToCols` makes in
9135
+ * `settings-modal.tsx`. The consequence is a panel sized short of its content,
9136
+ * not content the user cannot reach: the scrollbox measures its own children for
9137
+ * `scrollHeight`, so the tail stays scrollable either way.
9138
+ */
9139
+ function wrappedRows(text, width) {
9140
+ if (width <= 0) return text.split("\n").length;
9141
+ let rows = 0;
9142
+ for (const line of text.split("\n")) rows += Math.max(1, Math.ceil(line.length / width));
9143
+ return rows;
9144
+ }
8987
9145
  /**
8988
9146
  * Rows the intro contributes to the wizard's content-sized height.
8989
9147
  *
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.
9148
+ * A wrapped line count, plus the one-row bottom margin. Long intros are capped:
9149
+ * past {@link INTRO_ROWS_CAP} the panel is at its {@link wizardMaxRows} ceiling
9150
+ * anyway and the scrollbox takes over, so counting further buys nothing. Zero
9151
+ * when there is no intro, which keeps a bare confirm question at its ~4-row size.
8995
9152
  */
8996
- const INTRO_ROWS_CAP = 12;
8997
- function introRows(intro) {
9153
+ const INTRO_ROWS_CAP = 24;
9154
+ function introRows(intro, width = Number.POSITIVE_INFINITY) {
8998
9155
  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;
9156
+ return Math.min(INTRO_ROWS_CAP, wrappedRows(intro, width)) + 1;
9005
9157
  }
9006
9158
  /**
9007
9159
  * Estimated render height (rows) of one {@link QuestionRow}, mirroring its
@@ -9011,15 +9163,17 @@ function introRows(intro) {
9011
9163
  * confirm pair (2), or the 1-line summary / `waiting…` placeholder.
9012
9164
  *
9013
9165
  * 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.
9166
+ * instead of stretching to the {@link wizardMaxRows} ceiling, which stays as
9167
+ * the overflow bound — an under-estimate degrades to scrolling, never clipping
9168
+ * the active input out of reach, thanks to scroll-into-view on advance.
9169
+ *
9170
+ * Prompts, descriptions, and each choice's own description are measured with
9171
+ * {@link wrappedRows} at `width`, so a wrapped line is counted as the rows it
9172
+ * actually occupies.
9019
9173
  */
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;
9174
+ function estimateQuestionRowHeight(question, status, width = Number.POSITIVE_INFINITY) {
9175
+ const textWidth = width === Number.POSITIVE_INFINITY ? width : Math.max(1, width - 5);
9176
+ let height = wrappedRows(question.prompt, textWidth) + (question.description ? wrappedRows(question.description, textWidth) : 0) + 1;
9023
9177
  if (status !== "active") return height + 1;
9024
9178
  switch (question.type) {
9025
9179
  case "text":
@@ -9029,7 +9183,8 @@ function estimateQuestionRowHeight(question, status) {
9029
9183
  height += 6;
9030
9184
  break;
9031
9185
  case "select":
9032
- height += question.choices.length + (question.multi ? 1 : 0);
9186
+ for (const choice of question.choices) height += 1 + (choice.description ? wrappedRows(choice.description, textWidth) : 0);
9187
+ height += question.multi ? 1 : 0;
9033
9188
  break;
9034
9189
  case "confirm":
9035
9190
  height += 2;
@@ -9077,6 +9232,13 @@ function QuestionInteractionBlock({ request, onResolve }) {
9077
9232
  if (!canGoBack) return;
9078
9233
  setActiveIndex((i) => Math.max(0, i - 1));
9079
9234
  });
9235
+ useKeyboard((key) => {
9236
+ if (!focused) return;
9237
+ const direction = pageScrollDirection(key);
9238
+ const scrollbox = scrollboxRef.current;
9239
+ if (direction === null || !scrollbox) return;
9240
+ pageScrollBy(scrollbox, direction);
9241
+ });
9080
9242
  useEffect(() => {
9081
9243
  const scrollbox = scrollboxRef.current;
9082
9244
  if (!scrollbox) return;
@@ -9087,14 +9249,30 @@ function QuestionInteractionBlock({ request, onResolve }) {
9087
9249
  });
9088
9250
  return () => cancelAnimationFrame(handle);
9089
9251
  }, [activeIndex, questions]);
9090
- const contentRows = useMemo(() => introRows(intro) + questions.reduce((sum, q, i) => sum + estimateQuestionRowHeight(q, i < activeIndex ? "done" : i === activeIndex ? "active" : "pending"), 0), [
9252
+ const { width: termCols, height: termRows } = useTerminalDimensions();
9253
+ const maxRows = wizardMaxRows(termRows);
9254
+ const contentCols = Math.max(1, termCols - 4);
9255
+ const measuredRows = useMemo(() => introRows(intro, contentCols) + questions.reduce((sum, q, i) => sum + estimateQuestionRowHeight(q, i < activeIndex ? "done" : i === activeIndex ? "active" : "pending", contentCols), 0), [
9091
9256
  intro,
9092
9257
  questions,
9093
- activeIndex
9258
+ activeIndex,
9259
+ contentCols
9094
9260
  ]);
9261
+ const highWaterRef = useRef({
9262
+ key: "",
9263
+ rows: 0
9264
+ });
9265
+ const highWaterKey = `${request.id}:${contentCols}`;
9266
+ if (highWaterRef.current.key !== highWaterKey) highWaterRef.current = {
9267
+ key: highWaterKey,
9268
+ rows: 0
9269
+ };
9270
+ highWaterRef.current.rows = Math.max(highWaterRef.current.rows, measuredRows);
9271
+ const contentRows = Math.min(highWaterRef.current.rows, maxRows);
9095
9272
  return /* @__PURE__ */ jsxs(InteractionShell, {
9096
9273
  title: questions.length > 1 ? ` answer ${activeIndex + 1}/${questions.length} · esc to abort run ` : " answer · esc to abort run ",
9097
- maxHeight: QUESTION_WIZARD_MAX_HEIGHT,
9274
+ maxHeight: maxRows,
9275
+ minHeight: Math.min(maxRows, contentRows + 2, Math.max(WIZARD_MIN_ROWS, termRows - 2)),
9098
9276
  children: [/* @__PURE__ */ jsxs("scrollbox", {
9099
9277
  ref: scrollboxRef,
9100
9278
  focusable: false,
@@ -11651,6 +11829,7 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
11651
11829
  searchQuery: transcriptSearchActive ? transcriptSearchQuery : void 0,
11652
11830
  searchPending: transcriptSearchSearching,
11653
11831
  busy: !transcriptSearchActive && busy && !pending && !pendingInteraction,
11832
+ interactionPending: pendingInteraction != null,
11654
11833
  onLoadEarlier: transcriptSearchActive ? void 0 : onLoadEarlierTranscript
11655
11834
  })
11656
11835
  }),
@@ -13810,6 +13989,62 @@ function parentExtensionName(row) {
13810
13989
  function indexOfExtensionRow(rows, extName) {
13811
13990
  return rows.findIndex((r) => r.kind === "extension" && r.ext.name === extName);
13812
13991
  }
13992
+ /**
13993
+ * Filter segments by the active query and resolve each one's expanded state.
13994
+ *
13995
+ * Deliberately does NOT auto-expand matches the way the Extensions tab does:
13996
+ * the search corpus includes each segment's full text, so a query typically
13997
+ * matches deep inside a long document — auto-expanding every hit would dump
13998
+ * thousands of lines into the viewport on a keystroke. The user expands the row
13999
+ * they care about.
14000
+ */
14001
+ function buildVisibleInstructionRows(opts) {
14002
+ const { segments, expanded, queryActive, matchSegment } = opts;
14003
+ const out = [];
14004
+ for (const segment of segments) {
14005
+ if (queryActive && !matchSegment(segment)) continue;
14006
+ out.push({
14007
+ segment,
14008
+ expanded: expanded.has(segment.id)
14009
+ });
14010
+ }
14011
+ return out;
14012
+ }
14013
+ /**
14014
+ * Split a segment's text into renderable preview lines, clamped to
14015
+ * {@link INSTRUCTION_PREVIEW_LINE_CAP}.
14016
+ *
14017
+ * `hidden` is how many lines were dropped off the tail, so the view can render
14018
+ * an honest "+N more lines" marker instead of silently truncating. Tabs are
14019
+ * expanded to two spaces because the terminal text renderable doesn't
14020
+ * interpret `\t`, and `\r` is stripped so CRLF files don't leave artefacts at
14021
+ * every line end.
14022
+ */
14023
+ function instructionPreviewLines(text) {
14024
+ const all = text.replace(/\r\n?/g, "\n").replace(/\t/g, " ").split("\n");
14025
+ while (all.length > 0 && all[all.length - 1].trim() === "") all.pop();
14026
+ if (all.length <= 400) return {
14027
+ lines: all,
14028
+ hidden: 0
14029
+ };
14030
+ return {
14031
+ lines: all.slice(0, 400),
14032
+ hidden: all.length - 400
14033
+ };
14034
+ }
14035
+ /**
14036
+ * Short badge for a segment's origin — the "where does this come from" column.
14037
+ * `'file'` is intentionally the only one that reads like a location, since it's
14038
+ * the only origin the user can act on.
14039
+ */
14040
+ function instructionOriginBadge(segment) {
14041
+ switch (segment.origin) {
14042
+ case "file": return "file";
14043
+ case "generated": return "generated";
14044
+ case "mcp": return "mcp server";
14045
+ case "built-in": return "built-in";
14046
+ }
14047
+ }
13813
14048
  //#endregion
13814
14049
  //#region src/tui/logs-view.ts
13815
14050
  /** Level order for the tab's min-level filter (matches the logger's ordering). */
@@ -14606,13 +14841,14 @@ const TAB_LABELS = {
14606
14841
  ...Object.fromEntries(SETTINGS_CATEGORIES.map((c) => [c.id, c.label])),
14607
14842
  keybindings: "Keybindings",
14608
14843
  authentication: "Authentication",
14844
+ instructions: "Instructions",
14609
14845
  skills: "Skills",
14610
14846
  mcps: "MCP servers",
14611
14847
  extensions: "Extensions & Plugins",
14612
14848
  logs: "Logs"
14613
14849
  };
14614
14850
  function isCategoryTab(id) {
14615
- return id !== "skills" && id !== "mcps" && id !== "keybindings" && id !== "authentication" && id !== "extensions" && id !== "logs";
14851
+ return id !== "skills" && id !== "mcps" && id !== "keybindings" && id !== "authentication" && id !== "extensions" && id !== "instructions" && id !== "logs";
14616
14852
  }
14617
14853
  function anchorIdFor(index) {
14618
14854
  return `settings-row-${index}`;
@@ -14889,6 +15125,16 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14889
15125
  const mcpToolToggleMap = useMcpToolToggleMap({ catalogByServer: mcpToolsByServer });
14890
15126
  const [expandedMcps, setExpandedMcps] = useState(() => /* @__PURE__ */ new Set());
14891
15127
  const [expandedExtensions, setExpandedExtensions] = useState(() => /* @__PURE__ */ new Set());
15128
+ const [expandedInstructions, setExpandedInstructions] = useState(() => /* @__PURE__ */ new Set());
15129
+ const [instructionsNotice, setInstructionsNotice] = useState(null);
15130
+ const [instructionSegments, setInstructionSegments] = useState([]);
15131
+ const [instructionsLoading, setInstructionsLoading] = useState(false);
15132
+ const [instructionsError, setInstructionsError] = useState(void 0);
15133
+ const [instructionsLive, setInstructionsLive] = useState(true);
15134
+ const [instructionsRefreshKey, setInstructionsRefreshKey] = useState(0);
15135
+ const [instructionsShadowedSystem, setInstructionsShadowedSystem] = useState(void 0);
15136
+ const [instructionsCanOverride, setInstructionsCanOverride] = useState(true);
15137
+ const [instructionsOverrideBlockedBy, setInstructionsOverrideBlockedBy] = useState(void 0);
14892
15138
  const onRefreshMcpToolsRef = useRef(actions?.onRefreshMcpTools);
14893
15139
  onRefreshMcpToolsRef.current = actions?.onRefreshMcpTools;
14894
15140
  const [mcpToolsRefreshing, setMcpToolsRefreshing] = useState(false);
@@ -14905,6 +15151,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14905
15151
  const tabs = [...SETTINGS_CATEGORIES.map((c) => c.id)];
14906
15152
  if (keybindings) tabs.push("keybindings");
14907
15153
  if (authentication) tabs.push("authentication");
15154
+ if (actions?.getContextSegments) tabs.push("instructions");
14908
15155
  tabs.push("skills", "mcps");
14909
15156
  if (extensionsCatalogProp || extensionsInactiveProp) tabs.push("extensions");
14910
15157
  if (logs) tabs.push("logs");
@@ -14912,6 +15159,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
14912
15159
  }, [
14913
15160
  keybindings,
14914
15161
  authentication,
15162
+ actions?.getContextSegments,
14915
15163
  extensionsCatalogProp,
14916
15164
  extensionsInactiveProp,
14917
15165
  logs
@@ -15090,6 +15338,32 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15090
15338
  for (const d of mcpsCatalogLiveRef.current) mcpColdProbedRef.current.add(d.config.name);
15091
15339
  fireMcpToolsRefresh();
15092
15340
  }, [activeTab, fireMcpToolsRefresh]);
15341
+ useEffect(() => {
15342
+ const fetchSegments = actions?.getContextSegments;
15343
+ if (activeTab !== "instructions" || !fetchSegments) return;
15344
+ let cancelled = false;
15345
+ setInstructionsLoading(true);
15346
+ Promise.resolve(fetchSegments()).then((result) => {
15347
+ if (cancelled) return;
15348
+ setInstructionSegments(result.segments);
15349
+ setInstructionsLive(result.live);
15350
+ setInstructionsCanOverride(result.canOverrideSystem);
15351
+ setInstructionsOverrideBlockedBy(result.overrideBlockedBy);
15352
+ setInstructionsShadowedSystem(result.shadowedSystemPath);
15353
+ setInstructionsError(void 0);
15354
+ }).catch((err) => {
15355
+ if (!cancelled) setInstructionsError(errorMessage(err));
15356
+ }).finally(() => {
15357
+ if (!cancelled) setInstructionsLoading(false);
15358
+ });
15359
+ return () => {
15360
+ cancelled = true;
15361
+ };
15362
+ }, [
15363
+ activeTab,
15364
+ actions?.getContextSegments,
15365
+ instructionsRefreshKey
15366
+ ]);
15093
15367
  useEffect(() => {
15094
15368
  if (activeTab !== "mcps") return;
15095
15369
  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 +15491,24 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15217
15491
  mcpToolsByServer
15218
15492
  ]);
15219
15493
  const filteredProviders = useMemo(() => (authentication?.providers ?? []).filter((p) => matchesTerms(providerCorpus(p), queryTerms)), [authentication?.providers, queryTerms]);
15494
+ const instructionVisibleRows = useMemo(() => buildVisibleInstructionRows({
15495
+ segments: instructionSegments,
15496
+ expanded: expandedInstructions,
15497
+ queryActive,
15498
+ matchSegment: (seg) => matchesTerms(instructionCorpus(seg), queryTerms)
15499
+ }), [
15500
+ instructionSegments,
15501
+ expandedInstructions,
15502
+ queryActive,
15503
+ queryTerms
15504
+ ]);
15220
15505
  const authRowCount = filteredProviders.length;
15221
15506
  const filteredSize = {
15222
15507
  ...Object.fromEntries(SETTINGS_CATEGORIES.map((c) => [c.id, filteredByCategory[c.id].length])),
15223
15508
  skills: filteredSkills.length,
15224
15509
  mcps: mcpVisibleRows.length,
15225
15510
  extensions: extensionVisibleRows.length,
15511
+ instructions: instructionVisibleRows.length,
15226
15512
  keybindings: 0,
15227
15513
  logs: 0,
15228
15514
  authentication: authRowCount
@@ -15320,8 +15606,20 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15320
15606
  });
15321
15607
  }, [extensionVisibleRows]);
15322
15608
  const toggleExpandFocusedExtensionRow = useCallback(() => toggleExpandExtensionRowAt(cursor), [toggleExpandExtensionRowAt, cursor]);
15609
+ const toggleExpandInstructionRowAt = useCallback((index) => {
15610
+ const row = instructionVisibleRows[index];
15611
+ if (!row) return;
15612
+ setExpandedInstructions((prev) => {
15613
+ const next = new Set(prev);
15614
+ if (!next.delete(row.segment.id)) next.add(row.segment.id);
15615
+ return next;
15616
+ });
15617
+ }, [instructionVisibleRows]);
15618
+ const toggleExpandFocusedInstructionRow = useCallback(() => toggleExpandInstructionRowAt(cursor), [toggleExpandInstructionRowAt, cursor]);
15323
15619
  const mcpRowCount = mcpVisibleRows.length;
15324
15620
  const extensionRowCount = extensionVisibleRows.length;
15621
+ const instructionRowCount = instructionVisibleRows.length;
15622
+ const instructionExpandedCount = expandedInstructions.size;
15325
15623
  useEffect(() => {
15326
15624
  if (activeTab === "keybindings" || activeTab === "logs") return;
15327
15625
  const sb = scrollboxRef.current;
@@ -15345,7 +15643,9 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15345
15643
  activeTab,
15346
15644
  query,
15347
15645
  mcpRowCount,
15348
- extensionRowCount
15646
+ extensionRowCount,
15647
+ instructionRowCount,
15648
+ instructionExpandedCount
15349
15649
  ]);
15350
15650
  useEffect(() => {
15351
15651
  if (mcpRowCount === 0) return;
@@ -15367,6 +15667,19 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15367
15667
  };
15368
15668
  });
15369
15669
  }, [extensionRowCount]);
15670
+ useEffect(() => {
15671
+ if (instructionRowCount === 0) return;
15672
+ setCursorByTab((prev) => {
15673
+ if (prev.instructions < instructionRowCount) return prev;
15674
+ return {
15675
+ ...prev,
15676
+ instructions: instructionRowCount - 1
15677
+ };
15678
+ });
15679
+ }, [instructionRowCount]);
15680
+ useEffect(() => {
15681
+ setInstructionsNotice(null);
15682
+ }, [cursor, activeTab]);
15370
15683
  useEffect(() => {
15371
15684
  if (activeTab !== "keybindings") return;
15372
15685
  const sb = scrollboxRef.current;
@@ -15466,6 +15779,22 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15466
15779
  } else if (row.kind === "tool") mcpToolToggleMap[row.serverName]?.toggle(row.tool.name);
15467
15780
  return;
15468
15781
  }
15782
+ if (activeTab === "instructions") {
15783
+ const row = instructionVisibleRows[index];
15784
+ if (!row) return;
15785
+ const { segment } = row;
15786
+ if (segment.id === "system" && !segment.path && actions?.onCreateSystemPrompt) {
15787
+ if (!instructionsCanOverride) {
15788
+ 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.");
15789
+ return;
15790
+ }
15791
+ Promise.resolve(actions.onCreateSystemPrompt()).catch((err) => setInstructionsNotice(`Could not create SYSTEM.md: ${errorMessage(err)}`));
15792
+ return;
15793
+ }
15794
+ if (segment.path) Promise.resolve(actions?.onOpenInstructionsFile?.(segment.path)).catch((err) => setInstructionsNotice(`Could not open ${segment.label}: ${errorMessage(err)}`));
15795
+ else setInstructionsNotice(`"${segment.label}" is ${instructionOriginBadge(segment)} — no file to open. Press tab to read it inline.`);
15796
+ return;
15797
+ }
15469
15798
  if (activeTab === "keybindings") {
15470
15799
  actions?.onOpenKeybindings?.();
15471
15800
  return;
@@ -15492,7 +15821,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15492
15821
  fireMcpToolsRefresh,
15493
15822
  mcpToolToggleMap,
15494
15823
  actions,
15495
- filteredProviders
15824
+ filteredProviders,
15825
+ instructionVisibleRows,
15826
+ instructionsCanOverride,
15827
+ instructionsOverrideBlockedBy
15496
15828
  ]);
15497
15829
  /**
15498
15830
  * True while an inline editor or overlay dialog owns the modal's input
@@ -15549,6 +15881,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15549
15881
  activateRowAt(index);
15550
15882
  return;
15551
15883
  }
15884
+ if (activeTab === "instructions") {
15885
+ toggleExpandInstructionRowAt(index);
15886
+ return;
15887
+ }
15552
15888
  if (activeTab === "skills") return;
15553
15889
  if (isCategoryTab(activeTab) && filteredByCategory[activeTab][index]?.kind === "toggle") return;
15554
15890
  activateRowAt(index);
@@ -15560,6 +15896,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15560
15896
  filteredByCategory,
15561
15897
  toggleExpandMcpRowAt,
15562
15898
  toggleExpandExtensionRowAt,
15899
+ toggleExpandInstructionRowAt,
15563
15900
  activateRowAt
15564
15901
  ]);
15565
15902
  /** Tab-strip clicks — same input-capture guard as row clicks. */
@@ -15628,6 +15965,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15628
15965
  toggleExpandFocusedExtensionRow();
15629
15966
  return;
15630
15967
  }
15968
+ if (activeTab === "instructions" && key.name === "tab") {
15969
+ toggleExpandFocusedInstructionRow();
15970
+ return;
15971
+ }
15631
15972
  if (key.name === "return") {
15632
15973
  activateRowAt(cursor);
15633
15974
  return;
@@ -15832,6 +16173,14 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15832
16173
  actions.onRefreshSkills();
15833
16174
  return;
15834
16175
  }
16176
+ if (activeTab === "instructions" && actions?.onRefreshInstructions) {
16177
+ setInstructionsNotice("reloading…");
16178
+ actions.onRefreshInstructions().then((outcome) => {
16179
+ setInstructionsRefreshKey((n) => n + 1);
16180
+ 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");
16181
+ }).catch((err) => setInstructionsNotice(`Refresh failed: ${errorMessage(err)}`));
16182
+ return;
16183
+ }
15835
16184
  if (activeTab === "extensions" && actions?.onRefreshPlugins) {
15836
16185
  actions.onRefreshPlugins();
15837
16186
  return;
@@ -15981,6 +16330,21 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
15981
16330
  contentCols,
15982
16331
  authState
15983
16332
  }),
16333
+ activeTab === "instructions" && /* @__PURE__ */ jsx$1(InstructionsList, {
16334
+ onRowClick: clickRowAt,
16335
+ rows: instructionVisibleRows,
16336
+ totalCount: instructionSegments.length,
16337
+ loading: instructionsLoading,
16338
+ error: instructionsError,
16339
+ live: instructionsLive,
16340
+ shadowedSystemPath: instructionsShadowedSystem,
16341
+ canOverrideSystem: instructionsCanOverride,
16342
+ overrideBlockedBy: instructionsOverrideBlockedBy,
16343
+ cursor,
16344
+ highlightBg: SURFACE.selection,
16345
+ query,
16346
+ contentCols
16347
+ }),
15984
16348
  activeTab === "keybindings" && keybindings && /* @__PURE__ */ jsx$1(KeybindingsList, {
15985
16349
  bindings: keybindings,
15986
16350
  extensionRows: extensionKeybindings,
@@ -16013,6 +16377,13 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
16013
16377
  style: { flexShrink: 0 },
16014
16378
  children: renderMcpDetailPanel(focusedMcp, focusedMcpStatus, COLOR)
16015
16379
  }),
16380
+ activeTab === "instructions" && instructionsNotice && /* @__PURE__ */ jsx$1("box", {
16381
+ style: { flexShrink: 0 },
16382
+ children: /* @__PURE__ */ jsx$1("text", {
16383
+ fg: COLOR.mute,
16384
+ children: instructionsNotice
16385
+ })
16386
+ }),
16016
16387
  activeTab === "extensions" && extEditNotice && /* @__PURE__ */ jsx$1("box", {
16017
16388
  style: { flexShrink: 0 },
16018
16389
  children: /* @__PURE__ */ jsx$1("text", {
@@ -16125,7 +16496,9 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
16125
16496
  if (row?.kind !== "config" || row.item.type !== "string" || !row.item.secret) return false;
16126
16497
  const stored = settings.extensionConfig?.[row.extName]?.[row.item.key];
16127
16498
  return stored !== void 0 && stored !== row.item.default;
16128
- })()
16499
+ })(),
16500
+ focusedInstructionOpenable: activeTab === "instructions" && instructionVisibleRows[cursor]?.segment.path !== void 0,
16501
+ focusedInstructionCreatable: activeTab === "instructions" && actions?.onCreateSystemPrompt !== void 0 && instructionsCanOverride && instructionVisibleRows[cursor]?.segment.id === "system" && instructionVisibleRows[cursor]?.segment.path === void 0
16129
16502
  })
16130
16503
  }),
16131
16504
  extInstallActive && /* @__PURE__ */ jsx$1(ExtensionInstallDialog, {
@@ -17225,6 +17598,101 @@ function renderEmptyToolsListRow({ serverName, anchorId, focused, bg, COLOR, ref
17225
17598
  })]
17226
17599
  }, `tool-empty:${serverName}`);
17227
17600
  }
17601
+ function InstructionsList({ rows, totalCount, loading, error, live, shadowedSystemPath, canOverrideSystem, overrideBlockedBy, cursor, highlightBg, query, contentCols, onRowClick }) {
17602
+ const COLOR = useColors();
17603
+ if (error) return /* @__PURE__ */ jsx$1("text", {
17604
+ fg: COLOR.error,
17605
+ children: `Could not read context: ${error}`
17606
+ });
17607
+ if (loading && totalCount === 0) return /* @__PURE__ */ jsx$1(EmptyRow, { label: "reading session context…" });
17608
+ if (totalCount === 0) return /* @__PURE__ */ jsx$1(EmptyRow, { label: "this profile loads no default context" });
17609
+ if (rows.length === 0) return /* @__PURE__ */ jsx$1(EmptyRow, { label: `no context matches "${query}"` });
17610
+ const INDENT = " ";
17611
+ const detailCols = Math.max(0, contentCols - 1 - 1 - 4);
17612
+ return /* @__PURE__ */ jsxs("box", {
17613
+ style: { flexDirection: "column" },
17614
+ children: [
17615
+ !live && /* @__PURE__ */ jsx$1("text", {
17616
+ fg: COLOR.mute,
17617
+ children: " No active session — showing what your next one will load. MCP and extension context is added at run time."
17618
+ }),
17619
+ shadowedSystemPath !== void 0 && /* @__PURE__ */ jsx$1("text", {
17620
+ fg: COLOR.mute,
17621
+ 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.`
17622
+ }),
17623
+ rows.map(({ segment, expanded }, i) => {
17624
+ const focused = i === cursor;
17625
+ const bg = focused ? highlightBg : void 0;
17626
+ const openable = segment.path !== void 0;
17627
+ const secondLine = segment.path ? displayPath$1(segment.path, homedir()) : segment.detail ?? "";
17628
+ const preview = expanded ? instructionPreviewLines(segment.text) : null;
17629
+ return /* @__PURE__ */ jsxs("box", {
17630
+ onMouseDown: onRowClick && ((event) => {
17631
+ event.stopPropagation();
17632
+ event.preventDefault();
17633
+ onRowClick(i, "row");
17634
+ }),
17635
+ id: anchorIdFor(i),
17636
+ style: {
17637
+ flexDirection: "column",
17638
+ flexShrink: 0,
17639
+ paddingLeft: 1,
17640
+ paddingRight: 1,
17641
+ backgroundColor: bg
17642
+ },
17643
+ children: [
17644
+ /* @__PURE__ */ jsxs("text", {
17645
+ wrapMode: "none",
17646
+ children: [
17647
+ /* @__PURE__ */ jsx$1("span", {
17648
+ fg: focused ? COLOR.brand : COLOR.mute,
17649
+ children: focused ? "▶ " : " "
17650
+ }),
17651
+ /* @__PURE__ */ jsx$1("span", {
17652
+ fg: COLOR.mute,
17653
+ children: expanded ? "▾ " : "▸ "
17654
+ }),
17655
+ /* @__PURE__ */ jsx$1("span", {
17656
+ fg: focused ? COLOR.brand : COLOR.dim,
17657
+ children: segment.label
17658
+ }),
17659
+ /* @__PURE__ */ jsxs("span", {
17660
+ fg: COLOR.mute,
17661
+ children: [" · ", instructionOriginBadge(segment)]
17662
+ }),
17663
+ /* @__PURE__ */ jsxs("span", {
17664
+ fg: COLOR.mute,
17665
+ children: [" · ~", fmtTokens(segment.tokens)]
17666
+ })
17667
+ ]
17668
+ }),
17669
+ secondLine !== "" && /* @__PURE__ */ jsx$1("text", {
17670
+ wrapMode: "none",
17671
+ fg: openable ? COLOR.model : COLOR.mute,
17672
+ children: `${INDENT}${truncateToCols(secondLine, detailCols)}`
17673
+ }),
17674
+ preview && /* @__PURE__ */ jsxs("box", {
17675
+ style: {
17676
+ flexDirection: "column",
17677
+ paddingLeft: 4,
17678
+ paddingTop: 1
17679
+ },
17680
+ children: [preview.lines.map((line, n) => /* @__PURE__ */ jsx$1("text", {
17681
+ wrapMode: "none",
17682
+ fg: COLOR.dim,
17683
+ children: line === "" ? " " : truncateToCols(line, detailCols)
17684
+ }, n)), preview.hidden > 0 && /* @__PURE__ */ jsx$1("text", {
17685
+ wrapMode: "none",
17686
+ fg: COLOR.mute,
17687
+ children: `… +${preview.hidden} more line${preview.hidden === 1 ? "" : "s"}${openable ? " — press ↵ to open the file" : ""}`
17688
+ })]
17689
+ })
17690
+ ]
17691
+ }, segment.id);
17692
+ })
17693
+ ]
17694
+ });
17695
+ }
17228
17696
  function KeybindingsList({ bindings, extensionRows, query }) {
17229
17697
  const sections = useMemo(() => groupBindings(bindings, extensionRows), [bindings, extensionRows]);
17230
17698
  const filteredSections = useMemo(() => {
@@ -17713,7 +18181,7 @@ function renderMcpErrors(errors, home, warnColor, dimColor) {
17713
18181
  }, err.path))
17714
18182
  });
17715
18183
  }
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 }) {
18184
+ 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
18185
  const COLOR = useColors();
17718
18186
  const onServerRow = focusedMcpRow?.kind === "server";
17719
18187
  const showLogin = activeTab === "mcps" && onServerRow && !!focusedMcp && !!focusedMcpStatus && canLogin$1(focusedMcp, focusedMcpStatus);
@@ -17737,8 +18205,15 @@ function Hints({ activeTab, authorizing = false, focusedMcp, focusedMcpRow, focu
17737
18205
  label: "type to filter"
17738
18206
  }] : [{
17739
18207
  key: "↵",
17740
- label: isCategoryTab(activeTab) ? "toggle/cycle/select" : activeTab === "keybindings" ? "edit file" : activeTab === "authentication" ? "switch / re-authenticate" : "toggle"
18208
+ 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
18209
  }],
18210
+ ...activeTab === "instructions" ? [{
18211
+ key: "tab",
18212
+ label: "read inline"
18213
+ }, {
18214
+ key: "ctrl+r",
18215
+ label: "reload"
18216
+ }] : [],
17742
18217
  ...activeTab === "mcps" && focusedMcpRow ? [{
17743
18218
  key: "tab",
17744
18219
  label: focusedMcpRow.kind === "server" ? "expand/collapse" : "collapse"
@@ -17904,6 +18379,21 @@ function keybindingCorpus(row) {
17904
18379
  if (row.kind === "extension") return `${row.spec} ${row.label} ${row.description} ${row.id}`.toLowerCase();
17905
18380
  return `${row.spec} ${row.def.label} ${row.def.description} ${row.def.action}`.toLowerCase();
17906
18381
  }
18382
+ /**
18383
+ * Search corpus for an Instructions row — label, origin, detail, path AND the
18384
+ * segment's full text. Including the body is the point: it turns the shared
18385
+ * search box into "which instruction source told the agent this?", which is
18386
+ * the question this tab exists to answer.
18387
+ */
18388
+ function instructionCorpus(segment) {
18389
+ return [
18390
+ segment.label,
18391
+ instructionOriginBadge(segment),
18392
+ segment.detail ?? "",
18393
+ segment.path ?? "",
18394
+ segment.text
18395
+ ].join(" ").toLowerCase();
18396
+ }
17907
18397
  function providerCorpus(p) {
17908
18398
  return `${p.label} ${p.key} ${p.methods.map((m) => `${m.source} ${m.detail}`).join(" ")}`.toLowerCase();
17909
18399
  }
@@ -18820,29 +19310,45 @@ const debugLog = createDebugLog("tui");
18820
19310
  * spawned process is detached + unref'd so closing the TUI doesn't
18821
19311
  * kill the editor (and vice versa).
18822
19312
  *
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
19313
+ * Resolves once the spawn is DISPATCHED (the `'spawn'` event) — we don't wait
19314
+ * for the editor to exit. The user is expected to save and either restart
18825
19315
  * the TUI (for changes that need to be picked up at launch, like
18826
19316
  * keybindings) or simply continue working.
19317
+ *
19318
+ * Rejects when the spawn fails. `child_process.spawn` reports ENOENT through an
19319
+ * `'error'` EVENT rather than a throw, so a missing binary used to surface as an
19320
+ * unhandled emitter error — crashing into OpenTUI's error console right past
19321
+ * every caller's `try`/`catch`. Funnelling both outcomes into the promise is
19322
+ * what makes those guards effective.
18827
19323
  */
18828
19324
  async function launchEditor(path) {
18829
19325
  const envEditor = (process.env.VISUAL ?? process.env.EDITOR ?? "").trim();
19326
+ const win32 = process.platform === "win32";
19327
+ const q = win32 ? quoteForShell : (s) => s;
18830
19328
  const [cmd, args] = (() => {
18831
- if (envEditor) return [envEditor, [path]];
19329
+ const [bin, ...editorArgs] = parseEditorCommand(envEditor);
19330
+ if (bin) return [q(bin), [...editorArgs.map(q), q(path)]];
18832
19331
  if (process.platform === "darwin") return ["open", [path]];
18833
- if (process.platform === "win32") return ["cmd", [
19332
+ if (win32) return ["cmd", [
18834
19333
  "/c",
18835
19334
  "start",
18836
19335
  "\"\"",
18837
- path
19336
+ q(path)
18838
19337
  ]];
18839
19338
  return ["xdg-open", [path]];
18840
19339
  })();
18841
- spawn(cmd, args, {
18842
- detached: true,
18843
- stdio: "ignore",
18844
- shell: process.platform === "win32"
18845
- }).unref();
19340
+ await new Promise((resolve, reject) => {
19341
+ const child = spawn(cmd, args, {
19342
+ detached: true,
19343
+ stdio: "ignore",
19344
+ shell: win32
19345
+ });
19346
+ child.once("error", reject);
19347
+ child.once("spawn", () => {
19348
+ child.unref();
19349
+ resolve();
19350
+ });
19351
+ });
18846
19352
  }
18847
19353
  /**
18848
19354
  * Session-metadata key under which the TUI persists the user's "pinned"
@@ -19056,6 +19562,29 @@ function AppShell() {
19056
19562
  useEffect(() => {
19057
19563
  userInstructionsScopeRef.current = settings.userInstructionsScope;
19058
19564
  }, [settings.userInstructionsScope]);
19565
+ const sessionInstructionFilesRef = useRef([]);
19566
+ /**
19567
+ * Path of the `SYSTEM.md` override the CURRENT session's prompt was built
19568
+ * from, or `null` when the built-in doctrine is in force. Same activation-time
19569
+ * semantics as `sessionInstructionFilesRef`: what the live prompt came from,
19570
+ * not what's on disk right now.
19571
+ */
19572
+ const sessionSystemFileRef = useRef(null);
19573
+ /**
19574
+ * Path of the project-scope override whose warning is already in the visible
19575
+ * transcript, so a rebuild that changes nothing stays quiet. Reset by
19576
+ * `activateSession` because `teardown` clears the transcript the note lived in.
19577
+ */
19578
+ const announcedSystemOverrideRef = useRef(null);
19579
+ /**
19580
+ * The rendered AGENTS.md block this session's prompt was built with, or `null`
19581
+ * when none loaded. Threaded into `getContextSegments` so the base-prompt
19582
+ * segment can subtract it: the agent receives the block as an opaque substring
19583
+ * of the system string and can't find it on its own, and without this the
19584
+ * instruction text would be reported twice — once inside the system prompt
19585
+ * segment, once as its own file rows.
19586
+ */
19587
+ const sessionRulesBlockRef = useRef(null);
19059
19588
  const subagentModelsRef = useRef(settings.subagentModels);
19060
19589
  useEffect(() => {
19061
19590
  subagentModelsRef.current = settings.subagentModels;
@@ -20118,6 +20647,110 @@ function AppShell() {
20118
20647
  return d ? getContextWindow(d, entry.model) ?? void 0 : void 0;
20119
20648
  });
20120
20649
  }, [providerRegistry]);
20650
+ /**
20651
+ * The host-composed half of a session's context: the instruction files
20652
+ * discovered for the current cwd + scope, and the system prompt they get
20653
+ * rendered into.
20654
+ *
20655
+ * Extracted from `buildAgent` so session activation and the Settings →
20656
+ * Instructions tab share ONE composition path. The tab has to answer "what
20657
+ * does my context contain" on screens where no agent exists yet (home,
20658
+ * sessions list, model picker — settings opens everywhere but `auth`), and a
20659
+ * second copy of this logic would drift from what sessions actually load.
20660
+ *
20661
+ * Cheap and side-effect-free: a handful of `existsSync` + small reads plus
20662
+ * pure string composition, safe to call on every settings open.
20663
+ */
20664
+ /**
20665
+ * Warn in the transcript when a PROJECT-scope `SYSTEM.md` is in force.
20666
+ *
20667
+ * Such a file replaces the built-in doctrine — including the destructive-action
20668
+ * and git fragments — so one that merely exists at the root of a checkout can
20669
+ * silently drop "destructive actions need explicit confirmation" and "never
20670
+ * force-push without confirmation". `AGENTS.md` can only APPEND, and renders
20671
+ * under a header marking it as user-authored guidance; deleting doctrine is a
20672
+ * different risk profile from adding to it. The user-scope file stays quiet:
20673
+ * they wrote it themselves, in their own home directory.
20674
+ *
20675
+ * Called from EVERY path that installs a prompt — session activation and
20676
+ * `rebuildActiveAgent` (which serves the Instructions tab's `ctrl+r`, both its
20677
+ * immediate and its deferred-until-idle branch, plus post-login and
20678
+ * extension-swap rebuilds). Announcing only on activation meant that dropping a
20679
+ * `SYSTEM.md` into a checkout and hitting `ctrl+r` swapped the safety guidance
20680
+ * out in silence.
20681
+ *
20682
+ * De-duped on the announced path so an unchanged rebuild doesn't repeat itself.
20683
+ */
20684
+ const announceProjectSystemOverride = useCallback(() => {
20685
+ const override = sessionSystemFileRef.current;
20686
+ if (override?.source !== "project") {
20687
+ announcedSystemOverrideRef.current = null;
20688
+ return;
20689
+ }
20690
+ if (announcedSystemOverrideRef.current === override.path) return;
20691
+ announcedSystemOverrideRef.current = override.path;
20692
+ stream.appendImmediate({
20693
+ kind: "info",
20694
+ 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.`
20695
+ });
20696
+ }, [stream]);
20697
+ const composeHostContext = useCallback((profile, allowInteraction) => {
20698
+ const actualCwd = cwdRef.current;
20699
+ const agentsMd = discoverAgentsMd({
20700
+ cwd: actualCwd,
20701
+ prefix: config.prefix,
20702
+ scope: userInstructionsScopeRef.current,
20703
+ ruler: importRulerConfigsRef.current !== false
20704
+ });
20705
+ const systemMd = discoverSystemMd({
20706
+ cwd: actualCwd,
20707
+ prefix: config.prefix,
20708
+ scope: userInstructionsScopeRef.current
20709
+ });
20710
+ const supportsOverride = profile.id === "build" || profile.id === "plan";
20711
+ const envOpts = {
20712
+ cwd: actualCwd,
20713
+ ...projectDir !== actualCwd ? { projectRoot: projectDir } : {},
20714
+ allowInteraction,
20715
+ ...agentsMd.block ? { userInstructions: agentsMd.block } : {},
20716
+ ...systemMd.file ? { systemOverride: systemMd.file.contents } : {}
20717
+ };
20718
+ const build = profile.id === "build" ? buildBuildSystem : profile.id === "plan" ? buildPlanSystem : null;
20719
+ const system = build ? build(envOpts) : null;
20720
+ return {
20721
+ agentsMd,
20722
+ /** The override actually in force, or `null` when this profile ignores overrides. */
20723
+ systemFile: supportsOverride ? systemMd.file : null,
20724
+ /**
20725
+ * Overrides that exist on disk but aren't loaded. On a profile that can't
20726
+ * accept one, that's EVERY discovered file — not just the precedence
20727
+ * losers — so the tab can explain the silence.
20728
+ */
20729
+ shadowedSystemFiles: supportsOverride ? systemMd.shadowed : [...systemMd.file ? [systemMd.file] : [], ...systemMd.shadowed],
20730
+ supportsOverride,
20731
+ envOpts,
20732
+ system,
20733
+ /**
20734
+ * The BUILT-IN prompt: no AGENTS.md block, and deliberately no active
20735
+ * `SYSTEM.md` either — the correct seed for a new override.
20736
+ *
20737
+ * Seeding from `system` would bake the instructions into the override and
20738
+ * the next session would append them again (the override replaces the
20739
+ * doctrine; `userInstructions` still composes below it). Clearing
20740
+ * `systemOverride` too means this always answers "what would the built-in
20741
+ * doctrine be", so a future "reset my override" or "diff against default"
20742
+ * caller can't be handed a copy of the override it was trying to replace.
20743
+ *
20744
+ * A thunk so the extra composition only happens on the create path, not
20745
+ * on every session activation.
20746
+ */
20747
+ doctrineOnlySystem: () => build ? build({
20748
+ ...envOpts,
20749
+ userInstructions: void 0,
20750
+ systemOverride: void 0
20751
+ }) : null
20752
+ };
20753
+ }, [config.prefix, projectDir]);
20121
20754
  const buildAgent = useCallback((session, key) => {
20122
20755
  const descriptor = providerRegistry[key];
20123
20756
  if (!descriptor) throw new Error(`No provider registered for key "${key}"`);
@@ -20178,19 +20811,13 @@ function AppShell() {
20178
20811
  const allowInteraction = allowInteractionRef.current !== false;
20179
20812
  const interactionTools = allowInteraction ? createInteractionTools({ requestInteraction: makeRequestInteraction(interactions) }) : {};
20180
20813
  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;
20814
+ const { agentsMd, systemFile, system: builtInSystem } = composeHostContext(profile, allowInteraction);
20815
+ sessionInstructionFilesRef.current = agentsMd.files;
20816
+ sessionRulesBlockRef.current = agentsMd.block;
20817
+ sessionSystemFileRef.current = systemFile ? {
20818
+ path: systemFile.path,
20819
+ source: systemFile.source
20820
+ } : null;
20194
20821
  const persistDir = resolvePersistDir({
20195
20822
  userDir: cacheDir,
20196
20823
  sessionId: session.id
@@ -21168,6 +21795,8 @@ function AppShell() {
21168
21795
  sessionRef.current = session;
21169
21796
  agentRef.current = buildAgent(session, key);
21170
21797
  if (extensionRegistrySwapKeyRef.current === null || extensionRegistrySwapKeyRef.current === extensionRegistry) pendingAgentRebuildRef.current = false;
21798
+ announcedSystemOverrideRef.current = null;
21799
+ announceProjectSystemOverride();
21171
21800
  const persistedPins = readPinnedSkills(session.metadata["zidane.activeSkills"]);
21172
21801
  setActiveSkillNames(persistedPins);
21173
21802
  setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
@@ -22823,6 +23452,8 @@ function AppShell() {
22823
23452
  const mcpLoginAbortsRef = useRef(/* @__PURE__ */ new Map());
22824
23453
  const buildAgentRef = useRef(buildAgent);
22825
23454
  buildAgentRef.current = buildAgent;
23455
+ const announceProjectSystemOverrideRef = useRef(announceProjectSystemOverride);
23456
+ announceProjectSystemOverrideRef.current = announceProjectSystemOverride;
22826
23457
  /**
22827
23458
  * Rebuild the agent for the active session — used post-login so the
22828
23459
  * newly-authenticated MCP server actually connects. The current agent's
@@ -22843,6 +23474,7 @@ function AppShell() {
22843
23474
  const staleWarnLog = warnLogRef.current;
22844
23475
  const fresh = buildAgentRef.current(session, p.provider.key);
22845
23476
  agentRef.current = fresh;
23477
+ announceProjectSystemOverrideRef.current();
22846
23478
  if (stale) await stale.destroy().catch((err) => debugLog("rebuildActiveAgent: destroy failed", err));
22847
23479
  if (staleWarnLog) await staleWarnLog.close().catch((err) => debugLog("rebuildActiveAgent: warnLog.close failed", err));
22848
23480
  }, []);
@@ -23012,6 +23644,106 @@ function AppShell() {
23012
23644
  }
23013
23645
  })();
23014
23646
  }, [modal, config.paths.userDir]);
23647
+ const onReadContextSegments = useCallback(async () => {
23648
+ const { shadowedSystemFiles, supportsOverride } = composeHostContext(pickedAgentRef.current, allowInteractionRef.current !== false);
23649
+ const shadowedSystemPath = shadowedSystemFiles[0]?.path;
23650
+ const scopeLoadsUserOverride = userScopeOverridesLoad(userInstructionsScopeRef.current);
23651
+ const canOverrideSystem = supportsOverride && scopeLoadsUserOverride;
23652
+ const overrideBlockedBy = !supportsOverride ? "profile" : !scopeLoadsUserOverride ? "scope" : void 0;
23653
+ const agent = agentRef.current;
23654
+ if (agent) return {
23655
+ segments: await agent.getContextSegments({
23656
+ rulesFiles: sessionInstructionFilesRef.current,
23657
+ ...sessionRulesBlockRef.current ? { rulesBlock: sessionRulesBlockRef.current } : {},
23658
+ ...sessionSystemFileRef.current ? { systemPath: sessionSystemFileRef.current.path } : {}
23659
+ }) ?? [],
23660
+ live: true,
23661
+ canOverrideSystem,
23662
+ ...overrideBlockedBy ? { overrideBlockedBy } : {},
23663
+ ...shadowedSystemPath ? { shadowedSystemPath } : {}
23664
+ };
23665
+ const { agentsMd, systemFile, system } = composeHostContext(pickedAgentRef.current, allowInteractionRef.current !== false);
23666
+ const enabled = enabledSkillsRef.current;
23667
+ const sessionSkills = enabled === void 0 ? [...skillsCatalogRef.current] : skillsCatalogRef.current.filter((s) => enabled.includes(s.name));
23668
+ return {
23669
+ segments: buildContextSegments({
23670
+ ...system ? { baseSystem: system } : {},
23671
+ ...systemFile ? { systemPath: systemFile.path } : {},
23672
+ ...agentsMd.block ? { rulesBlock: agentsMd.block } : {},
23673
+ rulesFiles: agentsMd.files,
23674
+ skillsCatalog: buildCatalog(sessionSkills)
23675
+ }),
23676
+ live: false,
23677
+ canOverrideSystem,
23678
+ ...overrideBlockedBy ? { overrideBlockedBy } : {},
23679
+ ...shadowedSystemPath ? { shadowedSystemPath } : {}
23680
+ };
23681
+ }, [composeHostContext]);
23682
+ const onOpenInstructionsFile = useCallback(async (path) => {
23683
+ await launchEditor(path);
23684
+ modal.close();
23685
+ }, [modal]);
23686
+ /**
23687
+ * Create `~/.{prefix}/SYSTEM.md` and open it — the `↵` action on the System
23688
+ * prompt row when no override exists yet.
23689
+ *
23690
+ * Seeded with the doctrine currently in effect (see `renderSystemMdSeed`), so
23691
+ * creating the file changes nothing until the user edits it. An empty seed
23692
+ * would strip every line of doctrine the instant the file appeared.
23693
+ *
23694
+ * Idempotent: if the file already exists we open it rather than clobbering the
23695
+ * user's text. `composeHostContext` gives us the exact prompt the next session
23696
+ * would use as the seed source, and we take only the STATIC half so the
23697
+ * per-turn `<env>` block never gets frozen into the file.
23698
+ */
23699
+ const onCreateSystemPrompt = useCallback(async () => {
23700
+ const path = userSystemMdPath({ prefix: config.prefix });
23701
+ if (!existsSync(path)) {
23702
+ const { doctrineOnlySystem, supportsOverride } = composeHostContext(pickedAgentRef.current, allowInteractionRef.current !== false);
23703
+ if (!supportsOverride) throw new Error(`the "${pickedAgentRef.current.id}" profile supplies its own system prompt, so a SYSTEM.md override would not apply`);
23704
+ if (!userScopeOverridesLoad(userInstructionsScopeRef.current)) throw new Error(`userInstructionsScope is "${userInstructionsScopeRef.current}", so a SYSTEM.md in your home directory would not be loaded`);
23705
+ const doctrine = splitSystemPrompt(doctrineOnlySystem() ?? "").static;
23706
+ if (doctrine.trim().length === 0) throw new Error("could not compose a system prompt to seed the override from");
23707
+ mkdirSync(dirname(path), { recursive: true });
23708
+ writeFileSync(path, renderSystemMdSeed(doctrine), "utf8");
23709
+ }
23710
+ await launchEditor(path);
23711
+ modal.close();
23712
+ }, [
23713
+ modal,
23714
+ config.prefix,
23715
+ composeHostContext
23716
+ ]);
23717
+ /**
23718
+ * `ctrl+r` on the Instructions tab: re-read the instruction files from disk
23719
+ * and make the live agent actually use them.
23720
+ *
23721
+ * Re-discovery is implicit — `composeHostContext` hits the filesystem on every
23722
+ * call, so the modal's own re-fetch picks up new files. The work here is
23723
+ * rebuilding the agent so a session that's already running switches to the new
23724
+ * prompt without a TUI restart.
23725
+ *
23726
+ * Deferred while a run is in flight: swapping the system prompt underneath a
23727
+ * streaming turn is not safe, so we raise the same `pendingAgentRebuild` flag
23728
+ * that MCP-settings changes and extension-registry swaps use and let
23729
+ * `drainFollowUps` apply it once the turn ends.
23730
+ *
23731
+ * Returns which of the two happened so the tab can say so — a rebuild that
23732
+ * silently didn't take effect yet is worse than no rebuild.
23733
+ *
23734
+ * Side effect worth surfacing: `rebuildActiveAgent` destroys the stale agent,
23735
+ * which closes its MCP connections. The fresh agent reconnects lazily on its
23736
+ * next run, so the turn after a reload pays that cost. The tab's notice says so.
23737
+ */
23738
+ const onRefreshInstructions = useCallback(async () => {
23739
+ if (!agentRef.current || !sessionRef.current) return "no-session";
23740
+ if (runningRef.current) {
23741
+ pendingAgentRebuildRef.current = true;
23742
+ return "queued";
23743
+ }
23744
+ await rebuildActiveAgent();
23745
+ return "rebuilt";
23746
+ }, [rebuildActiveAgent]);
23015
23747
  const hasMultipleAgents = useMemo(() => Object.keys(liveAgentRegistry).length > 1, [liveAgentRegistry]);
23016
23748
  const turnIds = useMemo(() => selectableTurnIds(events, settings), [events, settings]);
23017
23749
  useEffect(() => {
@@ -23688,6 +24420,10 @@ function AppShell() {
23688
24420
  actions: {
23689
24421
  onConfigureProvider,
23690
24422
  onOpenKeybindings: onOpenKeybindingsFile,
24423
+ getContextSegments: onReadContextSegments,
24424
+ onOpenInstructionsFile,
24425
+ onCreateSystemPrompt,
24426
+ onRefreshInstructions,
23691
24427
  onLoginMcp,
23692
24428
  onLogoutMcp,
23693
24429
  onCancelLoginMcp,