zidane 6.5.3 → 6.5.5
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-BPJbXS2J.js} +6 -6
- package/dist/{acp-CIpD8UIw.js.map → acp-BPJbXS2J.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-gZjHsUft.js} +176 -166
- package/dist/agent-gZjHsUft.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{anthropic-_Hn1YiWr.js → anthropic-D3le2d-s.js} +75 -14
- package/dist/anthropic-D3le2d-s.js.map +1 -0
- package/dist/{auth-CS_e8ng1.js → auth-DCiXCwGa.js} +8 -6
- package/dist/{auth-CS_e8ng1.js.map → auth-DCiXCwGa.js.map} +1 -1
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat/pure.js +2 -2
- package/dist/chat.d.ts +29 -8
- 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/cursor-iojRXHF9.js +139 -0
- package/dist/cursor-iojRXHF9.js.map +1 -0
- 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-BKyWhILQ.d.ts → index-BYKzzYzi.d.ts} +85 -22
- package/dist/index-BYKzzYzi.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -14
- 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-CVVg5jUL.js} +2 -2
- package/dist/{messages-C_H9soi5.js.map → messages-CVVg5jUL.js.map} +1 -1
- package/dist/{oauth-BYjlFR-J.js → oauth-C4GvY_p1.js} +50 -11
- package/dist/oauth-C4GvY_p1.js.map +1 -0
- package/dist/{openai-compat-CnHeo4Lz.js → openai-compat-hmZzpDrB.js} +2 -2
- package/dist/{openai-compat-CnHeo4Lz.js.map → openai-compat-hmZzpDrB.js.map} +1 -1
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/pkce-t8BcPyEi.js +30 -0
- package/dist/pkce-t8BcPyEi.js.map +1 -0
- 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 +3 -3
- package/dist/providers-Cf0AXIIZ.js +137 -0
- package/dist/providers-Cf0AXIIZ.js.map +1 -0
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +5 -5
- 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-cqN5NbO-.js} +2 -2
- package/dist/{session-VpLrNaFQ.js.map → session-cqN5NbO-.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-DMr62Hpy.d.ts → tool-formatters-B5WSHMCU.d.ts} +2 -2
- package/dist/{tool-formatters-DMr62Hpy.d.ts.map → tool-formatters-B5WSHMCU.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-BHihk9nf.d.ts → transcript-anchors-BPnWHLyr.d.ts} +2 -2
- package/dist/{transcript-anchors-BHihk9nf.d.ts.map → transcript-anchors-BPnWHLyr.d.ts.map} +1 -1
- package/dist/{transcript-anchors-uU_KZLsp.js → transcript-anchors-BvC8t7dt.js} +177 -63
- package/dist/transcript-anchors-BvC8t7dt.js.map +1 -0
- package/dist/tui.d.ts +18 -29
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +737 -236
- 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-BgqVUjtl.d.ts → turn-operations-BvP3_HsU.d.ts} +3 -3
- package/dist/{turn-operations-BgqVUjtl.d.ts.map → turn-operations-BvP3_HsU.d.ts.map} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{xai-CfmI5Y5k.js → xai-DILdFGzN.js} +3 -29
- package/dist/xai-DILdFGzN.js.map +1 -0
- 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-_Hn1YiWr.js.map +0 -1
- package/dist/index-BKyWhILQ.d.ts.map +0 -1
- package/dist/oauth-BYjlFR-J.js.map +0 -1
- package/dist/providers-VypZh7rD.js +0 -264
- package/dist/providers-VypZh7rD.js.map +0 -1
- package/dist/timeout-BDetbuN6.js.map +0 -1
- package/dist/transcript-anchors-uU_KZLsp.js.map +0 -1
- package/dist/xai-CfmI5Y5k.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, C as resolveLegacySessionDir, Cn as resolveExtensionConfigValue, D as resolveTasksDir, Dn as OUTPUT_RESERVE_TOKENS, E as resolveSessionDir, Et as isSpawnTool, G as summaryToTurn, It as looksBinary, M as compactConversation, O as buildPostCompactAttachments, On as effectiveContextWindow, Pt as tailTruncate, Qt as formatBindingForDisplay, Rt as createModelSearchTool, S as resolveLegacyPersistDir, T as resolvePersistDir, X as createExtensionPromptBridge, Xt as KEYBINDING_KEY_COL_WIDTH, Z as ExtensionInstallError, Zt as ensureKeybindingsFile, at as createExtensionHud, bt as basic_default, cn as registerExtensionHostModules, dt as filterExtensionRegistry, en as keybindingsPath, f as applyCompactSummaryCutoff, fn as inlineLoadedExtension, ft as filterRegistryPlugins, gn as createDebugLog, hn as scanExtensionDirectories, ht as createExtensionHostRuntimeBridge, it as updateInstalledExtension, lt as composeExtensionPreset, mn as parseExtensionScanPathEnv, mt as partitionExtensionCatalog, on as resolveExtensionKeybindings, ot as discoverAndBuildExtensionRegistry, rt as removeInstalledExtension, t as createAgent, tn as matchesBinding, ut as disposeExtensionRegistry, v as cleanupPersistedSession, w as resolveMcpWarningsDir, wn as sanitizeExtensionConfigActionPatch, x as rehomePersistedStubs, yn as coerceExtensionConfigInput } from "./agent-
|
|
1
|
+
import { C as estimateRebilledCostUsd } from "./openai-compat-hmZzpDrB.js";
|
|
2
|
+
import { t as buildContextSegments } from "./context-segments-CJCtJ9sU.js";
|
|
3
|
+
import { $ as installExtensionFromSpec, $t as groupBindings, A as selectFilesFromSession, C as resolveLegacySessionDir, Cn as resolveExtensionConfigValue, D as resolveTasksDir, Dn as OUTPUT_RESERVE_TOKENS, E as resolveSessionDir, Et as isSpawnTool, G as summaryToTurn, It as looksBinary, M as compactConversation, O as buildPostCompactAttachments, On as effectiveContextWindow, Pt as tailTruncate, Qt as formatBindingForDisplay, Rt as createModelSearchTool, S as resolveLegacyPersistDir, T as resolvePersistDir, X as createExtensionPromptBridge, Xt as KEYBINDING_KEY_COL_WIDTH, Z as ExtensionInstallError, Zt as ensureKeybindingsFile, at as createExtensionHud, bt as basic_default, cn as registerExtensionHostModules, dt as filterExtensionRegistry, en as keybindingsPath, f as applyCompactSummaryCutoff, fn as inlineLoadedExtension, ft as filterRegistryPlugins, gn as createDebugLog, hn as scanExtensionDirectories, ht as createExtensionHostRuntimeBridge, it as updateInstalledExtension, lt as composeExtensionPreset, mn as parseExtensionScanPathEnv, mt as partitionExtensionCatalog, on as resolveExtensionKeybindings, ot as discoverAndBuildExtensionRegistry, rt as removeInstalledExtension, t as createAgent, tn as matchesBinding, ut as disposeExtensionRegistry, v as cleanupPersistedSession, w as resolveMcpWarningsDir, wn as sanitizeExtensionConfigActionPatch, x as rehomePersistedStubs, yn as coerceExtensionConfigInput } from "./agent-gZjHsUft.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-cqN5NbO-.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 { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning, w as renderDefaultCallbackPage } from "./auth-
|
|
20
|
+
import { a as ZIDANE_EXTENSION_SKILL_NAME, d as extensionScaffold, f as readSkillEditMarker, h as withZidaneSkillEditSkill, i as SKILL_EDIT_METADATA_KEY, l as editSessionMetadata, m as withZidaneExtensionSkill, o as ZIDANE_SKILL_EDIT_SKILL_NAME, t as loginMcpServer, u as extensionDirForCwd } from "./login-CB23LyI1.js";
|
|
21
|
+
import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning, w as renderDefaultCallbackPage } from "./auth-DCiXCwGa.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-BvC8t7dt.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";
|
|
@@ -11738,7 +11836,7 @@ const EMPTY_QUEUED_MESSAGES = [];
|
|
|
11738
11836
|
function shouldRenderTranscriptSearch(searchMode, query) {
|
|
11739
11837
|
return searchMode && query.trim().length > 0;
|
|
11740
11838
|
}
|
|
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
|
|
11839
|
+
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
11840
|
const COLOR = useColors();
|
|
11743
11841
|
const SURFACE = useSurfaces();
|
|
11744
11842
|
const titleText = session?.title ?? "New session";
|
|
@@ -11806,6 +11904,7 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
|
|
|
11806
11904
|
}, [onPopupOpenChange]);
|
|
11807
11905
|
const fileEditPending = pending && isFileEditTool(pending.tool) ? pending : null;
|
|
11808
11906
|
const planPending = !pending && pendingInteraction?.kind === "plan" ? pendingInteraction : null;
|
|
11907
|
+
const approvalPending = pending && !fileEditPending ? pending : null;
|
|
11809
11908
|
const modal = useModal();
|
|
11810
11909
|
useEffect(() => {
|
|
11811
11910
|
if (!fileEditPending) return;
|
|
@@ -11861,47 +11960,58 @@ function ChatScreen({ cwd, events, busy, compacting = false, queuedMessages = EM
|
|
|
11861
11960
|
request: fileEditPending,
|
|
11862
11961
|
onDecide: onApproval
|
|
11863
11962
|
}, fileEditPending.id),
|
|
11864
|
-
/* @__PURE__ */
|
|
11963
|
+
/* @__PURE__ */ jsxs("box", {
|
|
11865
11964
|
style: {
|
|
11866
11965
|
flexDirection: "column",
|
|
11867
11966
|
flexShrink: 0,
|
|
11868
11967
|
backgroundColor: SURFACE.background
|
|
11869
11968
|
},
|
|
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
|
|
11969
|
+
children: [
|
|
11970
|
+
approvalPending && /* @__PURE__ */ jsx$1(ApprovalBlock, {
|
|
11971
|
+
request: approvalPending,
|
|
11972
|
+
onPick: onApproval
|
|
11881
11973
|
}),
|
|
11882
|
-
/* @__PURE__ */ jsx$1(
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
|
|
11895
|
-
|
|
11896
|
-
|
|
11897
|
-
|
|
11898
|
-
|
|
11899
|
-
|
|
11900
|
-
|
|
11901
|
-
|
|
11902
|
-
|
|
11974
|
+
pendingInteraction && !approvalPending && /* @__PURE__ */ jsx$1(InteractionBlock, {
|
|
11975
|
+
request: pendingInteraction,
|
|
11976
|
+
onResolve: onInteraction
|
|
11977
|
+
}, pendingInteraction.id),
|
|
11978
|
+
/* @__PURE__ */ jsxs("box", {
|
|
11979
|
+
style: {
|
|
11980
|
+
flexDirection: "column",
|
|
11981
|
+
flexShrink: 0,
|
|
11982
|
+
visible: approvalPending == null && pendingInteraction == null
|
|
11983
|
+
},
|
|
11984
|
+
children: [
|
|
11985
|
+
queuedMessages.length > 0 && !completionPopupOpen && /* @__PURE__ */ jsx$1(QueuedMessagesBlock, {
|
|
11986
|
+
messages: queuedMessages,
|
|
11987
|
+
selectionIndex: queueSelectionIndex,
|
|
11988
|
+
shortcuts: queueShortcuts
|
|
11989
|
+
}),
|
|
11990
|
+
/* @__PURE__ */ jsx$1(ExtensionBanners, {}),
|
|
11991
|
+
/* @__PURE__ */ jsx$1(PromptBlock, {
|
|
11992
|
+
userPrompts,
|
|
11993
|
+
onSubmit,
|
|
11994
|
+
completionProviders,
|
|
11995
|
+
inlineAutocompleteProviders,
|
|
11996
|
+
onCompletionTab,
|
|
11997
|
+
enterSelectsCompletion,
|
|
11998
|
+
onPopupOpenChange: handlePopupOpenChange,
|
|
11999
|
+
searchMode: transcriptSearchMode,
|
|
12000
|
+
onSearchModeChange: onTranscriptSearchModeChange,
|
|
12001
|
+
onSearchQueryChange: onTranscriptSearchQueryChange,
|
|
12002
|
+
onSearchNavigate: onTranscriptSearchNavigate,
|
|
12003
|
+
onSearchOpen: onTranscriptSearchOpen,
|
|
12004
|
+
selectMode: queueSelectionIndex != null ? "queue" : null,
|
|
12005
|
+
triggerHints: promptTriggerHints,
|
|
12006
|
+
busy,
|
|
12007
|
+
onEnterQueueFromEmpty: onEnterQueueFromEmptyPrompt,
|
|
12008
|
+
attachImageBinding,
|
|
12009
|
+
editorBinding,
|
|
12010
|
+
suspended: approvalPending != null || pendingInteraction != null
|
|
12011
|
+
})
|
|
12012
|
+
]
|
|
11903
12013
|
})
|
|
11904
|
-
]
|
|
12014
|
+
]
|
|
11905
12015
|
}),
|
|
11906
12016
|
!planPending && !fileEditPending && /* @__PURE__ */ jsx$1(TitleOverlay, {
|
|
11907
12017
|
title: titleText,
|
|
@@ -12071,9 +12181,23 @@ function queueRowId(index) {
|
|
|
12071
12181
|
* (where committed messages live) and the prompt (where the user is
|
|
12072
12182
|
* typing the next one). Dropped entirely when the queue is empty.
|
|
12073
12183
|
*/
|
|
12184
|
+
/**
|
|
12185
|
+
* Badge text for a queued message's attachment count, or `null` when there is
|
|
12186
|
+
* nothing to show.
|
|
12187
|
+
*
|
|
12188
|
+
* Split out from the row so the logic — pluralization, and the distinction
|
|
12189
|
+
* between "no attachments" and "this preview predates the field" — is testable
|
|
12190
|
+
* without standing up `ChatScreen`'s provider stack. Padded with spaces because
|
|
12191
|
+
* it renders as a filled chip and needs breathing room inside its background.
|
|
12192
|
+
*/
|
|
12193
|
+
function attachmentBadgeLabel(count) {
|
|
12194
|
+
if (count === void 0 || count <= 0) return null;
|
|
12195
|
+
return ` +${count} ${count === 1 ? "attachment" : "attachments"} `;
|
|
12196
|
+
}
|
|
12074
12197
|
function QueuedMessagesBlock({ messages, selectionIndex, shortcuts }) {
|
|
12075
12198
|
const COLOR = useColors();
|
|
12076
12199
|
const SURFACE = useSurfaces();
|
|
12200
|
+
const attachmentChip = resolveChipColor(SURFACE.chips, "file");
|
|
12077
12201
|
const scrollboxRef = useRef(null);
|
|
12078
12202
|
const enterHintKey = shortcuts?.enter ? formatBindingForDisplay(shortcuts.enter) : "↑";
|
|
12079
12203
|
useEffect(() => {
|
|
@@ -12136,6 +12260,11 @@ function QueuedMessagesBlock({ messages, selectionIndex, shortcuts }) {
|
|
|
12136
12260
|
fg: COLOR.mute,
|
|
12137
12261
|
children: "❯ "
|
|
12138
12262
|
}),
|
|
12263
|
+
attachmentBadgeLabel(msg.attachments) !== null && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx$1("span", {
|
|
12264
|
+
fg: attachmentChip.fg,
|
|
12265
|
+
bg: attachmentChip.bg,
|
|
12266
|
+
children: attachmentBadgeLabel(msg.attachments)
|
|
12267
|
+
}), /* @__PURE__ */ jsx$1("span", { children: " " })] }),
|
|
12139
12268
|
/* @__PURE__ */ jsx$1("span", {
|
|
12140
12269
|
fg: focused ? COLOR.brand : COLOR.dim,
|
|
12141
12270
|
children: previewText(msg.text)
|
|
@@ -12225,7 +12354,7 @@ const INLINE_GHOST_DEBOUNCE_MS = 120;
|
|
|
12225
12354
|
function isTranscriptSearchExitKey(event, cursorOffset) {
|
|
12226
12355
|
return event.name === "escape" || (event.name === "backspace" || event.name === "delete") && !event.ctrl && !event.meta && !event.shift && cursorOffset === 0;
|
|
12227
12356
|
}
|
|
12228
|
-
function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocompleteProviders, onCompletionTab, enterSelectsCompletion, onPopupOpenChange, searchMode = false, onSearchModeChange, onSearchQueryChange, onSearchNavigate, onSearchOpen, selectMode = null, triggerHints, busy = false, onEnterQueueFromEmpty, attachImageBinding, editorBinding }) {
|
|
12357
|
+
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
12358
|
const focused = useModalAwareFocus();
|
|
12230
12359
|
const renderer = useRenderer();
|
|
12231
12360
|
const COLOR = useColors();
|
|
@@ -12488,30 +12617,12 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12488
12617
|
syncBuffer();
|
|
12489
12618
|
return true;
|
|
12490
12619
|
}, [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();
|
|
12620
|
+
/** Apply decoded clipboard text through the shared file/attachment/inline policy. */
|
|
12621
|
+
const applyTextPaste = useCallback((text) => {
|
|
12508
12622
|
const ta = textareaRef.current;
|
|
12509
12623
|
if (!ta) return;
|
|
12510
|
-
const normalized = stripAnsiSequences(
|
|
12511
|
-
if (normalized.length === 0)
|
|
12512
|
-
if (attachmentsEnabled) attachClipboardImage();
|
|
12513
|
-
return;
|
|
12514
|
-
}
|
|
12624
|
+
const normalized = stripAnsiSequences(text).replace(/\r\n?/g, "\n");
|
|
12625
|
+
if (normalized.length === 0) return;
|
|
12515
12626
|
if (attachmentsEnabled && !normalized.includes("\n")) {
|
|
12516
12627
|
const trimmed = normalized.trim().replace(/^["']|["']$/g, "");
|
|
12517
12628
|
let resolved = trimmed.startsWith("file://") ? decodeURIComponent(trimmed.slice(7)) : trimmed;
|
|
@@ -12535,15 +12646,12 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12535
12646
|
}
|
|
12536
12647
|
} catch {}
|
|
12537
12648
|
}
|
|
12538
|
-
const
|
|
12539
|
-
if (
|
|
12540
|
-
setAttachments((prev) => [...prev,
|
|
12541
|
-
name: "paste.txt",
|
|
12542
|
-
content: Buffer.from(normalized, "utf-8"),
|
|
12543
|
-
mediaType: "text/plain"
|
|
12544
|
-
}]);
|
|
12649
|
+
const pastePlan = planTextPaste(normalized, attachmentsEnabled);
|
|
12650
|
+
if (pastePlan.kind === "attachment") {
|
|
12651
|
+
setAttachments((prev) => [...prev, pastePlan.attachment]);
|
|
12545
12652
|
return;
|
|
12546
12653
|
}
|
|
12654
|
+
const lines = pastePlan.text.trim().split("\n");
|
|
12547
12655
|
for (let i = 0; i < lines.length; i++) {
|
|
12548
12656
|
if (i > 0) ta.newLine();
|
|
12549
12657
|
const part = lines[i];
|
|
@@ -12551,9 +12659,64 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12551
12659
|
}
|
|
12552
12660
|
syncBuffer();
|
|
12553
12661
|
}, [
|
|
12554
|
-
syncBuffer,
|
|
12555
12662
|
attachmentsEnabled,
|
|
12556
|
-
|
|
12663
|
+
showNotice,
|
|
12664
|
+
syncBuffer
|
|
12665
|
+
]);
|
|
12666
|
+
/**
|
|
12667
|
+
* Prevent the textarea's one-shot insert and explicitly preserve multiline
|
|
12668
|
+
* paste. Empty terminal paste remains the image-clipboard probe.
|
|
12669
|
+
*/
|
|
12670
|
+
const onPaste = useCallback((event) => {
|
|
12671
|
+
event.preventDefault();
|
|
12672
|
+
const raw = decodePasteBytes(event.bytes);
|
|
12673
|
+
if (stripAnsiSequences(raw).replace(/\r\n?/g, "\n").length === 0) {
|
|
12674
|
+
if (attachmentsEnabled) attachClipboardImage();
|
|
12675
|
+
return;
|
|
12676
|
+
}
|
|
12677
|
+
applyTextPaste(raw);
|
|
12678
|
+
}, [
|
|
12679
|
+
applyTextPaste,
|
|
12680
|
+
attachmentsEnabled,
|
|
12681
|
+
attachClipboardImage
|
|
12682
|
+
]);
|
|
12683
|
+
/**
|
|
12684
|
+
* Read the OS clipboard on the app-owned keybinding. Unlike terminal paste,
|
|
12685
|
+
* this moves only the shortcut bytes through the PTY, avoiding large-paste
|
|
12686
|
+
* backpressure bugs in integrated terminals. Text is re-injected through the
|
|
12687
|
+
* exact same paste handler as bracketed paste; images retain the existing
|
|
12688
|
+
* attachment behavior.
|
|
12689
|
+
*/
|
|
12690
|
+
const pasteClipboard = useCallback(async () => {
|
|
12691
|
+
const result = await clipboardReader();
|
|
12692
|
+
if (!result.ok) {
|
|
12693
|
+
showNotice(result.reason === "empty" ? "clipboard has no text or image" : result.detail ?? "could not read the clipboard");
|
|
12694
|
+
return;
|
|
12695
|
+
}
|
|
12696
|
+
if (result.kind === "text") {
|
|
12697
|
+
applyTextPaste(result.text);
|
|
12698
|
+
return;
|
|
12699
|
+
}
|
|
12700
|
+
if (!attachmentsEnabled) {
|
|
12701
|
+
showNotice("attachments are disabled — enable them in Settings → Agent");
|
|
12702
|
+
return;
|
|
12703
|
+
}
|
|
12704
|
+
if (result.data.length > MAX_IMAGE_BYTES) {
|
|
12705
|
+
showNotice(`clipboard image too large (${formatBytes(result.data.length)}) — max ${formatBytes(MAX_IMAGE_BYTES)}`);
|
|
12706
|
+
return;
|
|
12707
|
+
}
|
|
12708
|
+
setAttachments((prev) => {
|
|
12709
|
+
const n = prev.filter((a) => a.name.startsWith("clipboard-")).length + 1;
|
|
12710
|
+
return [...prev, {
|
|
12711
|
+
name: `clipboard-${n}.${extForImageMediaType(result.mediaType)}`,
|
|
12712
|
+
content: result.data,
|
|
12713
|
+
mediaType: result.mediaType
|
|
12714
|
+
}];
|
|
12715
|
+
});
|
|
12716
|
+
}, [
|
|
12717
|
+
applyTextPaste,
|
|
12718
|
+
attachmentsEnabled,
|
|
12719
|
+
clipboardReader,
|
|
12557
12720
|
showNotice
|
|
12558
12721
|
]);
|
|
12559
12722
|
/**
|
|
@@ -12620,7 +12783,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12620
12783
|
const onKeyDown = useCallback((event) => {
|
|
12621
12784
|
if (attachImageBinding && matchesBinding(event, attachImageBinding)) {
|
|
12622
12785
|
event.preventDefault();
|
|
12623
|
-
|
|
12786
|
+
pasteClipboard();
|
|
12624
12787
|
return;
|
|
12625
12788
|
}
|
|
12626
12789
|
if (editorBinding && matchesBinding(event, editorBinding)) {
|
|
@@ -12752,7 +12915,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12752
12915
|
enterSelectsCompletion,
|
|
12753
12916
|
syncBuffer,
|
|
12754
12917
|
attachImageBinding,
|
|
12755
|
-
|
|
12918
|
+
pasteClipboard,
|
|
12756
12919
|
editorBinding,
|
|
12757
12920
|
openExternalEditor,
|
|
12758
12921
|
attachments,
|
|
@@ -12848,7 +13011,7 @@ function PromptBlock({ userPrompts, onSubmit, completionProviders, inlineAutocom
|
|
|
12848
13011
|
},
|
|
12849
13012
|
children: [/* @__PURE__ */ jsx$1("textarea", {
|
|
12850
13013
|
ref: textareaRef,
|
|
12851
|
-
focused: focused && !selectMode,
|
|
13014
|
+
focused: focused && !selectMode && !suspended,
|
|
12852
13015
|
keyBindings: TEXTAREA_BINDINGS,
|
|
12853
13016
|
wrapMode: "word",
|
|
12854
13017
|
placeholder: selectMode === "queue" ? "— queue-select mode — press ⎋ to resume typing —" : searchMode ? "Search this conversation…" : shellMode ? "Run a shell command…" : "Ask zidane…",
|
|
@@ -18496,7 +18659,109 @@ function restoreTerminalTitle(out = process.stdout) {
|
|
|
18496
18659
|
} catch {}
|
|
18497
18660
|
}
|
|
18498
18661
|
//#endregion
|
|
18662
|
+
//#region src/chat/todo-edits.ts
|
|
18663
|
+
/**
|
|
18664
|
+
* Prefix for ids minted here, distinguishing user-authored rows from the
|
|
18665
|
+
* model's own ids in a session file. Purely diagnostic — nothing branches on
|
|
18666
|
+
* it — but it makes "who added this" answerable after the fact.
|
|
18667
|
+
*/
|
|
18668
|
+
const USER_TODO_ID_PREFIX = "user-";
|
|
18669
|
+
/**
|
|
18670
|
+
* Mint an id that cannot collide with an existing row.
|
|
18671
|
+
*
|
|
18672
|
+
* Counter-based rather than random: a stable, readable id keeps session diffs
|
|
18673
|
+
* legible, and the collision check makes it safe against ids the model already
|
|
18674
|
+
* used (including previous `user-*` ones after a reload).
|
|
18675
|
+
*/
|
|
18676
|
+
function nextTodoId(items) {
|
|
18677
|
+
const taken = new Set(items.map((i) => i.id));
|
|
18678
|
+
for (let n = 1;; n++) {
|
|
18679
|
+
const id = `${USER_TODO_ID_PREFIX}${n}`;
|
|
18680
|
+
if (!taken.has(id)) return id;
|
|
18681
|
+
}
|
|
18682
|
+
}
|
|
18683
|
+
/**
|
|
18684
|
+
* Append a todo. Returns the list unchanged when `content` is blank — an empty
|
|
18685
|
+
* row is never what the user meant, and rejecting it here means every caller
|
|
18686
|
+
* (keyboard, click, future GUI) gets the same guard.
|
|
18687
|
+
*/
|
|
18688
|
+
function addTodo(items, content) {
|
|
18689
|
+
const trimmed = content.trim();
|
|
18690
|
+
if (!trimmed) return [...items];
|
|
18691
|
+
return [...items, {
|
|
18692
|
+
id: nextTodoId(items),
|
|
18693
|
+
content: trimmed,
|
|
18694
|
+
status: "pending"
|
|
18695
|
+
}];
|
|
18696
|
+
}
|
|
18697
|
+
/**
|
|
18698
|
+
* Replace one todo's text. Blank content is rejected (see {@link addTodo}); an
|
|
18699
|
+
* unknown id is a no-op rather than an error, because the list can be replaced
|
|
18700
|
+
* by a `todowrite` between render and keypress.
|
|
18701
|
+
*/
|
|
18702
|
+
function editTodo(items, id, content) {
|
|
18703
|
+
const trimmed = content.trim();
|
|
18704
|
+
if (!trimmed) return [...items];
|
|
18705
|
+
return items.map((item) => item.id === id ? {
|
|
18706
|
+
...item,
|
|
18707
|
+
content: trimmed
|
|
18708
|
+
} : item);
|
|
18709
|
+
}
|
|
18710
|
+
/** Drop one todo. Unknown id is a no-op, same rationale as {@link editTodo}. */
|
|
18711
|
+
function removeTodo(items, id) {
|
|
18712
|
+
return items.filter((item) => item.id !== id);
|
|
18713
|
+
}
|
|
18714
|
+
/** Drop everything. Separate from `removeTodo` so the caller's intent is explicit at the call site. */
|
|
18715
|
+
function clearTodos() {
|
|
18716
|
+
return [];
|
|
18717
|
+
}
|
|
18718
|
+
/**
|
|
18719
|
+
* Advance a todo's status one step: pending → in_progress → completed →
|
|
18720
|
+
* pending. `cancelled` also lands on `pending`, so a dropped item can be
|
|
18721
|
+
* revived without a dedicated key.
|
|
18722
|
+
*
|
|
18723
|
+
* Cycling INTO `in_progress` demotes any other in-progress row to `pending`:
|
|
18724
|
+
* the list's whole purpose is "what is being worked on right now", and two
|
|
18725
|
+
* live rows make that unanswerable — `TodoIndicator`'s successor
|
|
18726
|
+
* (`todoProgressLabel`) reads the first one and would silently ignore the rest.
|
|
18727
|
+
*/
|
|
18728
|
+
function cycleTodoStatus(items, id) {
|
|
18729
|
+
const target = items.find((item) => item.id === id);
|
|
18730
|
+
if (!target) return [...items];
|
|
18731
|
+
const next = nextStatus(target.status);
|
|
18732
|
+
return items.map((item) => {
|
|
18733
|
+
if (item.id === id) return {
|
|
18734
|
+
...item,
|
|
18735
|
+
status: next
|
|
18736
|
+
};
|
|
18737
|
+
if (next === "in_progress" && item.status === "in_progress") return {
|
|
18738
|
+
...item,
|
|
18739
|
+
status: "pending"
|
|
18740
|
+
};
|
|
18741
|
+
return item;
|
|
18742
|
+
});
|
|
18743
|
+
}
|
|
18744
|
+
function nextStatus(status) {
|
|
18745
|
+
switch (status) {
|
|
18746
|
+
case "pending": return "in_progress";
|
|
18747
|
+
case "in_progress": return "completed";
|
|
18748
|
+
default: return "pending";
|
|
18749
|
+
}
|
|
18750
|
+
}
|
|
18751
|
+
//#endregion
|
|
18499
18752
|
//#region src/tui/todos-modal.tsx
|
|
18753
|
+
/**
|
|
18754
|
+
* Steering text for a "retry this todo" request.
|
|
18755
|
+
*
|
|
18756
|
+
* Quotes the content so a model reading it can match the row verbatim against
|
|
18757
|
+
* its own list, and names the status because "retry" means different things
|
|
18758
|
+
* for a row it believes is `completed` versus one it abandoned as `cancelled`.
|
|
18759
|
+
* Pure + exported so the wording is pinned by a test rather than living only
|
|
18760
|
+
* inside a JSX callback.
|
|
18761
|
+
*/
|
|
18762
|
+
function buildTodoRetryMessage(item) {
|
|
18763
|
+
return `Retry this todo (currently ${item.status}): "${item.content}"`;
|
|
18764
|
+
}
|
|
18500
18765
|
/** Floor on the modal height — keeps the header + at least a few rows visible on tiny terminals. */
|
|
18501
18766
|
const MIN_MODAL_HEIGHT = 12;
|
|
18502
18767
|
/** Ceiling on the modal height — prevents a giant list from blanket-filling a tall window. */
|
|
@@ -18534,11 +18799,16 @@ function statusColors(status, COLOR) {
|
|
|
18534
18799
|
};
|
|
18535
18800
|
}
|
|
18536
18801
|
}
|
|
18537
|
-
function TodoRow({ item, rowId }) {
|
|
18802
|
+
function TodoRow({ item, rowId, focused, onClick }) {
|
|
18538
18803
|
const COLOR = useColors();
|
|
18539
18804
|
const colors = statusColors(item.status, COLOR);
|
|
18540
18805
|
return /* @__PURE__ */ jsx$1("box", {
|
|
18541
18806
|
id: rowId,
|
|
18807
|
+
onMouseDown: onClick && ((event) => {
|
|
18808
|
+
event.stopPropagation();
|
|
18809
|
+
event.preventDefault();
|
|
18810
|
+
onClick();
|
|
18811
|
+
}),
|
|
18542
18812
|
style: {
|
|
18543
18813
|
flexShrink: 0,
|
|
18544
18814
|
flexDirection: "row"
|
|
@@ -18546,6 +18816,10 @@ function TodoRow({ item, rowId }) {
|
|
|
18546
18816
|
children: /* @__PURE__ */ jsxs("text", {
|
|
18547
18817
|
wrapMode: "word",
|
|
18548
18818
|
children: [
|
|
18819
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
18820
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
18821
|
+
children: focused ? "▶ " : " "
|
|
18822
|
+
}),
|
|
18549
18823
|
/* @__PURE__ */ jsx$1("span", {
|
|
18550
18824
|
fg: colors.glyph,
|
|
18551
18825
|
children: TODO_STATUS_GLYPHS[item.status]
|
|
@@ -18562,10 +18836,14 @@ function TodoRow({ item, rowId }) {
|
|
|
18562
18836
|
})
|
|
18563
18837
|
});
|
|
18564
18838
|
}
|
|
18565
|
-
function TodosModal({ session, agent }) {
|
|
18839
|
+
function TodosModal({ session, agent, onSteer }) {
|
|
18566
18840
|
const COLOR = useColors();
|
|
18841
|
+
const modal = useModal();
|
|
18567
18842
|
const { height: termHeight } = useTerminalDimensions();
|
|
18568
18843
|
const [, setTick] = useState(0);
|
|
18844
|
+
const [cursorRaw, setCursorRaw] = useState(0);
|
|
18845
|
+
const [mode, setMode] = useState("list");
|
|
18846
|
+
const [draft, setDraft] = useState("");
|
|
18569
18847
|
useEffect(() => {
|
|
18570
18848
|
if (!agent) return;
|
|
18571
18849
|
return agent.hooks.hook("tool:after", (ctx) => {
|
|
@@ -18588,46 +18866,213 @@ function TodosModal({ session, agent }) {
|
|
|
18588
18866
|
const maxHeight = Math.max(MIN_MODAL_HEIGHT, Math.min(MAX_MODAL_HEIGHT$1, idealHeight));
|
|
18589
18867
|
const display = state.todos.length > 0 ? state.todos : state.archive;
|
|
18590
18868
|
const total = display.length;
|
|
18591
|
-
|
|
18869
|
+
const cursor = total === 0 ? 0 : Math.min(cursorRaw, total - 1);
|
|
18870
|
+
const focusedItem = display[cursor] ?? null;
|
|
18871
|
+
/**
|
|
18872
|
+
* Write a transformed list back to the run.
|
|
18873
|
+
*
|
|
18874
|
+
* Editing while the ARCHIVE is on screen promotes it back to the live slot,
|
|
18875
|
+
* which is the only coherent option: `setTodosForRun` owns the live slot, and
|
|
18876
|
+
* an edit the user just made must be the list that's displayed next.
|
|
18877
|
+
*/
|
|
18878
|
+
const commit = useCallback((next) => {
|
|
18879
|
+
if (!session || !state.runId) return;
|
|
18880
|
+
setTodosForRun(session, state.runId, next, { clearArchive: true });
|
|
18881
|
+
setTick((t) => t + 1);
|
|
18882
|
+
}, [session, state.runId]);
|
|
18883
|
+
const editable = session !== null && state.runId !== null;
|
|
18884
|
+
/** Enter add / edit mode, seeding the draft from the focused row for edits. */
|
|
18885
|
+
const beginEdit = useCallback((next) => {
|
|
18886
|
+
setDraft(next === "edit" ? focusedItem?.content ?? "" : "");
|
|
18887
|
+
setMode(next);
|
|
18888
|
+
}, [focusedItem]);
|
|
18889
|
+
useKeyboard((key) => {
|
|
18890
|
+
if (!editable) return;
|
|
18891
|
+
if (mode === "add" || mode === "edit") {
|
|
18892
|
+
if (key.name === "escape") {
|
|
18893
|
+
setDraft("");
|
|
18894
|
+
setMode("list");
|
|
18895
|
+
}
|
|
18896
|
+
return;
|
|
18897
|
+
}
|
|
18898
|
+
if (mode === "confirm-clear") {
|
|
18899
|
+
if (key.name === "y") {
|
|
18900
|
+
commit(clearTodos());
|
|
18901
|
+
setMode("list");
|
|
18902
|
+
} else if (key.name === "n" || key.name === "escape") setMode("list");
|
|
18903
|
+
return;
|
|
18904
|
+
}
|
|
18905
|
+
if (key.name === "up") {
|
|
18906
|
+
setCursorRaw((c) => Math.max(0, c - 1));
|
|
18907
|
+
return;
|
|
18908
|
+
}
|
|
18909
|
+
if (key.name === "down") {
|
|
18910
|
+
setCursorRaw((c) => Math.min(Math.max(0, total - 1), c + 1));
|
|
18911
|
+
return;
|
|
18912
|
+
}
|
|
18913
|
+
if (key.name === "a") {
|
|
18914
|
+
beginEdit("add");
|
|
18915
|
+
return;
|
|
18916
|
+
}
|
|
18917
|
+
if (key.ctrl && key.name === "d") {
|
|
18918
|
+
if (total > 0) setMode("confirm-clear");
|
|
18919
|
+
return;
|
|
18920
|
+
}
|
|
18921
|
+
if (key.name === "d") {
|
|
18922
|
+
if (focusedItem) commit(removeTodo(display, focusedItem.id));
|
|
18923
|
+
return;
|
|
18924
|
+
}
|
|
18925
|
+
if (key.name === "e" || key.name === "return") {
|
|
18926
|
+
if (focusedItem) beginEdit("edit");
|
|
18927
|
+
return;
|
|
18928
|
+
}
|
|
18929
|
+
if (key.name === "space") {
|
|
18930
|
+
if (focusedItem) commit(cycleTodoStatus(display, focusedItem.id));
|
|
18931
|
+
return;
|
|
18932
|
+
}
|
|
18933
|
+
if (key.name === "r") {
|
|
18934
|
+
if (focusedItem && onSteer) {
|
|
18935
|
+
onSteer(buildTodoRetryMessage(focusedItem));
|
|
18936
|
+
modal.close();
|
|
18937
|
+
}
|
|
18938
|
+
}
|
|
18939
|
+
});
|
|
18940
|
+
const submitInput = useCallback(() => {
|
|
18941
|
+
const value = draft.trim();
|
|
18942
|
+
if (value) {
|
|
18943
|
+
if (mode === "add") commit(addTodo(display, value));
|
|
18944
|
+
else if (mode === "edit" && focusedItem) commit(editTodo(display, focusedItem.id, value));
|
|
18945
|
+
}
|
|
18946
|
+
setDraft("");
|
|
18947
|
+
setMode("list");
|
|
18948
|
+
}, [
|
|
18949
|
+
draft,
|
|
18950
|
+
mode,
|
|
18951
|
+
commit,
|
|
18952
|
+
display,
|
|
18953
|
+
focusedItem
|
|
18954
|
+
]);
|
|
18955
|
+
return /* @__PURE__ */ jsxs(Modal, {
|
|
18592
18956
|
title: "todos",
|
|
18593
18957
|
bottomTitle: total > 0 ? `${total} item${total === 1 ? "" : "s"} · esc close` : "esc close",
|
|
18594
18958
|
rightTitle: display.length > 0 ? /* @__PURE__ */ jsx$1(CountsBadge, { items: display }) : null,
|
|
18595
18959
|
maxWidth: 100,
|
|
18596
18960
|
maxHeight,
|
|
18597
|
-
|
|
18598
|
-
|
|
18599
|
-
|
|
18600
|
-
|
|
18961
|
+
disableEscape: mode !== "list",
|
|
18962
|
+
children: [
|
|
18963
|
+
total === 0 ? /* @__PURE__ */ jsxs("text", { children: [
|
|
18964
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
18965
|
+
fg: COLOR.mute,
|
|
18966
|
+
children: "(no todos yet — the agent hasn't called "
|
|
18967
|
+
}),
|
|
18968
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
18969
|
+
fg: COLOR.warn,
|
|
18970
|
+
children: "todowrite"
|
|
18971
|
+
}),
|
|
18972
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
18973
|
+
fg: COLOR.mute,
|
|
18974
|
+
children: ")"
|
|
18975
|
+
})
|
|
18976
|
+
] }) : /* @__PURE__ */ jsx$1("box", {
|
|
18977
|
+
style: {
|
|
18978
|
+
flexDirection: "column",
|
|
18979
|
+
flexGrow: 1,
|
|
18980
|
+
flexShrink: 1,
|
|
18981
|
+
overflow: "hidden"
|
|
18982
|
+
},
|
|
18983
|
+
children: /* @__PURE__ */ jsx$1("scrollbox", {
|
|
18984
|
+
ref: scrollRef,
|
|
18985
|
+
focusable: false,
|
|
18986
|
+
stickyScroll: false,
|
|
18987
|
+
style: {
|
|
18988
|
+
flexGrow: 1,
|
|
18989
|
+
flexShrink: 1
|
|
18990
|
+
},
|
|
18991
|
+
children: display.map((item, i) => /* @__PURE__ */ jsx$1(TodoRow, {
|
|
18992
|
+
item,
|
|
18993
|
+
rowId: `todo-row-${item.id}`,
|
|
18994
|
+
focused: editable && i === cursor,
|
|
18995
|
+
onClick: editable ? () => setCursorRaw(i) : void 0
|
|
18996
|
+
}, item.id))
|
|
18997
|
+
})
|
|
18601
18998
|
}),
|
|
18602
|
-
/* @__PURE__ */
|
|
18603
|
-
|
|
18604
|
-
|
|
18999
|
+
editable && (mode === "add" || mode === "edit") && /* @__PURE__ */ jsxs("box", {
|
|
19000
|
+
style: {
|
|
19001
|
+
flexShrink: 0,
|
|
19002
|
+
flexDirection: "row",
|
|
19003
|
+
paddingTop: 1
|
|
19004
|
+
},
|
|
19005
|
+
children: [/* @__PURE__ */ jsx$1("text", {
|
|
19006
|
+
wrapMode: "none",
|
|
19007
|
+
children: /* @__PURE__ */ jsx$1("span", {
|
|
19008
|
+
fg: COLOR.warn,
|
|
19009
|
+
children: mode === "add" ? "+ " : "✎ "
|
|
19010
|
+
})
|
|
19011
|
+
}), /* @__PURE__ */ jsx$1("input", {
|
|
19012
|
+
focused: true,
|
|
19013
|
+
value: draft,
|
|
19014
|
+
placeholder: mode === "add" ? "new todo…" : "edit todo…",
|
|
19015
|
+
onInput: setDraft,
|
|
19016
|
+
onSubmit: () => submitInput(),
|
|
19017
|
+
style: { flexGrow: 1 }
|
|
19018
|
+
})]
|
|
18605
19019
|
}),
|
|
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,
|
|
19020
|
+
editable && mode === "confirm-clear" && /* @__PURE__ */ jsxs("box", {
|
|
18621
19021
|
style: {
|
|
18622
|
-
|
|
18623
|
-
|
|
19022
|
+
flexShrink: 0,
|
|
19023
|
+
flexDirection: "column",
|
|
19024
|
+
paddingTop: 1
|
|
19025
|
+
},
|
|
19026
|
+
children: [/* @__PURE__ */ jsx$1("text", {
|
|
19027
|
+
wrapMode: "none",
|
|
19028
|
+
children: /* @__PURE__ */ jsx$1("span", {
|
|
19029
|
+
fg: COLOR.error,
|
|
19030
|
+
children: `delete all ${total} todo${total === 1 ? "" : "s"}?`
|
|
19031
|
+
})
|
|
19032
|
+
}), /* @__PURE__ */ jsx$1(ModalHints, { hints: [{
|
|
19033
|
+
key: "y",
|
|
19034
|
+
label: "yes",
|
|
19035
|
+
keyColor: COLOR.error
|
|
19036
|
+
}, {
|
|
19037
|
+
key: "n",
|
|
19038
|
+
label: "cancel"
|
|
19039
|
+
}] })]
|
|
19040
|
+
}),
|
|
19041
|
+
editable && mode === "list" && /* @__PURE__ */ jsx$1("box", {
|
|
19042
|
+
style: {
|
|
19043
|
+
flexShrink: 0,
|
|
19044
|
+
flexDirection: "column",
|
|
19045
|
+
paddingTop: 1
|
|
18624
19046
|
},
|
|
18625
|
-
children:
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
19047
|
+
children: /* @__PURE__ */ jsx$1(ModalHints, { hints: [
|
|
19048
|
+
{
|
|
19049
|
+
key: "a",
|
|
19050
|
+
label: "add"
|
|
19051
|
+
},
|
|
19052
|
+
total > 0 && {
|
|
19053
|
+
key: "e",
|
|
19054
|
+
label: "edit"
|
|
19055
|
+
},
|
|
19056
|
+
total > 0 && {
|
|
19057
|
+
key: "space",
|
|
19058
|
+
label: "status"
|
|
19059
|
+
},
|
|
19060
|
+
total > 0 && {
|
|
19061
|
+
key: "d",
|
|
19062
|
+
label: "remove"
|
|
19063
|
+
},
|
|
19064
|
+
total > 0 && onSteer !== void 0 && {
|
|
19065
|
+
key: "r",
|
|
19066
|
+
label: "retry"
|
|
19067
|
+
},
|
|
19068
|
+
total > 0 && {
|
|
19069
|
+
key: "ctrl+d",
|
|
19070
|
+
label: "delete all",
|
|
19071
|
+
keyColor: COLOR.error
|
|
19072
|
+
}
|
|
19073
|
+
] })
|
|
18629
19074
|
})
|
|
18630
|
-
|
|
19075
|
+
]
|
|
18631
19076
|
});
|
|
18632
19077
|
}
|
|
18633
19078
|
/**
|
|
@@ -19560,10 +20005,19 @@ function AppShell() {
|
|
|
19560
20005
|
useEffect(() => {
|
|
19561
20006
|
persistToolResultsRef.current = settings.persistToolResults;
|
|
19562
20007
|
}, [settings.persistToolResults]);
|
|
19563
|
-
const
|
|
20008
|
+
const interactionModeRef = useRef(settings.interactionMode);
|
|
19564
20009
|
useEffect(() => {
|
|
19565
|
-
|
|
19566
|
-
}, [settings.
|
|
20010
|
+
interactionModeRef.current = settings.interactionMode;
|
|
20011
|
+
}, [settings.interactionMode]);
|
|
20012
|
+
useEffect(() => {
|
|
20013
|
+
if (settings.interactionMode !== "skip" || !pendingInteraction) return;
|
|
20014
|
+
if (!isModelInteraction(pendingInteraction)) return;
|
|
20015
|
+
interactions.resolveHead(createAutomaticInteractionResponse(pendingInteraction));
|
|
20016
|
+
}, [
|
|
20017
|
+
interactions,
|
|
20018
|
+
pendingInteraction,
|
|
20019
|
+
settings.interactionMode
|
|
20020
|
+
]);
|
|
19567
20021
|
const userInstructionsScopeRef = useRef(settings.userInstructionsScope);
|
|
19568
20022
|
useEffect(() => {
|
|
19569
20023
|
userInstructionsScopeRef.current = settings.userInstructionsScope;
|
|
@@ -20470,7 +20924,7 @@ function AppShell() {
|
|
|
20470
20924
|
const profile = pickedAgentRef.current;
|
|
20471
20925
|
if (profile.id !== "build" && profile.id !== "plan") return;
|
|
20472
20926
|
return agent.hooks.hook("system:transform", (ctx) => {
|
|
20473
|
-
const allowInteraction =
|
|
20927
|
+
const allowInteraction = interactionModeRef.current !== "disabled";
|
|
20474
20928
|
const envOpts = {
|
|
20475
20929
|
cwd,
|
|
20476
20930
|
...projectDir !== cwd ? { projectRoot: projectDir } : {},
|
|
@@ -20814,8 +21268,11 @@ function AppShell() {
|
|
|
20814
21268
|
enabled: enabledMcpsRef.current,
|
|
20815
21269
|
...disabledMcpToolsRef.current ? { disabledTools: disabledMcpToolsRef.current } : {}
|
|
20816
21270
|
}), ...pluginContributions.mcpServers];
|
|
20817
|
-
const allowInteraction =
|
|
20818
|
-
const interactionTools = allowInteraction ? createInteractionTools({
|
|
21271
|
+
const allowInteraction = interactionModeRef.current !== "disabled";
|
|
21272
|
+
const interactionTools = allowInteraction ? createInteractionTools({
|
|
21273
|
+
requestInteraction: makeRequestInteraction(interactions),
|
|
21274
|
+
shouldSkipInteractions: () => interactionModeRef.current === "skip"
|
|
21275
|
+
}) : {};
|
|
20819
21276
|
const actualCwd = cwdRef.current;
|
|
20820
21277
|
const { agentsMd, systemFile, system: builtInSystem } = composeHostContext(profile, allowInteraction);
|
|
20821
21278
|
sessionInstructionFilesRef.current = agentsMd.files;
|
|
@@ -21716,6 +22173,35 @@ function AppShell() {
|
|
|
21716
22173
|
interactions
|
|
21717
22174
|
]);
|
|
21718
22175
|
/**
|
|
22176
|
+
* Persist the matched `tool_result` turn for a resolved batch WITHOUT
|
|
22177
|
+
* running. Automatic mode uses this on activation so opening an old
|
|
22178
|
+
* session repairs its history (every `tool_use` matched) instead of
|
|
22179
|
+
* silently starting a billed run nobody asked for; the next prompt
|
|
22180
|
+
* continues normally.
|
|
22181
|
+
*/
|
|
22182
|
+
const persistResumedInteractions = useCallback(async (requests, responses) => {
|
|
22183
|
+
const session = sessionRef.current;
|
|
22184
|
+
if (!session) {
|
|
22185
|
+
debugLog("resume-interaction: missing session", { count: requests.length });
|
|
22186
|
+
return false;
|
|
22187
|
+
}
|
|
22188
|
+
const turnId = await session.generateTurnId();
|
|
22189
|
+
const runId = requests[0]?.runId;
|
|
22190
|
+
let turn;
|
|
22191
|
+
try {
|
|
22192
|
+
turn = buildResumedToolResultsTurn(requests, responses, {
|
|
22193
|
+
turnId,
|
|
22194
|
+
runId
|
|
22195
|
+
});
|
|
22196
|
+
} catch (err) {
|
|
22197
|
+
debugLog("resume-interaction: turn build failed", err);
|
|
22198
|
+
return false;
|
|
22199
|
+
}
|
|
22200
|
+
await session.appendTurns([turn]);
|
|
22201
|
+
if (sessionRef.current === session) setEvents(capTranscriptEvents(eventsFromTurns(session.turns, session.runs)));
|
|
22202
|
+
return true;
|
|
22203
|
+
}, []);
|
|
22204
|
+
/**
|
|
21719
22205
|
* Continue a session from where pending interactions left it. Appends
|
|
21720
22206
|
* a single user turn containing one `tool_result` per pending request
|
|
21721
22207
|
* (in pending order) and triggers a prompt-less `agent.run()` so the
|
|
@@ -21830,7 +22316,12 @@ function AppShell() {
|
|
|
21830
22316
|
const pending = pendingInteractionsFromTurns(session.turns);
|
|
21831
22317
|
if (pending.length > 0) {
|
|
21832
22318
|
const collected = /* @__PURE__ */ new Map();
|
|
22319
|
+
const automatic = interactionModeRef.current === "skip";
|
|
21833
22320
|
for (const request of pending) {
|
|
22321
|
+
if (automatic) {
|
|
22322
|
+
collected.set(request.id, createAutomaticInteractionResponse(request));
|
|
22323
|
+
continue;
|
|
22324
|
+
}
|
|
21834
22325
|
const entry = {
|
|
21835
22326
|
request,
|
|
21836
22327
|
resolve: (response) => {
|
|
@@ -21841,6 +22332,7 @@ function AppShell() {
|
|
|
21841
22332
|
};
|
|
21842
22333
|
interactions.enqueue(entry);
|
|
21843
22334
|
}
|
|
22335
|
+
if (automatic) persistResumedInteractions(pending, collected).catch((err) => debugLog("resume-interaction: automatic persist failed", err));
|
|
21844
22336
|
}
|
|
21845
22337
|
}, [
|
|
21846
22338
|
teardown,
|
|
@@ -21850,6 +22342,7 @@ function AppShell() {
|
|
|
21850
22342
|
projectDir,
|
|
21851
22343
|
interactions,
|
|
21852
22344
|
continueResumedInteractions,
|
|
22345
|
+
persistResumedInteractions,
|
|
21853
22346
|
extensionRegistry
|
|
21854
22347
|
]);
|
|
21855
22348
|
/** Ref mirror — same rationale as `refreshSessionsRef` above. */
|
|
@@ -22890,7 +23383,8 @@ function AppShell() {
|
|
|
22890
23383
|
interaction: {
|
|
22891
23384
|
confirm: async (prompt, confirmOpts) => {
|
|
22892
23385
|
const response = await requestCommandInteraction({
|
|
22893
|
-
id:
|
|
23386
|
+
id: `${HOST_INTERACTION_ID_PREFIX}${crypto.randomUUID()}`,
|
|
23387
|
+
origin: "host",
|
|
22894
23388
|
kind: "question",
|
|
22895
23389
|
tool: ASK_USER_TOOL,
|
|
22896
23390
|
createdAt: Date.now(),
|
|
@@ -22910,7 +23404,8 @@ function AppShell() {
|
|
|
22910
23404
|
},
|
|
22911
23405
|
askUser: async (form) => {
|
|
22912
23406
|
const response = await requestCommandInteraction({
|
|
22913
|
-
id:
|
|
23407
|
+
id: `${HOST_INTERACTION_ID_PREFIX}${crypto.randomUUID()}`,
|
|
23408
|
+
origin: "host",
|
|
22914
23409
|
kind: "question",
|
|
22915
23410
|
tool: ASK_USER_TOOL,
|
|
22916
23411
|
createdAt: Date.now(),
|
|
@@ -22921,7 +23416,8 @@ function AppShell() {
|
|
|
22921
23416
|
},
|
|
22922
23417
|
presentPlan: async (plan) => {
|
|
22923
23418
|
const response = await requestCommandInteraction({
|
|
22924
|
-
id:
|
|
23419
|
+
id: `${HOST_INTERACTION_ID_PREFIX}${crypto.randomUUID()}`,
|
|
23420
|
+
origin: "host",
|
|
22925
23421
|
kind: "plan",
|
|
22926
23422
|
tool: PRESENT_PLAN_TOOL,
|
|
22927
23423
|
createdAt: Date.now(),
|
|
@@ -23651,7 +24147,7 @@ function AppShell() {
|
|
|
23651
24147
|
})();
|
|
23652
24148
|
}, [modal, config.paths.userDir]);
|
|
23653
24149
|
const onReadContextSegments = useCallback(async () => {
|
|
23654
|
-
const { shadowedSystemFiles, supportsOverride } = composeHostContext(pickedAgentRef.current,
|
|
24150
|
+
const { shadowedSystemFiles, supportsOverride } = composeHostContext(pickedAgentRef.current, interactionModeRef.current !== "disabled");
|
|
23655
24151
|
const shadowedSystemPath = shadowedSystemFiles[0]?.path;
|
|
23656
24152
|
const scopeLoadsUserOverride = userScopeOverridesLoad(userInstructionsScopeRef.current);
|
|
23657
24153
|
const canOverrideSystem = supportsOverride && scopeLoadsUserOverride;
|
|
@@ -23668,7 +24164,7 @@ function AppShell() {
|
|
|
23668
24164
|
...overrideBlockedBy ? { overrideBlockedBy } : {},
|
|
23669
24165
|
...shadowedSystemPath ? { shadowedSystemPath } : {}
|
|
23670
24166
|
};
|
|
23671
|
-
const { agentsMd, systemFile, system } = composeHostContext(pickedAgentRef.current,
|
|
24167
|
+
const { agentsMd, systemFile, system } = composeHostContext(pickedAgentRef.current, interactionModeRef.current !== "disabled");
|
|
23672
24168
|
const enabled = enabledSkillsRef.current;
|
|
23673
24169
|
const sessionSkills = enabled === void 0 ? [...skillsCatalogRef.current] : skillsCatalogRef.current.filter((s) => enabled.includes(s.name));
|
|
23674
24170
|
return {
|
|
@@ -23705,7 +24201,7 @@ function AppShell() {
|
|
|
23705
24201
|
const onCreateSystemPrompt = useCallback(async () => {
|
|
23706
24202
|
const path = userSystemMdPath({ prefix: config.prefix });
|
|
23707
24203
|
if (!existsSync(path)) {
|
|
23708
|
-
const { doctrineOnlySystem, supportsOverride } = composeHostContext(pickedAgentRef.current,
|
|
24204
|
+
const { doctrineOnlySystem, supportsOverride } = composeHostContext(pickedAgentRef.current, interactionModeRef.current !== "disabled");
|
|
23709
24205
|
if (!supportsOverride) throw new Error(`the "${pickedAgentRef.current.id}" profile supplies its own system prompt, so a SYSTEM.md override would not apply`);
|
|
23710
24206
|
if (!userScopeOverridesLoad(userInstructionsScopeRef.current)) throw new Error(`userInstructionsScope is "${userInstructionsScopeRef.current}", so a SYSTEM.md in your home directory would not be loaded`);
|
|
23711
24207
|
const doctrine = splitSystemPrompt(doctrineOnlySystem() ?? "").static;
|
|
@@ -24507,7 +25003,8 @@ function AppShell() {
|
|
|
24507
25003
|
if (matchesBinding(key, keybindings.openTodos) && screen === "chat" && currentSession) {
|
|
24508
25004
|
modal.open(/* @__PURE__ */ jsx$1(TodosModal, {
|
|
24509
25005
|
session: sessionRef.current,
|
|
24510
|
-
agent: agentRef.current
|
|
25006
|
+
agent: agentRef.current,
|
|
25007
|
+
onSteer: (message) => onSubmitPrompt(message, [], [])
|
|
24511
25008
|
}));
|
|
24512
25009
|
return;
|
|
24513
25010
|
}
|
|
@@ -24695,6 +25192,7 @@ function AppShell() {
|
|
|
24695
25192
|
COLOR.warn,
|
|
24696
25193
|
COLOR.dim
|
|
24697
25194
|
]);
|
|
25195
|
+
const todoProgress = todoProgressLabel(useActiveTodos(sessionRef.current));
|
|
24698
25196
|
const hints = useMemo(() => {
|
|
24699
25197
|
const base = buildHints({
|
|
24700
25198
|
screen,
|
|
@@ -24720,6 +25218,7 @@ function AppShell() {
|
|
|
24720
25218
|
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
25219
|
activeSkillCount: activeSkillNames.size,
|
|
24722
25220
|
skillsChipColor: COLOR.brand,
|
|
25221
|
+
todoProgress: settings.showTodoIndicator ? todoProgress : null,
|
|
24723
25222
|
updateHint,
|
|
24724
25223
|
cacheBreakHint,
|
|
24725
25224
|
sessionsScopeAll: sessionsShowAll
|
|
@@ -24741,6 +25240,8 @@ function AppShell() {
|
|
|
24741
25240
|
currentSession,
|
|
24742
25241
|
hasMultipleAgents,
|
|
24743
25242
|
settings.uiMode,
|
|
25243
|
+
settings.showTodoIndicator,
|
|
25244
|
+
todoProgress,
|
|
24744
25245
|
picked,
|
|
24745
25246
|
pickedAgent,
|
|
24746
25247
|
COLOR,
|
|
@@ -24762,7 +25263,8 @@ function AppShell() {
|
|
|
24762
25263
|
start: r.start,
|
|
24763
25264
|
end: r.end,
|
|
24764
25265
|
providerId: r.providerId
|
|
24765
|
-
}))
|
|
25266
|
+
})),
|
|
25267
|
+
attachments: m.attachments.length
|
|
24766
25268
|
})), [messageQueue]);
|
|
24767
25269
|
const queueShortcuts = useMemo(() => ({
|
|
24768
25270
|
enter: keybindings.enterQueueSelection,
|
|
@@ -24853,7 +25355,6 @@ function AppShell() {
|
|
|
24853
25355
|
settings,
|
|
24854
25356
|
onSubmit: onSubmitPrompt,
|
|
24855
25357
|
session: currentSession,
|
|
24856
|
-
liveSession: sessionRef.current,
|
|
24857
25358
|
pending: pendingApproval,
|
|
24858
25359
|
onApproval: resolveHead,
|
|
24859
25360
|
pendingInteraction,
|