@code-yeongyu/senpi 2026.9.23-5 → 2026.9.24-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/dist/bundle/chunks/{anthropic-messages-77YBORJ5.js → anthropic-messages-UPLQFNTT.js} +2 -2
- package/dist/bundle/chunks/{app-server-command-BGT65PGC.js → app-server-command-UHPJ2C4D.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-PSESFNHN.js → azure-openai-responses-XAR2E66I.js} +1 -1
- package/dist/bundle/chunks/{chunk-WXIRFRXY.js → chunk-2PLPSKHD.js} +1 -1
- package/dist/bundle/chunks/{chunk-7PANH3NB.js → chunk-3WBSBDBX.js} +1 -1
- package/dist/bundle/chunks/{chunk-BBOQ5H4W.js → chunk-6WBYI25N.js} +1 -1
- package/dist/bundle/chunks/{chunk-RG3UEFSF.js → chunk-7DY7UYLL.js} +1 -1
- package/dist/bundle/chunks/{chunk-VKBY4F76.js → chunk-BSOJHK4W.js} +1 -1
- package/dist/bundle/chunks/{chunk-3XHWW7SQ.js → chunk-CLZVNQW3.js} +1 -1
- package/dist/bundle/chunks/{chunk-ORCF262V.js → chunk-EJKCBRYK.js} +3 -3
- package/dist/bundle/chunks/chunk-J7IUURQ6.js +8 -0
- package/dist/bundle/chunks/{chunk-PSMTQDII.js → chunk-JRQSHZPP.js} +15 -9
- package/dist/bundle/chunks/{chunk-DUV3UFKX.js → chunk-MKAM7IRH.js} +1 -1
- package/dist/bundle/chunks/chunk-MO2U7YRX.js +11 -0
- package/dist/bundle/chunks/{chunk-ZEFQYQOU.js → chunk-NGHOUIPR.js} +1 -1
- package/dist/bundle/chunks/{chunk-XDSQYM4F.js → chunk-NKVUDCGZ.js} +2 -2
- package/dist/bundle/chunks/{chunk-J3QXPP4T.js → chunk-ONR57UMO.js} +1 -1
- package/dist/bundle/chunks/{chunk-TQDWU3ZQ.js → chunk-SYQVC7UM.js} +1 -1
- package/dist/bundle/chunks/chunk-TTHMOL5G.js +2 -0
- package/dist/bundle/chunks/{chunk-NNHFMZD5.js → chunk-UBPMXBOJ.js} +1 -1
- package/dist/bundle/chunks/chunk-WASCMT6Y.js +2 -0
- package/dist/bundle/chunks/{chunk-EN6KDFY2.js → chunk-WISJJQ4K.js} +1 -1
- package/dist/bundle/chunks/{chunk-IVYZMSFY.js → chunk-WL5EQYNL.js} +1 -1
- package/dist/bundle/chunks/{chunk-LC6SZTVE.js → chunk-WTSAB6AE.js} +1 -1
- package/dist/bundle/chunks/{chunk-HKE42SWX.js → chunk-Y54V65MS.js} +1 -1
- package/dist/bundle/chunks/{chunk-3RN6BFG2.js → chunk-YBC274GY.js} +4 -4
- package/dist/bundle/chunks/{chunk-RRSND33J.js → chunk-YKU45B65.js} +54 -54
- package/dist/bundle/chunks/{chunk-WUYBEF7C.js → chunk-YWO3OBYK.js} +1 -1
- package/dist/bundle/chunks/cli-main-O5Y3TBID.js +3 -0
- package/dist/bundle/chunks/{google-generative-ai-YJHSZAY5.js → google-generative-ai-B5QMFONL.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-JTFWEDNJ.js → google-vertex-O2TGMVWF.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-QXVCHTP3.js → help-fast-path-Y33OIZBU.js} +1 -1
- package/dist/bundle/chunks/{host-command-3PUZYQOK.js → host-command-WJOL27Q7.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-OHPSU5VU.js → host-lifecycle-YBFXYFZP.js} +1 -1
- package/dist/bundle/chunks/{interactive-host-runtime-57LEAJPU.js → interactive-host-runtime-XVTF3NUD.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-OO3NGM4G.js +2 -0
- package/dist/bundle/chunks/{list-tips-Z62FXPOJ.js → list-tips-EBRNBPBM.js} +1 -1
- package/dist/bundle/chunks/{mistral-conversations-VJPMPQ5T.js → mistral-conversations-AS3SAWPA.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-GFSDQQUJ.js → multi-session-host-6MKBBOKF.js} +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-IQ35QACK.js → openai-codex-responses-CK7T4WNM.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-74OLE55U.js → openai-completions-OPQKN7FH.js} +3 -3
- package/dist/bundle/chunks/openai-responses-QHWM3KPO.js +2 -0
- package/dist/bundle/chunks/{openrouter-images-T4GY3JYM.js → openrouter-images-MW5TXV7L.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-J7244EJE.js → package-manager-cli-BMEWLAI2.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-CZT4TRPD.js → rotation-stream-ZFA6ELXJ.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-75CRTY24.js → rpc-mode-5653CV65.js} +1 -1
- package/dist/bundle/chunks/{session-picker-ZIBANFBQ.js → session-picker-PPYK56TA.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +265 -259
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +13 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +28 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +75 -12
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/dynamic-prompt/build.d.ts +6 -1
- package/dist/core/dynamic-prompt/build.d.ts.map +1 -1
- package/dist/core/dynamic-prompt/build.js +0 -7
- package/dist/core/dynamic-prompt/build.js.map +1 -1
- package/dist/core/environment-context.d.ts +32 -0
- package/dist/core/environment-context.d.ts.map +1 -0
- package/dist/core/environment-context.js +63 -0
- package/dist/core/environment-context.js.map +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-sync.d.ts.map +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-sync.js +1 -15
- package/dist/core/extensions/builtin/anthropic-subscription/session-sync.js.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm-renderer.js +3 -0
- package/dist/core/extensions/builtin/goal/cache-warm-renderer.js.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts +19 -2
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm.js +28 -2
- package/dist/core/extensions/builtin/goal/cache-warm.js.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +4 -2
- package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/parked-wait.js +2 -0
- package/dist/core/extensions/builtin/goal/parked-wait.js.map +1 -1
- package/dist/core/extensions/builtin/goal/prompt.js +1 -1
- package/dist/core/extensions/builtin/goal/prompt.js.map +1 -1
- package/dist/core/extensions/builtin/herdr/herdr-state.d.ts +5 -0
- package/dist/core/extensions/builtin/herdr/herdr-state.d.ts.map +1 -1
- package/dist/core/extensions/builtin/herdr/herdr-state.js +28 -3
- package/dist/core/extensions/builtin/herdr/herdr-state.js.map +1 -1
- package/dist/core/extensions/builtin/herdr/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/herdr/index.js +7 -1
- package/dist/core/extensions/builtin/herdr/index.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/hooks/index.js +3 -15
- package/dist/core/extensions/builtin/hooks/index.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/stop-lifecycle.d.ts +26 -0
- package/dist/core/extensions/builtin/hooks/stop-lifecycle.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/stop-lifecycle.js +88 -0
- package/dist/core/extensions/builtin/hooks/stop-lifecycle.js.map +1 -0
- package/dist/core/extensions/builtin/recommended-models/index.d.ts +1 -1
- package/dist/core/extensions/builtin/recommended-models/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/recommended-models/index.js +28 -10
- package/dist/core/extensions/builtin/recommended-models/index.js.map +1 -1
- package/dist/core/extensions/builtin/rules/commands.d.ts.map +1 -1
- package/dist/core/extensions/builtin/rules/commands.js +5 -1
- package/dist/core/extensions/builtin/rules/commands.js.map +1 -1
- package/dist/core/extensions/builtin/rules/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/rules/index.js +25 -3
- package/dist/core/extensions/builtin/rules/index.js.map +1 -1
- package/dist/core/extensions/builtin/rules/rules/constants.d.ts.map +1 -1
- package/dist/core/extensions/builtin/rules/rules/constants.js +22 -14
- package/dist/core/extensions/builtin/rules/rules/constants.js.map +1 -1
- package/dist/core/extensions/builtin/rules/rules/engine.d.ts.map +1 -1
- package/dist/core/extensions/builtin/rules/rules/engine.js +39 -9
- package/dist/core/extensions/builtin/rules/rules/engine.js.map +1 -1
- package/dist/core/extensions/builtin/rules/rules/finder.js +3 -0
- package/dist/core/extensions/builtin/rules/rules/finder.js.map +1 -1
- package/dist/core/extensions/builtin/rules/rules/project-root.d.ts +17 -0
- package/dist/core/extensions/builtin/rules/rules/project-root.d.ts.map +1 -1
- package/dist/core/extensions/builtin/rules/rules/project-root.js +67 -5
- package/dist/core/extensions/builtin/rules/rules/project-root.js.map +1 -1
- package/dist/core/extensions/builtin/rules/rules/types.d.ts +2 -2
- package/dist/core/extensions/builtin/rules/rules/types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/rules/rules/types.js.map +1 -1
- package/dist/core/extensions/builtin/rules/ui/rules-banner.d.ts +3 -1
- package/dist/core/extensions/builtin/rules/ui/rules-banner.d.ts.map +1 -1
- package/dist/core/extensions/builtin/rules/ui/rules-banner.js +1 -1
- package/dist/core/extensions/builtin/rules/ui/rules-banner.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/todo-storage.d.ts.map +1 -1
- package/dist/core/extensions/builtin/todotools/todo-storage.js +10 -2
- package/dist/core/extensions/builtin/todotools/todo-storage.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/tools/todo.d.ts.map +1 -1
- package/dist/core/extensions/builtin/todotools/tools/todo.js +2 -1
- package/dist/core/extensions/builtin/todotools/tools/todo.js.map +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/config.d.ts.map +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/config.js +2 -0
- package/dist/core/extensions/builtin/websearch/websearch/config.js.map +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/provider-endpoints.d.ts.map +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/provider-endpoints.js +2 -0
- package/dist/core/extensions/builtin/websearch/websearch/provider-endpoints.js.map +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/providers/kagi.d.ts +3 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/kagi.d.ts.map +1 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/kagi.js +34 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/kagi.js.map +1 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/serpdive.d.ts +3 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/serpdive.d.ts.map +1 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/serpdive.js +22 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/serpdive.js.map +1 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/providers.js +4 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers.js.map +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/types.d.ts +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/types.js.map +1 -1
- package/dist/core/session-discovery.d.ts +9 -3
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +19 -8
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +8 -4
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-summary-cache.d.ts +16 -3
- package/dist/core/session-summary-cache.d.ts.map +1 -1
- package/dist/core/session-summary-cache.js +19 -3
- package/dist/core/session-summary-cache.js.map +1 -1
- package/dist/core/session-summary-index-file.d.ts +38 -0
- package/dist/core/session-summary-index-file.d.ts.map +1 -0
- package/dist/core/session-summary-index-file.js +119 -0
- package/dist/core/session-summary-index-file.js.map +1 -0
- package/dist/core/session-summary-index.d.ts +52 -0
- package/dist/core/session-summary-index.d.ts.map +1 -0
- package/dist/core/session-summary-index.js +157 -0
- package/dist/core/session-summary-index.js.map +1 -0
- package/dist/core/session-summary.d.ts +21 -2
- package/dist/core/session-summary.d.ts.map +1 -1
- package/dist/core/session-summary.js +53 -14
- package/dist/core/session-summary.js.map +1 -1
- package/dist/core/timings.d.ts +1 -1
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/renderers/read.d.ts.map +1 -1
- package/dist/core/tools/renderers/read.js +7 -2
- package/dist/core/tools/renderers/read.js.map +1 -1
- package/dist/core/tools/renderers/skill-read-path.d.ts +9 -0
- package/dist/core/tools/renderers/skill-read-path.d.ts.map +1 -0
- package/dist/core/tools/renderers/skill-read-path.js +49 -0
- package/dist/core/tools/renderers/skill-read-path.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +7 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/exploration-call.d.ts +1 -1
- package/dist/modes/interactive/components/exploration-call.d.ts.map +1 -1
- package/dist/modes/interactive/components/exploration-call.js +3 -1
- package/dist/modes/interactive/components/exploration-call.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js +32 -16
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-tree.d.ts +32 -0
- package/dist/modes/interactive/components/session-selector-tree.d.ts.map +1 -0
- package/dist/modes/interactive/components/session-selector-tree.js +82 -0
- package/dist/modes/interactive/components/session-selector-tree.js.map +1 -0
- package/dist/modes/interactive/components/session-selector.d.ts +1 -0
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +6 -71
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +4 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +32 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +8 -4
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/AGENTS.md +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/code-preview.ts +31 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/display-code.ts +34 -131
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/display-js-ast.ts +65 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/display-js-layout.ts +90 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/display-js-mask.ts +178 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/display-js.ts +100 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/display-python-script.ts +127 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/display-python.ts +150 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-request.ts +18 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +9 -25
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +16 -9
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts +1 -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 +22 -2
- 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.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +3 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/tool-name-alias.d.ts +6 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/tool-name-alias.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/tool-name-alias.js +6 -19
- package/node_modules/@earendil-works/pi-agent-core/dist/tool-name-alias.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +8 -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 +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-references.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-references.js +8 -35
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-references.js.map +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 +10 -6
- 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-shared.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +4 -3
- 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 +53 -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/openrouter-images.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +30 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +30 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.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 +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.js +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/openai-responses-compat.d.ts +17 -0
- package/node_modules/@earendil-works/pi-ai/dist/openai-responses-compat.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/openai-responses-compat.js +4 -1
- package/node_modules/@earendil-works/pi-ai/dist/openai-responses-compat.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/amazon-bedrock.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/chatgpt-subscription.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/google.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.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/opengateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/venice.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/types.d.ts +6 -0
- 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/prompt-cache-ttl.d.ts +30 -0
- 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 +71 -14
- package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/tool-name-match.d.ts +30 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/tool-name-match.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/tool-name-match.js +71 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/tool-name-match.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.d.ts +5 -0
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.js +7 -2
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/dist/bundle/chunks/chunk-KLDLZHHS.js +0 -11
- package/dist/bundle/chunks/chunk-LTIMMSDM.js +0 -8
- package/dist/bundle/chunks/chunk-XYM54ZWK.js +0 -2
- package/dist/bundle/chunks/cli-main-W55CWPEI.js +0 -3
- package/dist/bundle/chunks/interactive-mode-EDJZ4CSY.js +0 -2
- package/dist/bundle/chunks/openai-responses-7IGZSW33.js +0 -2
|
@@ -12,6 +12,38 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.9.24-2] - 2026-09-24
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Dense JavaScript eval cells are previewed with Bun's built-in printer when the TUI runs on Bun: one statement per line with normal spacing, while every string, number, template, name, and comment keeps its exact text. On Node, JavaScript cells are shown as sent. Dense Python cells are formatted by your own Python (ruff or black when installed, otherwise the standard library for comment-free cells), appearing formatted a moment after the cell is shown. Anything that cannot be shown faithfully is shown as sent, and the code that runs is unchanged. ([#2076](https://github.com/code-yeongyu/senpi/issues/2076))
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- The `eval` tool's run contract is no longer a trap: the schema descriptions now mark `language` and `code` as required for run, and a run call missing either fails with an error that names the enabled languages or the missing cell body instead of a bare "eval run requires language".
|
|
28
|
+
- An omitted `eval` `language` now names only the kernels enabled in this session (`js` and `py` by default), instead of advertising gated Ruby and Julia.
|
|
29
|
+
|
|
30
|
+
### Removed
|
|
31
|
+
|
|
32
|
+
## [2026.9.24] - 2026-09-24
|
|
33
|
+
|
|
34
|
+
### Breaking Changes
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Eval code previews no longer show code that means something different from the cell: breaking a long JavaScript array keeps the parentheses around each element, and a Python formatter result that changes string or docstring text (or is not valid Python) is discarded so the cell is shown as sent. ([#2076](https://github.com/code-yeongyu/senpi/issues/2076))
|
|
43
|
+
- Python eval previews give your ruff most of the 5-second formatting budget instead of a fixed 4 seconds, so a slow first ruff start still formats the cell. ([#2076](https://github.com/code-yeongyu/senpi/issues/2076))
|
|
44
|
+
|
|
45
|
+
### Removed
|
|
46
|
+
|
|
15
47
|
## [2026.9.23-5] - 2026-09-23
|
|
16
48
|
|
|
17
49
|
### Breaking Changes
|
|
@@ -207,15 +207,19 @@ that implements it. Reset only removes kernel variables: save `pool_id` and use
|
|
|
207
207
|
arguments in other languages). An open pool's adapter can be recreated with the
|
|
208
208
|
same name/spec/mode; no worker state is reconstructed in the prelude.
|
|
209
209
|
|
|
210
|
-
## Required
|
|
210
|
+
## Required run fields
|
|
211
211
|
|
|
212
|
-
Every `eval` run call MUST include a `
|
|
212
|
+
Every `eval` run call MUST include a `language` (the kernel that runs the
|
|
213
|
+
cell, one of the enabled languages), the `code` cell body, and a `summary` —
|
|
214
|
+
one line in the language the
|
|
213
215
|
user writes in: a progress update saying what the agent is doing and why, not
|
|
214
216
|
a label for the code. The
|
|
215
217
|
summary is shown in the TUI while the cell runs and in the finished result, so
|
|
216
218
|
you can always tell what is running and why. It has no length limit; a
|
|
217
|
-
collapsed block shows its first three lines.
|
|
218
|
-
`
|
|
219
|
+
collapsed block shows its first three lines. The schema marks all three
|
|
220
|
+
optional only because the control actions (`peek`, `stop`, `list`) share it; a
|
|
221
|
+
run request missing any of them fails with a teaching error that names what to
|
|
222
|
+
add.
|
|
219
223
|
|
|
220
224
|
## Detached cells
|
|
221
225
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.24-2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Source-only senpi extension package for codemode evaluation tools",
|
|
6
6
|
"type": "module",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@babel/parser": "8.0.4",
|
|
34
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.
|
|
34
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.24-2",
|
|
35
35
|
"typebox": "1.3.34"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
38
|
+
"@code-yeongyu/senpi": "2026.9.24-2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
41
|
+
"@code-yeongyu/senpi": "2026.9.24-2"
|
|
42
42
|
},
|
|
43
43
|
"keywords": [
|
|
44
44
|
"senpi",
|
|
@@ -14,6 +14,7 @@ anchor most suites).
|
|
|
14
14
|
| Cell execution, settlement | `cell-handler.ts`, `cell-execution.ts`, `cell-runtime.ts` |
|
|
15
15
|
| Detached cells | `detached-cell-manager.ts` + `detached-cell-{state,snapshot,notification}.ts`, `detached-notification-queue.ts`, `detached-eval-result.ts` |
|
|
16
16
|
| Call/result rendering | `render.ts`, `runtime-label.ts`, `json-tree.ts`, `image.ts`, `tool-widgets.ts` |
|
|
17
|
+
| Code preview layout | `code-preview.ts` -> `display-code.ts` (JS: `display-js*.ts` Bun-only masked `Bun.Transpiler`; Python: `display-python*.ts` user interpreter, background + repaint) |
|
|
17
18
|
| Status events, execution events | `status-events.ts`, `eval-execution-event.ts` |
|
|
18
19
|
| Interrupt, capture | `interrupt-note.ts`, `call-capture.ts` |
|
|
19
20
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { highlightCode, type Theme } from "@code-yeongyu/senpi";
|
|
2
|
+
import { displayCode } from "./display-code.ts";
|
|
3
|
+
import type { EvalLanguage } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
function languageForHighlighter(language: EvalLanguage): "python" | "javascript" | "ruby" | "julia" {
|
|
6
|
+
switch (language) {
|
|
7
|
+
case "py":
|
|
8
|
+
return "python";
|
|
9
|
+
case "js":
|
|
10
|
+
return "javascript";
|
|
11
|
+
case "rb":
|
|
12
|
+
return "ruby";
|
|
13
|
+
case "jl":
|
|
14
|
+
return "julia";
|
|
15
|
+
default: {
|
|
16
|
+
const unreachable: never = language;
|
|
17
|
+
throw new TypeError(`Unhandled eval language: ${String(unreachable)}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function highlightedCode(
|
|
23
|
+
code: string,
|
|
24
|
+
language: EvalLanguage,
|
|
25
|
+
theme: Theme | undefined,
|
|
26
|
+
onFormatted?: () => void,
|
|
27
|
+
): string {
|
|
28
|
+
const normalizedCode = code.trim().length > 0 ? code : "...";
|
|
29
|
+
const lines = highlightCode(displayCode(normalizedCode, language, onFormatted), languageForHighlighter(language));
|
|
30
|
+
return (theme === undefined ? lines.map((line) => line.replace(/\u001b\[[0-9;]*m/gu, "")) : lines).join("\n");
|
|
31
|
+
}
|
|
@@ -1,145 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bunJsPrinter, prettifyJs } from "./display-js.ts";
|
|
2
|
+
import { pythonDisplay } from "./display-python.ts";
|
|
2
3
|
import type { EvalLanguage } from "./types.ts";
|
|
3
4
|
|
|
4
|
-
// Display-only: models often send a
|
|
5
|
-
//
|
|
6
|
-
// comment verbatim. The tool arguments are never touched (senpi#1472).
|
|
5
|
+
// Display-only (senpi#2050, senpi#2076): models often send a cell as one long line of joined
|
|
6
|
+
// statements. The tool arguments are never touched (senpi#1472).
|
|
7
7
|
const DENSE_LINE_LENGTH = 100;
|
|
8
|
-
const ARRAY_BREAK_LENGTH = 60;
|
|
9
|
-
const INDENT = " ";
|
|
10
8
|
const CACHE_LIMIT = 64;
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// A whitespace/comma/comment region between two tokens that becomes a line break.
|
|
15
|
-
type Gap = {
|
|
16
|
-
readonly start: number;
|
|
17
|
-
readonly end: number;
|
|
18
|
-
readonly contentDepth: number;
|
|
19
|
-
readonly nextDepth: number;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const cache = new Map<string, string>();
|
|
23
|
-
|
|
24
|
-
function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
25
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function sourceNode(value: unknown): SourceNode | undefined {
|
|
29
|
-
if (!isRecord(value)) return undefined;
|
|
30
|
-
const { type, start, end } = value;
|
|
31
|
-
if (typeof type !== "string" || typeof start !== "number" || typeof end !== "number") return undefined;
|
|
32
|
-
return { type, start, end };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function sourceNodes(value: unknown): SourceNode[] {
|
|
36
|
-
if (!Array.isArray(value)) return [];
|
|
37
|
-
return value.flatMap((item) => sourceNode(item) ?? []);
|
|
38
|
-
}
|
|
10
|
+
const jsCache = new Map<string, string>();
|
|
39
11
|
|
|
40
12
|
function isDense(code: string): boolean {
|
|
41
13
|
return code.split("\n").some((line) => line.length > DENSE_LINE_LENGTH);
|
|
42
14
|
}
|
|
43
15
|
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (error instanceof SyntaxError) return undefined;
|
|
54
|
-
throw error;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function siblingGaps(children: readonly SourceNode[], depth: number): Gap[] {
|
|
59
|
-
return children.slice(1).map((child, index) => ({
|
|
60
|
-
start: children[index]?.end ?? child.start,
|
|
61
|
-
end: child.start,
|
|
62
|
-
contentDepth: depth,
|
|
63
|
-
nextDepth: depth,
|
|
64
|
-
}));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Gaps for a bracketed container whose children move onto their own indented lines.
|
|
68
|
-
function containerGaps(container: SourceNode, children: readonly SourceNode[], depth: number): Gap[] {
|
|
69
|
-
const first = children[0];
|
|
70
|
-
const last = children.at(-1);
|
|
71
|
-
if (first === undefined || last === undefined) return [];
|
|
72
|
-
const inner = depth + 1;
|
|
73
|
-
return [
|
|
74
|
-
{ start: container.start + 1, end: first.start, contentDepth: inner, nextDepth: inner },
|
|
75
|
-
...siblingGaps(children, inner),
|
|
76
|
-
{ start: last.end, end: container.end - 1, contentDepth: inner, nextDepth: depth },
|
|
77
|
-
];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function breakableChildren(node: SourceNode, record: Readonly<Record<string, unknown>>, code: string) {
|
|
81
|
-
if (node.type === "BlockStatement" || node.type === "StaticBlock") return sourceNodes(record.body);
|
|
82
|
-
if (node.type !== "ArrayExpression") return undefined;
|
|
83
|
-
const elements = sourceNodes(record.elements);
|
|
84
|
-
const text = code.slice(node.start, node.end);
|
|
85
|
-
if (elements.length < 2 || text.length <= ARRAY_BREAK_LENGTH || text.includes("\n")) return undefined;
|
|
86
|
-
return elements;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function collectGaps(value: unknown, depth: number, code: string, gaps: Gap[]): void {
|
|
90
|
-
if (Array.isArray(value)) {
|
|
91
|
-
for (const item of value) collectGaps(item, depth, code, gaps);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (!isRecord(value)) return;
|
|
95
|
-
const node = sourceNode(value);
|
|
96
|
-
const children = node === undefined ? undefined : breakableChildren(node, value, code);
|
|
97
|
-
const breaks = node !== undefined && children !== undefined && children.length > 0;
|
|
98
|
-
if (breaks) gaps.push(...containerGaps(node, children, depth));
|
|
99
|
-
for (const [key, child] of Object.entries(value)) {
|
|
100
|
-
if (key === "loc" || key === "extra" || key.endsWith("Comments")) continue;
|
|
101
|
-
if (child !== null && typeof child === "object") collectGaps(child, breaks ? depth + 1 : depth, code, gaps);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function renderGap(text: string, gap: Gap): string {
|
|
106
|
-
let rest = text.trim();
|
|
107
|
-
let attached = "";
|
|
108
|
-
if (rest.startsWith(",") || rest.startsWith(";")) {
|
|
109
|
-
attached = rest.slice(0, 1);
|
|
110
|
-
rest = rest.slice(1).trim();
|
|
111
|
-
}
|
|
112
|
-
const contentIndent = INDENT.repeat(gap.contentDepth);
|
|
113
|
-
const content = rest
|
|
114
|
-
.split("\n")
|
|
115
|
-
.map((line) => line.trim())
|
|
116
|
-
.filter((line) => line.length > 0)
|
|
117
|
-
.map((line) => `\n${contentIndent}${line}`)
|
|
118
|
-
.join("");
|
|
119
|
-
return `${attached}${content}\n${INDENT.repeat(gap.nextDepth)}`;
|
|
16
|
+
function displayJs(code: string): string {
|
|
17
|
+
const print = bunJsPrinter();
|
|
18
|
+
if (print === null) return code;
|
|
19
|
+
const cached = jsCache.get(code);
|
|
20
|
+
if (cached !== undefined) return cached;
|
|
21
|
+
const formatted = prettifyJs(code, print) ?? code;
|
|
22
|
+
if (jsCache.size >= CACHE_LIMIT) jsCache.delete(jsCache.keys().next().value ?? "");
|
|
23
|
+
jsCache.set(code, formatted);
|
|
24
|
+
return formatted;
|
|
120
25
|
}
|
|
121
26
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
27
|
+
/**
|
|
28
|
+
* The preview text for a cell. Dense JavaScript is laid out by the built-in Bun printer only when
|
|
29
|
+
* the renderer runs on Bun; dense Python is formatted by the user's own interpreter in the
|
|
30
|
+
* background, so the first render shows the cell as sent and `onFormatted` asks for a repaint.
|
|
31
|
+
* Pass `onFormatted` only for complete code; without it no formatter process starts.
|
|
32
|
+
*/
|
|
33
|
+
export function displayCode(code: string, language: EvalLanguage, onFormatted?: () => void): string {
|
|
34
|
+
if (!isDense(code)) return code;
|
|
35
|
+
switch (language) {
|
|
36
|
+
case "js":
|
|
37
|
+
return displayJs(code);
|
|
38
|
+
case "py":
|
|
39
|
+
return pythonDisplay.display(code, onFormatted);
|
|
40
|
+
case "rb":
|
|
41
|
+
case "jl":
|
|
42
|
+
return code;
|
|
43
|
+
default: {
|
|
44
|
+
const unreachable: never = language;
|
|
45
|
+
return unreachable;
|
|
46
|
+
}
|
|
133
47
|
}
|
|
134
|
-
return (output + code.slice(cursor)).trim();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function displayCode(code: string, language: EvalLanguage): string {
|
|
138
|
-
if (language !== "js" || !isDense(code)) return code;
|
|
139
|
-
const cached = cache.get(code);
|
|
140
|
-
if (cached !== undefined) return cached;
|
|
141
|
-
const formatted = reformat(code);
|
|
142
|
-
if (cache.size >= CACHE_LIMIT) cache.delete(cache.keys().next().value ?? "");
|
|
143
|
-
cache.set(code, formatted);
|
|
144
|
-
return formatted;
|
|
145
48
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
|
|
3
|
+
export type SourceNode = {
|
|
4
|
+
readonly type: string;
|
|
5
|
+
readonly start: number;
|
|
6
|
+
readonly end: number;
|
|
7
|
+
readonly record: Readonly<Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type Edit = { readonly start: number; readonly end: number; readonly text: string };
|
|
11
|
+
|
|
12
|
+
export function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
13
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function sourceNode(value: unknown): SourceNode | undefined {
|
|
17
|
+
if (!isRecord(value)) return undefined;
|
|
18
|
+
const { type, start, end } = value;
|
|
19
|
+
if (typeof type !== "string" || typeof start !== "number" || typeof end !== "number") return undefined;
|
|
20
|
+
return { type, start, end, record: value };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function sourceNodes(value: unknown): SourceNode[] {
|
|
24
|
+
if (!Array.isArray(value)) return [];
|
|
25
|
+
return value.flatMap((item) => sourceNode(item) ?? []);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function childNodes(node: SourceNode): SourceNode[] {
|
|
29
|
+
return Object.entries(node.record).flatMap(([key, value]) => {
|
|
30
|
+
if (key === "loc" || key === "extra" || key.endsWith("Comments")) return [];
|
|
31
|
+
return Array.isArray(value) ? sourceNodes(value) : (sourceNode(value) ?? []);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Parses a cell the way the js kernel accepts it: module syntax, top-level `await` and `return`. */
|
|
36
|
+
export function parseFile(code: string): SourceNode | undefined {
|
|
37
|
+
try {
|
|
38
|
+
return sourceNode(
|
|
39
|
+
parse(code, {
|
|
40
|
+
sourceType: "module",
|
|
41
|
+
allowAwaitOutsideFunction: true,
|
|
42
|
+
allowReturnOutsideFunction: true,
|
|
43
|
+
allowImportExportEverywhere: true,
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error instanceof SyntaxError) return undefined;
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function programOf(file: SourceNode | undefined): SourceNode | undefined {
|
|
53
|
+
return file === undefined ? undefined : sourceNode(file.record.program);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function applyEdits(code: string, edits: readonly Edit[]): string {
|
|
57
|
+
const sorted = [...edits].sort((left, right) => left.start - right.start);
|
|
58
|
+
let output = "";
|
|
59
|
+
let cursor = 0;
|
|
60
|
+
for (const edit of sorted) {
|
|
61
|
+
output += code.slice(cursor, edit.start) + edit.text;
|
|
62
|
+
cursor = edit.end;
|
|
63
|
+
}
|
|
64
|
+
return output + code.slice(cursor);
|
|
65
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyEdits,
|
|
3
|
+
childNodes,
|
|
4
|
+
type Edit,
|
|
5
|
+
parseFile,
|
|
6
|
+
programOf,
|
|
7
|
+
type SourceNode,
|
|
8
|
+
sourceNode,
|
|
9
|
+
sourceNodes,
|
|
10
|
+
} from "./display-js-ast.ts";
|
|
11
|
+
|
|
12
|
+
const ARRAY_BREAK_LENGTH = 60;
|
|
13
|
+
const INDENT = " ";
|
|
14
|
+
|
|
15
|
+
function forHeaderEdits(code: string, node: SourceNode, edits: Edit[]): void {
|
|
16
|
+
if (node.type === "ForStatement") {
|
|
17
|
+
const body = sourceNode(node.record.body);
|
|
18
|
+
const open = code.indexOf("(", node.start);
|
|
19
|
+
const close = body === undefined ? -1 : code.lastIndexOf(")", body.start);
|
|
20
|
+
if (open >= 0 && close > open) {
|
|
21
|
+
const parts = ["init", "test", "update"].map((key) => {
|
|
22
|
+
const part = sourceNode(node.record[key]);
|
|
23
|
+
return part === undefined ? "" : code.slice(part.start, part.end);
|
|
24
|
+
});
|
|
25
|
+
const header = parts.every((part) => part.length === 0) ? ";;" : parts.join("; ");
|
|
26
|
+
if (code.slice(open + 1, close) !== header) edits.push({ start: open + 1, end: close, text: header });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const child of childNodes(node)) forHeaderEdits(code, child, edits);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function lineIndent(code: string, position: number): string {
|
|
33
|
+
const line = code.slice(code.lastIndexOf("\n", position - 1) + 1);
|
|
34
|
+
return line.slice(0, line.length - line.trimStart().length);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Only whitespace in a gap is rewritten: a gap between babel element nodes can also hold the
|
|
38
|
+
// parentheses of a parenthesized element, and those must stay (senpi#2076).
|
|
39
|
+
function gapTokens(code: string, start: number, end: number): string {
|
|
40
|
+
return code.slice(start, end).replace(/\s+/gu, "");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// A one-line array longer than ARRAY_BREAK_LENGTH puts each element on its own line (senpi#2050).
|
|
44
|
+
// `forcedIndent` is the indentation of an element line an enclosing break just created.
|
|
45
|
+
function arrayEdits(code: string, node: SourceNode, forcedIndent: string | undefined, edits: Edit[]): void {
|
|
46
|
+
const rawElements = node.record.elements;
|
|
47
|
+
const elements = sourceNodes(rawElements);
|
|
48
|
+
const first = elements[0];
|
|
49
|
+
const last = elements.at(-1);
|
|
50
|
+
const text = code.slice(node.start, node.end);
|
|
51
|
+
const breaks =
|
|
52
|
+
node.type === "ArrayExpression" &&
|
|
53
|
+
Array.isArray(rawElements) &&
|
|
54
|
+
elements.length === rawElements.length &&
|
|
55
|
+
elements.length >= 2 &&
|
|
56
|
+
text.length > ARRAY_BREAK_LENGTH &&
|
|
57
|
+
!text.includes("\n");
|
|
58
|
+
if (!breaks || first === undefined || last === undefined) {
|
|
59
|
+
for (const child of childNodes(node)) arrayEdits(code, child, forcedIndent, edits);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const base = forcedIndent ?? lineIndent(code, node.start);
|
|
63
|
+
const inner = `${base}${INDENT}`;
|
|
64
|
+
edits.push({
|
|
65
|
+
start: node.start + 1,
|
|
66
|
+
end: first.start,
|
|
67
|
+
text: `\n${inner}${gapTokens(code, node.start + 1, first.start)}`,
|
|
68
|
+
});
|
|
69
|
+
elements.slice(1).forEach((element, index) => {
|
|
70
|
+
const start = elements[index]?.end ?? element.start;
|
|
71
|
+
const [before = "", after = ""] = gapTokens(code, start, element.start).split(",");
|
|
72
|
+
edits.push({ start, end: element.start, text: `${before},\n${inner}${after}` });
|
|
73
|
+
});
|
|
74
|
+
edits.push({ start: last.end, end: node.end - 1, text: `${gapTokens(code, last.end, node.end - 1)}\n${base}` });
|
|
75
|
+
for (const element of elements) arrayEdits(code, element, inner, edits);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Printer fix-ups on the restored cell: `for (a; b; c)` headers, then long-array breaks. */
|
|
79
|
+
export function finishLayout(code: string): string | undefined {
|
|
80
|
+
const program = programOf(parseFile(code));
|
|
81
|
+
if (program === undefined) return undefined;
|
|
82
|
+
const headerEdits: Edit[] = [];
|
|
83
|
+
forHeaderEdits(code, program, headerEdits);
|
|
84
|
+
const withHeaders = applyEdits(code, headerEdits);
|
|
85
|
+
const reparsed = programOf(parseFile(withHeaders));
|
|
86
|
+
if (reparsed === undefined) return undefined;
|
|
87
|
+
const breaks: Edit[] = [];
|
|
88
|
+
arrayEdits(withHeaders, reparsed, undefined, breaks);
|
|
89
|
+
return applyEdits(withHeaders, breaks);
|
|
90
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { applyEdits, childNodes, type Edit, type SourceNode, sourceNode } from "./display-js-ast.ts";
|
|
2
|
+
|
|
3
|
+
const LITERAL_TYPES = new Set([
|
|
4
|
+
"StringLiteral",
|
|
5
|
+
"NumericLiteral",
|
|
6
|
+
"BigIntLiteral",
|
|
7
|
+
"RegExpLiteral",
|
|
8
|
+
"BooleanLiteral",
|
|
9
|
+
"NullLiteral",
|
|
10
|
+
"TemplateLiteral",
|
|
11
|
+
"TaggedTemplateExpression",
|
|
12
|
+
"DirectiveLiteral",
|
|
13
|
+
]);
|
|
14
|
+
// One placeholder per spelling, so `break outer` still resolves against `outer:` for Bun's parser.
|
|
15
|
+
const NAME_TYPES = new Set(["Identifier", "PrivateName"]);
|
|
16
|
+
// The grammar requires a string literal under these parents (module specifiers, attributes, and
|
|
17
|
+
// string export names), so the placeholder stays a string there.
|
|
18
|
+
const STRING_SLOT_PARENTS = new Set([
|
|
19
|
+
"ImportDeclaration",
|
|
20
|
+
"ExportAllDeclaration",
|
|
21
|
+
"ExportNamedDeclaration",
|
|
22
|
+
"ImportAttribute",
|
|
23
|
+
"ImportSpecifier",
|
|
24
|
+
"ExportSpecifier",
|
|
25
|
+
"ExportNamespaceSpecifier",
|
|
26
|
+
"ExportDefaultSpecifier",
|
|
27
|
+
]);
|
|
28
|
+
const STATEMENT_LISTS = new Set(["Program", "BlockStatement", "StaticBlock", "ClassBody", "SwitchCase"]);
|
|
29
|
+
|
|
30
|
+
type SourceComment = { readonly start: number; readonly end: number; readonly line: boolean };
|
|
31
|
+
type MaskedRange = SourceNode & { readonly stringSlot: boolean };
|
|
32
|
+
export type Placeholder = { readonly id: string; readonly raw: string; readonly expected: number };
|
|
33
|
+
export type CommentPlaceholder = Placeholder & { readonly trailing: boolean };
|
|
34
|
+
export type Mask = {
|
|
35
|
+
readonly prefix: string;
|
|
36
|
+
readonly masked: string;
|
|
37
|
+
readonly values: ReadonlyMap<string, Placeholder>;
|
|
38
|
+
readonly comments: readonly CommentPlaceholder[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function fileComments(file: SourceNode): SourceComment[] | undefined {
|
|
42
|
+
const comments = file.record.comments;
|
|
43
|
+
if (!Array.isArray(comments)) return undefined;
|
|
44
|
+
const parsed: SourceComment[] = [];
|
|
45
|
+
for (const comment of comments) {
|
|
46
|
+
const node = sourceNode(comment);
|
|
47
|
+
if (node === undefined) return undefined;
|
|
48
|
+
parsed.push({ start: node.start, end: node.end, line: node.type === "CommentLine" });
|
|
49
|
+
}
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function innermostContainer(node: SourceNode, start: number, end: number): SourceNode {
|
|
54
|
+
for (const child of childNodes(node)) {
|
|
55
|
+
if (child.start <= start && end <= child.end) return innermostContainer(child, start, end);
|
|
56
|
+
}
|
|
57
|
+
return node;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function sitsInStatementList(code: string, program: SourceNode, comment: SourceComment): boolean {
|
|
61
|
+
const container = innermostContainer(program, comment.start, comment.end);
|
|
62
|
+
if (!STATEMENT_LISTS.has(container.type)) return false;
|
|
63
|
+
if (container.type !== "SwitchCase") return true;
|
|
64
|
+
const test = sourceNode(container.record.test);
|
|
65
|
+
const colon = code.indexOf(":", test?.end ?? container.start);
|
|
66
|
+
return colon >= 0 && colon < comment.start;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function followsCodeOnItsLine(code: string, comment: SourceComment, comments: readonly SourceComment[]): boolean {
|
|
70
|
+
let index = comment.start - 1;
|
|
71
|
+
while (index >= 0 && code[index] !== "\n") {
|
|
72
|
+
const position = index;
|
|
73
|
+
const covering = comments.find((other) => other.start <= position && position < other.end);
|
|
74
|
+
if (covering !== undefined) {
|
|
75
|
+
index = covering.start - 1;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (code[index] !== " " && code[index] !== "\t") return true;
|
|
79
|
+
index -= 1;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function placeholderPrefix(code: string): string {
|
|
85
|
+
let prefix = "$display";
|
|
86
|
+
while (code.includes(prefix)) prefix = `${prefix}_`;
|
|
87
|
+
return prefix;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function collectRanges(node: SourceNode, parent: SourceNode | undefined, ranges: Map<string, MaskedRange>): void {
|
|
91
|
+
if (LITERAL_TYPES.has(node.type) || NAME_TYPES.has(node.type)) {
|
|
92
|
+
const stringSlot = node.type === "StringLiteral" && parent !== undefined && STRING_SLOT_PARENTS.has(parent.type);
|
|
93
|
+
const key = `${node.start}:${node.end}`;
|
|
94
|
+
if (!ranges.has(key)) ranges.set(key, { ...node, stringSlot });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
for (const child of childNodes(node)) collectRanges(child, node, ranges);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Swaps every literal, name, and statement-level comment for an opaque placeholder so a printer
|
|
102
|
+
* can only move them. Undefined when a comment sits where a placeholder statement cannot stand.
|
|
103
|
+
*/
|
|
104
|
+
export function maskSource(code: string, file: SourceNode): Mask | undefined {
|
|
105
|
+
const program = sourceNode(file.record.program);
|
|
106
|
+
const comments = fileComments(file);
|
|
107
|
+
if (program === undefined || comments === undefined) return undefined;
|
|
108
|
+
const ranges = new Map<string, MaskedRange>();
|
|
109
|
+
collectRanges(program, undefined, ranges);
|
|
110
|
+
const prefix = placeholderPrefix(code);
|
|
111
|
+
const edits: Edit[] = [];
|
|
112
|
+
const values = new Map<string, Placeholder>();
|
|
113
|
+
const nameIds = new Map<string, string>();
|
|
114
|
+
let counter = 0;
|
|
115
|
+
for (const range of ranges.values()) {
|
|
116
|
+
const raw = code.slice(range.start, range.end);
|
|
117
|
+
const reused = NAME_TYPES.has(range.type) ? nameIds.get(raw) : undefined;
|
|
118
|
+
const id = reused ?? `${prefix}${counter++}`;
|
|
119
|
+
if (NAME_TYPES.has(range.type)) nameIds.set(raw, id);
|
|
120
|
+
values.set(id, { id, raw, expected: (values.get(id)?.expected ?? 0) + 1 });
|
|
121
|
+
edits.push({ start: range.start, end: range.end, text: range.stringSlot ? `"${id}"` : id });
|
|
122
|
+
}
|
|
123
|
+
const masked: CommentPlaceholder[] = [];
|
|
124
|
+
for (const comment of comments) {
|
|
125
|
+
if (!sitsInStatementList(code, program, comment)) return undefined;
|
|
126
|
+
const id = `${prefix}${counter++}`;
|
|
127
|
+
const trailing = comment.line && followsCodeOnItsLine(code, comment, comments);
|
|
128
|
+
masked.push({ id, raw: code.slice(comment.start, comment.end), expected: 1, trailing });
|
|
129
|
+
edits.push({ start: comment.start, end: comment.end, text: `\n${id};\n` });
|
|
130
|
+
}
|
|
131
|
+
return { prefix, masked: applyEdits(code, edits), values, comments: masked };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function restoreComments(printed: string, comments: readonly CommentPlaceholder[]): string | undefined {
|
|
135
|
+
const lines = printed.split("\n");
|
|
136
|
+
for (const comment of comments) {
|
|
137
|
+
const matches = lines.flatMap((line, index) => (line.trim() === `${comment.id};` ? [index] : []));
|
|
138
|
+
const index = matches[0];
|
|
139
|
+
if (matches.length !== 1 || index === undefined) return undefined;
|
|
140
|
+
const line = lines[index] ?? "";
|
|
141
|
+
let previous = index - 1;
|
|
142
|
+
while (previous >= 0 && (lines[previous] ?? "").trim().length === 0) previous -= 1;
|
|
143
|
+
const target = lines[previous];
|
|
144
|
+
if (comment.trailing && target !== undefined && !target.includes("//")) {
|
|
145
|
+
lines[previous] = `${target} ${comment.raw}`;
|
|
146
|
+
lines.splice(index, 1);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
lines[index] = `${line.slice(0, line.length - line.trimStart().length)}${comment.raw}`;
|
|
150
|
+
}
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function escapeRegExp(text: string): string {
|
|
155
|
+
return text.replace(/[\\^$.*+?()[\]{}|]/gu, "\\$&");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function restoreValues(printed: string, mask: Mask): string | undefined {
|
|
159
|
+
const id = `${escapeRegExp(mask.prefix)}\\d+`;
|
|
160
|
+
const pattern = new RegExp(`"(${id})"|'(${id})'|(?<![\\w$])(${id})(?![\\w$])`, "gu");
|
|
161
|
+
const counts = new Map<string, number>();
|
|
162
|
+
let unknown = false;
|
|
163
|
+
const restored = printed.replace(pattern, (match, double: unknown, single: unknown, bare: unknown) => {
|
|
164
|
+
const key = [double, single, bare].find((group): group is string => typeof group === "string") ?? "";
|
|
165
|
+
const value = mask.values.get(key);
|
|
166
|
+
if (value === undefined) {
|
|
167
|
+
unknown = true;
|
|
168
|
+
return match;
|
|
169
|
+
}
|
|
170
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
171
|
+
return value.raw;
|
|
172
|
+
});
|
|
173
|
+
if (unknown) return undefined;
|
|
174
|
+
for (const value of mask.values.values()) {
|
|
175
|
+
if (counts.get(value.id) !== value.expected) return undefined;
|
|
176
|
+
}
|
|
177
|
+
return restored;
|
|
178
|
+
}
|