@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
|
@@ -8,6 +8,11 @@ export interface DynamicPromptCoreContext {
|
|
|
8
8
|
toolSection: string;
|
|
9
9
|
}
|
|
10
10
|
export interface BuildDynamicSystemPromptOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Session working directory. Not rendered: cwd and date reach the model as an
|
|
13
|
+
* append-only environment-context message so this prompt stays byte-stable
|
|
14
|
+
* across days and directories (senpi#2093).
|
|
15
|
+
*/
|
|
11
16
|
cwd: string;
|
|
12
17
|
selectedTools: string[];
|
|
13
18
|
toolSnippets: Record<string, string>;
|
|
@@ -21,7 +26,7 @@ export interface BuildDynamicSystemPromptOptions {
|
|
|
21
26
|
/**
|
|
22
27
|
* Replaces the default core sections (identity through style) with a
|
|
23
28
|
* model-specific full rewrite. Tool section, tuning, context files, skills,
|
|
24
|
-
*
|
|
29
|
+
* and workstation assembly stay in this builder.
|
|
25
30
|
*/
|
|
26
31
|
corePrompt?: (context: DynamicPromptCoreContext) => string;
|
|
27
32
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/core/dynamic-prompt/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAQ1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAA2B,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEpF,oFAAoF;AACpF,MAAM,WAAW,wBAAwB;IACxC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,+BAA+B;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvD,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,MAAM,CAAC;IAC3D;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACxC;AAmBD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/core/dynamic-prompt/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAQ1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAA2B,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEpF,oFAAoF;AACpF,MAAM,WAAW,wBAAwB;IACxC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,+BAA+B;IAC/C;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvD,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,MAAM,CAAC;IAC3D;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACxC;AAmBD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,GAAG,MAAM,CAmDzF"}
|
|
@@ -24,9 +24,7 @@ function buildContextFilesSection(contextFiles) {
|
|
|
24
24
|
return lines.join("\n").trimEnd();
|
|
25
25
|
}
|
|
26
26
|
export function buildDynamicSystemPrompt(options) {
|
|
27
|
-
const promptCwd = options.cwd.replace(/\\/g, "/");
|
|
28
27
|
const tools = categorizeTools(options.selectedTools);
|
|
29
|
-
const date = new Date().toISOString().slice(0, 10);
|
|
30
28
|
const toolSection = buildToolSection({
|
|
31
29
|
tools,
|
|
32
30
|
toolSnippets: options.toolSnippets,
|
|
@@ -65,11 +63,6 @@ export function buildDynamicSystemPrompt(options) {
|
|
|
65
63
|
selectedTools: options.selectedTools,
|
|
66
64
|
dialect: options.workstationDialect ?? "default",
|
|
67
65
|
}));
|
|
68
|
-
// The anthropic-subscription lane appends these dynamic lines after the stable sections so the composed
|
|
69
|
-
// prompt is a single string. An earlier draft split at this point for prompt-cache scoping, but a
|
|
70
|
-
// wire-level probe proved the installed CLI joins array elements into one system block, so the
|
|
71
|
-
// split was removed (the sentinel leaked to the model as literal text).
|
|
72
|
-
sections.push("", `Current date: ${date}`, `Current working directory: ${promptCwd}`);
|
|
73
66
|
return sections.join("\n");
|
|
74
67
|
}
|
|
75
68
|
//# sourceMappingURL=build.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/core/dynamic-prompt/build.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAA2B,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/core/dynamic-prompt/build.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Skill } from \"../skills.ts\";\nimport { formatSkillsForPrompt } from \"../skills.ts\";\nimport { buildIdentitySection } from \"./identity.ts\";\nimport { buildIntentGate } from \"./intent-gate.ts\";\nimport { buildPoliciesSection } from \"./policies.ts\";\nimport { buildStyleSection } from \"./style.ts\";\nimport { categorizeTools } from \"./tool-categorization.ts\";\nimport { buildToolSection } from \"./tool-section.ts\";\nimport type { AvailableTool } from \"./types.ts\";\nimport { buildVerificationSection } from \"./verification.ts\";\nimport { buildWorkingTaskSection } from \"./working-task.ts\";\nimport { buildWorkstationSection, type WorkstationDialect } from \"./workstation.ts\";\n\n/** Context handed to a `corePrompt` override so it can reuse the dynamic pieces. */\nexport interface DynamicPromptCoreContext {\n\ttools: AvailableTool[];\n\t/** Rendered \"## Available Tools\" (+ \"## Tool Guidelines\") section. */\n\ttoolSection: string;\n}\n\nexport interface BuildDynamicSystemPromptOptions {\n\t/**\n\t * Session working directory. Not rendered: cwd and date reach the model as an\n\t * append-only environment-context message so this prompt stays byte-stable\n\t * across days and directories (senpi#2093).\n\t */\n\tcwd: string;\n\tselectedTools: string[];\n\ttoolSnippets: Record<string, string>;\n\tpromptGuidelines: string[];\n\tcontextFiles: Array<{ path: string; content: string }>;\n\tskills: Skill[];\n\ttuningSection?: string;\n\t/**\n\t * Replaces the default core sections (identity through style) with a\n\t * model-specific full rewrite. Tool section, tuning, context files, skills,\n\t * and workstation assembly stay in this builder.\n\t */\n\tcorePrompt?: (context: DynamicPromptCoreContext) => string;\n\t/**\n\t * Wording dialect for the workstation execution-context instruction.\n\t * Presets pass their model family; the fallback prompt uses `default`\n\t * (maximum emphasis).\n\t */\n\tworkstationDialect?: WorkstationDialect;\n}\n\nfunction buildContextFilesSection(contextFiles: Array<{ path: string; content: string }>): string {\n\tif (contextFiles.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"## Project Context\",\n\t\t\"\",\n\t\t\"Project instruction files (below, and in [Directory Context: ...] blocks injected during reads) bind files under their directory; deeper files win on conflict; explicit user instructions override.\",\n\t\t\"\",\n\t];\n\tfor (const contextFile of contextFiles) {\n\t\tlines.push(`### ${contextFile.path}`, \"\", contextFile.content.trimEnd(), \"\");\n\t}\n\treturn lines.join(\"\\n\").trimEnd();\n}\n\nexport function buildDynamicSystemPrompt(options: BuildDynamicSystemPromptOptions): string {\n\tconst tools = categorizeTools(options.selectedTools);\n\n\tconst toolSection = buildToolSection({\n\t\ttools,\n\t\ttoolSnippets: options.toolSnippets,\n\t\tpromptGuidelines: options.promptGuidelines,\n\t});\n\n\tconst sections = options.corePrompt\n\t\t? [options.corePrompt({ tools, toolSection })]\n\t\t: [\n\t\t\t\tbuildIdentitySection(),\n\t\t\t\t\"\",\n\t\t\t\tbuildIntentGate({ tools }),\n\t\t\t\t\"\",\n\t\t\t\tbuildWorkingTaskSection(),\n\t\t\t\t\"\",\n\t\t\t\tbuildVerificationSection(),\n\t\t\t\t\"\",\n\t\t\t\ttoolSection,\n\t\t\t\t\"\",\n\t\t\t\tbuildPoliciesSection(),\n\t\t\t\t\"\",\n\t\t\t\tbuildStyleSection(),\n\t\t\t];\n\n\tconst tuning = options.tuningSection?.trim();\n\tif (tuning) {\n\t\tsections.push(\"\", tuning);\n\t}\n\n\tconst contextFilesSection = buildContextFilesSection(options.contextFiles);\n\tif (contextFilesSection) {\n\t\tsections.push(\"\", contextFilesSection);\n\t}\n\n\tconst skillsSection = formatSkillsForPrompt(options.skills);\n\tif (skillsSection) {\n\t\tsections.push(skillsSection);\n\t}\n\n\tsections.push(\n\t\t\"\",\n\t\tbuildWorkstationSection({\n\t\t\tselectedTools: options.selectedTools,\n\t\t\tdialect: options.workstationDialect ?? \"default\",\n\t\t}),\n\t);\n\n\treturn sections.join(\"\\n\");\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment context (working directory + current date) delivered to the
|
|
3
|
+
* model as an append-only, hidden user-role custom message instead of as
|
|
4
|
+
* system prompt text (senpi#2093).
|
|
5
|
+
*
|
|
6
|
+
* A date or cwd line inside the system prompt rewrites the provider-visible
|
|
7
|
+
* prefix every day and in every directory, so automatic prefix caches
|
|
8
|
+
* (OpenAI, Anthropic, and the rest) miss the whole system prompt and
|
|
9
|
+
* everything after it. Carrying the values in a message placed before the
|
|
10
|
+
* next user turn keeps the system prompt byte-stable; a new message is
|
|
11
|
+
* appended only when a value changes, and earlier ones are never rewritten.
|
|
12
|
+
*/
|
|
13
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
14
|
+
import type { CustomMessage } from "./messages.ts";
|
|
15
|
+
export declare const ENVIRONMENT_CONTEXT_MESSAGE_TYPE = "environment-context";
|
|
16
|
+
export interface EnvironmentContext {
|
|
17
|
+
/** Working directory with `/` separators. */
|
|
18
|
+
cwd: string;
|
|
19
|
+
/** UTC calendar date, `YYYY-MM-DD`. */
|
|
20
|
+
currentDate: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function resolveEnvironmentContext(cwd: string, now?: Date): EnvironmentContext;
|
|
23
|
+
export declare function formatEnvironmentContext(context: EnvironmentContext): string;
|
|
24
|
+
export declare function latestEnvironmentContext(messages: readonly AgentMessage[]): EnvironmentContext | undefined;
|
|
25
|
+
export declare function createEnvironmentContextMessage(context: EnvironmentContext, timestamp?: number): CustomMessage<EnvironmentContext>;
|
|
26
|
+
/**
|
|
27
|
+
* A new environment context message when `current` differs from the latest
|
|
28
|
+
* one in `messages` (or none is visible, e.g. a fresh session or a compaction
|
|
29
|
+
* that summarized it away); `undefined` when the model already sees it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function environmentContextMessageIfChanged(messages: readonly AgentMessage[], current: EnvironmentContext): CustomMessage<EnvironmentContext> | undefined;
|
|
32
|
+
//# sourceMappingURL=environment-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment-context.d.ts","sourceRoot":"","sources":["../../src/core/environment-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,eAAO,MAAM,gCAAgC,wBAAwB,CAAC;AAEtE,MAAM,WAAW,kBAAkB;IAClC,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,kBAAkB,CAEjG;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAO5E;AAQD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,kBAAkB,GAAG,SAAS,CAY1G;AAED,wBAAgB,+BAA+B,CAC9C,OAAO,EAAE,kBAAkB,EAC3B,SAAS,GAAE,MAAmB,GAC5B,aAAa,CAAC,kBAAkB,CAAC,CASnC;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CACjD,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,OAAO,EAAE,kBAAkB,GACzB,aAAa,CAAC,kBAAkB,CAAC,GAAG,SAAS,CAI/C"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment context (working directory + current date) delivered to the
|
|
3
|
+
* model as an append-only, hidden user-role custom message instead of as
|
|
4
|
+
* system prompt text (senpi#2093).
|
|
5
|
+
*
|
|
6
|
+
* A date or cwd line inside the system prompt rewrites the provider-visible
|
|
7
|
+
* prefix every day and in every directory, so automatic prefix caches
|
|
8
|
+
* (OpenAI, Anthropic, and the rest) miss the whole system prompt and
|
|
9
|
+
* everything after it. Carrying the values in a message placed before the
|
|
10
|
+
* next user turn keeps the system prompt byte-stable; a new message is
|
|
11
|
+
* appended only when a value changes, and earlier ones are never rewritten.
|
|
12
|
+
*/
|
|
13
|
+
export const ENVIRONMENT_CONTEXT_MESSAGE_TYPE = "environment-context";
|
|
14
|
+
export function resolveEnvironmentContext(cwd, now = new Date()) {
|
|
15
|
+
return { cwd: cwd.replace(/\\/g, "/"), currentDate: now.toISOString().slice(0, 10) };
|
|
16
|
+
}
|
|
17
|
+
export function formatEnvironmentContext(context) {
|
|
18
|
+
return [
|
|
19
|
+
"<environment_context>",
|
|
20
|
+
` <cwd>${context.cwd}</cwd>`,
|
|
21
|
+
` <current_date>${context.currentDate}</current_date>`,
|
|
22
|
+
"</environment_context>",
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
function isEnvironmentContext(value) {
|
|
26
|
+
if (typeof value !== "object" || value === null)
|
|
27
|
+
return false;
|
|
28
|
+
const candidate = value;
|
|
29
|
+
return typeof candidate.cwd === "string" && typeof candidate.currentDate === "string";
|
|
30
|
+
}
|
|
31
|
+
export function latestEnvironmentContext(messages) {
|
|
32
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
33
|
+
const message = messages[index];
|
|
34
|
+
if (message.role === "custom" &&
|
|
35
|
+
message.customType === ENVIRONMENT_CONTEXT_MESSAGE_TYPE &&
|
|
36
|
+
isEnvironmentContext(message.details)) {
|
|
37
|
+
return message.details;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
export function createEnvironmentContextMessage(context, timestamp = Date.now()) {
|
|
43
|
+
return {
|
|
44
|
+
role: "custom",
|
|
45
|
+
customType: ENVIRONMENT_CONTEXT_MESSAGE_TYPE,
|
|
46
|
+
content: formatEnvironmentContext(context),
|
|
47
|
+
display: false,
|
|
48
|
+
details: { cwd: context.cwd, currentDate: context.currentDate },
|
|
49
|
+
timestamp,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A new environment context message when `current` differs from the latest
|
|
54
|
+
* one in `messages` (or none is visible, e.g. a fresh session or a compaction
|
|
55
|
+
* that summarized it away); `undefined` when the model already sees it.
|
|
56
|
+
*/
|
|
57
|
+
export function environmentContextMessageIfChanged(messages, current) {
|
|
58
|
+
const latest = latestEnvironmentContext(messages);
|
|
59
|
+
if (latest?.cwd === current.cwd && latest.currentDate === current.currentDate)
|
|
60
|
+
return undefined;
|
|
61
|
+
return createEnvironmentContextMessage(current);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=environment-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment-context.js","sourceRoot":"","sources":["../../src/core/environment-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,MAAM,CAAC,MAAM,gCAAgC,GAAG,qBAAqB,CAAC;AAStE,MAAM,UAAU,yBAAyB,CAAC,GAAW,EAAE,GAAG,GAAS,IAAI,IAAI,EAAE;IAC5E,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,OAA2B;IACnE,OAAO;QACN,uBAAuB;QACvB,UAAU,OAAO,CAAC,GAAG,QAAQ;QAC7B,mBAAmB,OAAO,CAAC,WAAW,iBAAiB;QACvD,wBAAwB;KACxB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAc;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,SAAS,GAAG,KAAgC,CAAC;IACnD,OAAO,OAAO,SAAS,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,SAAS,CAAC,WAAW,KAAK,QAAQ,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,QAAiC;IACzE,KAAK,IAAI,KAAK,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;QAC3D,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAChC,IACC,OAAO,CAAC,IAAI,KAAK,QAAQ;YACzB,OAAO,CAAC,UAAU,KAAK,gCAAgC;YACvD,oBAAoB,CAAC,OAAO,CAAC,OAAO,CAAC,EACpC,CAAC;YACF,OAAO,OAAO,CAAC,OAAO,CAAC;QACxB,CAAC;IACF,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC9C,OAA2B,EAC3B,SAAS,GAAW,IAAI,CAAC,GAAG,EAAE;IAE9B,OAAO;QACN,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,gCAAgC;QAC5C,OAAO,EAAE,wBAAwB,CAAC,OAAO,CAAC;QAC1C,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE;QAC/D,SAAS;KACT,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kCAAkC,CACjD,QAAiC,EACjC,OAA2B;IAE3B,MAAM,MAAM,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,IAAI,MAAM,CAAC,WAAW,KAAK,OAAO,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IAChG,OAAO,+BAA+B,CAAC,OAAO,CAAC,CAAC;AACjD,CAAC","sourcesContent":["/**\n * Environment context (working directory + current date) delivered to the\n * model as an append-only, hidden user-role custom message instead of as\n * system prompt text (senpi#2093).\n *\n * A date or cwd line inside the system prompt rewrites the provider-visible\n * prefix every day and in every directory, so automatic prefix caches\n * (OpenAI, Anthropic, and the rest) miss the whole system prompt and\n * everything after it. Carrying the values in a message placed before the\n * next user turn keeps the system prompt byte-stable; a new message is\n * appended only when a value changes, and earlier ones are never rewritten.\n */\n\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { CustomMessage } from \"./messages.ts\";\n\nexport const ENVIRONMENT_CONTEXT_MESSAGE_TYPE = \"environment-context\";\n\nexport interface EnvironmentContext {\n\t/** Working directory with `/` separators. */\n\tcwd: string;\n\t/** UTC calendar date, `YYYY-MM-DD`. */\n\tcurrentDate: string;\n}\n\nexport function resolveEnvironmentContext(cwd: string, now: Date = new Date()): EnvironmentContext {\n\treturn { cwd: cwd.replace(/\\\\/g, \"/\"), currentDate: now.toISOString().slice(0, 10) };\n}\n\nexport function formatEnvironmentContext(context: EnvironmentContext): string {\n\treturn [\n\t\t\"<environment_context>\",\n\t\t` <cwd>${context.cwd}</cwd>`,\n\t\t` <current_date>${context.currentDate}</current_date>`,\n\t\t\"</environment_context>\",\n\t].join(\"\\n\");\n}\n\nfunction isEnvironmentContext(value: unknown): value is EnvironmentContext {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tconst candidate = value as Record<string, unknown>;\n\treturn typeof candidate.cwd === \"string\" && typeof candidate.currentDate === \"string\";\n}\n\nexport function latestEnvironmentContext(messages: readonly AgentMessage[]): EnvironmentContext | undefined {\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tconst message = messages[index];\n\t\tif (\n\t\t\tmessage.role === \"custom\" &&\n\t\t\tmessage.customType === ENVIRONMENT_CONTEXT_MESSAGE_TYPE &&\n\t\t\tisEnvironmentContext(message.details)\n\t\t) {\n\t\t\treturn message.details;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nexport function createEnvironmentContextMessage(\n\tcontext: EnvironmentContext,\n\ttimestamp: number = Date.now(),\n): CustomMessage<EnvironmentContext> {\n\treturn {\n\t\trole: \"custom\",\n\t\tcustomType: ENVIRONMENT_CONTEXT_MESSAGE_TYPE,\n\t\tcontent: formatEnvironmentContext(context),\n\t\tdisplay: false,\n\t\tdetails: { cwd: context.cwd, currentDate: context.currentDate },\n\t\ttimestamp,\n\t};\n}\n\n/**\n * A new environment context message when `current` differs from the latest\n * one in `messages` (or none is visible, e.g. a fresh session or a compaction\n * that summarized it away); `undefined` when the model already sees it.\n */\nexport function environmentContextMessageIfChanged(\n\tmessages: readonly AgentMessage[],\n\tcurrent: EnvironmentContext,\n): CustomMessage<EnvironmentContext> | undefined {\n\tconst latest = latestEnvironmentContext(messages);\n\tif (latest?.cwd === current.cwd && latest.currentDate === current.currentDate) return undefined;\n\treturn createEnvironmentContextMessage(current);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-sync.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/anthropic-subscription/session-sync.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAG/E,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,MAAM,wBAAwB,GAAG;IACtC,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACpB,CAAC;AA4BF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAExD;AAkBD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,EAAE,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,IAAI,WAAW,CAGtF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,EAAE,CAc5E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,SAAgB,GAAG,MAAM,CAE7F;AAED,kFAAkF;AAElF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iCAAiC,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAE1G;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iCAAiC,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAM5G;
|
|
1
|
+
{"version":3,"file":"session-sync.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/anthropic-subscription/session-sync.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAG/E,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,MAAM,wBAAwB,GAAG;IACtC,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACpB,CAAC;AA4BF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAExD;AAkBD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,EAAE,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,IAAI,WAAW,CAGtF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,EAAE,CAc5E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,SAAgB,GAAG,MAAM,CAE7F;AAED,kFAAkF;AAElF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iCAAiC,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAE1G;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iCAAiC,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAM5G;AAED,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,6BAA6B,EACvC,WAAW,EAAE,MAAM,GACjB,wBAAwB,CA2B1B;AAaD,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,mBAAmB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/C,iBAAiB,EAAE,CAarB"}
|
|
@@ -87,23 +87,9 @@ export function recordSyncedStream(entry, hashes) {
|
|
|
87
87
|
entry.syncedPrefixHash = sentHashPrefixDigest(copy);
|
|
88
88
|
entry.branchInfo = null;
|
|
89
89
|
}
|
|
90
|
-
const GENERATED_DATE_LINE = /\nCurrent date: \d{4}-\d{2}-\d{2}(?=\nCurrent working directory: [^\n]*)/;
|
|
91
|
-
/**
|
|
92
|
-
* The generated date line advances at UTC midnight while the conversation is
|
|
93
|
-
* unchanged; hashing it verbatim retires a live session at midnight for no
|
|
94
|
-
* semantic reason. Only that exact date-plus-cwd pair is neutralized - cwd and
|
|
95
|
-
* every other prompt region stay fail-closed. Extension appends legitimately
|
|
96
|
-
* follow the cwd line (oh-my-openagent#7884), so the pair is matched wherever
|
|
97
|
-
* it appears, not only at the end of the prompt.
|
|
98
|
-
*/
|
|
99
|
-
function fingerprintSystemPrompt(systemPrompt) {
|
|
100
|
-
if (typeof systemPrompt !== "string")
|
|
101
|
-
return systemPrompt ?? null;
|
|
102
|
-
return systemPrompt.replace(GENERATED_DATE_LINE, "\nCurrent date: <session-date>");
|
|
103
|
-
}
|
|
104
90
|
export function configFingerprint(options, context, authLane, accountName) {
|
|
105
91
|
return {
|
|
106
|
-
systemPromptHash: digest(
|
|
92
|
+
systemPromptHash: digest(options.systemPrompt ?? null),
|
|
107
93
|
toolsetHash: digest({
|
|
108
94
|
tools: options.tools ?? [],
|
|
109
95
|
reasoning: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-sync.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/anthropic-subscription/session-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { Context, Message } from \"@earendil-works/pi-ai\";\nimport { appendSdkContentBlocks } from \"./content-blocks.ts\";\nimport type { AnthropicSubscriptionAuthLane } from \"./options.ts\";\nimport type { ContentBlockParam, Options } from \"./sdk-boundary.ts\";\nimport type { AnthropicSubscriptionSessionEntry } from \"./session-registry.ts\";\nimport { HOST_TOOL_POLICY_FINGERPRINT, mapPiToolNameToSdk } from \"./tools.ts\";\n\nexport type SentMessage = Extract<Message, { role: \"user\" | \"toolResult\" }>;\n\nexport type SessionConfigFingerprint = {\n\tsystemPromptHash: string;\n\ttoolsetHash: string;\n};\n\nconst sentHashesByEntry = new WeakMap<AnthropicSubscriptionSessionEntry, string[]>();\n\nfunction stableValue(value: unknown, seen = new WeakSet<object>()): unknown {\n\tif (typeof value === \"function\") return `[function:${value.name}:${value.toString()}]`;\n\tif (typeof value === \"bigint\") return value.toString();\n\tif (value === null || typeof value !== \"object\") return value;\n\tif (seen.has(value)) return \"[circular]\";\n\tseen.add(value);\n\tconst normalized = Array.isArray(value)\n\t\t? value.map((item) => stableValue(item, seen))\n\t\t: Object.fromEntries(\n\t\t\t\tObject.entries(value as Record<string, unknown>)\n\t\t\t\t\t.filter(([, item]) => item !== undefined)\n\t\t\t\t\t.sort(([left], [right]) => left.localeCompare(right))\n\t\t\t\t\t.map(([key, item]) => [key, stableValue(item, seen)]),\n\t\t\t);\n\tseen.delete(value);\n\treturn normalized;\n}\n\nfunction digest(value: unknown): string {\n\treturn createHash(\"sha256\")\n\t\t.update(JSON.stringify(stableValue(value)))\n\t\t.digest(\"hex\");\n}\n\nexport function sessionSyncDigest(value: unknown): string {\n\treturn digest(value);\n}\n\n/**\n * A user message with no content blocks carries nothing to transmit and is\n * transient: it is present for a single provider call and gone by the next.\n * Hashing one shifts every later index, so the following turn reports\n * `sent_stream_diverged` and re-sends the whole history even though the\n * conversation never changed. Tool results are never filtered - an empty tool\n * result is a real observation, and its id and name stay hash-significant.\n */\nfunction isContentlessUserMessage(message: SentMessage): boolean {\n\tif (message.role !== \"user\") return false;\n\t// Only a literal zero-block array is content-less. Whitespace-only text and\n\t// explicit empty-text blocks still emit transport blocks, so they must stay\n\t// hash-significant to keep divergence detection fail-closed.\n\treturn Array.isArray(message.content) && message.content.length === 0;\n}\n\nexport function sentMessages(context: Context): SentMessage[] {\n\treturn context.messages.filter(isTransmittedMessage);\n}\n\n/**\n * The one selection rule for \"message the provider was sent\". Branch-derived and\n * context-derived hashes MUST share it: a content-less user message that only one\n * side skips shifts every later index and reports a false divergence.\n */\nexport function isTransmittedMessage(message: { role: string }): message is SentMessage {\n\tif (message.role !== \"user\" && message.role !== \"toolResult\") return false;\n\treturn !isContentlessUserMessage(message as SentMessage);\n}\n\n/**\n * Applies the transmitted-message rule itself, so no caller can produce a hash\n * list that disagrees with another caller's by forgetting the filter.\n */\nexport function sentMessageHashes(messages: readonly SentMessage[]): string[] {\n\tconst hashes = messages.filter(isTransmittedMessage).map((message) =>\n\t\tdigest(\n\t\t\tmessage.role === \"user\"\n\t\t\t\t? { role: message.role, content: message.content }\n\t\t\t\t: {\n\t\t\t\t\t\trole: message.role,\n\t\t\t\t\t\ttoolCallId: message.toolCallId,\n\t\t\t\t\t\ttoolName: message.toolName,\n\t\t\t\t\t\tcontent: message.content,\n\t\t\t\t\t},\n\t\t),\n\t);\n\treturn hashes;\n}\n\nexport function sentHashPrefixDigest(hashes: readonly string[], count = hashes.length): string {\n\treturn digest(hashes.slice(0, count));\n}\n\n/** Pure divergence guard: every non-proven continuation resolves to cold-seed. */\n\nexport function sentHashesForEntry(entry: AnthropicSubscriptionSessionEntry): readonly string[] | undefined {\n\treturn sentHashesByEntry.get(entry);\n}\n\nexport function recordSyncedStream(entry: AnthropicSubscriptionSessionEntry, hashes: readonly string[]): void {\n\tconst copy = [...hashes];\n\tsentHashesByEntry.set(entry, copy);\n\tentry.sentCount = copy.length;\n\tentry.syncedPrefixHash = sentHashPrefixDigest(copy);\n\tentry.branchInfo = null;\n}\n\nconst GENERATED_DATE_LINE = /\\nCurrent date: \\d{4}-\\d{2}-\\d{2}(?=\\nCurrent working directory: [^\\n]*)/;\n\n/**\n * The generated date line advances at UTC midnight while the conversation is\n * unchanged; hashing it verbatim retires a live session at midnight for no\n * semantic reason. Only that exact date-plus-cwd pair is neutralized - cwd and\n * every other prompt region stay fail-closed. Extension appends legitimately\n * follow the cwd line (oh-my-openagent#7884), so the pair is matched wherever\n * it appears, not only at the end of the prompt.\n */\nfunction fingerprintSystemPrompt(systemPrompt: Options[\"systemPrompt\"]): unknown {\n\tif (typeof systemPrompt !== \"string\") return systemPrompt ?? null;\n\treturn systemPrompt.replace(GENERATED_DATE_LINE, \"\\nCurrent date: <session-date>\");\n}\n\nexport function configFingerprint(\n\toptions: Options,\n\tcontext: Context,\n\tauthLane: AnthropicSubscriptionAuthLane,\n\taccountName: string,\n): SessionConfigFingerprint {\n\treturn {\n\t\tsystemPromptHash: digest(fingerprintSystemPrompt(options.systemPrompt)),\n\t\ttoolsetHash: digest({\n\t\t\ttools: options.tools ?? [],\n\t\t\treasoning: {\n\t\t\t\tthinking: options.thinking,\n\t\t\t\teffort: options.effort,\n\t\t\t\tmaxThinkingTokens: options.maxThinkingTokens,\n\t\t\t},\n\t\t\tcontextTools: (context.tools ?? []).map((tool) => ({\n\t\t\t\tname: tool.name,\n\t\t\t\tdescription: tool.description,\n\t\t\t\tparameters: tool.parameters,\n\t\t\t})),\n\t\t\tcwd: options.cwd,\n\t\t\tauthLane,\n\t\t\taccountName,\n\t\t\tpermissionMode: options.permissionMode,\n\t\t\t// Bump HOST_TOOL_POLICY_FINGERPRINT in tools.ts when denial copy or hooks change.\n\t\t\thostToolPolicy: HOST_TOOL_POLICY_FINGERPRINT,\n\t\t\tsettingSources: options.settingSources,\n\t\t\textraArgs: options.extraArgs,\n\t\t\tpathToClaudeCodeExecutable: options.pathToClaudeCodeExecutable,\n\t\t\tincludePartialMessages: options.includePartialMessages,\n\t\t}),\n\t};\n}\n\nfunction appendContent(blocks: ContentBlockParam[], content: string | readonly unknown[]): void {\n\t// The delta path has always transmitted a whole-content empty string as an\n\t// empty text block; keep that wire shape so resident sessions see the same\n\t// payload as before the shared mapper (prompt-bridge intentionally skips it).\n\tif (content === \"\") {\n\t\tblocks.push({ type: \"text\", text: \"\" });\n\t\treturn;\n\t}\n\tappendSdkContentBlocks(blocks, content);\n}\n\nexport function buildDeltaPromptBlocks(\n\tmessages: readonly SentMessage[],\n\tcustomToolNameToSdk?: ReadonlyMap<string, string>,\n): ContentBlockParam[] {\n\tconst blocks: ContentBlockParam[] = [];\n\tfor (const [index, message] of messages.entries()) {\n\t\tif (index > 0) blocks.push({ type: \"text\", text: \"\\n\\n\" });\n\t\tif (message.role === \"toolResult\") {\n\t\t\tblocks.push({\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: `Tool result (${mapPiToolNameToSdk(message.toolName, customToolNameToSdk)}, id=${message.toolCallId}):\\n`,\n\t\t\t});\n\t\t}\n\t\tappendContent(blocks, message.content);\n\t}\n\treturn blocks;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-sync.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/anthropic-subscription/session-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { Context, Message } from \"@earendil-works/pi-ai\";\nimport { appendSdkContentBlocks } from \"./content-blocks.ts\";\nimport type { AnthropicSubscriptionAuthLane } from \"./options.ts\";\nimport type { ContentBlockParam, Options } from \"./sdk-boundary.ts\";\nimport type { AnthropicSubscriptionSessionEntry } from \"./session-registry.ts\";\nimport { HOST_TOOL_POLICY_FINGERPRINT, mapPiToolNameToSdk } from \"./tools.ts\";\n\nexport type SentMessage = Extract<Message, { role: \"user\" | \"toolResult\" }>;\n\nexport type SessionConfigFingerprint = {\n\tsystemPromptHash: string;\n\ttoolsetHash: string;\n};\n\nconst sentHashesByEntry = new WeakMap<AnthropicSubscriptionSessionEntry, string[]>();\n\nfunction stableValue(value: unknown, seen = new WeakSet<object>()): unknown {\n\tif (typeof value === \"function\") return `[function:${value.name}:${value.toString()}]`;\n\tif (typeof value === \"bigint\") return value.toString();\n\tif (value === null || typeof value !== \"object\") return value;\n\tif (seen.has(value)) return \"[circular]\";\n\tseen.add(value);\n\tconst normalized = Array.isArray(value)\n\t\t? value.map((item) => stableValue(item, seen))\n\t\t: Object.fromEntries(\n\t\t\t\tObject.entries(value as Record<string, unknown>)\n\t\t\t\t\t.filter(([, item]) => item !== undefined)\n\t\t\t\t\t.sort(([left], [right]) => left.localeCompare(right))\n\t\t\t\t\t.map(([key, item]) => [key, stableValue(item, seen)]),\n\t\t\t);\n\tseen.delete(value);\n\treturn normalized;\n}\n\nfunction digest(value: unknown): string {\n\treturn createHash(\"sha256\")\n\t\t.update(JSON.stringify(stableValue(value)))\n\t\t.digest(\"hex\");\n}\n\nexport function sessionSyncDigest(value: unknown): string {\n\treturn digest(value);\n}\n\n/**\n * A user message with no content blocks carries nothing to transmit and is\n * transient: it is present for a single provider call and gone by the next.\n * Hashing one shifts every later index, so the following turn reports\n * `sent_stream_diverged` and re-sends the whole history even though the\n * conversation never changed. Tool results are never filtered - an empty tool\n * result is a real observation, and its id and name stay hash-significant.\n */\nfunction isContentlessUserMessage(message: SentMessage): boolean {\n\tif (message.role !== \"user\") return false;\n\t// Only a literal zero-block array is content-less. Whitespace-only text and\n\t// explicit empty-text blocks still emit transport blocks, so they must stay\n\t// hash-significant to keep divergence detection fail-closed.\n\treturn Array.isArray(message.content) && message.content.length === 0;\n}\n\nexport function sentMessages(context: Context): SentMessage[] {\n\treturn context.messages.filter(isTransmittedMessage);\n}\n\n/**\n * The one selection rule for \"message the provider was sent\". Branch-derived and\n * context-derived hashes MUST share it: a content-less user message that only one\n * side skips shifts every later index and reports a false divergence.\n */\nexport function isTransmittedMessage(message: { role: string }): message is SentMessage {\n\tif (message.role !== \"user\" && message.role !== \"toolResult\") return false;\n\treturn !isContentlessUserMessage(message as SentMessage);\n}\n\n/**\n * Applies the transmitted-message rule itself, so no caller can produce a hash\n * list that disagrees with another caller's by forgetting the filter.\n */\nexport function sentMessageHashes(messages: readonly SentMessage[]): string[] {\n\tconst hashes = messages.filter(isTransmittedMessage).map((message) =>\n\t\tdigest(\n\t\t\tmessage.role === \"user\"\n\t\t\t\t? { role: message.role, content: message.content }\n\t\t\t\t: {\n\t\t\t\t\t\trole: message.role,\n\t\t\t\t\t\ttoolCallId: message.toolCallId,\n\t\t\t\t\t\ttoolName: message.toolName,\n\t\t\t\t\t\tcontent: message.content,\n\t\t\t\t\t},\n\t\t),\n\t);\n\treturn hashes;\n}\n\nexport function sentHashPrefixDigest(hashes: readonly string[], count = hashes.length): string {\n\treturn digest(hashes.slice(0, count));\n}\n\n/** Pure divergence guard: every non-proven continuation resolves to cold-seed. */\n\nexport function sentHashesForEntry(entry: AnthropicSubscriptionSessionEntry): readonly string[] | undefined {\n\treturn sentHashesByEntry.get(entry);\n}\n\nexport function recordSyncedStream(entry: AnthropicSubscriptionSessionEntry, hashes: readonly string[]): void {\n\tconst copy = [...hashes];\n\tsentHashesByEntry.set(entry, copy);\n\tentry.sentCount = copy.length;\n\tentry.syncedPrefixHash = sentHashPrefixDigest(copy);\n\tentry.branchInfo = null;\n}\n\nexport function configFingerprint(\n\toptions: Options,\n\tcontext: Context,\n\tauthLane: AnthropicSubscriptionAuthLane,\n\taccountName: string,\n): SessionConfigFingerprint {\n\treturn {\n\t\tsystemPromptHash: digest(options.systemPrompt ?? null),\n\t\ttoolsetHash: digest({\n\t\t\ttools: options.tools ?? [],\n\t\t\treasoning: {\n\t\t\t\tthinking: options.thinking,\n\t\t\t\teffort: options.effort,\n\t\t\t\tmaxThinkingTokens: options.maxThinkingTokens,\n\t\t\t},\n\t\t\tcontextTools: (context.tools ?? []).map((tool) => ({\n\t\t\t\tname: tool.name,\n\t\t\t\tdescription: tool.description,\n\t\t\t\tparameters: tool.parameters,\n\t\t\t})),\n\t\t\tcwd: options.cwd,\n\t\t\tauthLane,\n\t\t\taccountName,\n\t\t\tpermissionMode: options.permissionMode,\n\t\t\t// Bump HOST_TOOL_POLICY_FINGERPRINT in tools.ts when denial copy or hooks change.\n\t\t\thostToolPolicy: HOST_TOOL_POLICY_FINGERPRINT,\n\t\t\tsettingSources: options.settingSources,\n\t\t\textraArgs: options.extraArgs,\n\t\t\tpathToClaudeCodeExecutable: options.pathToClaudeCodeExecutable,\n\t\t\tincludePartialMessages: options.includePartialMessages,\n\t\t}),\n\t};\n}\n\nfunction appendContent(blocks: ContentBlockParam[], content: string | readonly unknown[]): void {\n\t// The delta path has always transmitted a whole-content empty string as an\n\t// empty text block; keep that wire shape so resident sessions see the same\n\t// payload as before the shared mapper (prompt-bridge intentionally skips it).\n\tif (content === \"\") {\n\t\tblocks.push({ type: \"text\", text: \"\" });\n\t\treturn;\n\t}\n\tappendSdkContentBlocks(blocks, content);\n}\n\nexport function buildDeltaPromptBlocks(\n\tmessages: readonly SentMessage[],\n\tcustomToolNameToSdk?: ReadonlyMap<string, string>,\n): ContentBlockParam[] {\n\tconst blocks: ContentBlockParam[] = [];\n\tfor (const [index, message] of messages.entries()) {\n\t\tif (index > 0) blocks.push({ type: \"text\", text: \"\\n\\n\" });\n\t\tif (message.role === \"toolResult\") {\n\t\t\tblocks.push({\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: `Tool result (${mapPiToolNameToSdk(message.toolName, customToolNameToSdk)}, id=${message.toolCallId}):\\n`,\n\t\t\t});\n\t\t}\n\t\tappendContent(blocks, message.content);\n\t}\n\treturn blocks;\n}\n"]}
|
|
@@ -67,6 +67,9 @@ function warmLine(data) {
|
|
|
67
67
|
if (cache === undefined || cache.cachedTokens <= 0)
|
|
68
68
|
return undefined;
|
|
69
69
|
const tokens = `~${formatWarmTokenCount(cache.cachedTokens)} tokens`;
|
|
70
|
+
if (cache.cacheLifetime === "best-effort") {
|
|
71
|
+
return `${tokens} were cached after the prior turn · provider caching is best-effort, with no expiry to beat`;
|
|
72
|
+
}
|
|
70
73
|
const ttlMayHaveElapsed = cache.ttlSeconds !== undefined && (data.waitedMs ?? data.delayMs) >= cache.ttlSeconds * 1000;
|
|
71
74
|
if (ttlMayHaveElapsed) {
|
|
72
75
|
return `${tokens} were cached after the prior turn · prompt-cache TTL may have elapsed before this wake`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-warm-renderer.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/cache-warm-renderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EACN,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,GAEpB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,CAAC,MAAM,0BAA0B,GAA4C,mBAAmB,CAAC,CAAC,KAAK,EAAE,EAAE;IAChH,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO;QACN,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC;QACtB,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC;QAClB,KAAK,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAClE,YAAY,EAAE,YAAY,CAAC,IAAI,CAAC;KAChC,CAAC;AACH,CAAC,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACtC,QAA+C,EAC/C,IAA2C;IAE3C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;IACjC,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAC5F,CAAC;AAED,SAAS,SAAS,CAAC,IAA8B;IAChD,MAAM,WAAW,GAChB,IAAI,CAAC,kBAAkB,KAAK,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,kBAAkB,uBAAuB,CAAC;IAC7G,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjD,MAAM,aAAa,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,SAAS,EAAE,CAAC;IACjF,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,KAAK,WAAW;YACf,OAAO,oBAAoB,aAAa,MAAM,WAAW,EAAE,CAAC;QAC7D,KAAK,SAAS;YACb,OAAO,oBAAoB,aAAa,MAAM,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,WAAW,EAAE,CAAC;IACnI,CAAC;AACF,CAAC;AAED,SAAS,cAAc,CAAC,KAAyB;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9F,CAAC;AAED,SAAS,OAAO,CAAC,IAA8B;IAC9C,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,KAAK,WAAW,EAAE,CAAC;YAClB,oEAAoE;YACpE,0DAA0D;YAC1D,MAAM,QAAQ,GAAG,kBAAkB,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACpF,IAAI,IAAI,CAAC,KAAK,EAAE,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC1C,OAAO,GAAG,QAAQ,wDAAwD,CAAC;YAC5E,CAAC;YACD,OAAO,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI;gBACjD,CAAC,CAAC,GAAG,QAAQ,qEAAqE,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,oBAAoB;gBAC3I,CAAC,CAAC,GAAG,QAAQ,8DAA8D,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,qCAAqC,CAAC;QACxJ,CAAC;QACD,KAAK,SAAS;YACb,OAAO,6CAA6C,CAAC;IACvD,CAAC;AACF,CAAC;AAED,SAAS,kBAAkB,CAAC,OAA2B,EAAE,SAAiB;IACzE,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,UAAU,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;IACzG,OAAO,SAAS,mBAAmB,CAAC,OAAO,CAAC,KAAK,kBAAkB,CAAC,SAAS,CAAC,GAAG,CAAC;AACnF,CAAC;AAED,SAAS,YAAY,CAAC,IAA8B;IACnD,MAAM,OAAO,GAAG,QAAQ,IAAI,CAAC,MAAM,oBAAoB,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IAC1F,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW;QAAE,OAAO,GAAG,OAAO,MAAM,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IACxG,OAAO,GAAG,OAAO,MAAM,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;AAC1F,CAAC;AAED,SAAS,QAAQ,CAAC,IAA8B;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACrE,MAAM,MAAM,GAAG,IAAI,oBAAoB,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC;IACrE,MAAM,iBAAiB,GACtB,KAAK,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;IAC9F,IAAI,iBAAiB,EAAE,CAAC;QACvB,OAAO,GAAG,MAAM,wFAAwF,CAAC;IAC1G,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,GAAG,MAAM,kCAAkC,CAAC;IAC9G,MAAM,KAAK,GACV,KAAK,CAAC,iBAAiB,KAAK,SAAS,IAAI,KAAK,CAAC,iBAAiB,GAAG,CAAC;QACnE,CAAC,CAAC,WAAW,cAAc,CAAC,KAAK,CAAC,iBAAiB,CAAC,0BAA0B;QAC9E,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,GAAG,IAAI,GAAG,KAAK,EAAE,CAAC;AAC1B,CAAC","sourcesContent":["import type { CustomEntry } from \"../../../session-manager.ts\";\nimport { noticeEntryRenderer } from \"../../notice/index.ts\";\nimport type { EntryRenderer } from \"../../types.ts\";\nimport {\n\tformatCacheTtl,\n\tformatSavedUsd,\n\tformatWakeDuration,\n\tformatWakeTimestamp,\n\tformatWarmTokenCount,\n\ttype GoalCacheWarmupEntryData,\n} from \"./cache-warm.ts\";\n\nexport const renderGoalCacheWarmupEntry: EntryRenderer<GoalCacheWarmupEntryData> = noticeEntryRenderer((entry) => {\n\tconst data = entry.data;\n\tif (data === undefined) return undefined;\n\tconst warm = warmLine(data);\n\treturn {\n\t\ttitle: titleLine(data),\n\t\twhy: whyLine(data),\n\t\textra: warm === undefined ? [] : [{ text: warm, tone: \"success\" }],\n\t\texpandedLine: expandedLine(data),\n\t};\n});\n\n/**\n * One card per wait cycle: a cache-warm entry for the same Goal that directly follows the\n * previous card (a reload re-arm, or the wake that ends the wait) replaces it in place.\n */\nexport function isSameGoalCacheWarmCard(\n\tprevious: CustomEntry<GoalCacheWarmupEntryData>,\n\tnext: CustomEntry<GoalCacheWarmupEntryData>,\n): boolean {\n\tconst goalId = next.data?.goalId;\n\treturn typeof goalId === \"string\" && goalId.length > 0 && previous.data?.goalId === goalId;\n}\n\nfunction titleLine(data: GoalCacheWarmupEntryData): string {\n\tconst wakeSources =\n\t\tdata.activeMonitorCount === 1 ? \"1 wake source on duty\" : `${data.activeMonitorCount} wake sources on duty`;\n\tconst iteration = validIteration(data.iteration);\n\tconst iterationText = iteration === undefined ? \"\" : ` · iteration ${iteration}`;\n\tswitch (data.phase) {\n\t\tcase \"scheduled\":\n\t\t\treturn `⚡ Cache-warm wait${iterationText} · ${wakeSources}`;\n\t\tcase \"resumed\":\n\t\t\treturn `⚡ Cache-warm wake${iterationText} · ${formatExpectedWake(data.dueAtMs, data.waitedMs ?? data.delayMs)} · ${wakeSources}`;\n\t}\n}\n\nfunction validIteration(value: number | undefined): number | undefined {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value > 0 ? value : undefined;\n}\n\nfunction whyLine(data: GoalCacheWarmupEntryData): string {\n\tswitch (data.phase) {\n\t\tcase \"scheduled\": {\n\t\t\t// The wait is not a cache-warm timer: it lets the live wake sources\n\t\t\t// deliver, and the timed wake is only the stall backstop.\n\t\t\tconst backstop = `Stall backstop ${formatExpectedWake(data.dueAtMs, data.delayMs)}`;\n\t\t\tif (data.cache?.ttlSeconds === undefined) {\n\t\t\t\treturn `${backstop} - the goal resumes as soon as a wake source delivers.`;\n\t\t\t}\n\t\t\treturn data.delayMs < data.cache.ttlSeconds * 1000\n\t\t\t\t? `${backstop} - the goal resumes as soon as a wake source delivers, inside the ${formatCacheTtl(data.cache.ttlSeconds)} prompt-cache TTL.`\n\t\t\t\t: `${backstop} - the goal resumes as soon as a wake source delivers; the ${formatCacheTtl(data.cache.ttlSeconds)} prompt-cache TTL may elapse first.`;\n\t\t}\n\t\tcase \"resumed\":\n\t\t\treturn \"Woke on schedule to keep pursuing the goal.\";\n\t}\n}\n\nfunction formatExpectedWake(dueAtMs: number | undefined, elapsedMs: number): string {\n\tif (dueAtMs === undefined || !Number.isFinite(dueAtMs)) return `waited ${formatWakeDuration(elapsedMs)}`;\n\treturn `ready ${formatWakeTimestamp(dueAtMs)} (${formatWakeDuration(elapsedMs)})`;\n}\n\nfunction expandedLine(data: GoalCacheWarmupEntryData): string {\n\tconst planned = `goal ${data.goalId} · planned delay ${formatWakeDuration(data.delayMs)}`;\n\tif (data.phase === \"scheduled\") return `${planned} · ${formatExpectedWake(data.dueAtMs, data.delayMs)}`;\n\treturn `${planned} · ${formatExpectedWake(data.dueAtMs, data.waitedMs ?? data.delayMs)}`;\n}\n\nfunction warmLine(data: GoalCacheWarmupEntryData): string | undefined {\n\tconst cache = data.cache;\n\tif (cache === undefined || cache.cachedTokens <= 0) return undefined;\n\tconst tokens = `~${formatWarmTokenCount(cache.cachedTokens)} tokens`;\n\tconst ttlMayHaveElapsed =\n\t\tcache.ttlSeconds !== undefined && (data.waitedMs ?? data.delayMs) >= cache.ttlSeconds * 1000;\n\tif (ttlMayHaveElapsed) {\n\t\treturn `${tokens} were cached after the prior turn · prompt-cache TTL may have elapsed before this wake`;\n\t}\n\tconst body = data.phase === \"scheduled\" ? `${tokens} kept warm` : `${tokens} stayed warm in the prompt cache`;\n\tconst saved =\n\t\tcache.estimatedSavedUsd !== undefined && cache.estimatedSavedUsd > 0\n\t\t\t? ` · est. ${formatSavedUsd(cache.estimatedSavedUsd)} saved vs a cold re-read`\n\t\t\t: \"\";\n\treturn `${body}${saved}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cache-warm-renderer.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/cache-warm-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CustomEntry } from \"../../../session-manager.ts\";\nimport { noticeEntryRenderer } from \"../../notice/index.ts\";\nimport type { EntryRenderer } from \"../../types.ts\";\nimport {\n\tformatCacheTtl,\n\tformatSavedUsd,\n\tformatWakeDuration,\n\tformatWakeTimestamp,\n\tformatWarmTokenCount,\n\ttype GoalCacheWarmupEntryData,\n} from \"./cache-warm.ts\";\n\nexport const renderGoalCacheWarmupEntry: EntryRenderer<GoalCacheWarmupEntryData> = noticeEntryRenderer((entry) => {\n\tconst data = entry.data;\n\tif (data === undefined) return undefined;\n\tconst warm = warmLine(data);\n\treturn {\n\t\ttitle: titleLine(data),\n\t\twhy: whyLine(data),\n\t\textra: warm === undefined ? [] : [{ text: warm, tone: \"success\" }],\n\t\texpandedLine: expandedLine(data),\n\t};\n});\n\n/**\n * One card per wait cycle: a cache-warm entry for the same Goal that directly follows the\n * previous card (a reload re-arm, or the wake that ends the wait) replaces it in place.\n */\nexport function isSameGoalCacheWarmCard(\n\tprevious: CustomEntry<GoalCacheWarmupEntryData>,\n\tnext: CustomEntry<GoalCacheWarmupEntryData>,\n): boolean {\n\tconst goalId = next.data?.goalId;\n\treturn typeof goalId === \"string\" && goalId.length > 0 && previous.data?.goalId === goalId;\n}\n\nfunction titleLine(data: GoalCacheWarmupEntryData): string {\n\tconst wakeSources =\n\t\tdata.activeMonitorCount === 1 ? \"1 wake source on duty\" : `${data.activeMonitorCount} wake sources on duty`;\n\tconst iteration = validIteration(data.iteration);\n\tconst iterationText = iteration === undefined ? \"\" : ` · iteration ${iteration}`;\n\tswitch (data.phase) {\n\t\tcase \"scheduled\":\n\t\t\treturn `⚡ Cache-warm wait${iterationText} · ${wakeSources}`;\n\t\tcase \"resumed\":\n\t\t\treturn `⚡ Cache-warm wake${iterationText} · ${formatExpectedWake(data.dueAtMs, data.waitedMs ?? data.delayMs)} · ${wakeSources}`;\n\t}\n}\n\nfunction validIteration(value: number | undefined): number | undefined {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value > 0 ? value : undefined;\n}\n\nfunction whyLine(data: GoalCacheWarmupEntryData): string {\n\tswitch (data.phase) {\n\t\tcase \"scheduled\": {\n\t\t\t// The wait is not a cache-warm timer: it lets the live wake sources\n\t\t\t// deliver, and the timed wake is only the stall backstop.\n\t\t\tconst backstop = `Stall backstop ${formatExpectedWake(data.dueAtMs, data.delayMs)}`;\n\t\t\tif (data.cache?.ttlSeconds === undefined) {\n\t\t\t\treturn `${backstop} - the goal resumes as soon as a wake source delivers.`;\n\t\t\t}\n\t\t\treturn data.delayMs < data.cache.ttlSeconds * 1000\n\t\t\t\t? `${backstop} - the goal resumes as soon as a wake source delivers, inside the ${formatCacheTtl(data.cache.ttlSeconds)} prompt-cache TTL.`\n\t\t\t\t: `${backstop} - the goal resumes as soon as a wake source delivers; the ${formatCacheTtl(data.cache.ttlSeconds)} prompt-cache TTL may elapse first.`;\n\t\t}\n\t\tcase \"resumed\":\n\t\t\treturn \"Woke on schedule to keep pursuing the goal.\";\n\t}\n}\n\nfunction formatExpectedWake(dueAtMs: number | undefined, elapsedMs: number): string {\n\tif (dueAtMs === undefined || !Number.isFinite(dueAtMs)) return `waited ${formatWakeDuration(elapsedMs)}`;\n\treturn `ready ${formatWakeTimestamp(dueAtMs)} (${formatWakeDuration(elapsedMs)})`;\n}\n\nfunction expandedLine(data: GoalCacheWarmupEntryData): string {\n\tconst planned = `goal ${data.goalId} · planned delay ${formatWakeDuration(data.delayMs)}`;\n\tif (data.phase === \"scheduled\") return `${planned} · ${formatExpectedWake(data.dueAtMs, data.delayMs)}`;\n\treturn `${planned} · ${formatExpectedWake(data.dueAtMs, data.waitedMs ?? data.delayMs)}`;\n}\n\nfunction warmLine(data: GoalCacheWarmupEntryData): string | undefined {\n\tconst cache = data.cache;\n\tif (cache === undefined || cache.cachedTokens <= 0) return undefined;\n\tconst tokens = `~${formatWarmTokenCount(cache.cachedTokens)} tokens`;\n\tif (cache.cacheLifetime === \"best-effort\") {\n\t\treturn `${tokens} were cached after the prior turn · provider caching is best-effort, with no expiry to beat`;\n\t}\n\tconst ttlMayHaveElapsed =\n\t\tcache.ttlSeconds !== undefined && (data.waitedMs ?? data.delayMs) >= cache.ttlSeconds * 1000;\n\tif (ttlMayHaveElapsed) {\n\t\treturn `${tokens} were cached after the prior turn · prompt-cache TTL may have elapsed before this wake`;\n\t}\n\tconst body = data.phase === \"scheduled\" ? `${tokens} kept warm` : `${tokens} stayed warm in the prompt cache`;\n\tconst saved =\n\t\tcache.estimatedSavedUsd !== undefined && cache.estimatedSavedUsd > 0\n\t\t\t? ` · est. ${formatSavedUsd(cache.estimatedSavedUsd)} saved vs a cold re-read`\n\t\t\t: \"\";\n\treturn `${body}${saved}`;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
1
|
+
import type { Api, Model, PromptCacheLifetime } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { TokenUsageSnapshot } from "./types.ts";
|
|
3
3
|
/** Custom session-entry type carrying the cache-warm continuation story. */
|
|
4
4
|
export declare const GOAL_CACHE_WARMUP_ENTRY_TYPE = "goal-cache-warmup";
|
|
@@ -15,6 +15,12 @@ export declare const GOAL_MONITOR_CONTINUATION_FALLBACK_DELAY_MS = 240000;
|
|
|
15
15
|
* periodic re-check lands inside the cache.
|
|
16
16
|
*/
|
|
17
17
|
export declare const GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS = 270000;
|
|
18
|
+
/**
|
|
19
|
+
* Backstop used instead of the 270s default when the active model's prompt cache is best-effort
|
|
20
|
+
* (for example direct DeepSeek, code-yeongyu/senpi#831): there is no TTL for the re-check to land
|
|
21
|
+
* inside, so the goal only needs the long liveness re-check, not a cache-preservation wake.
|
|
22
|
+
*/
|
|
23
|
+
export declare const GOAL_MONITOR_BEST_EFFORT_BACKSTOP_SECONDS = 3570;
|
|
18
24
|
/**
|
|
19
25
|
* Delay of the goal-monitor backstop, in milliseconds.
|
|
20
26
|
*
|
|
@@ -28,10 +34,21 @@ export declare const GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS = 270000;
|
|
|
28
34
|
* [1s, 1h].
|
|
29
35
|
*/
|
|
30
36
|
export declare function resolveGoalMonitorContinuationDelayMs(goalBackstopMaxSeconds: number | undefined): number;
|
|
37
|
+
/**
|
|
38
|
+
* Backstop ceiling in seconds for the active model's cache lifetime. The built-in 270s default exists
|
|
39
|
+
* to land the re-check inside a 5-minute TTL; a best-effort cache has no TTL, so an unconfigured
|
|
40
|
+
* backstop becomes the long liveness re-check. The settings getter reports an unset value as the
|
|
41
|
+
* 270s default, so a value equal to it is treated as the default.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveGoalBackstopMaxSecondsForCache(goalBackstopMaxSeconds: number | undefined, lifetime: PromptCacheLifetime | undefined): number | undefined;
|
|
44
|
+
/** Prompt-cache lifetime of the active model, from the provider's documented cache contract. */
|
|
45
|
+
export declare function resolveGoalPromptCacheLifetime(model: Model<Api> | undefined, env: NodeJS.ProcessEnv): PromptCacheLifetime | undefined;
|
|
31
46
|
/** Cache context captured when a monitor-wait continuation is scheduled. */
|
|
32
47
|
export interface GoalCacheWarmMetrics {
|
|
33
|
-
/** Prompt-cache TTL of the active model in seconds, when
|
|
48
|
+
/** Prompt-cache TTL of the active model in seconds, when the provider documents one. */
|
|
34
49
|
readonly ttlSeconds?: number;
|
|
50
|
+
/** Set when the provider caches automatically with no expiry contract; no TTL or savings are claimed. */
|
|
51
|
+
readonly cacheLifetime?: "best-effort";
|
|
35
52
|
/** Tokens sitting warm in the provider prompt cache after the last turn. */
|
|
36
53
|
readonly cachedTokens: number;
|
|
37
54
|
/** Estimated USD saved by re-reading those tokens from cache instead of paying a cold input read. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-warm.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/cache-warm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAe,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"cache-warm.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/cache-warm.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolvePromptCacheLifetime } from "@earendil-works/pi-ai";
|
|
2
2
|
/** Custom session-entry type carrying the cache-warm continuation story. */
|
|
3
3
|
export const GOAL_CACHE_WARMUP_ENTRY_TYPE = "goal-cache-warmup";
|
|
4
4
|
/**
|
|
@@ -14,6 +14,12 @@ export const GOAL_MONITOR_CONTINUATION_FALLBACK_DELAY_MS = 240_000;
|
|
|
14
14
|
* periodic re-check lands inside the cache.
|
|
15
15
|
*/
|
|
16
16
|
export const GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS = 270_000;
|
|
17
|
+
/**
|
|
18
|
+
* Backstop used instead of the 270s default when the active model's prompt cache is best-effort
|
|
19
|
+
* (for example direct DeepSeek, code-yeongyu/senpi#831): there is no TTL for the re-check to land
|
|
20
|
+
* inside, so the goal only needs the long liveness re-check, not a cache-preservation wake.
|
|
21
|
+
*/
|
|
22
|
+
export const GOAL_MONITOR_BEST_EFFORT_BACKSTOP_SECONDS = 3570;
|
|
17
23
|
const GOAL_MONITOR_CONTINUATION_MIN_DELAY_MS = 1_000;
|
|
18
24
|
const GOAL_MONITOR_CONTINUATION_HARD_CEILING_MS = 3_600_000;
|
|
19
25
|
/**
|
|
@@ -36,6 +42,22 @@ export function resolveGoalMonitorContinuationDelayMs(goalBackstopMaxSeconds) {
|
|
|
36
42
|
: GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS;
|
|
37
43
|
return Math.max(GOAL_MONITOR_CONTINUATION_MIN_DELAY_MS, Math.min(configuredMs, GOAL_MONITOR_CONTINUATION_HARD_CEILING_MS));
|
|
38
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Backstop ceiling in seconds for the active model's cache lifetime. The built-in 270s default exists
|
|
47
|
+
* to land the re-check inside a 5-minute TTL; a best-effort cache has no TTL, so an unconfigured
|
|
48
|
+
* backstop becomes the long liveness re-check. The settings getter reports an unset value as the
|
|
49
|
+
* 270s default, so a value equal to it is treated as the default.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveGoalBackstopMaxSecondsForCache(goalBackstopMaxSeconds, lifetime) {
|
|
52
|
+
if (lifetime?.kind !== "best-effort")
|
|
53
|
+
return goalBackstopMaxSeconds;
|
|
54
|
+
const isDefault = goalBackstopMaxSeconds === undefined || goalBackstopMaxSeconds * 1000 === GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS;
|
|
55
|
+
return isDefault ? GOAL_MONITOR_BEST_EFFORT_BACKSTOP_SECONDS : goalBackstopMaxSeconds;
|
|
56
|
+
}
|
|
57
|
+
/** Prompt-cache lifetime of the active model, from the provider's documented cache contract. */
|
|
58
|
+
export function resolveGoalPromptCacheLifetime(model, env) {
|
|
59
|
+
return model === undefined ? undefined : resolvePromptCacheLifetime(model, toProviderEnv(env));
|
|
60
|
+
}
|
|
39
61
|
export function createGoalCacheWarmScheduleData(options) {
|
|
40
62
|
return {
|
|
41
63
|
goalId: options.goalId,
|
|
@@ -50,7 +72,11 @@ export function createGoalCacheWarmScheduleData(options) {
|
|
|
50
72
|
const TOKENS_PER_PRICE_UNIT = 1_000_000;
|
|
51
73
|
export function estimateCacheWarmMetrics(model, env, lastTurnUsage) {
|
|
52
74
|
const cachedTokens = clampTokens(lastTurnUsage?.cacheRead) + clampTokens(lastTurnUsage?.cacheWrite);
|
|
53
|
-
const
|
|
75
|
+
const lifetime = resolveGoalPromptCacheLifetime(model, env);
|
|
76
|
+
if (lifetime?.kind === "best-effort") {
|
|
77
|
+
return cachedTokens === 0 ? undefined : { cachedTokens, cacheLifetime: "best-effort" };
|
|
78
|
+
}
|
|
79
|
+
const ttlSeconds = lifetime?.kind === "ttl" ? lifetime.ttlSeconds : undefined;
|
|
54
80
|
if (ttlSeconds === undefined && cachedTokens === 0)
|
|
55
81
|
return undefined;
|
|
56
82
|
const estimatedSavedUsd = model !== undefined && cachedTokens > 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-warm.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/cache-warm.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, Model, ProviderEnv } from \"@earendil-works/pi-ai\";\nimport { resolvePromptCacheTtlSeconds } from \"@earendil-works/pi-ai\";\nimport type { TokenUsageSnapshot } from \"./types.ts\";\n\n/** Custom session-entry type carrying the cache-warm continuation story. */\nexport const GOAL_CACHE_WARMUP_ENTRY_TYPE = \"goal-cache-warmup\";\n\n/**\n * Accounting fallback for a monitor continuation whose scheduled delay is no\n * longer known (a held timer restored across a reload). It is deliberately NOT\n * the armed delay: while a wake source is live the monitor arms the configured\n * backstop below, never a timer derived from the cache-safe wait.\n */\nexport const GOAL_MONITOR_CONTINUATION_FALLBACK_DELAY_MS = 240_000;\n/**\n * Default `promptCache.goalBackstopMaxSeconds` (settings-manager.ts) expressed in ms:\n * the 5-minute Anthropic prompt-cache TTL minus the 30s safety buffer, so the\n * periodic re-check lands inside the cache.\n */\nexport const GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS = 270_000;\nconst GOAL_MONITOR_CONTINUATION_MIN_DELAY_MS = 1_000;\nconst GOAL_MONITOR_CONTINUATION_HARD_CEILING_MS = 3_600_000;\n\n/**\n * Delay of the goal-monitor backstop, in milliseconds.\n *\n * A live wake source normally resumes the goal when it delivers (the drain\n * fire), but a wake source can be misconfigured - a filter that never matches,\n * a stream that never ends - so the backstop is the floor underneath it: the\n * goal re-checks at least this often instead of trusting the source for an\n * hour. It is configured, never derived from the prompt-cache safe wait, through\n * `promptCache.goalBackstopMaxSeconds`; a missing, non-finite, or non-positive\n * setting falls back to the 270s default, and the result is clamped into\n * [1s, 1h].\n */\nexport function resolveGoalMonitorContinuationDelayMs(goalBackstopMaxSeconds: number | undefined): number {\n\tconst configuredMs =\n\t\ttypeof goalBackstopMaxSeconds === \"number\" &&\n\t\tNumber.isFinite(goalBackstopMaxSeconds) &&\n\t\tgoalBackstopMaxSeconds > 0\n\t\t\t? goalBackstopMaxSeconds * 1000\n\t\t\t: GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS;\n\treturn Math.max(\n\t\tGOAL_MONITOR_CONTINUATION_MIN_DELAY_MS,\n\t\tMath.min(configuredMs, GOAL_MONITOR_CONTINUATION_HARD_CEILING_MS),\n\t);\n}\n\n/** Cache context captured when a monitor-wait continuation is scheduled. */\nexport interface GoalCacheWarmMetrics {\n\t/** Prompt-cache TTL of the active model in seconds, when known. */\n\treadonly ttlSeconds?: number;\n\t/** Tokens sitting warm in the provider prompt cache after the last turn. */\n\treadonly cachedTokens: number;\n\t/** Estimated USD saved by re-reading those tokens from cache instead of paying a cold input read. */\n\treadonly estimatedSavedUsd?: number;\n}\n\nexport type GoalCacheWarmupPhase = \"scheduled\" | \"resumed\";\n\n/**\n * Durable payload appended as a `goal-cache-warmup` custom entry and carried by\n * the `goal_continuation_scheduled` / `goal_continuation_resumed` pi-events, so\n * external consumers (for example omo-desktop-app) can render the story later.\n */\nexport interface GoalCacheWarmupEntryData {\n\treadonly phase: GoalCacheWarmupPhase;\n\treadonly goalId: string;\n\t/** Display ordinal within the current in-memory Goal/wake epoch; absent on legacy persisted entries. */\n\treadonly iteration?: number;\n\t/** Planned continuation delay in milliseconds. */\n\treadonly delayMs: number;\n\t/** Epoch milliseconds when the scheduled continuation is expected to resume. */\n\treadonly dueAtMs?: number;\n\t/** Actual wait in milliseconds; present on the `resumed` phase only. */\n\treadonly waitedMs?: number;\n\t/** Backward-compatible field containing the total active wake-source count. */\n\treadonly activeMonitorCount: number;\n\t/** Full source-keyed snapshot; absent on entries written before wake sources were generalized. */\n\treadonly wakeSources?: Readonly<Record<string, number>>;\n\treadonly cache?: GoalCacheWarmMetrics;\n}\n\n/** Live entries always carry an iteration; the ordinal is intentionally not persisted into Goal state. */\nexport type LiveGoalCacheWarmupEntryData = GoalCacheWarmupEntryData & { readonly iteration: number };\n\nexport type GoalCacheWarmScheduleData = Omit<LiveGoalCacheWarmupEntryData, \"phase\" | \"waitedMs\">;\n\nexport function createGoalCacheWarmScheduleData(options: {\n\treadonly goalId: string;\n\treadonly delayMs: number;\n\treadonly scheduledAtMs: number;\n\treadonly iteration: number;\n\treadonly activeMonitorCount: number;\n\treadonly wakeSources: Readonly<Record<string, number>>;\n\treadonly cache?: GoalCacheWarmMetrics;\n}): GoalCacheWarmScheduleData {\n\treturn {\n\t\tgoalId: options.goalId,\n\t\tdelayMs: options.delayMs,\n\t\tdueAtMs: options.scheduledAtMs + options.delayMs,\n\t\titeration: options.iteration,\n\t\tactiveMonitorCount: options.activeMonitorCount,\n\t\twakeSources: options.wakeSources,\n\t\t...(options.cache !== undefined ? { cache: options.cache } : {}),\n\t};\n}\n\nconst TOKENS_PER_PRICE_UNIT = 1_000_000;\n\nexport function estimateCacheWarmMetrics(\n\tmodel: Model<Api> | undefined,\n\tenv: NodeJS.ProcessEnv,\n\tlastTurnUsage: Pick<TokenUsageSnapshot, \"cacheRead\" | \"cacheWrite\"> | undefined,\n): GoalCacheWarmMetrics | undefined {\n\tconst cachedTokens = clampTokens(lastTurnUsage?.cacheRead) + clampTokens(lastTurnUsage?.cacheWrite);\n\tconst ttlSeconds = model === undefined ? undefined : resolvePromptCacheTtlSeconds(model, toProviderEnv(env));\n\tif (ttlSeconds === undefined && cachedTokens === 0) return undefined;\n\tconst estimatedSavedUsd =\n\t\tmodel !== undefined && cachedTokens > 0\n\t\t\t? (Math.max(0, model.cost.input - model.cost.cacheRead) * cachedTokens) / TOKENS_PER_PRICE_UNIT\n\t\t\t: undefined;\n\treturn {\n\t\tcachedTokens,\n\t\t...(ttlSeconds !== undefined ? { ttlSeconds } : {}),\n\t\t...(estimatedSavedUsd !== undefined ? { estimatedSavedUsd } : {}),\n\t};\n}\n\nexport function formatWarmTokenCount(tokens: number): string {\n\tif (tokens >= 1_000_000) return `${trimTrailingZero((tokens / 1_000_000).toFixed(1))}M`;\n\tif (tokens >= 1000) return `${trimTrailingZero((tokens / 1000).toFixed(1))}K`;\n\treturn String(Math.max(0, Math.trunc(tokens)));\n}\n\nexport function formatWakeDuration(ms: number): string {\n\tconst seconds = Math.round(ms / 1000);\n\tif (seconds < 60) return `${seconds}s`;\n\tconst minutes = Math.floor(seconds / 60);\n\tconst restSeconds = seconds % 60;\n\tif (minutes < 60) return restSeconds === 0 ? `${minutes}m` : `${minutes}m ${restSeconds}s`;\n\tconst hours = Math.floor(minutes / 60);\n\tconst restMinutes = minutes % 60;\n\treturn restMinutes === 0 ? `${hours}h` : `${hours}h ${restMinutes}m`;\n}\n\n/**\n * Wake timestamp shown in cache-warm notices. Rendered in the user's local\n * system timezone with its short zone label (for example `GMT+9`); falls back\n * to the legacy UTC shape when local timezone formatting is unavailable.\n */\nexport function formatWakeTimestamp(dueAtMs: number): string {\n\tconst date = new Date(dueAtMs);\n\tconst local = formatLocalWakeTimestamp(date);\n\tif (local !== undefined) return local;\n\treturn `${date.toISOString().slice(0, 16).replace(\"T\", \" \")} UTC`;\n}\n\nfunction formatLocalWakeTimestamp(date: Date): string | undefined {\n\ttry {\n\t\tconst parts = new Intl.DateTimeFormat(\"en-CA\", {\n\t\t\tyear: \"numeric\",\n\t\t\tmonth: \"2-digit\",\n\t\t\tday: \"2-digit\",\n\t\t\thour: \"2-digit\",\n\t\t\tminute: \"2-digit\",\n\t\t\thourCycle: \"h23\",\n\t\t\ttimeZoneName: \"short\",\n\t\t}).formatToParts(date);\n\t\tconst read = (type: string): string | undefined => parts.find((part) => part.type === type)?.value;\n\t\tconst year = read(\"year\");\n\t\tconst month = read(\"month\");\n\t\tconst day = read(\"day\");\n\t\tconst hour = read(\"hour\");\n\t\tconst minute = read(\"minute\");\n\t\tif (\n\t\t\tyear === undefined ||\n\t\t\tmonth === undefined ||\n\t\t\tday === undefined ||\n\t\t\thour === undefined ||\n\t\t\tminute === undefined\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst zone = read(\"timeZoneName\");\n\t\treturn `${year}-${month}-${day} ${hour}:${minute}${zone === undefined ? \"\" : ` ${zone}`}`;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function formatCacheTtl(seconds: number): string {\n\tif (seconds % 3600 === 0) return `${seconds / 3600}h`;\n\tif (seconds % 60 === 0) return `${seconds / 60}m`;\n\treturn `${seconds}s`;\n}\n\nexport function formatSavedUsd(value: number): string {\n\tif (value < 0.0005) return \"<$0.001\";\n\tif (value < 1) return `$${value.toFixed(3)}`;\n\treturn `$${value.toFixed(2)}`;\n}\n\nfunction trimTrailingZero(value: string): string {\n\treturn value.endsWith(\".0\") ? value.slice(0, -2) : value;\n}\n\nfunction clampTokens(value: number | undefined): number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;\n}\n\nfunction toProviderEnv(env: NodeJS.ProcessEnv): ProviderEnv {\n\tconst resolved: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(env)) {\n\t\tif (value !== undefined) resolved[key] = value;\n\t}\n\treturn resolved;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cache-warm.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/cache-warm.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, Model, PromptCacheLifetime, ProviderEnv } from \"@earendil-works/pi-ai\";\nimport { resolvePromptCacheLifetime } from \"@earendil-works/pi-ai\";\nimport type { TokenUsageSnapshot } from \"./types.ts\";\n\n/** Custom session-entry type carrying the cache-warm continuation story. */\nexport const GOAL_CACHE_WARMUP_ENTRY_TYPE = \"goal-cache-warmup\";\n\n/**\n * Accounting fallback for a monitor continuation whose scheduled delay is no\n * longer known (a held timer restored across a reload). It is deliberately NOT\n * the armed delay: while a wake source is live the monitor arms the configured\n * backstop below, never a timer derived from the cache-safe wait.\n */\nexport const GOAL_MONITOR_CONTINUATION_FALLBACK_DELAY_MS = 240_000;\n/**\n * Default `promptCache.goalBackstopMaxSeconds` (settings-manager.ts) expressed in ms:\n * the 5-minute Anthropic prompt-cache TTL minus the 30s safety buffer, so the\n * periodic re-check lands inside the cache.\n */\nexport const GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS = 270_000;\n/**\n * Backstop used instead of the 270s default when the active model's prompt cache is best-effort\n * (for example direct DeepSeek, code-yeongyu/senpi#831): there is no TTL for the re-check to land\n * inside, so the goal only needs the long liveness re-check, not a cache-preservation wake.\n */\nexport const GOAL_MONITOR_BEST_EFFORT_BACKSTOP_SECONDS = 3570;\nconst GOAL_MONITOR_CONTINUATION_MIN_DELAY_MS = 1_000;\nconst GOAL_MONITOR_CONTINUATION_HARD_CEILING_MS = 3_600_000;\n\n/**\n * Delay of the goal-monitor backstop, in milliseconds.\n *\n * A live wake source normally resumes the goal when it delivers (the drain\n * fire), but a wake source can be misconfigured - a filter that never matches,\n * a stream that never ends - so the backstop is the floor underneath it: the\n * goal re-checks at least this often instead of trusting the source for an\n * hour. It is configured, never derived from the prompt-cache safe wait, through\n * `promptCache.goalBackstopMaxSeconds`; a missing, non-finite, or non-positive\n * setting falls back to the 270s default, and the result is clamped into\n * [1s, 1h].\n */\nexport function resolveGoalMonitorContinuationDelayMs(goalBackstopMaxSeconds: number | undefined): number {\n\tconst configuredMs =\n\t\ttypeof goalBackstopMaxSeconds === \"number\" &&\n\t\tNumber.isFinite(goalBackstopMaxSeconds) &&\n\t\tgoalBackstopMaxSeconds > 0\n\t\t\t? goalBackstopMaxSeconds * 1000\n\t\t\t: GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS;\n\treturn Math.max(\n\t\tGOAL_MONITOR_CONTINUATION_MIN_DELAY_MS,\n\t\tMath.min(configuredMs, GOAL_MONITOR_CONTINUATION_HARD_CEILING_MS),\n\t);\n}\n\n/**\n * Backstop ceiling in seconds for the active model's cache lifetime. The built-in 270s default exists\n * to land the re-check inside a 5-minute TTL; a best-effort cache has no TTL, so an unconfigured\n * backstop becomes the long liveness re-check. The settings getter reports an unset value as the\n * 270s default, so a value equal to it is treated as the default.\n */\nexport function resolveGoalBackstopMaxSecondsForCache(\n\tgoalBackstopMaxSeconds: number | undefined,\n\tlifetime: PromptCacheLifetime | undefined,\n): number | undefined {\n\tif (lifetime?.kind !== \"best-effort\") return goalBackstopMaxSeconds;\n\tconst isDefault =\n\t\tgoalBackstopMaxSeconds === undefined || goalBackstopMaxSeconds * 1000 === GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS;\n\treturn isDefault ? GOAL_MONITOR_BEST_EFFORT_BACKSTOP_SECONDS : goalBackstopMaxSeconds;\n}\n\n/** Prompt-cache lifetime of the active model, from the provider's documented cache contract. */\nexport function resolveGoalPromptCacheLifetime(\n\tmodel: Model<Api> | undefined,\n\tenv: NodeJS.ProcessEnv,\n): PromptCacheLifetime | undefined {\n\treturn model === undefined ? undefined : resolvePromptCacheLifetime(model, toProviderEnv(env));\n}\n\n/** Cache context captured when a monitor-wait continuation is scheduled. */\nexport interface GoalCacheWarmMetrics {\n\t/** Prompt-cache TTL of the active model in seconds, when the provider documents one. */\n\treadonly ttlSeconds?: number;\n\t/** Set when the provider caches automatically with no expiry contract; no TTL or savings are claimed. */\n\treadonly cacheLifetime?: \"best-effort\";\n\t/** Tokens sitting warm in the provider prompt cache after the last turn. */\n\treadonly cachedTokens: number;\n\t/** Estimated USD saved by re-reading those tokens from cache instead of paying a cold input read. */\n\treadonly estimatedSavedUsd?: number;\n}\n\nexport type GoalCacheWarmupPhase = \"scheduled\" | \"resumed\";\n\n/**\n * Durable payload appended as a `goal-cache-warmup` custom entry and carried by\n * the `goal_continuation_scheduled` / `goal_continuation_resumed` pi-events, so\n * external consumers (for example omo-desktop-app) can render the story later.\n */\nexport interface GoalCacheWarmupEntryData {\n\treadonly phase: GoalCacheWarmupPhase;\n\treadonly goalId: string;\n\t/** Display ordinal within the current in-memory Goal/wake epoch; absent on legacy persisted entries. */\n\treadonly iteration?: number;\n\t/** Planned continuation delay in milliseconds. */\n\treadonly delayMs: number;\n\t/** Epoch milliseconds when the scheduled continuation is expected to resume. */\n\treadonly dueAtMs?: number;\n\t/** Actual wait in milliseconds; present on the `resumed` phase only. */\n\treadonly waitedMs?: number;\n\t/** Backward-compatible field containing the total active wake-source count. */\n\treadonly activeMonitorCount: number;\n\t/** Full source-keyed snapshot; absent on entries written before wake sources were generalized. */\n\treadonly wakeSources?: Readonly<Record<string, number>>;\n\treadonly cache?: GoalCacheWarmMetrics;\n}\n\n/** Live entries always carry an iteration; the ordinal is intentionally not persisted into Goal state. */\nexport type LiveGoalCacheWarmupEntryData = GoalCacheWarmupEntryData & { readonly iteration: number };\n\nexport type GoalCacheWarmScheduleData = Omit<LiveGoalCacheWarmupEntryData, \"phase\" | \"waitedMs\">;\n\nexport function createGoalCacheWarmScheduleData(options: {\n\treadonly goalId: string;\n\treadonly delayMs: number;\n\treadonly scheduledAtMs: number;\n\treadonly iteration: number;\n\treadonly activeMonitorCount: number;\n\treadonly wakeSources: Readonly<Record<string, number>>;\n\treadonly cache?: GoalCacheWarmMetrics;\n}): GoalCacheWarmScheduleData {\n\treturn {\n\t\tgoalId: options.goalId,\n\t\tdelayMs: options.delayMs,\n\t\tdueAtMs: options.scheduledAtMs + options.delayMs,\n\t\titeration: options.iteration,\n\t\tactiveMonitorCount: options.activeMonitorCount,\n\t\twakeSources: options.wakeSources,\n\t\t...(options.cache !== undefined ? { cache: options.cache } : {}),\n\t};\n}\n\nconst TOKENS_PER_PRICE_UNIT = 1_000_000;\n\nexport function estimateCacheWarmMetrics(\n\tmodel: Model<Api> | undefined,\n\tenv: NodeJS.ProcessEnv,\n\tlastTurnUsage: Pick<TokenUsageSnapshot, \"cacheRead\" | \"cacheWrite\"> | undefined,\n): GoalCacheWarmMetrics | undefined {\n\tconst cachedTokens = clampTokens(lastTurnUsage?.cacheRead) + clampTokens(lastTurnUsage?.cacheWrite);\n\tconst lifetime = resolveGoalPromptCacheLifetime(model, env);\n\tif (lifetime?.kind === \"best-effort\") {\n\t\treturn cachedTokens === 0 ? undefined : { cachedTokens, cacheLifetime: \"best-effort\" };\n\t}\n\tconst ttlSeconds = lifetime?.kind === \"ttl\" ? lifetime.ttlSeconds : undefined;\n\tif (ttlSeconds === undefined && cachedTokens === 0) return undefined;\n\tconst estimatedSavedUsd =\n\t\tmodel !== undefined && cachedTokens > 0\n\t\t\t? (Math.max(0, model.cost.input - model.cost.cacheRead) * cachedTokens) / TOKENS_PER_PRICE_UNIT\n\t\t\t: undefined;\n\treturn {\n\t\tcachedTokens,\n\t\t...(ttlSeconds !== undefined ? { ttlSeconds } : {}),\n\t\t...(estimatedSavedUsd !== undefined ? { estimatedSavedUsd } : {}),\n\t};\n}\n\nexport function formatWarmTokenCount(tokens: number): string {\n\tif (tokens >= 1_000_000) return `${trimTrailingZero((tokens / 1_000_000).toFixed(1))}M`;\n\tif (tokens >= 1000) return `${trimTrailingZero((tokens / 1000).toFixed(1))}K`;\n\treturn String(Math.max(0, Math.trunc(tokens)));\n}\n\nexport function formatWakeDuration(ms: number): string {\n\tconst seconds = Math.round(ms / 1000);\n\tif (seconds < 60) return `${seconds}s`;\n\tconst minutes = Math.floor(seconds / 60);\n\tconst restSeconds = seconds % 60;\n\tif (minutes < 60) return restSeconds === 0 ? `${minutes}m` : `${minutes}m ${restSeconds}s`;\n\tconst hours = Math.floor(minutes / 60);\n\tconst restMinutes = minutes % 60;\n\treturn restMinutes === 0 ? `${hours}h` : `${hours}h ${restMinutes}m`;\n}\n\n/**\n * Wake timestamp shown in cache-warm notices. Rendered in the user's local\n * system timezone with its short zone label (for example `GMT+9`); falls back\n * to the legacy UTC shape when local timezone formatting is unavailable.\n */\nexport function formatWakeTimestamp(dueAtMs: number): string {\n\tconst date = new Date(dueAtMs);\n\tconst local = formatLocalWakeTimestamp(date);\n\tif (local !== undefined) return local;\n\treturn `${date.toISOString().slice(0, 16).replace(\"T\", \" \")} UTC`;\n}\n\nfunction formatLocalWakeTimestamp(date: Date): string | undefined {\n\ttry {\n\t\tconst parts = new Intl.DateTimeFormat(\"en-CA\", {\n\t\t\tyear: \"numeric\",\n\t\t\tmonth: \"2-digit\",\n\t\t\tday: \"2-digit\",\n\t\t\thour: \"2-digit\",\n\t\t\tminute: \"2-digit\",\n\t\t\thourCycle: \"h23\",\n\t\t\ttimeZoneName: \"short\",\n\t\t}).formatToParts(date);\n\t\tconst read = (type: string): string | undefined => parts.find((part) => part.type === type)?.value;\n\t\tconst year = read(\"year\");\n\t\tconst month = read(\"month\");\n\t\tconst day = read(\"day\");\n\t\tconst hour = read(\"hour\");\n\t\tconst minute = read(\"minute\");\n\t\tif (\n\t\t\tyear === undefined ||\n\t\t\tmonth === undefined ||\n\t\t\tday === undefined ||\n\t\t\thour === undefined ||\n\t\t\tminute === undefined\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst zone = read(\"timeZoneName\");\n\t\treturn `${year}-${month}-${day} ${hour}:${minute}${zone === undefined ? \"\" : ` ${zone}`}`;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function formatCacheTtl(seconds: number): string {\n\tif (seconds % 3600 === 0) return `${seconds / 3600}h`;\n\tif (seconds % 60 === 0) return `${seconds / 60}m`;\n\treturn `${seconds}s`;\n}\n\nexport function formatSavedUsd(value: number): string {\n\tif (value < 0.0005) return \"<$0.001\";\n\tif (value < 1) return `$${value.toFixed(3)}`;\n\treturn `$${value.toFixed(2)}`;\n}\n\nfunction trimTrailingZero(value: string): string {\n\treturn value.endsWith(\".0\") ? value.slice(0, -2) : value;\n}\n\nfunction clampTokens(value: number | undefined): number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;\n}\n\nfunction toProviderEnv(env: NodeJS.ProcessEnv): ProviderEnv {\n\tconst resolved: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(env)) {\n\t\tif (value !== undefined) resolved[key] = value;\n\t}\n\treturn resolved;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"monitor-continuation.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/monitor-continuation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"monitor-continuation.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/monitor-continuation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAgBrE,OAAO,EACN,sCAAsC,EACtC,2CAA2C,GAC3C,MAAM,iBAAiB,CAAC;AAoBzB,OAAO,KAAK,EACX,eAAe,EAIf,uBAAuB,EAEvB,kBAAkB,EAClB,MAAM,iCAAiC,CAAC;AAOzC,OAAO,KAAK,EAAE,IAAI,EAAsB,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOvD,eAAO,MAAM,iCAAiC,gCAAgC,CAAC;AAC/E,eAAO,MAAM,+BAA+B,8BAA8B,CAAC;AAC3E,eAAO,MAAM,mCAAmC,kCAAkC,CAAC;AACnF,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AAE1E,qBAAa,4BAA4B;;IAiCxC,YACC,EAAE,EAAE,YAAY,EAChB,qBAAqB,GAAE,MAAM,OAAqB,EAClD,uBAAuB,GAAE,MAAM,IAAe,EAC9C,UAAU,CAAC,EAAE,cAAc,EAO3B;IAED,KAAK,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,CASjC;IAEK,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAwDlE;IAEK,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAkBxE;IAEK,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAcjF;IAEK,iBAAiB,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC,CAyB1D;IAED,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAOhC;IAED,sGAAsG;IACtG,oBAAoB,IAAI,OAAO,CAE9B;IAED;;;;;;;OAOG;IACH,oBAAoB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAIrC;IAYD,mGAAmG;IACnG,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAsBrC;IAED,6FAA6F;IAC7F,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAY3D;IAED,sEAAsE;IACtE,cAAc,IAAI,IAAI,CAKrB;IAED,mGAAmG;IACnG,uBAAuB,IAAI,IAAI,CAE9B;IAED,OAAO,IAAI,IAAI,CAUd;CAyaD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createGoalCacheWarmScheduleData, estimateCacheWarmMetrics, GOAL_CACHE_WARMUP_ENTRY_TYPE, GOAL_MONITOR_CONTINUATION_FALLBACK_DELAY_MS, resolveGoalMonitorContinuationDelayMs, } from "./cache-warm.js";
|
|
1
|
+
import { createGoalCacheWarmScheduleData, estimateCacheWarmMetrics, GOAL_CACHE_WARMUP_ENTRY_TYPE, GOAL_MONITOR_CONTINUATION_FALLBACK_DELAY_MS, resolveGoalBackstopMaxSecondsForCache, resolveGoalMonitorContinuationDelayMs, resolveGoalPromptCacheLifetime, } from "./cache-warm.js";
|
|
2
2
|
import { subscribeGoalChannelState } from "./channel-state-subscriptions.js";
|
|
3
3
|
export { GOAL_MONITOR_BACKSTOP_DEFAULT_DELAY_MS, GOAL_MONITOR_CONTINUATION_FALLBACK_DELAY_MS, } from "./cache-warm.js";
|
|
4
4
|
import { continuationTurnUsedTools, evaluateGoalContinuation, GOAL_USER_GRACE_DELAY_MS, hasGoalContinuationProgress, hashAssistantText, isMalformedToolUseTurn, normalizeAssistantText, } from "./continuation.js";
|
|
@@ -287,11 +287,13 @@ export class MonitorAwareGoalContinuation {
|
|
|
287
287
|
// delivers. A pending ask-user question is the exception: re-prompting the
|
|
288
288
|
// model every backstop while the user is deciding is noise it cannot act
|
|
289
289
|
// on, so the wait is parked on the question's own deadline instead.
|
|
290
|
+
// A best-effort prompt cache has no TTL to land inside, so an unconfigured
|
|
291
|
+
// backstop there is the long liveness re-check (senpi#831).
|
|
290
292
|
const askUserWaitMs = kind === "monitor" ? this.#askUserWaitMs() : undefined;
|
|
291
293
|
const delayMs = kind === "monitor"
|
|
292
294
|
? (parked?.delayMs ??
|
|
293
295
|
askUserWaitMs ??
|
|
294
|
-
resolveGoalMonitorContinuationDelayMs(this.#ctx?.getPromptCacheGoalBackstopMaxSeconds?.()))
|
|
296
|
+
resolveGoalMonitorContinuationDelayMs(resolveGoalBackstopMaxSecondsForCache(this.#ctx?.getPromptCacheGoalBackstopMaxSeconds?.(), resolveGoalPromptCacheLifetime(this.#ctx?.model, process.env))))
|
|
295
297
|
: GOAL_USER_GRACE_DELAY_MS;
|
|
296
298
|
this.#scheduledDelayMs = delayMs;
|
|
297
299
|
let remainingMs = delayMs;
|