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.
- package/dist/{acp-o-LQAjFw.js → acp-CIpD8UIw.js} +4 -4
- package/dist/{acp-o-LQAjFw.js.map → acp-CIpD8UIw.js.map} +1 -1
- package/dist/acp-cli.js +5 -5
- package/dist/acp.d.ts +1 -1
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-BVNkC1h4.js → agent-8kfGIDb_.js} +755 -586
- package/dist/agent-8kfGIDb_.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{anthropic-Dov2mC5e.js → anthropic-_Hn1YiWr.js} +2 -2
- package/dist/{anthropic-Dov2mC5e.js.map → anthropic-_Hn1YiWr.js.map} +1 -1
- package/dist/{auth-B8ZuBcKC.js → auth-CS_e8ng1.js} +6 -5
- package/dist/{auth-B8ZuBcKC.js.map → auth-CS_e8ng1.js.map} +1 -1
- package/dist/chat/pure.d.ts +4 -4
- package/dist/chat/pure.js +3 -3
- package/dist/chat.d.ts +124 -10
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +6 -6
- package/dist/{context-breakdown-BLF10tOO.js → context-segments-1mcqSCM3.js} +110 -2
- package/dist/context-segments-1mcqSCM3.js.map +1 -0
- package/dist/{context-transport-D-daWOmp.js → context-transport-Bco9Wo0b.js} +3 -3
- package/dist/{context-transport-D-daWOmp.js.map → context-transport-Bco9Wo0b.js.map} +1 -1
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/daytona.d.ts.map +1 -1
- package/dist/contexts/daytona.js +5 -2
- package/dist/contexts/daytona.js.map +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/{index-CWBSrqI6.d.ts → index-BKyWhILQ.d.ts} +179 -7
- package/dist/index-BKyWhILQ.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -7
- package/dist/{login-CR3fDHXh.js → login-D3CKhgbI.js} +2 -2
- package/dist/{login-CR3fDHXh.js.map → login-D3CKhgbI.js.map} +1 -1
- package/dist/{mcp-C6IkXNOS.js → mcp-D9fqOQo5.js} +3 -3
- package/dist/{mcp-C6IkXNOS.js.map → mcp-D9fqOQo5.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{oauth-bkEOybqm.js → oauth-BYjlFR-J.js} +49 -10
- package/dist/oauth-BYjlFR-J.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +68 -1
- package/dist/providers/xai.js.map +1 -0
- package/dist/{providers-B6mFe7OD.js → providers-VypZh7rD.js} +3 -3
- package/dist/{providers-B6mFe7OD.js.map → providers-VypZh7rD.js.map} +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +4 -4
- package/dist/restate.d.ts +1 -1
- package/dist/restate.js +1 -1
- package/dist/rolldown-runtime-BhDjJH2R.js +15 -0
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/{stdio-DzUUkkhS.js → stdio-DPpBDPx5.js} +13 -4
- package/dist/{stdio-DzUUkkhS.js.map → stdio-DPpBDPx5.js.map} +1 -1
- package/dist/{stdio-loader-BILyUGmi.js → stdio-loader-CI4sPFve.js} +11 -6
- package/dist/stdio-loader-CI4sPFve.js.map +1 -0
- package/dist/{tool-formatters-gFqQbzPY.d.ts → tool-formatters-DMr62Hpy.d.ts} +3 -3
- package/dist/{tool-formatters-gFqQbzPY.d.ts.map → tool-formatters-DMr62Hpy.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-qCs3wZpu.d.ts → transcript-anchors-BHihk9nf.d.ts} +2 -2
- package/dist/{transcript-anchors-qCs3wZpu.d.ts.map → transcript-anchors-BHihk9nf.d.ts.map} +1 -1
- package/dist/{transcript-anchors-DEIr0tJC.js → transcript-anchors-uU_KZLsp.js} +161 -36
- package/dist/transcript-anchors-uU_KZLsp.js.map +1 -0
- package/dist/tui.d.ts +74 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +823 -81
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-M9-jUyDd.d.ts → turn-operations-BgqVUjtl.d.ts} +3 -3
- package/dist/{turn-operations-M9-jUyDd.d.ts.map → turn-operations-BgqVUjtl.d.ts.map} +1 -1
- package/dist/{turn-operations-C3IijXMW.js → turn-operations-iGu5J08j.js} +2 -2
- package/dist/{turn-operations-C3IijXMW.js.map → turn-operations-iGu5J08j.js.map} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/{xai-Du6Wfnlb.js → xai-CfmI5Y5k.js} +10 -67
- package/dist/xai-CfmI5Y5k.js.map +1 -0
- package/docs/CHAT.md +42 -1
- package/docs/INTERACTIONS.md +39 -0
- package/docs/RESTATE.md +7 -0
- package/docs/TUI.md +28 -2
- package/package.json +18 -17
- package/dist/agent-BVNkC1h4.js.map +0 -1
- package/dist/context-breakdown-BLF10tOO.js.map +0 -1
- package/dist/index-CWBSrqI6.d.ts.map +0 -1
- package/dist/oauth-bkEOybqm.js.map +0 -1
- package/dist/stdio-loader-BILyUGmi.js.map +0 -1
- package/dist/transcript-anchors-DEIr0tJC.js.map +0 -1
- 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 {
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
8982
|
-
*
|
|
8983
|
-
*
|
|
8984
|
-
*
|
|
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
|
|
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.
|
|
8991
|
-
*
|
|
8992
|
-
*
|
|
8993
|
-
*
|
|
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 =
|
|
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
|
-
|
|
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
|
|
9015
|
-
*
|
|
9016
|
-
* active input out of reach, thanks to scroll-into-view on advance
|
|
9017
|
-
*
|
|
9018
|
-
*
|
|
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
|
|
9022
|
-
let height =
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
18842
|
-
|
|
18843
|
-
|
|
18844
|
-
|
|
18845
|
-
|
|
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 =
|
|
20182
|
-
|
|
20183
|
-
|
|
20184
|
-
|
|
20185
|
-
|
|
20186
|
-
|
|
20187
|
-
|
|
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,
|