@vantran-se/openchamber-web 1.23.0 → 1.24.3
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/bin/cli.test.js +29 -6
- package/bin/lib/cli-http.js +5 -1
- package/bin/lib/commands-status.js +5 -0
- package/dist/assets/{CodeMirrorCommentWidgets-DFg2wnpo.js → CodeMirrorCommentWidgets-BjJHrzmU.js} +1 -1
- package/dist/assets/DiffView-COVQhWGd.js +4 -0
- package/dist/assets/FilesView-B1V5JxFX.js +13 -0
- package/dist/assets/GitView-CDOJy5Ay.js +6 -0
- package/dist/assets/GuestAttachDialog-D3S0Rjic.js +1 -0
- package/dist/assets/{JsonTreeView-BKdsjg5B.js → JsonTreeView-D0C9nU7K.js} +1 -1
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/LinearIssuesView-D7FKg1XJ.js +1 -0
- package/dist/assets/{MarkdownImageGallery-_5gEVr0X.js → MarkdownImageGallery-BrFjAVEf.js} +1 -1
- package/dist/assets/MarkdownRendererImpl-DWepTafh.js +12 -0
- package/dist/assets/{OnboardingScreen-DdP4_nuc.js → OnboardingScreen-Dyxxiqdt.js} +1 -1
- package/dist/assets/{PatchDiff-am3SAss2.js → PatchDiff-DnEhjXcC.js} +1 -1
- package/dist/assets/PierreDiffViewer-DyxZVVVR.js +210 -0
- package/dist/assets/{PlanView-BoJxdRZe.js → PlanView-LW2z_EAP.js} +4 -4
- package/dist/assets/PluginPane-B4tKno2h.js +3 -0
- package/dist/assets/SettingsView-DEaMulN9.js +29 -0
- package/dist/assets/{SettingsWindow-DVTdDSol.js → SettingsWindow-FLDJXOgQ.js} +1 -1
- package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +1 -0
- package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +15 -0
- package/dist/assets/{ToolPartDiffPreview-C7WLk5i9.js → ToolPartDiffPreview-CkR4msKP.js} +2 -2
- package/dist/assets/{VirtualizedFileDiff-DcdgkH-x.js → VirtualizedFileDiff-0pyr_mHY.js} +4 -4
- package/dist/assets/WalkthroughView-yNj4nOP5.js +4 -0
- package/dist/assets/appThemeRegistry-BuUKhbYQ.js +1 -0
- package/dist/assets/commitAndPush-DvJKa3Ks.js +1 -0
- package/dist/assets/{computeVirtualFileMetrics-C8byMnRh.js → computeVirtualFileMetrics-DxTBMuu-.js} +2 -2
- package/dist/assets/de-DgxgFyGG.js +10 -0
- package/dist/assets/es-BFsZj5F_.js +16 -0
- package/dist/assets/fr-jdZ5KKQi.js +14 -0
- package/dist/assets/{index-BB7ryz8y.js → index-CKHjPKbq.js} +1 -1
- package/dist/assets/index-Cbci4i1W.js +40 -0
- package/dist/assets/index-DqaMWNrC.css +1 -0
- package/dist/assets/{isDiffPlainText-45ujMPGb.js → isDiffPlainText-D7FkBMP6.js} +1 -1
- package/dist/assets/{isFilePlainText-Cc4KFSYi.js → isFilePlainText-CL2vsrIZ.js} +1 -1
- package/dist/assets/ja-C5kKrhO4.js +16 -0
- package/dist/assets/ko-DXDTT_0O.js +16 -0
- package/dist/assets/main-CZALtVek.js +896 -0
- package/dist/assets/main-CgXMBNvG.js +2 -0
- package/dist/assets/{markdown-shiki.worker-D5fBU-lw.js → markdown-shiki.worker-CahLvHIq.js} +5 -5
- package/dist/assets/markdownCore-Dh7WEsdM.js +5 -0
- package/dist/assets/miniChat-CwTmDixi.js +2 -0
- package/dist/assets/mobile-CLf2vCtO.js +2 -0
- package/dist/assets/patchFileDiff-Bsba8KFp.js +13 -0
- package/dist/assets/pl-D-7x4Y8Q.js +16 -0
- package/dist/assets/pt-BR-CSBZrHa8.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +2 -0
- package/dist/assets/renderMobileApp-V4hYd32H.js +25 -0
- package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +25 -0
- package/dist/assets/scheduledTasksApi-DzjbLd8P.js +1 -0
- package/dist/assets/shikiHighlight-8_3csmr6.js +1 -0
- package/dist/assets/surface-B6n7wJtt.js +1 -0
- package/dist/assets/textMateThemeFromAppTheme-CHo2fCgW.js +1 -0
- package/dist/assets/tr-Dr-oJ22L.js +7 -0
- package/dist/assets/uk-9Nx83UcI.js +16 -0
- package/dist/assets/useAppFontEffects-khZXpFL_.js +7333 -0
- package/dist/assets/useInlineCommentController-DkQ_gqpn.js +2 -0
- package/dist/assets/useRootScrollLock-DdvHf9Pt.js +2 -0
- package/dist/assets/{vendor-base-ui-DdV8vhHn.js → vendor-base-ui-CsXYnR5c.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DzlSIu_W.js → vendor-codemirror-language-data-CdzUmUyZ.js} +2 -2
- package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +1 -0
- package/dist/assets/vendor-katex-Cp579UoJ.css +1 -0
- package/dist/assets/{vendor-motion-dom-D_o3BTI3.js → vendor-motion-dom-DcCgeG69.js} +1 -1
- package/dist/assets/{vendor-remixicon-react-Dil0zKKT.js → vendor-remixicon-react-BDmGmFCA.js} +1 -1
- package/dist/assets/vendor-strip-json-comments-5dJlOkP1.js +4 -0
- package/dist/assets/{vendor-tanstack-react-virtual-g05Cx3z9.js → vendor-tanstack-react-virtual-ClYN8CzB.js} +1 -1
- package/dist/assets/{vendor-tanstack-virtual-core-Dubg10g5.js → vendor-tanstack-virtual-core-BewVQryB.js} +1 -1
- package/dist/assets/vendor-zod-CgAdXWZO.js +39 -0
- package/dist/assets/{web-components-CdGfR2XJ.js → web-components-B8IcVQAA.js} +2 -2
- package/dist/assets/zh-CN-Bxy_Hj3r.js +16 -0
- package/dist/assets/zh-TW-0BB4gpHM.js +16 -0
- package/dist/index.html +6 -4
- package/dist/mini-chat.html +6 -4
- package/dist/mobile.html +6 -4
- package/package.json +12 -9
- package/server/built-in-extensions/registry.json +4 -0
- package/server/index.d.ts +2 -0
- package/server/index.js +47 -23
- package/server/lib/agent-memory/runtime.js +7 -2
- package/server/lib/agent-memory/runtime.test.js +22 -0
- package/server/lib/agent-tool/DOCUMENTATION.md +17 -4
- package/server/lib/agent-tool/runtime.js +62 -15
- package/server/lib/agent-tool/runtime.test.js +88 -2
- package/server/lib/changelog/update-notes.js +2 -1
- package/server/lib/changelog/update-notes.test.js +19 -0
- package/server/lib/event-stream/DOCUMENTATION.md +26 -0
- package/server/lib/event-stream/delta-coalescer.js +0 -0
- package/server/lib/event-stream/delta-coalescer.test.js +292 -0
- package/server/lib/event-stream/global-hub.js +68 -13
- package/server/lib/event-stream/global-hub.test.js +182 -0
- package/server/lib/event-stream/global-ws-bridge.js +16 -18
- package/server/lib/event-stream/index.js +4 -0
- package/server/lib/event-stream/protocol.js +21 -7
- package/server/lib/event-stream/runtime.js +9 -6
- package/server/lib/event-stream/runtime.test.js +60 -0
- package/server/lib/fs/DOCUMENTATION.md +10 -1
- package/server/lib/fs/git-process.test.js +77 -0
- package/server/lib/fs/routes.js +58 -5
- package/server/lib/fs/routes.test.js +116 -0
- package/server/lib/fs/search.js +2 -1
- package/server/lib/git/DOCUMENTATION.md +30 -6
- package/server/lib/git/routes.js +37 -11
- package/server/lib/git/routes.test.js +78 -0
- package/server/lib/git/serial-refresh.js +93 -0
- package/server/lib/git/serial-refresh.test.js +112 -0
- package/server/lib/git/service.js +588 -74
- package/server/lib/git/service.test.js +492 -0
- package/server/lib/github/DOCUMENTATION.md +5 -0
- package/server/lib/github/pr-status.js +5 -5
- package/server/lib/github/pr-status.test.js +1 -1
- package/server/lib/github/routes.js +6 -6
- package/server/lib/guests/DOCUMENTATION.md +68 -0
- package/server/lib/guests/auth-store.js +184 -0
- package/server/lib/guests/auth-store.test.js +112 -0
- package/server/lib/guests/background.test.js +83 -0
- package/server/lib/guests/builtins.js +34 -0
- package/server/lib/guests/builtins.test.js +174 -0
- package/server/lib/guests/catalog.js +477 -0
- package/server/lib/guests/catalog.test.js +390 -0
- package/server/lib/guests/clone.js +297 -0
- package/server/lib/guests/extract-zip.js +136 -0
- package/server/lib/guests/extract-zip.test.js +109 -0
- package/server/lib/guests/files.js +308 -0
- package/server/lib/guests/files.test.js +262 -0
- package/server/lib/guests/grant-scope.js +81 -0
- package/server/lib/guests/grant-scope.test.js +75 -0
- package/server/lib/guests/host-session.js +73 -0
- package/server/lib/guests/host-session.test.js +30 -0
- package/server/lib/guests/html-styles.js +6 -0
- package/server/lib/guests/html-styles.test.js +45 -0
- package/server/lib/guests/html-tokens.js +73 -0
- package/server/lib/guests/html-tokens.test.js +53 -0
- package/server/lib/guests/install.js +406 -0
- package/server/lib/guests/install.test.js +338 -0
- package/server/lib/guests/oauth.js +501 -0
- package/server/lib/guests/oauth.test.js +481 -0
- package/server/lib/guests/persist.js +336 -0
- package/server/lib/guests/persist.test.js +288 -0
- package/server/lib/guests/request.js +112 -0
- package/server/lib/guests/request.test.js +206 -0
- package/server/lib/guests/routes.js +838 -0
- package/server/lib/guests/service.js +538 -0
- package/server/lib/guests/service.test.js +277 -0
- package/server/lib/guests/sockets.js +129 -0
- package/server/lib/guests/sockets.test.js +110 -0
- package/server/lib/guests/ssh-install.test.js +188 -0
- package/server/lib/guests/storage.js +68 -0
- package/server/lib/guests/storage.test.js +59 -0
- package/server/lib/guests/updates.js +294 -0
- package/server/lib/guests/updates.test.js +334 -0
- package/server/lib/guests/upload.js +50 -0
- package/server/lib/guests/upload.test.js +131 -0
- package/server/lib/linear/DOCUMENTATION.md +1 -1
- package/server/lib/message-queue/DOCUMENTATION.md +17 -1
- package/server/lib/message-queue/runtime.js +34 -1
- package/server/lib/message-queue/runtime.test.js +83 -6
- package/server/lib/notifications/DOCUMENTATION.md +1 -1
- package/server/lib/notifications/emitter-runtime.js +4 -3
- package/server/lib/notifications/routes.js +5 -0
- package/server/lib/openchamber-control/DOCUMENTATION.md +6 -0
- package/server/lib/openchamber-sessions/routes.js +62 -29
- package/server/lib/openchamber-sessions/routes.test.js +96 -0
- package/server/lib/opencode/DOCUMENTATION.md +73 -5
- package/server/lib/opencode/bootstrap-runtime.js +1 -0
- package/server/lib/opencode/core-routes.js +39 -1
- package/server/lib/opencode/env-runtime.js +5 -1
- package/server/lib/opencode/env-runtime.test.js +72 -0
- package/server/lib/opencode/feature-routes-runtime.js +30 -1
- package/server/lib/opencode/lifecycle-process.test.js +84 -0
- package/server/lib/opencode/lifecycle.js +58 -70
- package/server/lib/opencode/lifecycle.test.js +29 -0
- package/server/lib/opencode/managed-plugin-config.js +1 -1
- package/server/lib/opencode/openchamber-routes.js +72 -14
- package/server/lib/opencode/openchamber-routes.test.js +71 -3
- package/server/lib/opencode/proxy.js +33 -0
- package/server/lib/opencode/session-runtime.js +68 -0
- package/server/lib/opencode/session-runtime.test.js +43 -0
- package/server/lib/opencode/settings-helpers.js +11 -0
- package/server/lib/opencode/settings-helpers.test.js +24 -2
- package/server/lib/opencode/settings-normalization-runtime.js +2 -0
- package/server/lib/opencode/settings-normalization-runtime.test.js +13 -0
- package/server/lib/opencode/settings-registry.json +18 -0
- package/server/lib/opencode/settings-runtime.js +36 -9
- package/server/lib/opencode/settings-runtime.test.js +158 -2
- package/server/lib/opencode/shutdown-runtime.js +3 -0
- package/server/lib/opencode/shutdown-runtime.test.js +24 -0
- package/server/lib/opencode/theme-archive.js +53 -0
- package/server/lib/opencode/theme-catalog.js +171 -0
- package/server/lib/opencode/theme-catalog.test.js +125 -0
- package/server/lib/opencode/theme-import.test.js +144 -0
- package/server/lib/opencode/theme-runtime.js +100 -28
- package/server/lib/opencode/theme-runtime.test.js +15 -0
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +8 -0
- package/server/lib/permission-auto-accept/runtime.js +13 -0
- package/server/lib/permission-auto-accept/runtime.test.js +37 -1
- package/server/lib/project-context/DOCUMENTATION.md +13 -3
- package/server/lib/project-context/runtime.js +8 -2
- package/server/lib/project-context/runtime.test.js +54 -0
- package/server/lib/projects/DOCUMENTATION.md +2 -1
- package/server/lib/projects/project-config.js +40 -11
- package/server/lib/projects/project-config.test.js +97 -0
- package/server/lib/projects/project-id.js +26 -0
- package/server/lib/quota/DOCUMENTATION.md +0 -1
- package/server/lib/quota/providers/index.js +0 -7
- package/server/lib/realtime-proxy-terminal.test.js +150 -0
- package/server/lib/realtime-proxy.js +2 -0
- package/server/lib/relay/DOCUMENTATION.md +20 -0
- package/server/lib/relay/host-client.js +7 -0
- package/server/lib/relay/host-client.test.js +14 -0
- package/server/lib/routing/DOCUMENTATION.md +83 -0
- package/server/lib/routing/defaults.js +98 -0
- package/server/lib/routing/feature-flag.js +11 -0
- package/server/lib/routing/history.js +34 -0
- package/server/lib/routing/jev.js +98 -0
- package/server/lib/routing/routes.http.test.js +100 -0
- package/server/lib/routing/routes.js +81 -0
- package/server/lib/routing/runtime.js +217 -0
- package/server/lib/routing/runtime.test.js +188 -0
- package/server/lib/routing/store.js +249 -0
- package/server/lib/routing/store.test.js +96 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +9 -1
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +28 -0
- package/server/lib/scheduled-tasks/runtime.js +14 -4
- package/server/lib/scheduled-tasks/runtime.test.js +67 -0
- package/server/lib/security/request-security.js +9 -5
- package/server/lib/security/request-security.test.js +37 -0
- package/server/lib/session-assist/DOCUMENTATION.md +131 -66
- package/server/lib/session-assist/context.js +127 -0
- package/server/lib/session-assist/context.test.js +132 -0
- package/server/lib/session-assist/prompt.js +54 -0
- package/server/lib/session-assist/runtime.js +111 -257
- package/server/lib/session-assist/runtime.test.js +203 -0
- package/server/lib/small-model/DOCUMENTATION.md +14 -2
- package/server/lib/small-model/call.js +37 -6
- package/server/lib/small-model/call.test.js +182 -0
- package/server/lib/terminal/DOCUMENTATION.md +23 -2
- package/server/lib/terminal/runtime.js +27 -5
- package/server/lib/terminal/runtime.test.js +67 -0
- package/server/lib/terminal/shutdown.js +106 -0
- package/server/lib/terminal/shutdown.test.js +84 -0
- package/server/lib/tts/service.test.js +78 -0
- package/server/lib/ui-auth/DOCUMENTATION.md +8 -0
- package/server/lib/ui-auth/session-cookie.js +51 -0
- package/server/lib/ui-auth/session-cookie.test.js +46 -0
- package/server/lib/ui-auth/ui-auth.js +56 -17
- package/server/lib/ui-auth/ui-auth.test.js +96 -0
- package/server/lib/walkthrough/DOCUMENTATION.md +15 -5
- package/server/lib/walkthrough/pr-source.test.js +21 -0
- package/server/lib/walkthrough/pull-request.js +7 -4
- package/server/lib/walkthrough/pull-request.test.js +16 -0
- package/server/lib/walkthrough/routes.js +19 -0
- package/server/lib/walkthrough/routes.test.js +26 -0
- package/server/lib/walkthrough/sources.js +13 -2
- package/server/opencode-worktree-readiness.test.js +130 -0
- package/dist/assets/DiffView-3Dn4QHbu.js +0 -4
- package/dist/assets/DirtyBranchSwitchDialog-CwOerqVi.js +0 -1
- package/dist/assets/FilesView-BdTOpew9.js +0 -17
- package/dist/assets/GitView-Dqf9vHJh.js +0 -6
- package/dist/assets/LinearIssuesView-CmbeL7I6.js +0 -1
- package/dist/assets/MarkdownRendererImpl-wVZlx4dd.js +0 -12
- package/dist/assets/PierreDiffViewer-DCsydlKs.js +0 -161
- package/dist/assets/SettingsView-CcM8pF5O.js +0 -28
- package/dist/assets/ToolOutputDialog-BgfTbDZ4.js +0 -15
- package/dist/assets/WalkthroughView-BEVUDxuw.js +0 -4
- package/dist/assets/appThemeRegistry-BK0dHKa6.js +0 -1
- package/dist/assets/de-C8jV7J9y.js +0 -10
- package/dist/assets/es-BI6J1M5R.js +0 -16
- package/dist/assets/fr-D6oDFt7E.js +0 -14
- package/dist/assets/index-BqhiCmYS.js +0 -39
- package/dist/assets/index-FqSOBz0J.css +0 -1
- package/dist/assets/ja-C_4i5EQu.js +0 -16
- package/dist/assets/ko-BFbOTpbq.js +0 -16
- package/dist/assets/main-C9ETLKFc.js +0 -2
- package/dist/assets/main-CJmb2dF8.js +0 -930
- package/dist/assets/markdownCore-DVuZpnli.js +0 -5
- package/dist/assets/miniChat-B4Ytoq2n.js +0 -2
- package/dist/assets/mobile-BUT6-lo1.js +0 -2
- package/dist/assets/patchFileDiff-8j1apICU.js +0 -13
- package/dist/assets/pl-COFvT56l.js +0 -16
- package/dist/assets/pt-BR-DtVOO2Lj.js +0 -16
- package/dist/assets/registerCustomTheme-BBXUCRnG.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-B6vUG-vF.js +0 -2
- package/dist/assets/renderMobileApp-BNgHraq6.js +0 -25
- package/dist/assets/runtimeEndpointReset-B8LYpWvr.js +0 -17
- package/dist/assets/scheduledTasksApi-GC4epIYA.js +0 -1
- package/dist/assets/shikiHighlight-BJISeZsQ.js +0 -1
- package/dist/assets/surface-BvRzS58U.js +0 -1
- package/dist/assets/tr-B8Q0Go2l.js +0 -7
- package/dist/assets/uk-Dk4a-Fn-.js +0 -16
- package/dist/assets/useAppFontEffects-DpFNefUA.js +0 -7284
- package/dist/assets/useGitComparison-Dfxaqd0s.js +0 -1
- package/dist/assets/useInlineCommentController-CscsE4t-.js +0 -2
- package/dist/assets/useRootScrollLock-BaCvuRlT.js +0 -1
- package/dist/assets/vendor-framer-motion-livSKyyV.js +0 -1
- package/dist/assets/vendor-zod-BPAANqvH.js +0 -39
- package/dist/assets/zh-CN-B_BXxG1Q.js +0 -16
- package/dist/assets/zh-TW-DLrl1SPZ.js +0 -16
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +0 -55
- package/server/lib/mcp-reconnect/runtime.js +0 -122
- package/server/lib/mcp-reconnect/runtime.test.js +0 -140
- package/server/lib/quota/providers/crof.js +0 -96
- package/server/lib/quota/providers/crof.test.js +0 -98
- /package/dist/assets/{vendor-katex-k3Ppq1Ha.js → vendor-katex-CHD9BdDO.js} +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createRoutingStore, parseEffectiveConfig, resolveEffectiveConfig, toStoredConfig } from './store.js';
|
|
6
|
+
import { BUILTIN_CATEGORIES } from './defaults.js';
|
|
7
|
+
|
|
8
|
+
const tempDir = () => fs.mkdtemp(path.join(os.tmpdir(), 'routing-store-'));
|
|
9
|
+
|
|
10
|
+
const sampleConfig = (patch = {}) => ({
|
|
11
|
+
enabled: true,
|
|
12
|
+
fallback: { model: { providerID: 'anthropic', modelID: 'claude-sonnet-5' }, variant: 'medium' },
|
|
13
|
+
minConfidence: 0.6,
|
|
14
|
+
safetyNet: { enabled: true, threshold: 0.6 },
|
|
15
|
+
categories: resolveEffectiveConfig(null).categories,
|
|
16
|
+
...patch,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('routing store', () => {
|
|
20
|
+
it('resolves the defaults when nothing is stored', async () => {
|
|
21
|
+
const store = createRoutingStore({ dataDir: await tempDir() });
|
|
22
|
+
const config = await store.readConfig();
|
|
23
|
+
expect(config.enabled).toBe(false);
|
|
24
|
+
expect(config.fallback).toBeNull();
|
|
25
|
+
expect(config.categories.map((c) => c.id)).toEqual(BUILTIN_CATEGORIES.map((c) => c.id));
|
|
26
|
+
expect(config.categories.every((c) => c.builtin && c.enabled && c.model === null)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('stores only deviations from the built-ins and round-trips them', async () => {
|
|
30
|
+
const dir = await tempDir();
|
|
31
|
+
const store = createRoutingStore({ dataDir: dir });
|
|
32
|
+
const config = sampleConfig();
|
|
33
|
+
config.categories[0] = { ...config.categories[0], enabled: false };
|
|
34
|
+
config.categories[3] = { ...config.categories[3], model: { providerID: 'openai', modelID: 'gpt-6-astra' }, variant: 'high', agent: 'plan' };
|
|
35
|
+
config.categories.push({ id: 'my-refactors', builtin: false, enabled: true, name: 'My refactors', description: 'Refactors across modules.', model: null, variant: null, agent: null });
|
|
36
|
+
await store.writeConfig(config);
|
|
37
|
+
|
|
38
|
+
const stored = JSON.parse(await fs.readFile(path.join(dir, 'routing.json'), 'utf8'));
|
|
39
|
+
expect(stored.categories).toEqual({
|
|
40
|
+
trivial: { builtin: true, disabled: true },
|
|
41
|
+
hard: { builtin: true, model: { providerID: 'openai', modelID: 'gpt-6-astra' }, variant: 'high', agent: 'plan' },
|
|
42
|
+
'my-refactors': { builtin: false, name: 'My refactors', description: 'Refactors across modules.' },
|
|
43
|
+
});
|
|
44
|
+
expect(await store.readConfig()).toEqual(config);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('records a removed built-in as deleted and keeps the others', async () => {
|
|
48
|
+
const config = sampleConfig();
|
|
49
|
+
config.categories = config.categories.filter((c) => c.id !== 'research');
|
|
50
|
+
const stored = toStoredConfig(config);
|
|
51
|
+
expect(stored.categories.research).toEqual({ builtin: true, deleted: true });
|
|
52
|
+
const resolved = resolveEffectiveConfig(stored);
|
|
53
|
+
expect(resolved.categories.map((c) => c.id)).toEqual(['trivial', 'implement', 'hard']);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('applies later built-in text to an untouched category but keeps an edited one', () => {
|
|
57
|
+
const edited = resolveEffectiveConfig({ version: 1, categories: { hard: { builtin: true, description: 'My own hard.' } } });
|
|
58
|
+
expect(edited.categories.find((c) => c.id === 'hard').description).toBe('My own hard.');
|
|
59
|
+
expect(edited.categories.find((c) => c.id === 'trivial').description).toBe(BUILTIN_CATEGORIES[0].description);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('rejects a malformed file instead of treating it as empty', async () => {
|
|
63
|
+
const dir = await tempDir();
|
|
64
|
+
await fs.writeFile(path.join(dir, 'routing.json'), '{"version":1,"categories":{"x":{"builtin":false}}}');
|
|
65
|
+
const store = createRoutingStore({ dataDir: dir });
|
|
66
|
+
await expect(store.readConfig()).rejects.toThrow(/Invalid routing.json/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('rejects an invalid PUT body with a 400 status', () => {
|
|
70
|
+
expect(() => parseEffectiveConfig({ enabled: true })).toThrow(expect.objectContaining({ status: 400 }));
|
|
71
|
+
const duplicate = sampleConfig();
|
|
72
|
+
duplicate.categories = [...duplicate.categories, { ...duplicate.categories[0], builtin: false }];
|
|
73
|
+
expect(() => parseEffectiveConfig(duplicate)).toThrow(/Duplicate category id/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('refuses the Auto sentinel as a fallback or category model', () => {
|
|
77
|
+
const asFallback = sampleConfig({ fallback: { model: { providerID: 'openchamber', modelID: 'auto' }, variant: null } });
|
|
78
|
+
expect(() => parseEffectiveConfig(asFallback)).toThrow(/Auto model cannot be a routing target/);
|
|
79
|
+
const asCategory = sampleConfig();
|
|
80
|
+
asCategory.categories = asCategory.categories.map((c, i) => (i === 0 ? { ...c, model: { providerID: 'openchamber', modelID: 'auto' } } : c));
|
|
81
|
+
expect(() => parseEffectiveConfig(asCategory)).toThrow(/Auto model cannot be a routing target/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('keeps the token in its own 0600 file and clears it', async () => {
|
|
85
|
+
const dir = await tempDir();
|
|
86
|
+
const store = createRoutingStore({ dataDir: dir });
|
|
87
|
+
expect(await store.readToken()).toBeNull();
|
|
88
|
+
await store.writeToken('ts-secret');
|
|
89
|
+
expect(await store.readToken()).toBe('ts-secret');
|
|
90
|
+
const stat = await fs.stat(path.join(dir, 'routing-auth.json'));
|
|
91
|
+
if (process.platform !== 'win32') expect(stat.mode & 0o777).toBe(0o600);
|
|
92
|
+
expect(JSON.stringify(JSON.parse(await fs.readFile(path.join(dir, 'routing.json'), 'utf8').catch(() => '{}')))).not.toContain('ts-secret');
|
|
93
|
+
await store.clearToken();
|
|
94
|
+
expect(await store.readToken()).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -25,6 +25,12 @@ in shared project config under the project write lock:
|
|
|
25
25
|
from the winner's persisted `nextRunAt`.
|
|
26
26
|
- Project config writes also take a cross-process `.json.lock` file so the
|
|
27
27
|
read-modify-write is serialized across processes, not only within one process.
|
|
28
|
+
- `syncAllProjects` (startup and every full resync) syncs each registered
|
|
29
|
+
project on its own: a project whose config cannot be read or written (a
|
|
30
|
+
broken file, a lock timeout) is logged with its id and skipped, and every
|
|
31
|
+
other project's tasks are still scheduled. Only `listProjects` failing
|
|
32
|
+
aborts the sync as a whole. `syncProject` for one project still throws, so
|
|
33
|
+
a route for that project reports the failure.
|
|
28
34
|
- The sharing processes may run different OpenChamber versions. Normalization
|
|
29
35
|
keeps only the fields a build knows, so every writer persists tasks it did
|
|
30
36
|
not change verbatim from disk and swaps only `state` onto a task whose state
|
|
@@ -63,7 +69,9 @@ in shared project config under the project write lock:
|
|
|
63
69
|
- Claiming always writes `nextRunAt` (including `undefined`) so a past once-slot
|
|
64
70
|
is cleared when there is no following occurrence.
|
|
65
71
|
|
|
66
|
-
Manual `runNow` does not claim a schedule occurrence.
|
|
72
|
+
Manual `runNow` does not claim a schedule occurrence. It also runs paused
|
|
73
|
+
(`enabled: false`) tasks — that is the point of the button — while scheduled
|
|
74
|
+
dispatches still skip disabled tasks, and completion never re-arms a paused task.
|
|
67
75
|
|
|
68
76
|
## Files
|
|
69
77
|
|
|
@@ -485,4 +485,32 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
|
|
|
485
485
|
|
|
486
486
|
runtime.stop();
|
|
487
487
|
});
|
|
488
|
+
|
|
489
|
+
it('manual runNow runs a paused task but does not schedule future runs', async () => {
|
|
490
|
+
vi.setSystemTime(UTC(2026, 0, 1, 14, 0, 0));
|
|
491
|
+
const task = { ...makeTask({ kind: 'daily', times: ['15:00'] }), enabled: false };
|
|
492
|
+
const projectConfigRuntime = createSharedProjectConfigRuntime(task);
|
|
493
|
+
|
|
494
|
+
const runtime = createScheduledTasksRuntime(createRuntimeDeps(projectConfigRuntime));
|
|
495
|
+
await runtime.start();
|
|
496
|
+
|
|
497
|
+
// The scheduler must not fire a disabled task on its own.
|
|
498
|
+
await vi.advanceTimersByTimeAsync(HOUR + 3_000);
|
|
499
|
+
expect(sdk.sessionCreates.length).toBe(0);
|
|
500
|
+
|
|
501
|
+
const manual = await runtime.runNow('p1', 'task-1');
|
|
502
|
+
expect(manual.ok).toBe(true);
|
|
503
|
+
expect(manual.sessionID).toBeTruthy();
|
|
504
|
+
expect(sdk.sessionCreates.length).toBe(1);
|
|
505
|
+
expect(runtime.getStatus().runningScheduledTasksCount).toBe(0);
|
|
506
|
+
|
|
507
|
+
// Completion records state but leaves the task paused with no next run.
|
|
508
|
+
const tasks = await projectConfigRuntime.listScheduledTasks('p1');
|
|
509
|
+
expect(tasks[0].enabled).toBe(false);
|
|
510
|
+
expect(tasks[0].state.lastStatus).toBe('success');
|
|
511
|
+
expect(tasks[0].state.lastSessionId).toBe('sess-1');
|
|
512
|
+
expect(tasks[0].state.nextRunAt).toBeUndefined();
|
|
513
|
+
|
|
514
|
+
runtime.stop();
|
|
515
|
+
});
|
|
488
516
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createOpencodeClient } from '@opencode-ai/sdk/v2';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
|
-
import
|
|
3
|
+
import { CronExpressionParser } from 'cron-parser';
|
|
4
4
|
import { expandSnippets } from '../opencode/snippets.js';
|
|
5
5
|
import { buildGoalIntroText, createSessionGoal } from '../session-goal/create.js';
|
|
6
6
|
import { discoverLoops } from './loops.js';
|
|
@@ -216,7 +216,7 @@ export const computeNextRunAt = (task, nowMs = Date.now()) => {
|
|
|
216
216
|
|
|
217
217
|
if (schedule.kind === 'cron') {
|
|
218
218
|
try {
|
|
219
|
-
const iterator =
|
|
219
|
+
const iterator = CronExpressionParser.parse(schedule.cron, {
|
|
220
220
|
tz: zone,
|
|
221
221
|
currentDate: new Date(nowMs),
|
|
222
222
|
});
|
|
@@ -426,7 +426,16 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
for (const projectID of activeProjectIDs) {
|
|
429
|
-
|
|
429
|
+
try {
|
|
430
|
+
await syncProject(projectID);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
// One project's config being unusable (a broken file, a lock timeout)
|
|
433
|
+
// must not keep every other project's tasks from being scheduled.
|
|
434
|
+
logger.warn?.('[ScheduledTasks] failed to sync project', {
|
|
435
|
+
projectID,
|
|
436
|
+
error: error?.message ?? String(error),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
430
439
|
}
|
|
431
440
|
};
|
|
432
441
|
|
|
@@ -676,7 +685,8 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
676
685
|
const runTask = async (projectID, taskID, reason, scheduledFor) => {
|
|
677
686
|
const taskMap = tasksByProject.get(projectID);
|
|
678
687
|
const task = taskMap?.get(taskID);
|
|
679
|
-
|
|
688
|
+
// Manual runNow runs paused tasks too; only scheduled dispatches skip them.
|
|
689
|
+
if (!task || (reason !== 'manual' && !task.enabled)) {
|
|
680
690
|
return { ok: false, skipped: true };
|
|
681
691
|
}
|
|
682
692
|
|
|
@@ -12,6 +12,20 @@ import {
|
|
|
12
12
|
import { createProjectConfigRuntime } from '../projects/project-config.js';
|
|
13
13
|
|
|
14
14
|
describe('scheduled-tasks runtime helpers', () => {
|
|
15
|
+
it.each([
|
|
16
|
+
['*/15 * * * *', 'UTC', '2026-09-18T08:07:00Z', '2026-09-18T08:15:00Z'],
|
|
17
|
+
['30 */5 * * * *', 'UTC', '2026-09-18T08:07:00Z', '2026-09-18T08:10:30Z'],
|
|
18
|
+
['0 9 * * MON-FRI', 'Europe/Kyiv', '2026-09-18T07:00:00Z', '2026-09-21T06:00:00Z'],
|
|
19
|
+
['0 9 * * *', 'Europe/Kyiv', '2026-03-28T08:00:00Z', '2026-03-29T06:00:00Z'],
|
|
20
|
+
['0 9 * * *', 'Europe/Kyiv', '2026-10-24T08:00:00Z', '2026-10-25T07:00:00Z'],
|
|
21
|
+
['0 0 L * *', 'UTC', '2026-02-01T00:00:00Z', '2026-02-28T00:00:00Z'],
|
|
22
|
+
])('computes cron %s in %s from %s', (cron, timezone, now, expected) => {
|
|
23
|
+
expect(computeNextRunAt({
|
|
24
|
+
enabled: true,
|
|
25
|
+
schedule: { kind: 'cron', cron, timezone },
|
|
26
|
+
}, Date.parse(now))).toBe(Date.parse(expected));
|
|
27
|
+
});
|
|
28
|
+
|
|
15
29
|
it('computes next daily run in timezone', () => {
|
|
16
30
|
const nowUtc = Date.UTC(2025, 0, 1, 8, 0, 0);
|
|
17
31
|
const next = computeNextRunAt({
|
|
@@ -206,3 +220,56 @@ Run daily.
|
|
|
206
220
|
}
|
|
207
221
|
});
|
|
208
222
|
});
|
|
223
|
+
|
|
224
|
+
describe('scheduled-tasks runtime syncAllProjects', () => {
|
|
225
|
+
it('keeps scheduling the other projects when one project cannot be synced', async () => {
|
|
226
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'oc-runtime-sync-all-'));
|
|
227
|
+
try {
|
|
228
|
+
const brokenPath = path.join(tempRoot, 'broken');
|
|
229
|
+
const healthyPath = path.join(tempRoot, 'healthy');
|
|
230
|
+
await mkdir(path.join(healthyPath, '.agents', 'loops'), { recursive: true });
|
|
231
|
+
await mkdir(brokenPath, { recursive: true });
|
|
232
|
+
await writeFile(path.join(healthyPath, '.agents', 'loops', 'daily.md'), `---
|
|
233
|
+
name: daily
|
|
234
|
+
schedule: "0 9 * * *"
|
|
235
|
+
enabled: true
|
|
236
|
+
model: openai/gpt-5
|
|
237
|
+
---
|
|
238
|
+
Run daily.
|
|
239
|
+
`, 'utf8');
|
|
240
|
+
|
|
241
|
+
const projectConfigRuntime = createProjectConfigRuntime({
|
|
242
|
+
fsPromises: await import('fs/promises'),
|
|
243
|
+
path,
|
|
244
|
+
projectsDirPath: path.join(tempRoot, 'config'),
|
|
245
|
+
createTaskID: () => 'task-fixed-id',
|
|
246
|
+
});
|
|
247
|
+
await mkdir(path.join(tempRoot, 'config'), { recursive: true });
|
|
248
|
+
await writeFile(projectConfigRuntime.resolveProjectConfigPath('broken'), '{ not json', 'utf8');
|
|
249
|
+
|
|
250
|
+
const warnings = [];
|
|
251
|
+
const runtime = createScheduledTasksRuntime({
|
|
252
|
+
buildOpenCodeUrl: () => 'http://localhost',
|
|
253
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
254
|
+
waitForOpenCodeReady: async () => {},
|
|
255
|
+
projectConfigRuntime,
|
|
256
|
+
listProjects: async () => [
|
|
257
|
+
{ id: 'broken', path: brokenPath },
|
|
258
|
+
{ id: 'healthy', path: healthyPath },
|
|
259
|
+
],
|
|
260
|
+
logger: { warn: (...args) => warnings.push(args) },
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await expect(runtime.start()).resolves.toBeUndefined();
|
|
264
|
+
|
|
265
|
+
expect(runtime.getStatus().enabledScheduledTasksCount).toBe(1);
|
|
266
|
+
const healthyTasks = await projectConfigRuntime.listScheduledTasks('healthy');
|
|
267
|
+
expect(healthyTasks.map((task) => task.id)).toEqual(['loop:project:daily']);
|
|
268
|
+
expect(warnings).toHaveLength(1);
|
|
269
|
+
expect(warnings[0][1]).toMatchObject({ projectID: 'broken' });
|
|
270
|
+
runtime.stop();
|
|
271
|
+
} finally {
|
|
272
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { sessionCookieNameForRequest } from '../ui-auth/session-cookie.js';
|
|
2
|
+
|
|
1
3
|
export const createRequestSecurityRuntime = (deps) => {
|
|
2
4
|
const { readSettingsFromDiskMigrated } = deps;
|
|
3
5
|
// Origins of packaged (non-browser) clients whose WebView origin never
|
|
@@ -17,12 +19,14 @@ export const createRequestSecurityRuntime = (deps) => {
|
|
|
17
19
|
if (!cookieHeader || typeof cookieHeader !== 'string') {
|
|
18
20
|
return null;
|
|
19
21
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
// Match the exact slot for the host:port this request arrived on. A browser
|
|
23
|
+
// shares cookies across ports on LAN and loopback hosts. This extracts
|
|
24
|
+
// notification/session identity, not a CSRF token, using the same name
|
|
25
|
+
// as ui-auth's session issuance and validation.
|
|
26
|
+
const expected = sessionCookieNameForRequest(req);
|
|
27
|
+
for (const segment of cookieHeader.split(';')) {
|
|
22
28
|
const [rawName, ...rest] = segment.split('=');
|
|
23
|
-
|
|
24
|
-
if (!name) continue;
|
|
25
|
-
if (name !== 'oc_ui_session') continue;
|
|
29
|
+
if (rawName?.trim() !== expected) continue;
|
|
26
30
|
const value = rest.join('=').trim();
|
|
27
31
|
try {
|
|
28
32
|
return decodeURIComponent(value || '');
|
|
@@ -80,4 +80,41 @@ describe('request security runtime', () => {
|
|
|
80
80
|
headers: { ...request.headers, origin: 'https://evil.example.com' },
|
|
81
81
|
})).toBe(false);
|
|
82
82
|
});
|
|
83
|
+
|
|
84
|
+
test('reads the slot for the request port when a browser shares jars across LAN ports', () => {
|
|
85
|
+
const runtime = createRuntime();
|
|
86
|
+
// Two instances on one LAN IP: the browser sends BOTH session cookies to
|
|
87
|
+
// either port (browsers key cookie jars on host, not port — issue #2377).
|
|
88
|
+
const req = {
|
|
89
|
+
headers: {
|
|
90
|
+
host: '192.168.0.1:3001',
|
|
91
|
+
cookie: 'oc_ui_session_3000=token-a; oc_ui_session_3001=token-b',
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
expect(runtime.getUiSessionTokenFromRequest(req)).toBe('token-b');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('never reads another port cookie when this port has none of its own', () => {
|
|
98
|
+
const runtime = createRuntime();
|
|
99
|
+
// Reaching :3001 with only the :3000 cookie in the jar must NOT borrow the
|
|
100
|
+
// other instance's session — it stays unauthenticated for this port.
|
|
101
|
+
const req = {
|
|
102
|
+
headers: { host: '192.168.0.1:3001', cookie: 'oc_ui_session_3000=token-a' },
|
|
103
|
+
};
|
|
104
|
+
expect(runtime.getUiSessionTokenFromRequest(req)).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('reads the bare cookie for a host without an explicit port', () => {
|
|
108
|
+
const runtime = createRuntime();
|
|
109
|
+
const req = {
|
|
110
|
+
headers: { host: '192.168.0.1', cookie: 'oc_ui_session=token-bare' },
|
|
111
|
+
};
|
|
112
|
+
expect(runtime.getUiSessionTokenFromRequest(req)).toBe('token-bare');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('returns null when no session cookie is present', () => {
|
|
116
|
+
const runtime = createRuntime();
|
|
117
|
+
const req = { headers: { host: '192.168.0.1:3000', cookie: 'theme=dark; oc_url_token=x' } };
|
|
118
|
+
expect(runtime.getUiSessionTokenFromRequest(req)).toBeNull();
|
|
119
|
+
});
|
|
83
120
|
});
|
|
@@ -1,66 +1,131 @@
|
|
|
1
|
-
# Session
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
1
|
+
# Session assist
|
|
2
|
+
|
|
3
|
+
The server generates a short reminder of recent work and an optional next user
|
|
4
|
+
message with Small Model. Results live in `metadata.openchamber.assist` with
|
|
5
|
+
`recap`, `suggestion`, `forMessageID`, and `generatedAt`. The payload shape is
|
|
6
|
+
unchanged; an empty suggestion is a successful outcome.
|
|
7
|
+
|
|
8
|
+
## Ownership
|
|
9
|
+
|
|
10
|
+
- `runtime.js` owns idle timers, cancellation, provider selection, SDK reads,
|
|
11
|
+
freshness checks, settings gates, and metadata writes.
|
|
12
|
+
- `context.js` reads bounded history and constructs human turns. It removes
|
|
13
|
+
tool payloads and injected prompts before retaining message text.
|
|
14
|
+
- `prompt.js` owns the generation instructions and total input budget.
|
|
15
|
+
- `../small-model/DOCUMENTATION.md` owns provider/auth resolution, generation,
|
|
16
|
+
output limits, and overflow behavior.
|
|
17
|
+
|
|
18
|
+
## What the model receives
|
|
19
|
+
|
|
20
|
+
Read backward through the official SDK in pages of 50 messages until three
|
|
21
|
+
human turns are covered, history ends, or eight pages have been read. A failed
|
|
22
|
+
page or repeated cursor aborts generation; it is not treated as complete history.
|
|
23
|
+
At the page limit, use fewer available human turns. If the latest answer's human
|
|
24
|
+
request has not been found, skip generation rather than invent its context.
|
|
25
|
+
|
|
26
|
+
Three turns retain the substance behind short commit confirmations without
|
|
27
|
+
bringing an entire old task back into the prompt. This was compared against
|
|
28
|
+
one, five, ten, and full-history contexts on long maintainer sessions. There
|
|
29
|
+
is no full-history cache and no assumed provider prefix-cache behavior.
|
|
30
|
+
|
|
31
|
+
The latest record must be a completed, successful, non-summary assistant answer
|
|
32
|
+
with visible text. Child, archived, and reverted sessions are skipped. A new
|
|
33
|
+
prompt clears the revert boundary before its next idle event.
|
|
34
|
+
|
|
35
|
+
Human turns follow chronological message intervals. OpenCode can insert
|
|
36
|
+
synthetic continuation users during compaction, so a final answer's `parentID`
|
|
37
|
+
need not point directly at the original human request. These continuations stay
|
|
38
|
+
within their human turn; compaction summaries are excluded. An interrupted
|
|
39
|
+
request remains context with its last visible progress explicitly labeled as
|
|
40
|
+
unfinished, rather than being dropped or called a final answer.
|
|
41
|
+
An answer must still reference that human user or one of its continuation
|
|
42
|
+
users; a late answer for an older request cannot be assigned to a newer request.
|
|
43
|
+
|
|
44
|
+
### Attached context and language
|
|
45
|
+
|
|
46
|
+
The persisted attachment contract is owned by
|
|
47
|
+
`packages/ui/src/lib/messages/contextParts.ts`. Its user-facing Markdown
|
|
48
|
+
formatter is `packages/ui/src/lib/messages/messageMarkdown.ts`.
|
|
49
|
+
|
|
50
|
+
The server projects those persisted parts into model context without importing
|
|
51
|
+
the UI runtime: code comments, file/chat quotes, browser annotations, PR comments,
|
|
52
|
+
checks, terminal selections, and linked GitHub/Linear items remain attached to
|
|
53
|
+
the user turn even when their transport part is synthetic. The OpenCode
|
|
54
|
+
`opencodeComment` mirror is also accepted. Unrecognized synthetic prompts and
|
|
55
|
+
ignored parts are excluded. Malformed attached text fails the generation.
|
|
56
|
+
|
|
57
|
+
Quoted material and the user's own comment are separate blocks. The user's
|
|
58
|
+
authored text is also supplied separately for language selection. Quoted source,
|
|
59
|
+
logs, assistant replies, and injected memory instructions do not choose the
|
|
60
|
+
language. A language-neutral final acknowledgment can use recent authored text.
|
|
61
|
+
The existing Cyrillic/CJK mismatch guard uses that authored sample per field;
|
|
62
|
+
it is not a complete language detector, and it is skipped if no sample exists.
|
|
63
|
+
|
|
64
|
+
### Input bounds
|
|
65
|
+
|
|
66
|
+
User text is bounded to 8,000 characters and each assistant answer to 16,000.
|
|
67
|
+
Attached quote bodies have their own 4,000-character limit so a large quote
|
|
68
|
+
does not consume the user's comment. Excerpts preserve both ends with an
|
|
69
|
+
explicit omission marker, including the conclusion of a long final report.
|
|
70
|
+
|
|
71
|
+
The complete user prompt is limited to 32,000 characters and the resolved small
|
|
72
|
+
model's input allowance, reserving space for the system prompt. Under pressure,
|
|
73
|
+
drop older whole turns first. If the latest pair itself is too large, excerpt
|
|
74
|
+
both its user request and answer rather than discarding either side. If even
|
|
75
|
+
the minimum prompt cannot fit, skip generation. `onOverflow: 'error'` prevents
|
|
76
|
+
the Small Model service from silently cutting off the instructions. Expected
|
|
77
|
+
context/output-budget failures are quiet and do not write metadata.
|
|
78
|
+
|
|
79
|
+
OpenCode message pages still contain complete tool payloads on the wire. A
|
|
80
|
+
single long turn can therefore require substantial I/O even though its retained
|
|
81
|
+
model context is small. Page/count bounds are not a network-byte quota.
|
|
82
|
+
|
|
83
|
+
## Generation and lifecycle
|
|
84
|
+
|
|
85
|
+
1. The server's existing global event fan-out calls `processPayload`. An idle
|
|
86
|
+
event arms the 60-second quiet window. No history scan or startup backfill runs.
|
|
87
|
+
2. Busy/retry events and newly created user messages clear pending work and
|
|
88
|
+
abort in-flight reads/generation. Re-emitted old user updates do not cancel it.
|
|
89
|
+
3. One generation runs per session. If a newer quiet window expires while an
|
|
90
|
+
old canceled request is still settling, retain that pending run and start it
|
|
91
|
+
after the old one finishes. Later activity cancels the pending run as well.
|
|
92
|
+
4. Resolve the small model using the last answer's provider/model and the
|
|
93
|
+
existing explicit settings/config overrides. `restrictToPreferredProvider`
|
|
94
|
+
prevents an implicit cross-provider fallback. Production does not pin the
|
|
95
|
+
experimental model. Generation accepts an abort signal and a 120-second limit.
|
|
96
|
+
5. Recap describes the substantive work and its current result, including the
|
|
97
|
+
work behind a closing commit or acknowledgment. Suggestion is independent:
|
|
98
|
+
only unfinished requested agent work should produce a sendable user message.
|
|
99
|
+
Completed work, optional offers, or a decision/action belonging to the user
|
|
100
|
+
should return an empty suggestion. This is model judgment, not authorization
|
|
101
|
+
enforcement or a guarantee that every generated field is factually correct.
|
|
102
|
+
6. Re-read the latest message and fresh session before writing. A moved tail,
|
|
103
|
+
canceled run, changed endpoint/directory, archive, revert, or failed fresh
|
|
104
|
+
read discards the result. Never merge from the old pre-generation metadata.
|
|
105
|
+
7. Re-check settings, clamp the enabled fields, and merge into fresh metadata.
|
|
106
|
+
The OpenCode update endpoint has no compare-and-set operation; another
|
|
107
|
+
writer after the final read is not guarded atomically.
|
|
108
|
+
|
|
109
|
+
Stopping the runtime clears pending timers/runs and aborts in-flight operations.
|
|
110
|
+
No failed session blocks another session.
|
|
111
|
+
|
|
112
|
+
## Settings and consumers
|
|
113
|
+
|
|
114
|
+
`sessionRecapEnabled` and `sessionSuggestionEnabled` default on and are checked
|
|
115
|
+
before work and before writing. With both off there are no reads, model calls,
|
|
116
|
+
or writes. With one on, the shared recent context is still available, but only
|
|
117
|
+
that field is requested. An empty suggestion does not erase a valid recap.
|
|
118
|
+
|
|
119
|
+
Clients render an assist only while its `forMessageID` is the last message and
|
|
120
|
+
the session is idle. A new message invalidates it without clearing writes.
|
|
121
|
+
|
|
122
|
+
- `packages/ui/src/lib/sessionAssistMetadata.ts` parses the payload.
|
|
123
|
+
- `packages/ui/src/hooks/useSessionAssist.ts` owns freshness/settings gating.
|
|
124
|
+
- `SessionRecapSpacer` shows the reminder in the reserved gap under the reply.
|
|
125
|
+
- `SessionSuggestionChip` fills the composer; it never sends automatically.
|
|
126
|
+
|
|
127
|
+
Web, Electron, hosted mobile, and Capacitor use the server watcher. VS Code's
|
|
128
|
+
extension-only runtime does not generate assists; shared UI can render payloads
|
|
129
|
+
produced by a server. The background watcher cannot use the browser's message
|
|
130
|
+
store when the UI is closed. Manual AI rename uses that store through
|
|
131
|
+
`SessionMessageLoader`; these are intentionally different retrieval lifecycles.
|