@code-yeongyu/senpi 2026.9.12-3 → 2026.9.13-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +3 -1
- package/dist/bun/cli.js +2 -3
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/runtime-modules.d.ts +3 -0
- package/dist/bun/runtime-modules.d.ts.map +1 -0
- package/dist/bun/runtime-modules.js +17 -0
- package/dist/bun/runtime-modules.js.map +1 -0
- package/dist/bun/runtime-setup.js +2 -5
- package/dist/bun/runtime-setup.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +38 -30
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/discovered-resource-scope.d.ts +23 -0
- package/dist/core/discovered-resource-scope.d.ts.map +1 -0
- package/dist/core/discovered-resource-scope.js +44 -0
- package/dist/core/discovered-resource-scope.js.map +1 -0
- package/dist/core/extensions/builtin/ask-user/notify.d.ts +24 -0
- package/dist/core/extensions/builtin/ask-user/notify.d.ts.map +1 -0
- package/dist/core/extensions/builtin/ask-user/notify.js +11 -0
- package/dist/core/extensions/builtin/ask-user/notify.js.map +1 -0
- package/dist/core/extensions/builtin/ask-user/registry.d.ts +2 -0
- package/dist/core/extensions/builtin/ask-user/registry.d.ts.map +1 -1
- package/dist/core/extensions/builtin/ask-user/registry.js.map +1 -1
- package/dist/core/extensions/builtin/ask-user/resume.d.ts +1 -1
- package/dist/core/extensions/builtin/ask-user/resume.d.ts.map +1 -1
- package/dist/core/extensions/builtin/ask-user/resume.js +10 -20
- package/dist/core/extensions/builtin/ask-user/resume.js.map +1 -1
- package/dist/core/extensions/builtin/ask-user/tool.d.ts +5 -2
- package/dist/core/extensions/builtin/ask-user/tool.d.ts.map +1 -1
- package/dist/core/extensions/builtin/ask-user/tool.js +41 -6
- package/dist/core/extensions/builtin/ask-user/tool.js.map +1 -1
- package/dist/core/extensions/builtin/goal/channel-state-subscriptions.d.ts +2 -1
- package/dist/core/extensions/builtin/goal/channel-state-subscriptions.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/channel-state-subscriptions.js +1 -1
- package/dist/core/extensions/builtin/goal/channel-state-subscriptions.js.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +27 -13
- package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/config-loader.d.ts +2 -0
- package/dist/core/extensions/builtin/hooks/config-loader.d.ts.map +1 -1
- package/dist/core/extensions/builtin/hooks/config-loader.js +27 -0
- package/dist/core/extensions/builtin/hooks/config-loader.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/dispatcher.js +1 -0
- package/dist/core/extensions/builtin/hooks/dispatcher.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/hooks/index.js +51 -2
- package/dist/core/extensions/builtin/hooks/index.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts +18 -1
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts.map +1 -1
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js +36 -1
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/matcher.js +1 -0
- package/dist/core/extensions/builtin/hooks/matcher.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/output-parser.js +6 -0
- package/dist/core/extensions/builtin/hooks/output-parser.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/trust-storage.d.ts +2 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.d.ts.map +1 -1
- package/dist/core/extensions/builtin/hooks/trust-storage.js +13 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/types.d.ts +14 -2
- package/dist/core/extensions/builtin/hooks/types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/hooks/types.js +1 -1
- package/dist/core/extensions/builtin/hooks/types.js.map +1 -1
- package/dist/core/extensions/builtin/monitor-state-event.d.ts +1 -0
- package/dist/core/extensions/builtin/monitor-state-event.d.ts.map +1 -1
- package/dist/core/extensions/builtin/monitor-state-event.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +1 -0
- package/dist/core/extensions/builtin/terminal/tools/bash.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.js +13 -2
- package/dist/core/extensions/builtin/terminal/tools/bash.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +5 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -16
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +16 -5
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +28 -5
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/wrapper.d.ts +8 -3
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +12 -6
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/keybindings.d.ts +8 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +7 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/package-manager.d.ts +4 -2
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +7 -2
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/pi-manifest.d.ts +2 -0
- package/dist/core/pi-manifest.d.ts.map +1 -1
- package/dist/core/pi-manifest.js +3 -0
- package/dist/core/pi-manifest.js.map +1 -1
- package/dist/core/resource-loader.d.ts +9 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +69 -21
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +1 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-shapes.d.ts +1 -0
- package/dist/core/settings-shapes.d.ts.map +1 -1
- package/dist/core/settings-shapes.js.map +1 -1
- package/dist/core/source-info.d.ts +5 -1
- package/dist/core/source-info.d.ts.map +1 -1
- package/dist/core/source-info.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +5 -0
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/modes/app-server/server/skills.d.ts.map +1 -1
- package/dist/modes/app-server/server/skills.js +1 -0
- package/dist/modes/app-server/server/skills.js.map +1 -1
- package/dist/modes/interactive/components/ask-user-answer-chip.d.ts +17 -0
- package/dist/modes/interactive/components/ask-user-answer-chip.d.ts.map +1 -0
- package/dist/modes/interactive/components/ask-user-answer-chip.js +86 -0
- package/dist/modes/interactive/components/ask-user-answer-chip.js.map +1 -0
- package/dist/modes/interactive/components/ask-user-answer-key.d.ts +3 -1
- package/dist/modes/interactive/components/ask-user-answer-key.d.ts.map +1 -1
- package/dist/modes/interactive/components/ask-user-answer-key.js +11 -1
- package/dist/modes/interactive/components/ask-user-answer-key.js.map +1 -1
- package/dist/modes/interactive/components/ask-user-async-widget.d.ts +5 -2
- package/dist/modes/interactive/components/ask-user-async-widget.d.ts.map +1 -1
- package/dist/modes/interactive/components/ask-user-async-widget.js +18 -12
- package/dist/modes/interactive/components/ask-user-async-widget.js.map +1 -1
- package/dist/modes/interactive/components/ask-user-countdown.d.ts +8 -0
- package/dist/modes/interactive/components/ask-user-countdown.d.ts.map +1 -0
- package/dist/modes/interactive/components/ask-user-countdown.js +23 -0
- package/dist/modes/interactive/components/ask-user-countdown.js.map +1 -0
- package/dist/modes/interactive/components/ask-user-question-keys.d.ts.map +1 -1
- package/dist/modes/interactive/components/ask-user-question-keys.js +85 -17
- package/dist/modes/interactive/components/ask-user-question-keys.js.map +1 -1
- package/dist/modes/interactive/components/ask-user-question-render.d.ts.map +1 -1
- package/dist/modes/interactive/components/ask-user-question-render.js +22 -7
- package/dist/modes/interactive/components/ask-user-question-render.js.map +1 -1
- package/dist/modes/interactive/components/ask-user-question-state.d.ts +15 -0
- package/dist/modes/interactive/components/ask-user-question-state.d.ts.map +1 -1
- package/dist/modes/interactive/components/ask-user-question-state.js +57 -13
- package/dist/modes/interactive/components/ask-user-question-state.js.map +1 -1
- package/dist/modes/interactive/components/ask-user-question.d.ts +8 -2
- package/dist/modes/interactive/components/ask-user-question.d.ts.map +1 -1
- package/dist/modes/interactive/components/ask-user-question.js +16 -8
- package/dist/modes/interactive/components/ask-user-question.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts +2 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +2 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +10 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/loaded-resource-section.d.ts +14 -0
- package/dist/modes/interactive/components/loaded-resource-section.d.ts.map +1 -0
- package/dist/modes/interactive/components/loaded-resource-section.js +25 -0
- package/dist/modes/interactive/components/loaded-resource-section.js.map +1 -0
- package/dist/modes/interactive/components/user-message.d.ts +2 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +12 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +14 -11
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +263 -165
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/loaded-resource-scopes.d.ts +28 -0
- package/dist/modes/interactive/loaded-resource-scopes.d.ts.map +1 -0
- package/dist/modes/interactive/loaded-resource-scopes.js +107 -0
- package/dist/modes/interactive/loaded-resource-scopes.js.map +1 -0
- package/dist/modes/interactive/tips/catalog/input-tips.d.ts +1 -1
- package/dist/modes/interactive/tips/catalog/input-tips.js +2 -2
- package/dist/modes/interactive/tips/catalog/input-tips.js.map +1 -1
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
- package/dist/modes/rpc/host-lifecycle.js +7 -0
- package/dist/modes/rpc/host-lifecycle.js.map +1 -1
- package/dist/modes/rpc/session-worker.js +5 -0
- package/dist/modes/rpc/session-worker.js.map +1 -1
- package/docs/environment-variables.md +5 -1
- package/docs/extensions.md +43 -5
- package/docs/keybindings.md +5 -2
- package/docs/packages.md +17 -0
- package/docs/rpc.md +6 -3
- package/docs/settings.md +5 -1
- package/docs/tui.md +15 -3
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +28 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +16 -4
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/session-env.ts +6 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +6 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +31 -12
- package/node_modules/@earendil-works/chord/package.json +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/messages.d.ts +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/messages.js +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +4 -4
- package/node_modules/@earendil-works/pi-ai/dist/api/devin-agent.lazy.d.ts +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/devin-agent.lazy.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/devin-agent.lazy.js +6 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/devin-agent.lazy.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +55 -17
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +9 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/responses-completion-grace.d.ts +30 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/responses-completion-grace.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/responses-completion-grace.js +94 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/responses-completion-grace.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/websocket-liveness.d.ts +48 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/websocket-liveness.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/websocket-liveness.js +104 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/websocket-liveness.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/devin-provider.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/devin-provider.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/devin-provider.js +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/devin-provider.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +7 -3
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +10 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/README.md +11 -3
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +2 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/mouse-input.d.ts +37 -0
- package/node_modules/@earendil-works/pi-tui/dist/mouse-input.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/mouse-input.js +97 -0
- package/node_modules/@earendil-works/pi-tui/dist/mouse-input.js.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.d.ts +2 -0
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.js +38 -0
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +25 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +110 -4
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js +5 -50
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui-main-screen.d.ts +12 -0
- package/node_modules/@earendil-works/pi-tui/dist/tui-main-screen.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui-main-screen.js +129 -3
- package/node_modules/@earendil-works/pi-tui/dist/tui-main-screen.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +28 -0
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +160 -0
- package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +13 -9
- package/dist/bun/register-cursor-agent.d.ts +0 -2
- package/dist/bun/register-cursor-agent.d.ts.map +0 -1
- package/dist/bun/register-cursor-agent.js +0 -4
- package/dist/bun/register-cursor-agent.js.map +0 -1
|
@@ -7,7 +7,7 @@ import * as os from "node:os";
|
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { contentText, modelsAreEqual } from "@earendil-works/pi-ai";
|
|
9
9
|
import * as TuiLayouts from "@earendil-works/pi-tui";
|
|
10
|
-
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, outerKittyGraphicsMode, Spacer, sanitizeTerminalLabel, setCapabilityOverrides, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@earendil-works/pi-tui";
|
|
10
|
+
import { CombinedAutocompleteProvider, Container, decodeKittyPrintable, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, outerKittyGraphicsMode, SelectList, Spacer, sanitizeTerminalLabel, setCapabilityOverrides, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@earendil-works/pi-tui";
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import { spawn, spawnSync } from "child_process";
|
|
13
13
|
import { APP_COMMAND, APP_NAME, APP_TITLE, BRAND, CONFIG_DIR_NAME, DISPLAY_VERSION, expandTildePath, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, } from "../../config.js";
|
|
@@ -19,6 +19,7 @@ import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, }
|
|
|
19
19
|
import { resolveChangelogSource } from "../../core/changelog-source.js";
|
|
20
20
|
import { collectEntriesForBranchSummary } from "../../core/compaction/branch-summarization.js";
|
|
21
21
|
import { AssistantEditError, assistantTextEquals } from "../../core/edited-assistant-message.js";
|
|
22
|
+
import { formatUserMessage } from "../../core/extensions/builtin/ask-user/format.js";
|
|
22
23
|
import { buildNoticeBox } from "../../core/extensions/notice/index.js";
|
|
23
24
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
24
25
|
import { appendUncaughtCrashLog } from "../../core/hidden-stdout-log.js";
|
|
@@ -53,9 +54,11 @@ import { abortedMessageForRendering } from "./aborted-error-label.js";
|
|
|
53
54
|
import { createChatViewport } from "./chat-viewport.js";
|
|
54
55
|
import { transferCompactionQueue, waitForPromptDisposition, } from "./compaction-queue-transfer.js";
|
|
55
56
|
import { ArminComponent } from "./components/armin.js";
|
|
57
|
+
import { getAskUserAnswerHeaders, parseAskUserAnswerFrame } from "./components/ask-user-answer-chip.js";
|
|
56
58
|
import { matchesAskUserAnswerKey } from "./components/ask-user-answer-key.js";
|
|
57
|
-
import { ASK_USER_WIDGET_KEY, AskUserAsyncWidget, buildCommentResponse, buildTimedOutResponse, } from "./components/ask-user-async-widget.js";
|
|
59
|
+
import { ASK_USER_WIDGET_KEY, AskUserAsyncWidget, buildCommentResponse, buildTimedOutResponse, unansweredIds, } from "./components/ask-user-async-widget.js";
|
|
58
60
|
import { AskUserQuestionComponent } from "./components/ask-user-question.js";
|
|
61
|
+
import { formatCountdownLabel } from "./components/ask-user-question-state.js";
|
|
59
62
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
60
63
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
61
64
|
import { BorderedLoader } from "./components/bordered-loader.js";
|
|
@@ -74,6 +77,7 @@ import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
|
74
77
|
import { FavoriteModelsSelectorComponent } from "./components/favorite-models-selector.js";
|
|
75
78
|
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
76
79
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
80
|
+
import { LoadedResourceSection } from "./components/loaded-resource-section.js";
|
|
77
81
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
78
82
|
import { createMermaidMarkdownTransformer } from "./components/mermaid.js";
|
|
79
83
|
import { getModelFullId, mergeFavoritePatternsForPersist, } from "./components/model-favorites.js";
|
|
@@ -97,12 +101,13 @@ import { editFileInExternalEditor, editInExternalEditor } from "./external-edito
|
|
|
97
101
|
import { GrokChrome } from "./grok/chrome.js";
|
|
98
102
|
import { restoreInteractiveStderr, takeOverInteractiveStderr } from "./interactive-stderr-guard.js";
|
|
99
103
|
import { applyKeybindingsFileEdit, seedKeybindingsFile } from "./keybindings-command.js";
|
|
104
|
+
import { buildResourceScopeGroups, formatResourceScopeGroups, getDisplaySourceInfo, getScopeAutocompleteTag, isPackageSourceInfo, isSystemResource, } from "./loaded-resource-scopes.js";
|
|
100
105
|
import { describeLoginFailure } from "./login-outcome.js";
|
|
101
106
|
import { refreshModelCatalogs } from "./model-catalog-refresh.js";
|
|
102
107
|
import { getModelSearchText } from "./model-search.js";
|
|
103
108
|
import { isRiskyMainModel, RISKY_MAIN_MODEL_WARNING } from "./risky-main-model-warning.js";
|
|
104
109
|
import { DEFAULT_SMOOTH_FPS, StreamingRevealController } from "./streaming-reveal.js";
|
|
105
|
-
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
110
|
+
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getSelectListTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
106
111
|
import { InteractiveThemeController } from "./theme/theme-controller.js";
|
|
107
112
|
import { buildFavoriteCycleStatusMessage } from "./tips/favorite-messages.js";
|
|
108
113
|
import { recordTipShown } from "./tips/history-writer.js";
|
|
@@ -494,6 +499,9 @@ export class InteractiveMode {
|
|
|
494
499
|
: DEFAULT_SMOOTH_FPS;
|
|
495
500
|
this.ui.setMaxRenderFps(fps);
|
|
496
501
|
}
|
|
502
|
+
get shownQuestion() {
|
|
503
|
+
return this.shownQuestionId === undefined ? undefined : this.pendingQuestions.get(this.shownQuestionId);
|
|
504
|
+
}
|
|
497
505
|
// Convenience accessors
|
|
498
506
|
// The session may be the local AgentSession or the shared-host RPC proxy; the
|
|
499
507
|
// four reads widened on InteractiveSession must be awaited at every call site.
|
|
@@ -596,7 +604,11 @@ export class InteractiveMode {
|
|
|
596
604
|
this.extensionInput = undefined;
|
|
597
605
|
this.extensionEditor = undefined;
|
|
598
606
|
this.askUserQuestion = undefined;
|
|
599
|
-
this.
|
|
607
|
+
this.pendingQuestions = new Map();
|
|
608
|
+
this.pendingOrder = [];
|
|
609
|
+
this.questionSurface = "collapsed";
|
|
610
|
+
this.composerDestination = { kind: "chat" };
|
|
611
|
+
this.questionArrivalEpochMs = Date.now();
|
|
600
612
|
this.extensionTerminalInputSubscriptions = new Set();
|
|
601
613
|
// Extension widgets (components rendered above/below the editor)
|
|
602
614
|
this.extensionWidgetsAbove = new Map();
|
|
@@ -724,7 +736,7 @@ export class InteractiveMode {
|
|
|
724
736
|
if (!sourceInfo) {
|
|
725
737
|
return undefined;
|
|
726
738
|
}
|
|
727
|
-
const scopePrefix = sourceInfo.scope
|
|
739
|
+
const scopePrefix = getScopeAutocompleteTag(sourceInfo.scope);
|
|
728
740
|
const source = sourceInfo.source.trim();
|
|
729
741
|
if (source === "auto" || source === "local" || source === "cli") {
|
|
730
742
|
return scopePrefix;
|
|
@@ -1121,8 +1133,10 @@ export class InteractiveMode {
|
|
|
1121
1133
|
return `${APP_TITLE} - ${cwdBasename}`;
|
|
1122
1134
|
}
|
|
1123
1135
|
applyTerminalTitle() {
|
|
1136
|
+
const questionHeader = this.shownQuestion?.request.questions[0]?.header ?? this.blockingQuestionHeader;
|
|
1124
1137
|
this.ui.terminal.setTitle(this.activeToolTerminalTitle ??
|
|
1125
1138
|
this.activeToolExecutionTerminalTitle ??
|
|
1139
|
+
(questionHeader === undefined ? undefined : `? ${questionHeader}`) ??
|
|
1126
1140
|
this.extensionTerminalTitle ??
|
|
1127
1141
|
this.getNormalTerminalTitle());
|
|
1128
1142
|
}
|
|
@@ -1563,84 +1577,16 @@ export class InteractiveMode {
|
|
|
1563
1577
|
});
|
|
1564
1578
|
}
|
|
1565
1579
|
getDisplaySourceInfo(sourceInfo) {
|
|
1566
|
-
|
|
1567
|
-
const scope = sourceInfo?.scope ?? "project";
|
|
1568
|
-
if (source === "local") {
|
|
1569
|
-
if (scope === "user") {
|
|
1570
|
-
return { label: "user", color: "muted" };
|
|
1571
|
-
}
|
|
1572
|
-
if (scope === "project") {
|
|
1573
|
-
return { label: "project", color: "muted" };
|
|
1574
|
-
}
|
|
1575
|
-
if (scope === "temporary") {
|
|
1576
|
-
return { label: "path", scopeLabel: "temp", color: "muted" };
|
|
1577
|
-
}
|
|
1578
|
-
return { label: "path", color: "muted" };
|
|
1579
|
-
}
|
|
1580
|
-
if (source === "cli") {
|
|
1581
|
-
return {
|
|
1582
|
-
label: "path",
|
|
1583
|
-
scopeLabel: scope === "temporary" ? "temp" : undefined,
|
|
1584
|
-
color: "muted",
|
|
1585
|
-
};
|
|
1586
|
-
}
|
|
1587
|
-
const scopeLabel = scope === "user" ? "user" : scope === "project" ? "project" : scope === "temporary" ? "temp" : undefined;
|
|
1588
|
-
return { label: source, scopeLabel, color: "accent" };
|
|
1589
|
-
}
|
|
1590
|
-
getScopeGroup(sourceInfo) {
|
|
1591
|
-
const source = sourceInfo?.source ?? "local";
|
|
1592
|
-
const scope = sourceInfo?.scope ?? "project";
|
|
1593
|
-
if (source === "cli" || scope === "temporary")
|
|
1594
|
-
return "path";
|
|
1595
|
-
if (scope === "user")
|
|
1596
|
-
return "user";
|
|
1597
|
-
if (scope === "project")
|
|
1598
|
-
return "project";
|
|
1599
|
-
return "path";
|
|
1580
|
+
return getDisplaySourceInfo(sourceInfo);
|
|
1600
1581
|
}
|
|
1601
1582
|
isPackageSource(sourceInfo) {
|
|
1602
|
-
|
|
1603
|
-
return source.startsWith("npm:") || source.startsWith("git:");
|
|
1583
|
+
return isPackageSourceInfo(sourceInfo);
|
|
1604
1584
|
}
|
|
1605
1585
|
buildScopeGroups(items) {
|
|
1606
|
-
|
|
1607
|
-
user: { scope: "user", paths: [], packages: new Map() },
|
|
1608
|
-
project: { scope: "project", paths: [], packages: new Map() },
|
|
1609
|
-
path: { scope: "path", paths: [], packages: new Map() },
|
|
1610
|
-
};
|
|
1611
|
-
for (const item of items) {
|
|
1612
|
-
const groupKey = this.getScopeGroup(item.sourceInfo);
|
|
1613
|
-
const group = groups[groupKey];
|
|
1614
|
-
const source = item.sourceInfo?.source ?? "local";
|
|
1615
|
-
if (this.isPackageSource(item.sourceInfo)) {
|
|
1616
|
-
const list = group.packages.get(source) ?? [];
|
|
1617
|
-
list.push(item);
|
|
1618
|
-
group.packages.set(source, list);
|
|
1619
|
-
}
|
|
1620
|
-
else {
|
|
1621
|
-
group.paths.push(item);
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
return [groups.project, groups.user, groups.path].filter((group) => group.paths.length > 0 || group.packages.size > 0);
|
|
1586
|
+
return buildResourceScopeGroups(items);
|
|
1625
1587
|
}
|
|
1626
1588
|
formatScopeGroups(groups, options) {
|
|
1627
|
-
|
|
1628
|
-
for (const group of groups) {
|
|
1629
|
-
lines.push(` ${theme.fg("accent", group.scope)}`);
|
|
1630
|
-
const sortedPaths = [...group.paths].sort((a, b) => a.path.localeCompare(b.path));
|
|
1631
|
-
for (const item of sortedPaths) {
|
|
1632
|
-
lines.push(theme.fg("dim", ` ${options.formatPath(item)}`));
|
|
1633
|
-
}
|
|
1634
|
-
const sortedPackages = Array.from(group.packages.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
1635
|
-
for (const [source, items] of sortedPackages) {
|
|
1636
|
-
lines.push(` ${theme.fg("mdLink", source)}`);
|
|
1637
|
-
const sortedPackagePaths = [...items].sort((a, b) => a.path.localeCompare(b.path));
|
|
1638
|
-
for (const item of sortedPackagePaths) {
|
|
1639
|
-
lines.push(theme.fg("dim", ` ${options.formatPackagePath(item, source)}`));
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
return lines.join("\n");
|
|
1589
|
+
return formatResourceScopeGroups(groups, options);
|
|
1644
1590
|
}
|
|
1645
1591
|
findSourceInfoForPath(p, sourceInfos) {
|
|
1646
1592
|
const exact = sourceInfos.get(p);
|
|
@@ -1722,15 +1668,19 @@ export class InteractiveMode {
|
|
|
1722
1668
|
const sectionHeader = (name, color = "mdHeading") => theme.fg(color, `[${name}]`);
|
|
1723
1669
|
const formatCompactList = (items, options) => {
|
|
1724
1670
|
const labels = items.map((item) => item.trim()).filter((item) => item.length > 0);
|
|
1671
|
+
if (labels.length === 0) {
|
|
1672
|
+
return "";
|
|
1673
|
+
}
|
|
1725
1674
|
if (options?.sort !== false) {
|
|
1726
1675
|
labels.sort((a, b) => a.localeCompare(b));
|
|
1727
1676
|
}
|
|
1728
1677
|
return theme.fg("dim", ` ${labels.join(", ")}`);
|
|
1729
1678
|
};
|
|
1679
|
+
// System resources are left out of the compact body; a section with nothing else to show stays
|
|
1680
|
+
// hidden until the listing is expanded, where the system group lists them.
|
|
1730
1681
|
const addLoadedSection = (name, collapsedBody, expandedBody = collapsedBody, color = "mdHeading") => {
|
|
1731
|
-
const section = new
|
|
1682
|
+
const section = new LoadedResourceSection(collapsedBody.length === 0 ? "" : `${sectionHeader(name, color)}\n${collapsedBody}`, `${sectionHeader(name, color)}\n${expandedBody}`, this.getStartupExpansionState());
|
|
1732
1683
|
this.loadedResourcesContainer.addChild(section);
|
|
1733
|
-
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1734
1684
|
};
|
|
1735
1685
|
const skillsResult = this.session.resourceLoader.getSkills();
|
|
1736
1686
|
const promptsResult = this.session.resourceLoader.getPrompts();
|
|
@@ -1789,7 +1739,7 @@ export class InteractiveMode {
|
|
|
1789
1739
|
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
1790
1740
|
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
1791
1741
|
});
|
|
1792
|
-
const skillCompactList = formatCompactList(skills.map((skill) => skill.name));
|
|
1742
|
+
const skillCompactList = formatCompactList(skills.filter((skill) => !isSystemResource(skill.sourceInfo)).map((skill) => skill.name));
|
|
1793
1743
|
addLoadedSection("Skills", skillCompactList, skillList);
|
|
1794
1744
|
}
|
|
1795
1745
|
const templates = this.session.promptTemplates;
|
|
@@ -1809,7 +1759,9 @@ export class InteractiveMode {
|
|
|
1809
1759
|
return template ? `/${template.name}` : this.formatDisplayPath(item.path);
|
|
1810
1760
|
},
|
|
1811
1761
|
});
|
|
1812
|
-
const promptCompactList = formatCompactList(templates
|
|
1762
|
+
const promptCompactList = formatCompactList(templates
|
|
1763
|
+
.filter((template) => !isSystemResource(template.sourceInfo))
|
|
1764
|
+
.map((template) => `/${template.name}`));
|
|
1813
1765
|
addLoadedSection("Prompts", promptCompactList, templateList);
|
|
1814
1766
|
}
|
|
1815
1767
|
if (extensions.length > 0) {
|
|
@@ -1818,7 +1770,7 @@ export class InteractiveMode {
|
|
|
1818
1770
|
formatPath: (item) => this.formatExtensionDisplayPath(item.path),
|
|
1819
1771
|
formatPackagePath: (item) => this.formatExtensionDisplayPath(this.getShortPath(item.path, item.sourceInfo)),
|
|
1820
1772
|
});
|
|
1821
|
-
const extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));
|
|
1773
|
+
const extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions.filter((extension) => !isSystemResource(extension.sourceInfo))));
|
|
1822
1774
|
addLoadedSection("Extensions", extensionCompactList, extList, "mdHeading");
|
|
1823
1775
|
}
|
|
1824
1776
|
// Show loaded themes (excluding built-in)
|
|
@@ -1833,7 +1785,9 @@ export class InteractiveMode {
|
|
|
1833
1785
|
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
1834
1786
|
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
1835
1787
|
});
|
|
1836
|
-
const themeCompactList = formatCompactList(customThemes
|
|
1788
|
+
const themeCompactList = formatCompactList(customThemes
|
|
1789
|
+
.filter((loadedTheme) => !isSystemResource(loadedTheme.sourceInfo))
|
|
1790
|
+
.map((loadedTheme) => loadedTheme.name ?? this.getCompactPathLabel(loadedTheme.sourcePath, loadedTheme.sourceInfo)));
|
|
1837
1791
|
addLoadedSection("Themes", themeCompactList, themeList);
|
|
1838
1792
|
}
|
|
1839
1793
|
}
|
|
@@ -2144,10 +2098,19 @@ export class InteractiveMode {
|
|
|
2144
2098
|
return false;
|
|
2145
2099
|
};
|
|
2146
2100
|
}
|
|
2101
|
+
async withBlockedHostDialog(id, label, show) {
|
|
2102
|
+
this.session.emitExtensionEvent("herdr:blocked", { active: true, label, id });
|
|
2103
|
+
try {
|
|
2104
|
+
return await show();
|
|
2105
|
+
}
|
|
2106
|
+
finally {
|
|
2107
|
+
this.session.emitExtensionEvent("herdr:blocked", { active: false, id });
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2147
2110
|
async handleHostUiRequest(request) {
|
|
2148
2111
|
switch (request.method) {
|
|
2149
2112
|
case "select": {
|
|
2150
|
-
const value = await this.showExtensionSelector(request.title ?? "", request.options ?? []);
|
|
2113
|
+
const value = await this.withBlockedHostDialog(request.id, request.title ?? "", () => this.showExtensionSelector(request.title ?? "", request.options ?? []));
|
|
2151
2114
|
return value === undefined
|
|
2152
2115
|
? { type: "extension_ui_response", id: request.id, cancelled: true }
|
|
2153
2116
|
: { type: "extension_ui_response", id: request.id, value };
|
|
@@ -2156,16 +2119,16 @@ export class InteractiveMode {
|
|
|
2156
2119
|
return {
|
|
2157
2120
|
type: "extension_ui_response",
|
|
2158
2121
|
id: request.id,
|
|
2159
|
-
confirmed: await this.showExtensionConfirm(request.title ?? "", request.message ?? ""),
|
|
2122
|
+
confirmed: await this.withBlockedHostDialog(request.id, request.title ?? "", () => this.showExtensionConfirm(request.title ?? "", request.message ?? "")),
|
|
2160
2123
|
};
|
|
2161
2124
|
case "input": {
|
|
2162
|
-
const value = await this.showExtensionInput(request.title ?? "", request.placeholder);
|
|
2125
|
+
const value = await this.withBlockedHostDialog(request.id, request.title ?? "", () => this.showExtensionInput(request.title ?? "", request.placeholder));
|
|
2163
2126
|
return value === undefined
|
|
2164
2127
|
? { type: "extension_ui_response", id: request.id, cancelled: true }
|
|
2165
2128
|
: { type: "extension_ui_response", id: request.id, value };
|
|
2166
2129
|
}
|
|
2167
2130
|
case "editor": {
|
|
2168
|
-
const value = await this.showExtensionEditor(request.title ?? "", request.prefill);
|
|
2131
|
+
const value = await this.withBlockedHostDialog(request.id, request.title ?? "", () => this.showExtensionEditor(request.title ?? "", request.prefill));
|
|
2169
2132
|
return value === undefined
|
|
2170
2133
|
? { type: "extension_ui_response", id: request.id, cancelled: true }
|
|
2171
2134
|
: { type: "extension_ui_response", id: request.id, value };
|
|
@@ -2189,6 +2152,7 @@ export class InteractiveMode {
|
|
|
2189
2152
|
timeoutMs: request.timeout ?? 0,
|
|
2190
2153
|
}, {
|
|
2191
2154
|
timeout: remainingMs,
|
|
2155
|
+
notifyArrival: request.askedAtMs === undefined || request.askedAtMs >= this.questionArrivalEpochMs,
|
|
2192
2156
|
onProgress: (draft) => {
|
|
2193
2157
|
lastDraft = draft;
|
|
2194
2158
|
if (progressTimer !== undefined)
|
|
@@ -2601,11 +2565,13 @@ export class InteractiveMode {
|
|
|
2601
2565
|
if (this.askUserQuestion) {
|
|
2602
2566
|
this.hideQuestionOverlay();
|
|
2603
2567
|
}
|
|
2604
|
-
this.
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2568
|
+
for (const state of this.pendingQuestions.values()) {
|
|
2569
|
+
state.finish({
|
|
2570
|
+
status: "cancelled",
|
|
2571
|
+
answers: {},
|
|
2572
|
+
unanswered: state.request.questions.map((question) => question.id),
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2609
2575
|
this.ui.hideOverlay();
|
|
2610
2576
|
this.clearExtensionTerminalInputListeners();
|
|
2611
2577
|
this.setExtensionFooter(undefined);
|
|
@@ -2759,9 +2725,9 @@ export class InteractiveMode {
|
|
|
2759
2725
|
}
|
|
2760
2726
|
createExtensionUIContext() {
|
|
2761
2727
|
return {
|
|
2762
|
-
select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
|
|
2763
|
-
confirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),
|
|
2764
|
-
input: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),
|
|
2728
|
+
select: (title, options, opts) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionSelector(title, options, opts)),
|
|
2729
|
+
confirm: (title, message, opts) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionConfirm(title, message, opts)),
|
|
2730
|
+
input: (title, placeholder, opts) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionInput(title, placeholder, opts)),
|
|
2765
2731
|
// Async answers are delivered by the ask-user extension, not here: the
|
|
2766
2732
|
// widget only resolves the question, so the model sees exactly one
|
|
2767
2733
|
// framed user message no matter which surface answered.
|
|
@@ -2787,7 +2753,7 @@ export class InteractiveMode {
|
|
|
2787
2753
|
pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
|
|
2788
2754
|
setEditorText: (text) => this.editor.setText(text),
|
|
2789
2755
|
getEditorText: () => this.getExpandedEditorText(),
|
|
2790
|
-
editor: (title, prefill) => this.showExtensionEditor(title, prefill),
|
|
2756
|
+
editor: (title, prefill) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionEditor(title, prefill)),
|
|
2791
2757
|
addAutocompleteProvider: (factory) => {
|
|
2792
2758
|
this.autocompleteProviderWrappers.push(factory);
|
|
2793
2759
|
this.setupAutocompleteProvider();
|
|
@@ -2955,6 +2921,8 @@ export class InteractiveMode {
|
|
|
2955
2921
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
2956
2922
|
this.workingMessage = previousWorkingMessage;
|
|
2957
2923
|
this.updateWorkingIndicatorMessage();
|
|
2924
|
+
this.blockingQuestionHeader = undefined;
|
|
2925
|
+
this.applyTerminalTitle();
|
|
2958
2926
|
this.hideQuestionOverlay();
|
|
2959
2927
|
resolve(response);
|
|
2960
2928
|
};
|
|
@@ -2974,11 +2942,16 @@ export class InteractiveMode {
|
|
|
2974
2942
|
return;
|
|
2975
2943
|
}
|
|
2976
2944
|
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2945
|
+
this.blockingQuestionHeader = request.questions[0]?.header;
|
|
2946
|
+
this.applyTerminalTitle();
|
|
2947
|
+
if (opts?.notifyArrival !== false && this.settingsManager.getAskUserSettings().bell)
|
|
2948
|
+
this.ui.terminal.write("\x07");
|
|
2977
2949
|
this.workingMessage = "Waiting for your answer";
|
|
2978
2950
|
this.updateWorkingIndicatorMessage();
|
|
2979
2951
|
this.askUserQuestion = new AskUserQuestionComponent(request, (response) => finish(response), {
|
|
2980
2952
|
tui: this.ui,
|
|
2981
2953
|
timeoutMs: opts?.timeout ?? request.timeoutMs,
|
|
2954
|
+
getDeadlineAtMs: opts?.getDeadlineAtMs,
|
|
2982
2955
|
onProgress: opts?.onProgress,
|
|
2983
2956
|
});
|
|
2984
2957
|
this.disposeActiveSelector();
|
|
@@ -2994,82 +2967,199 @@ export class InteractiveMode {
|
|
|
2994
2967
|
hideQuestionOverlay() {
|
|
2995
2968
|
this.askUserQuestion?.dispose();
|
|
2996
2969
|
this.askUserQuestion = undefined;
|
|
2970
|
+
this.questionSurface = "collapsed";
|
|
2997
2971
|
this.editorContainer.clear();
|
|
2998
2972
|
this.editorContainer.addChild(this.editor);
|
|
2999
2973
|
this.ui.setFocus(this.editor);
|
|
3000
2974
|
this.ui.requestRender();
|
|
3001
2975
|
}
|
|
3002
|
-
/**
|
|
3003
|
-
* Park a waitForAnswer=false question behind a one-line widget above the
|
|
3004
|
-
* editor. The turn keeps running; the promise settles when the user submits
|
|
3005
|
-
* from the expanded component, types an ordinary reply (comment), the idle
|
|
3006
|
-
* countdown expires, or the caller aborts. A newer async question supersedes
|
|
3007
|
-
* a pending one, which resolves as cancelled.
|
|
3008
|
-
*/
|
|
2976
|
+
/** Queue an async request without replacing another request or taking editor focus. */
|
|
3009
2977
|
showAsyncQuestion(request, opts) {
|
|
3010
|
-
|
|
3011
|
-
|
|
2978
|
+
const existing = this.pendingQuestions.get(request.requestId);
|
|
2979
|
+
if (existing)
|
|
2980
|
+
return existing.completion;
|
|
2981
|
+
const cancelled = () => ({
|
|
2982
|
+
status: "cancelled",
|
|
2983
|
+
answers: {},
|
|
2984
|
+
unanswered: request.questions.map((question) => question.id),
|
|
2985
|
+
});
|
|
2986
|
+
if (opts?.signal?.aborted)
|
|
2987
|
+
return Promise.resolve(cancelled());
|
|
2988
|
+
const completion = Promise.withResolvers();
|
|
2989
|
+
const onAbort = () => state.finish(cancelled());
|
|
2990
|
+
const state = {
|
|
2991
|
+
request,
|
|
2992
|
+
timeoutMs: opts?.timeout ?? request.timeoutMs,
|
|
2993
|
+
askedAtMs: Date.now(),
|
|
2994
|
+
completion: completion.promise,
|
|
2995
|
+
getDeadlineAtMs: opts?.getDeadlineAtMs,
|
|
2996
|
+
onProgress: opts?.onProgress,
|
|
2997
|
+
draft: { answers: {} },
|
|
2998
|
+
finish: (response) => {
|
|
2999
|
+
if (this.pendingQuestions.get(request.requestId) !== state)
|
|
3000
|
+
return;
|
|
3001
|
+
this.pendingQuestions.delete(request.requestId);
|
|
3002
|
+
this.pendingOrder = this.pendingOrder.filter((id) => id !== request.requestId);
|
|
3003
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
3004
|
+
if (this.composerDestination.kind === "answer" &&
|
|
3005
|
+
this.composerDestination.requestId === request.requestId) {
|
|
3006
|
+
this.setComposerReply();
|
|
3007
|
+
if (this.editor.getText() !== "")
|
|
3008
|
+
this.showStatus("That question is no longer pending");
|
|
3009
|
+
}
|
|
3010
|
+
if (this.pendingOrder.length === 0 && this.questionSurface === "list") {
|
|
3011
|
+
this.ui.hideOverlay();
|
|
3012
|
+
this.questionSurface = "collapsed";
|
|
3013
|
+
}
|
|
3014
|
+
if (this.shownQuestionId === request.requestId) {
|
|
3015
|
+
if (this.questionSurface === "expanded")
|
|
3016
|
+
this.hideQuestionOverlay();
|
|
3017
|
+
this.shownQuestionId = this.pendingOrder[0];
|
|
3018
|
+
}
|
|
3019
|
+
this.refreshAsyncWidget();
|
|
3020
|
+
completion.resolve(response);
|
|
3021
|
+
},
|
|
3022
|
+
};
|
|
3023
|
+
this.pendingQuestions.set(request.requestId, state);
|
|
3024
|
+
this.pendingOrder.push(request.requestId);
|
|
3025
|
+
this.shownQuestionId ??= request.requestId;
|
|
3026
|
+
if (opts?.notifyArrival !== false && this.settingsManager.getAskUserSettings().bell)
|
|
3027
|
+
this.ui.terminal.write("\x07");
|
|
3028
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
3029
|
+
this.refreshAsyncWidget();
|
|
3030
|
+
return completion.promise;
|
|
3031
|
+
}
|
|
3032
|
+
/** Only the visible surface ticks; extension deadlines remain authoritative. */
|
|
3033
|
+
refreshAsyncWidget() {
|
|
3034
|
+
this.applyTerminalTitle();
|
|
3035
|
+
const state = this.shownQuestion;
|
|
3036
|
+
if (!state || this.questionSurface === "expanded") {
|
|
3037
|
+
this.setExtensionWidget(ASK_USER_WIDGET_KEY, undefined);
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
this.setExtensionWidget(ASK_USER_WIDGET_KEY, (tui) => new AskUserAsyncWidget({
|
|
3041
|
+
request: state.request,
|
|
3042
|
+
draft: state.draft,
|
|
3043
|
+
timeoutMs: state.timeoutMs,
|
|
3044
|
+
getDeadlineAtMs: state.getDeadlineAtMs,
|
|
3045
|
+
pendingCount: this.pendingOrder.length,
|
|
3046
|
+
tui,
|
|
3047
|
+
onExpire: () => state.finish(buildTimedOutResponse(state.request, state.draft, Date.now() - state.askedAtMs)),
|
|
3048
|
+
}));
|
|
3049
|
+
}
|
|
3050
|
+
/** Intercept question chords and first typed input before the editor inserts it. */
|
|
3051
|
+
handleAskUserShortcut(data) {
|
|
3052
|
+
const state = this.shownQuestion;
|
|
3053
|
+
if (!state ||
|
|
3054
|
+
this.askUserQuestion ||
|
|
3055
|
+
this.ui.hasOverlay() ||
|
|
3056
|
+
this.extensionSelector ||
|
|
3057
|
+
this.extensionInput ||
|
|
3058
|
+
this.extensionEditor)
|
|
3059
|
+
return false;
|
|
3060
|
+
if (matchesAskUserAnswerKey(data))
|
|
3061
|
+
return this.expandPendingQuestion();
|
|
3062
|
+
if (this.editor.getText() !== "" ||
|
|
3063
|
+
this.ui.getFocusedComponent() !== this.editor ||
|
|
3064
|
+
this.defaultEditor.isShowingAutocomplete())
|
|
3065
|
+
return false;
|
|
3066
|
+
if (this.keybindings.matches(data, "app.question.next")) {
|
|
3067
|
+
if (this.pendingOrder.length < 2)
|
|
3068
|
+
return false;
|
|
3069
|
+
const index = this.pendingOrder.indexOf(state.request.requestId);
|
|
3070
|
+
this.shownQuestionId = this.pendingOrder[(index + 1) % this.pendingOrder.length];
|
|
3071
|
+
this.refreshAsyncWidget();
|
|
3072
|
+
return true;
|
|
3073
|
+
}
|
|
3074
|
+
const unanswered = unansweredIds(state.request, state.draft);
|
|
3075
|
+
const questionIndex = state.request.questions.findIndex((question) => question.id === unanswered[0]);
|
|
3076
|
+
const question = state.request.questions[questionIndex];
|
|
3077
|
+
if (/^[1-9]$/.test(data) && question?.options[Number(data) - 1]) {
|
|
3078
|
+
this.expandPendingQuestion(state.request.requestId, questionIndex, data);
|
|
3079
|
+
return true;
|
|
3080
|
+
}
|
|
3081
|
+
const printable = decodeKittyPrintable(data) ?? data;
|
|
3082
|
+
if (data.startsWith("\x1b[200~") ||
|
|
3083
|
+
(printable !== "" && !/[\x00-\x1f\x7f]/.test(printable) && !/^[/!]/.test(printable))) {
|
|
3084
|
+
this.setComposerReply(state.request.requestId);
|
|
3085
|
+
}
|
|
3086
|
+
return false;
|
|
3087
|
+
}
|
|
3088
|
+
setComposerReply(requestId) {
|
|
3089
|
+
this.composerDestination = requestId === undefined ? { kind: "chat" } : { kind: "answer", requestId };
|
|
3090
|
+
const state = requestId === undefined ? undefined : this.pendingQuestions.get(requestId);
|
|
3091
|
+
this.defaultEditor.setReplyLabel(state
|
|
3092
|
+
? theme.fg("muted", `↳ reply to ${state.request.questions[0].header} · ${keyText("app.message.followUp")} sends as message`)
|
|
3093
|
+
: undefined);
|
|
3094
|
+
this.ui.requestRender();
|
|
3095
|
+
}
|
|
3096
|
+
async handleAnswerCommand(argument) {
|
|
3097
|
+
const state = this.shownQuestion;
|
|
3098
|
+
if (!state) {
|
|
3099
|
+
this.showStatus("No question is pending.");
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
3102
|
+
if (argument === "skip") {
|
|
3103
|
+
const response = {
|
|
3012
3104
|
status: "cancelled",
|
|
3013
|
-
answers: {},
|
|
3014
|
-
unanswered: request
|
|
3105
|
+
answers: state.draft.answers ?? {},
|
|
3106
|
+
unanswered: unansweredIds(state.request, state.draft),
|
|
3107
|
+
};
|
|
3108
|
+
state.finish(response);
|
|
3109
|
+
await state.completion;
|
|
3110
|
+
this.showStatus("The user dismissed the question.");
|
|
3111
|
+
// Ordinary cancellation/abort stays silent in the builtin. Only this
|
|
3112
|
+
// explicit command acknowledges dismissal, including on host bridges.
|
|
3113
|
+
await this.session.sendUserMessage(formatUserMessage(response, state.request.requestId, state.request.questions), {
|
|
3114
|
+
deliverAs: this.session.isStreaming ? "steer" : "followUp",
|
|
3015
3115
|
});
|
|
3016
|
-
|
|
3017
|
-
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
if (argument !== "") {
|
|
3119
|
+
const id = /^[1-9]\d*$/.test(argument) ? this.pendingOrder[Number(argument) - 1] : undefined;
|
|
3120
|
+
if (id === undefined) {
|
|
3121
|
+
this.showStatus("Choose a pending question number or /answer skip.");
|
|
3018
3122
|
return;
|
|
3019
3123
|
}
|
|
3020
|
-
this.
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
this.hideQuestionOverlay();
|
|
3034
|
-
this.setExtensionWidget(ASK_USER_WIDGET_KEY, undefined);
|
|
3035
|
-
resolve(response);
|
|
3036
|
-
},
|
|
3124
|
+
this.expandPendingQuestion(id);
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
if (this.pendingOrder.length === 1) {
|
|
3128
|
+
this.expandPendingQuestion();
|
|
3129
|
+
return;
|
|
3130
|
+
}
|
|
3131
|
+
const list = new SelectList(this.pendingOrder.map((id) => {
|
|
3132
|
+
const pending = this.pendingQuestions.get(id);
|
|
3133
|
+
const remaining = (pending.getDeadlineAtMs?.() ?? pending.askedAtMs + pending.timeoutMs) - Date.now();
|
|
3134
|
+
return {
|
|
3135
|
+
value: id,
|
|
3136
|
+
label: `${pending.request.questions[0].header} · ${formatCountdownLabel(Date.now() - pending.askedAtMs)} ago · ${formatCountdownLabel(remaining)} remaining`,
|
|
3037
3137
|
};
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
this.
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
timeoutMs: state.timeoutMs,
|
|
3051
|
-
tui,
|
|
3052
|
-
onExpire: () => state.finish(buildTimedOutResponse(state.request, state.draft, Date.now() - startedAt)),
|
|
3053
|
-
});
|
|
3054
|
-
});
|
|
3055
|
-
}
|
|
3056
|
-
/** Editor shortcut: expand the pending async question into the full component. */
|
|
3057
|
-
handleAskUserShortcut(data) {
|
|
3058
|
-
if (!this.asyncQuestion || this.askUserQuestion || !matchesAskUserAnswerKey(data))
|
|
3059
|
-
return false;
|
|
3060
|
-
return this.expandPendingQuestion();
|
|
3138
|
+
}), 10, getSelectListTheme());
|
|
3139
|
+
const close = () => {
|
|
3140
|
+
this.ui.hideOverlay();
|
|
3141
|
+
this.questionSurface = "collapsed";
|
|
3142
|
+
};
|
|
3143
|
+
list.onCancel = close;
|
|
3144
|
+
list.onSelect = ({ value }) => {
|
|
3145
|
+
close();
|
|
3146
|
+
this.expandPendingQuestion(value);
|
|
3147
|
+
};
|
|
3148
|
+
this.questionSurface = "list";
|
|
3149
|
+
this.ui.showOverlay(list, { width: "80%", anchor: "bottom-center" });
|
|
3061
3150
|
}
|
|
3062
3151
|
/**
|
|
3063
3152
|
* Key-free entry into the pending async question (empty Enter, /answer):
|
|
3064
3153
|
* mounts the full component in place of the editor. Returns false when
|
|
3065
3154
|
* nothing is pending or the component is already open.
|
|
3066
3155
|
*/
|
|
3067
|
-
expandPendingQuestion() {
|
|
3068
|
-
const state = this.
|
|
3156
|
+
expandPendingQuestion(requestId = this.shownQuestionId, initialQuestionIndex, initialInput) {
|
|
3157
|
+
const state = requestId === undefined ? undefined : this.pendingQuestions.get(requestId);
|
|
3069
3158
|
if (!state || this.askUserQuestion)
|
|
3070
3159
|
return false;
|
|
3160
|
+
this.shownQuestionId = state.request.requestId;
|
|
3071
3161
|
const component = new AskUserQuestionComponent(state.request, (response) => {
|
|
3072
|
-
if (this.
|
|
3162
|
+
if (this.pendingQuestions.get(state.request.requestId) !== state)
|
|
3073
3163
|
return;
|
|
3074
3164
|
if (response.status !== "cancelled") {
|
|
3075
3165
|
state.finish(response);
|
|
@@ -3077,21 +3167,28 @@ export class InteractiveMode {
|
|
|
3077
3167
|
}
|
|
3078
3168
|
// Esc collapses back to the widget; the question stays pending.
|
|
3079
3169
|
this.hideQuestionOverlay();
|
|
3080
|
-
this.refreshAsyncWidget(
|
|
3170
|
+
this.refreshAsyncWidget();
|
|
3081
3171
|
}, {
|
|
3082
3172
|
tui: this.ui,
|
|
3083
3173
|
timeoutMs: state.timeoutMs,
|
|
3174
|
+
getDeadlineAtMs: state.getDeadlineAtMs,
|
|
3175
|
+
initialDraft: state.draft,
|
|
3176
|
+
initialQuestionIndex,
|
|
3084
3177
|
onProgress: (draft) => {
|
|
3085
3178
|
state.draft = draft;
|
|
3086
3179
|
state.onProgress?.(draft);
|
|
3087
3180
|
},
|
|
3088
3181
|
});
|
|
3089
3182
|
this.askUserQuestion = component;
|
|
3183
|
+
this.questionSurface = "expanded";
|
|
3184
|
+
this.refreshAsyncWidget();
|
|
3090
3185
|
this.disposeActiveSelector();
|
|
3091
3186
|
this.editorContainer.clear();
|
|
3092
3187
|
this.editorContainer.addChild(component);
|
|
3093
3188
|
this.ui.setFocus(component);
|
|
3094
3189
|
this.ui.requestRender();
|
|
3190
|
+
if (initialInput !== undefined)
|
|
3191
|
+
component.handleInput(initialInput);
|
|
3095
3192
|
return true;
|
|
3096
3193
|
}
|
|
3097
3194
|
/**
|
|
@@ -3100,9 +3197,11 @@ export class InteractiveMode {
|
|
|
3100
3197
|
* the raw text. Returns false when nothing is pending.
|
|
3101
3198
|
*/
|
|
3102
3199
|
submitAsyncQuestionComment(text) {
|
|
3103
|
-
const
|
|
3200
|
+
const destination = this.composerDestination;
|
|
3201
|
+
const state = destination.kind === "answer" ? this.pendingQuestions.get(destination.requestId) : undefined;
|
|
3104
3202
|
if (!state)
|
|
3105
3203
|
return false;
|
|
3204
|
+
this.setComposerReply();
|
|
3106
3205
|
this.editor.addToHistory?.(text);
|
|
3107
3206
|
this.editor.setText("");
|
|
3108
3207
|
state.finish(buildCommentResponse(state.request, state.draft, text));
|
|
@@ -3580,12 +3679,8 @@ export class InteractiveMode {
|
|
|
3580
3679
|
this.expandPendingQuestion();
|
|
3581
3680
|
return;
|
|
3582
3681
|
}
|
|
3583
|
-
//
|
|
3584
|
-
|
|
3585
|
-
if (!text.startsWith("/") &&
|
|
3586
|
-
!text.startsWith("!") &&
|
|
3587
|
-
this.asyncQuestion &&
|
|
3588
|
-
this.submitAsyncQuestionComment(text))
|
|
3682
|
+
// Only an explicitly bound reply claims ordinary text; pre-arrival drafts and history stay chat.
|
|
3683
|
+
if (!text.startsWith("/") && !text.startsWith("!") && this.submitAsyncQuestionComment(text))
|
|
3589
3684
|
return;
|
|
3590
3685
|
// Handle commands
|
|
3591
3686
|
if (text === "/settings") {
|
|
@@ -3649,10 +3744,9 @@ export class InteractiveMode {
|
|
|
3649
3744
|
await this.handleKeybindingsCommand();
|
|
3650
3745
|
return;
|
|
3651
3746
|
}
|
|
3652
|
-
if (text === "/answer") {
|
|
3747
|
+
if (text === "/answer" || text.startsWith("/answer ")) {
|
|
3653
3748
|
this.editor.setText("");
|
|
3654
|
-
|
|
3655
|
-
this.showStatus("No question is pending.");
|
|
3749
|
+
await this.handleAnswerCommand(text.slice("/answer".length).trim());
|
|
3656
3750
|
return;
|
|
3657
3751
|
}
|
|
3658
3752
|
if (text === "/hotkeys") {
|
|
@@ -4594,7 +4688,8 @@ export class InteractiveMode {
|
|
|
4594
4688
|
}
|
|
4595
4689
|
}
|
|
4596
4690
|
else {
|
|
4597
|
-
const
|
|
4691
|
+
const answer = parseAskUserAnswerFrame(textContent);
|
|
4692
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers(), answer ? getAskUserAnswerHeaders(this.sessionManager.getBranch(), answer.requestId) : undefined);
|
|
4598
4693
|
this.chatContainer.addChild(userComponent);
|
|
4599
4694
|
}
|
|
4600
4695
|
if (options?.populateHistory) {
|
|
@@ -5191,6 +5286,7 @@ export class InteractiveMode {
|
|
|
5191
5286
|
}
|
|
5192
5287
|
}
|
|
5193
5288
|
async handleFollowUp() {
|
|
5289
|
+
this.setComposerReply();
|
|
5194
5290
|
const text = this.getExpandedEditorText().trim();
|
|
5195
5291
|
if (!text)
|
|
5196
5292
|
return;
|
|
@@ -7612,6 +7708,7 @@ export class InteractiveMode {
|
|
|
7612
7708
|
const followUp = this.getAppKeyDisplay("app.message.followUp");
|
|
7613
7709
|
const dequeue = this.getAppKeyDisplay("app.message.dequeue");
|
|
7614
7710
|
const answerQuestion = this.getAppKeyDisplay("app.question.answer");
|
|
7711
|
+
const nextQuestion = this.getAppKeyDisplay("app.question.next");
|
|
7615
7712
|
const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
|
|
7616
7713
|
let hotkeys = `
|
|
7617
7714
|
**Navigation**
|
|
@@ -7655,7 +7752,8 @@ export class InteractiveMode {
|
|
|
7655
7752
|
| \`${copyMessage}\` | Copy last assistant message |
|
|
7656
7753
|
| \`${followUp}\` | Queue follow-up message |
|
|
7657
7754
|
| \`${dequeue}\` | Restore queued messages |
|
|
7658
|
-
| \`${answerQuestion}\` | Open the pending question (also:
|
|
7755
|
+
| \`${answerQuestion}\` | Open the pending question before dequeue (also: empty Enter; /answer lists requests) |
|
|
7756
|
+
| \`${nextQuestion}\` | Cycle pending questions from an empty composer |
|
|
7659
7757
|
| \`${pasteImage}\` | Paste image or text from clipboard |
|
|
7660
7758
|
| \`/\` | Slash commands |
|
|
7661
7759
|
| \`!\` | Run bash command |
|