@code-yeongyu/senpi 2026.7.10-2 → 2026.7.11
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 +41 -0
- package/README.md +3 -1
- package/dist/core/extensions/builtin/bash-timeout/timeout.js +2 -2
- package/dist/core/extensions/builtin/bash-timeout/timeout.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/index.js +10 -1
- package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
- package/dist/core/extensions/builtin/goal/store.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/store.js +88 -3
- package/dist/core/extensions/builtin/goal/store.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts +3 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +83 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset/presets.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/presets.js +6 -0
- package/dist/core/extensions/builtin/prompt-preset/presets.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/settings.d.ts +1 -1
- package/dist/core/extensions/builtin/prompt-preset/settings.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/settings.js +1 -0
- package/dist/core/extensions/builtin/prompt-preset/settings.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/notify.d.ts +3 -3
- package/dist/core/extensions/builtin/terminal/notify.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/notify.js +7 -4
- package/dist/core/extensions/builtin/terminal/notify.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +5 -3
- package/dist/core/extensions/builtin/terminal/runtime-session.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/runtime-session.js +32 -16
- package/dist/core/extensions/builtin/terminal/runtime-session.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash-output.d.ts +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash-output.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash-output.js +10 -4
- package/dist/core/extensions/builtin/terminal/tools/bash-output.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/tools/todowrite.d.ts.map +1 -1
- package/dist/core/extensions/builtin/todotools/tools/todowrite.js +5 -8
- package/dist/core/extensions/builtin/todotools/tools/todowrite.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +5 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +3 -1
- 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.map +1 -1
- package/dist/core/extensions/wrapper.js +22 -3
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/keybindings.d.ts +8 -3
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +7 -3
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +1 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +4 -0
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +9 -2
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution-fallback.d.ts +6 -0
- package/dist/modes/interactive/components/tool-execution-fallback.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-execution-fallback.js +40 -0
- package/dist/modes/interactive/components/tool-execution-fallback.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution-images.d.ts +23 -0
- package/dist/modes/interactive/components/tool-execution-images.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-execution-images.js +104 -0
- package/dist/modes/interactive/components/tool-execution-images.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution-renderer.d.ts +28 -0
- package/dist/modes/interactive/components/tool-execution-renderer.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-execution-renderer.js +135 -0
- package/dist/modes/interactive/components/tool-execution-renderer.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution-types.d.ts +23 -0
- package/dist/modes/interactive/components/tool-execution-types.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-execution-types.js +2 -0
- package/dist/modes/interactive/components/tool-execution-types.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts +13 -39
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +51 -294
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-renderer-boundary.d.ts +17 -0
- package/dist/modes/interactive/components/tool-renderer-boundary.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-renderer-boundary.js +75 -0
- package/dist/modes/interactive/components/tool-renderer-boundary.js.map +1 -0
- package/dist/modes/interactive/components/tree-selector.d.ts +5 -0
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +50 -17
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +44 -40
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +1 -0
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/clipboard.d.ts +2 -0
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +13 -0
- package/dist/utils/clipboard.js.map +1 -1
- package/docs/extensions.md +139 -0
- package/docs/keybindings.md +1 -0
- package/docs/providers.md +3 -0
- package/docs/quickstart.md +1 -1
- package/docs/settings.md +1 -1
- package/docs/usage.md +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +7 -7
- package/node_modules/@code-yeongyu/senpi-codemode/src/completion/tool-bridge.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +69 -8
- package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +39 -13
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +191 -148
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker-entry.js +23 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker.ts +4 -175
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +38 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/run-queue.ts +82 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +7 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-host.ts +117 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel-contract.ts +32 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +231 -199
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/process.ts +119 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +237 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-contract.ts +22 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +214 -189
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-process.ts +174 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +100 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-run.ts +98 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +205 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +208 -203
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +215 -25
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- 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 +82 -32
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.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 +4 -1
- 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/openai-codex-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +5 -2
- 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 +3 -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 +29 -0
- 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.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +7 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
- 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 +8 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/compat.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/compat.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +270 -21
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +5 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts +17 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js +17 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-auth.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-auth.js +9 -6
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-auth.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts +92 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +59 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts +18 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts +38 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js +24 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts +66 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +55 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +5 -21
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +48 -65
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +34 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +37 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +15 -1
- 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/deferred-tools.d.ts +9 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/deferred-tools.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/deferred-tools.js +36 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/deferred-tools.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js +18 -5
- package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/image.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/image.js +2 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/image.js.map +1 -1
- 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 +2 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/keys.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/keys.js +6 -5
- package/node_modules/@earendil-works/pi-tui/dist/keys.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +0 -9
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-text.d.ts +4 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal-text.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal-text.js +18 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal-text.js.map +1 -0
- package/node_modules/@earendil-works/pi-tui/package.json +2 -2
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard-native.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-native.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG;IAC7B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,cAAc,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;CAC7C,CAAC;AAEF,KAAK,gBAAgB,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;AAMhD,wBAAgB,mBAAmB,CAClC,QAAQ,GAAE,SAAS,gBAAgB,EAA0C,GAC3E,eAAe,GAAG,IAAI,CASxB;AAED,QAAA,MAAM,SAAS,wBAA2E,CAAC;AAE3F,OAAO,EAAE,SAAS,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"clipboard-native.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-native.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG;IAC7B,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,cAAc,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;CAC7C,CAAC;AAEF,KAAK,gBAAgB,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;AAMhD,wBAAgB,mBAAmB,CAClC,QAAQ,GAAE,SAAS,gBAAgB,EAA0C,GAC3E,eAAe,GAAG,IAAI,CASxB;AAED,QAAA,MAAM,SAAS,wBAA2E,CAAC;AAE3F,OAAO,EAAE,SAAS,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard-native.js","sourceRoot":"","sources":["../../src/utils/clipboard-native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"clipboard-native.js","sourceRoot":"","sources":["../../src/utils/clipboard-native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAWpC,MAAM,aAAa,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AACrD,MAAM,oBAAoB,GAAG,aAAa,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAChH,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;AAE/G,MAAM,UAAU,mBAAmB,CAClC,QAAQ,GAAgC,CAAC,aAAa,EAAE,oBAAoB,CAAC;IAE7E,KAAK,MAAM,gBAAgB,IAAI,QAAQ,EAAE,CAAC;QACzC,IAAI,CAAC;YACJ,OAAO,gBAAgB,CAAC,yBAAyB,CAAoB,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACR,gCAAgC;QACjC,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,UAAU,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAE3F,OAAO,EAAE,SAAS,EAAE,CAAC","sourcesContent":["import { createRequire } from \"module\";\nimport { dirname, join } from \"path\";\nimport { pathToFileURL } from \"url\";\n\nexport type ClipboardModule = {\n\tgetText: () => Promise<string>;\n\tsetText: (text: string) => Promise<void>;\n\thasImage: () => boolean;\n\tgetImageBinary: () => Promise<Array<number>>;\n};\n\ntype ClipboardRequire = (id: string) => unknown;\n\nconst moduleRequire = createRequire(import.meta.url);\nconst executableDirRequire = createRequire(pathToFileURL(join(dirname(process.execPath), \"package.json\")).href);\nconst hasDisplay = process.platform !== \"linux\" || Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);\n\nexport function loadClipboardNative(\n\trequires: readonly ClipboardRequire[] = [moduleRequire, executableDirRequire],\n): ClipboardModule | null {\n\tfor (const requireClipboard of requires) {\n\t\ttry {\n\t\t\treturn requireClipboard(\"@mariozechner/clipboard\") as ClipboardModule;\n\t\t} catch {\n\t\t\t// Try the next resolution root.\n\t\t}\n\t}\n\treturn null;\n}\n\nconst clipboard = !process.env.TERMUX_VERSION && hasDisplay ? loadClipboardNative() : null;\n\nexport { clipboard };\n"]}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
/** Read plain text from the system clipboard, if native clipboard access is available. */
|
|
2
|
+
export declare function readClipboardText(): Promise<string | null>;
|
|
1
3
|
export declare function copyToClipboard(text: string): Promise<void>;
|
|
2
4
|
//# sourceMappingURL=clipboard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAkCA,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4FjE"}
|
|
1
|
+
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAkCA,0FAA0F;AAC1F,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWhE;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4FjE"}
|
package/dist/utils/clipboard.js
CHANGED
|
@@ -22,6 +22,19 @@ function emitOsc52(text) {
|
|
|
22
22
|
process.stdout.write(`\x1b]52;c;${encoded}\x07`);
|
|
23
23
|
return true;
|
|
24
24
|
}
|
|
25
|
+
/** Read plain text from the system clipboard, if native clipboard access is available. */
|
|
26
|
+
export async function readClipboardText() {
|
|
27
|
+
if (!clipboard) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const text = await clipboard.getText();
|
|
32
|
+
return text || null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
25
38
|
export async function copyToClipboard(text) {
|
|
26
39
|
let copied = false;
|
|
27
40
|
const p = platform();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { isWaylandSession } from \"./clipboard-image.ts\";\nimport { clipboard } from \"./clipboard-native.ts\";\n\ntype NativeClipboardExecOptions = {\n\tinput: string;\n\ttimeout: number;\n\tstdio: [\"pipe\", \"ignore\", \"ignore\"];\n};\n\nfunction copyToX11Clipboard(options: NativeClipboardExecOptions): void {\n\ttry {\n\t\texecSync(\"xclip -selection clipboard\", options);\n\t} catch {\n\t\texecSync(\"xsel --clipboard --input\", options);\n\t}\n}\n\nconst MAX_OSC52_ENCODED_LENGTH = 100_000;\n\nfunction isRemoteSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.MOSH_CONNECTION);\n}\n\nfunction emitOsc52(text: string): boolean {\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tif (encoded.length > MAX_OSC52_ENCODED_LENGTH) {\n\t\treturn false;\n\t}\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\treturn true;\n}\n\nexport async function copyToClipboard(text: string): Promise<void> {\n\tlet copied = false;\n\n\tconst p = platform();\n\n\t// Prefer direct clipboard writes. Emitting OSC 52 first can make terminals\n\t// write the same native clipboard concurrently with the addon, and very large\n\t// OSC 52 payloads can desynchronize terminal rendering.\n\t//\n\t// On Linux, skip the native addon. The underlying `clipboard-rs` crate is\n\t// X11-only and does not retain selection ownership after `set_text`\n\t// resolves, so on Wayland-only compositors (Hyprland, Niri, ...) and even\n\t// some X11 sessions the call resolves successfully without populating the\n\t// clipboard. The platform tools below (wl-copy, xclip, xsel) properly\n\t// daemonize and keep ownership.\n\ttry {\n\t\tif (clipboard && p !== \"linux\") {\n\t\t\tawait clipboard.setText(text);\n\t\t\tcopied = true;\n\t\t}\n\t} catch {\n\t\t// Fall through to platform-specific clipboard tools.\n\t}\n\n\tconst remote = isRemoteSession();\n\tif (copied && !remote) {\n\t\treturn;\n\t}\n\n\tconst options: NativeClipboardExecOptions = { input: text, timeout: 5000, stdio: [\"pipe\", \"ignore\", \"ignore\"] };\n\n\tif (!copied) {\n\t\ttry {\n\t\t\tif (p === \"darwin\") {\n\t\t\t\texecSync(\"pbcopy\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else if (p === \"win32\") {\n\t\t\t\texecSync(\"clip\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else {\n\t\t\t\t// Linux. Try Termux, Wayland, or X11 clipboard tools.\n\t\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Fall back to Wayland or X11 tools.\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!copied) {\n\t\t\t\t\tconst hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);\n\t\t\t\t\tconst hasX11Display = Boolean(process.env.DISPLAY);\n\t\t\t\t\tconst isWayland = isWaylandSession();\n\t\t\t\t\tif (isWayland && hasWaylandDisplay) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Verify wl-copy exists (spawn errors are async and won't be caught)\n\t\t\t\t\t\t\texecSync(\"which wl-copy\", { stdio: \"ignore\" });\n\t\t\t\t\t\t\t// wl-copy with execSync hangs due to fork behavior; use spawn instead\n\t\t\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], { stdio: [\"pipe\", \"ignore\", \"ignore\"] });\n\t\t\t\t\t\t\tproc.stdin.on(\"error\", () => {\n\t\t\t\t\t\t\t\t// Ignore EPIPE errors if wl-copy exits early\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tproc.stdin.write(text);\n\t\t\t\t\t\t\tproc.stdin.end();\n\t\t\t\t\t\t\tproc.unref();\n\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tif (hasX11Display) {\n\t\t\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (hasX11Display) {\n\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to OSC 52 fallback.\n\t\t}\n\t}\n\n\tif (remote || !copied) {\n\t\tconst osc52Copied = emitOsc52(text);\n\t\tcopied = copied || osc52Copied;\n\t}\n\n\tif (!copied) {\n\t\tthrow new Error(\"Failed to copy to clipboard\");\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { isWaylandSession } from \"./clipboard-image.ts\";\nimport { clipboard } from \"./clipboard-native.ts\";\n\ntype NativeClipboardExecOptions = {\n\tinput: string;\n\ttimeout: number;\n\tstdio: [\"pipe\", \"ignore\", \"ignore\"];\n};\n\nfunction copyToX11Clipboard(options: NativeClipboardExecOptions): void {\n\ttry {\n\t\texecSync(\"xclip -selection clipboard\", options);\n\t} catch {\n\t\texecSync(\"xsel --clipboard --input\", options);\n\t}\n}\n\nconst MAX_OSC52_ENCODED_LENGTH = 100_000;\n\nfunction isRemoteSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.MOSH_CONNECTION);\n}\n\nfunction emitOsc52(text: string): boolean {\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tif (encoded.length > MAX_OSC52_ENCODED_LENGTH) {\n\t\treturn false;\n\t}\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\treturn true;\n}\n\n/** Read plain text from the system clipboard, if native clipboard access is available. */\nexport async function readClipboardText(): Promise<string | null> {\n\tif (!clipboard) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst text = await clipboard.getText();\n\t\treturn text || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport async function copyToClipboard(text: string): Promise<void> {\n\tlet copied = false;\n\n\tconst p = platform();\n\n\t// Prefer direct clipboard writes. Emitting OSC 52 first can make terminals\n\t// write the same native clipboard concurrently with the addon, and very large\n\t// OSC 52 payloads can desynchronize terminal rendering.\n\t//\n\t// On Linux, skip the native addon. The underlying `clipboard-rs` crate is\n\t// X11-only and does not retain selection ownership after `set_text`\n\t// resolves, so on Wayland-only compositors (Hyprland, Niri, ...) and even\n\t// some X11 sessions the call resolves successfully without populating the\n\t// clipboard. The platform tools below (wl-copy, xclip, xsel) properly\n\t// daemonize and keep ownership.\n\ttry {\n\t\tif (clipboard && p !== \"linux\") {\n\t\t\tawait clipboard.setText(text);\n\t\t\tcopied = true;\n\t\t}\n\t} catch {\n\t\t// Fall through to platform-specific clipboard tools.\n\t}\n\n\tconst remote = isRemoteSession();\n\tif (copied && !remote) {\n\t\treturn;\n\t}\n\n\tconst options: NativeClipboardExecOptions = { input: text, timeout: 5000, stdio: [\"pipe\", \"ignore\", \"ignore\"] };\n\n\tif (!copied) {\n\t\ttry {\n\t\t\tif (p === \"darwin\") {\n\t\t\t\texecSync(\"pbcopy\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else if (p === \"win32\") {\n\t\t\t\texecSync(\"clip\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else {\n\t\t\t\t// Linux. Try Termux, Wayland, or X11 clipboard tools.\n\t\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Fall back to Wayland or X11 tools.\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!copied) {\n\t\t\t\t\tconst hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);\n\t\t\t\t\tconst hasX11Display = Boolean(process.env.DISPLAY);\n\t\t\t\t\tconst isWayland = isWaylandSession();\n\t\t\t\t\tif (isWayland && hasWaylandDisplay) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Verify wl-copy exists (spawn errors are async and won't be caught)\n\t\t\t\t\t\t\texecSync(\"which wl-copy\", { stdio: \"ignore\" });\n\t\t\t\t\t\t\t// wl-copy with execSync hangs due to fork behavior; use spawn instead\n\t\t\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], { stdio: [\"pipe\", \"ignore\", \"ignore\"] });\n\t\t\t\t\t\t\tproc.stdin.on(\"error\", () => {\n\t\t\t\t\t\t\t\t// Ignore EPIPE errors if wl-copy exits early\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tproc.stdin.write(text);\n\t\t\t\t\t\t\tproc.stdin.end();\n\t\t\t\t\t\t\tproc.unref();\n\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tif (hasX11Display) {\n\t\t\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (hasX11Display) {\n\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to OSC 52 fallback.\n\t\t}\n\t}\n\n\tif (remote || !copied) {\n\t\tconst osc52Copied = emitOsc52(text);\n\t\tcopied = copied || osc52Copied;\n\t}\n\n\tif (!copied) {\n\t\tthrow new Error(\"Failed to copy to clipboard\");\n\t}\n}\n"]}
|
package/docs/extensions.md
CHANGED
|
@@ -52,6 +52,7 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
|
|
|
52
52
|
- [ExtensionAPI Methods](#extensionapi-methods)
|
|
53
53
|
- [State Management](#state-management)
|
|
54
54
|
- [Custom Tools](#custom-tools)
|
|
55
|
+
- [Dynamic Tool Loading](#dynamic-tool-loading)
|
|
55
56
|
- [Custom UI](#custom-ui)
|
|
56
57
|
- [Error Handling](#error-handling)
|
|
57
58
|
- [Mode Behavior](#mode-behavior)
|
|
@@ -2632,6 +2633,7 @@ pi.registerTool({
|
|
|
2632
2633
|
- `lastComponent` - the previously returned component for that slot, if any
|
|
2633
2634
|
- `invalidate()` - request a rerender of this tool row
|
|
2634
2635
|
- `toolCallId`, `cwd`, `executionStarted`, `argsComplete`, `isPartial`, `expanded`, `showImages`, `isError`
|
|
2636
|
+
- `imageProtocol` - optional for compatibility with older hosts; when present it is `"kitty"`, `"iterm2"`, or `null` when the terminal cannot render inline images. With an active protocol, the host owns native image rendering and protocol fallbacks for image result blocks, so custom renderers can suppress duplicate image indicators.
|
|
2635
2637
|
|
|
2636
2638
|
Use `context.state` for cross-slot shared state. Keep slot-local caches on the returned component instance when you want to reuse and mutate the same component across renders.
|
|
2637
2639
|
|
|
@@ -2727,6 +2729,143 @@ If a slot renderer is not defined or throws:
|
|
|
2727
2729
|
- `renderCall`: Shows the tool name
|
|
2728
2730
|
- `renderResult`: Shows raw text from `content`
|
|
2729
2731
|
|
|
2732
|
+
### Dynamic Tool Loading
|
|
2733
|
+
|
|
2734
|
+
Extensions can register many tools while keeping only a small initial set active. A tool can then add more tools with `pi.setActiveTools()` during execution. Pi detects purely additive changes, records the newly available tool names on that tool result, and applies the updated active set before the next model request.
|
|
2735
|
+
|
|
2736
|
+
This works with every model. Models with native deferred-loading support preserve the stable prompt prefix and load the new definitions at the tool-result position. Other models use the fallback described below.
|
|
2737
|
+
|
|
2738
|
+
The lifecycle is:
|
|
2739
|
+
|
|
2740
|
+
1. Register every tool with `pi.registerTool()` so it appears in `pi.getAllTools()`.
|
|
2741
|
+
2. Keep loader tools, such as `search_tools`, active and leave searchable tools inactive.
|
|
2742
|
+
3. During loader execution, call `pi.setActiveTools([...currentTools, ...matchingTools])`. The change must be additive: do not remove currently active tools in the same call.
|
|
2743
|
+
4. Pi records which tools were added on the loader's tool result.
|
|
2744
|
+
5. Before the next model response, Pi exposes the added definitions using native deferred loading when supported, or the normal active tool list otherwise.
|
|
2745
|
+
|
|
2746
|
+
You do not need to return provider-specific tool references or mark the loader as a special search tool. The active-tool change is the signal. Names passed to `pi.setActiveTools()` must already be registered; unknown names are ignored.
|
|
2747
|
+
|
|
2748
|
+
#### Models with native deferred loading
|
|
2749
|
+
|
|
2750
|
+
- **Anthropic**
|
|
2751
|
+
- **Models:** Sonnet, Opus, Fable version 4.5 or newer (without Haiku)
|
|
2752
|
+
- **Native representation:** Deferred definitions use `defer_loading`; the load point uses `tool_reference` content.
|
|
2753
|
+
- **OpenAI**
|
|
2754
|
+
- **Models:** `gpt-5.4` and newer family
|
|
2755
|
+
- **Native representation:** Pi adds completed client `tool_search_call` and `tool_search_output` items at the load point.
|
|
2756
|
+
|
|
2757
|
+
For a verified custom model or proxy, native handling can be enabled with `compat.supportsToolReferences: true` for `anthropic-messages`, or `compat.supportsToolSearch: true` for `openai-responses` and `openai-codex-responses`. Leave these disabled unless the endpoint and model accept the corresponding native protocol.
|
|
2758
|
+
|
|
2759
|
+
#### Fallback behavior
|
|
2760
|
+
|
|
2761
|
+
For all other models and providers, dynamic activation still works: Pi sends the complete current active tool list normally on the next request. The model can call the newly activated tools, but adding their definitions may invalidate the provider's cached prompt prefix.
|
|
2762
|
+
|
|
2763
|
+
Pi also uses this safe fallback when the active set is not purely additive, such as replacing one group of tools with another. Tool removals therefore work, but they do not use deferred loading.
|
|
2764
|
+
|
|
2765
|
+
For the best cache behavior, keep the loader tool active for the whole session and add tools instead of replacing the active set. Also note that activating a tool with `promptSnippet` or `promptGuidelines` rebuilds the system prompt; that system-prompt change can invalidate the prefix even when the provider supports deferred schemas. Lazily loaded tools should usually rely on their tool `description` and omit active-only prompt metadata.
|
|
2766
|
+
|
|
2767
|
+
#### Search tool example
|
|
2768
|
+
|
|
2769
|
+
The following extension registers two searchable tools, removes them from the initial active set, and keeps only `search_tools` as their loader. The example uses simple keyword matching, but the search implementation could use BM25, embeddings, a remote catalog, or project-specific routing.
|
|
2770
|
+
|
|
2771
|
+
```typescript
|
|
2772
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2773
|
+
import { Type } from "typebox";
|
|
2774
|
+
|
|
2775
|
+
const SEARCHABLE_TOOL_NAMES = new Set(["lookup_weather", "search_issues"]);
|
|
2776
|
+
|
|
2777
|
+
export default function (pi: ExtensionAPI) {
|
|
2778
|
+
pi.registerTool({
|
|
2779
|
+
name: "lookup_weather",
|
|
2780
|
+
label: "Lookup Weather",
|
|
2781
|
+
description: "Look up the current weather for a city",
|
|
2782
|
+
parameters: Type.Object({ city: Type.String() }),
|
|
2783
|
+
async execute(_toolCallId, params) {
|
|
2784
|
+
return {
|
|
2785
|
+
content: [{ type: "text", text: `Weather for ${params.city}: sunny` }],
|
|
2786
|
+
details: {},
|
|
2787
|
+
};
|
|
2788
|
+
},
|
|
2789
|
+
});
|
|
2790
|
+
|
|
2791
|
+
pi.registerTool({
|
|
2792
|
+
name: "search_issues",
|
|
2793
|
+
label: "Search Issues",
|
|
2794
|
+
description: "Search project issues by keyword",
|
|
2795
|
+
parameters: Type.Object({ query: Type.String() }),
|
|
2796
|
+
async execute(_toolCallId, params) {
|
|
2797
|
+
return {
|
|
2798
|
+
content: [{ type: "text", text: `No open issues matching ${params.query}` }],
|
|
2799
|
+
details: {},
|
|
2800
|
+
};
|
|
2801
|
+
},
|
|
2802
|
+
});
|
|
2803
|
+
|
|
2804
|
+
pi.registerTool({
|
|
2805
|
+
name: "search_tools",
|
|
2806
|
+
label: "Search Tools",
|
|
2807
|
+
description: "Search for and enable tools relevant to a task",
|
|
2808
|
+
promptSnippet: "Search for additional tools when the active tools cannot perform the task",
|
|
2809
|
+
promptGuidelines: [
|
|
2810
|
+
"Use search_tools when a task requires a capability that is not currently available.",
|
|
2811
|
+
],
|
|
2812
|
+
parameters: Type.Object({
|
|
2813
|
+
query: Type.String({ description: "Capability or task to search for" }),
|
|
2814
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 10 })),
|
|
2815
|
+
}),
|
|
2816
|
+
async execute(_toolCallId, params) {
|
|
2817
|
+
const terms = params.query.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
2818
|
+
const matches = pi.getAllTools()
|
|
2819
|
+
.filter((tool) => SEARCHABLE_TOOL_NAMES.has(tool.name))
|
|
2820
|
+
.map((tool) => ({
|
|
2821
|
+
tool,
|
|
2822
|
+
score: terms.reduce(
|
|
2823
|
+
(score, term) =>
|
|
2824
|
+
score + (`${tool.name} ${tool.description}`.toLowerCase().includes(term) ? 1 : 0),
|
|
2825
|
+
0,
|
|
2826
|
+
),
|
|
2827
|
+
}))
|
|
2828
|
+
.filter((match) => match.score > 0)
|
|
2829
|
+
.sort((a, b) => b.score - a.score)
|
|
2830
|
+
.slice(0, params.limit ?? 3)
|
|
2831
|
+
.map((match) => match.tool.name);
|
|
2832
|
+
|
|
2833
|
+
if (matches.length === 0) {
|
|
2834
|
+
return {
|
|
2835
|
+
content: [{ type: "text", text: `No tools found for: ${params.query}` }],
|
|
2836
|
+
details: { matches: [] },
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
const active = pi.getActiveTools();
|
|
2841
|
+
const added = matches.filter((name) => !active.includes(name));
|
|
2842
|
+
pi.setActiveTools([...new Set([...active, ...added])]);
|
|
2843
|
+
|
|
2844
|
+
return {
|
|
2845
|
+
content: [{
|
|
2846
|
+
type: "text",
|
|
2847
|
+
text: added.length > 0
|
|
2848
|
+
? `Loaded tools: ${added.join(", ")}`
|
|
2849
|
+
: `Matching tools already active: ${matches.join(", ")}`,
|
|
2850
|
+
}],
|
|
2851
|
+
details: { matches, added },
|
|
2852
|
+
};
|
|
2853
|
+
},
|
|
2854
|
+
});
|
|
2855
|
+
|
|
2856
|
+
pi.on("session_start", () => {
|
|
2857
|
+
// Keep searchable tools registered but initially inactive. Preserve built-ins
|
|
2858
|
+
// and tools owned by other extensions, and keep the loader itself active.
|
|
2859
|
+
const initialTools = pi.getActiveTools().filter(
|
|
2860
|
+
(name) => !SEARCHABLE_TOOL_NAMES.has(name),
|
|
2861
|
+
);
|
|
2862
|
+
pi.setActiveTools([...new Set([...initialTools, "search_tools"])]);
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
```
|
|
2866
|
+
|
|
2867
|
+
When `search_tools` adds a match, the model receives that definition on the immediately following request. On a native-capable model the definition is anchored after the search result without changing the initial tool-schema prefix. On other models it appears in the normal tool list on that same following request.
|
|
2868
|
+
|
|
2730
2869
|
## Custom UI
|
|
2731
2870
|
|
|
2732
2871
|
Extensions can interact with users via `ctx.ui` methods and customize how messages/tools render.
|
package/docs/keybindings.md
CHANGED
|
@@ -121,6 +121,7 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `ctrl+1
|
|
|
121
121
|
| Keybinding id | Default | Description |
|
|
122
122
|
|--------|---------|-------------|
|
|
123
123
|
| `app.tools.expand` | `ctrl+o` | Collapse or expand tool output |
|
|
124
|
+
| `app.message.copy` | `ctrl+x` | Copy the last assistant message, or the selected message in `/tree` |
|
|
124
125
|
| `app.message.followUp` | `alt+enter` | Queue follow-up message |
|
|
125
126
|
| `app.message.dequeue` | `alt+up` | Restore queued messages to editor |
|
|
126
127
|
|
package/docs/providers.md
CHANGED
|
@@ -55,6 +55,7 @@ senpi
|
|
|
55
55
|
| DeepSeek | `DEEPSEEK_API_KEY` | `deepseek` |
|
|
56
56
|
| NVIDIA NIM | `NVIDIA_API_KEY` | `nvidia` |
|
|
57
57
|
| Google Gemini | `GEMINI_API_KEY` | `google` |
|
|
58
|
+
| Amazon Bedrock | `AWS_BEARER_TOKEN_BEDROCK` | `amazon-bedrock` |
|
|
58
59
|
| Mistral | `MISTRAL_API_KEY` | `mistral` |
|
|
59
60
|
| Groq | `GROQ_API_KEY` | `groq` |
|
|
60
61
|
| Cerebras | `CEREBRAS_API_KEY` | `cerebras` |
|
|
@@ -170,6 +171,8 @@ export AZURE_OPENAI_DEPLOYMENT_NAME_MAP=gpt-4=my-gpt4,gpt-4o=my-gpt4o
|
|
|
170
171
|
|
|
171
172
|
### Amazon Bedrock
|
|
172
173
|
|
|
174
|
+
Use `/login amazon-bedrock` to store a Bedrock API key, or configure one of the ambient AWS credential sources below:
|
|
175
|
+
|
|
173
176
|
```bash
|
|
174
177
|
# Option 1: AWS Profile
|
|
175
178
|
export AWS_PROFILE=your-profile
|
package/docs/quickstart.md
CHANGED
|
@@ -111,7 +111,7 @@ senpi @README.md "Summarize this"
|
|
|
111
111
|
senpi @src/app.ts @src/app.test.ts "Review these together"
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
Images can be pasted with Ctrl+V (Alt+V on Windows)
|
|
114
|
+
Images or text can be pasted with Ctrl+V (Alt+V on Windows); images can also be dragged into supported terminals.
|
|
115
115
|
|
|
116
116
|
### Run shell commands
|
|
117
117
|
|
package/docs/settings.md
CHANGED
|
@@ -84,7 +84,7 @@ Permission rules are a confirmation policy, not a sandbox. Senpi, extensions, pa
|
|
|
84
84
|
| `defaultProvider` | string | - | Default provider (e.g., `"anthropic"`, `"openai"`) |
|
|
85
85
|
| `defaultModel` | string | - | Default model ID |
|
|
86
86
|
| `defaultThinkingLevel` | string | - | `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"` |
|
|
87
|
-
| `promptPreset` | string | `"auto"` | Force a system prompt preset: `"auto"`, `"kimi-k2-6"`, `"kimi-k2-7"`, `"glm-5.2"`, `"claude-fable-5"`, `"claude-opus-4-5"`, `"claude-opus-4-6"`, `"claude-opus-4-7"`, `"claude-opus-4-8"`, `"gpt-5"`, `"gpt-5.2"`, `"gpt-5.3-codex"`, `"gpt-5.4"`, or `"gpt-5.
|
|
87
|
+
| `promptPreset` | string | `"auto"` | Force a system prompt preset: `"auto"`, `"kimi-k2-6"`, `"kimi-k2-7"`, `"glm-5.2"`, `"claude-fable-5"`, `"claude-opus-4-5"`, `"claude-opus-4-6"`, `"claude-opus-4-7"`, `"claude-opus-4-8"`, `"gpt-5"`, `"gpt-5.2"`, `"gpt-5.3-codex"`, `"gpt-5.4"`, `"gpt-5.5"`, or `"gpt-5.6"` |
|
|
88
88
|
| `hideThinkingBlock` | boolean | `false` | Hide thinking blocks in output |
|
|
89
89
|
| `showCacheMissNotices` | boolean | `false` | Show transcript notices for significant prompt-cache misses |
|
|
90
90
|
| `thinkingBudgets` | object | - | Custom token budgets per thinking level |
|
package/docs/usage.md
CHANGED
|
@@ -22,6 +22,7 @@ The editor can be replaced temporarily by built-in UI such as `/settings` or by
|
|
|
22
22
|
| File reference | Type `@` to fuzzy-search project files |
|
|
23
23
|
| Path completion | Press Tab to complete paths |
|
|
24
24
|
| Multi-line input | Shift+Enter, or Ctrl+Enter on Windows Terminal |
|
|
25
|
+
| Copy response | Ctrl+X copies the last assistant message; in `/tree`, it copies the selected message |
|
|
25
26
|
| Images | Paste with Ctrl+V, Alt+V on Windows, or drag into the terminal |
|
|
26
27
|
| Shell command | `!command` runs and sends output to the model |
|
|
27
28
|
| Hidden shell command | `!!command` runs without sending output to the model |
|
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
### Changed
|
|
11
11
|
|
|
12
12
|
- Documented that the current exported extension factory is still a no-op until the `eval` tool and bundled host loading land.
|
|
13
|
+
- Improved `eval` TUI rendering with streaming status and timing, bounded expandable previews, width-safe ANSI/CJK/emoji reflow, nested tool-call state, and terminal-aware image fallbacks.
|
|
13
14
|
|
|
14
15
|
### Fixed
|
|
15
16
|
|
|
17
|
+
- Prevented image MIME labels from injecting terminal control sequences through eval text fallbacks.
|
|
18
|
+
- Fixed eval cancellation and timeout handling across JavaScript, Python, Ruby, and Julia kernels: aborts now interrupt active work, unresponsive subprocesses escalate to bounded hard termination, queued Python cells cannot execute after cancellation, persistent Python state survives graceful interrupts, timeout/death durations remain truthful, and late bridge or retired-process output cannot keep an eval hung or contaminate the next cell.
|
|
16
19
|
- Fixed the bundled `eval` extension failing to load in packaged installs: `completion/handler.ts` imported peer symbols via the monorepo source path `../../../ai/src/*`, which only resolves inside the workspace and threw `Cannot find module` once packed. It now imports from the `@earendil-works/pi-ai/compat` package entry, so `eval` loads in the shipped Node package.
|
|
17
20
|
- Fixed a temporal-dead-zone crash in the `eval` tool: subprocess kernels (py/rb/jl) emit their `ready` frame synchronously during kernel startup, which invoked the message handler before the `kernel` binding initialized and crashed the whole agent process. The self-referential binding is now hoisted so startup frames no longer throw.
|
|
18
21
|
- Fixed cell-output misattribution on reused persistent kernels: `getKernel` now rebinds the per-cell `onMessage` on every call, so a second (and later) cell's streamed `text`/`display`/`log` output is delivered to that cell instead of the previous one.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.7.
|
|
3
|
+
"version": "2026.7.11",
|
|
4
4
|
"description": "Source-only senpi extension package for codemode evaluation tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@earendil-works/pi-ai": "^2026.7.
|
|
32
|
+
"@earendil-works/pi-ai": "^2026.7.11",
|
|
33
33
|
"typebox": "1.1.38"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@code-yeongyu/senpi": "*"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@code-yeongyu/senpi": "2026.7.
|
|
39
|
+
"@code-yeongyu/senpi": "2026.7.11"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"senpi",
|
|
@@ -158,9 +158,10 @@ export function parseBridgeJsonLine(line: string, options: BridgeFrameOptions =
|
|
|
158
158
|
};
|
|
159
159
|
}
|
|
160
160
|
try {
|
|
161
|
-
return { ok: true, value: JSON.parse(trimmedLine)
|
|
161
|
+
return { ok: true, value: JSON.parse(trimmedLine) };
|
|
162
162
|
} catch (error) {
|
|
163
|
-
|
|
163
|
+
const message = error instanceof Error ? error.message : "Invalid JSON bridge frame";
|
|
164
|
+
return { ok: false, error: { code: "malformed_json", message } };
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
|
|
@@ -175,11 +176,10 @@ export function decodeBridgeFrame(line: string, options: BridgeFrameOptions = {}
|
|
|
175
176
|
return { ok: false, error: { code: "invalid_message", message } };
|
|
176
177
|
}
|
|
177
178
|
|
|
178
|
-
function
|
|
179
|
-
return
|
|
179
|
+
export function isKernelToHostMessage(message: BridgeMessage): message is KernelToHostMessage {
|
|
180
|
+
return Value.Check(kernelToHostMessageSchema, message);
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
return "Invalid JSON bridge frame";
|
|
183
|
+
function tokenHash(token: string): Buffer {
|
|
184
|
+
return createHash("sha256").update(token, "utf8").digest();
|
|
185
185
|
}
|
|
@@ -8,6 +8,7 @@ export interface CompletionToolCallOptions {
|
|
|
8
8
|
readonly kernel: EvalKernel;
|
|
9
9
|
readonly complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
|
|
10
10
|
readonly ctx: ExtensionContext;
|
|
11
|
+
readonly isActive: () => boolean;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export type CompletionToolCallSummary = { readonly ok: true } | { readonly ok: false; readonly error: string };
|
|
@@ -15,10 +16,12 @@ export type CompletionToolCallSummary = { readonly ok: true } | { readonly ok: f
|
|
|
15
16
|
export async function handleCompletionToolCall(options: CompletionToolCallOptions): Promise<CompletionToolCallSummary> {
|
|
16
17
|
try {
|
|
17
18
|
const value = await options.complete(toCompletionRequest(options.message.args), options.ctx);
|
|
19
|
+
if (!options.isActive()) return { ok: false, error: "completion() result ignored after eval finalization" };
|
|
18
20
|
options.kernel.deliverToolReply({ type: "tool-reply", callId: options.message.callId, ok: true, value });
|
|
19
21
|
return { ok: true };
|
|
20
22
|
} catch (error) {
|
|
21
23
|
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
if (!options.isActive()) return { ok: false, error: message };
|
|
22
25
|
options.kernel.deliverToolReply({
|
|
23
26
|
type: "tool-reply",
|
|
24
27
|
callId: options.message.callId,
|
|
@@ -33,12 +33,31 @@ export async function createCodemodeSessionManager(
|
|
|
33
33
|
return manager;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
class CodemodeSessionDisposedError extends Error {
|
|
37
|
+
readonly name = "CodemodeSessionDisposedError";
|
|
38
|
+
|
|
39
|
+
constructor() {
|
|
40
|
+
super("codemode session manager is disposed");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class CodemodeContextUnavailableError extends Error {
|
|
45
|
+
readonly name = "CodemodeContextUnavailableError";
|
|
46
|
+
|
|
47
|
+
constructor() {
|
|
48
|
+
super("codemode completion context is unavailable");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
37
53
|
readonly #options: CreateCodemodeSessionManagerOptions;
|
|
38
54
|
#bridge: BridgeServerHandle | undefined;
|
|
39
55
|
#kernels = new Map<EvalLanguage, EvalKernel>();
|
|
56
|
+
#kernelCreations = new Map<EvalLanguage, Promise<EvalKernel>>();
|
|
40
57
|
#onMessageRefs = new Map<EvalLanguage, (message: KernelToHostMessage) => void>();
|
|
41
58
|
#context: ExtensionContext | undefined;
|
|
59
|
+
#generation = 0;
|
|
60
|
+
#disposePromise: Promise<void> | undefined;
|
|
42
61
|
|
|
43
62
|
constructor(options: CreateCodemodeSessionManagerOptions) {
|
|
44
63
|
this.#options = options;
|
|
@@ -55,6 +74,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
async getKernel(language: EvalLanguage, onMessage: (message: KernelToHostMessage) => void): Promise<EvalKernel> {
|
|
77
|
+
if (this.#disposePromise) throw new CodemodeSessionDisposedError();
|
|
58
78
|
// Persistent kernels are reused across cells, but each cell needs its OWN
|
|
59
79
|
// onMessage (bound to that cell's streaming state). Rebind on every call via
|
|
60
80
|
// a stable dispatcher so the 2nd+ cell's text/display/log output is attributed
|
|
@@ -62,10 +82,17 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
62
82
|
this.#onMessageRefs.set(language, onMessage);
|
|
63
83
|
const existing = this.#kernels.get(language);
|
|
64
84
|
if (existing) return existing;
|
|
85
|
+
const pending = this.#kernelCreations.get(language);
|
|
86
|
+
if (pending) return await pending;
|
|
65
87
|
const dispatch = (message: KernelToHostMessage): void => this.#onMessageRefs.get(language)?.(message);
|
|
66
|
-
const
|
|
67
|
-
this.#
|
|
68
|
-
|
|
88
|
+
const generation = this.#generation;
|
|
89
|
+
const creation = this.#createAndStoreKernel(language, dispatch, generation);
|
|
90
|
+
this.#kernelCreations.set(language, creation);
|
|
91
|
+
try {
|
|
92
|
+
return await creation;
|
|
93
|
+
} finally {
|
|
94
|
+
if (this.#kernelCreations.get(language) === creation) this.#kernelCreations.delete(language);
|
|
95
|
+
}
|
|
69
96
|
}
|
|
70
97
|
|
|
71
98
|
async complete(request: CompletionRequest, ctx: ExtensionContext): Promise<CompletionResult> {
|
|
@@ -76,12 +103,46 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
76
103
|
this.#context = ctx;
|
|
77
104
|
}
|
|
78
105
|
|
|
79
|
-
|
|
106
|
+
dispose(): Promise<void> {
|
|
107
|
+
if (this.#disposePromise) return this.#disposePromise;
|
|
108
|
+
this.#generation++;
|
|
109
|
+
this.#disposePromise = this.#disposeGeneration();
|
|
110
|
+
return this.#disposePromise;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async #disposeGeneration(): Promise<void> {
|
|
114
|
+
await Promise.allSettled(this.#kernelCreations.values());
|
|
80
115
|
const kernels = [...this.#kernels.values()];
|
|
116
|
+
const bridge = this.#bridge;
|
|
81
117
|
this.#kernels.clear();
|
|
82
|
-
|
|
83
|
-
await this.#bridge?.close();
|
|
118
|
+
this.#onMessageRefs.clear();
|
|
84
119
|
this.#bridge = undefined;
|
|
120
|
+
this.#context = undefined;
|
|
121
|
+
const failures: unknown[] = [];
|
|
122
|
+
for (const outcome of await Promise.allSettled(kernels.map((kernel) => kernel.close()))) {
|
|
123
|
+
if (outcome.status === "rejected") failures.push(outcome.reason);
|
|
124
|
+
}
|
|
125
|
+
if (bridge) {
|
|
126
|
+
const [outcome] = await Promise.allSettled([bridge.close()]);
|
|
127
|
+
if (outcome?.status === "rejected") failures.push(outcome.reason);
|
|
128
|
+
}
|
|
129
|
+
if (failures.length > 0) {
|
|
130
|
+
throw new AggregateError(failures, "Failed to dispose codemode session manager");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async #createAndStoreKernel(
|
|
135
|
+
language: EvalLanguage,
|
|
136
|
+
onMessage: (message: KernelToHostMessage) => void,
|
|
137
|
+
generation: number,
|
|
138
|
+
): Promise<EvalKernel> {
|
|
139
|
+
const kernel = await this.#createKernel(language, onMessage);
|
|
140
|
+
if (generation !== this.#generation) {
|
|
141
|
+
await kernel.close();
|
|
142
|
+
throw new CodemodeSessionDisposedError();
|
|
143
|
+
}
|
|
144
|
+
this.#kernels.set(language, kernel);
|
|
145
|
+
return kernel;
|
|
85
146
|
}
|
|
86
147
|
|
|
87
148
|
async #createKernel(language: EvalLanguage, onMessage: (message: KernelToHostMessage) => void): Promise<EvalKernel> {
|
|
@@ -127,7 +188,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
127
188
|
|
|
128
189
|
#contextFor(signal: AbortSignal): ExtensionContext {
|
|
129
190
|
const ctx = this.#context;
|
|
130
|
-
if (!ctx)
|
|
131
|
-
return ctx;
|
|
191
|
+
if (!ctx) throw new CodemodeContextUnavailableError();
|
|
192
|
+
return { ...ctx, signal: ctx.signal ? AbortSignal.any([ctx.signal, signal]) : signal };
|
|
132
193
|
}
|
|
133
194
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
2
2
|
import type { KernelToHostMessage } from "./bridge/protocol.ts";
|
|
3
3
|
import { type CompletionRequest, type CompletionResult, createCompletionHandler } from "./completion/handler.ts";
|
|
4
4
|
import { type CodemodeSettings, loadCodemodeSettings } from "./config/settings.ts";
|
|
@@ -13,8 +13,15 @@ import {
|
|
|
13
13
|
type InterpreterAvailability,
|
|
14
14
|
} from "./interpreters/detect.ts";
|
|
15
15
|
import { createEvalTool } from "./tool/eval-tool.ts";
|
|
16
|
-
import type { EvalKernel, EvalLanguage } from "./tool/types.ts";
|
|
17
|
-
|
|
16
|
+
import type { EvalKernel, EvalLanguage, ExecuteTool } from "./tool/types.ts";
|
|
17
|
+
|
|
18
|
+
type SessionLifecycleEvent = "session_start" | "session_shutdown" | "session_before_switch" | "session_before_fork";
|
|
19
|
+
|
|
20
|
+
export interface CodemodeExtensionAPI {
|
|
21
|
+
registerTool(tool: ReturnType<typeof createEvalTool>): void;
|
|
22
|
+
on(event: SessionLifecycleEvent, handler: (event: unknown, ctx: ExtensionContext) => Promise<void> | void): void;
|
|
23
|
+
executeTool: ExecuteTool;
|
|
24
|
+
}
|
|
18
25
|
|
|
19
26
|
export interface SenpiCodemodeOptions {
|
|
20
27
|
readonly createSessionManager?: (
|
|
@@ -23,7 +30,7 @@ export interface SenpiCodemodeOptions {
|
|
|
23
30
|
readonly complete?: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
export default function senpiCodemode(pi:
|
|
33
|
+
export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCodemodeOptions = {}): void {
|
|
27
34
|
const manager = new SessionManagerProxy();
|
|
28
35
|
const complete = options.complete ?? ((request, ctx) => createCompletionHandler()(ctx)(request));
|
|
29
36
|
pi.registerTool(
|
|
@@ -37,7 +44,8 @@ export default function senpiCodemode(pi: ExtensionAPI, options: SenpiCodemodeOp
|
|
|
37
44
|
);
|
|
38
45
|
|
|
39
46
|
pi.on("session_start", async (event, ctx) => {
|
|
40
|
-
|
|
47
|
+
const generation = manager.beginReplacement();
|
|
48
|
+
await manager.replace(generation, await createManager(pi, ctx, event, complete, options));
|
|
41
49
|
});
|
|
42
50
|
pi.on("session_shutdown", async () => manager.dispose());
|
|
43
51
|
pi.on("session_before_switch", async () => manager.dispose());
|
|
@@ -46,9 +54,25 @@ export default function senpiCodemode(pi: ExtensionAPI, options: SenpiCodemodeOp
|
|
|
46
54
|
|
|
47
55
|
class SessionManagerProxy implements CodemodeSessionManager {
|
|
48
56
|
#current: CodemodeSessionManager | undefined;
|
|
57
|
+
#generation = 0;
|
|
58
|
+
|
|
59
|
+
beginReplacement(): number {
|
|
60
|
+
this.#generation++;
|
|
61
|
+
return this.#generation;
|
|
62
|
+
}
|
|
49
63
|
|
|
50
|
-
async replace(next: CodemodeSessionManager): Promise<void> {
|
|
51
|
-
|
|
64
|
+
async replace(generation: number, next: CodemodeSessionManager): Promise<void> {
|
|
65
|
+
if (generation !== this.#generation) {
|
|
66
|
+
await next.dispose();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const current = this.#current;
|
|
70
|
+
this.#current = undefined;
|
|
71
|
+
await current?.dispose();
|
|
72
|
+
if (generation !== this.#generation) {
|
|
73
|
+
await next.dispose();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
52
76
|
this.#current = next;
|
|
53
77
|
}
|
|
54
78
|
|
|
@@ -67,6 +91,7 @@ class SessionManagerProxy implements CodemodeSessionManager {
|
|
|
67
91
|
}
|
|
68
92
|
|
|
69
93
|
async dispose(): Promise<void> {
|
|
94
|
+
this.#generation++;
|
|
70
95
|
const current = this.#current;
|
|
71
96
|
this.#current = undefined;
|
|
72
97
|
await current?.dispose();
|
|
@@ -74,7 +99,7 @@ class SessionManagerProxy implements CodemodeSessionManager {
|
|
|
74
99
|
}
|
|
75
100
|
|
|
76
101
|
async function createManager(
|
|
77
|
-
pi:
|
|
102
|
+
pi: CodemodeExtensionAPI,
|
|
78
103
|
ctx: ExtensionContext,
|
|
79
104
|
event: unknown,
|
|
80
105
|
complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>,
|
|
@@ -104,9 +129,10 @@ export function enabledLanguagesFrom(
|
|
|
104
129
|
settings: CodemodeSettings,
|
|
105
130
|
availability: InterpreterAvailability,
|
|
106
131
|
): Record<EvalLanguage, boolean> {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
132
|
+
return {
|
|
133
|
+
py: settings.languages.py && availability.py.detected.ok,
|
|
134
|
+
js: settings.languages.js && availability.js.detected.ok,
|
|
135
|
+
rb: settings.languages.rb && availability.rb.detected.ok,
|
|
136
|
+
jl: settings.languages.jl && availability.jl.detected.ok,
|
|
137
|
+
};
|
|
112
138
|
}
|