shadow-claw 1.40.0 → 1.41.0
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/.well-known/mcp/server-card.json +2 -2
- package/.well-known/mcp/servers.json +1 -1
- package/.well-known/mcp.json +2 -2
- package/README.md +1 -0
- package/dist/cli/build/build.js +1 -1
- package/dist/cli/{chunk-Bfz2NYgD.js → chunk-0IeC97mU.js} +7 -7
- package/dist/cli/chunk-3oLxlxPn.js +1 -0
- package/dist/cli/{chunk-BXg4-9Rq2.js → chunk-8CrZyP1b2.js} +1 -1
- package/dist/cli/{chunk-BP5Hg6CM.js → chunk-B--ueArB.js} +1 -1
- package/dist/cli/chunk-BIh7rXlJ2.js +1 -0
- package/dist/cli/{chunk-Drr0VSGA2.js → chunk-BKnTOLLS2.js} +1 -1
- package/dist/cli/{chunk-C5PuRV08.js → chunk-BLMC1Bxv.js} +1 -1
- package/dist/cli/{chunk-B2tNSlFy2.js → chunk-BOigzz2U2.js} +1 -1
- package/dist/cli/{chunk-DOf3DZdR.js → chunk-BOzn335y.js} +1 -1
- package/dist/cli/chunk-BjOeVLb42.js +1 -0
- package/dist/cli/{chunk-DqTx0rRp2.js → chunk-Bjqh5_SU2.js} +1 -1
- package/dist/cli/chunk-BqWBd_jF2.js +1 -0
- package/dist/cli/{chunk-Bc9nRNyN.js → chunk-BvVUnHTq.js} +12 -9
- package/dist/cli/{chunk-DIbSoQmd2.js → chunk-C6FFByBU2.js} +2 -2
- package/dist/cli/{chunk-Drq18VZe.js → chunk-C9-aL3rF.js} +1 -1
- package/dist/cli/{chunk-BUhqHeEA2.js → chunk-C9czddEh2.js} +2 -2
- package/dist/cli/chunk-CCx8WOwz2.js +1 -0
- package/dist/cli/{chunk-BkMSjVJb.js → chunk-CK6ejMig.js} +1 -1
- package/dist/cli/chunk-CUUERioR2.js +1 -0
- package/dist/cli/chunk-CXAobnJd.js +1 -0
- package/dist/cli/chunk-CY1X0pOF2.js +1 -0
- package/dist/cli/chunk-CZvvgLVv.js +1 -0
- package/dist/cli/chunk-CbcwbfJB.js +1 -0
- package/dist/cli/{chunk-quilB3TV2.js → chunk-Ci9OJGcd2.js} +1 -1
- package/dist/cli/chunk-Cj9wl0qx.js +1 -0
- package/dist/cli/{chunk-3gRhOmGo2.js → chunk-CjJsVrKI2.js} +1 -1
- package/dist/cli/{chunk-CmEfzm-_.js → chunk-ClPZ4Rea.js} +1 -1
- package/dist/cli/{chunk-WMPAfIvV2.js → chunk-DHJCGRuG2.js} +1 -1
- package/dist/cli/{chunk-C7Y2xIk4.js → chunk-DKVkYTV9.js} +1 -1
- package/dist/cli/chunk-DR2XtWRo.js +1 -0
- package/dist/cli/{chunk-DID5VvOG.js → chunk-DTES-dE6.js} +3 -3
- package/dist/cli/{chunk-BiVJVh_S.js → chunk-DUlIqkaX.js} +7 -7
- package/dist/cli/{chunk-CkpueGaZ2.js → chunk-DjWle8FF2.js} +1 -1
- package/dist/cli/{chunk-CSvHmNOk.js → chunk-DmRXVLfy.js} +1 -1
- package/dist/cli/{chunk-BT1xXZn72.js → chunk-HlSiTC-u2.js} +1 -1
- package/dist/cli/{chunk-da0bsiOn.js → chunk-P4JSeFBI.js} +1 -1
- package/dist/cli/chunk-UcNbHaRQ.js +1 -0
- package/dist/cli/{chunk-BXFHFbEd.js → chunk-UweLmfQS.js} +1 -1
- package/dist/cli/chunk-bQQkVCw-.js +94 -0
- package/dist/cli/{chunk-CGiXWHTi2.js → chunk-gwpxl-0W2.js} +1 -1
- package/dist/cli/{chunk-NH8koEis2.js → chunk-nmDhxXH_2.js} +1 -1
- package/dist/cli/chunk-p0n_c-I2.js +1 -0
- package/dist/cli/{chunk-D7xP72La.js → chunk-q7r4FosZ.js} +1 -1
- package/dist/cli/cli.js +3 -3
- package/dist/cli/prerender/dsd-shell/prerender-dsd-shell.js +1 -1
- package/dist/electron/main.cjs +1 -1
- package/dist/headless-agent.js +1 -1
- package/dist/lib/{app-routes-BAEMLu9g.js → app-routes-CKkiX12g.js} +7 -2
- package/dist/lib/app-routes-CKkiX12g.js.map +1 -0
- package/dist/lib/components/index.js +26 -26
- package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
- package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/settings/shadow-claw-git.js +1 -1
- package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
- package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
- package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
- package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
- package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
- package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.d.ts +10 -0
- package/dist/lib/components/shadow-claw-a2ui.js +1 -1
- package/dist/lib/components/shadow-claw-accounts.js +1 -1
- package/dist/lib/components/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/shadow-claw-channels.js +1 -1
- package/dist/lib/components/shadow-claw-chat.js +1 -1
- package/dist/lib/components/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/shadow-claw-conversations.js +1 -1
- package/dist/lib/components/shadow-claw-file-viewer/shadow-claw-file-viewer.d.ts +1 -1
- package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
- package/dist/lib/components/shadow-claw-files.js +1 -1
- package/dist/lib/components/shadow-claw-git.js +1 -1
- package/dist/lib/components/shadow-claw-integrations.js +1 -1
- package/dist/lib/components/shadow-claw-llm.js +1 -1
- package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
- package/dist/lib/components/shadow-claw-networking.js +1 -1
- package/dist/lib/components/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/shadow-claw-page-header.js +1 -1
- package/dist/lib/components/shadow-claw-pages.js +1 -1
- package/dist/lib/components/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/shadow-claw-settings.js +1 -1
- package/dist/lib/components/shadow-claw-storage.js +1 -1
- package/dist/lib/components/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/shadow-claw-tasks.js +1 -1
- package/dist/lib/components/shadow-claw-terminal.js +1 -1
- package/dist/lib/components/shadow-claw-tools.js +1 -1
- package/dist/lib/components/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw.js +1 -1
- package/dist/lib/{config-CPHDmyTx.js → config-DZkF9LiO.js} +2 -2
- package/dist/lib/{config-CPHDmyTx.js.map → config-DZkF9LiO.js.map} +1 -1
- package/dist/lib/{configurePeerJs-BsJoycQd.js → configurePeerJs-B3IE9BY2.js} +3 -3
- package/dist/lib/{configurePeerJs-BsJoycQd.js.map → configurePeerJs-B3IE9BY2.js.map} +1 -1
- package/dist/lib/{connections-BdIru7bF.js → connections-CalJGWvZ.js} +2 -2
- package/dist/lib/{connections-BdIru7bF.js.map → connections-CalJGWvZ.js.map} +1 -1
- package/dist/lib/core/orchestrator/orchestrator-state.d.ts +2 -1
- package/dist/lib/core/orchestrator/orchestrator.d.ts +2 -1
- package/dist/lib/core/orchestrator/utils/operations/task.d.ts +1 -0
- package/dist/lib/{downloadGroupFile-Dn0KsMlV.js → downloadGroupFile-DDE1fIhJ.js} +2 -2
- package/dist/lib/{downloadGroupFile-Dn0KsMlV.js.map → downloadGroupFile-DDE1fIhJ.js.map} +1 -1
- package/dist/lib/{file-viewer-BYrXXu-X.js → file-viewer-Bll2EoGc.js} +5 -5
- package/dist/lib/{file-viewer-BYrXXu-X.js.map → file-viewer-Bll2EoGc.js.map} +1 -1
- package/dist/lib/{getGroupDir-DF-RZO8X.js → getGroupDir-MHv7Lvmp.js} +4 -4
- package/dist/lib/{getGroupDir-DF-RZO8X.js.map → getGroupDir-MHv7Lvmp.js.map} +1 -1
- package/dist/lib/{git-s_0cgKm1.js → git-Bv_M3dgw.js} +3 -3
- package/dist/lib/{git-s_0cgKm1.js.map → git-Bv_M3dgw.js.map} +1 -1
- package/dist/lib/{iframe-sanitizer-DFdda1jJ.js → iframe-sanitizer-C7ZGDjEI.js} +2 -2
- package/dist/lib/{iframe-sanitizer-DFdda1jJ.js.map → iframe-sanitizer-C7ZGDjEI.js.map} +1 -1
- package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js → iframe-storage-proxy-CR8ZYHPd.js} +2 -2
- package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js.map → iframe-storage-proxy-CR8ZYHPd.js.map} +1 -1
- package/dist/lib/index.js +27 -27
- package/dist/lib/{initChatSplitResize-DuNlwCuI.js → initChatSplitResize-DiKTI-wk.js} +2 -2
- package/dist/lib/{initChatSplitResize-DuNlwCuI.js.map → initChatSplitResize-DiKTI-wk.js.map} +1 -1
- package/dist/lib/{initControlPlane-D4UZ4EhV.js → initControlPlane-CzjtQ4aM.js} +15 -15
- package/dist/lib/{initControlPlane-D4UZ4EhV.js.map → initControlPlane-CzjtQ4aM.js.map} +1 -1
- package/dist/lib/{markdown-CRRwbN9h.js → markdown-k8wA9LKy.js} +4 -4
- package/dist/lib/{markdown-CRRwbN9h.js.map → markdown-k8wA9LKy.js.map} +1 -1
- package/dist/lib/{mcp-reconnect-COdZ-cea.js → mcp-reconnect-CGIdfZnS.js} +2 -2
- package/dist/lib/{mcp-reconnect-COdZ-cea.js.map → mcp-reconnect-CGIdfZnS.js.map} +1 -1
- package/dist/lib/{memoryStorage-BsMe2FOQ.js → memoryStorage-DL-Bo3dh.js} +2 -2
- package/dist/lib/{memoryStorage-BsMe2FOQ.js.map → memoryStorage-DL-Bo3dh.js.map} +1 -1
- package/dist/lib/{orchestrator-D9f5fDCb.js → orchestrator-DKHCoikY.js} +11 -11
- package/dist/lib/{orchestrator-D9f5fDCb.js.map → orchestrator-DKHCoikY.js.map} +1 -1
- package/dist/lib/{orchestrator-Bq9ft3uG.js → orchestrator-Dn1ZOz9B.js} +20 -24
- package/dist/lib/orchestrator-Dn1ZOz9B.js.map +1 -0
- package/dist/lib/{peerjs-6u8KtenE.js → peerjs-BK41iBvP.js} +7 -7
- package/dist/lib/{peerjs-6u8KtenE.js.map → peerjs-BK41iBvP.js.map} +1 -1
- package/dist/lib/{push-client-DCnB2-OU.js → push-client-DKgVFD2Z.js} +3 -3
- package/dist/lib/{push-client-DCnB2-OU.js.map → push-client-DKgVFD2Z.js.map} +1 -1
- package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js → readGroupFileBytes-CgU_UfGv.js} +2 -2
- package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js.map → readGroupFileBytes-CgU_UfGv.js.map} +1 -1
- package/dist/lib/{shadow-claw-MumteAJy.js → shadow-claw-CtCj5Rn_.js} +121 -16
- package/dist/lib/shadow-claw-CtCj5Rn_.js.map +1 -0
- package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js → shadow-claw-a2ui-CvK5w940.js} +2 -2
- package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js.map → shadow-claw-a2ui-CvK5w940.js.map} +1 -1
- package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js → shadow-claw-accounts-Br3XdHro.js} +3 -3
- package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js.map → shadow-claw-accounts-Br3XdHro.js.map} +1 -1
- package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js → shadow-claw-channel-config-Da2dN19M.js} +3 -3
- package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js.map → shadow-claw-channel-config-Da2dN19M.js.map} +1 -1
- package/dist/lib/{shadow-claw-channels-CaceK52s.js → shadow-claw-channels-BofYHgz8.js} +3 -3
- package/dist/lib/{shadow-claw-channels-CaceK52s.js.map → shadow-claw-channels-BofYHgz8.js.map} +1 -1
- package/dist/lib/{shadow-claw-chat-BD18-mAR.js → shadow-claw-chat-Dw32DPV2.js} +12 -12
- package/dist/lib/{shadow-claw-chat-BD18-mAR.js.map → shadow-claw-chat-Dw32DPV2.js.map} +1 -1
- package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js → shadow-claw-control-plane-CwFotFDh.js} +7 -7
- package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js.map → shadow-claw-control-plane-CwFotFDh.js.map} +1 -1
- package/dist/lib/{shadow-claw-conversations-CDsza_oB.js → shadow-claw-conversations-0UXge23k.js} +5 -5
- package/dist/lib/{shadow-claw-conversations-CDsza_oB.js.map → shadow-claw-conversations-0UXge23k.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-CMOnFmcP.js → shadow-claw-file-viewer-CAsn9NxH.js} +30 -30
- package/dist/lib/{shadow-claw-file-viewer-CMOnFmcP.js.map → shadow-claw-file-viewer-CAsn9NxH.js.map} +1 -1
- package/dist/lib/{shadow-claw-files-BI0P3m8a.js → shadow-claw-files-xGGhsHNH.js} +9 -9
- package/dist/lib/{shadow-claw-files-BI0P3m8a.js.map → shadow-claw-files-xGGhsHNH.js.map} +1 -1
- package/dist/lib/{shadow-claw-git-xRApLEMN.js → shadow-claw-git-B606hcMU.js} +6 -6
- package/dist/lib/{shadow-claw-git-xRApLEMN.js.map → shadow-claw-git-B606hcMU.js.map} +1 -1
- package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js → shadow-claw-integrations-Bwq5cba1.js} +4 -4
- package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js.map → shadow-claw-integrations-Bwq5cba1.js.map} +1 -1
- package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js → shadow-claw-llm-BgpcMBIy.js} +6 -6
- package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js.map → shadow-claw-llm-BgpcMBIy.js.map} +1 -1
- package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js → shadow-claw-mcp-remote-DvQCA-Bk.js} +4 -4
- package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js.map → shadow-claw-mcp-remote-DvQCA-Bk.js.map} +1 -1
- package/dist/lib/{shadow-claw-networking-F5W3n5ci.js → shadow-claw-networking-Cxvf9c7E.js} +2 -2
- package/dist/lib/{shadow-claw-networking-F5W3n5ci.js.map → shadow-claw-networking-Cxvf9c7E.js.map} +1 -1
- package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js → shadow-claw-notifications-CGVcFYBy.js} +3 -3
- package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js.map → shadow-claw-notifications-CGVcFYBy.js.map} +1 -1
- package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js → shadow-claw-page-header-DAYz3I_o.js} +2 -2
- package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js.map → shadow-claw-page-header-DAYz3I_o.js.map} +1 -1
- package/dist/lib/{shadow-claw-pages-Bo8_z2S-.js → shadow-claw-pages-BLeEZb9k.js} +24 -12
- package/dist/lib/shadow-claw-pages-BLeEZb9k.js.map +1 -0
- package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js → shadow-claw-peerjs-BcFnbK74.js} +3 -3
- package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js.map → shadow-claw-peerjs-BcFnbK74.js.map} +1 -1
- package/dist/lib/{shadow-claw-settings-D00B4p4L.js → shadow-claw-settings-B7o0Z4hu.js} +9 -9
- package/dist/lib/{shadow-claw-settings-D00B4p4L.js.map → shadow-claw-settings-B7o0Z4hu.js.map} +1 -1
- package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js → shadow-claw-storage-afMVqB7E.js} +5 -5
- package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js.map → shadow-claw-storage-afMVqB7E.js.map} +1 -1
- package/dist/lib/{shadow-claw-task-server-D103YOuI.js → shadow-claw-task-server-B9-MQjl6.js} +2 -2
- package/dist/lib/{shadow-claw-task-server-D103YOuI.js.map → shadow-claw-task-server-B9-MQjl6.js.map} +1 -1
- package/dist/lib/{shadow-claw-tasks-6OhALkq1.js → shadow-claw-tasks-Dc4BC-t-.js} +8 -8
- package/dist/lib/{shadow-claw-tasks-6OhALkq1.js.map → shadow-claw-tasks-Dc4BC-t-.js.map} +1 -1
- package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js → shadow-claw-terminal-BINcTdsk.js} +2 -2
- package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js.map → shadow-claw-terminal-BINcTdsk.js.map} +1 -1
- package/dist/lib/{shadow-claw-tools-OeIwAnnd.js → shadow-claw-tools-BjFgXNLR.js} +7 -7
- package/dist/lib/{shadow-claw-tools-OeIwAnnd.js.map → shadow-claw-tools-BjFgXNLR.js.map} +1 -1
- package/dist/lib/{shadow-claw-webvm-gVFZ197u.js → shadow-claw-webvm-Xe3ByMDw.js} +3 -3
- package/dist/lib/{shadow-claw-webvm-gVFZ197u.js.map → shadow-claw-webvm-Xe3ByMDw.js.map} +1 -1
- package/dist/lib/subsystems/mcp/webmcp.d.ts +64 -0
- package/dist/lib/{syncWebMcpRegistration-Bbph0XZl.js → syncWebMcpRegistration-D7alChz0.js} +21 -6
- package/dist/lib/syncWebMcpRegistration-D7alChz0.js.map +1 -0
- package/dist/lib/{tools-D0OlSzMD.js → tools-Cmhpk3X8.js} +4 -4
- package/dist/lib/{tools-D0OlSzMD.js.map → tools-Cmhpk3X8.js.map} +1 -1
- package/dist/lib/utils/index.js +1 -1
- package/dist/lib/utils/namespacedStorage.js +1 -1
- package/dist/lib/utils/utils.js +1 -1
- package/dist/lib/{utils-DYdrO2KI.js → utils-CzgKe_XZ.js} +4 -4
- package/dist/lib/{utils-DYdrO2KI.js.map → utils-CzgKe_XZ.js.map} +1 -1
- package/dist/lib/{webmcp-D2bocsgh.js → webmcp-oEP9YpR2.js} +119 -3
- package/dist/lib/{webmcp-D2bocsgh.js.map → webmcp-oEP9YpR2.js.map} +1 -1
- package/dist/public/.well-known/mcp/server-card.json +2 -2
- package/dist/public/.well-known/mcp/servers.json +1 -1
- package/dist/public/.well-known/mcp.json +2 -2
- package/dist/public/AGENTS.md +2 -1
- package/dist/public/README.md +1 -0
- package/dist/public/agent.worker.js +1 -1
- package/dist/public/app-routes-C6-H2zPH.js +1 -0
- package/dist/public/components/shadow-claw/shadow-claw.css +1 -1
- package/dist/public/components/shadow-claw/shadow-claw.html +1 -1
- package/dist/public/components/shadow-claw-pages/shadow-claw-pages.css +1 -1
- package/dist/public/{config-D4lVqMs8.js → config-Dewnqegf.js} +1 -1
- package/dist/public/{configurePeerJs-DDOAVSaH.js → configurePeerJs-CaPU2sN5.js} +1 -1
- package/dist/public/{connections-CiukjGiH.js → connections-CwE7jE9l.js} +1 -1
- package/dist/public/{custom-element-security-piU2U-Ec.js → custom-element-security-DLgHMf5R.js} +1 -1
- package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
- package/dist/public/docs/example/article/index.html +5 -5
- package/dist/public/docs/guides/publishing-to-github-pages.md +49 -6
- package/dist/public/docs/publishing/index.html +5 -5
- package/dist/public/docs/skill-creator/index.html +5 -5
- package/dist/public/docs/subsystems/pages.md +10 -3
- package/dist/public/docs/subsystems/webmcp.md +89 -1
- package/dist/public/{downloadGroupFile-BiZQYY-O.js → downloadGroupFile-ChWHuap_.js} +1 -1
- package/dist/public/e2e/README.md +2 -2
- package/dist/public/{e2e-bridge-CybB810Q.js → e2e-bridge-BkaldjV2.js} +1 -1
- package/dist/public/{file-viewer-DLUBvOVD.js → file-viewer-Df4qUJCn.js} +1 -1
- package/dist/public/{getGroupDir-CLz5c8cg.js → getGroupDir-CeSr4fzS.js} +1 -1
- package/dist/public/{git-Ba8AeviI.js → git-Bscc1tK-.js} +2 -2
- package/dist/public/{iframe-sanitizer-CKOO7cxp.js → iframe-sanitizer-BVdkQsor.js} +1 -1
- package/dist/public/{iframe-storage-proxy-BvRiccH0.js → iframe-storage-proxy-Cg8aFw7o.js} +1 -1
- package/dist/public/index.css +1 -1
- package/dist/public/index.html +5 -5
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-osJXM2gu.js → initChatSplitResize-C3Rp1LeZ.js} +1 -1
- package/dist/public/{initControlPlane-CNggzsRw.js → initControlPlane-C1pnzG16.js} +1 -1
- package/dist/public/llms.txt +1 -1
- package/dist/public/main/index.html +5 -5
- package/dist/public/main/memory/index.html +5 -5
- package/dist/public/{markdown-CZA-Ye2Q.js → markdown-D3A902nT.js} +1 -1
- package/dist/public/{mcp-reconnect-BK1bYBdC.js → mcp-reconnect-BJWxQBm_.js} +1 -1
- package/dist/public/{memoryStorage-Bew1X8Uy.js → memoryStorage-ykv-Ri-y.js} +1 -1
- package/dist/public/{orchestrator-D6tel2Zp.js → orchestrator-BVnC4Vtt.js} +2 -2
- package/dist/public/{orchestrator-BLkS_jpm.js → orchestrator-CQcc7OFA.js} +2 -2
- package/dist/public/{peerjs-CYs1F7sq.js → peerjs-CYC2cI1Y.js} +1 -1
- package/dist/public/{push-client-_HUyJlFJ.js → push-client-BJrnOULM.js} +1 -1
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-DmrxnX3Q.js → shadow-claw-WwfOu-tu.js} +33 -3
- package/dist/public/{shadow-claw-accounts-BqGrm47Z.js → shadow-claw-accounts-BFZxrPsH.js} +1 -1
- package/dist/public/{shadow-claw-channels-Ci2UPaaA.js → shadow-claw-channels-C0w0c1EF.js} +1 -1
- package/dist/public/{shadow-claw-chat-BxR6cBYQ.js → shadow-claw-chat-DIJ0A-QN.js} +1 -1
- package/dist/public/{shadow-claw-control-plane-C1A-CZ9H.js → shadow-claw-control-plane-DZoxUd-1.js} +2 -2
- package/dist/public/{shadow-claw-conversations-DcwG05-H.js → shadow-claw-conversations-DMyG_5zj.js} +1 -1
- package/dist/public/{shadow-claw-file-viewer-a_uRA30F.js → shadow-claw-file-viewer-DYgZjZb9.js} +2 -2
- package/dist/public/{shadow-claw-files-B774Xz1d.js → shadow-claw-files-DIOUdKJr.js} +1 -1
- package/dist/public/{shadow-claw-git-CTIPP0vs.js → shadow-claw-git-if5S0QJr.js} +2 -2
- package/dist/public/{shadow-claw-integrations-CjtkA6l0.js → shadow-claw-integrations-BQt4hcWe.js} +1 -1
- package/dist/public/{shadow-claw-llm-DfmITHde.js → shadow-claw-llm-9uf1KFA0.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-BPuV22So.js → shadow-claw-mcp-remote-COpNIzJd.js} +1 -1
- package/dist/public/{shadow-claw-networking-DEMMygy0.js → shadow-claw-networking-AOVRUn6B.js} +1 -1
- package/dist/public/{shadow-claw-notifications-B9Xhcj1P.js → shadow-claw-notifications-CKB-W1TA.js} +1 -1
- package/dist/public/{shadow-claw-page-header-CAXokjQy.js → shadow-claw-page-header-D4LmUigA.js} +1 -1
- package/dist/public/{shadow-claw-pages-BRQw8bTm.js → shadow-claw-pages-BpHRY4vH.js} +12 -3
- package/dist/public/{shadow-claw-settings-BGrDNyqh.js → shadow-claw-settings-CE-aUo_J.js} +3 -3
- package/dist/public/{shadow-claw-storage-DK3pf11w.js → shadow-claw-storage-CFKC-Y3V.js} +1 -1
- package/dist/public/{shadow-claw-task-server-CMp5Vjza.js → shadow-claw-task-server-B_ZjqK4B.js} +1 -1
- package/dist/public/{shadow-claw-tasks-BO8aXM2B.js → shadow-claw-tasks-CGsEv48K.js} +1 -1
- package/dist/public/{shadow-claw-terminal-BSeTuyDa.js → shadow-claw-terminal-DcaS7cPH.js} +1 -1
- package/dist/public/{shadow-claw-tools-Bj6DLZ-2.js → shadow-claw-tools-Cnu9hKy0.js} +1 -1
- package/dist/public/{shadow-claw-webvm-M7d5RH8H.js → shadow-claw-webvm-BB8SFvJ8.js} +1 -1
- package/dist/public/syncWebMcpRegistration-D2Zx4oA1.js +1 -0
- package/dist/public/theme-init.js +1 -1
- package/dist/public/{tools-nysPKnGT.js → tools-sOLCHTj8.js} +1 -1
- package/dist/public/{webmcp-Bo3KTgRb.js → webmcp-YFt6vM1_.js} +1 -1
- package/dist/server.js +189 -189
- package/package.json +1 -1
- package/src/cli/cli.ts +2 -2
- package/src/cli/prerender/dsd-shell/prerender-dsd-shell.test.ts +4 -1
- package/src/cli/prerender/dsd-shell/prerender-dsd-shell.ts +7 -1
- package/src/cli/prerender/utils/insert-before-closing-head.test.ts +12 -0
- package/src/cli/prerender/utils/insert-before-closing-head.ts +4 -6
- package/src/cli/site-config/apply.test.ts +90 -0
- package/src/cli/site-config/apply.ts +181 -13
- package/src/components/shadow-claw/shadow-claw.css +26 -0
- package/src/components/shadow-claw/shadow-claw.html +4 -0
- package/src/components/shadow-claw/utils/applyRoute.ts +3 -0
- package/src/components/shadow-claw/utils/bindEventListeners.test.ts +6 -0
- package/src/components/shadow-claw/utils/bindEventListeners.ts +30 -1
- package/src/components/shadow-claw/utils/getRoute.test.ts +52 -0
- package/src/components/shadow-claw/utils/getRoute.ts +18 -0
- package/src/components/shadow-claw/utils/navigateToRoute.test.ts +21 -0
- package/src/components/shadow-claw/utils/navigateToRoute.ts +10 -0
- package/src/components/shadow-claw/utils/sidebarVisibility.ts +12 -4
- package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.test.ts +93 -0
- package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.ts +100 -0
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +31 -31
- package/src/components/shadow-claw-pages/shadow-claw-pages.css +10 -1
- package/src/components/shadow-claw-pages/utils/removePreviewIframe.ts +3 -0
- package/src/core/effect.test.ts +19 -0
- package/src/core/orchestrator/orchestrator-state.ts +2 -1
- package/src/core/orchestrator/orchestrator.test.ts +686 -5
- package/src/core/orchestrator/orchestrator.ts +3 -10
- package/src/core/orchestrator/utils/compactContext.test.ts +423 -121
- package/src/core/orchestrator/utils/configurePeerJs.test.ts +27 -0
- package/src/core/orchestrator/utils/deliverResponse.test.ts +127 -0
- package/src/core/orchestrator/utils/dispatchSubagentInvoke.test.ts +23 -0
- package/src/core/orchestrator/utils/enqueue.test.ts +520 -86
- package/src/core/orchestrator/utils/handleWorkerMessage.test.ts +664 -144
- package/src/core/orchestrator/utils/initTasks.node.test.ts +147 -0
- package/src/core/orchestrator/utils/initTasks.test.ts +926 -191
- package/src/core/orchestrator/utils/initTasks.ts +5 -4
- package/src/core/orchestrator/utils/invokeAgent.test.ts +563 -110
- package/src/core/orchestrator/utils/invokeAgent.ts +8 -10
- package/src/core/orchestrator/utils/loadChannelConfigurations.test.ts +50 -0
- package/src/core/orchestrator/utils/operations/channel.test.ts +377 -170
- package/src/core/orchestrator/utils/operations/provider.test.ts +663 -85
- package/src/core/orchestrator/utils/operations/room.test.ts +74 -11
- package/src/core/orchestrator/utils/operations/task.test.ts +452 -56
- package/src/core/orchestrator/utils/operations/task.ts +1 -1
- package/src/core/orchestrator/utils/operations/vm.test.ts +66 -7
- package/src/core/orchestrator/utils/parseDirectToolCommand.test.ts +6 -0
- package/src/core/orchestrator/utils/settings.test.ts +248 -81
- package/src/core/orchestrator/utils/setupPushTaskListener.test.ts +58 -0
- package/src/core/orchestrator/utils/syncWebMcpRegistration.test.ts +392 -64
- package/src/core/orchestrator/utils/syncWebMcpRegistration.ts +32 -3
- package/src/server/middleware/static-files.test.ts +36 -0
- package/src/server/middleware/static-files.ts +14 -6
- package/src/server/routes/mcp.ts +2 -2
- package/src/storage/staticRouting.test.ts +36 -0
- package/src/storage/staticRouting.ts +31 -3
- package/src/stores/file-viewer.test.ts +11 -0
- package/src/stores/theme.test.ts +41 -2
- package/src/stores/toast.test.ts +25 -0
- package/src/subsystems/mcp/webmcp.test.ts +255 -0
- package/src/subsystems/mcp/webmcp.ts +253 -0
- package/src/worker/tools/bash/native-bash-executor.test.ts +31 -1
- package/src/worker/tools/git/utils/extractConflictPaths.test.ts +24 -0
- package/src/worker/tools/git/utils/parseConflictRegions.test.ts +46 -0
- package/src/worker/tools/git/utils/resolveCorsProxy.test.ts +26 -5
- package/src/worker/tools/git/utils/truncateSnippet.test.ts +16 -0
- package/src/worker/tools/node-transformers-executor.test.ts +105 -6
- package/src/worker/tools/remote-mcp/remote-mcp.test.ts +172 -47
- package/src/worker/tools/remote-mcp/utils/formatListToolsOutput.test.ts +19 -0
- package/src/worker/tools/remote-mcp/utils/isMcpReauthError.test.ts +45 -0
- package/src/worker/tools/remote-mcp/utils/resolveMcpReauth.test.ts +48 -0
- package/src/worker/tools/rooms/list-room-members.test.ts +24 -10
- package/src/worker/tools/tasks/list-tasks.test.ts +39 -8
- package/src/worker/tools/tasks/update-task.test.ts +78 -14
- package/src/worker/tools/ui/javascript.test.ts +70 -17
- package/src/worker/tools/ui/list-tool-profiles.test.ts +31 -9
- package/dist/cli/chunk-00GAzVPl2.js +0 -1
- package/dist/cli/chunk-2hnFoNNC.js +0 -1
- package/dist/cli/chunk-BKQL4nzF2.js +0 -1
- package/dist/cli/chunk-BQ4K3fqj2.js +0 -1
- package/dist/cli/chunk-BVCGo6uf2.js +0 -1
- package/dist/cli/chunk-BcTH_-FL.js +0 -1
- package/dist/cli/chunk-BmD_sPRt.js +0 -1
- package/dist/cli/chunk-BsxNqqer.js +0 -1
- package/dist/cli/chunk-CCcF5T-c.js +0 -1
- package/dist/cli/chunk-CFhvwUeL.js +0 -91
- package/dist/cli/chunk-CwWtvULn.js +0 -1
- package/dist/cli/chunk-Drf-hXIR2.js +0 -1
- package/dist/cli/chunk-Dt5HNYVi.js +0 -1
- package/dist/cli/chunk-DwZQi9Ax.js +0 -1
- package/dist/cli/chunk-m9ZX7TyF2.js +0 -1
- package/dist/lib/app-routes-BAEMLu9g.js.map +0 -1
- package/dist/lib/orchestrator-Bq9ft3uG.js.map +0 -1
- package/dist/lib/shadow-claw-MumteAJy.js.map +0 -1
- package/dist/lib/shadow-claw-pages-Bo8_z2S-.js.map +0 -1
- package/dist/lib/syncWebMcpRegistration-Bbph0XZl.js.map +0 -1
- package/dist/public/app-routes-BMQ4HFaM.js +0 -1
- package/dist/public/syncWebMcpRegistration-4bT0YUyZ.js +0 -1
|
@@ -1,16 +1,80 @@
|
|
|
1
1
|
import { jest } from "@jest/globals";
|
|
2
|
+
import { webcrypto } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
const mockSubtle = {
|
|
5
|
+
generateKey: jest.fn(async () => ({ type: "secret" })),
|
|
6
|
+
encrypt: jest.fn(
|
|
7
|
+
async (_cfg: unknown, _key: unknown, data: BufferSource) =>
|
|
8
|
+
new Uint8Array(data as ArrayBuffer).slice().buffer,
|
|
9
|
+
),
|
|
10
|
+
decrypt: jest.fn(
|
|
11
|
+
async (_cfg: unknown, _key: unknown, data: BufferSource) =>
|
|
12
|
+
new Uint8Array(data as ArrayBuffer).slice().buffer,
|
|
13
|
+
),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
Object.defineProperty(globalThis, "crypto", {
|
|
17
|
+
value: {
|
|
18
|
+
...webcrypto,
|
|
19
|
+
subtle: mockSubtle,
|
|
20
|
+
getRandomValues: <T extends ArrayBufferView | null>(array: T): T => {
|
|
21
|
+
if (array && "fill" in array) {
|
|
22
|
+
(array as unknown as Uint8Array).fill(1);
|
|
23
|
+
}
|
|
24
|
+
return array;
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
async function resetKeystore(): Promise<void> {
|
|
32
|
+
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
|
33
|
+
const req = indexedDB.open("shadowclaw-keystore", 1);
|
|
34
|
+
req.onupgradeneeded = () => {
|
|
35
|
+
req.result.createObjectStore("keys");
|
|
36
|
+
};
|
|
37
|
+
req.onsuccess = () => resolve(req.result);
|
|
38
|
+
req.onerror = () => reject(req.error);
|
|
39
|
+
});
|
|
40
|
+
const tx = db.transaction("keys", "readwrite");
|
|
41
|
+
tx.objectStore("keys").put(
|
|
42
|
+
{ type: "secret" } as CryptoKey,
|
|
43
|
+
"api-key-encryption",
|
|
44
|
+
);
|
|
45
|
+
await new Promise<void>((resolve, reject) => {
|
|
46
|
+
tx.oncomplete = () => resolve();
|
|
47
|
+
tx.onerror = () => reject(tx.error);
|
|
48
|
+
});
|
|
49
|
+
db.close();
|
|
50
|
+
}
|
|
2
51
|
|
|
3
52
|
import {
|
|
4
53
|
ASSISTANT_NAME,
|
|
54
|
+
CONFIG_KEYS,
|
|
5
55
|
LLAMAFILE_PROXY_URL,
|
|
6
56
|
getProvider,
|
|
57
|
+
getProviderApiKeyConfigKey,
|
|
7
58
|
} from "../../config/config.js";
|
|
8
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
createGroup,
|
|
61
|
+
saveGroupMetadata,
|
|
62
|
+
updateGroupPinnedProvider,
|
|
63
|
+
} from "../../db/groups.js";
|
|
64
|
+
import { openDatabase } from "../../db/openDatabase.js";
|
|
65
|
+
import { getConfig } from "../../db/getConfig.js";
|
|
66
|
+
import { setConfig } from "../../db/setConfig.js";
|
|
67
|
+
import { encryptValue } from "../../security/crypto.js";
|
|
9
68
|
import { orchestratorStore } from "../../stores/orchestrator.js";
|
|
10
69
|
import { toolsStore } from "../../stores/tools.js";
|
|
11
70
|
import { getWebMcpMode } from "../../subsystems/mcp/webmcp.js";
|
|
12
71
|
import { buildSystemPrompt } from "../../worker/utils/system-prompt.js";
|
|
13
72
|
import { Orchestrator } from "./orchestrator.js";
|
|
73
|
+
import type { ShadowClawDatabase } from "../../db/db.js";
|
|
74
|
+
import type { A2UIAction } from "../../ui/a2ui/types.js";
|
|
75
|
+
import type { ToolDefinition } from "../../subsystems/tools/tools.js";
|
|
76
|
+
import type { TaskScheduler } from "../../subsystems/tools/task-scheduler.js";
|
|
77
|
+
import type { ControlPlaneClient } from "../control-plane-client.js";
|
|
14
78
|
|
|
15
79
|
import {
|
|
16
80
|
deliverIntermediateResponse,
|
|
@@ -1898,7 +1962,7 @@ describe("Orchestrator", () => {
|
|
|
1898
1962
|
describe("ensureAllConnections", () => {
|
|
1899
1963
|
it("coordinates Control Plane, channels, task server, and scheduler", async () => {
|
|
1900
1964
|
const o = new Orchestrator();
|
|
1901
|
-
const fakeDb = {} as
|
|
1965
|
+
const fakeDb = {} as unknown as ShadowClawDatabase;
|
|
1902
1966
|
o.db = fakeDb;
|
|
1903
1967
|
o.taskServerEnabled = true;
|
|
1904
1968
|
|
|
@@ -1906,14 +1970,16 @@ describe("Orchestrator", () => {
|
|
|
1906
1970
|
start: jest.fn(),
|
|
1907
1971
|
tick: jest.fn().mockResolvedValue(undefined as never),
|
|
1908
1972
|
};
|
|
1909
|
-
o.scheduler = mockScheduler as
|
|
1973
|
+
o.scheduler = mockScheduler as unknown as TaskScheduler;
|
|
1910
1974
|
|
|
1911
1975
|
const ensureControlPlaneSpy = jest
|
|
1912
1976
|
.spyOn(o, "ensureControlPlaneConnected")
|
|
1913
|
-
.mockResolvedValue(null as
|
|
1977
|
+
.mockResolvedValue(null as unknown as ControlPlaneClient);
|
|
1914
1978
|
|
|
1915
1979
|
const peerjsEnsureSpy = jest.fn();
|
|
1916
|
-
(
|
|
1980
|
+
(
|
|
1981
|
+
o.peerjs as unknown as { ensureConnected: (force?: boolean) => void }
|
|
1982
|
+
).ensureConnected = peerjsEnsureSpy;
|
|
1917
1983
|
o.peerjs.running = true;
|
|
1918
1984
|
o.channelEnabledByType.peerjs = true;
|
|
1919
1985
|
o.peerjsMyPeerId = "my-peer";
|
|
@@ -1938,9 +2004,624 @@ describe("Orchestrator", () => {
|
|
|
1938
2004
|
expect(mockScheduler.start).toHaveBeenCalled();
|
|
1939
2005
|
expect(mockScheduler.tick).toHaveBeenCalled();
|
|
1940
2006
|
|
|
2007
|
+
// Test with default options (force: false) and no taskServer / no scheduler
|
|
2008
|
+
o.taskServerEnabled = false;
|
|
2009
|
+
o.db = null;
|
|
2010
|
+
o.scheduler = null;
|
|
2011
|
+
await o.ensureAllConnections();
|
|
2012
|
+
expect(ensureControlPlaneSpy).toHaveBeenCalledWith({
|
|
2013
|
+
orchestrator: o,
|
|
2014
|
+
db: undefined,
|
|
2015
|
+
force: false,
|
|
2016
|
+
});
|
|
2017
|
+
|
|
2018
|
+
// Test error handling in Control Plane and channel running states
|
|
2019
|
+
const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
2020
|
+
ensureControlPlaneSpy.mockRejectedValueOnce(
|
|
2021
|
+
new Error("CP network failure"),
|
|
2022
|
+
);
|
|
2023
|
+
jest.spyOn(o.browserChat, "start").mockImplementationOnce(() => {
|
|
2024
|
+
throw new Error("Browser chat error");
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
await o.ensureAllConnections();
|
|
2028
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
2029
|
+
warnSpy.mockRestore();
|
|
2030
|
+
|
|
1941
2031
|
ensureControlPlaneSpy.mockRestore();
|
|
1942
2032
|
replaySpy.mockRestore();
|
|
1943
2033
|
loadTasksSpy.mockRestore();
|
|
1944
2034
|
});
|
|
1945
2035
|
});
|
|
2036
|
+
|
|
2037
|
+
describe("Orchestrator instance methods and lifecycle", () => {
|
|
2038
|
+
it("initializes room notification handler delegating to roomManager", () => {
|
|
2039
|
+
const o = new Orchestrator();
|
|
2040
|
+
const handleSpy = jest
|
|
2041
|
+
.spyOn(o.roomManager, "handleNotification")
|
|
2042
|
+
.mockImplementation(() => {});
|
|
2043
|
+
|
|
2044
|
+
const peerChannel = o.peerjs as unknown as {
|
|
2045
|
+
_roomNotificationHandler?: (
|
|
2046
|
+
from: string,
|
|
2047
|
+
method: string,
|
|
2048
|
+
params: unknown,
|
|
2049
|
+
) => void;
|
|
2050
|
+
};
|
|
2051
|
+
peerChannel._roomNotificationHandler?.("peer-sender", "room/ping", {
|
|
2052
|
+
data: 123,
|
|
2053
|
+
});
|
|
2054
|
+
|
|
2055
|
+
expect(handleSpy).toHaveBeenCalledWith("peer-sender", "room/ping", {
|
|
2056
|
+
data: 123,
|
|
2057
|
+
});
|
|
2058
|
+
});
|
|
2059
|
+
|
|
2060
|
+
it("initializes orchestrator via init() and sets database and listeners", async () => {
|
|
2061
|
+
const origWorker = globalThis.Worker;
|
|
2062
|
+
globalThis.Worker = class {
|
|
2063
|
+
postMessage() {}
|
|
2064
|
+
terminate() {}
|
|
2065
|
+
addEventListener() {}
|
|
2066
|
+
removeEventListener() {}
|
|
2067
|
+
} as unknown as typeof Worker;
|
|
2068
|
+
|
|
2069
|
+
const o = new Orchestrator();
|
|
2070
|
+
jest.spyOn(o.roomManager, "loadRooms").mockImplementation(() => {});
|
|
2071
|
+
jest.spyOn(toolsStore, "load").mockResolvedValue(undefined);
|
|
2072
|
+
jest.spyOn(o, "loadApiKeyForProvider").mockResolvedValue();
|
|
2073
|
+
jest.spyOn(o, "loadSecretConfig").mockResolvedValue("");
|
|
2074
|
+
|
|
2075
|
+
const readySpy = jest.fn();
|
|
2076
|
+
o.events.on("ready", readySpy);
|
|
2077
|
+
|
|
2078
|
+
const db = await o.init();
|
|
2079
|
+
expect(db).toBeDefined();
|
|
2080
|
+
expect(o.db).toBe(db);
|
|
2081
|
+
expect(readySpy).toHaveBeenCalled();
|
|
2082
|
+
|
|
2083
|
+
// Flush microtasks and tick timer for background channel setup
|
|
2084
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 30));
|
|
2085
|
+
await o.browserChat.send("br:main", "test-display");
|
|
2086
|
+
|
|
2087
|
+
if (!origWorker) {
|
|
2088
|
+
delete (globalThis as unknown as { Worker?: unknown }).Worker;
|
|
2089
|
+
} else {
|
|
2090
|
+
globalThis.Worker = origWorker;
|
|
2091
|
+
}
|
|
2092
|
+
});
|
|
2093
|
+
|
|
2094
|
+
it("shuts down channels, scheduler, pollers, worker, and webMcp cleanup", () => {
|
|
2095
|
+
const o = new Orchestrator();
|
|
2096
|
+
const mockWorker = {
|
|
2097
|
+
terminate: jest.fn(),
|
|
2098
|
+
postMessage: jest.fn(),
|
|
2099
|
+
} as unknown as Worker;
|
|
2100
|
+
o.agentWorker = mockWorker;
|
|
2101
|
+
|
|
2102
|
+
const mockScheduler = {
|
|
2103
|
+
stop: jest.fn(),
|
|
2104
|
+
start: jest.fn(),
|
|
2105
|
+
tick: jest.fn(),
|
|
2106
|
+
} as unknown as TaskScheduler;
|
|
2107
|
+
o.scheduler = mockScheduler;
|
|
2108
|
+
|
|
2109
|
+
const mockCleanup = jest.fn();
|
|
2110
|
+
o.webMcpEffectCleanup = mockCleanup;
|
|
2111
|
+
|
|
2112
|
+
o.transformersProgressPollers.set("group-poll-1", 123);
|
|
2113
|
+
|
|
2114
|
+
o.shutdown();
|
|
2115
|
+
|
|
2116
|
+
expect(mockWorker.terminate).toHaveBeenCalled();
|
|
2117
|
+
expect(mockScheduler.stop).toHaveBeenCalled();
|
|
2118
|
+
expect(mockCleanup).toHaveBeenCalled();
|
|
2119
|
+
expect(o.transformersProgressPollers.size).toBe(0);
|
|
2120
|
+
});
|
|
2121
|
+
|
|
2122
|
+
it("manages provider request IDs for llamafile vs other providers", () => {
|
|
2123
|
+
const o = new Orchestrator();
|
|
2124
|
+
o.provider = "openrouter";
|
|
2125
|
+
expect(o.createProviderRequestId("group-1")).toBe("");
|
|
2126
|
+
|
|
2127
|
+
o.provider = "llamafile";
|
|
2128
|
+
const reqId = o.createProviderRequestId("group-llama");
|
|
2129
|
+
expect(reqId).toContain("group-llama:");
|
|
2130
|
+
expect(o.inFlightProviderRequestIds.get("group-llama")).toBe(reqId);
|
|
2131
|
+
|
|
2132
|
+
o.clearProviderRequest("group-llama");
|
|
2133
|
+
expect(o.inFlightProviderRequestIds.has("group-llama")).toBe(false);
|
|
2134
|
+
});
|
|
2135
|
+
|
|
2136
|
+
it("manages state transitions, submitMessage, and stopCurrentRequest across providers", () => {
|
|
2137
|
+
const o = new Orchestrator();
|
|
2138
|
+
const stateSpy = jest.fn();
|
|
2139
|
+
o.events.on("state-change", stateSpy);
|
|
2140
|
+
|
|
2141
|
+
o.setState("thinking", "group-state");
|
|
2142
|
+
expect(o.state).toBe("thinking");
|
|
2143
|
+
expect(stateSpy).toHaveBeenCalledWith({
|
|
2144
|
+
state: "thinking",
|
|
2145
|
+
groupId: "group-state",
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
const submitSpy = jest
|
|
2149
|
+
.spyOn(o.browserChat, "submit")
|
|
2150
|
+
.mockImplementation(() => {});
|
|
2151
|
+
o.submitMessage("Hello from user", "group-submit");
|
|
2152
|
+
expect(submitSpy).toHaveBeenCalledWith("Hello from user", "group-submit");
|
|
2153
|
+
o.submitMessage("Default message");
|
|
2154
|
+
expect(submitSpy).toHaveBeenCalledWith("Default message", "br:main");
|
|
2155
|
+
|
|
2156
|
+
// Early return when not thinking or responding
|
|
2157
|
+
o.state = "idle";
|
|
2158
|
+
o.stopCurrentRequest("group-idle");
|
|
2159
|
+
o.stopCurrentRequest();
|
|
2160
|
+
|
|
2161
|
+
// Stop current request with llamafile, promptController, and agentWorker
|
|
2162
|
+
o.state = "thinking";
|
|
2163
|
+
o.provider = "llamafile";
|
|
2164
|
+
o.inFlightProviderRequestIds.set("group-stop", "req-stop-1");
|
|
2165
|
+
const mockAbort = jest.fn();
|
|
2166
|
+
o.promptControllers.set("group-stop", {
|
|
2167
|
+
abort: mockAbort,
|
|
2168
|
+
} as unknown as AbortController);
|
|
2169
|
+
o.inFlightTriggerByGroup.set("group-stop", "trigger-content");
|
|
2170
|
+
o.inFlightEffectiveProviderByGroup.set("group-stop", {
|
|
2171
|
+
providerId: "llamafile",
|
|
2172
|
+
providerConfig: o.providerConfig,
|
|
2173
|
+
model: "model",
|
|
2174
|
+
});
|
|
2175
|
+
|
|
2176
|
+
const mockWorker = {
|
|
2177
|
+
postMessage: jest.fn(),
|
|
2178
|
+
terminate: jest.fn(),
|
|
2179
|
+
} as unknown as Worker;
|
|
2180
|
+
o.agentWorker = mockWorker;
|
|
2181
|
+
|
|
2182
|
+
o.stopCurrentRequest("group-stop");
|
|
2183
|
+
|
|
2184
|
+
expect(mockWorker.postMessage).toHaveBeenCalledWith({
|
|
2185
|
+
type: "cancel",
|
|
2186
|
+
payload: { groupId: "group-stop" },
|
|
2187
|
+
});
|
|
2188
|
+
expect(mockAbort).toHaveBeenCalled();
|
|
2189
|
+
expect(o.inFlightTriggerByGroup.has("group-stop")).toBe(false);
|
|
2190
|
+
expect(o.inFlightEffectiveProviderByGroup.has("group-stop")).toBe(false);
|
|
2191
|
+
expect(o.state).toBe("idle");
|
|
2192
|
+
|
|
2193
|
+
// Stop current request when responding with non-llamafile
|
|
2194
|
+
o.state = "responding";
|
|
2195
|
+
o.provider = "anthropic";
|
|
2196
|
+
o.stopCurrentRequest("group-resp");
|
|
2197
|
+
expect(o.state).toBe("idle");
|
|
2198
|
+
});
|
|
2199
|
+
|
|
2200
|
+
it("handles API key retrieval, caching, setting, and decryption failure", async () => {
|
|
2201
|
+
const o = new Orchestrator();
|
|
2202
|
+
const db = await openDatabase();
|
|
2203
|
+
|
|
2204
|
+
// No encrypted key returns null
|
|
2205
|
+
expect(await o.getApiKey()).toBeNull();
|
|
2206
|
+
|
|
2207
|
+
// Set key and retrieve (first call decrypts, second hits cache)
|
|
2208
|
+
await o.setApiKey(db, "sk-test-secret-key-1");
|
|
2209
|
+
const key1 = await o.getApiKey();
|
|
2210
|
+
expect(key1).toBe("sk-test-secret-key-1");
|
|
2211
|
+
|
|
2212
|
+
const key2 = await o.getApiKey();
|
|
2213
|
+
expect(key2).toBe("sk-test-secret-key-1");
|
|
2214
|
+
|
|
2215
|
+
// setApiKey throws when encryptValue returns null
|
|
2216
|
+
indexedDB.deleteDatabase("shadowclaw-keystore");
|
|
2217
|
+
mockSubtle.generateKey.mockResolvedValue(null as unknown as CryptoKey);
|
|
2218
|
+
await expect(o.setApiKey(db, "bad-key")).rejects.toThrow(
|
|
2219
|
+
"key failed to encrypt",
|
|
2220
|
+
);
|
|
2221
|
+
mockSubtle.generateKey.mockResolvedValue({ type: "secret" });
|
|
2222
|
+
await resetKeystore();
|
|
2223
|
+
|
|
2224
|
+
// Decryption failure caught and returns null
|
|
2225
|
+
const errSpy = jest.spyOn(console, "error").mockImplementation(() => {});
|
|
2226
|
+
await o.setApiKey(db, "key-to-fail");
|
|
2227
|
+
mockSubtle.decrypt.mockRejectedValueOnce(new Error("Decryption error"));
|
|
2228
|
+
const failedKey = await o.getApiKey();
|
|
2229
|
+
expect(failedKey).toBeNull();
|
|
2230
|
+
errSpy.mockRestore();
|
|
2231
|
+
|
|
2232
|
+
// Decryption returns null when decryptValue returns null
|
|
2233
|
+
indexedDB.deleteDatabase("shadowclaw-keystore");
|
|
2234
|
+
mockSubtle.generateKey.mockResolvedValueOnce(
|
|
2235
|
+
null as unknown as CryptoKey,
|
|
2236
|
+
);
|
|
2237
|
+
const nullKey = await o.getApiKey();
|
|
2238
|
+
expect(nullKey).toBeNull();
|
|
2239
|
+
await resetKeystore();
|
|
2240
|
+
});
|
|
2241
|
+
|
|
2242
|
+
it("gets API key for specific provider with openrouter legacy fallback and error handling", async () => {
|
|
2243
|
+
const o = new Orchestrator();
|
|
2244
|
+
const db = await openDatabase();
|
|
2245
|
+
|
|
2246
|
+
// Provider key exists
|
|
2247
|
+
const encAnthropic = await encryptValue("sk-anthropic-specific");
|
|
2248
|
+
await setConfig(
|
|
2249
|
+
db,
|
|
2250
|
+
getProviderApiKeyConfigKey("anthropic"),
|
|
2251
|
+
encAnthropic,
|
|
2252
|
+
);
|
|
2253
|
+
expect(await o.getApiKeyForSpecificProvider(db, "anthropic")).toBe(
|
|
2254
|
+
"sk-anthropic-specific",
|
|
2255
|
+
);
|
|
2256
|
+
|
|
2257
|
+
// OpenRouter legacy fallback
|
|
2258
|
+
await setConfig(db, getProviderApiKeyConfigKey("openrouter"), "");
|
|
2259
|
+
const encLegacy = await encryptValue("sk-openrouter-legacy");
|
|
2260
|
+
await setConfig(db, CONFIG_KEYS.API_KEY, encLegacy);
|
|
2261
|
+
expect(await o.getApiKeyForSpecificProvider(db, "openrouter")).toBe(
|
|
2262
|
+
"sk-openrouter-legacy",
|
|
2263
|
+
);
|
|
2264
|
+
|
|
2265
|
+
// OpenRouter with empty key and empty legacy key
|
|
2266
|
+
await setConfig(db, getProviderApiKeyConfigKey("openrouter"), "");
|
|
2267
|
+
await setConfig(db, CONFIG_KEYS.API_KEY, "");
|
|
2268
|
+
expect(await o.getApiKeyForSpecificProvider(db, "openrouter")).toBe("");
|
|
2269
|
+
|
|
2270
|
+
// Decrypted empty string returns empty string
|
|
2271
|
+
const encVal = await encryptValue("val");
|
|
2272
|
+
await setConfig(db, getProviderApiKeyConfigKey("openrouter"), encVal);
|
|
2273
|
+
mockSubtle.decrypt.mockResolvedValueOnce(new Uint8Array(0).buffer);
|
|
2274
|
+
expect(await o.getApiKeyForSpecificProvider(db, "openrouter")).toBe("");
|
|
2275
|
+
|
|
2276
|
+
// Missing key returns empty string
|
|
2277
|
+
expect(await o.getApiKeyForSpecificProvider(db, "nonexistent")).toBe("");
|
|
2278
|
+
|
|
2279
|
+
// Decryption failure caught and returns empty string
|
|
2280
|
+
const errSpy = jest.spyOn(console, "error").mockImplementation(() => {});
|
|
2281
|
+
mockSubtle.decrypt.mockRejectedValueOnce(new Error("Bad cipher"));
|
|
2282
|
+
expect(await o.getApiKeyForSpecificProvider(db, "openrouter")).toBe("");
|
|
2283
|
+
errSpy.mockRestore();
|
|
2284
|
+
});
|
|
2285
|
+
|
|
2286
|
+
it("loads API key for provider with legacy migration and missing key handling", async () => {
|
|
2287
|
+
const o = new Orchestrator();
|
|
2288
|
+
const db = await openDatabase();
|
|
2289
|
+
|
|
2290
|
+
// Direct key exists
|
|
2291
|
+
const encDirect = await encryptValue("sk-direct-key");
|
|
2292
|
+
await setConfig(
|
|
2293
|
+
db,
|
|
2294
|
+
getProviderApiKeyConfigKey("bedrock_proxy"),
|
|
2295
|
+
encDirect,
|
|
2296
|
+
);
|
|
2297
|
+
await o.loadApiKeyForProvider(db, "bedrock_proxy");
|
|
2298
|
+
expect(await o.getApiKey()).toBe("sk-direct-key");
|
|
2299
|
+
|
|
2300
|
+
// OpenRouter with legacy key migration
|
|
2301
|
+
await setConfig(db, getProviderApiKeyConfigKey("openrouter"), "");
|
|
2302
|
+
const encLegacy = await encryptValue("sk-migrated-openrouter");
|
|
2303
|
+
await setConfig(db, CONFIG_KEYS.API_KEY, encLegacy);
|
|
2304
|
+
await o.loadApiKeyForProvider(db, "openrouter");
|
|
2305
|
+
expect(await o.getApiKey()).toBe("sk-migrated-openrouter");
|
|
2306
|
+
expect(
|
|
2307
|
+
await getConfig(db, getProviderApiKeyConfigKey("openrouter")),
|
|
2308
|
+
).toBe(encLegacy);
|
|
2309
|
+
|
|
2310
|
+
// OpenRouter with missing key and missing legacy key
|
|
2311
|
+
await setConfig(db, getProviderApiKeyConfigKey("openrouter"), "");
|
|
2312
|
+
await setConfig(db, CONFIG_KEYS.API_KEY, "");
|
|
2313
|
+
await o.loadApiKeyForProvider(db, "openrouter");
|
|
2314
|
+
expect(await o.getApiKey()).toBeNull();
|
|
2315
|
+
|
|
2316
|
+
// Missing key clears internal key
|
|
2317
|
+
await o.loadApiKeyForProvider(db, "missing-provider");
|
|
2318
|
+
expect(await o.getApiKey()).toBeNull();
|
|
2319
|
+
});
|
|
2320
|
+
|
|
2321
|
+
it("loads and saves secret config with auto-migration and error handling", async () => {
|
|
2322
|
+
const o = new Orchestrator();
|
|
2323
|
+
const db = await openDatabase();
|
|
2324
|
+
|
|
2325
|
+
// Empty value clears secret
|
|
2326
|
+
await o.saveSecretConfig(db, "empty_secret", "");
|
|
2327
|
+
expect(await getConfig(db, "empty_secret")).toBe("");
|
|
2328
|
+
expect(await o.loadSecretConfig(db, "empty_secret")).toBe("");
|
|
2329
|
+
|
|
2330
|
+
// Non-empty value encrypted and decrypted
|
|
2331
|
+
await o.saveSecretConfig(db, "my_secret", "secret-content-xyz");
|
|
2332
|
+
const storedEncrypted = await getConfig(db, "my_secret");
|
|
2333
|
+
expect(storedEncrypted).not.toBe("secret-content-xyz");
|
|
2334
|
+
expect(await o.loadSecretConfig(db, "my_secret")).toBe(
|
|
2335
|
+
"secret-content-xyz",
|
|
2336
|
+
);
|
|
2337
|
+
|
|
2338
|
+
// Decrypted empty string returns empty string
|
|
2339
|
+
const encEmptyCipher = await encryptValue("val-empty");
|
|
2340
|
+
await setConfig(db, "empty_cipher_secret", encEmptyCipher);
|
|
2341
|
+
mockSubtle.decrypt.mockResolvedValueOnce(new Uint8Array(0).buffer);
|
|
2342
|
+
expect(await o.loadSecretConfig(db, "empty_cipher_secret")).toBe("");
|
|
2343
|
+
|
|
2344
|
+
// Auto-encrypt migration for plaintext value
|
|
2345
|
+
await setConfig(db, "legacy_plaintext", "raw-secret-value");
|
|
2346
|
+
const migrated = await o.loadSecretConfig(db, "legacy_plaintext");
|
|
2347
|
+
expect(migrated).toBe("raw-secret-value");
|
|
2348
|
+
const newEncrypted = await getConfig(db, "legacy_plaintext");
|
|
2349
|
+
expect(newEncrypted).not.toBe("raw-secret-value");
|
|
2350
|
+
|
|
2351
|
+
// Auto-encrypt migration where encryptValue returns null
|
|
2352
|
+
const encTemp = await encryptValue("plain-text-val");
|
|
2353
|
+
await setConfig(db, "unencrypted-secret-fail", encTemp);
|
|
2354
|
+
mockSubtle.decrypt.mockImplementationOnce(async () => {
|
|
2355
|
+
indexedDB.deleteDatabase("shadowclaw-keystore");
|
|
2356
|
+
mockSubtle.generateKey.mockResolvedValue(null as unknown as CryptoKey);
|
|
2357
|
+
throw new Error("bad decrypt");
|
|
2358
|
+
});
|
|
2359
|
+
expect(await o.loadSecretConfig(db, "unencrypted-secret-fail")).toBe(
|
|
2360
|
+
encTemp,
|
|
2361
|
+
);
|
|
2362
|
+
mockSubtle.generateKey.mockResolvedValue({ type: "secret" });
|
|
2363
|
+
await resetKeystore();
|
|
2364
|
+
|
|
2365
|
+
// saveSecretConfig throws when encryptValue returns null
|
|
2366
|
+
indexedDB.deleteDatabase("shadowclaw-keystore");
|
|
2367
|
+
mockSubtle.generateKey.mockResolvedValueOnce(
|
|
2368
|
+
null as unknown as CryptoKey,
|
|
2369
|
+
);
|
|
2370
|
+
await expect(
|
|
2371
|
+
o.saveSecretConfig(db, "fail_secret", "val"),
|
|
2372
|
+
).rejects.toThrow("Failed to encrypt secret config");
|
|
2373
|
+
mockSubtle.generateKey.mockResolvedValue({ type: "secret" });
|
|
2374
|
+
await resetKeystore();
|
|
2375
|
+
});
|
|
2376
|
+
|
|
2377
|
+
it("handles compaction and new session lifecycle", async () => {
|
|
2378
|
+
const o = new Orchestrator();
|
|
2379
|
+
const db = await openDatabase();
|
|
2380
|
+
|
|
2381
|
+
const compactSpy = jest.fn();
|
|
2382
|
+
const typingSpy = jest.fn();
|
|
2383
|
+
o.events.on("context-compacted", compactSpy);
|
|
2384
|
+
o.events.on("typing", typingSpy);
|
|
2385
|
+
|
|
2386
|
+
await o.handleCompactDone(db, "group-compact", "Compacted chat history");
|
|
2387
|
+
expect(compactSpy).toHaveBeenCalledWith({
|
|
2388
|
+
groupId: "group-compact",
|
|
2389
|
+
summary: "Compacted chat history",
|
|
2390
|
+
});
|
|
2391
|
+
expect(typingSpy).toHaveBeenCalledWith({
|
|
2392
|
+
groupId: "group-compact",
|
|
2393
|
+
typing: false,
|
|
2394
|
+
});
|
|
2395
|
+
expect(o.state).toBe("idle");
|
|
2396
|
+
|
|
2397
|
+
const sessionSpy = jest.fn();
|
|
2398
|
+
o.events.on("session-reset", sessionSpy);
|
|
2399
|
+
await o.newSession(db, "group-session");
|
|
2400
|
+
expect(sessionSpy).toHaveBeenCalledWith({ groupId: "group-session" });
|
|
2401
|
+
|
|
2402
|
+
// newSession with default groupId
|
|
2403
|
+
await o.newSession(db);
|
|
2404
|
+
expect(sessionSpy).toHaveBeenCalledWith({ groupId: "br:main" });
|
|
2405
|
+
});
|
|
2406
|
+
|
|
2407
|
+
it("restarts current request when active or returns false when inactive", async () => {
|
|
2408
|
+
const o = new Orchestrator();
|
|
2409
|
+
|
|
2410
|
+
// State is idle (also testing default groupId)
|
|
2411
|
+
o.state = "idle";
|
|
2412
|
+
expect(await o.restartCurrentRequest("g-restart")).toBe(false);
|
|
2413
|
+
expect(await o.restartCurrentRequest()).toBe(false);
|
|
2414
|
+
|
|
2415
|
+
// State is thinking but db is null
|
|
2416
|
+
o.state = "thinking";
|
|
2417
|
+
o.db = null;
|
|
2418
|
+
expect(await o.restartCurrentRequest("g-restart")).toBe(false);
|
|
2419
|
+
|
|
2420
|
+
// Db present but no in-flight trigger
|
|
2421
|
+
const db = await openDatabase();
|
|
2422
|
+
o.db = db;
|
|
2423
|
+
expect(await o.restartCurrentRequest("g-restart")).toBe(false);
|
|
2424
|
+
|
|
2425
|
+
// Active request restarted
|
|
2426
|
+
o.state = "thinking";
|
|
2427
|
+
o.inFlightTriggerByGroup.set("g-restart", "Original prompt");
|
|
2428
|
+
o.agentWorker = {
|
|
2429
|
+
postMessage: jest.fn(),
|
|
2430
|
+
terminate: jest.fn(),
|
|
2431
|
+
} as unknown as Worker;
|
|
2432
|
+
|
|
2433
|
+
const stopSpy = jest.spyOn(o, "stopCurrentRequest");
|
|
2434
|
+
const restarted = await o.restartCurrentRequest("g-restart");
|
|
2435
|
+
expect(restarted).toBe(true);
|
|
2436
|
+
expect(stopSpy).toHaveBeenCalledWith("g-restart");
|
|
2437
|
+
});
|
|
2438
|
+
|
|
2439
|
+
it("routes room A2UI actions to local enqueue or remote broadcast", async () => {
|
|
2440
|
+
const o = new Orchestrator();
|
|
2441
|
+
const action: A2UIAction = {
|
|
2442
|
+
type: "a2ui-action",
|
|
2443
|
+
actionId: "act-1",
|
|
2444
|
+
surfaceId: "surf-1",
|
|
2445
|
+
dataModel: { val: 42 },
|
|
2446
|
+
};
|
|
2447
|
+
|
|
2448
|
+
// Local surface with no db returns early
|
|
2449
|
+
o.db = null;
|
|
2450
|
+
await o.routeRoomA2UIAction("room:room-local", action);
|
|
2451
|
+
expect(o.messageQueue).toHaveLength(0);
|
|
2452
|
+
|
|
2453
|
+
// Local surface with db enqueues action
|
|
2454
|
+
const db = await openDatabase();
|
|
2455
|
+
o.db = db;
|
|
2456
|
+
o.peerjsMyPeerId = "peer-local";
|
|
2457
|
+
o.peerjs.myPeerId = "peer-local";
|
|
2458
|
+
jest
|
|
2459
|
+
.spyOn(o.roomManager, "getSurfaceOwner")
|
|
2460
|
+
.mockReturnValue("peer-local");
|
|
2461
|
+
|
|
2462
|
+
await o.routeRoomA2UIAction("room:room-local", action);
|
|
2463
|
+
expect(o.messageQueue.length).toBeGreaterThanOrEqual(1);
|
|
2464
|
+
|
|
2465
|
+
// Local surface with missing alias and peer id defaults sender to "you"
|
|
2466
|
+
o.peerjsMyAlias = "";
|
|
2467
|
+
o.peerjs.myPeerId = "";
|
|
2468
|
+
o.peerjsMyPeerId = "";
|
|
2469
|
+
jest.spyOn(o.roomManager, "getSurfaceOwner").mockReturnValue("");
|
|
2470
|
+
await o.routeRoomA2UIAction("room:room-local-you", action);
|
|
2471
|
+
expect(o.messageQueue[o.messageQueue.length - 1].sender).toBe("you");
|
|
2472
|
+
|
|
2473
|
+
// Remote surface broadcasts action to room mesh
|
|
2474
|
+
jest
|
|
2475
|
+
.spyOn(o.roomManager, "getSurfaceOwner")
|
|
2476
|
+
.mockReturnValue("peer-remote-99");
|
|
2477
|
+
const broadcastSpy = jest
|
|
2478
|
+
.spyOn(o.roomManager, "broadcastA2UIAction")
|
|
2479
|
+
.mockImplementation(() => null);
|
|
2480
|
+
|
|
2481
|
+
await o.routeRoomA2UIAction("room:room-remote", action);
|
|
2482
|
+
expect(broadcastSpy).toHaveBeenCalledWith("room-remote", action);
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
it("refreshes context usage across group pinned models, prompt_api fallbacks, and tokenUsage blending", async () => {
|
|
2486
|
+
const o = new Orchestrator();
|
|
2487
|
+
const db = await openDatabase();
|
|
2488
|
+
o.db = db;
|
|
2489
|
+
|
|
2490
|
+
const contextSpy = jest.fn();
|
|
2491
|
+
o.events.on("context-usage", contextSpy);
|
|
2492
|
+
|
|
2493
|
+
// Groups with pinnedProvider, pinnedModel, pinnedMaxTokens, and prompt_api
|
|
2494
|
+
await saveGroupMetadata(db, [
|
|
2495
|
+
{
|
|
2496
|
+
groupId: "group-pinned-model",
|
|
2497
|
+
name: "Pinned Group",
|
|
2498
|
+
createdAt: Date.now(),
|
|
2499
|
+
pinnedProvider: "openrouter",
|
|
2500
|
+
pinnedModel: "anthropic/claude-3-5-sonnet",
|
|
2501
|
+
pinnedMaxTokens: 4096,
|
|
2502
|
+
},
|
|
2503
|
+
{
|
|
2504
|
+
groupId: "group-provider-default-model",
|
|
2505
|
+
name: "Provider Default Model",
|
|
2506
|
+
createdAt: Date.now(),
|
|
2507
|
+
pinnedProvider: "anthropic",
|
|
2508
|
+
},
|
|
2509
|
+
{
|
|
2510
|
+
groupId: "group-unknown-provider",
|
|
2511
|
+
name: "Unknown Provider",
|
|
2512
|
+
createdAt: Date.now(),
|
|
2513
|
+
pinnedProvider: "unknown_xyz_provider",
|
|
2514
|
+
},
|
|
2515
|
+
{
|
|
2516
|
+
groupId: "group-prompt-api",
|
|
2517
|
+
name: "Prompt API Group",
|
|
2518
|
+
createdAt: Date.now(),
|
|
2519
|
+
pinnedProvider: "prompt_api",
|
|
2520
|
+
pinnedModel: "browser-built-in",
|
|
2521
|
+
},
|
|
2522
|
+
{
|
|
2523
|
+
groupId: "group-prompt-api-default",
|
|
2524
|
+
name: "Prompt API Default Fallback",
|
|
2525
|
+
createdAt: Date.now(),
|
|
2526
|
+
pinnedProvider: "prompt_api",
|
|
2527
|
+
pinnedModel: "",
|
|
2528
|
+
},
|
|
2529
|
+
]);
|
|
2530
|
+
|
|
2531
|
+
// Token usage blending via orchestratorStore
|
|
2532
|
+
const usageSpy = jest
|
|
2533
|
+
.spyOn(orchestratorStore, "tokenUsage", "get")
|
|
2534
|
+
.mockReturnValue({
|
|
2535
|
+
inputTokens: 600,
|
|
2536
|
+
cacheReadTokens: 150,
|
|
2537
|
+
outputTokens: 250,
|
|
2538
|
+
totalTokens: 1000,
|
|
2539
|
+
cacheCreationTokens: 0,
|
|
2540
|
+
contextLimit: 4096,
|
|
2541
|
+
groupId: "group-pinned-model",
|
|
2542
|
+
});
|
|
2543
|
+
|
|
2544
|
+
await o.refreshContextUsage(db, "group-pinned-model");
|
|
2545
|
+
expect(contextSpy).toHaveBeenCalled();
|
|
2546
|
+
usageSpy.mockRestore();
|
|
2547
|
+
|
|
2548
|
+
// Token usage blending with falsy/zero values
|
|
2549
|
+
const usageZeroSpy = jest
|
|
2550
|
+
.spyOn(orchestratorStore, "tokenUsage", "get")
|
|
2551
|
+
.mockReturnValue({
|
|
2552
|
+
inputTokens: 0,
|
|
2553
|
+
cacheReadTokens: 0,
|
|
2554
|
+
outputTokens: 0,
|
|
2555
|
+
totalTokens: 0,
|
|
2556
|
+
cacheCreationTokens: 0,
|
|
2557
|
+
contextLimit: 4096,
|
|
2558
|
+
groupId: "group-provider-default-model",
|
|
2559
|
+
});
|
|
2560
|
+
|
|
2561
|
+
await o.refreshContextUsage(db, "group-provider-default-model");
|
|
2562
|
+
expect(contextSpy).toHaveBeenCalled();
|
|
2563
|
+
usageZeroSpy.mockRestore();
|
|
2564
|
+
|
|
2565
|
+
// Provider default model lookup when pinnedProvider has no pinnedModel
|
|
2566
|
+
await o.refreshContextUsage(db, "group-provider-default-model");
|
|
2567
|
+
// Unknown provider fallback to this.model
|
|
2568
|
+
await o.refreshContextUsage(db, "group-unknown-provider");
|
|
2569
|
+
|
|
2570
|
+
// Active tools undefined fallback to 0 tokens
|
|
2571
|
+
const toolsSpy = jest
|
|
2572
|
+
.spyOn(toolsStore, "enabledTools", "get")
|
|
2573
|
+
.mockReturnValue(undefined as unknown as ToolDefinition[]);
|
|
2574
|
+
await o.refreshContextUsage(db, "group-provider-default-model");
|
|
2575
|
+
toolsSpy.mockRestore();
|
|
2576
|
+
|
|
2577
|
+
// Default groupId argument
|
|
2578
|
+
await o.refreshContextUsage(db);
|
|
2579
|
+
|
|
2580
|
+
const savedLm = (globalThis as Record<string, unknown>).LanguageModel;
|
|
2581
|
+
const savedWinLm = (window as unknown as Record<string, unknown>)
|
|
2582
|
+
.LanguageModel;
|
|
2583
|
+
(globalThis as Record<string, unknown>).LanguageModel = undefined;
|
|
2584
|
+
(window as unknown as Record<string, unknown>).LanguageModel = undefined;
|
|
2585
|
+
|
|
2586
|
+
// Configured fallback model for prompt_api
|
|
2587
|
+
await setConfig(
|
|
2588
|
+
db,
|
|
2589
|
+
CONFIG_KEYS.PROMPT_API_FALLBACK_MODEL,
|
|
2590
|
+
"custom-prompt-fallback",
|
|
2591
|
+
);
|
|
2592
|
+
await o.refreshContextUsage(db, "group-prompt-api");
|
|
2593
|
+
expect(contextSpy).toHaveBeenCalled();
|
|
2594
|
+
|
|
2595
|
+
// Default fallback model for prompt_api when configuredFallback is empty
|
|
2596
|
+
await setConfig(db, CONFIG_KEYS.PROMPT_API_FALLBACK_MODEL, "");
|
|
2597
|
+
await o.refreshContextUsage(db, "group-prompt-api-default");
|
|
2598
|
+
expect(contextSpy).toHaveBeenCalled();
|
|
2599
|
+
|
|
2600
|
+
// Prompt API fallback when this.provider === "prompt_api" and group has no pinned provider
|
|
2601
|
+
o.provider = "prompt_api";
|
|
2602
|
+
o.model = "";
|
|
2603
|
+
await o.refreshContextUsage(db, "group-unknown");
|
|
2604
|
+
expect(contextSpy).toHaveBeenCalled();
|
|
2605
|
+
|
|
2606
|
+
if (savedLm !== undefined) {
|
|
2607
|
+
(globalThis as Record<string, unknown>).LanguageModel = savedLm;
|
|
2608
|
+
}
|
|
2609
|
+
if (savedWinLm !== undefined) {
|
|
2610
|
+
(window as unknown as Record<string, unknown>).LanguageModel =
|
|
2611
|
+
savedWinLm;
|
|
2612
|
+
}
|
|
2613
|
+
});
|
|
2614
|
+
|
|
2615
|
+
it("handles saveSecretConfig encryption failure", async () => {
|
|
2616
|
+
const o = new Orchestrator();
|
|
2617
|
+
const db = await openDatabase();
|
|
2618
|
+
indexedDB.deleteDatabase("shadowclaw-keystore");
|
|
2619
|
+
mockSubtle.generateKey.mockResolvedValue(null as unknown as CryptoKey);
|
|
2620
|
+
await expect(
|
|
2621
|
+
o.saveSecretConfig(db, "secret-key", "secret-value"),
|
|
2622
|
+
).rejects.toThrow("Failed to encrypt secret config");
|
|
2623
|
+
mockSubtle.generateKey.mockResolvedValue({ type: "secret" });
|
|
2624
|
+
await resetKeystore();
|
|
2625
|
+
});
|
|
2626
|
+
});
|
|
1946
2627
|
});
|