zidane 6.5.4 → 6.5.6
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-CIpD8UIw.js → acp-Dm95NwNM.js} +6 -6
- package/dist/{acp-CIpD8UIw.js.map → acp-Dm95NwNM.js.map} +1 -1
- package/dist/acp-cli.js +6 -6
- package/dist/acp.d.ts +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-8kfGIDb_.js → agent-CcTWL-Z6.js} +231 -167
- package/dist/agent-CcTWL-Z6.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{anthropic-qaw8XAc9.js → anthropic-DLc_Kw-1.js} +420 -25
- package/dist/anthropic-DLc_Kw-1.js.map +1 -0
- package/dist/{auth-BLy7X1SY.js → auth-BAclQnl3.js} +127 -35
- package/dist/{auth-BLy7X1SY.js.map → auth-BAclQnl3.js.map} +1 -1
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat/pure.js +2 -2
- package/dist/chat.d.ts +115 -9
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +8 -8
- package/dist/{context-segments-1mcqSCM3.js → context-segments-CJCtJ9sU.js} +16 -3
- package/dist/{context-segments-1mcqSCM3.js.map → context-segments-CJCtJ9sU.js.map} +1 -1
- package/dist/{context-transport-Bco9Wo0b.js → context-transport-D8yeGdpd.js} +2 -2
- package/dist/{context-transport-Bco9Wo0b.js.map → context-transport-D8yeGdpd.js.map} +1 -1
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/daytona.js +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/{errors-BwBxu3zD.js → errors-DKiMrOKq.js} +61 -2
- package/dist/{errors-BwBxu3zD.js.map → errors-DKiMrOKq.js.map} +1 -1
- package/dist/errors.js +2 -2
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +1 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/{fetch-url-B7aSg6Bp.js → fetch-url-ma-5_p0p.js} +2 -2
- package/dist/{fetch-url-B7aSg6Bp.js.map → fetch-url-ma-5_p0p.js.map} +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +3 -3
- package/dist/{index-BSNr0OQn.d.ts → index-ZWibV230.d.ts} +100 -28
- package/dist/index-ZWibV230.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -13
- package/dist/{login-D3CKhgbI.js → login-CB23LyI1.js} +3 -3
- package/dist/{login-D3CKhgbI.js.map → login-CB23LyI1.js.map} +1 -1
- package/dist/{mcp-D9fqOQo5.js → mcp-CVtSQTWg.js} +3 -3
- package/dist/{mcp-D9fqOQo5.js.map → mcp-CVtSQTWg.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{messages-C_H9soi5.js → messages-CZMUuKzo.js} +2 -2
- package/dist/{messages-C_H9soi5.js.map → messages-CZMUuKzo.js.map} +1 -1
- package/dist/{oauth-ZebH9NHb.js → oauth-C4GvY_p1.js} +27 -8
- package/dist/oauth-C4GvY_p1.js.map +1 -0
- package/dist/{openai-compat-CnHeo4Lz.js → openai-compat-BoTi-9fd.js} +9 -4
- package/dist/openai-compat-BoTi-9fd.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/{plugins-2PMTVcp-.js → plugins-onDvTHEm.js} +2 -2
- package/dist/{plugins-2PMTVcp-.js.map → plugins-onDvTHEm.js.map} +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +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/arcee.js +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/cerebras.js +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai-compat.js +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +4 -4
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +2 -2
- package/dist/{providers-Bxs-XHir.js → providers-CmhdtD7w.js} +3 -3
- package/dist/{providers-Bxs-XHir.js.map → providers-CmhdtD7w.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +4 -4
- package/dist/restate.d.ts +1 -1
- package/dist/restate.js +2 -2
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session/sqlite.js +1 -1
- package/dist/{session-VpLrNaFQ.js → session-CyRLSsVa.js} +2 -2
- package/dist/{session-VpLrNaFQ.js.map → session-CyRLSsVa.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/{skills-Cp9vnBwd.js → skills-BMGH_KK1.js} +2 -2
- package/dist/{skills-Cp9vnBwd.js.map → skills-BMGH_KK1.js.map} +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +1 -1
- package/dist/{timeout-BDetbuN6.js → timeout-CjOG2mea.js} +29 -2
- package/dist/timeout-CjOG2mea.js.map +1 -0
- package/dist/timeout-DEaqslLO.d.ts.map +1 -1
- package/dist/{tool-formatters-BfKqWm5N.d.ts → tool-formatters-D3wSS2zz.d.ts} +2 -2
- package/dist/{tool-formatters-BfKqWm5N.d.ts.map → tool-formatters-D3wSS2zz.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/fetch-url.js +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools/web-search.js +2 -2
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-Bv8HQ3S2.js → transcript-anchors-D-Yuimif.js} +177 -63
- package/dist/transcript-anchors-D-Yuimif.js.map +1 -0
- package/dist/{transcript-anchors-CNqDbrc9.d.ts → transcript-anchors-llwMrLRG.d.ts} +2 -2
- package/dist/{transcript-anchors-CNqDbrc9.d.ts.map → transcript-anchors-llwMrLRG.d.ts.map} +1 -1
- package/dist/tui.d.ts +24 -29
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +964 -244
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-iGu5J08j.js → turn-operations-7_Ff7INF.js} +2 -2
- package/dist/{turn-operations-iGu5J08j.js.map → turn-operations-7_Ff7INF.js.map} +1 -1
- package/dist/{turn-operations-CnIpcenJ.d.ts → turn-operations-C_XvfBU3.d.ts} +3 -3
- package/dist/{turn-operations-CnIpcenJ.d.ts.map → turn-operations-C_XvfBU3.d.ts.map} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/docs/CHAT.md +1 -1
- package/docs/TUI.md +4 -4
- package/package.json +2 -1
- package/dist/agent-8kfGIDb_.js.map +0 -1
- package/dist/anthropic-qaw8XAc9.js.map +0 -1
- package/dist/index-BSNr0OQn.d.ts.map +0 -1
- package/dist/oauth-ZebH9NHb.js.map +0 -1
- package/dist/openai-compat-CnHeo4Lz.js.map +0 -1
- package/dist/timeout-BDetbuN6.js.map +0 -1
- package/dist/transcript-anchors-Bv8HQ3S2.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { C as estimateRebilledCostUsd } from "./openai-compat-
|
|
2
|
-
import { t as buildContextSegments } from "./context-segments-
|
|
3
|
-
import { $ as installExtensionFromSpec, $t as groupBindings, A as selectFilesFromSession,
|
|
1
|
+
import { C as estimateRebilledCostUsd } from "./openai-compat-BoTi-9fd.js";
|
|
2
|
+
import { t as buildContextSegments } from "./context-segments-CJCtJ9sU.js";
|
|
3
|
+
import { $ as installExtensionFromSpec, $t as groupBindings, A as selectFilesFromSession, An as OUTPUT_RESERVE_TOKENS, C as resolveLegacySessionDir, D as resolveTasksDir, Dn as sanitizeExtensionConfigActionPatch, E as resolveSessionDir, En as resolveExtensionConfigValue, Et as isSpawnTool, G as summaryToTurn, It as looksBinary, M as compactConversation, O as buildPostCompactAttachments, Pt as tailTruncate, Qt as formatBindingForDisplay, Rt as createModelSearchTool, S as resolveLegacyPersistDir, Sn as coerceExtensionConfigInput, T as resolvePersistDir, X as createExtensionPromptBridge, Xt as KEYBINDING_KEY_COL_WIDTH, Z as ExtensionInstallError, Zt as ensureKeybindingsFile, _n as parseExtensionScanPathEnv, at as createExtensionHud, bt as basic_default, dn as registerExtensionHostModules, dt as filterExtensionRegistry, en as keybindingsPath, f as applyCompactSummaryCutoff, ft as filterRegistryPlugins, hn as inlineLoadedExtension, ht as createExtensionHostRuntimeBridge, it as updateInstalledExtension, jn as effectiveContextWindow, lt as composeExtensionPreset, mt as partitionExtensionCatalog, on as resolveExtensionKeybindings, ot as discoverAndBuildExtensionRegistry, rt as removeInstalledExtension, t as createAgent, tn as matchesBinding, ut as disposeExtensionRegistry, v as cleanupPersistedSession, vn as scanExtensionDirectories, w as resolveMcpWarningsDir, x as rehomePersistedStubs, yn as createDebugLog } from "./agent-CcTWL-Z6.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
|
-
import { u as errorMessage } from "./errors-
|
|
8
|
-
import { E as buildCatalog, R as projectUserPaths, c as SkillInstallError, l as installSkillFromSpec, z as findGitRoot } from "./skills-
|
|
9
|
-
import { n as defaultPluginDataRoot, r as loadAgentPlugin } from "./plugins-
|
|
7
|
+
import { u as errorMessage } from "./errors-DKiMrOKq.js";
|
|
8
|
+
import { E as buildCatalog, R as projectUserPaths, c as SkillInstallError, l as installSkillFromSpec, z as findGitRoot } from "./skills-BMGH_KK1.js";
|
|
9
|
+
import { n as defaultPluginDataRoot, r as loadAgentPlugin } from "./plugins-onDvTHEm.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-
|
|
12
|
+
import { _ as hasAuthorizationHeader, h as McpOAuthProvider, n as connectMcpServers, s as nonOwningMcpView } from "./mcp-CVtSQTWg.js";
|
|
13
13
|
import { replaceDynamicSection, splitSystemPrompt } from "./prompt.js";
|
|
14
|
-
import { a as getExtensionSessionState, c as setExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, t as createSession } from "./session-
|
|
14
|
+
import { a as getExtensionSessionState, c as setExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, t as createSession } from "./session-CyRLSsVa.js";
|
|
15
15
|
import { n as flattenTurns, r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
|
|
16
|
-
import {
|
|
16
|
+
import { s as withTimeout } from "./timeout-CjOG2mea.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 {
|
|
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-CB23LyI1.js";
|
|
21
|
+
import { E as renderDefaultCallbackPage, T as restoreModelOptions, b as modelSupportsReasoning, c as setPreferredCredential, g as getContextWindow, h as enabledModelOptions, l as setProviderCredential, n as applyApiKeyEnv, t as detectAuth, w as piIdOf, y as modelOptionsFor } from "./auth-BAclQnl3.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-7_Ff7INF.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
|
|
26
|
+
import { $ as McpAuthProvider, $t as resolveStoragePaths, At as SETTINGS_INPUTS, B as mcpToolsCachePath, C as addToSafelist, Cn as buildUpdateHint, Ct as buildHints, Dr as listSessionMeta, Dt as DEFAULT_SETTINGS, Er as lastContextSizeFromTurns, Et as useEnabledToggleSet, F as oauthUsesManualCodePaste, Fr as toolCallPreview, G as useMcpToolToggleMap, Hn as accentColor, I as runOAuthLogin, Ir as toolResultText, J as indexOfServerRow, Jn as TODO_STATUS_GLYPHS, Jt as useDiscovery, Kt as createDiscoverySlot, L as supportsOAuth, Lr as updateToolEventOutcomes, Lt as resolveChipColor, M as fetchOAuthRedirect, Mr as stripSpawnTokensLine, Mt as SettingsProvider, Nn as chatAutoCompactBehavior, Nr as sumRunCosts, Nt as clampFps, Or as loadState, Ot as SETTINGS_CATEGORIES, P as redirectUriFromAuthUrl, Pn as discoverAgentsMd, Pt as useSettings, Qt as resolveConfig, Rr as usageByModel, Rt as resolveTheme, Sn as bootTick, Sr as compactSummaryEvent, St as truncateTrailing, T as isOnSafelist, Tr as eventsFromTurns, Tt as listProjectFiles, U as subscribeMcpToolsCache, V as refreshMcpToolsCatalog, X as createFileMcpCredentialStore, Xt as ConfigProvider, Y as parentServerName, Yt as useDiscoveryOptional, Zt as useConfig, _r as buildPlanSystem, _t as useInteractionsActions, a as useSurfaces, an as restoreChainFallbacks, ar as useActiveTodos, b as useSafeModeActions, bn as tryOpenBrowser, br as createChatTurnsReadTool, bt as clipHintsToWidth, c as discoverSystemMd, cn as useCompletion, ct as PRESENT_PLAN_TOOL, d as userSystemMdPath, dn as importFromClaudeCode, dt as createInteractionTools, en as buildRunModel, et as useMcpAuthDispatch, f as useStreamBuffer, fn as mcpsWritePath, gn as dedupMcpServersByName, gr as buildBuildSystem, h as discoverProjectSkills, hn as buildMcpServers, ht as pendingInteractionsFromTurns, i as useSelectStyle, in as providerKeySetChanged, ir as todoProgressLabel, it as ASK_USER_TOOL, j as formatPathForCwd, jr as serverToolResultSummary, jt as SETTINGS_TOGGLES, k as suggestSafelistEntries, kr as marginTopFor, kt as SETTINGS_CHOICES, l as renderSystemMdSeed, ln as countClaudeSessionFiles, lt as buildResumedToolResultsTurn, m as defaultSkillScanPaths, mn as upsertMcpServer, mt as makeRequestInteraction, n as ThemeProvider, nn as chainsEqual, nt as getMcpAuthStatus, ot as HOST_INTERACTION_ID_PREFIX, p as buildSkillsConfig, pn as removeMcpServer, pt as isModelInteraction, q as buildVisibleMcpRows, qt as DiscoveryProvider, r as useColors, rn as filterChainFallbacks, rr as setTodosForRun, s as useTheme, sn as toggleChainEntry, st as InteractionsProvider, t as computeTurnAnchors, tn as chainPositionOf, tr as pruneTodosByRun, tt as useMcpAuthState, u as userScopeOverridesLoad, un as hasClaudeCodeSetup, ut as createAutomaticInteractionResponse, v as writeSessionExport, vn as discoverProjectMcps, vr as envSection, vt as useInteractionsQueue, w as getSafelist, wn as useUpdateCheck, wr as deriveSessionTitle, x as useSafeModeQueue, xt as hintsLength, y as SafeModeProvider, yr as chatReferenceTokensForPrompt, yt as EMPTY_HINTS, z as loadMcpToolsCache } from "./transcript-anchors-D-Yuimif.js";
|
|
27
27
|
import { basename, delimiter, dirname, join, relative, resolve } from "node:path";
|
|
28
28
|
import { Buffer } from "node:buffer";
|
|
29
29
|
import * as fs from "node:fs";
|
|
@@ -6079,8 +6079,14 @@ function parseEditorCommand(spec) {
|
|
|
6079
6079
|
* `<`, `>` and friends carry none of the obvious "needs quoting" signals, so a
|
|
6080
6080
|
* repo cloned into `C:\work\a&b` would otherwise hand `C:\work\a&b\AGENTS.md`
|
|
6081
6081
|
* over bare, and cmd would split it at the `&` and run the tail as a second
|
|
6082
|
-
* command.
|
|
6083
|
-
*
|
|
6082
|
+
* command.
|
|
6083
|
+
*
|
|
6084
|
+
* `%` and `!` are in the class so such a token is quoted rather than passed
|
|
6085
|
+
* bare — but be clear about the limit: quoting does NOT stop cmd.exe expanding
|
|
6086
|
+
* `%VAR%`, nor `!VAR!` where delayed expansion is on. Double quotes suppress
|
|
6087
|
+
* splitting and other syntax, not expansion. That is a property of the shell
|
|
6088
|
+
* which no amount of quoting can close; the worst case is a wrong path, not an
|
|
6089
|
+
* injected command.
|
|
6084
6090
|
*/
|
|
6085
6091
|
function quoteForShell(token) {
|
|
6086
6092
|
if (token === "") return "\"\"";
|
|
@@ -7500,36 +7506,31 @@ function runEditorOnText(initialText, options = {}) {
|
|
|
7500
7506
|
//#endregion
|
|
7501
7507
|
//#region src/tui/clipboard-image.ts
|
|
7502
7508
|
/**
|
|
7503
|
-
* Read
|
|
7504
|
-
*
|
|
7505
|
-
* The companion to the write-only {@link import('./clipboard')} module. Pasting
|
|
7506
|
-
* a clipboard image into a terminal is fundamentally different from pasting
|
|
7507
|
-
* text: terminals deliver text via bracketed paste (`PasteEvent`), but they do
|
|
7508
|
-
* NOT transmit binary clipboard image data that way — on macOS a `cmd+V` of a
|
|
7509
|
-
* screenshot usually produces no paste event at all. So to support "paste the
|
|
7510
|
-
* image I just copied" we have to read the OS clipboard directly via a
|
|
7511
|
-
* platform helper, on an explicit trigger.
|
|
7509
|
+
* Read image or text content directly from the OS clipboard.
|
|
7512
7510
|
*
|
|
7513
|
-
*
|
|
7514
|
-
*
|
|
7515
|
-
*
|
|
7516
|
-
*
|
|
7517
|
-
* - `helper-missing` → the platform helper isn't installed (`detail` carries
|
|
7518
|
-
* an install hint).
|
|
7519
|
-
* - `unsupported-env` → remote session (SSH): the helper would read the
|
|
7520
|
-
* REMOTE box's clipboard, not the user's.
|
|
7521
|
-
* - `failed` → helper ran but produced no usable image.
|
|
7511
|
+
* Images are never carried by terminal paste. Text normally is, but some
|
|
7512
|
+
* integrated-terminal PTYs freeze while forwarding large bracketed pastes. The
|
|
7513
|
+
* explicit app-owned shortcut therefore reads either representation through a
|
|
7514
|
+
* platform helper, keeping the clipboard payload out of the terminal stream.
|
|
7522
7515
|
*
|
|
7523
|
-
*
|
|
7524
|
-
*
|
|
7525
|
-
*
|
|
7516
|
+
* Every outcome is classified rather than thrown so the composer can surface a
|
|
7517
|
+
* precise notice. Remote sessions are rejected because native helpers would
|
|
7518
|
+
* read the remote machine's clipboard, not the user's local clipboard.
|
|
7519
|
+
*/
|
|
7520
|
+
/**
|
|
7521
|
+
* Cap on clipboard TEXT (raw bytes), matching the dragged-file attachment
|
|
7522
|
+
* ceiling in the composer. Native reads deliberately bypass the terminal's
|
|
7523
|
+
* paste backpressure, which used to be the de-facto limit — without a cap a
|
|
7524
|
+
* multi-gigabyte clipboard would be buffered here, folded into `paste.txt`,
|
|
7525
|
+
* persisted in the session, and shipped to the provider.
|
|
7526
7526
|
*/
|
|
7527
|
+
const MAX_CLIPBOARD_TEXT_BYTES = 20 * 1024 * 1024;
|
|
7527
7528
|
/** True in a remote session, where a local clipboard helper would read the wrong box. */
|
|
7528
7529
|
function isRemoteSession(env = process.env) {
|
|
7529
7530
|
return !!(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
|
|
7530
7531
|
}
|
|
7531
7532
|
/** Spawn a helper and capture its (binary) stdout. Never throws. */
|
|
7532
|
-
function captureViaSpawn(cmd, args, timeoutMs = 5e3) {
|
|
7533
|
+
function captureViaSpawn(cmd, args, timeoutMs = 5e3, maxBytes = Infinity) {
|
|
7533
7534
|
return new Promise((resolveP) => {
|
|
7534
7535
|
let child;
|
|
7535
7536
|
try {
|
|
@@ -7549,8 +7550,19 @@ function captureViaSpawn(cmd, args, timeoutMs = 5e3) {
|
|
|
7549
7550
|
}
|
|
7550
7551
|
const out = [];
|
|
7551
7552
|
const err = [];
|
|
7553
|
+
let size = 0;
|
|
7554
|
+
let truncated = false;
|
|
7552
7555
|
const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
|
|
7553
|
-
child.stdout?.on("data", (chunk) =>
|
|
7556
|
+
child.stdout?.on("data", (chunk) => {
|
|
7557
|
+
if (truncated) return;
|
|
7558
|
+
size += chunk.length;
|
|
7559
|
+
if (size > maxBytes) {
|
|
7560
|
+
truncated = true;
|
|
7561
|
+
child.kill("SIGKILL");
|
|
7562
|
+
return;
|
|
7563
|
+
}
|
|
7564
|
+
out.push(chunk);
|
|
7565
|
+
});
|
|
7554
7566
|
child.stderr?.setEncoding("utf8");
|
|
7555
7567
|
child.stderr?.on("data", (chunk) => err.push(chunk));
|
|
7556
7568
|
child.on("error", () => {
|
|
@@ -7568,7 +7580,8 @@ function captureViaSpawn(cmd, args, timeoutMs = 5e3) {
|
|
|
7568
7580
|
code,
|
|
7569
7581
|
stdout: Buffer.concat(out),
|
|
7570
7582
|
stderr: err.join(""),
|
|
7571
|
-
spawnError: false
|
|
7583
|
+
spawnError: false,
|
|
7584
|
+
truncated
|
|
7572
7585
|
});
|
|
7573
7586
|
});
|
|
7574
7587
|
});
|
|
@@ -7715,6 +7728,143 @@ async function readClipboardImage(hooks = {}) {
|
|
|
7715
7728
|
detail: "no clipboard helper available in this environment"
|
|
7716
7729
|
};
|
|
7717
7730
|
}
|
|
7731
|
+
async function readClipboardTextWith(capture, platform, env) {
|
|
7732
|
+
let command;
|
|
7733
|
+
let args;
|
|
7734
|
+
let installHint;
|
|
7735
|
+
/**
|
|
7736
|
+
* Second text target to try when the first isn't on the clipboard.
|
|
7737
|
+
* `xclip` has no "any text" alias, so a source offering only the legacy
|
|
7738
|
+
* `STRING` target fails the `UTF8_STRING` read; retrying keeps such
|
|
7739
|
+
* sources working without falling back to an untyped read, which would
|
|
7740
|
+
* hand back image bytes on an image-only clipboard.
|
|
7741
|
+
*/
|
|
7742
|
+
let fallbackArgs;
|
|
7743
|
+
/**
|
|
7744
|
+
* Whether the helper was asked for a SPECIFIC text target. Such a helper
|
|
7745
|
+
* exits non-zero when the clipboard doesn't offer that type, which means
|
|
7746
|
+
* "no text here", not "the helper broke" — so the exit classifies as
|
|
7747
|
+
* `no-text` instead of surfacing its stderr as an inline notice.
|
|
7748
|
+
* `pbpaste` / PowerShell are untyped: they exit 0 with empty output on an
|
|
7749
|
+
* empty clipboard and reach the same verdict via the length check below.
|
|
7750
|
+
*/
|
|
7751
|
+
let textTargetsExhausted = false;
|
|
7752
|
+
if (platform === "darwin") {
|
|
7753
|
+
command = "pbpaste";
|
|
7754
|
+
args = [];
|
|
7755
|
+
installHint = "pbpaste is required to paste text";
|
|
7756
|
+
} else if (platform === "win32") {
|
|
7757
|
+
command = "powershell";
|
|
7758
|
+
args = [
|
|
7759
|
+
"-NoProfile",
|
|
7760
|
+
"-NonInteractive",
|
|
7761
|
+
"-Command",
|
|
7762
|
+
"Add-Type -AssemblyName System.Windows.Forms; [Console]::OutputEncoding=[System.Text.UTF8Encoding]::new($false); [Console]::Out.Write([System.Windows.Forms.Clipboard]::GetText())"
|
|
7763
|
+
];
|
|
7764
|
+
installHint = "PowerShell is required to paste text";
|
|
7765
|
+
} else if (env.WAYLAND_DISPLAY) {
|
|
7766
|
+
command = "wl-paste";
|
|
7767
|
+
args = [
|
|
7768
|
+
"--no-newline",
|
|
7769
|
+
"--type",
|
|
7770
|
+
"text"
|
|
7771
|
+
];
|
|
7772
|
+
textTargetsExhausted = true;
|
|
7773
|
+
installHint = "install wl-clipboard to paste text";
|
|
7774
|
+
} else if (env.DISPLAY) {
|
|
7775
|
+
command = "xclip";
|
|
7776
|
+
args = [
|
|
7777
|
+
"-selection",
|
|
7778
|
+
"clipboard",
|
|
7779
|
+
"-t",
|
|
7780
|
+
"UTF8_STRING",
|
|
7781
|
+
"-o"
|
|
7782
|
+
];
|
|
7783
|
+
fallbackArgs = [
|
|
7784
|
+
"-selection",
|
|
7785
|
+
"clipboard",
|
|
7786
|
+
"-t",
|
|
7787
|
+
"STRING",
|
|
7788
|
+
"-o"
|
|
7789
|
+
];
|
|
7790
|
+
textTargetsExhausted = true;
|
|
7791
|
+
installHint = "install xclip to paste text";
|
|
7792
|
+
} else return {
|
|
7793
|
+
ok: false,
|
|
7794
|
+
reason: "helper-missing",
|
|
7795
|
+
detail: "no clipboard helper available in this environment"
|
|
7796
|
+
};
|
|
7797
|
+
let result = await capture(command, args, void 0, MAX_CLIPBOARD_TEXT_BYTES);
|
|
7798
|
+
if (result.spawnError) return {
|
|
7799
|
+
ok: false,
|
|
7800
|
+
reason: "helper-missing",
|
|
7801
|
+
detail: installHint
|
|
7802
|
+
};
|
|
7803
|
+
if (result.code !== 0 && !result.truncated && fallbackArgs) result = await capture(command, fallbackArgs, void 0, MAX_CLIPBOARD_TEXT_BYTES);
|
|
7804
|
+
if (result.truncated || result.stdout.length > 20971520) return {
|
|
7805
|
+
ok: false,
|
|
7806
|
+
reason: "too-large",
|
|
7807
|
+
detail: `clipboard text exceeds ${Math.round(MAX_CLIPBOARD_TEXT_BYTES / (1024 * 1024))} MB — paste it from a file instead`
|
|
7808
|
+
};
|
|
7809
|
+
if (result.code !== 0 && textTargetsExhausted) return {
|
|
7810
|
+
ok: false,
|
|
7811
|
+
reason: "no-text"
|
|
7812
|
+
};
|
|
7813
|
+
if (result.code !== 0) return {
|
|
7814
|
+
ok: false,
|
|
7815
|
+
reason: "failed",
|
|
7816
|
+
detail: result.stderr.trim() || "clipboard helper failed"
|
|
7817
|
+
};
|
|
7818
|
+
if (result.stdout.length === 0) return {
|
|
7819
|
+
ok: false,
|
|
7820
|
+
reason: "no-text"
|
|
7821
|
+
};
|
|
7822
|
+
return {
|
|
7823
|
+
ok: true,
|
|
7824
|
+
text: result.stdout.toString("utf8")
|
|
7825
|
+
};
|
|
7826
|
+
}
|
|
7827
|
+
/** Read text directly from the OS clipboard without sending it through the terminal PTY. */
|
|
7828
|
+
async function readClipboardText(hooks = {}) {
|
|
7829
|
+
const platform = hooks.platform ?? process.platform;
|
|
7830
|
+
const env = hooks.env ?? process.env;
|
|
7831
|
+
if (isRemoteSession(env)) return {
|
|
7832
|
+
ok: false,
|
|
7833
|
+
reason: "unsupported-env",
|
|
7834
|
+
detail: "clipboard paste is unavailable over SSH — paste the text through your local terminal instead"
|
|
7835
|
+
};
|
|
7836
|
+
return readClipboardTextWith(hooks.capture ?? captureViaSpawn, platform, env);
|
|
7837
|
+
}
|
|
7838
|
+
/** Prefer clipboard images, then fall back to text read outside the terminal PTY. */
|
|
7839
|
+
async function readClipboardContent(hooks = {}) {
|
|
7840
|
+
const image = await readClipboardImage(hooks);
|
|
7841
|
+
if (image.ok) return {
|
|
7842
|
+
ok: true,
|
|
7843
|
+
kind: "image",
|
|
7844
|
+
data: image.data,
|
|
7845
|
+
mediaType: image.mediaType
|
|
7846
|
+
};
|
|
7847
|
+
const text = await readClipboardText(hooks);
|
|
7848
|
+
if (text.ok) return {
|
|
7849
|
+
ok: true,
|
|
7850
|
+
kind: "text",
|
|
7851
|
+
text: text.text
|
|
7852
|
+
};
|
|
7853
|
+
if (text.reason === "no-text" && image.reason === "no-image") return {
|
|
7854
|
+
ok: false,
|
|
7855
|
+
reason: "empty"
|
|
7856
|
+
};
|
|
7857
|
+
if (text.reason === "no-text") return {
|
|
7858
|
+
ok: false,
|
|
7859
|
+
reason: image.reason === "no-image" ? "empty" : image.reason,
|
|
7860
|
+
...image.detail ? { detail: image.detail } : {}
|
|
7861
|
+
};
|
|
7862
|
+
return {
|
|
7863
|
+
ok: false,
|
|
7864
|
+
reason: text.reason,
|
|
7865
|
+
...text.detail ? { detail: text.detail } : {}
|
|
7866
|
+
};
|
|
7867
|
+
}
|
|
7718
7868
|
//#endregion
|
|
7719
7869
|
//#region src/tui/completion-popup.tsx
|
|
7720
7870
|
/**
|
|
@@ -9258,23 +9408,17 @@ function QuestionInteractionBlock({ request, onResolve }) {
|
|
|
9258
9408
|
const { width: termCols, height: termRows } = useTerminalDimensions();
|
|
9259
9409
|
const maxRows = wizardMaxRows(termRows);
|
|
9260
9410
|
const contentCols = Math.max(1, termCols - 4);
|
|
9261
|
-
const
|
|
9411
|
+
const contentRows = useMemo(() => {
|
|
9412
|
+
const introHeight = introRows(intro, contentCols);
|
|
9413
|
+
const heightWithFocus = (focus) => introHeight + questions.reduce((sum, q, i) => sum + estimateQuestionRowHeight(q, i < focus ? "done" : i === focus ? "active" : "pending", contentCols), 0);
|
|
9414
|
+
const tallest = questions.length === 0 ? introHeight : questions.reduce((max, _, focus) => Math.max(max, heightWithFocus(focus)), 0);
|
|
9415
|
+
return Math.min(tallest, maxRows);
|
|
9416
|
+
}, [
|
|
9262
9417
|
intro,
|
|
9263
9418
|
questions,
|
|
9264
|
-
|
|
9265
|
-
|
|
9419
|
+
contentCols,
|
|
9420
|
+
maxRows
|
|
9266
9421
|
]);
|
|
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);
|
|
9278
9422
|
return /* @__PURE__ */ jsxs(InteractionShell, {
|
|
9279
9423
|
title: questions.length > 1 ? ` answer ${activeIndex + 1}/${questions.length} · esc to abort run ` : " answer · esc to abort run ",
|
|
9280
9424
|
maxHeight: maxRows,
|
|
@@ -10328,72 +10472,6 @@ function McpAuthorizingPanel({ serverName, authUrl, maxLineWidth, chromeWidth, i
|
|
|
10328
10472
|
});
|
|
10329
10473
|
}
|
|
10330
10474
|
//#endregion
|
|
10331
|
-
//#region src/tui/todo-indicator.tsx
|
|
10332
|
-
/**
|
|
10333
|
-
* Layout budget when truncating the content. The bar never wraps —
|
|
10334
|
-
* single-line by contract — so we subtract every column consumed by
|
|
10335
|
-
* surrounding chrome before measuring how much room is left for the
|
|
10336
|
-
* todo's `content` text.
|
|
10337
|
-
*
|
|
10338
|
-
* - 2 cols ⇒ `<ChatScreen>`'s `border: true` (1 cell each side).
|
|
10339
|
-
* - 2 cols ⇒ this indicator's own `paddingLeft: 1 + paddingRight: 1`
|
|
10340
|
-
* (mirrors the queue block's outer padding so the bar
|
|
10341
|
-
* and queue read as aligned siblings).
|
|
10342
|
-
* - 4 cols ⇒ the indicator's own visible chrome: glyph "◐" (1) +
|
|
10343
|
-
* two spaces (2) + 1 col of trailing breathing room.
|
|
10344
|
-
*
|
|
10345
|
-
* That's 8 cols total of non-content overhead. Below `MIN_VISIBLE_TAIL`
|
|
10346
|
-
* the bar bails — a one-or-two-char tail isn't worth painting.
|
|
10347
|
-
*/
|
|
10348
|
-
const SCREEN_BORDER_COLS = 2;
|
|
10349
|
-
const INDICATOR_PADDING_COLS = 2;
|
|
10350
|
-
const CHROME_COLS = 4;
|
|
10351
|
-
const MIN_VISIBLE_TAIL = 8;
|
|
10352
|
-
function truncateForWidth(text, max) {
|
|
10353
|
-
if (max <= 0) return "";
|
|
10354
|
-
if (text.length <= max) return text;
|
|
10355
|
-
if (max <= 1) return "…";
|
|
10356
|
-
return `${text.slice(0, max - 1)}…`;
|
|
10357
|
-
}
|
|
10358
|
-
function TodoIndicator({ session }) {
|
|
10359
|
-
const { settings } = useSettings();
|
|
10360
|
-
const COLOR = useColors();
|
|
10361
|
-
const { width: termWidth } = useTerminalDimensions();
|
|
10362
|
-
const state = useActiveTodos(session);
|
|
10363
|
-
if (!settings.showTodoIndicator) return null;
|
|
10364
|
-
const item = state.inProgress;
|
|
10365
|
-
if (!item) return null;
|
|
10366
|
-
const usable = Math.max(0, termWidth - SCREEN_BORDER_COLS - INDICATOR_PADDING_COLS - CHROME_COLS);
|
|
10367
|
-
if (usable < MIN_VISIBLE_TAIL) return null;
|
|
10368
|
-
const display = truncateForWidth(item.content.replace(/\s+/g, " ").trim(), usable);
|
|
10369
|
-
return /* @__PURE__ */ jsx$1("box", {
|
|
10370
|
-
style: {
|
|
10371
|
-
marginTop: 1,
|
|
10372
|
-
flexShrink: 0,
|
|
10373
|
-
flexDirection: "row",
|
|
10374
|
-
paddingLeft: 1,
|
|
10375
|
-
paddingRight: 1
|
|
10376
|
-
},
|
|
10377
|
-
children: /* @__PURE__ */ jsxs("text", {
|
|
10378
|
-
wrapMode: "none",
|
|
10379
|
-
children: [
|
|
10380
|
-
/* @__PURE__ */ jsx$1("span", {
|
|
10381
|
-
fg: COLOR.warn,
|
|
10382
|
-
children: TODO_STATUS_GLYPHS.in_progress
|
|
10383
|
-
}),
|
|
10384
|
-
/* @__PURE__ */ jsx$1("span", {
|
|
10385
|
-
fg: COLOR.mute,
|
|
10386
|
-
children: " "
|
|
10387
|
-
}),
|
|
10388
|
-
/* @__PURE__ */ jsx$1("span", {
|
|
10389
|
-
fg: COLOR.dim,
|
|
10390
|
-
children: display
|
|
10391
|
-
})
|
|
10392
|
-
]
|
|
10393
|
-
})
|
|
10394
|
-
});
|
|
10395
|
-
}
|
|
10396
|
-
//#endregion
|
|
10397
10475
|
//#region src/tui/screens.tsx
|
|
10398
10476
|
/** How long a clipboard-paste status line stays before self-clearing. */
|
|
10399
10477
|
const NOTICE_TTL_MS = 5e3;
|
|
@@ -10418,6 +10496,26 @@ const MAX_FILE_ATTACHMENT_BYTES = 20 * 1024 * 1024;
|
|
|
10418
10496
|
* every subsequent render of the prompt) crawl.
|
|
10419
10497
|
*/
|
|
10420
10498
|
const PASTE_INLINE_MAX_CHARS = 1e4;
|
|
10499
|
+
/**
|
|
10500
|
+
* Normalize terminal/native clipboard text and decide whether the composer
|
|
10501
|
+
* should insert it or fold it into a text attachment.
|
|
10502
|
+
*/
|
|
10503
|
+
function planTextPaste(text, attachmentsEnabled) {
|
|
10504
|
+
const normalized = stripAnsiSequences(text).replace(/\r\n?/g, "\n");
|
|
10505
|
+
const lines = normalized.trim().split("\n");
|
|
10506
|
+
if (attachmentsEnabled && (lines.length > 10 || normalized.length > PASTE_INLINE_MAX_CHARS)) return {
|
|
10507
|
+
kind: "attachment",
|
|
10508
|
+
attachment: {
|
|
10509
|
+
name: "paste.txt",
|
|
10510
|
+
content: Buffer.from(normalized, "utf8"),
|
|
10511
|
+
mediaType: "text/plain"
|
|
10512
|
+
}
|
|
10513
|
+
};
|
|
10514
|
+
return {
|
|
10515
|
+
kind: "inline",
|
|
10516
|
+
text: normalized
|
|
10517
|
+
};
|
|
10518
|
+
}
|
|
10421
10519
|
/** `image/png` → `png` etc., for naming the synthesized clipboard attachment. */
|
|
10422
10520
|
function extForImageMediaType(mediaType) {
|
|
10423
10521
|
const sub = mediaType.split("/")[1] ?? "png";
|
|
@@ -11091,6 +11189,7 @@ function OAuthRunningStep({ descriptor, dataDir, onSuccess, onError }) {
|
|
|
11091
11189
|
kind: "oauth",
|
|
11092
11190
|
...creds
|
|
11093
11191
|
});
|
|
11192
|
+
if (descriptor.envKey) delete process.env[descriptor.envKey];
|
|
11094
11193
|
onSuccess();
|
|
11095
11194
|
} catch (err) {
|
|
11096
11195
|
if (cancelled) return;
|
|
@@ -11738,7 +11837,7 @@ const EMPTY_QUEUED_MESSAGES = [];
|
|
|
11738
11837
|
function shouldRenderTranscriptSearch(searchMode, query) {
|
|
11739
11838
|
return searchMode && query.trim().length > 0;
|
|
11740
11839
|
}
|
|
11741
|
-
function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, inlineAutocompleteProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, selectedTurnId, onSelectTurn, onOpenTurn, transcriptSearchMode = false, transcriptSearchQuery = "", transcriptSearchEvents = [], transcriptSearchSearching = false, onTranscriptSearchModeChange, onTranscriptSearchQueryChange, onTranscriptSearchNavigate, onTranscriptSearchOpen, onLoadEarlierTranscript, promptTriggerHints, attachImageBinding, editorBinding, sessionDetailsBinding
|
|
11840
|
+
function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, inlineAutocompleteProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, selectedTurnId, onSelectTurn, onOpenTurn, transcriptSearchMode = false, transcriptSearchQuery = "", transcriptSearchEvents = [], transcriptSearchSearching = false, onTranscriptSearchModeChange, onTranscriptSearchQueryChange, onTranscriptSearchNavigate, onTranscriptSearchOpen, onLoadEarlierTranscript, promptTriggerHints, attachImageBinding, editorBinding, sessionDetailsBinding }) {
|
|
11742
11841
|
const COLOR = useColors();
|
|
11743
11842
|
const SURFACE = useSurfaces();
|
|
11744
11843
|
const titleText = session?.title ?? "New session";
|
|
@@ -11806,6 +11905,7 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
|
|
|
11806
11905
|
}, [onPopupOpenChange]);
|
|
11807
11906
|
const fileEditPending = pending && isFileEditTool(pending.tool) ? pending : null;
|
|
11808
11907
|
const planPending = !pending && pendingInteraction?.kind === "plan" ? pendingInteraction : null;
|
|
11908
|
+
const approvalPending = pending && !fileEditPending ? pending : null;
|
|
11809
11909
|
const modal = useModal();
|
|
11810
11910
|
useEffect(() => {
|
|
11811
11911
|
if (!fileEditPending) return;
|
|
@@ -11861,47 +11961,58 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
|
|
|
11861
11961
|
request: fileEditPending,
|
|
11862
11962
|
onDecide: onApproval
|
|
11863
11963
|
}, fileEditPending.id),
|
|
11864
|
-
/* @__PURE__ */
|
|
11964
|
+
/* @__PURE__ */ jsxs("box", {
|
|
11865
11965
|
style: {
|
|
11866
11966
|
flexDirection: "column",
|
|
11867
11967
|
flexShrink: 0,
|
|
11868
11968
|
backgroundColor: SURFACE.background
|
|
11869
11969
|
},
|
|
11870
|
-
children:
|
|
11871
|
-
|
|
11872
|
-
|
|
11873
|
-
|
|
11874
|
-
request: pendingInteraction,
|
|
11875
|
-
onResolve: onInteraction
|
|
11876
|
-
}, pendingInteraction.id) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11877
|
-
queuedMessages.length > 0 && !completionPopupOpen && /* @__PURE__ */ jsx$1(QueuedMessagesBlock, {
|
|
11878
|
-
messages: queuedMessages,
|
|
11879
|
-
selectionIndex: queueSelectionIndex,
|
|
11880
|
-
shortcuts: queueShortcuts
|
|
11970
|
+
children: [
|
|
11971
|
+
approvalPending && /* @__PURE__ */ jsx$1(ApprovalBlock, {
|
|
11972
|
+
request: approvalPending,
|
|
11973
|
+
onPick: onApproval
|
|
11881
11974
|
}),
|
|
11882
|
-
/* @__PURE__ */ jsx$1(
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
|
|
11895
|
-
|
|
11896
|
-
|
|
11897
|
-
|
|
11898
|
-
|
|
11899
|
-
|
|
11900
|
-
|
|
11901
|
-
|
|
11902
|
-
|
|
11975
|
+
pendingInteraction && !approvalPending && /* @__PURE__ */ jsx$1(InteractionBlock, {
|
|
11976
|
+
request: pendingInteraction,
|
|
11977
|
+
onResolve: onInteraction
|
|
11978
|
+
}, pendingInteraction.id),
|
|
11979
|
+
/* @__PURE__ */ jsxs("box", {
|
|
11980
|
+
style: {
|
|
11981
|
+
flexDirection: "column",
|
|
11982
|
+
flexShrink: 0,
|
|
11983
|
+
visible: approvalPending == null && pendingInteraction == null
|
|
11984
|
+
},
|
|
11985
|
+
children: [
|
|
11986
|
+
queuedMessages.length > 0 && !completionPopupOpen && /* @__PURE__ */ jsx$1(QueuedMessagesBlock, {
|
|
11987
|
+
messages: queuedMessages,
|
|
11988
|
+
selectionIndex: queueSelectionIndex,
|
|
11989
|
+
shortcuts: queueShortcuts
|
|
11990
|
+
}),
|
|
11991
|
+
/* @__PURE__ */ jsx$1(ExtensionBanners, {}),
|
|
11992
|
+
/* @__PURE__ */ jsx$1(PromptBlock, {
|
|
11993
|
+
userPrompts,
|
|
11994
|
+
onSubmit,
|
|
11995
|
+
completionProviders,
|
|
11996
|
+
inlineAutocompleteProviders,
|
|
11997
|
+
onCompletionTab,
|
|
11998
|
+
enterSelectsCompletion,
|
|
11999
|
+
onPopupOpenChange: handlePopupOpenChange,
|
|
12000
|
+
searchMode: transcriptSearchMode,
|
|
12001
|
+
onSearchModeChange: onTranscriptSearchModeChange,
|
|
12002
|
+
onSearchQueryChange: onTranscriptSearchQueryChange,
|
|
12003
|
+
onSearchNavigate: onTranscriptSearchNavigate,
|
|
12004
|
+
onSearchOpen: onTranscriptSearchOpen,
|
|
12005
|
+
selectMode: queueSelectionIndex != null ? "queue" : null,
|
|
12006
|
+
triggerHints: promptTriggerHints,
|
|
12007
|
+
busy,
|
|
12008
|
+
onEnterQueueFromEmpty: onEnterQueueFromEmptyPrompt,
|
|
12009
|
+
attachImageBinding,
|
|
12010
|
+
editorBinding,
|
|
12011
|
+
suspended: approvalPending != null || pendingInteraction != null
|
|
12012
|
+
})
|
|
12013
|
+
]
|
|
11903
12014
|
})
|
|
11904
|
-
]
|
|
12015
|
+
]
|
|
11905
12016
|
}),
|
|
11906
12017
|
!planPending && !fileEditPending && /* @__PURE__ */ jsx$1(TitleOverlay, {
|
|
11907
12018
|
title: titleText,
|
|
@@ -12071,9 +12182,23 @@ function queueRowId(index) {
|
|
|
12071
12182
|
* (where committed messages live) and the prompt (where the user is
|
|
12072
12183
|
* typing the next one). Dropped entirely when the queue is empty.
|
|
12073
12184
|
*/
|
|
12185
|
+
/**
|
|
12186
|
+
* Badge text for a queued message's attachment count, or `null` when there is
|
|
12187
|
+
* nothing to show.
|
|
12188
|
+
*
|
|
12189
|
+
* Split out from the row so the logic — pluralization, and the distinction
|
|
12190
|
+
* between "no attachments" and "this preview predates the field" — is testable
|
|
12191
|
+
* without standing up `ChatScreen`'s provider stack. Padded with spaces because
|
|
12192
|
+
* it renders as a filled chip and needs breathing room inside its background.
|
|
12193
|
+
*/
|
|
12194
|
+
function attachmentBadgeLabel(count) {
|
|
12195
|
+
if (count === void 0 || count <= 0) return null;
|
|
12196
|
+
return ` +${count} ${count === 1 ? "attachment" : "attachments"} `;
|
|
12197
|
+
}
|
|
12074
12198
|
function QueuedMessagesBlock({ messages, selectionIndex, shortcuts }) {
|
|
12075
12199
|
const COLOR = useColors();
|
|
12076
12200
|
const SURFACE = useSurfaces();
|
|
12201
|
+
const attachmentChip = resolveChipColor(SURFACE.chips, "file");
|
|
12077
12202
|
const scrollboxRef = useRef(null);
|
|
12078
12203
|
const enterHintKey = shortcuts?.enter ? formatBindingForDisplay(shortcuts.enter) : "↑";
|
|
12079
12204
|
useEffect(() => {
|
|
@@ -12136,6 +12261,11 @@ function QueuedMessagesBlock({ messages, selectionIndex, shortcuts }) {
|
|
|
12136
12261
|
fg: COLOR.mute,
|
|
12137
12262
|
children: "❯ "
|
|
12138
12263
|
}),
|
|
12264
|
+
attachmentBadgeLabel(msg.attachments) !== null && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
|
|
12265
|
+
fg: attachmentChip.fg,
|
|
12266
|
+
bg: attachmentChip.bg,
|
|
12267
|
+
children: attachmentBadgeLabel(msg.attachments)
|
|
12268
|
+
}), /* @__PURE__ */ jsx$1("span", { children: " " })] }),
|
|
12139
12269
|
/* @__PURE__ */ jsx$1("span", {
|
|
12140
12270
|
fg: focused ? COLOR.brand : COLOR.dim,
|
|
12141
12271
|
children: previewText(msg.text)
|
|
@@ -12225,7 +12355,7 @@ const INLINE_GHOST_DEBOUNCE_MS = 120;
|
|
|
12225
12355
|
function isTranscriptSearchExitKey(event, cursorOffset) {
|
|
12226
12356
|
return event.name === "escape" || (event.name === "backspace" || event.name === "delete") && !event.ctrl && !event.meta && !event.shift && cursorOffset === 0;
|
|
12227
12357
|
}
|
|
12228
|
-
function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocompleteProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, searchMode = false, onSearchModeChange, onSearchQueryChange, onSearchNavigate, onSearchOpen, selectMode = null, triggerHints, busy = false, onEnterQueueFromEmpty, attachImageBinding, editorBinding }) {
|
|
12358
|
+
function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocompleteProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, searchMode = false, onSearchModeChange, onSearchQueryChange, onSearchNavigate, onSearchOpen, selectMode = null, triggerHints, busy = false, onEnterQueueFromEmpty, attachImageBinding, editorBinding, clipboardReader = readClipboardContent, suspended = false }) {
|
|
12229
12359
|
const focused = useModalAwareFocus();
|
|
12230
12360
|
const renderer = useRenderer();
|
|
12231
12361
|
const COLOR = useColors();
|
|
@@ -12488,30 +12618,12 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12488
12618
|
syncBuffer();
|
|
12489
12619
|
return true;
|
|
12490
12620
|
}, [completion, syncBuffer]);
|
|
12491
|
-
/**
|
|
12492
|
-
|
|
12493
|
-
* forwards the decoded bytes to `insertText` in one call, which the
|
|
12494
|
-
* native edit buffer treats as raw characters — embedded `\n` lands
|
|
12495
|
-
* as a literal glyph rather than splitting the line, and bracketed
|
|
12496
|
-
* paste from macOS Terminal often substitutes CR for LF on top of
|
|
12497
|
-
* that. The result was a multi-line paste collapsing to one giant
|
|
12498
|
-
* row.
|
|
12499
|
-
*
|
|
12500
|
-
* We `preventDefault` to skip the textarea's default path, normalize
|
|
12501
|
-
* `\r\n` / `\r` to `\n`, then drive the edit buffer explicitly:
|
|
12502
|
-
* `insertText` for each segment, `newLine()` between segments. The
|
|
12503
|
-
* cursor is positioned at the end of the pasted block (the natural
|
|
12504
|
-
* place to keep typing).
|
|
12505
|
-
*/
|
|
12506
|
-
const onPaste = useCallback((event) => {
|
|
12507
|
-
event.preventDefault();
|
|
12621
|
+
/** Apply decoded clipboard text through the shared file/attachment/inline policy. */
|
|
12622
|
+
const applyTextPaste = useCallback((text) => {
|
|
12508
12623
|
const ta = textareaRef.current;
|
|
12509
12624
|
if (!ta) return;
|
|
12510
|
-
const normalized = stripAnsiSequences(
|
|
12511
|
-
if (normalized.length === 0)
|
|
12512
|
-
if (attachmentsEnabled) attachClipboardImage();
|
|
12513
|
-
return;
|
|
12514
|
-
}
|
|
12625
|
+
const normalized = stripAnsiSequences(text).replace(/\r\n?/g, "\n");
|
|
12626
|
+
if (normalized.length === 0) return;
|
|
12515
12627
|
if (attachmentsEnabled && !normalized.includes("\n")) {
|
|
12516
12628
|
const trimmed = normalized.trim().replace(/^["']|["']$/g, "");
|
|
12517
12629
|
let resolved = trimmed.startsWith("file://") ? decodeURIComponent(trimmed.slice(7)) : trimmed;
|
|
@@ -12535,15 +12647,12 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12535
12647
|
}
|
|
12536
12648
|
} catch {}
|
|
12537
12649
|
}
|
|
12538
|
-
const
|
|
12539
|
-
if (
|
|
12540
|
-
setAttachments((prev) => [...prev,
|
|
12541
|
-
name: "paste.txt",
|
|
12542
|
-
content: Buffer.from(normalized, "utf-8"),
|
|
12543
|
-
mediaType: "text/plain"
|
|
12544
|
-
}]);
|
|
12650
|
+
const pastePlan = planTextPaste(normalized, attachmentsEnabled);
|
|
12651
|
+
if (pastePlan.kind === "attachment") {
|
|
12652
|
+
setAttachments((prev) => [...prev, pastePlan.attachment]);
|
|
12545
12653
|
return;
|
|
12546
12654
|
}
|
|
12655
|
+
const lines = pastePlan.text.trim().split("\n");
|
|
12547
12656
|
for (let i = 0; i < lines.length; i++) {
|
|
12548
12657
|
if (i > 0) ta.newLine();
|
|
12549
12658
|
const part = lines[i];
|
|
@@ -12551,9 +12660,64 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12551
12660
|
}
|
|
12552
12661
|
syncBuffer();
|
|
12553
12662
|
}, [
|
|
12554
|
-
syncBuffer,
|
|
12555
12663
|
attachmentsEnabled,
|
|
12556
|
-
|
|
12664
|
+
showNotice,
|
|
12665
|
+
syncBuffer
|
|
12666
|
+
]);
|
|
12667
|
+
/**
|
|
12668
|
+
* Prevent the textarea's one-shot insert and explicitly preserve multiline
|
|
12669
|
+
* paste. Empty terminal paste remains the image-clipboard probe.
|
|
12670
|
+
*/
|
|
12671
|
+
const onPaste = useCallback((event) => {
|
|
12672
|
+
event.preventDefault();
|
|
12673
|
+
const raw = decodePasteBytes(event.bytes);
|
|
12674
|
+
if (stripAnsiSequences(raw).replace(/\r\n?/g, "\n").length === 0) {
|
|
12675
|
+
if (attachmentsEnabled) attachClipboardImage();
|
|
12676
|
+
return;
|
|
12677
|
+
}
|
|
12678
|
+
applyTextPaste(raw);
|
|
12679
|
+
}, [
|
|
12680
|
+
applyTextPaste,
|
|
12681
|
+
attachmentsEnabled,
|
|
12682
|
+
attachClipboardImage
|
|
12683
|
+
]);
|
|
12684
|
+
/**
|
|
12685
|
+
* Read the OS clipboard on the app-owned keybinding. Unlike terminal paste,
|
|
12686
|
+
* this moves only the shortcut bytes through the PTY, avoiding large-paste
|
|
12687
|
+
* backpressure bugs in integrated terminals. Text is re-injected through the
|
|
12688
|
+
* exact same paste handler as bracketed paste; images retain the existing
|
|
12689
|
+
* attachment behavior.
|
|
12690
|
+
*/
|
|
12691
|
+
const pasteClipboard = useCallback(async () => {
|
|
12692
|
+
const result = await clipboardReader();
|
|
12693
|
+
if (!result.ok) {
|
|
12694
|
+
showNotice(result.reason === "empty" ? "clipboard has no text or image" : result.detail ?? "could not read the clipboard");
|
|
12695
|
+
return;
|
|
12696
|
+
}
|
|
12697
|
+
if (result.kind === "text") {
|
|
12698
|
+
applyTextPaste(result.text);
|
|
12699
|
+
return;
|
|
12700
|
+
}
|
|
12701
|
+
if (!attachmentsEnabled) {
|
|
12702
|
+
showNotice("attachments are disabled — enable them in Settings → Agent");
|
|
12703
|
+
return;
|
|
12704
|
+
}
|
|
12705
|
+
if (result.data.length > MAX_IMAGE_BYTES) {
|
|
12706
|
+
showNotice(`clipboard image too large (${formatBytes(result.data.length)}) — max ${formatBytes(MAX_IMAGE_BYTES)}`);
|
|
12707
|
+
return;
|
|
12708
|
+
}
|
|
12709
|
+
setAttachments((prev) => {
|
|
12710
|
+
const n = prev.filter((a) => a.name.startsWith("clipboard-")).length + 1;
|
|
12711
|
+
return [...prev, {
|
|
12712
|
+
name: `clipboard-${n}.${extForImageMediaType(result.mediaType)}`,
|
|
12713
|
+
content: result.data,
|
|
12714
|
+
mediaType: result.mediaType
|
|
12715
|
+
}];
|
|
12716
|
+
});
|
|
12717
|
+
}, [
|
|
12718
|
+
applyTextPaste,
|
|
12719
|
+
attachmentsEnabled,
|
|
12720
|
+
clipboardReader,
|
|
12557
12721
|
showNotice
|
|
12558
12722
|
]);
|
|
12559
12723
|
/**
|
|
@@ -12620,7 +12784,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12620
12784
|
const onKeyDown = useCallback((event) => {
|
|
12621
12785
|
if (attachImageBinding && matchesBinding(event, attachImageBinding)) {
|
|
12622
12786
|
event.preventDefault();
|
|
12623
|
-
|
|
12787
|
+
pasteClipboard();
|
|
12624
12788
|
return;
|
|
12625
12789
|
}
|
|
12626
12790
|
if (editorBinding && matchesBinding(event, editorBinding)) {
|
|
@@ -12752,7 +12916,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12752
12916
|
enterSelectsCompletion,
|
|
12753
12917
|
syncBuffer,
|
|
12754
12918
|
attachImageBinding,
|
|
12755
|
-
|
|
12919
|
+
pasteClipboard,
|
|
12756
12920
|
editorBinding,
|
|
12757
12921
|
openExternalEditor,
|
|
12758
12922
|
attachments,
|
|
@@ -12848,7 +13012,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12848
13012
|
},
|
|
12849
13013
|
children: [/* @__PURE__ */ jsx$1("textarea", {
|
|
12850
13014
|
ref: textareaRef,
|
|
12851
|
-
focused: focused && !selectMode,
|
|
13015
|
+
focused: focused && !selectMode && !suspended,
|
|
12852
13016
|
keyBindings: TEXTAREA_BINDINGS,
|
|
12853
13017
|
wrapMode: "word",
|
|
12854
13018
|
placeholder: selectMode === "queue" ? "— queue-select mode — press ⎋ to resume typing —" : searchMode ? "Search this conversation…" : shellMode ? "Run a shell command…" : "Ask zidane…",
|
|
@@ -13692,6 +13856,78 @@ function statusColor(status, COLOR) {
|
|
|
13692
13856
|
}
|
|
13693
13857
|
}
|
|
13694
13858
|
//#endregion
|
|
13859
|
+
//#region src/chat/auth-rows.ts
|
|
13860
|
+
/**
|
|
13861
|
+
* Stored credentials, i.e. the ones a user can actually choose between.
|
|
13862
|
+
*
|
|
13863
|
+
* `env` is excluded: it is observed rather than stored, and is usually just the
|
|
13864
|
+
* mirror `applyApiKeyEnv` wrote from the stored API key, so counting it would
|
|
13865
|
+
* advertise a choice that does not exist.
|
|
13866
|
+
*/
|
|
13867
|
+
function selectableSources(provider) {
|
|
13868
|
+
return provider.methods.filter((m) => m.source !== "env");
|
|
13869
|
+
}
|
|
13870
|
+
/** A provider earns a chevron only when there is a real choice to reveal. */
|
|
13871
|
+
function isExpandableProvider(provider) {
|
|
13872
|
+
return selectableSources(provider).length > 1;
|
|
13873
|
+
}
|
|
13874
|
+
/**
|
|
13875
|
+
* Flatten the provider list plus the expanded set into the visible-row list.
|
|
13876
|
+
*
|
|
13877
|
+
* - `providers` — filtered / ordered provider list (post-search).
|
|
13878
|
+
* - `expanded` — keys of providers whose sources are open. Keys for
|
|
13879
|
+
* providers that are no longer expandable are ignored rather
|
|
13880
|
+
* than cleaned up, so stale expansion state (a credential
|
|
13881
|
+
* removed while the modal is open) can't desync the cursor.
|
|
13882
|
+
*/
|
|
13883
|
+
function buildVisibleAuthRows(providers, expanded) {
|
|
13884
|
+
const out = [];
|
|
13885
|
+
for (const provider of providers) {
|
|
13886
|
+
const expandable = isExpandableProvider(provider);
|
|
13887
|
+
out.push({
|
|
13888
|
+
kind: "provider",
|
|
13889
|
+
provider,
|
|
13890
|
+
expandable
|
|
13891
|
+
});
|
|
13892
|
+
if (!expandable || !expanded.has(provider.key)) continue;
|
|
13893
|
+
for (const method of selectableSources(provider)) out.push({
|
|
13894
|
+
kind: "source",
|
|
13895
|
+
providerKey: provider.key,
|
|
13896
|
+
method
|
|
13897
|
+
});
|
|
13898
|
+
if (provider.envKey) {
|
|
13899
|
+
const envMethod = provider.methods.find((m) => m.source === "env");
|
|
13900
|
+
out.push({
|
|
13901
|
+
kind: "source",
|
|
13902
|
+
providerKey: provider.key,
|
|
13903
|
+
method: envMethod ?? {
|
|
13904
|
+
source: "env",
|
|
13905
|
+
detail: provider.envKey,
|
|
13906
|
+
active: false
|
|
13907
|
+
}
|
|
13908
|
+
});
|
|
13909
|
+
}
|
|
13910
|
+
}
|
|
13911
|
+
return out;
|
|
13912
|
+
}
|
|
13913
|
+
/**
|
|
13914
|
+
* Provider key a row "belongs to" — its own for a provider header, the parent's
|
|
13915
|
+
* for a source row.
|
|
13916
|
+
*/
|
|
13917
|
+
function parentProviderKey(row) {
|
|
13918
|
+
return row.kind === "provider" ? row.provider.key : row.providerKey;
|
|
13919
|
+
}
|
|
13920
|
+
/**
|
|
13921
|
+
* Index in `rows` of the header for the given provider key, or `-1`.
|
|
13922
|
+
*
|
|
13923
|
+
* Collapse handlers snap the cursor up to the parent BEFORE the row list
|
|
13924
|
+
* shrinks; without that the cursor keeps its flat index and silently lands on
|
|
13925
|
+
* whichever sibling row slid into that slot.
|
|
13926
|
+
*/
|
|
13927
|
+
function indexOfProviderRow(rows, providerKey) {
|
|
13928
|
+
return rows.findIndex((r) => r.kind === "provider" && r.provider.key === providerKey);
|
|
13929
|
+
}
|
|
13930
|
+
//#endregion
|
|
13695
13931
|
//#region src/chat/contribution-origin.ts
|
|
13696
13932
|
/**
|
|
13697
13933
|
* Union the three skill sources into the list the Skills tab shows, in the
|
|
@@ -15132,6 +15368,17 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15132
15368
|
const [expandedMcps, setExpandedMcps] = useState(() => /* @__PURE__ */ new Set());
|
|
15133
15369
|
const [expandedExtensions, setExpandedExtensions] = useState(() => /* @__PURE__ */ new Set());
|
|
15134
15370
|
const [expandedInstructions, setExpandedInstructions] = useState(() => /* @__PURE__ */ new Set());
|
|
15371
|
+
const [expandedProviders, setExpandedProviders] = useState(() => /* @__PURE__ */ new Set());
|
|
15372
|
+
/**
|
|
15373
|
+
* Provider list after an in-modal credential swap.
|
|
15374
|
+
*
|
|
15375
|
+
* `authentication.providers` is a snapshot frozen when the modal opened, so
|
|
15376
|
+
* promoting a credential would leave the ● marker pointing at the old winner
|
|
15377
|
+
* until the user closed and re-opened — which reads as "nothing happened".
|
|
15378
|
+
* The host returns freshly-detected providers from the action and they land
|
|
15379
|
+
* here, so the swap is visible in place.
|
|
15380
|
+
*/
|
|
15381
|
+
const [providersAfterSwap, setProvidersAfterSwap] = useState(null);
|
|
15135
15382
|
const [instructionsNotice, setInstructionsNotice] = useState(null);
|
|
15136
15383
|
const [instructionSegments, setInstructionSegments] = useState([]);
|
|
15137
15384
|
const [instructionsLoading, setInstructionsLoading] = useState(false);
|
|
@@ -15496,7 +15743,11 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15496
15743
|
expandedMcps,
|
|
15497
15744
|
mcpToolsByServer
|
|
15498
15745
|
]);
|
|
15499
|
-
const filteredProviders = useMemo(() => (authentication?.providers ?? []).filter((p) => matchesTerms(providerCorpus(p), queryTerms)), [
|
|
15746
|
+
const filteredProviders = useMemo(() => (providersAfterSwap ?? authentication?.providers ?? []).filter((p) => matchesTerms(providerCorpus(p), queryTerms)), [
|
|
15747
|
+
providersAfterSwap,
|
|
15748
|
+
authentication?.providers,
|
|
15749
|
+
queryTerms
|
|
15750
|
+
]);
|
|
15500
15751
|
const instructionVisibleRows = useMemo(() => buildVisibleInstructionRows({
|
|
15501
15752
|
segments: instructionSegments,
|
|
15502
15753
|
expanded: expandedInstructions,
|
|
@@ -15508,7 +15759,8 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15508
15759
|
queryActive,
|
|
15509
15760
|
queryTerms
|
|
15510
15761
|
]);
|
|
15511
|
-
const
|
|
15762
|
+
const authVisibleRows = useMemo(() => buildVisibleAuthRows(filteredProviders, expandedProviders), [filteredProviders, expandedProviders]);
|
|
15763
|
+
const authRowCount = authVisibleRows.length;
|
|
15512
15764
|
const filteredSize = {
|
|
15513
15765
|
...Object.fromEntries(SETTINGS_CATEGORIES.map((c) => [c.id, filteredByCategory[c.id].length])),
|
|
15514
15766
|
skills: filteredSkills.length,
|
|
@@ -15622,6 +15874,27 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15622
15874
|
});
|
|
15623
15875
|
}, [instructionVisibleRows]);
|
|
15624
15876
|
const toggleExpandFocusedInstructionRow = useCallback(() => toggleExpandInstructionRowAt(cursor), [toggleExpandInstructionRowAt, cursor]);
|
|
15877
|
+
const toggleExpandProviderRowAt = useCallback((index) => {
|
|
15878
|
+
const row = authVisibleRows[index];
|
|
15879
|
+
if (!row) return;
|
|
15880
|
+
if (row.kind === "provider" && !row.expandable) return;
|
|
15881
|
+
const providerKey = parentProviderKey(row);
|
|
15882
|
+
setExpandedProviders((prev) => {
|
|
15883
|
+
const next = new Set(prev);
|
|
15884
|
+
if (next.has(providerKey)) {
|
|
15885
|
+
if (row.kind === "source") {
|
|
15886
|
+
const parentIndex = indexOfProviderRow(authVisibleRows, providerKey);
|
|
15887
|
+
if (parentIndex >= 0) setCursorByTab((c) => ({
|
|
15888
|
+
...c,
|
|
15889
|
+
authentication: parentIndex
|
|
15890
|
+
}));
|
|
15891
|
+
}
|
|
15892
|
+
next.delete(providerKey);
|
|
15893
|
+
} else next.add(providerKey);
|
|
15894
|
+
return next;
|
|
15895
|
+
});
|
|
15896
|
+
}, [authVisibleRows]);
|
|
15897
|
+
const toggleExpandFocusedProviderRow = useCallback(() => toggleExpandProviderRowAt(cursor), [toggleExpandProviderRowAt, cursor]);
|
|
15625
15898
|
const mcpRowCount = mcpVisibleRows.length;
|
|
15626
15899
|
const extensionRowCount = extensionVisibleRows.length;
|
|
15627
15900
|
const instructionRowCount = instructionVisibleRows.length;
|
|
@@ -15806,8 +16079,17 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15806
16079
|
return;
|
|
15807
16080
|
}
|
|
15808
16081
|
if (activeTab === "authentication") {
|
|
15809
|
-
const
|
|
15810
|
-
if (
|
|
16082
|
+
const row = authVisibleRows[index];
|
|
16083
|
+
if (!row) return;
|
|
16084
|
+
if (row.kind === "provider") {
|
|
16085
|
+
actions?.onConfigureProvider?.(row.provider);
|
|
16086
|
+
return;
|
|
16087
|
+
}
|
|
16088
|
+
if (row.method.source === "env" || row.method.active) return;
|
|
16089
|
+
const provider = filteredProviders.find((p) => p.key === row.providerKey);
|
|
16090
|
+
if (!provider) return;
|
|
16091
|
+
const refreshed = actions?.onSetPreferredCredential?.(provider, row.method.source);
|
|
16092
|
+
if (refreshed) setProvidersAfterSwap(refreshed);
|
|
15811
16093
|
}
|
|
15812
16094
|
}, [
|
|
15813
16095
|
activeTab,
|
|
@@ -15828,6 +16110,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15828
16110
|
mcpToolToggleMap,
|
|
15829
16111
|
actions,
|
|
15830
16112
|
filteredProviders,
|
|
16113
|
+
authVisibleRows,
|
|
15831
16114
|
instructionVisibleRows,
|
|
15832
16115
|
instructionsCanOverride,
|
|
15833
16116
|
instructionsOverrideBlockedBy
|
|
@@ -15975,6 +16258,10 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
15975
16258
|
toggleExpandFocusedInstructionRow();
|
|
15976
16259
|
return;
|
|
15977
16260
|
}
|
|
16261
|
+
if (activeTab === "authentication" && key.name === "tab") {
|
|
16262
|
+
toggleExpandFocusedProviderRow();
|
|
16263
|
+
return;
|
|
16264
|
+
}
|
|
15978
16265
|
if (key.name === "return") {
|
|
15979
16266
|
activateRowAt(cursor);
|
|
15980
16267
|
return;
|
|
@@ -16359,6 +16646,7 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
16359
16646
|
activeTab === "authentication" && authentication && /* @__PURE__ */ jsx$1(AuthenticationList, {
|
|
16360
16647
|
view: authentication,
|
|
16361
16648
|
providers: filteredProviders,
|
|
16649
|
+
rows: authVisibleRows,
|
|
16362
16650
|
cursor,
|
|
16363
16651
|
highlightBg: SURFACE.selection,
|
|
16364
16652
|
query
|
|
@@ -17761,7 +18049,7 @@ function LogsList({ buffer, filePath, minLevel, query, scrollboxRef }) {
|
|
|
17761
18049
|
]
|
|
17762
18050
|
});
|
|
17763
18051
|
}
|
|
17764
|
-
function AuthenticationList({ view, providers, cursor, highlightBg, query }) {
|
|
18052
|
+
function AuthenticationList({ view, providers, rows, cursor, highlightBg, query }) {
|
|
17765
18053
|
const COLOR = useColors();
|
|
17766
18054
|
const home = homedir();
|
|
17767
18055
|
const totalProviders = view.providers?.length ?? 0;
|
|
@@ -17842,10 +18130,51 @@ function AuthenticationList({ view, providers, cursor, highlightBg, query }) {
|
|
|
17842
18130
|
})
|
|
17843
18131
|
}),
|
|
17844
18132
|
totalProviders > 0 && providers.length === 0 && /* @__PURE__ */ jsx$1(EmptyRow, { label: `no providers match "${query}"` }),
|
|
17845
|
-
|
|
17846
|
-
const isCurrent = view.currentProviderKey === p.key;
|
|
18133
|
+
rows.map((row, i) => {
|
|
17847
18134
|
const focused = i === cursor;
|
|
17848
18135
|
const bg = focused ? highlightBg : void 0;
|
|
18136
|
+
if (row.kind === "source") {
|
|
18137
|
+
const { method } = row;
|
|
18138
|
+
const isEnv = method.source === "env";
|
|
18139
|
+
const dotColor = isEnv ? COLOR.model : method.active ? COLOR.accent : COLOR.mute;
|
|
18140
|
+
return /* @__PURE__ */ jsx$1("box", {
|
|
18141
|
+
id: anchorIdFor(i),
|
|
18142
|
+
style: {
|
|
18143
|
+
flexShrink: 0,
|
|
18144
|
+
paddingLeft: 1,
|
|
18145
|
+
paddingRight: 1,
|
|
18146
|
+
backgroundColor: bg
|
|
18147
|
+
},
|
|
18148
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
18149
|
+
wrapMode: "none",
|
|
18150
|
+
children: [
|
|
18151
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
18152
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
18153
|
+
children: focused ? " ▶ " : " "
|
|
18154
|
+
}),
|
|
18155
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
18156
|
+
fg: dotColor,
|
|
18157
|
+
children: isEnv || method.active ? "● " : "○ "
|
|
18158
|
+
}),
|
|
18159
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
18160
|
+
fg: isEnv ? COLOR.model : method.active ? COLOR.accent : focused ? COLOR.brand : COLOR.dim,
|
|
18161
|
+
children: method.source
|
|
18162
|
+
}),
|
|
18163
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
18164
|
+
fg: COLOR.mute,
|
|
18165
|
+
children: ` ${displayPath$1(method.detail, home)}`
|
|
18166
|
+
}),
|
|
18167
|
+
method.active && /* @__PURE__ */ jsx$1("span", {
|
|
18168
|
+
fg: COLOR.mute,
|
|
18169
|
+
children: " (in use)"
|
|
18170
|
+
})
|
|
18171
|
+
]
|
|
18172
|
+
})
|
|
18173
|
+
}, `${i}:${row.providerKey}:${method.source}`);
|
|
18174
|
+
}
|
|
18175
|
+
const p = row.provider;
|
|
18176
|
+
const expanded = row.expandable && rows[i + 1]?.kind === "source";
|
|
18177
|
+
const isCurrent = view.currentProviderKey === p.key;
|
|
17849
18178
|
const marker = focused ? "▶ " : isCurrent ? "▸ " : " ";
|
|
17850
18179
|
const labelColor = focused || isCurrent ? COLOR.brand : p.available ? COLOR.dim : COLOR.mute;
|
|
17851
18180
|
return /* @__PURE__ */ jsxs("box", {
|
|
@@ -17875,9 +18204,17 @@ function AuthenticationList({ view, providers, cursor, highlightBg, query }) {
|
|
|
17875
18204
|
isCurrent && /* @__PURE__ */ jsx$1("span", {
|
|
17876
18205
|
fg: COLOR.mute,
|
|
17877
18206
|
children: " (active)"
|
|
18207
|
+
}),
|
|
18208
|
+
row.expandable && /* @__PURE__ */ jsx$1("span", {
|
|
18209
|
+
fg: COLOR.mute,
|
|
18210
|
+
children: ` ${expanded ? "▾" : "▸"} ${selectableSources(p).length} sources`
|
|
18211
|
+
}),
|
|
18212
|
+
row.expandable && focused && /* @__PURE__ */ jsx$1("span", {
|
|
18213
|
+
fg: COLOR.dim,
|
|
18214
|
+
children: " tab"
|
|
17878
18215
|
})
|
|
17879
18216
|
]
|
|
17880
|
-
}), p.methods.length > 0 && /* @__PURE__ */ jsx$1("text", {
|
|
18217
|
+
}), p.methods.length > 0 && !expanded && /* @__PURE__ */ jsx$1("text", {
|
|
17881
18218
|
wrapMode: "none",
|
|
17882
18219
|
fg: COLOR.mute,
|
|
17883
18220
|
children: ` ${p.methods.map((m) => `${m.source}: ${displayPath$1(m.detail, home)}`).join(" · ")}`
|
|
@@ -18496,7 +18833,109 @@ function restoreTerminalTitle(out = process.stdout) {
|
|
|
18496
18833
|
} catch {}
|
|
18497
18834
|
}
|
|
18498
18835
|
//#endregion
|
|
18836
|
+
//#region src/chat/todo-edits.ts
|
|
18837
|
+
/**
|
|
18838
|
+
* Prefix for ids minted here, distinguishing user-authored rows from the
|
|
18839
|
+
* model's own ids in a session file. Purely diagnostic — nothing branches on
|
|
18840
|
+
* it — but it makes "who added this" answerable after the fact.
|
|
18841
|
+
*/
|
|
18842
|
+
const USER_TODO_ID_PREFIX = "user-";
|
|
18843
|
+
/**
|
|
18844
|
+
* Mint an id that cannot collide with an existing row.
|
|
18845
|
+
*
|
|
18846
|
+
* Counter-based rather than random: a stable, readable id keeps session diffs
|
|
18847
|
+
* legible, and the collision check makes it safe against ids the model already
|
|
18848
|
+
* used (including previous `user-*` ones after a reload).
|
|
18849
|
+
*/
|
|
18850
|
+
function nextTodoId(items) {
|
|
18851
|
+
const taken = new Set(items.map((i) => i.id));
|
|
18852
|
+
for (let n = 1;; n++) {
|
|
18853
|
+
const id = `${USER_TODO_ID_PREFIX}${n}`;
|
|
18854
|
+
if (!taken.has(id)) return id;
|
|
18855
|
+
}
|
|
18856
|
+
}
|
|
18857
|
+
/**
|
|
18858
|
+
* Append a todo. Returns the list unchanged when `content` is blank — an empty
|
|
18859
|
+
* row is never what the user meant, and rejecting it here means every caller
|
|
18860
|
+
* (keyboard, click, future GUI) gets the same guard.
|
|
18861
|
+
*/
|
|
18862
|
+
function addTodo(items, content) {
|
|
18863
|
+
const trimmed = content.trim();
|
|
18864
|
+
if (!trimmed) return [...items];
|
|
18865
|
+
return [...items, {
|
|
18866
|
+
id: nextTodoId(items),
|
|
18867
|
+
content: trimmed,
|
|
18868
|
+
status: "pending"
|
|
18869
|
+
}];
|
|
18870
|
+
}
|
|
18871
|
+
/**
|
|
18872
|
+
* Replace one todo's text. Blank content is rejected (see {@link addTodo}); an
|
|
18873
|
+
* unknown id is a no-op rather than an error, because the list can be replaced
|
|
18874
|
+
* by a `todowrite` between render and keypress.
|
|
18875
|
+
*/
|
|
18876
|
+
function editTodo(items, id, content) {
|
|
18877
|
+
const trimmed = content.trim();
|
|
18878
|
+
if (!trimmed) return [...items];
|
|
18879
|
+
return items.map((item) => item.id === id ? {
|
|
18880
|
+
...item,
|
|
18881
|
+
content: trimmed
|
|
18882
|
+
} : item);
|
|
18883
|
+
}
|
|
18884
|
+
/** Drop one todo. Unknown id is a no-op, same rationale as {@link editTodo}. */
|
|
18885
|
+
function removeTodo(items, id) {
|
|
18886
|
+
return items.filter((item) => item.id !== id);
|
|
18887
|
+
}
|
|
18888
|
+
/** Drop everything. Separate from `removeTodo` so the caller's intent is explicit at the call site. */
|
|
18889
|
+
function clearTodos() {
|
|
18890
|
+
return [];
|
|
18891
|
+
}
|
|
18892
|
+
/**
|
|
18893
|
+
* Advance a todo's status one step: pending → in_progress → completed →
|
|
18894
|
+
* pending. `cancelled` also lands on `pending`, so a dropped item can be
|
|
18895
|
+
* revived without a dedicated key.
|
|
18896
|
+
*
|
|
18897
|
+
* Cycling INTO `in_progress` demotes any other in-progress row to `pending`:
|
|
18898
|
+
* the list's whole purpose is "what is being worked on right now", and two
|
|
18899
|
+
* live rows make that unanswerable — `TodoIndicator`'s successor
|
|
18900
|
+
* (`todoProgressLabel`) reads the first one and would silently ignore the rest.
|
|
18901
|
+
*/
|
|
18902
|
+
function cycleTodoStatus(items, id) {
|
|
18903
|
+
const target = items.find((item) => item.id === id);
|
|
18904
|
+
if (!target) return [...items];
|
|
18905
|
+
const next = nextStatus(target.status);
|
|
18906
|
+
return items.map((item) => {
|
|
18907
|
+
if (item.id === id) return {
|
|
18908
|
+
...item,
|
|
18909
|
+
status: next
|
|
18910
|
+
};
|
|
18911
|
+
if (next === "in_progress" && item.status === "in_progress") return {
|
|
18912
|
+
...item,
|
|
18913
|
+
status: "pending"
|
|
18914
|
+
};
|
|
18915
|
+
return item;
|
|
18916
|
+
});
|
|
18917
|
+
}
|
|
18918
|
+
function nextStatus(status) {
|
|
18919
|
+
switch (status) {
|
|
18920
|
+
case "pending": return "in_progress";
|
|
18921
|
+
case "in_progress": return "completed";
|
|
18922
|
+
default: return "pending";
|
|
18923
|
+
}
|
|
18924
|
+
}
|
|
18925
|
+
//#endregion
|
|
18499
18926
|
//#region src/tui/todos-modal.tsx
|
|
18927
|
+
/**
|
|
18928
|
+
* Steering text for a "retry this todo" request.
|
|
18929
|
+
*
|
|
18930
|
+
* Quotes the content so a model reading it can match the row verbatim against
|
|
18931
|
+
* its own list, and names the status because "retry" means different things
|
|
18932
|
+
* for a row it believes is `completed` versus one it abandoned as `cancelled`.
|
|
18933
|
+
* Pure + exported so the wording is pinned by a test rather than living only
|
|
18934
|
+
* inside a JSX callback.
|
|
18935
|
+
*/
|
|
18936
|
+
function buildTodoRetryMessage(item) {
|
|
18937
|
+
return `Retry this todo (currently ${item.status}): "${item.content}"`;
|
|
18938
|
+
}
|
|
18500
18939
|
/** Floor on the modal height — keeps the header + at least a few rows visible on tiny terminals. */
|
|
18501
18940
|
const MIN_MODAL_HEIGHT = 12;
|
|
18502
18941
|
/** Ceiling on the modal height — prevents a giant list from blanket-filling a tall window. */
|
|
@@ -18534,11 +18973,16 @@ function statusColors(status, COLOR) {
|
|
|
18534
18973
|
};
|
|
18535
18974
|
}
|
|
18536
18975
|
}
|
|
18537
|
-
function TodoRow({ item, rowId }) {
|
|
18976
|
+
function TodoRow({ item, rowId, focused, onClick }) {
|
|
18538
18977
|
const COLOR = useColors();
|
|
18539
18978
|
const colors = statusColors(item.status, COLOR);
|
|
18540
18979
|
return /* @__PURE__ */ jsx$1("box", {
|
|
18541
18980
|
id: rowId,
|
|
18981
|
+
onMouseDown: onClick && ((event) => {
|
|
18982
|
+
event.stopPropagation();
|
|
18983
|
+
event.preventDefault();
|
|
18984
|
+
onClick();
|
|
18985
|
+
}),
|
|
18542
18986
|
style: {
|
|
18543
18987
|
flexShrink: 0,
|
|
18544
18988
|
flexDirection: "row"
|
|
@@ -18546,6 +18990,10 @@ function TodoRow({ item, rowId }) {
|
|
|
18546
18990
|
children: /* @__PURE__ */ jsxs("text", {
|
|
18547
18991
|
wrapMode: "word",
|
|
18548
18992
|
children: [
|
|
18993
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
18994
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
18995
|
+
children: focused ? "▶ " : " "
|
|
18996
|
+
}),
|
|
18549
18997
|
/* @__PURE__ */ jsx$1("span", {
|
|
18550
18998
|
fg: colors.glyph,
|
|
18551
18999
|
children: TODO_STATUS_GLYPHS[item.status]
|
|
@@ -18562,10 +19010,14 @@ function TodoRow({ item, rowId }) {
|
|
|
18562
19010
|
})
|
|
18563
19011
|
});
|
|
18564
19012
|
}
|
|
18565
|
-
function TodosModal({ session, agent }) {
|
|
19013
|
+
function TodosModal({ session, agent, onSteer }) {
|
|
18566
19014
|
const COLOR = useColors();
|
|
19015
|
+
const modal = useModal();
|
|
18567
19016
|
const { height: termHeight } = useTerminalDimensions();
|
|
18568
19017
|
const [, setTick] = useState(0);
|
|
19018
|
+
const [cursorRaw, setCursorRaw] = useState(0);
|
|
19019
|
+
const [mode, setMode] = useState("list");
|
|
19020
|
+
const [draft, setDraft] = useState("");
|
|
18569
19021
|
useEffect(() => {
|
|
18570
19022
|
if (!agent) return;
|
|
18571
19023
|
return agent.hooks.hook("tool:after", (ctx) => {
|
|
@@ -18588,46 +19040,213 @@ function TodosModal({ session, agent }) {
|
|
|
18588
19040
|
const maxHeight = Math.max(MIN_MODAL_HEIGHT, Math.min(MAX_MODAL_HEIGHT$1, idealHeight));
|
|
18589
19041
|
const display = state.todos.length > 0 ? state.todos : state.archive;
|
|
18590
19042
|
const total = display.length;
|
|
18591
|
-
|
|
19043
|
+
const cursor = total === 0 ? 0 : Math.min(cursorRaw, total - 1);
|
|
19044
|
+
const focusedItem = display[cursor] ?? null;
|
|
19045
|
+
/**
|
|
19046
|
+
* Write a transformed list back to the run.
|
|
19047
|
+
*
|
|
19048
|
+
* Editing while the ARCHIVE is on screen promotes it back to the live slot,
|
|
19049
|
+
* which is the only coherent option: `setTodosForRun` owns the live slot, and
|
|
19050
|
+
* an edit the user just made must be the list that's displayed next.
|
|
19051
|
+
*/
|
|
19052
|
+
const commit = useCallback((next) => {
|
|
19053
|
+
if (!session || !state.runId) return;
|
|
19054
|
+
setTodosForRun(session, state.runId, next, { clearArchive: true });
|
|
19055
|
+
setTick((t) => t + 1);
|
|
19056
|
+
}, [session, state.runId]);
|
|
19057
|
+
const editable = session !== null && state.runId !== null;
|
|
19058
|
+
/** Enter add / edit mode, seeding the draft from the focused row for edits. */
|
|
19059
|
+
const beginEdit = useCallback((next) => {
|
|
19060
|
+
setDraft(next === "edit" ? focusedItem?.content ?? "" : "");
|
|
19061
|
+
setMode(next);
|
|
19062
|
+
}, [focusedItem]);
|
|
19063
|
+
useKeyboard((key) => {
|
|
19064
|
+
if (!editable) return;
|
|
19065
|
+
if (mode === "add" || mode === "edit") {
|
|
19066
|
+
if (key.name === "escape") {
|
|
19067
|
+
setDraft("");
|
|
19068
|
+
setMode("list");
|
|
19069
|
+
}
|
|
19070
|
+
return;
|
|
19071
|
+
}
|
|
19072
|
+
if (mode === "confirm-clear") {
|
|
19073
|
+
if (key.name === "y") {
|
|
19074
|
+
commit(clearTodos());
|
|
19075
|
+
setMode("list");
|
|
19076
|
+
} else if (key.name === "n" || key.name === "escape") setMode("list");
|
|
19077
|
+
return;
|
|
19078
|
+
}
|
|
19079
|
+
if (key.name === "up") {
|
|
19080
|
+
setCursorRaw((c) => Math.max(0, c - 1));
|
|
19081
|
+
return;
|
|
19082
|
+
}
|
|
19083
|
+
if (key.name === "down") {
|
|
19084
|
+
setCursorRaw((c) => Math.min(Math.max(0, total - 1), c + 1));
|
|
19085
|
+
return;
|
|
19086
|
+
}
|
|
19087
|
+
if (key.name === "a") {
|
|
19088
|
+
beginEdit("add");
|
|
19089
|
+
return;
|
|
19090
|
+
}
|
|
19091
|
+
if (key.ctrl && key.name === "d") {
|
|
19092
|
+
if (total > 0) setMode("confirm-clear");
|
|
19093
|
+
return;
|
|
19094
|
+
}
|
|
19095
|
+
if (key.name === "d") {
|
|
19096
|
+
if (focusedItem) commit(removeTodo(display, focusedItem.id));
|
|
19097
|
+
return;
|
|
19098
|
+
}
|
|
19099
|
+
if (key.name === "e" || key.name === "return") {
|
|
19100
|
+
if (focusedItem) beginEdit("edit");
|
|
19101
|
+
return;
|
|
19102
|
+
}
|
|
19103
|
+
if (key.name === "space") {
|
|
19104
|
+
if (focusedItem) commit(cycleTodoStatus(display, focusedItem.id));
|
|
19105
|
+
return;
|
|
19106
|
+
}
|
|
19107
|
+
if (key.name === "r") {
|
|
19108
|
+
if (focusedItem && onSteer) {
|
|
19109
|
+
onSteer(buildTodoRetryMessage(focusedItem));
|
|
19110
|
+
modal.close();
|
|
19111
|
+
}
|
|
19112
|
+
}
|
|
19113
|
+
});
|
|
19114
|
+
const submitInput = useCallback(() => {
|
|
19115
|
+
const value = draft.trim();
|
|
19116
|
+
if (value) {
|
|
19117
|
+
if (mode === "add") commit(addTodo(display, value));
|
|
19118
|
+
else if (mode === "edit" && focusedItem) commit(editTodo(display, focusedItem.id, value));
|
|
19119
|
+
}
|
|
19120
|
+
setDraft("");
|
|
19121
|
+
setMode("list");
|
|
19122
|
+
}, [
|
|
19123
|
+
draft,
|
|
19124
|
+
mode,
|
|
19125
|
+
commit,
|
|
19126
|
+
display,
|
|
19127
|
+
focusedItem
|
|
19128
|
+
]);
|
|
19129
|
+
return /* @__PURE__ */ jsxs(Modal, {
|
|
18592
19130
|
title: "todos",
|
|
18593
19131
|
bottomTitle: total > 0 ? `${total} item${total === 1 ? "" : "s"} · esc close` : "esc close",
|
|
18594
19132
|
rightTitle: display.length > 0 ? /* @__PURE__ */ jsx$1(CountsBadge, { items: display }) : null,
|
|
18595
19133
|
maxWidth: 100,
|
|
18596
19134
|
maxHeight,
|
|
18597
|
-
|
|
18598
|
-
|
|
18599
|
-
|
|
18600
|
-
|
|
19135
|
+
disableEscape: mode !== "list",
|
|
19136
|
+
children: [
|
|
19137
|
+
total === 0 ? /* @__PURE__ */ jsxs("text", { children: [
|
|
19138
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
19139
|
+
fg: COLOR.mute,
|
|
19140
|
+
children: "(no todos yet — the agent hasn't called "
|
|
19141
|
+
}),
|
|
19142
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
19143
|
+
fg: COLOR.warn,
|
|
19144
|
+
children: "todowrite"
|
|
19145
|
+
}),
|
|
19146
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
19147
|
+
fg: COLOR.mute,
|
|
19148
|
+
children: ")"
|
|
19149
|
+
})
|
|
19150
|
+
] }) : /* @__PURE__ */ jsx$1("box", {
|
|
19151
|
+
style: {
|
|
19152
|
+
flexDirection: "column",
|
|
19153
|
+
flexGrow: 1,
|
|
19154
|
+
flexShrink: 1,
|
|
19155
|
+
overflow: "hidden"
|
|
19156
|
+
},
|
|
19157
|
+
children: /* @__PURE__ */ jsx$1("scrollbox", {
|
|
19158
|
+
ref: scrollRef,
|
|
19159
|
+
focusable: false,
|
|
19160
|
+
stickyScroll: false,
|
|
19161
|
+
style: {
|
|
19162
|
+
flexGrow: 1,
|
|
19163
|
+
flexShrink: 1
|
|
19164
|
+
},
|
|
19165
|
+
children: display.map((item, i) => /* @__PURE__ */ jsx$1(TodoRow, {
|
|
19166
|
+
item,
|
|
19167
|
+
rowId: `todo-row-${item.id}`,
|
|
19168
|
+
focused: editable && i === cursor,
|
|
19169
|
+
onClick: editable ? () => setCursorRaw(i) : void 0
|
|
19170
|
+
}, item.id))
|
|
19171
|
+
})
|
|
18601
19172
|
}),
|
|
18602
|
-
/* @__PURE__ */
|
|
18603
|
-
|
|
18604
|
-
|
|
19173
|
+
editable && (mode === "add" || mode === "edit") && /* @__PURE__ */ jsxs("box", {
|
|
19174
|
+
style: {
|
|
19175
|
+
flexShrink: 0,
|
|
19176
|
+
flexDirection: "row",
|
|
19177
|
+
paddingTop: 1
|
|
19178
|
+
},
|
|
19179
|
+
children: [/* @__PURE__ */ jsx$1("text", {
|
|
19180
|
+
wrapMode: "none",
|
|
19181
|
+
children: /* @__PURE__ */ jsx$1("span", {
|
|
19182
|
+
fg: COLOR.warn,
|
|
19183
|
+
children: mode === "add" ? "+ " : "✎ "
|
|
19184
|
+
})
|
|
19185
|
+
}), /* @__PURE__ */ jsx$1("input", {
|
|
19186
|
+
focused: true,
|
|
19187
|
+
value: draft,
|
|
19188
|
+
placeholder: mode === "add" ? "new todo…" : "edit todo…",
|
|
19189
|
+
onInput: setDraft,
|
|
19190
|
+
onSubmit: () => submitInput(),
|
|
19191
|
+
style: { flexGrow: 1 }
|
|
19192
|
+
})]
|
|
18605
19193
|
}),
|
|
18606
|
-
/* @__PURE__ */
|
|
18607
|
-
fg: COLOR.mute,
|
|
18608
|
-
children: ")"
|
|
18609
|
-
})
|
|
18610
|
-
] }) : /* @__PURE__ */ jsx$1("box", {
|
|
18611
|
-
style: {
|
|
18612
|
-
flexDirection: "column",
|
|
18613
|
-
flexGrow: 1,
|
|
18614
|
-
flexShrink: 1,
|
|
18615
|
-
overflow: "hidden"
|
|
18616
|
-
},
|
|
18617
|
-
children: /* @__PURE__ */ jsx$1("scrollbox", {
|
|
18618
|
-
ref: scrollRef,
|
|
18619
|
-
focusable: false,
|
|
18620
|
-
stickyScroll: false,
|
|
19194
|
+
editable && mode === "confirm-clear" && /* @__PURE__ */ jsxs("box", {
|
|
18621
19195
|
style: {
|
|
18622
|
-
|
|
18623
|
-
|
|
19196
|
+
flexShrink: 0,
|
|
19197
|
+
flexDirection: "column",
|
|
19198
|
+
paddingTop: 1
|
|
18624
19199
|
},
|
|
18625
|
-
children:
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
19200
|
+
children: [/* @__PURE__ */ jsx$1("text", {
|
|
19201
|
+
wrapMode: "none",
|
|
19202
|
+
children: /* @__PURE__ */ jsx$1("span", {
|
|
19203
|
+
fg: COLOR.error,
|
|
19204
|
+
children: `delete all ${total} todo${total === 1 ? "" : "s"}?`
|
|
19205
|
+
})
|
|
19206
|
+
}), /* @__PURE__ */ jsx$1(ModalHints, { hints: [{
|
|
19207
|
+
key: "y",
|
|
19208
|
+
label: "yes",
|
|
19209
|
+
keyColor: COLOR.error
|
|
19210
|
+
}, {
|
|
19211
|
+
key: "n",
|
|
19212
|
+
label: "cancel"
|
|
19213
|
+
}] })]
|
|
19214
|
+
}),
|
|
19215
|
+
editable && mode === "list" && /* @__PURE__ */ jsx$1("box", {
|
|
19216
|
+
style: {
|
|
19217
|
+
flexShrink: 0,
|
|
19218
|
+
flexDirection: "column",
|
|
19219
|
+
paddingTop: 1
|
|
19220
|
+
},
|
|
19221
|
+
children: /* @__PURE__ */ jsx$1(ModalHints, { hints: [
|
|
19222
|
+
{
|
|
19223
|
+
key: "a",
|
|
19224
|
+
label: "add"
|
|
19225
|
+
},
|
|
19226
|
+
total > 0 && {
|
|
19227
|
+
key: "e",
|
|
19228
|
+
label: "edit"
|
|
19229
|
+
},
|
|
19230
|
+
total > 0 && {
|
|
19231
|
+
key: "space",
|
|
19232
|
+
label: "status"
|
|
19233
|
+
},
|
|
19234
|
+
total > 0 && {
|
|
19235
|
+
key: "d",
|
|
19236
|
+
label: "remove"
|
|
19237
|
+
},
|
|
19238
|
+
total > 0 && onSteer !== void 0 && {
|
|
19239
|
+
key: "r",
|
|
19240
|
+
label: "retry"
|
|
19241
|
+
},
|
|
19242
|
+
total > 0 && {
|
|
19243
|
+
key: "ctrl+d",
|
|
19244
|
+
label: "delete all",
|
|
19245
|
+
keyColor: COLOR.error
|
|
19246
|
+
}
|
|
19247
|
+
] })
|
|
18629
19248
|
})
|
|
18630
|
-
|
|
19249
|
+
]
|
|
18631
19250
|
});
|
|
18632
19251
|
}
|
|
18633
19252
|
/**
|
|
@@ -19560,10 +20179,19 @@ function AppShell() {
|
|
|
19560
20179
|
useEffect(() => {
|
|
19561
20180
|
persistToolResultsRef.current = settings.persistToolResults;
|
|
19562
20181
|
}, [settings.persistToolResults]);
|
|
19563
|
-
const
|
|
20182
|
+
const interactionModeRef = useRef(settings.interactionMode);
|
|
19564
20183
|
useEffect(() => {
|
|
19565
|
-
|
|
19566
|
-
}, [settings.
|
|
20184
|
+
interactionModeRef.current = settings.interactionMode;
|
|
20185
|
+
}, [settings.interactionMode]);
|
|
20186
|
+
useEffect(() => {
|
|
20187
|
+
if (settings.interactionMode !== "skip" || !pendingInteraction) return;
|
|
20188
|
+
if (!isModelInteraction(pendingInteraction)) return;
|
|
20189
|
+
interactions.resolveHead(createAutomaticInteractionResponse(pendingInteraction));
|
|
20190
|
+
}, [
|
|
20191
|
+
interactions,
|
|
20192
|
+
pendingInteraction,
|
|
20193
|
+
settings.interactionMode
|
|
20194
|
+
]);
|
|
19567
20195
|
const userInstructionsScopeRef = useRef(settings.userInstructionsScope);
|
|
19568
20196
|
useEffect(() => {
|
|
19569
20197
|
userInstructionsScopeRef.current = settings.userInstructionsScope;
|
|
@@ -20470,7 +21098,7 @@ function AppShell() {
|
|
|
20470
21098
|
const profile = pickedAgentRef.current;
|
|
20471
21099
|
if (profile.id !== "build" && profile.id !== "plan") return;
|
|
20472
21100
|
return agent.hooks.hook("system:transform", (ctx) => {
|
|
20473
|
-
const allowInteraction =
|
|
21101
|
+
const allowInteraction = interactionModeRef.current !== "disabled";
|
|
20474
21102
|
const envOpts = {
|
|
20475
21103
|
cwd,
|
|
20476
21104
|
...projectDir !== cwd ? { projectRoot: projectDir } : {},
|
|
@@ -20814,8 +21442,11 @@ function AppShell() {
|
|
|
20814
21442
|
enabled: enabledMcpsRef.current,
|
|
20815
21443
|
...disabledMcpToolsRef.current ? { disabledTools: disabledMcpToolsRef.current } : {}
|
|
20816
21444
|
}), ...pluginContributions.mcpServers];
|
|
20817
|
-
const allowInteraction =
|
|
20818
|
-
const interactionTools = allowInteraction ? createInteractionTools({
|
|
21445
|
+
const allowInteraction = interactionModeRef.current !== "disabled";
|
|
21446
|
+
const interactionTools = allowInteraction ? createInteractionTools({
|
|
21447
|
+
requestInteraction: makeRequestInteraction(interactions),
|
|
21448
|
+
shouldSkipInteractions: () => interactionModeRef.current === "skip"
|
|
21449
|
+
}) : {};
|
|
20819
21450
|
const actualCwd = cwdRef.current;
|
|
20820
21451
|
const { agentsMd, systemFile, system: builtInSystem } = composeHostContext(profile, allowInteraction);
|
|
20821
21452
|
sessionInstructionFilesRef.current = agentsMd.files;
|
|
@@ -21716,6 +22347,35 @@ function AppShell() {
|
|
|
21716
22347
|
interactions
|
|
21717
22348
|
]);
|
|
21718
22349
|
/**
|
|
22350
|
+
* Persist the matched `tool_result` turn for a resolved batch WITHOUT
|
|
22351
|
+
* running. Automatic mode uses this on activation so opening an old
|
|
22352
|
+
* session repairs its history (every `tool_use` matched) instead of
|
|
22353
|
+
* silently starting a billed run nobody asked for; the next prompt
|
|
22354
|
+
* continues normally.
|
|
22355
|
+
*/
|
|
22356
|
+
const persistResumedInteractions = useCallback(async (requests, responses) => {
|
|
22357
|
+
const session = sessionRef.current;
|
|
22358
|
+
if (!session) {
|
|
22359
|
+
debugLog("resume-interaction: missing session", { count: requests.length });
|
|
22360
|
+
return false;
|
|
22361
|
+
}
|
|
22362
|
+
const turnId = await session.generateTurnId();
|
|
22363
|
+
const runId = requests[0]?.runId;
|
|
22364
|
+
let turn;
|
|
22365
|
+
try {
|
|
22366
|
+
turn = buildResumedToolResultsTurn(requests, responses, {
|
|
22367
|
+
turnId,
|
|
22368
|
+
runId
|
|
22369
|
+
});
|
|
22370
|
+
} catch (err) {
|
|
22371
|
+
debugLog("resume-interaction: turn build failed", err);
|
|
22372
|
+
return false;
|
|
22373
|
+
}
|
|
22374
|
+
await session.appendTurns([turn]);
|
|
22375
|
+
if (sessionRef.current === session) setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
|
|
22376
|
+
return true;
|
|
22377
|
+
}, []);
|
|
22378
|
+
/**
|
|
21719
22379
|
* Continue a session from where pending interactions left it. Appends
|
|
21720
22380
|
* a single user turn containing one `tool_result` per pending request
|
|
21721
22381
|
* (in pending order) and triggers a prompt-less `agent.run()` so the
|
|
@@ -21830,7 +22490,12 @@ function AppShell() {
|
|
|
21830
22490
|
const pending = pendingInteractionsFromTurns(session.turns);
|
|
21831
22491
|
if (pending.length > 0) {
|
|
21832
22492
|
const collected = /* @__PURE__ */ new Map();
|
|
22493
|
+
const automatic = interactionModeRef.current === "skip";
|
|
21833
22494
|
for (const request of pending) {
|
|
22495
|
+
if (automatic) {
|
|
22496
|
+
collected.set(request.id, createAutomaticInteractionResponse(request));
|
|
22497
|
+
continue;
|
|
22498
|
+
}
|
|
21834
22499
|
const entry = {
|
|
21835
22500
|
request,
|
|
21836
22501
|
resolve: (response) => {
|
|
@@ -21841,6 +22506,7 @@ function AppShell() {
|
|
|
21841
22506
|
};
|
|
21842
22507
|
interactions.enqueue(entry);
|
|
21843
22508
|
}
|
|
22509
|
+
if (automatic) persistResumedInteractions(pending, collected).catch((err) => debugLog("resume-interaction: automatic persist failed", err));
|
|
21844
22510
|
}
|
|
21845
22511
|
}, [
|
|
21846
22512
|
teardown,
|
|
@@ -21850,6 +22516,7 @@ function AppShell() {
|
|
|
21850
22516
|
projectDir,
|
|
21851
22517
|
interactions,
|
|
21852
22518
|
continueResumedInteractions,
|
|
22519
|
+
persistResumedInteractions,
|
|
21853
22520
|
extensionRegistry
|
|
21854
22521
|
]);
|
|
21855
22522
|
/** Ref mirror — same rationale as `refreshSessionsRef` above. */
|
|
@@ -22890,7 +23557,8 @@ function AppShell() {
|
|
|
22890
23557
|
interaction: {
|
|
22891
23558
|
confirm: async (prompt, confirmOpts) => {
|
|
22892
23559
|
const response = await requestCommandInteraction({
|
|
22893
|
-
id:
|
|
23560
|
+
id: `${HOST_INTERACTION_ID_PREFIX}${crypto.randomUUID()}`,
|
|
23561
|
+
origin: "host",
|
|
22894
23562
|
kind: "question",
|
|
22895
23563
|
tool: ASK_USER_TOOL,
|
|
22896
23564
|
createdAt: Date.now(),
|
|
@@ -22910,7 +23578,8 @@ function AppShell() {
|
|
|
22910
23578
|
},
|
|
22911
23579
|
askUser: async (form) => {
|
|
22912
23580
|
const response = await requestCommandInteraction({
|
|
22913
|
-
id:
|
|
23581
|
+
id: `${HOST_INTERACTION_ID_PREFIX}${crypto.randomUUID()}`,
|
|
23582
|
+
origin: "host",
|
|
22914
23583
|
kind: "question",
|
|
22915
23584
|
tool: ASK_USER_TOOL,
|
|
22916
23585
|
createdAt: Date.now(),
|
|
@@ -22921,7 +23590,8 @@ function AppShell() {
|
|
|
22921
23590
|
},
|
|
22922
23591
|
presentPlan: async (plan) => {
|
|
22923
23592
|
const response = await requestCommandInteraction({
|
|
22924
|
-
id:
|
|
23593
|
+
id: `${HOST_INTERACTION_ID_PREFIX}${crypto.randomUUID()}`,
|
|
23594
|
+
origin: "host",
|
|
22925
23595
|
kind: "plan",
|
|
22926
23596
|
tool: PRESENT_PLAN_TOOL,
|
|
22927
23597
|
createdAt: Date.now(),
|
|
@@ -23485,6 +24155,50 @@ function AppShell() {
|
|
|
23485
24155
|
if (staleWarnLog) await staleWarnLog.close().catch((err) => debugLog("rebuildActiveAgent: warnLog.close failed", err));
|
|
23486
24156
|
}, []);
|
|
23487
24157
|
rebuildActiveAgentRef.current = rebuildActiveAgent;
|
|
24158
|
+
/**
|
|
24159
|
+
* Promote one of a provider's stored credentials — Settings → Authentication
|
|
24160
|
+
* → `↵` on a source row, for a provider holding both an API key and an OAuth
|
|
24161
|
+
* grant.
|
|
24162
|
+
*
|
|
24163
|
+
* The preference IS the storage layout (the canonical file key holds the
|
|
24164
|
+
* winner), so persisting it is the entire change — there is no separate
|
|
24165
|
+
* precedence setting to keep in sync. `applyApiKeyEnv` then re-reconciles
|
|
24166
|
+
* `process.env`, which is the step that actually makes it take effect: a
|
|
24167
|
+
* promoted API key has to be injected, and a promoted OAuth grant has to
|
|
24168
|
+
* CLEAR the ambient var so the refresh path runs instead of a stale token.
|
|
24169
|
+
*
|
|
24170
|
+
* Returns freshly-detected providers instead of closing the modal. The
|
|
24171
|
+
* settings modal's provider list is frozen at open time, so an earlier
|
|
24172
|
+
* version closed the modal to avoid showing a stale active marker — which
|
|
24173
|
+
* made a SUCCESSFUL swap look like the key did nothing, since the panel
|
|
24174
|
+
* vanished at the moment of success. Handing detection back lets the modal
|
|
24175
|
+
* re-render the marker in place and stay put.
|
|
24176
|
+
*
|
|
24177
|
+
* Gated like `onConfigureProvider` so a mid-run credential swap can't race
|
|
24178
|
+
* the agent.
|
|
24179
|
+
*/
|
|
24180
|
+
const onSetPreferredCredential = useMemo(() => {
|
|
24181
|
+
if (busy || pendingApproval) return void 0;
|
|
24182
|
+
return (provider, source) => {
|
|
24183
|
+
const descriptor = providerRegistry[provider.key];
|
|
24184
|
+
if (!descriptor) return void 0;
|
|
24185
|
+
setPreferredCredential(config.paths.userDir, descriptor, source);
|
|
24186
|
+
applyApiKeyEnv(config.paths.userDir, providerRegistry, { log: (message) => getTuiLogging()?.sink.emit({
|
|
24187
|
+
level: "debug",
|
|
24188
|
+
timestamp: Date.now(),
|
|
24189
|
+
message: message.trimEnd(),
|
|
24190
|
+
attrs: { scope: "chat" }
|
|
24191
|
+
}) });
|
|
24192
|
+
if (provider.key === pickedRef.current?.provider.key) rebuildActiveAgent().catch((err) => debugLog("onSetPreferredCredential: rebuild failed", err));
|
|
24193
|
+
return detectAuth(config.paths.userDir, providerRegistry);
|
|
24194
|
+
};
|
|
24195
|
+
}, [
|
|
24196
|
+
busy,
|
|
24197
|
+
pendingApproval,
|
|
24198
|
+
config.paths.userDir,
|
|
24199
|
+
providerRegistry,
|
|
24200
|
+
rebuildActiveAgent
|
|
24201
|
+
]);
|
|
23488
24202
|
useEffect(() => {
|
|
23489
24203
|
const prev = mcpRuntimeKeyRef.current;
|
|
23490
24204
|
if (prev === null) {
|
|
@@ -23651,7 +24365,7 @@ function AppShell() {
|
|
|
23651
24365
|
})();
|
|
23652
24366
|
}, [modal, config.paths.userDir]);
|
|
23653
24367
|
const onReadContextSegments = useCallback(async () => {
|
|
23654
|
-
const { shadowedSystemFiles, supportsOverride } = composeHostContext(pickedAgentRef.current,
|
|
24368
|
+
const { shadowedSystemFiles, supportsOverride } = composeHostContext(pickedAgentRef.current, interactionModeRef.current !== "disabled");
|
|
23655
24369
|
const shadowedSystemPath = shadowedSystemFiles[0]?.path;
|
|
23656
24370
|
const scopeLoadsUserOverride = userScopeOverridesLoad(userInstructionsScopeRef.current);
|
|
23657
24371
|
const canOverrideSystem = supportsOverride && scopeLoadsUserOverride;
|
|
@@ -23668,7 +24382,7 @@ function AppShell() {
|
|
|
23668
24382
|
...overrideBlockedBy ? { overrideBlockedBy } : {},
|
|
23669
24383
|
...shadowedSystemPath ? { shadowedSystemPath } : {}
|
|
23670
24384
|
};
|
|
23671
|
-
const { agentsMd, systemFile, system } = composeHostContext(pickedAgentRef.current,
|
|
24385
|
+
const { agentsMd, systemFile, system } = composeHostContext(pickedAgentRef.current, interactionModeRef.current !== "disabled");
|
|
23672
24386
|
const enabled = enabledSkillsRef.current;
|
|
23673
24387
|
const sessionSkills = enabled === void 0 ? [...skillsCatalogRef.current] : skillsCatalogRef.current.filter((s) => enabled.includes(s.name));
|
|
23674
24388
|
return {
|
|
@@ -23705,7 +24419,7 @@ function AppShell() {
|
|
|
23705
24419
|
const onCreateSystemPrompt = useCallback(async () => {
|
|
23706
24420
|
const path = userSystemMdPath({ prefix: config.prefix });
|
|
23707
24421
|
if (!existsSync(path)) {
|
|
23708
|
-
const { doctrineOnlySystem, supportsOverride } = composeHostContext(pickedAgentRef.current,
|
|
24422
|
+
const { doctrineOnlySystem, supportsOverride } = composeHostContext(pickedAgentRef.current, interactionModeRef.current !== "disabled");
|
|
23709
24423
|
if (!supportsOverride) throw new Error(`the "${pickedAgentRef.current.id}" profile supplies its own system prompt, so a SYSTEM.md override would not apply`);
|
|
23710
24424
|
if (!userScopeOverridesLoad(userInstructionsScopeRef.current)) throw new Error(`userInstructionsScope is "${userInstructionsScopeRef.current}", so a SYSTEM.md in your home directory would not be loaded`);
|
|
23711
24425
|
const doctrine = splitSystemPrompt(doctrineOnlySystem() ?? "").static;
|
|
@@ -24425,6 +25139,7 @@ function AppShell() {
|
|
|
24425
25139
|
},
|
|
24426
25140
|
actions: {
|
|
24427
25141
|
onConfigureProvider,
|
|
25142
|
+
onSetPreferredCredential,
|
|
24428
25143
|
onOpenKeybindings: onOpenKeybindingsFile,
|
|
24429
25144
|
getContextSegments: onReadContextSegments,
|
|
24430
25145
|
onOpenInstructionsFile,
|
|
@@ -24507,7 +25222,8 @@ function AppShell() {
|
|
|
24507
25222
|
if (matchesBinding(key, keybindings.openTodos) && screen === "chat" && currentSession) {
|
|
24508
25223
|
modal.open(/* @__PURE__ */ jsx$1(TodosModal, {
|
|
24509
25224
|
session: sessionRef.current,
|
|
24510
|
-
agent: agentRef.current
|
|
25225
|
+
agent: agentRef.current,
|
|
25226
|
+
onSteer: (message) => onSubmitPrompt(message, [], [])
|
|
24511
25227
|
}));
|
|
24512
25228
|
return;
|
|
24513
25229
|
}
|
|
@@ -24695,6 +25411,7 @@ function AppShell() {
|
|
|
24695
25411
|
COLOR.warn,
|
|
24696
25412
|
COLOR.dim
|
|
24697
25413
|
]);
|
|
25414
|
+
const todoProgress = todoProgressLabel(useActiveTodos(sessionRef.current));
|
|
24698
25415
|
const hints = useMemo(() => {
|
|
24699
25416
|
const base = buildHints({
|
|
24700
25417
|
screen,
|
|
@@ -24720,6 +25437,7 @@ function AppShell() {
|
|
|
24720
25437
|
inFlightToolCount: inFlightTools.reduce((n, entry) => entry.childId === void 0 ? n + 1 : n, 0) + backgroundTasks.reduce((n, entry) => entry.childId === void 0 ? n + 1 : n, 0),
|
|
24721
25438
|
activeSkillCount: activeSkillNames.size,
|
|
24722
25439
|
skillsChipColor: COLOR.brand,
|
|
25440
|
+
todoProgress: settings.showTodoIndicator ? todoProgress : null,
|
|
24723
25441
|
updateHint,
|
|
24724
25442
|
cacheBreakHint,
|
|
24725
25443
|
sessionsScopeAll: sessionsShowAll
|
|
@@ -24741,6 +25459,8 @@ function AppShell() {
|
|
|
24741
25459
|
currentSession,
|
|
24742
25460
|
hasMultipleAgents,
|
|
24743
25461
|
settings.uiMode,
|
|
25462
|
+
settings.showTodoIndicator,
|
|
25463
|
+
todoProgress,
|
|
24744
25464
|
picked,
|
|
24745
25465
|
pickedAgent,
|
|
24746
25466
|
COLOR,
|
|
@@ -24762,7 +25482,8 @@ function AppShell() {
|
|
|
24762
25482
|
start: r.start,
|
|
24763
25483
|
end: r.end,
|
|
24764
25484
|
providerId: r.providerId
|
|
24765
|
-
}))
|
|
25485
|
+
})),
|
|
25486
|
+
attachments: m.attachments.length
|
|
24766
25487
|
})), [messageQueue]);
|
|
24767
25488
|
const queueShortcuts = useMemo(() => ({
|
|
24768
25489
|
enter: keybindings.enterQueueSelection,
|
|
@@ -24853,7 +25574,6 @@ function AppShell() {
|
|
|
24853
25574
|
settings,
|
|
24854
25575
|
onSubmit: onSubmitPrompt,
|
|
24855
25576
|
session: currentSession,
|
|
24856
|
-
liveSession: sessionRef.current,
|
|
24857
25577
|
pending: pendingApproval,
|
|
24858
25578
|
onApproval: resolveHead,
|
|
24859
25579
|
pendingInteraction,
|