@code-yeongyu/senpi 2026.8.18-3 → 2026.8.19
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 +52 -0
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +33 -4
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +293 -55
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +3 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +20 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +182 -52
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.d.ts +3 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.js +18 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.d.ts +1 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.js +7 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/settings.d.ts +8 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/settings.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/settings.js +18 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/settings.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.d.ts +7 -0
- package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.js +10 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/index.js +12 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/index.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts +12 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js +20 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/settings.d.ts +11 -0
- package/dist/core/extensions/builtin/cursor-cli-oauth/settings.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/settings.js +7 -2
- package/dist/core/extensions/builtin/cursor-cli-oauth/settings.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +7 -2
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +2 -0
- package/dist/core/extensions/builtin/index.js.map +1 -1
- package/dist/core/extensions/builtin/loop/command-registration.d.ts +7 -0
- package/dist/core/extensions/builtin/loop/command-registration.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/command-registration.js +13 -0
- package/dist/core/extensions/builtin/loop/command-registration.js.map +1 -0
- package/dist/core/extensions/builtin/loop/command.d.ts +41 -0
- package/dist/core/extensions/builtin/loop/command.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/command.js +281 -0
- package/dist/core/extensions/builtin/loop/command.js.map +1 -0
- package/dist/core/extensions/builtin/loop/cron-planner.d.ts +23 -0
- package/dist/core/extensions/builtin/loop/cron-planner.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/cron-planner.js +88 -0
- package/dist/core/extensions/builtin/loop/cron-planner.js.map +1 -0
- package/dist/core/extensions/builtin/loop/index.d.ts +134 -0
- package/dist/core/extensions/builtin/loop/index.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/index.js +742 -0
- package/dist/core/extensions/builtin/loop/index.js.map +1 -0
- package/dist/core/extensions/builtin/loop/loopfile.d.ts +40 -0
- package/dist/core/extensions/builtin/loop/loopfile.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/loopfile.js +102 -0
- package/dist/core/extensions/builtin/loop/loopfile.js.map +1 -0
- package/dist/core/extensions/builtin/loop/parse.d.ts +45 -0
- package/dist/core/extensions/builtin/loop/parse.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/parse.js +92 -0
- package/dist/core/extensions/builtin/loop/parse.js.map +1 -0
- package/dist/core/extensions/builtin/loop/scheduler.d.ts +225 -0
- package/dist/core/extensions/builtin/loop/scheduler.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/scheduler.js +708 -0
- package/dist/core/extensions/builtin/loop/scheduler.js.map +1 -0
- package/dist/core/extensions/builtin/loop/status.d.ts +24 -0
- package/dist/core/extensions/builtin/loop/status.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/status.js +113 -0
- package/dist/core/extensions/builtin/loop/status.js.map +1 -0
- package/dist/core/extensions/builtin/loop/store.d.ts +49 -0
- package/dist/core/extensions/builtin/loop/store.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/store.js +424 -0
- package/dist/core/extensions/builtin/loop/store.js.map +1 -0
- package/dist/core/extensions/builtin/loop/tick-prompt.d.ts +64 -0
- package/dist/core/extensions/builtin/loop/tick-prompt.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/tick-prompt.js +183 -0
- package/dist/core/extensions/builtin/loop/tick-prompt.js.map +1 -0
- package/dist/core/extensions/builtin/loop/tools.d.ts +122 -0
- package/dist/core/extensions/builtin/loop/tools.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/tools.js +155 -0
- package/dist/core/extensions/builtin/loop/tools.js.map +1 -0
- package/dist/core/extensions/builtin/loop/types.d.ts +206 -0
- package/dist/core/extensions/builtin/loop/types.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/types.js +37 -0
- package/dist/core/extensions/builtin/loop/types.js.map +1 -0
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +10 -5
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +30 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +5 -10
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/model-registry.d.ts +2 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +4 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-runtime.d.ts +7 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +17 -0
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +8 -4
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/provider-composer.d.ts +8 -0
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +2 -1
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/retry-fallback/chains.d.ts +1 -0
- package/dist/core/retry-fallback/chains.d.ts.map +1 -1
- package/dist/core/retry-fallback/chains.js +3 -0
- package/dist/core/retry-fallback/chains.js.map +1 -1
- package/dist/core/retry-fallback/controller.d.ts +2 -0
- package/dist/core/retry-fallback/controller.d.ts.map +1 -1
- package/dist/core/retry-fallback/controller.js.map +1 -1
- package/dist/core/retry-fallback/expansion.d.ts +8 -0
- package/dist/core/retry-fallback/expansion.d.ts.map +1 -1
- package/dist/core/retry-fallback/expansion.js +2 -0
- package/dist/core/retry-fallback/expansion.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +14 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +51 -33
- package/dist/core/skills.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +17 -2
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/extensions/llama/index.d.ts.map +1 -1
- package/dist/extensions/llama/index.js +2 -0
- package/dist/extensions/llama/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +6 -6
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +4 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -4
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/editor-paste-transfer.d.ts +10 -1
- package/dist/modes/interactive/editor-paste-transfer.d.ts.map +1 -1
- package/dist/modes/interactive/editor-paste-transfer.js +27 -2
- package/dist/modes/interactive/editor-paste-transfer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +69 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +434 -63
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/tips/catalog/dag-tips.d.ts +7 -0
- package/dist/modes/interactive/tips/catalog/dag-tips.d.ts.map +1 -0
- package/dist/modes/interactive/tips/catalog/dag-tips.js +57 -0
- package/dist/modes/interactive/tips/catalog/dag-tips.js.map +1 -0
- package/dist/modes/interactive/tips/catalog/memory-tips.d.ts +7 -0
- package/dist/modes/interactive/tips/catalog/memory-tips.d.ts.map +1 -0
- package/dist/modes/interactive/tips/catalog/memory-tips.js +100 -0
- package/dist/modes/interactive/tips/catalog/memory-tips.js.map +1 -0
- package/dist/modes/interactive/tips/registry.d.ts.map +1 -1
- package/dist/modes/interactive/tips/registry.js +4 -0
- package/dist/modes/interactive/tips/registry.js.map +1 -1
- package/dist/utils/management-http.d.ts +5 -3
- package/dist/utils/management-http.d.ts.map +1 -1
- package/dist/utils/management-http.js +14 -10
- package/dist/utils/management-http.js.map +1 -1
- package/docs/compaction.md +17 -0
- package/docs/custom-provider.md +5 -2
- package/docs/extensions.md +11 -2
- package/docs/llama-cpp.md +2 -0
- package/docs/models.md +8 -2
- package/docs/providers.md +5 -3
- package/docs/settings.md +22 -2
- package/docs/skills.md +4 -3
- package/examples/extensions/border-status-editor.ts +1 -1
- package/examples/extensions/git-checkpoint.ts +2 -2
- package/examples/extensions/notify.ts +3 -1
- package/examples/extensions/subagent/index.ts +6 -1
- package/examples/extensions/titlebar-spinner.ts +1 -1
- package/examples/rpc-extension-ui.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts +3 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +51 -17
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +4 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +5 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.d.ts +3 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.js +13 -3
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js +14 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/README.md +5 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts +7 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +21 -4
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +7 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +39 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.d.ts +22 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +51 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +14 -11
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.d.ts +5 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.js +16 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +13 -10
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +4 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.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 +4 -1
- 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-completions.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +45 -29
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -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 +8 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js +18 -9
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js +22 -9
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/anthropic.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.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/qwen-token-plan-individual.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/providers/data/xai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.js +1 -5
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +28 -4
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.d.ts +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.js +7 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -3
- package/node_modules/@earendil-works/pi-pty/dist/loader.d.ts +6 -0
- package/node_modules/@earendil-works/pi-pty/dist/loader.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/loader.js +11 -0
- package/node_modules/@earendil-works/pi-pty/dist/loader.js.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/quarantine.d.ts +15 -0
- package/node_modules/@earendil-works/pi-pty/dist/quarantine.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-pty/dist/quarantine.js +27 -0
- package/node_modules/@earendil-works/pi-pty/dist/quarantine.js.map +1 -0
- 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/dist/components/editor.d.ts +73 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +173 -17
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/text.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/text.js +5 -4
- package/node_modules/@earendil-works/pi-tui/dist/components/text.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts +69 -0
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/image-markers.d.ts +46 -0
- package/node_modules/@earendil-works/pi-tui/dist/image-markers.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/image-markers.js +130 -0
- package/node_modules/@earendil-works/pi-tui/dist/image-markers.js.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js +2 -0
- package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/paste-markers.d.ts +3 -0
- package/node_modules/@earendil-works/pi-tui/dist/paste-markers.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/paste-markers.js +23 -7
- package/node_modules/@earendil-works/pi-tui/dist/paste-markers.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 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Interactive mode for the coding agent.
|
|
3
3
|
* Handles TUI rendering and user interaction, delegating business logic to AgentSession.
|
|
4
4
|
*/
|
|
5
|
-
import * as crypto from "node:crypto";
|
|
6
5
|
import * as fs from "node:fs";
|
|
7
6
|
import * as os from "node:os";
|
|
8
7
|
import * as path from "node:path";
|
|
@@ -36,8 +35,9 @@ import { getUsageCostBreakdown } from "../../core/usage-totals.js";
|
|
|
36
35
|
import { consumeEarlyInspectorVmImportRecoveries, INSPECTOR_VM_IMPORT_WARNING, isRecoverableInspectorVmImportError, } from "../../inspector-policy.js";
|
|
37
36
|
import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
|
|
38
37
|
import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
|
|
39
|
-
import {
|
|
38
|
+
import { readClipboardImage } from "../../utils/clipboard-image.js";
|
|
40
39
|
import { parseGitUrl } from "../../utils/git.js";
|
|
40
|
+
import { processImage } from "../../utils/image-process.js";
|
|
41
41
|
import { openBrowser } from "../../utils/open-browser.js";
|
|
42
42
|
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
43
43
|
import { getPiUserAgent } from "../../utils/pi-user-agent.js";
|
|
@@ -104,6 +104,11 @@ import { ToolArgsRevealController } from "./tool-args-reveal.js";
|
|
|
104
104
|
import { readToolProgress } from "./tool-progress.js";
|
|
105
105
|
import { ToolResultRevealController } from "./tool-result-reveal.js";
|
|
106
106
|
import { blendWorkingStatusShimmerRgbColor, formatActiveToolWorkingLabel, formatToolHookStatusMessageFrame, formatWorkingStatusMessageFrame, largeSessionWorkingStatusInterval, sanitizeWorkingStatusPlainText, } from "./working-status.js";
|
|
107
|
+
function llamaCppPostLoginGuidance(actionLabel, loadedModelCount) {
|
|
108
|
+
return loadedModelCount === 0
|
|
109
|
+
? `${actionLabel}. No llama.cpp models are loaded. Use /llama to load a model, then /model to select it.`
|
|
110
|
+
: `${actionLabel}. Use /model to select a loaded llama.cpp model, or /llama to manage models.`;
|
|
111
|
+
}
|
|
107
112
|
function isExpandable(obj) {
|
|
108
113
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
109
114
|
}
|
|
@@ -133,6 +138,9 @@ function formatToolHookTerminalTitle(event) {
|
|
|
133
138
|
function isCustomSessionEntry(item) {
|
|
134
139
|
return "type" in item && item.type === "custom";
|
|
135
140
|
}
|
|
141
|
+
function isCompactionCostNotice(item) {
|
|
142
|
+
return "type" in item && item.type === "compaction_cost";
|
|
143
|
+
}
|
|
136
144
|
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
137
145
|
const DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS = 600;
|
|
138
146
|
const DEFAULT_WORKING_STATUS_MESSAGE_ANIMATION_INTERVAL_MS = 32;
|
|
@@ -140,10 +148,26 @@ const LARGE_SESSION_WORKING_STATUS_REFRESH_INTERVAL_MS = 60_000;
|
|
|
140
148
|
const LARGE_SESSION_WORKING_STATUS_MESSAGE_INTERVAL_MS = 1_000;
|
|
141
149
|
const FALLBACK_STATUS_KEY = "fallback";
|
|
142
150
|
const RGB_FOREGROUND_PATTERN = /\x1b\[38;2;(\d+);(\d+);(\d+)m/;
|
|
143
|
-
const DARK_DEFAULT_WORKING_TEXT_RGB = {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
const DARK_DEFAULT_WORKING_TEXT_RGB = {
|
|
152
|
+
r: 229,
|
|
153
|
+
g: 229,
|
|
154
|
+
b: 231,
|
|
155
|
+
};
|
|
156
|
+
const LIGHT_DEFAULT_WORKING_TEXT_RGB = {
|
|
157
|
+
r: 17,
|
|
158
|
+
g: 17,
|
|
159
|
+
b: 17,
|
|
160
|
+
};
|
|
161
|
+
const DARK_DEFAULT_WORKING_BASE_RGB = {
|
|
162
|
+
r: 102,
|
|
163
|
+
g: 102,
|
|
164
|
+
b: 102,
|
|
165
|
+
};
|
|
166
|
+
const LIGHT_DEFAULT_WORKING_BASE_RGB = {
|
|
167
|
+
r: 118,
|
|
168
|
+
g: 118,
|
|
169
|
+
b: 118,
|
|
170
|
+
};
|
|
147
171
|
function parseAnsiRgbForeground(ansi) {
|
|
148
172
|
const match = RGB_FOREGROUND_PATTERN.exec(ansi);
|
|
149
173
|
const red = match?.[1];
|
|
@@ -258,6 +282,63 @@ function formatLoginProviderCompletionDescription(provider) {
|
|
|
258
282
|
const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
|
|
259
283
|
return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
|
|
260
284
|
}
|
|
285
|
+
/** Local copy of pi-tui's image-marker pattern so submission scanning never mutates a shared /g regex. */
|
|
286
|
+
const IMAGE_MARKER_PATTERN = /\[Image #([1-9]\d*)\]/g;
|
|
287
|
+
/**
|
|
288
|
+
* Attach a clipboard bitmap as an in-memory image behind an atomic
|
|
289
|
+
* `[Image #N]` marker. Returns false when nothing was attached, so the caller
|
|
290
|
+
* falls through to the plain-text clipboard path.
|
|
291
|
+
*
|
|
292
|
+
* The bytes deliberately never touch the filesystem: writing a temp file and
|
|
293
|
+
* inserting its path as literal text (the previous behavior) shipped an
|
|
294
|
+
* unreadable `/var/folders/.../pi-clipboard-<uuid>.png` string to the model and
|
|
295
|
+
* attached no image at all.
|
|
296
|
+
*/
|
|
297
|
+
async function attachClipboardImage(deps, image) {
|
|
298
|
+
if (deps.settings.getBlockImages()) {
|
|
299
|
+
// Pinned behavior: nothing is attached and nothing is inserted, but the
|
|
300
|
+
// paste is never a silent no-op - the user must learn why their screenshot
|
|
301
|
+
// vanished, and which setting to flip.
|
|
302
|
+
deps.showStatus("Image paste blocked by the images.blockImages setting");
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
if (!deps.editor.insertImageMarker) {
|
|
306
|
+
// A marker-unaware editor cannot keep the marker atomic, and a dead literal
|
|
307
|
+
// `[Image #N]` with a live payload behind it would misnumber every other
|
|
308
|
+
// attachment at submit.
|
|
309
|
+
deps.showStatus("Image paste is not supported by the active editor");
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
if (deps.isCompacting()) {
|
|
313
|
+
// The compaction queue carries text only; an attachment queued behind
|
|
314
|
+
// compaction would be silently lost at delivery, so drop it here with a
|
|
315
|
+
// visible status. Consume the paste (true) - the clipboard holds a
|
|
316
|
+
// bitmap, so the plain-text fallback has nothing useful to insert.
|
|
317
|
+
deps.showStatus("Image paste dropped: messages sent during compaction cannot carry images - paste again after compaction finishes");
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
const processed = await processImage(image.bytes, image.mimeType, {
|
|
321
|
+
autoResizeImages: deps.settings.getImageAutoResize(),
|
|
322
|
+
});
|
|
323
|
+
if (!processed.ok) {
|
|
324
|
+
deps.showStatus(processed.message);
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
// insertImageMarker fires onImageMarkersChanged (with the pre-renumber
|
|
328
|
+
// ids) synchronously and only then returns the marker's FINAL canonical id,
|
|
329
|
+
// whose slot the reconcile pass just vacated - so this write can neither
|
|
330
|
+
// land in an orphaned map (reconcile mutates pendingImages in place) nor
|
|
331
|
+
// overwrite a surviving payload (the new marker had no payload when the
|
|
332
|
+
// reconcile ran, so nothing was keyed onto its slot).
|
|
333
|
+
const id = deps.editor.insertImageMarker();
|
|
334
|
+
deps.pendingImages.set(id, {
|
|
335
|
+
type: "image",
|
|
336
|
+
data: processed.data,
|
|
337
|
+
mimeType: processed.mimeType,
|
|
338
|
+
});
|
|
339
|
+
deps.requestRender();
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
261
342
|
/** Composition root for selecting the interactive terminal renderer. */
|
|
262
343
|
export function createInteractiveTui(options) {
|
|
263
344
|
const terminal = options.terminal ?? new ProcessTerminal({ onExternalStdoutWrite: appendHiddenTuiStdout });
|
|
@@ -348,6 +429,14 @@ export class InteractiveMode {
|
|
|
348
429
|
this.autocompleteProviderWrappers = [];
|
|
349
430
|
this.isInitialized = false;
|
|
350
431
|
this.pendingUserInputs = [];
|
|
432
|
+
/**
|
|
433
|
+
* Clipboard images pasted into the composer, keyed by their visible
|
|
434
|
+
* `[Image #N]` marker number. The editor stores marker ids only, so these
|
|
435
|
+
* bytes must live here (and survive an editor swap, which discards the
|
|
436
|
+
* editor instance). Kept aligned with the displayed numbers by
|
|
437
|
+
* {@link reconcilePendingImages}.
|
|
438
|
+
*/
|
|
439
|
+
this.pendingImages = new Map();
|
|
351
440
|
this.activeStatusIndicator = undefined;
|
|
352
441
|
this.idleStatus = new IdleStatus();
|
|
353
442
|
this.workingMessage = undefined;
|
|
@@ -777,7 +866,13 @@ export class InteractiveMode {
|
|
|
777
866
|
{ component: this.footerContainer, shrink: 1, minSize: 1 },
|
|
778
867
|
]);
|
|
779
868
|
this.fullscreenLayoutRoot = new TuiLayouts.VStack([
|
|
780
|
-
{
|
|
869
|
+
{
|
|
870
|
+
component: this.transcriptScrollView,
|
|
871
|
+
basis: 0,
|
|
872
|
+
grow: 1,
|
|
873
|
+
shrink: 1,
|
|
874
|
+
minSize: 1,
|
|
875
|
+
},
|
|
781
876
|
{ component: dock, basis: "auto", grow: 0, shrink: 1, minSize: 1 },
|
|
782
877
|
]);
|
|
783
878
|
const rootComponents = [
|
|
@@ -1016,7 +1111,10 @@ export class InteractiveMode {
|
|
|
1016
1111
|
while (true) {
|
|
1017
1112
|
const userInput = await this.getUserInput();
|
|
1018
1113
|
try {
|
|
1019
|
-
await this.session.prompt(userInput, {
|
|
1114
|
+
await this.session.prompt(userInput.text, {
|
|
1115
|
+
streamingBehavior: "steer",
|
|
1116
|
+
...(userInput.images ? { images: userInput.images } : {}),
|
|
1117
|
+
});
|
|
1020
1118
|
}
|
|
1021
1119
|
catch (error) {
|
|
1022
1120
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
@@ -1173,7 +1271,10 @@ export class InteractiveMode {
|
|
|
1173
1271
|
return;
|
|
1174
1272
|
}
|
|
1175
1273
|
const seeded = seedKeybindingsFile(configPath, this.keybindings);
|
|
1176
|
-
const edit = await editFileInExternalEditor({
|
|
1274
|
+
const edit = await editFileInExternalEditor({
|
|
1275
|
+
command: editorCommand,
|
|
1276
|
+
path: configPath,
|
|
1277
|
+
});
|
|
1177
1278
|
if (edit.status === "launch-failed") {
|
|
1178
1279
|
// The editor never ran, so a file we just seeded carries no user content.
|
|
1179
1280
|
if (seeded)
|
|
@@ -1356,7 +1457,11 @@ export class InteractiveMode {
|
|
|
1356
1457
|
return { label: "path", color: "muted" };
|
|
1357
1458
|
}
|
|
1358
1459
|
if (source === "cli") {
|
|
1359
|
-
return {
|
|
1460
|
+
return {
|
|
1461
|
+
label: "path",
|
|
1462
|
+
scopeLabel: scope === "temporary" ? "temp" : undefined,
|
|
1463
|
+
color: "muted",
|
|
1464
|
+
};
|
|
1360
1465
|
}
|
|
1361
1466
|
const scopeLabel = scope === "user" ? "user" : scope === "project" ? "project" : scope === "temporary" ? "temp" : undefined;
|
|
1362
1467
|
return { label: source, scopeLabel, color: "accent" };
|
|
@@ -1548,7 +1653,10 @@ export class InteractiveMode {
|
|
|
1548
1653
|
}
|
|
1549
1654
|
const skills = skillsResult.skills;
|
|
1550
1655
|
if (skills.length > 0) {
|
|
1551
|
-
const groups = this.buildScopeGroups(skills.map((skill) => ({
|
|
1656
|
+
const groups = this.buildScopeGroups(skills.map((skill) => ({
|
|
1657
|
+
path: skill.filePath,
|
|
1658
|
+
sourceInfo: skill.sourceInfo,
|
|
1659
|
+
})));
|
|
1552
1660
|
const skillList = this.formatScopeGroups(groups, {
|
|
1553
1661
|
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
1554
1662
|
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
@@ -1558,7 +1666,10 @@ export class InteractiveMode {
|
|
|
1558
1666
|
}
|
|
1559
1667
|
const templates = this.session.promptTemplates;
|
|
1560
1668
|
if (templates.length > 0) {
|
|
1561
|
-
const groups = this.buildScopeGroups(templates.map((template) => ({
|
|
1669
|
+
const groups = this.buildScopeGroups(templates.map((template) => ({
|
|
1670
|
+
path: template.filePath,
|
|
1671
|
+
sourceInfo: template.sourceInfo,
|
|
1672
|
+
})));
|
|
1562
1673
|
const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
|
|
1563
1674
|
const templateList = this.formatScopeGroups(groups, {
|
|
1564
1675
|
formatPath: (item) => {
|
|
@@ -1615,7 +1726,11 @@ export class InteractiveMode {
|
|
|
1615
1726
|
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
|
1616
1727
|
if (extensionErrors.length > 0) {
|
|
1617
1728
|
for (const error of extensionErrors) {
|
|
1618
|
-
extensionDiagnostics.push({
|
|
1729
|
+
extensionDiagnostics.push({
|
|
1730
|
+
type: "error",
|
|
1731
|
+
message: error.error,
|
|
1732
|
+
path: error.path,
|
|
1733
|
+
});
|
|
1619
1734
|
}
|
|
1620
1735
|
}
|
|
1621
1736
|
const commandDiagnostics = this.session.extensionRunner.getCommandDiagnostics();
|
|
@@ -2312,7 +2427,10 @@ export class InteractiveMode {
|
|
|
2312
2427
|
this.ui.requestRender();
|
|
2313
2428
|
}
|
|
2314
2429
|
addExtensionTerminalInputListener(handler) {
|
|
2315
|
-
const subscription = {
|
|
2430
|
+
const subscription = {
|
|
2431
|
+
handler,
|
|
2432
|
+
unsubscribe: this.ui.addInputListener(handler),
|
|
2433
|
+
};
|
|
2316
2434
|
this.extensionTerminalInputSubscriptions.add(subscription);
|
|
2317
2435
|
return () => {
|
|
2318
2436
|
subscription.unsubscribe();
|
|
@@ -2423,7 +2541,11 @@ export class InteractiveMode {
|
|
|
2423
2541
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
2424
2542
|
this.hideExtensionSelector();
|
|
2425
2543
|
resolve(undefined);
|
|
2426
|
-
}, {
|
|
2544
|
+
}, {
|
|
2545
|
+
tui: this.ui,
|
|
2546
|
+
timeout: opts?.timeout,
|
|
2547
|
+
onToggleToolsExpanded: () => this.toggleToolOutputExpansion(),
|
|
2548
|
+
});
|
|
2427
2549
|
this.disposeActiveSelector();
|
|
2428
2550
|
this.editorContainer.clear();
|
|
2429
2551
|
this.editorContainer.addChild(this.extensionSelector);
|
|
@@ -2556,8 +2678,13 @@ export class InteractiveMode {
|
|
|
2556
2678
|
this.defaultEditor.onSubmit?.(expandSubmittedText(newEditor, text));
|
|
2557
2679
|
};
|
|
2558
2680
|
newEditor.onChange = this.defaultEditor.onChange;
|
|
2559
|
-
// Copy text (and any collapsed paste markers) from previous editor
|
|
2560
|
-
|
|
2681
|
+
// Copy text (and any collapsed paste/image markers) from previous editor.
|
|
2682
|
+
// Image payloads live on this instance, so they must be dropped when the
|
|
2683
|
+
// destination cannot own their markers.
|
|
2684
|
+
if (!transferEditorContent(this.editor, newEditor).imageMarkersTransferred) {
|
|
2685
|
+
this.pendingImages.clear();
|
|
2686
|
+
}
|
|
2687
|
+
this.subscribeImageMarkers(newEditor);
|
|
2561
2688
|
// Copy appearance settings if supported
|
|
2562
2689
|
if (newEditor.borderColor !== undefined) {
|
|
2563
2690
|
newEditor.borderColor = this.defaultEditor.borderColor;
|
|
@@ -2600,7 +2727,10 @@ export class InteractiveMode {
|
|
|
2600
2727
|
// hand-off, and a setText round-trip would be pure churn on the
|
|
2601
2728
|
// user's draft.
|
|
2602
2729
|
if (this.editor !== this.defaultEditor) {
|
|
2603
|
-
transferEditorContent(this.editor, this.defaultEditor)
|
|
2730
|
+
if (!transferEditorContent(this.editor, this.defaultEditor).imageMarkersTransferred) {
|
|
2731
|
+
this.pendingImages.clear();
|
|
2732
|
+
}
|
|
2733
|
+
this.subscribeImageMarkers(this.defaultEditor);
|
|
2604
2734
|
}
|
|
2605
2735
|
this.editor = this.defaultEditor;
|
|
2606
2736
|
}
|
|
@@ -2787,8 +2917,11 @@ export class InteractiveMode {
|
|
|
2787
2917
|
}
|
|
2788
2918
|
this.updateShortcutOverlay(text);
|
|
2789
2919
|
};
|
|
2790
|
-
//
|
|
2791
|
-
|
|
2920
|
+
// Keep pendingImages aligned with the markers the editor displays.
|
|
2921
|
+
this.subscribeImageMarkers(this.defaultEditor);
|
|
2922
|
+
// Handle clipboard paste (triggered on Ctrl+V). Images are attached in
|
|
2923
|
+
// memory behind an atomic `[Image #N]` marker; otherwise, paste plain text
|
|
2924
|
+
// from the system clipboard.
|
|
2792
2925
|
this.defaultEditor.onPasteImage = () => {
|
|
2793
2926
|
this.lastInputWasPaste = true;
|
|
2794
2927
|
void this.handleClipboardPaste();
|
|
@@ -2818,16 +2951,17 @@ export class InteractiveMode {
|
|
|
2818
2951
|
async handleClipboardPaste() {
|
|
2819
2952
|
try {
|
|
2820
2953
|
const image = await readClipboardImage();
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2954
|
+
const attached = image &&
|
|
2955
|
+
(await attachClipboardImage({
|
|
2956
|
+
editor: this.editor,
|
|
2957
|
+
pendingImages: this.pendingImages,
|
|
2958
|
+
settings: this.settingsManager,
|
|
2959
|
+
isCompacting: () => this.session?.isCompacting === true,
|
|
2960
|
+
showStatus: (message) => this.showStatus(message),
|
|
2961
|
+
requestRender: () => this.ui.requestRender(),
|
|
2962
|
+
}, image));
|
|
2963
|
+
if (attached)
|
|
2829
2964
|
return;
|
|
2830
|
-
}
|
|
2831
2965
|
const text = await readClipboardText();
|
|
2832
2966
|
if (text) {
|
|
2833
2967
|
this.editor.insertTextAtCursor?.(text);
|
|
@@ -2836,10 +2970,89 @@ export class InteractiveMode {
|
|
|
2836
2970
|
}
|
|
2837
2971
|
catch (error) {
|
|
2838
2972
|
const message = error instanceof Error ? error.message : String(error);
|
|
2839
|
-
this.getSessionLogger().warn("clipboard_error", {
|
|
2973
|
+
this.getSessionLogger().warn("clipboard_error", {
|
|
2974
|
+
op: "paste",
|
|
2975
|
+
error: message,
|
|
2976
|
+
});
|
|
2840
2977
|
this.showStatus(`Clipboard paste failed: ${sanitizeTuiErrorMessage(message)}`);
|
|
2841
2978
|
}
|
|
2842
2979
|
}
|
|
2980
|
+
/** Route an editor's image-marker changes into {@link reconcilePendingImages}. */
|
|
2981
|
+
subscribeImageMarkers(editor) {
|
|
2982
|
+
if (!editor.insertImageMarker)
|
|
2983
|
+
return;
|
|
2984
|
+
editor.onImageMarkersChanged = (order) => this.reconcilePendingImages(order);
|
|
2985
|
+
// The editor's undo stack restores marker TEXT and registry ids, but the
|
|
2986
|
+
// payloads live HERE; mirror them into every undo snapshot so undo restores
|
|
2987
|
+
// both halves of the pairing. Without this, deleting a marker re-keys the
|
|
2988
|
+
// survivors onto its number and a later undo re-displays the deleted marker
|
|
2989
|
+
// with no (or the wrong) payload behind it.
|
|
2990
|
+
editor.snapshotAttachmentState = () => new Map(this.pendingImages);
|
|
2991
|
+
editor.restoreAttachmentState = (state) => {
|
|
2992
|
+
if (!(state instanceof Map))
|
|
2993
|
+
return;
|
|
2994
|
+
this.pendingImages.clear();
|
|
2995
|
+
for (const [key, image] of state) {
|
|
2996
|
+
this.pendingImages.set(key, image);
|
|
2997
|
+
}
|
|
2998
|
+
};
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* Re-key {@link pendingImages} onto the marker numbers the editor now
|
|
3002
|
+
* displays. `order` lists the SURVIVING marker ids (pre-renumber, i.e. the
|
|
3003
|
+
* keys the payloads currently sit under) in text reading order, and the
|
|
3004
|
+
* editor keeps the visible numbers canonical 1..k in reading order, so a
|
|
3005
|
+
* payload's new key is its position in that list. Ids absent from `order`
|
|
3006
|
+
* are dropped; reported ids with no payload (a hand-typed marker) are skipped
|
|
3007
|
+
* without consuming anyone else's slot.
|
|
3008
|
+
*
|
|
3009
|
+
* The map is mutated IN PLACE, never replaced: handleClipboardPaste hands
|
|
3010
|
+
* this.pendingImages by reference into attachClipboardImage's deps, and a
|
|
3011
|
+
* fresh identity would orphan that reference - the second paste in a turn
|
|
3012
|
+
* then wrote into the dead map and silently destroyed its image.
|
|
3013
|
+
*/
|
|
3014
|
+
reconcilePendingImages(order) {
|
|
3015
|
+
if (this.pendingImages.size === 0)
|
|
3016
|
+
return;
|
|
3017
|
+
const reconciled = new Map();
|
|
3018
|
+
order.forEach((id, index) => {
|
|
3019
|
+
const image = this.pendingImages.get(id);
|
|
3020
|
+
if (image)
|
|
3021
|
+
reconciled.set(index + 1, image);
|
|
3022
|
+
});
|
|
3023
|
+
this.pendingImages.clear();
|
|
3024
|
+
for (const [key, image] of reconciled) {
|
|
3025
|
+
this.pendingImages.set(key, image);
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* Resolve the `[Image #N]` markers in `submittedText` (in READING order)
|
|
3030
|
+
* into the image array submitted alongside it, then clear
|
|
3031
|
+
* {@link pendingImages}.
|
|
3032
|
+
*
|
|
3033
|
+
* Reads ONLY the submitted text plus pendingImages - never live editor
|
|
3034
|
+
* state: pi-tui's `Editor.submitValue()` resets the editor and clears its
|
|
3035
|
+
* registries BEFORE `onSubmit` fires. A marker with no pending payload (a
|
|
3036
|
+
* hand-typed `[Image #N]`, or the second half of a kill/yank duplicate) is
|
|
3037
|
+
* passed through untouched and consumes no slot; a payload-bearing
|
|
3038
|
+
* marker's FIRST occurrence wins. Slots are assigned 1..k in reading
|
|
3039
|
+
* order, so the Nth marker in the final text is `images[N-1]`.
|
|
3040
|
+
*/
|
|
3041
|
+
takeSubmissionImages(submittedText) {
|
|
3042
|
+
const images = [];
|
|
3043
|
+
const consumed = new Set();
|
|
3044
|
+
for (const match of submittedText.matchAll(IMAGE_MARKER_PATTERN)) {
|
|
3045
|
+
const id = Number.parseInt(match[1] ?? "0", 10);
|
|
3046
|
+
if (consumed.has(id))
|
|
3047
|
+
continue;
|
|
3048
|
+
consumed.add(id);
|
|
3049
|
+
const image = this.pendingImages.get(id);
|
|
3050
|
+
if (image)
|
|
3051
|
+
images.push(image);
|
|
3052
|
+
}
|
|
3053
|
+
this.pendingImages.clear();
|
|
3054
|
+
return images;
|
|
3055
|
+
}
|
|
2843
3056
|
getSessionLogger() {
|
|
2844
3057
|
this.sessionLogger ??= createSessionLogger(this.runtimeHost.services.agentDir);
|
|
2845
3058
|
return this.sessionLogger;
|
|
@@ -2850,6 +3063,13 @@ export class InteractiveMode {
|
|
|
2850
3063
|
}
|
|
2851
3064
|
setupEditorSubmitHandler() {
|
|
2852
3065
|
this.defaultEditor.onSubmit = async (text) => {
|
|
3066
|
+
// Capture-then-clear BEFORE any branch: handleFollowUp's non-streaming
|
|
3067
|
+
// path pre-resolves images and hands off here, but slash / extension /
|
|
3068
|
+
// bash submissions return before the consuming branches below. Clearing
|
|
3069
|
+
// only after use would leak a stale array into a later ordinary
|
|
3070
|
+
// submission whose text never references it.
|
|
3071
|
+
const preResolvedImages = this.preResolvedSubmissionImages;
|
|
3072
|
+
this.preResolvedSubmissionImages = undefined;
|
|
2853
3073
|
this.hideShortcutOverlay();
|
|
2854
3074
|
this.lastEditorText = "";
|
|
2855
3075
|
text = text.trim();
|
|
@@ -3033,7 +3253,7 @@ export class InteractiveMode {
|
|
|
3033
3253
|
await this.session.prompt(text);
|
|
3034
3254
|
}
|
|
3035
3255
|
else {
|
|
3036
|
-
this.
|
|
3256
|
+
this.queueCompactionSubmission(text, "steer");
|
|
3037
3257
|
}
|
|
3038
3258
|
return;
|
|
3039
3259
|
}
|
|
@@ -3043,9 +3263,15 @@ export class InteractiveMode {
|
|
|
3043
3263
|
// behavior here applies only to ordinary text, prompt template expansion,
|
|
3044
3264
|
// and queueing.
|
|
3045
3265
|
if (this.session.isStreaming) {
|
|
3266
|
+
// Resolve BEFORE setText(""): the editor's prune chain fires
|
|
3267
|
+
// onImageMarkersChanged([]) and destroys pendingImages.
|
|
3268
|
+
const images = preResolvedImages ?? this.takeSubmissionImages(text);
|
|
3046
3269
|
this.editor.addToHistory?.(text);
|
|
3047
3270
|
this.editor.setText("");
|
|
3048
|
-
await this.session.prompt(text, {
|
|
3271
|
+
await this.session.prompt(text, {
|
|
3272
|
+
streamingBehavior: "steer",
|
|
3273
|
+
...(images.length > 0 ? { images } : {}),
|
|
3274
|
+
});
|
|
3049
3275
|
this.updatePendingMessagesDisplay();
|
|
3050
3276
|
this.ui.requestRender();
|
|
3051
3277
|
return;
|
|
@@ -3053,11 +3279,13 @@ export class InteractiveMode {
|
|
|
3053
3279
|
// Normal message submission
|
|
3054
3280
|
// First, move any pending bash components to chat
|
|
3055
3281
|
this.flushPendingBashComponents();
|
|
3282
|
+
const images = preResolvedImages ?? this.takeSubmissionImages(text);
|
|
3283
|
+
const submission = images.length > 0 ? { text, images } : { text };
|
|
3056
3284
|
if (this.onInputCallback) {
|
|
3057
|
-
this.onInputCallback(
|
|
3285
|
+
this.onInputCallback(submission);
|
|
3058
3286
|
}
|
|
3059
3287
|
else {
|
|
3060
|
-
this.pendingUserInputs.push(
|
|
3288
|
+
this.pendingUserInputs.push(submission);
|
|
3061
3289
|
}
|
|
3062
3290
|
this.editor.addToHistory?.(text);
|
|
3063
3291
|
};
|
|
@@ -3248,7 +3476,9 @@ export class InteractiveMode {
|
|
|
3248
3476
|
}
|
|
3249
3477
|
const activity = readToolProgress(event.partialResult.details)?.activity;
|
|
3250
3478
|
if (activity) {
|
|
3251
|
-
const label = formatActiveToolWorkingLabel(event.toolName, {
|
|
3479
|
+
const label = formatActiveToolWorkingLabel(event.toolName, {
|
|
3480
|
+
command: activity,
|
|
3481
|
+
});
|
|
3252
3482
|
this.activeToolExecutions.set(event.toolCallId, label);
|
|
3253
3483
|
this.workingMessage = label;
|
|
3254
3484
|
this.activeToolExecutionTerminalTitle = `${APP_TITLE} - ${label}`;
|
|
@@ -3351,9 +3581,35 @@ export class InteractiveMode {
|
|
|
3351
3581
|
}
|
|
3352
3582
|
}
|
|
3353
3583
|
else if (event.result) {
|
|
3584
|
+
// Compaction event consumers in the fork are session-backed and do not
|
|
3585
|
+
// necessarily expose InteractiveMode's SessionManager convenience getter.
|
|
3586
|
+
// Keep the structural fallback for focused handler consumers while using
|
|
3587
|
+
// the session-owned manager on the real TUI path.
|
|
3588
|
+
const sessionManager = this.session.sessionManager ?? this.sessionManager;
|
|
3589
|
+
const entries = sessionManager.buildContextEntries();
|
|
3590
|
+
if (entries[0]?.type !== "compaction") {
|
|
3591
|
+
throw new Error("Completed compaction is missing from the session context");
|
|
3592
|
+
}
|
|
3354
3593
|
this.chatContainer.clear();
|
|
3355
|
-
|
|
3356
|
-
|
|
3594
|
+
const summaryMessage = createCompactionSummaryMessage(sanitizeTerminalLabel(event.result.summary), event.result.tokensBefore, new Date().toISOString(), event.result.details);
|
|
3595
|
+
if (typeof this.renderSessionEntries === "function") {
|
|
3596
|
+
// The latest compaction is prepended for model context; append it below at its chronological position.
|
|
3597
|
+
this.renderSessionEntries(entries.slice(1));
|
|
3598
|
+
this.addMessageToChat(summaryMessage);
|
|
3599
|
+
if (event.result.usage) {
|
|
3600
|
+
this.addCompactionCostNotice({
|
|
3601
|
+
type: "compaction_cost",
|
|
3602
|
+
kind: "compaction",
|
|
3603
|
+
usage: event.result.usage,
|
|
3604
|
+
});
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
else {
|
|
3608
|
+
// Fork-owned compaction consumers expose the established rebuild helper,
|
|
3609
|
+
// not InteractiveMode's private entry renderer.
|
|
3610
|
+
this.rebuildChatFromMessages();
|
|
3611
|
+
this.addMessageToChat(summaryMessage);
|
|
3612
|
+
}
|
|
3357
3613
|
this.footer.invalidate();
|
|
3358
3614
|
}
|
|
3359
3615
|
else if (event.errorMessage) {
|
|
@@ -3389,7 +3645,10 @@ export class InteractiveMode {
|
|
|
3389
3645
|
// over-threshold context would just repeat the failure.
|
|
3390
3646
|
const compactionSucceeded = event.accepted === true || event.result !== undefined;
|
|
3391
3647
|
if (compactionSucceeded) {
|
|
3392
|
-
void this.flushCompactionQueue({
|
|
3648
|
+
void this.flushCompactionQueue({
|
|
3649
|
+
willRetry: event.willRetry,
|
|
3650
|
+
deferAdmission: false,
|
|
3651
|
+
});
|
|
3393
3652
|
}
|
|
3394
3653
|
else if (event.willRetry === true) {
|
|
3395
3654
|
const heldCount = this.compactionQueuedMessages.length;
|
|
@@ -3401,7 +3660,10 @@ export class InteractiveMode {
|
|
|
3401
3660
|
});
|
|
3402
3661
|
this.showStatus(`${heldCount} queued message${heldCount === 1 ? "" : "s"} will send with the next turn (compaction will retry)`);
|
|
3403
3662
|
}
|
|
3404
|
-
void this.flushCompactionQueue({
|
|
3663
|
+
void this.flushCompactionQueue({
|
|
3664
|
+
willRetry: true,
|
|
3665
|
+
deferAdmission: true,
|
|
3666
|
+
});
|
|
3405
3667
|
}
|
|
3406
3668
|
else {
|
|
3407
3669
|
const restoredCount = this.restoreQueuedMessagesToEditor();
|
|
@@ -3731,6 +3993,10 @@ export class InteractiveMode {
|
|
|
3731
3993
|
this.addCustomEntryToChat(item);
|
|
3732
3994
|
continue;
|
|
3733
3995
|
}
|
|
3996
|
+
if (isCompactionCostNotice(item)) {
|
|
3997
|
+
this.addCompactionCostNotice(item);
|
|
3998
|
+
continue;
|
|
3999
|
+
}
|
|
3734
4000
|
const message = item;
|
|
3735
4001
|
// Assistant messages need special handling for tool calls
|
|
3736
4002
|
if (message.role === "assistant") {
|
|
@@ -3749,7 +4015,10 @@ export class InteractiveMode {
|
|
|
3749
4015
|
else {
|
|
3750
4016
|
errorMessage = message.errorMessage || "Error";
|
|
3751
4017
|
}
|
|
3752
|
-
component.updateResult({
|
|
4018
|
+
component.updateResult({
|
|
4019
|
+
content: [{ type: "text", text: errorMessage }],
|
|
4020
|
+
isError: true,
|
|
4021
|
+
});
|
|
3753
4022
|
}
|
|
3754
4023
|
else {
|
|
3755
4024
|
renderedPendingTools.set(content.id, component);
|
|
@@ -3795,10 +4064,28 @@ export class InteractiveMode {
|
|
|
3795
4064
|
if (entry.type === "custom") {
|
|
3796
4065
|
return [entry];
|
|
3797
4066
|
}
|
|
3798
|
-
|
|
4067
|
+
const messages = sessionEntryToContextMessages(entry);
|
|
4068
|
+
if ((entry.type === "compaction" || entry.type === "branch_summary") && entry.usage && messages.length > 0) {
|
|
4069
|
+
return [...messages, { type: "compaction_cost", kind: entry.type, usage: entry.usage }];
|
|
4070
|
+
}
|
|
4071
|
+
return messages;
|
|
3799
4072
|
});
|
|
3800
4073
|
this.renderSessionItems(items, options);
|
|
3801
4074
|
}
|
|
4075
|
+
/**
|
|
4076
|
+
* Render billing usage for a compaction or branch summary. The notice is derived
|
|
4077
|
+
* from persisted summary usage and is not stored as a separate session entry.
|
|
4078
|
+
*/
|
|
4079
|
+
addCompactionCostNotice(notice) {
|
|
4080
|
+
if (!this.settingsManager.getShowCacheMissNotices())
|
|
4081
|
+
return;
|
|
4082
|
+
const { usage } = notice;
|
|
4083
|
+
const tokens = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
4084
|
+
const cost = usage.cost.total >= 0.01 ? ` (~$${usage.cost.total.toFixed(2)})` : "";
|
|
4085
|
+
const label = notice.kind === "compaction" ? "Compaction" : "Branch summary";
|
|
4086
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4087
|
+
this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${formatTokens(tokens)} tokens billed${cost}`), 1, 0));
|
|
4088
|
+
}
|
|
3802
4089
|
/**
|
|
3803
4090
|
* Show a transcript notice when a completed assistant message paid for a
|
|
3804
4091
|
* significant cache miss. Only states observable facts: the miss itself,
|
|
@@ -3865,9 +4152,9 @@ export class InteractiveMode {
|
|
|
3865
4152
|
return queuedInput;
|
|
3866
4153
|
}
|
|
3867
4154
|
return new Promise((resolve) => {
|
|
3868
|
-
this.onInputCallback = (
|
|
4155
|
+
this.onInputCallback = (input) => {
|
|
3869
4156
|
this.onInputCallback = undefined;
|
|
3870
|
-
resolve(
|
|
4157
|
+
resolve(input);
|
|
3871
4158
|
};
|
|
3872
4159
|
});
|
|
3873
4160
|
}
|
|
@@ -4070,7 +4357,10 @@ export class InteractiveMode {
|
|
|
4070
4357
|
// does NOT pass through onSubmit, so the isExtensionCommand check below is the
|
|
4071
4358
|
// live dispatch point here: commands go straight to AgentSession.prompt(),
|
|
4072
4359
|
// which runs them immediately even while compaction is active, while ordinary
|
|
4073
|
-
// text is queued for delivery after compaction settles.
|
|
4360
|
+
// text is queued for delivery after compaction settles. Image attachments
|
|
4361
|
+
// are consumed and dropped VISIBLY inside queueCompactionSubmission - the
|
|
4362
|
+
// queue is text-only, so resolving them here and queueing the text with a
|
|
4363
|
+
// dead marker would lose the images without telling the user.
|
|
4074
4364
|
if (this.session.isCompacting) {
|
|
4075
4365
|
if (this.isExtensionCommand(text)) {
|
|
4076
4366
|
this.editor.addToHistory?.(text);
|
|
@@ -4078,10 +4368,15 @@ export class InteractiveMode {
|
|
|
4078
4368
|
await this.session.prompt(text);
|
|
4079
4369
|
}
|
|
4080
4370
|
else {
|
|
4081
|
-
this.
|
|
4371
|
+
this.queueCompactionSubmission(text, "followUp");
|
|
4082
4372
|
}
|
|
4083
4373
|
return;
|
|
4084
4374
|
}
|
|
4375
|
+
// Resolve attachments BEFORE any setText("") below: the editor's prune
|
|
4376
|
+
// chain fires onImageMarkersChanged([]) and the reconciler destroys
|
|
4377
|
+
// pendingImages, so resolving after the clear would ship a literal
|
|
4378
|
+
// `[Image #N]` with no attachment behind it.
|
|
4379
|
+
const images = this.takeSubmissionImages(text);
|
|
4085
4380
|
// Alt+Enter queues a follow-up message (waits until agent finishes).
|
|
4086
4381
|
// Extension commands never reach this branch: the compaction branch above
|
|
4087
4382
|
// dispatches them while compacting, and otherwise prompt() runs them
|
|
@@ -4090,12 +4385,20 @@ export class InteractiveMode {
|
|
|
4090
4385
|
if (this.session.isStreaming) {
|
|
4091
4386
|
this.editor.addToHistory?.(text);
|
|
4092
4387
|
this.editor.setText("");
|
|
4093
|
-
await this.session.prompt(text, {
|
|
4388
|
+
await this.session.prompt(text, {
|
|
4389
|
+
streamingBehavior: "followUp",
|
|
4390
|
+
...(images.length > 0 ? { images } : {}),
|
|
4391
|
+
});
|
|
4094
4392
|
this.updatePendingMessagesDisplay();
|
|
4095
4393
|
this.ui.requestRender();
|
|
4096
4394
|
}
|
|
4097
4395
|
// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
|
|
4098
4396
|
else if (this.editor.onSubmit) {
|
|
4397
|
+
// The public `onSubmit(text: string)` API cannot be widened, so hand
|
|
4398
|
+
// the pre-resolved images over out-of-band; the submit handler
|
|
4399
|
+
// captures-and-clears the field at entry and delivers them through the
|
|
4400
|
+
// widened main-loop channel.
|
|
4401
|
+
this.preResolvedSubmissionImages = images.length > 0 ? images : undefined;
|
|
4099
4402
|
this.editor.setText("");
|
|
4100
4403
|
this.editor.onSubmit(text);
|
|
4101
4404
|
}
|
|
@@ -4234,7 +4537,12 @@ export class InteractiveMode {
|
|
|
4234
4537
|
why: sanitizeTuiErrorMessage(spec.why),
|
|
4235
4538
|
...(spec.extra === undefined
|
|
4236
4539
|
? {}
|
|
4237
|
-
: {
|
|
4540
|
+
: {
|
|
4541
|
+
extra: spec.extra.map((line) => ({
|
|
4542
|
+
...line,
|
|
4543
|
+
text: sanitizeTuiErrorMessage(line.text),
|
|
4544
|
+
})),
|
|
4545
|
+
}),
|
|
4238
4546
|
...(spec.expandedLine === undefined ? {} : { expandedLine: sanitizeTuiErrorMessage(spec.expandedLine) }),
|
|
4239
4547
|
};
|
|
4240
4548
|
this.chatContainer.addChild(new Spacer(1));
|
|
@@ -4316,7 +4624,11 @@ export class InteractiveMode {
|
|
|
4316
4624
|
const clearedNative = this.session.clearQueue(options);
|
|
4317
4625
|
const { steering, followUp } = clearedNative;
|
|
4318
4626
|
const nativeMessages = clearedNative.ordered ?? [
|
|
4319
|
-
...steering.map((text, enqueueOrder) => ({
|
|
4627
|
+
...steering.map((text, enqueueOrder) => ({
|
|
4628
|
+
text,
|
|
4629
|
+
mode: "steer",
|
|
4630
|
+
enqueueOrder,
|
|
4631
|
+
})),
|
|
4320
4632
|
...followUp.map((text, index) => ({
|
|
4321
4633
|
text,
|
|
4322
4634
|
mode: "followUp",
|
|
@@ -4343,7 +4655,10 @@ export class InteractiveMode {
|
|
|
4343
4655
|
steering: [...steering, ...compactionSteering],
|
|
4344
4656
|
followUp: [...followUp, ...compactionFollowUp],
|
|
4345
4657
|
};
|
|
4346
|
-
Object.defineProperty(cleared, "ordered", {
|
|
4658
|
+
Object.defineProperty(cleared, "ordered", {
|
|
4659
|
+
value: ordered,
|
|
4660
|
+
enumerable: false,
|
|
4661
|
+
});
|
|
4347
4662
|
return cleared;
|
|
4348
4663
|
}
|
|
4349
4664
|
updatePendingMessagesDisplay() {
|
|
@@ -4365,7 +4680,9 @@ export class InteractiveMode {
|
|
|
4365
4680
|
}
|
|
4366
4681
|
}
|
|
4367
4682
|
restoreQueuedMessagesToEditor(options) {
|
|
4368
|
-
const { steering, followUp, ordered } = this.clearAllQueues({
|
|
4683
|
+
const { steering, followUp, ordered } = this.clearAllQueues({
|
|
4684
|
+
abortWillFollow: options?.abort === true,
|
|
4685
|
+
});
|
|
4369
4686
|
const allQueued = ordered?.map((message) => message.text) ?? [...steering, ...followUp];
|
|
4370
4687
|
if (allQueued.length === 0) {
|
|
4371
4688
|
this.updatePendingMessagesDisplay();
|
|
@@ -4394,7 +4711,9 @@ export class InteractiveMode {
|
|
|
4394
4711
|
* - The helper never auto-prompts restored queue text; the user decides whether to send it.
|
|
4395
4712
|
*/
|
|
4396
4713
|
async abortAndFireQueuedMessages() {
|
|
4397
|
-
const { steering, followUp, ordered } = this.clearAllQueues({
|
|
4714
|
+
const { steering, followUp, ordered } = this.clearAllQueues({
|
|
4715
|
+
abortWillFollow: true,
|
|
4716
|
+
});
|
|
4398
4717
|
const allQueued = ordered?.map((message) => message.text) ?? [...steering, ...followUp];
|
|
4399
4718
|
this.updatePendingMessagesDisplay();
|
|
4400
4719
|
await this.session.abort();
|
|
@@ -4407,13 +4726,43 @@ export class InteractiveMode {
|
|
|
4407
4726
|
this.editor.setText(combinedText);
|
|
4408
4727
|
return allQueued.length;
|
|
4409
4728
|
}
|
|
4410
|
-
queueCompactionMessage(text, mode) {
|
|
4411
|
-
this.compactionQueuedMessages.push({
|
|
4412
|
-
|
|
4729
|
+
queueCompactionMessage(text, mode, droppedImageCount = 0) {
|
|
4730
|
+
this.compactionQueuedMessages.push({
|
|
4731
|
+
text,
|
|
4732
|
+
mode,
|
|
4733
|
+
enqueueOrder: this.session.reserveQueuedInputOrder(),
|
|
4734
|
+
});
|
|
4735
|
+
this.getSessionLogger().debug("compaction_queue_enqueue", {
|
|
4736
|
+
mode,
|
|
4737
|
+
count: this.compactionQueuedMessages.length,
|
|
4738
|
+
});
|
|
4413
4739
|
this.editor.addToHistory?.(text);
|
|
4414
4740
|
this.editor.setText("");
|
|
4415
4741
|
this.updatePendingMessagesDisplay();
|
|
4416
|
-
this.showStatus(
|
|
4742
|
+
this.showStatus(droppedImageCount > 0
|
|
4743
|
+
? `Queued message for after compaction; dropped ${droppedImageCount} image${droppedImageCount > 1 ? "s" : ""}: messages sent during compaction cannot carry images - paste again after compaction finishes`
|
|
4744
|
+
: "Queued message for after compaction");
|
|
4745
|
+
}
|
|
4746
|
+
/**
|
|
4747
|
+
* Queue a user submission for delivery after compaction. The compaction
|
|
4748
|
+
* queue carries text only, so pasted attachments are dropped here -
|
|
4749
|
+
* VISIBLY, never silently, mirroring attachClipboardImage's paste-time
|
|
4750
|
+
* contract - and their `[Image #N]` markers are stripped, because a queued
|
|
4751
|
+
* literal marker has no payload behind it and would ship an unreadable
|
|
4752
|
+
* `[Image #N]` string to the model once the queue drains.
|
|
4753
|
+
*/
|
|
4754
|
+
queueCompactionSubmission(text, mode) {
|
|
4755
|
+
const images = this.takeSubmissionImages(text);
|
|
4756
|
+
if (images.length === 0) {
|
|
4757
|
+
this.queueCompactionMessage(text, mode);
|
|
4758
|
+
return;
|
|
4759
|
+
}
|
|
4760
|
+
const queued = text.replace(IMAGE_MARKER_PATTERN, "").trim();
|
|
4761
|
+
if (queued) {
|
|
4762
|
+
this.queueCompactionMessage(queued, mode, images.length);
|
|
4763
|
+
return;
|
|
4764
|
+
}
|
|
4765
|
+
this.showStatus(`Dropped ${images.length} image${images.length > 1 ? "s" : ""}: messages sent during compaction cannot carry images - paste again after compaction finishes`);
|
|
4417
4766
|
}
|
|
4418
4767
|
hasRegisteredCommand(command) {
|
|
4419
4768
|
return !!this.session.extensionRunner.getCommand(command);
|
|
@@ -4480,8 +4829,12 @@ export class InteractiveMode {
|
|
|
4480
4829
|
return message.mode === "followUp" ? session.followUp(message.text) : session.steer(message.text);
|
|
4481
4830
|
}
|
|
4482
4831
|
return message.mode === "followUp"
|
|
4483
|
-
? session.followUp(message.text, undefined, {
|
|
4484
|
-
|
|
4832
|
+
? session.followUp(message.text, undefined, {
|
|
4833
|
+
enqueueOrder: message.enqueueOrder,
|
|
4834
|
+
})
|
|
4835
|
+
: session.steer(message.text, undefined, {
|
|
4836
|
+
enqueueOrder: message.enqueueOrder,
|
|
4837
|
+
});
|
|
4485
4838
|
},
|
|
4486
4839
|
reportFailure: (error, undeliveredCount) => {
|
|
4487
4840
|
this.showError(`Failed to send queued message${undeliveredCount === 1 ? "" : "s"}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -4991,7 +5344,11 @@ export class InteractiveMode {
|
|
|
4991
5344
|
await this.applyFavoriteSelection(favoriteIds, allModels, true, await favoritePatternSnapshot);
|
|
4992
5345
|
},
|
|
4993
5346
|
});
|
|
4994
|
-
return {
|
|
5347
|
+
return {
|
|
5348
|
+
component: selector,
|
|
5349
|
+
focus: selector,
|
|
5350
|
+
dispose: () => selector.dispose(),
|
|
5351
|
+
};
|
|
4995
5352
|
});
|
|
4996
5353
|
}
|
|
4997
5354
|
async showFavoriteModelsSelector() {
|
|
@@ -5399,7 +5756,9 @@ export class InteractiveMode {
|
|
|
5399
5756
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
5400
5757
|
}
|
|
5401
5758
|
async getLogoutProviderOptions() {
|
|
5402
|
-
return (await this.session.modelRuntime.listCredentials({
|
|
5759
|
+
return (await this.session.modelRuntime.listCredentials({
|
|
5760
|
+
signal: AbortSignal.timeout(15_000),
|
|
5761
|
+
}))
|
|
5403
5762
|
.map(({ providerId, type }) => ({
|
|
5404
5763
|
id: providerId,
|
|
5405
5764
|
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
@@ -5580,7 +5939,11 @@ export class InteractiveMode {
|
|
|
5580
5939
|
if (isUnknownModel(previousModel)) {
|
|
5581
5940
|
const availableModels = this.session.modelRuntime.getAvailableSnapshot();
|
|
5582
5941
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
5583
|
-
|
|
5942
|
+
// Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
|
|
5943
|
+
if (providerId === "llama.cpp") {
|
|
5944
|
+
selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
|
|
5945
|
+
}
|
|
5946
|
+
else if (!hasDefaultModelProvider(providerId)) {
|
|
5584
5947
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
5585
5948
|
}
|
|
5586
5949
|
else if (providerModels.length === 0) {
|
|
@@ -5627,7 +5990,11 @@ export class InteractiveMode {
|
|
|
5627
5990
|
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
5628
5991
|
const refreshProviders = providerId === "cursor" ? [providerId, "cursor-cli-oauth"] : [providerId];
|
|
5629
5992
|
void this.session.modelRuntime
|
|
5630
|
-
.refresh({
|
|
5993
|
+
.refresh({
|
|
5994
|
+
allowNetwork: true,
|
|
5995
|
+
providers: refreshProviders,
|
|
5996
|
+
signal: controller.signal,
|
|
5997
|
+
})
|
|
5631
5998
|
.then((result) => {
|
|
5632
5999
|
if (result.aborted) {
|
|
5633
6000
|
this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
|
|
@@ -5855,7 +6222,9 @@ export class InteractiveMode {
|
|
|
5855
6222
|
chatRestoredBeforeSessionStart = true;
|
|
5856
6223
|
};
|
|
5857
6224
|
try {
|
|
5858
|
-
const reloadResult = await this.session.reload({
|
|
6225
|
+
const reloadResult = await this.session.reload({
|
|
6226
|
+
beforeSessionStart: restoreChatBeforeSessionStart,
|
|
6227
|
+
});
|
|
5859
6228
|
if (reloadResult.cancelled) {
|
|
5860
6229
|
dismissReloadBox(previousEditor);
|
|
5861
6230
|
reloadBoxDismissed = true;
|
|
@@ -5986,7 +6355,9 @@ export class InteractiveMode {
|
|
|
5986
6355
|
async handleShareCommand() {
|
|
5987
6356
|
// Check if gh is available and logged in
|
|
5988
6357
|
try {
|
|
5989
|
-
const authResult = spawnSync("gh", ["auth", "status"], {
|
|
6358
|
+
const authResult = spawnSync("gh", ["auth", "status"], {
|
|
6359
|
+
encoding: "utf-8",
|
|
6360
|
+
});
|
|
5990
6361
|
if (authResult.status !== 0) {
|
|
5991
6362
|
this.showError("GitHub CLI is not logged in. Run 'gh auth login' first.");
|
|
5992
6363
|
return;
|