@openchambery/web 1.19.7-beta.9 → 1.19.8-beta.1
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/dist/assets/AgentAvatar-CX0a1rKE.js +1 -0
- package/dist/assets/AssistantView-DkXW9Fzq.js +2 -0
- package/dist/assets/DiagramView-C8MZY0fA.js +1 -0
- package/dist/assets/FadeInOnReveal-CK4asqVR.js +1 -0
- package/dist/assets/Icon-BYM02QyU.js +1 -0
- package/dist/assets/MarkdownLoadingSkeleton-DWs4Jg7b.js +3 -0
- package/dist/assets/MarkdownRenderer-cxYw8-8G.js +2 -0
- package/dist/assets/MarkdownRendererImpl-B39nwiZX.js +7 -0
- package/dist/assets/MarkstreamRendererImpl-CdYqdejg.js +1 -0
- package/dist/assets/MarkstreamRendererImpl-CsNwy1T5.css +1 -0
- package/dist/assets/MobileDetailNavigation-D6ri-ePj.js +1 -0
- package/dist/assets/MobileShareBridge-HRTN71wg.js +4 -0
- package/dist/assets/MobileSurface-D2yHjnZw.js +2 -0
- package/dist/assets/{MultiRunWindow-Ohj6UbWj.js → MultiRunWindow-CTRgfYQD.js} +1 -1
- package/dist/assets/OnboardingScreen-C3-wzeSX.js +1 -0
- package/dist/assets/OverlayScrollbar-KUpvYHdv.js +2 -0
- package/dist/assets/SettingsView-ImI02YSp.js +25 -0
- package/dist/assets/{SettingsWindow-B4lzhAbO.js → SettingsWindow-DWo2lUaN.js} +1 -1
- package/dist/assets/TerminalView-BseZ5dR-.js +8 -0
- package/dist/assets/ToolOutputDialog-BIK3cPqo.js +10 -0
- package/dist/assets/appThemeRegistry-4_bwYAWt.js +1 -0
- package/dist/assets/{button-z0GOBtWi.js → button-C0cth9tl.js} +1 -1
- package/dist/assets/clipboard-DJ4nQEuY.js +1 -0
- package/dist/assets/{collapsible-D9zJRpeB.js → collapsible-DFT9cE80.js} +1 -1
- package/dist/assets/context-menu-CAp74NfV.js +1 -0
- package/dist/assets/desktopHosts-CWxex1c3.js +1 -0
- package/dist/assets/desktopRelayRestore-9EbBYQ9s.js +1 -0
- package/dist/assets/{diagram-CoEqmReY.js → diagram-BryzMuta.js} +1 -1
- package/dist/assets/{dialog-x2hirWBd.js → dialog-CgbV5ljg.js} +1 -1
- package/dist/assets/dropdown-menu.styles-Bzo6L3FH.js +1 -0
- package/dist/assets/{es-C8Sqjsia.js → es-BHqnSjoZ.js} +1 -1
- package/dist/assets/fileQueries-BY-9OWzp.js +1 -0
- package/dist/assets/fileReferenceActions-BJJtX_S5.js +1 -0
- package/dist/assets/{fr-p-Zwm_j2.js → fr-BfrlJY9i.js} +1 -1
- package/dist/assets/{gitApi-CtS4kWoo.js → gitApi-m09MWmzE.js} +3 -3
- package/dist/assets/{input-BG3VWTIF.js → input-DFLpqjGR.js} +1 -1
- package/dist/assets/insertionBoundaries-B0fzEL22.js +4 -0
- package/dist/assets/{ja-B6REu1qP.js → ja-DP-7qt-x.js} +1 -1
- package/dist/assets/{ko-CJBNH9IZ.js → ko-E2B5vGQP.js} +1 -1
- package/dist/assets/main-BYX7_XVg.js +169 -0
- package/dist/assets/main-BpbNOMNf.js +2 -0
- package/dist/assets/markdown-worker-BuveaH3O.js +1 -0
- package/dist/assets/markdownTheme-CzZm3riB.js +2 -0
- package/dist/assets/miniChat-CFBgCVoZ.js +2 -0
- package/dist/assets/mobile-DcZkxRBz.js +2 -0
- package/dist/assets/multirun-CBqJQgS2.js +4 -0
- package/dist/assets/outsideFileGrants-DnplRxr-.js +1 -0
- package/dist/assets/path-utils-jGozTg9d.js +1 -0
- package/dist/assets/persistence-DdUac14I.js +3 -0
- package/dist/assets/{pl-B2cBI_7K.js → pl-r8X5gRM5.js} +1 -1
- package/dist/assets/platform-B4KKLnZ5.js +2 -0
- package/dist/assets/projectMeta-Ct1qHZhU.js +3 -0
- package/dist/assets/{pt-BR-CrCc9hmt.js → pt-BR-DcpdXCf5.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-DxmxYdiE.js +2 -0
- package/dist/assets/renderMobileApp-XXw7Fnrr.js +28 -0
- package/dist/assets/runtimeConfig-CcIqxMFB.js +2 -0
- package/dist/assets/runtimeEndpointReset-CU1HhJfC.js +147 -0
- package/dist/assets/sessionChangeSummary-DOq8EN_H.js +1 -0
- package/dist/assets/sessionLookup-CNMNR-yh.js +1 -0
- package/dist/assets/sortable-tabs-strip-C8_9kHqu.js +1 -0
- package/dist/assets/src-DasvfkEg.css +2 -0
- package/dist/assets/{store-CL3WP48T.js → store-BDFdnk6l.js} +2 -2
- package/dist/assets/terminalApi-947l7Ra3.js +1 -0
- package/dist/assets/textarea-Cgo_nWUU.js +1 -0
- package/dist/assets/toolHelpers-DEvVP9U0.js +2 -0
- package/dist/assets/{tooltip-CtH61mQh.js → tooltip-BifLJ8o0.js} +1 -1
- package/dist/assets/{uk-C8YKAz0x.js → uk-Di0vl9V-.js} +1 -1
- package/dist/assets/url-Pk6tQ1EZ.js +1 -0
- package/dist/assets/{ChatContainer-CDExXwgn.js → useAppFontEffects-CBLEH9it.js} +44 -39
- package/dist/assets/useAssistantUIStore-me6s63b2.js +1 -0
- package/dist/assets/useDesktopWindowControlsLayout-DMCmF610.js +1 -0
- package/dist/assets/useEffectiveDirectory-CMvw1bLA.js +1 -0
- package/dist/assets/useFontPreferences-BCOBlaAd.js +1 -0
- package/dist/assets/useMobileNavigationStore-Di9IWKfu.js +413 -0
- package/dist/assets/useSessionAutoCleanup-DMyMP_xI.js +1 -0
- package/dist/assets/useUIStore-CuaA8-Hp.js +1 -0
- package/dist/assets/useWorkerHighlightedLines-uNzY5rmU.js +7 -0
- package/dist/assets/utils-BiK3tBGM.js +1 -0
- package/dist/assets/{vendor-markstream-react-CwvbEg9R.js → vendor-markstream-react-C3K8YH0d.js} +4 -4
- package/dist/assets/{zh-CN-CCYwUnmB.js → zh-CN-coFCfeQE.js} +1 -1
- package/dist/assets/{zh-TW-D8Y6py08.js → zh-TW-oDRZ1-yb.js} +1 -1
- package/dist/index.html +9 -11
- package/dist/mini-chat.html +9 -11
- package/dist/mobile.html +15 -17
- package/package.json +2 -1
- package/server/index.js +2 -0
- package/server/lib/assistant-delivery-parts.js +3 -1
- package/server/lib/assistant-delivery-parts.test.js +11 -0
- package/server/lib/assistants/DOCUMENTATION.md +77 -4
- package/server/lib/assistants/assign.js +225 -0
- package/server/lib/assistants/assign.test.js +118 -0
- package/server/lib/assistants/bubbles.js +45 -0
- package/server/lib/assistants/bubbles.test.js +36 -0
- package/server/lib/assistants/cards.js +148 -0
- package/server/lib/assistants/cards.test.js +116 -0
- package/server/lib/assistants/contact-store.js +284 -0
- package/server/lib/assistants/contact-store.test.js +131 -0
- package/server/lib/assistants/contact-tools.js +764 -0
- package/server/lib/assistants/contact-tools.test.js +337 -0
- package/server/lib/assistants/contracts.js +20 -1
- package/server/lib/assistants/harness.js +575 -0
- package/server/lib/assistants/harness.test.js +697 -0
- package/server/lib/assistants/pi-tools.js +118 -0
- package/server/lib/assistants/pi-tools.test.js +114 -0
- package/server/lib/assistants/routes.js +44 -4
- package/server/lib/assistants/service.js +930 -17
- package/server/lib/assistants/service.test.js +1163 -45
- package/server/lib/event-stream/DOCUMENTATION.md +12 -1
- package/server/lib/event-stream/directory-ws-bridge.js +11 -2
- package/server/lib/event-stream/global-ws-bridge.js +19 -3
- package/server/lib/event-stream/protocol.js +11 -1
- package/server/lib/event-stream/reasoning-projection.js +425 -0
- package/server/lib/event-stream/reasoning-projection.test.js +300 -0
- package/server/lib/event-stream/runtime.js +10 -0
- package/server/lib/event-stream/runtime.test.js +53 -0
- package/server/lib/llm/DOCUMENTATION.md +84 -0
- package/server/lib/llm/catalog.js +100 -0
- package/server/lib/llm/catalog.test.js +78 -0
- package/server/lib/llm/completions.js +151 -0
- package/server/lib/llm/completions.test.js +127 -0
- package/server/lib/llm/generate.js +570 -0
- package/server/lib/llm/generate.test.js +459 -0
- package/server/lib/llm/routes.js +47 -0
- package/server/lib/llm/temp-directory.js +37 -0
- package/server/lib/notifications/APNS.md +19 -13
- package/server/lib/notifications/DOCUMENTATION.md +4 -3
- package/server/lib/notifications/runtime.js +148 -37
- package/server/lib/notifications/runtime.test.js +100 -0
- package/server/lib/opencode/DOCUMENTATION.md +2 -1
- package/server/lib/opencode/feature-routes-runtime.js +30 -0
- package/server/lib/opencode/feature-routes-runtime.test.js +20 -0
- package/server/lib/opencode/proxy.js +103 -9
- package/server/lib/opencode/proxy.test.js +177 -1
- package/server/lib/opencode/settings-runtime.js +2 -2
- package/server/lib/relay/reasoning-projection.e2e.test.ts +529 -0
- package/server/lib/session-goal/DOCUMENTATION.md +8 -1
- package/server/lib/session-goal/runtime.js +20 -0
- package/server/lib/session-index/DOCUMENTATION.md +3 -1
- package/server/lib/session-index/service.js +10 -4
- package/server/lib/session-index/service.test.js +37 -0
- package/server/lib/session-title/runtime.js +1 -0
- package/server/lib/session-title/runtime.test.js +11 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +2 -1
- package/server/lib/session-turn-pages/routes.js +22 -3
- package/server/lib/session-turn-pages/routes.test.js +43 -0
- package/server/lib/transcript-cache/DOCUMENTATION.md +2 -2
- package/server/lib/transcript-cache/routes.js +9 -2
- package/server/lib/transcript-cache/routes.test.js +32 -0
- package/dist/assets/AgentAvatar-Di8F4QJh.js +0 -1
- package/dist/assets/AssistantView-DSxVAAb_.js +0 -1
- package/dist/assets/ContextPanel-DgwEIdqR.js +0 -285
- package/dist/assets/DiagramView-BKQO61y8.js +0 -1
- package/dist/assets/FadeInOnReveal-CV2s6YN8.js +0 -1
- package/dist/assets/Icon-CLpn1mIz.js +0 -1
- package/dist/assets/MarkdownRendererImpl-CTgmghSh.js +0 -7
- package/dist/assets/MarkstreamRendererImpl-BKsc-avF.js +0 -1
- package/dist/assets/MarkstreamRendererImpl-CxeWpDCK.css +0 -1
- package/dist/assets/MobileDetailNavigation-BcA6rd8Q.js +0 -1
- package/dist/assets/MobileShareBridge-w6T2ZrsW.js +0 -4
- package/dist/assets/MobileSurface-CG1n7v0h.js +0 -2
- package/dist/assets/OnboardingScreen-D6rtE7rj.js +0 -1
- package/dist/assets/OverlayScrollbar-DlNZho3q.js +0 -2
- package/dist/assets/SettingsView-CiF3r83e.js +0 -25
- package/dist/assets/TerminalView-Ctx_Jt_b.js +0 -8
- package/dist/assets/ToolOutputDialog-Dv9_fUYl.js +0 -10
- package/dist/assets/appThemeRegistry-DnwUIQB9.js +0 -2
- package/dist/assets/clipboard-D35XSwID.js +0 -1
- package/dist/assets/desktop-CfLlmafK.js +0 -1
- package/dist/assets/desktopHosts-C1Ns3DUu.js +0 -1
- package/dist/assets/desktopRelayRestore-8pvI_dSc.js +0 -1
- package/dist/assets/fileQueries-DGHZqZHc.js +0 -1
- package/dist/assets/insertionBoundaries-hK4mQj4W.js +0 -4
- package/dist/assets/main-BmbZFoO4.js +0 -29
- package/dist/assets/main-BwNhn8ml.js +0 -2
- package/dist/assets/markdown-worker-y3ewaqAK.js +0 -4
- package/dist/assets/markdownTheme-SPe4jOwX.js +0 -2
- package/dist/assets/miniChat-BN89siC3.js +0 -2
- package/dist/assets/mobile-DE9rVo5D.js +0 -2
- package/dist/assets/multirun-BiEuyDq3.js +0 -4
- package/dist/assets/persistence-w-AiX2i4.js +0 -3
- package/dist/assets/platform-4eCmsdol.js +0 -2
- package/dist/assets/projectMeta-DSLKcH1a.js +0 -3
- package/dist/assets/radio-HD5AB2Qf.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-f_UxpFjv.js +0 -2
- package/dist/assets/renderMobileApp-BAhCai6d.js +0 -28
- package/dist/assets/runtime-switch-BkpTTbEi.js +0 -1
- package/dist/assets/runtimeConfig-qG_oNBoa.js +0 -2
- package/dist/assets/runtimeEndpointReset-BDeKD3EM.js +0 -4
- package/dist/assets/shikiHighlight-BphLZnac.js +0 -1
- package/dist/assets/sortable-tabs-strip-CNDgyX0B.js +0 -1
- package/dist/assets/src-DZt1nWf5.css +0 -2
- package/dist/assets/terminalApi-CGtOtc8I.js +0 -1
- package/dist/assets/url-KeihbE0d.js +0 -1
- package/dist/assets/useAppFontEffects--0dwVTSm.js +0 -7
- package/dist/assets/useAssistantUIStore-I0VWrB5V.js +0 -1
- package/dist/assets/useDesktopWindowControlsLayout-DsbeBLvv.js +0 -1
- package/dist/assets/useEffectiveDirectory-CHvvohcG.js +0 -1
- package/dist/assets/useFontPreferences-BLZ3y6mN.js +0 -1
- package/dist/assets/useMobileNavigationStore-peTDuC00.js +0 -412
- package/dist/assets/useSessionAutoCleanup-D_ZC9E5g.js +0 -1
- package/dist/assets/useUIStore-CNLmCoPU.js +0 -1
- package/dist/assets/useWorkerHighlightedLines-B1YzuMYR.js +0 -7
- package/dist/assets/utils-CTL16t2f.js +0 -1
package/dist/index.html
CHANGED
|
@@ -552,27 +552,25 @@
|
|
|
552
552
|
opacity: 0.72;
|
|
553
553
|
}
|
|
554
554
|
</style>
|
|
555
|
-
<script type="module" crossorigin src="/assets/main-
|
|
556
|
-
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-BkpTTbEi.js">
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-BpbNOMNf.js"></script>
|
|
557
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
558
|
-
<link rel="modulepreload" crossorigin href="/assets/desktop-CfLlmafK.js">
|
|
559
557
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
560
558
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
561
|
-
<link rel="modulepreload" crossorigin href="/assets/useUIStore-
|
|
559
|
+
<link rel="modulepreload" crossorigin href="/assets/useUIStore-CuaA8-Hp.js">
|
|
562
560
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
563
561
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
|
|
564
562
|
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
565
|
-
<link rel="modulepreload" crossorigin href="/assets/persistence-
|
|
566
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-
|
|
567
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-
|
|
563
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-DdUac14I.js">
|
|
564
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-CWxex1c3.js">
|
|
565
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-9EbBYQ9s.js">
|
|
568
566
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-C07HQypa.js">
|
|
569
567
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
|
|
570
|
-
<link rel="modulepreload" crossorigin href="/assets/platform-
|
|
571
|
-
<link rel="modulepreload" crossorigin href="/assets/terminalApi-
|
|
568
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-B4KKLnZ5.js">
|
|
569
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-947l7Ra3.js">
|
|
572
570
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
573
571
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
574
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
575
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
572
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CcIqxMFB.js">
|
|
573
|
+
<link rel="stylesheet" crossorigin href="/assets/src-DasvfkEg.css">
|
|
576
574
|
</head>
|
|
577
575
|
<body class="h-full bg-background text-foreground">
|
|
578
576
|
<!-- Keep the startup layer outside #root. React replaces every child of
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,27 +74,25 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
78
|
-
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-BkpTTbEi.js">
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-CFBgCVoZ.js"></script>
|
|
79
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
80
|
-
<link rel="modulepreload" crossorigin href="/assets/desktop-CfLlmafK.js">
|
|
81
79
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
82
80
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
83
|
-
<link rel="modulepreload" crossorigin href="/assets/useUIStore-
|
|
81
|
+
<link rel="modulepreload" crossorigin href="/assets/useUIStore-CuaA8-Hp.js">
|
|
84
82
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
85
83
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
|
|
86
84
|
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
87
|
-
<link rel="modulepreload" crossorigin href="/assets/persistence-
|
|
88
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-
|
|
89
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-
|
|
85
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-DdUac14I.js">
|
|
86
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-CWxex1c3.js">
|
|
87
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-9EbBYQ9s.js">
|
|
90
88
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-C07HQypa.js">
|
|
91
89
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
|
|
92
|
-
<link rel="modulepreload" crossorigin href="/assets/platform-
|
|
93
|
-
<link rel="modulepreload" crossorigin href="/assets/terminalApi-
|
|
90
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-B4KKLnZ5.js">
|
|
91
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-947l7Ra3.js">
|
|
94
92
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
95
93
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
96
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
97
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
94
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CcIqxMFB.js">
|
|
95
|
+
<link rel="stylesheet" crossorigin href="/assets/src-DasvfkEg.css">
|
|
98
96
|
</head>
|
|
99
97
|
<body class="h-full bg-background text-foreground">
|
|
100
98
|
<div id="root" class="h-full">
|
package/dist/mobile.html
CHANGED
|
@@ -41,18 +41,18 @@
|
|
|
41
41
|
// splashBg* are the resolved theme surface.background colors persisted by the app
|
|
42
42
|
// theme system (see ThemeSystemContext). When absent (e.g. a fresh install before
|
|
43
43
|
// the theme has been persisted once), fall back to the default theme's
|
|
44
|
-
// surface.background —
|
|
45
|
-
// app renders on first launch — so the pre-paint matches instead
|
|
46
|
-
// different colour.
|
|
47
|
-
var bgDark = localStorage.getItem('splashBgDark') || '#
|
|
48
|
-
var bgLight = localStorage.getItem('splashBgLight') || '#
|
|
44
|
+
// surface.background — carbonfox-light / vitesse-dark-dark (see lib/theme/themes),
|
|
45
|
+
// which is what the app renders on first launch — so the pre-paint matches instead
|
|
46
|
+
// of flashing a different colour.
|
|
47
|
+
var bgDark = localStorage.getItem('splashBgDark') || '#121212';
|
|
48
|
+
var bgLight = localStorage.getItem('splashBgLight') || '#FFFFFF';
|
|
49
49
|
var bg = isDark ? bgDark : bgLight;
|
|
50
50
|
|
|
51
51
|
// Set the actual --background CSS variable, not just the element background.
|
|
52
52
|
// `<body class="bg-background">` paints `var(--background)`, and design-system.css
|
|
53
53
|
// ships a baked default (.dark { --background: ... /* #151313 */ }) that otherwise
|
|
54
54
|
// overpaints our html background the moment the stylesheet loads — before React's
|
|
55
|
-
// theme system injects the real (
|
|
55
|
+
// theme system injects the real (default/custom) vars with !important. Setting the
|
|
56
56
|
// var inline on the root wins over that non-!important default, so body paints the
|
|
57
57
|
// correct colour immediately; React's later !important vars still take over.
|
|
58
58
|
root.style.setProperty('--background', bg);
|
|
@@ -63,27 +63,25 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
67
|
-
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-BkpTTbEi.js">
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-DcZkxRBz.js"></script>
|
|
68
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
69
|
-
<link rel="modulepreload" crossorigin href="/assets/desktop-CfLlmafK.js">
|
|
70
68
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
71
69
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
72
|
-
<link rel="modulepreload" crossorigin href="/assets/useUIStore-
|
|
70
|
+
<link rel="modulepreload" crossorigin href="/assets/useUIStore-CuaA8-Hp.js">
|
|
73
71
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
74
72
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
|
|
75
73
|
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
76
|
-
<link rel="modulepreload" crossorigin href="/assets/persistence-
|
|
77
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-
|
|
78
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-
|
|
74
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-DdUac14I.js">
|
|
75
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-CWxex1c3.js">
|
|
76
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-9EbBYQ9s.js">
|
|
79
77
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-C07HQypa.js">
|
|
80
78
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
|
|
81
|
-
<link rel="modulepreload" crossorigin href="/assets/platform-
|
|
82
|
-
<link rel="modulepreload" crossorigin href="/assets/terminalApi-
|
|
79
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-B4KKLnZ5.js">
|
|
80
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-947l7Ra3.js">
|
|
83
81
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
84
82
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
85
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
86
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
83
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CcIqxMFB.js">
|
|
84
|
+
<link rel="stylesheet" crossorigin href="/assets/src-DasvfkEg.css">
|
|
87
85
|
</head>
|
|
88
86
|
<body class="h-full bg-background text-foreground">
|
|
89
87
|
<div id="root" class="h-full"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openchambery/web",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.8-beta.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./server/index.js",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@clack/prompts": "^1.1.0",
|
|
28
|
+
"@earendil-works/pi-agent-core": "0.84.4",
|
|
28
29
|
"@octokit/rest": "^22.0.1",
|
|
29
30
|
"@opencode-ai/sdk": "1.18.4",
|
|
30
31
|
"@simplewebauthn/server": "13.3.1",
|
package/server/index.js
CHANGED
|
@@ -1602,6 +1602,8 @@ async function main(options = {}) {
|
|
|
1602
1602
|
messageQueueRuntime,
|
|
1603
1603
|
globalMessageStreamHub,
|
|
1604
1604
|
getServerId: () => relayService.getServerId(),
|
|
1605
|
+
sessionIndexService,
|
|
1606
|
+
notifyContactTurnComplete: (event) => notificationTriggerRuntime.sendContactTurnNotification(event),
|
|
1605
1607
|
});
|
|
1606
1608
|
|
|
1607
1609
|
const previewProxyRuntime = createPreviewProxyRuntime({
|
|
@@ -20,5 +20,7 @@ export const validAssistantDeliveryParts = (parts, { allowAttachmentRefs = false
|
|
|
20
20
|
&& part.text.length <= MAX_TEXT_LENGTH
|
|
21
21
|
&& (part.synthetic === undefined || typeof part.synthetic === 'boolean');
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
const filenameOk = part.filename === undefined
|
|
24
|
+
|| (typeof part.filename === 'string' && part.filename.length > 0 && part.filename.length <= MAX_STRING_LENGTH);
|
|
25
|
+
return part.type === 'file' && typeof part.mime === 'string' && part.mime.length > 0 && part.mime.length <= MAX_STRING_LENGTH && filenameOk && ((allowAttachmentRefs && Object.keys(part).every((key) => key === 'type' || key === 'mime' || key === 'attachmentID') && typeof part.attachmentID === 'string' && part.attachmentID.length > 0 && part.attachmentID.length <= MAX_STRING_LENGTH) || (Object.keys(part).every((key) => key === 'type' || key === 'mime' || key === 'url' || key === 'filename') && typeof part.url === 'string' && part.url.length > 0 && part.url.length <= MAX_FILE_URL_LENGTH));
|
|
24
26
|
});
|
|
@@ -13,4 +13,15 @@ describe('validAssistantDeliveryParts', () => {
|
|
|
13
13
|
expect(validAssistantDeliveryParts([{ type: 'text', text: 'hello', id: 'part_1' }])).toBe(false);
|
|
14
14
|
expect(validAssistantDeliveryParts([{ type: 'text', text: 'hello', synthetic: 'yes' }])).toBe(false);
|
|
15
15
|
});
|
|
16
|
+
|
|
17
|
+
it('accepts file parts with mime+url and optional filename', () => {
|
|
18
|
+
expect(validAssistantDeliveryParts([
|
|
19
|
+
{ type: 'text', text: 'look' },
|
|
20
|
+
{ type: 'file', mime: 'image/png', url: 'data:image/png;base64,aa', filename: 'shot.png' },
|
|
21
|
+
{ type: 'file', mime: 'text/plain', url: 'data:text/plain;base64,eA==' },
|
|
22
|
+
])).toBe(true);
|
|
23
|
+
expect(validAssistantDeliveryParts([
|
|
24
|
+
{ type: 'file', mime: 'image/png', url: 'data:image/png;base64,aa', id: 'part_1' },
|
|
25
|
+
])).toBe(false);
|
|
26
|
+
});
|
|
16
27
|
});
|
|
@@ -1,16 +1,89 @@
|
|
|
1
1
|
# Assistants Module
|
|
2
2
|
|
|
3
|
+
## Acceptance criteria
|
|
4
|
+
|
|
5
|
+
The in-app Assistant is an OpenChamber-owned **contact**. OpenCode is only the LLM gateway (`POST /api/openchamber/llm/chat/completions`) using already-connected `{providerID, modelID}`. Composer send runs `@earendil-works/pi-agent-core` with `thinkingLevel: 'off'`. When the assistant has an effective workspace, the harness attaches pi's four coding tools (`read`, `write`, `edit`, `bash`) via `NodeExecutionEnv` in that directory, applies `defaultPrompt`, and merges skills from `~/.claude/skills`, `~/.agents/skills`, plus the project's `.claude/skills` and `.agents/skills` (project wins on name). OpenChamber API tools remain (`new_conversation`, `list_projects`, `list_sessions`, `create_assistant`, `schedule_task`, `message_assistant`, `assign_session`). It does not use `SessionPrompt` / `promptAsync` as the conversation engine. The LLM gateway throwaway session still denies OpenCode tools; file and shell work runs in-process through pi.
|
|
6
|
+
|
|
7
|
+
**Session cards (this PR)**
|
|
8
|
+
|
|
9
|
+
- First-class contact parts reuse one slot: `cardType` `session` | `assistant` | `schedule`.
|
|
10
|
+
- Persist in `assistant_contact_message` / `assistant_contact_part`. Survive reload.
|
|
11
|
+
- Cards are **assistant-emitted UI**, not user slash commands. The user never types `/card`. Insert: tool auto-cards (`assign_session`, `create_assistant`, `schedule_task`), and API `POST /:id/contact/cards` for harness/tests. Later project/worktree/watch/PR, inbound pushes, and summon **must** reuse this card slot.
|
|
12
|
+
|
|
13
|
+
**Assign work (this PR)**
|
|
14
|
+
|
|
15
|
+
- `assign_session` is an OpenChamber AgentTool on the contact harness. It creates or reuses a visible OpenCode/OpenChamber session on a **registered project path** (Settings projects / `getAllowedRoots()`), optionally an **existing** Chat worktree/branch via `getWorktrees`, then kicks the coding prompt into that session with `session.create` + `session.promptAsync` (same worker path as `POST /api/openchamber/conversations`).
|
|
16
|
+
- That worker session is the coder. The contact does not code. Assign sessions are **not** archived Assistant bindings and must not use `assistant-workspaces`.
|
|
17
|
+
- After a successful assign, the service persists the existing session card into the contact transcript (user bubbles first, then the card) and watches that worker session in `assistant_contact_watch`.
|
|
18
|
+
- Existing OpenCode SSE on the same `globalEventHub` as notifications (`session.idle`, `session.error`, `session.status`, `question.asked`, `permission.asked`) updates **the same card** (`busy` → `complete` / `error` / `question`) and appends one idempotent settle bubble (`oc.settle.*`). Session-goal settle (`emitGoalNotification`) is wired into that same reporter — not a second scheduler. Idle after `session.error` must not rewrite 失败 as 完成.
|
|
19
|
+
- Restart or a missed `session.idle` cannot leave the watch stuck: boot and the existing 60s reconcile timer `listInFlightWatches` and poll OpenCode `session.get` / `session.messages`. Idle, `time.completed`, or a missing session settles the same card (`busy` → 完成) and appends the existing `oc.settle.complete` bubble. `session.error` or last-assistant `info.error` settles 失败. Transport / non-404 get errors leave the watch in flight (failure is not empty success). One failed watch does not block unrelated watches. Reconcile reuses `reportAssignedSession`, so an error watch is never rewritten as complete.
|
|
20
|
+
- Worker sessions stamp `metadata.openchamber.assigned` (`from: 'contact'`, `assistantID`, `name`). They must **not** use `openchamber.assistant.assistantID` — that marker hides archived Assistant bindings from Chat.
|
|
21
|
+
- `AssistantDTO.assignedSessionIDs` lists in-flight (`busy`/`question`) watches. `AssistantDTO.working` is true only while a watch is `busy` (the Grok-Bot green avatar dot). Question/complete/error settle the running indicator.
|
|
22
|
+
- No registered project → clear `project_required` (ask the user to configure a project). Never fall back to managed assistant-workspaces.
|
|
23
|
+
- Assign must not go through the peer DM channel.
|
|
24
|
+
- Share/queue may still use `promptAsync` on Assistant bindings.
|
|
25
|
+
|
|
26
|
+
**Registered projects + sessions (this PR)**
|
|
27
|
+
|
|
28
|
+
- Every contact turn injects a **Registered projects** block (`id`, `path`, `label`) from `listProjects()` into the system prompt via `formatRegisteredProjectsPrompt`. The model can fuzzy-match names like "openchamber yee" without a tool call first. It must never claim it cannot see the list; empty catalog → tell the user to add a project in Settings.
|
|
29
|
+
- `list_projects` refreshes/filters that catalog (`query` optional). `list_sessions` searches the existing session-index (`sessionIndexService.snapshot()`) for sessions in a project (`projectPath` / `projectID`, optional title query). Results are bounded (`sessionID`, `title`, `directory`, `updatedAt`). Index/catalog failure is an error, never an empty success.
|
|
30
|
+
- After matching a project, open work with `assign_session` (`projectPath` and/or existing `sessionID`).
|
|
31
|
+
|
|
32
|
+
**Create assistant / schedule task (this PR)**
|
|
33
|
+
|
|
34
|
+
- `create_assistant` calls the same in-process `createAssistant` (`name`, connected OpenCode `providerID`/`modelID`, `mode: 'continuous'`). It emits an `assistant` card that opens that contact.
|
|
35
|
+
- `schedule_task` calls in-process `projectConfigRuntime.upsertScheduledTask` with the same payload as `PUT /api/projects/:id/scheduled-tasks` (`name`, `schedule.kind/time/timezone`, `execution.prompt` + provider/model) on a registered project, then best-effort `scheduledTasksRuntime.syncProject`. It emits a `schedule` card.
|
|
36
|
+
- Successful `schedule_task` also writes an assistants-owned mapping row in `assistants.sqlite` (`assistant_scheduled_task`: assistantID + projectID + taskID). Project-config execution strips unknown fields, so ownership stays out of the task payload. `GET /api/openchamber/assistants/:id/scheduled-tasks` returns those mappings joined with live `listScheduledTasks` records when possible; failed project lookups keep the mapping with `task: null`.
|
|
37
|
+
- `CONTACT_SYSTEM_PROMPT` + `formatContactToolsPrompt` tell the model to call these from natural language (开新对话 / 找项目 / 现有对话 / 建助理 / 建会话 / 排定时任务 / 给 X 说一声). A reply without the tool call does nothing; never say 已创建 / 已发送 unless the tool returned. Completions are messages-only (no native tools), so `parseContactToolCalls` accepts a fence, a whole-message JSON object, or a bare `{name, arguments}` object anywhere in the reply.
|
|
38
|
+
- If the user asked for `new_conversation` / `list_projects` / `list_sessions` / `create_assistant` / `schedule_task` / `message_assistant` / `assign_session` and the first completion has no tool call, the harness runs **one** follow-up completion whose user content is only `emit the fence now, do not claim success.` If still no tool, the turn replies that it could not complete and does not fake a card. No slash commands. No second store.
|
|
39
|
+
|
|
40
|
+
**New conversation (this PR)**
|
|
41
|
+
|
|
42
|
+
- `new_conversation` clears this assistant's OpenChamber contact transcript via `deleteContactMessages` (messages, parts, watches), bumps revision / `assistants-changed`, and confirms with a short bubble. Shared API: `POST /api/openchamber/assistants/:id/contact/reset`.
|
|
43
|
+
- The tool terminates the contact turn. Persist only `NEW_CONVERSATION_CONFIRM_BUBBLE`. Discard leftover assistant text and cards from the same pre-reset generation — do not re-feed that history after reset.
|
|
44
|
+
- It must **not** call OpenCode `session/new` / `createNew`. That path is worker binding, not contact context reset.
|
|
45
|
+
- Natural language: 开新对话 / new conversation / clear chat.
|
|
46
|
+
|
|
47
|
+
**Contact LLM window (this PR)**
|
|
48
|
+
|
|
49
|
+
- `contactHistoryForLlm` is a short algorithmic window documented in `contact-store.js`: `CONTACT_LLM_MAX_TURNS` (8), `CONTACT_LLM_MAX_CHARS` (6000), `CONTACT_LLM_FETCH_LIMIT` (40), `CONTACT_LLM_FILE_CHAR_WEIGHT` (80). Newest user+assistant text pairs are kept; peer / error / pure-card rows are dropped. There is no summarizer and no user-facing compress or continuous/stateless memory control. SQLite and the transcript UI may still scroll older bubbles.
|
|
50
|
+
|
|
51
|
+
**Contact attachments (this PR)**
|
|
52
|
+
|
|
53
|
+
- Attach is required on the contact composer: images and non-image files via picker, paste, and drag-drop. Same OpenCode file-part convention (`mime` + data `url`, optional `filename`) stored in `assistant_contact_part`. Do not invent a second store or route uploads through SessionPrompt / ChatInput / the message-queue attachment store.
|
|
54
|
+
- User bubbles persist file parts. Images render inline; other files render as a named chip. Refresh hydrates the same parts.
|
|
55
|
+
- The contact harness turn receives those file parts so the OpenChamber Chat Completions / OpenCode provider gateway can show images to the model.
|
|
56
|
+
|
|
57
|
+
**Inter-assistant DM (this PR, read-only)**
|
|
58
|
+
|
|
59
|
+
- `message_assistant` is the contact tool for natural language (给 PeerQA 说一声 / tell PeerQA). It calls in-process `deliverPeerMessage` and confirms with a short bubble. No card required. The user never types `/dm`.
|
|
60
|
+
- `POST /:fromAssistantID/contact/dm` `{ toAssistantID, text, parts? }` delivers into B’s contact transcript as `role: 'peer'` with `fromAssistantID` / `fromAssistantName`. The harness/API sends peer messages.
|
|
61
|
+
- Read-only: no `session.promptAsync`, no session/file/worktree mutation, no tools on the recipient. Assign is not delivered here.
|
|
62
|
+
- Peer rows are excluded from `contactHistoryForLlm` so they cannot become harness turns.
|
|
63
|
+
- This is the inbox primitive for later summon. Do not invent a second inbox.
|
|
64
|
+
|
|
65
|
+
`GET /api/openchamber/assistants/capability` remains the navigation gate.
|
|
66
|
+
|
|
3
67
|
## History coverage
|
|
4
68
|
|
|
5
69
|
Snapshots expose the latest 50 `historySessionIDs` in chronological order and `historySessionCount` for the full count. The messages pagination endpoint owns complete history, including the current binding. A successful direct, shared, or queued OpenCode admission writes its user message and provisional parts to the Assistant SQLite mirror before the HTTP operation settles; this covered admission row survives binding replacement, restart, and an unavailable OpenCode history endpoint. Matching live parts replace provisional parts by message ID. Other event mirrors retain raw JSON as uncovered (`covered=0`) provisional fallback rows: they remain visible in archived history until an explicit shrink signal. Authoritative OpenCode backfill upserts covered rows and reconciles authoritative parts, rejects entries whose embedded session ID belongs to another session, and never deletes provisional mirrors merely because a successful page omitted them. Pagination `complete` only records that the OpenCode cursor was exhausted; it does not authorize deleting uncovered event mirrors. The only shrink whitelist is explicit `message.removed`, an authoritative `session.messages` 404 (`session-missing`), or assistant deletion. Late archived `message.updated` / `message.part.updated` and session lifecycle events (`session.idle`, `session.error`, `session.status` busy/retry/idle) call `invalidateBackfill` to reopen that session's cursor/complete without clearing covered flags. `history-state.js` owns the pure backfill reducer (`invalidate` → preserve; `page` → complete iff next cursor empty, always preserve; `session-missing` → discard provisional). Backfill persists the OpenCode `x-next-cursor` header verbatim and derives completion from its presence. A demand request commits up to three upstream pages and returns committed archived mirror rows (covered and provisional) with a stable scan-boundary cursor when more work remains. Startup begins one non-blocking current-binding backfill page. Archived rows preserve their stored directory; a legacy null directory triggers an authoritative session lookup of `data.directory` and `data.project.worktree`, with containment validation before persistence. Unresolved legacy directories remain `null` in persisted and historical response data. Ordinary mirror upserts preserve covered rows and never blanket-invalidate a session; explicit authoritative upserts elevate coverage; session-wide `invalidateCoverage` preserves every already-covered user row, including admitted rows whose local marker was replaced by live OpenCode info, while resetting other coverage on archive/re-archive; message-scoped coverage invalidation runs only after structural part deletes that require re-backfill. Removing an assistant tombstones the row and deletes its session history, message/part mirrors, and backfill state in one transaction. Schema v11 adds the mirror coverage marker.
|
|
6
70
|
|
|
7
71
|
`assistants` owns Assistant configuration, session bindings, and a durable assistant-owned OpenCode message/part mirror in `assistants.sqlite`. Routes register before the generic OpenCode proxy.
|
|
8
72
|
|
|
9
|
-
`GET /api/openchamber/assistants/capability` returns `supported`, the persisted `enabled` switch, domain revision, and relay-derived stable `serverInstanceID`. Fresh databases default `enabled` to off; Settings flips the switch, and product navigation entries stay hidden until it is on. `PUT /api/openchamber/assistants/settings` changes the global switch with `expectedRevision`. CRUD and snapshots live under `/api/openchamber/assistants`. Session routes are `POST /:id/session/ensure`, `/:id/session/new`, and `/:id/session/compact`; composer sends use `POST /:id/messages`;
|
|
73
|
+
`GET /api/openchamber/assistants/capability` returns `supported`, the persisted `enabled` switch, domain revision, and relay-derived stable `serverInstanceID`. Fresh databases default `enabled` to off; Settings flips the switch, and product navigation entries stay hidden until it is on. `PUT /api/openchamber/assistants/settings` changes the global switch with `expectedRevision`. CRUD and snapshots live under `/api/openchamber/assistants`. Session routes are `POST /:id/session/ensure`, `/:id/session/new`, and `/:id/session/compact`; composer sends use `POST /:id/messages` (contact harness, not `promptAsync`); contact history is `GET /:id/contact/messages`; contact reset is `POST /:id/contact/reset`; session cards are `POST /:id/contact/cards`; read-only inter-assistant DMs are `POST /:fromID/contact/dm`; assistant-owned scheduled tasks are `GET /:id/scheduled-tasks`. Shares still use `POST /:id/share` and `GET /share-operations/:operationID`.
|
|
74
|
+
|
|
75
|
+
`AssistantDTO.mode` remains `continuous` or `stateless` (default `continuous`) for share/queue OpenCode session binding. It is **not** a user-facing memory setting: Settings no longer exposes the continuous/stateless toggle, and new assistants stay continuous contact history with server-side LLM trim. `historySessionIDs` lists prior OpenCode session bindings in chronological order (oldest first, capped). Continuous Assistants keep one long-lived current session across composer turns. Stateless Assistants create and bind a fresh OpenCode session for every composer send, share admission, and queued delivery so the model executes without prior transcript context; prior sessions remain available in OpenCode history. The Assistant surface follows the current binding, so each stateless turn rematerializes onto that fresh session. `AssistantDTO.workspacePath` retains the configuration value: `null` selects the managed workspace and a string selects a registered project path. `AssistantDTO.managedWorkspacePath` always carries the canonical managed target at `dataDir/assistant-workspaces/<assistantID>`, including for project-configured Assistants. `AssistantDTO.effectiveWorkspacePath` carries the resolved OpenCode session directory. `AssistantDTO.variant` is nullable and reaches OpenCode `promptAsync` with provider, model, agent, and system prompt. `SessionBinding` is `{ sessionID, directory, sessionGeneration }` with the effective directory. `ensure` and `new` return `SessionBinding`; `compact` returns `{ binding, summarized: true }`; messages return `{ binding, messageID, admitted: true }`; shares return top-level `{ operationID, assistantID, sessionID, messageID, state, phase, attempt, leaseExpiresAt, errorCode }`. `contracts.js` exports serialization fixtures for these DTOs. Binding replacement uses generation CAS, so concurrent creation retains one winner while orphan sessions stay available in OpenCode history. Workspace changes create and bind a session in the new directory. Session metadata carries `openchamber.assistant.assistantID` and `name` as the authoritative ownership marker. New OpenCode sessions use the fixed human title `[Assistant] ${name}` and are archived via `session.update({ time: { archived } })` immediately after create and before binding is returned, so ordinary active lists never flash them; metadata isolation still applies if archive fails after create. The session index and global session lists hide system sessions by this metadata (never by title prefix). Assistant history and direct source open remain the entry points.
|
|
76
|
+
|
|
77
|
+
**Contact send admission + SSE (this PR)**
|
|
10
78
|
|
|
11
|
-
|
|
79
|
+
- `POST /api/openchamber/assistants/:id/messages` returns **202** `{ messageID, admitted: true, binding }` as soon as the **user row** is persisted (`status: complete`). It does **not** wait for the LLM turn. Validation errors still fail synchronously with 4xx before admit.
|
|
80
|
+
- After admit: bump revision + `openchamber:assistants-changed`, broadcast `openchamber:contact-turn-start` `{ assistantID, turnID, messageID, occurredAt }` (`turnID` = client `messageID`), then kick `runContactTurn` asynchronously (serialized per assistant).
|
|
81
|
+
- Contact SSE reuses `/api/openchamber/events` (no new endpoint): `openchamber:contact-bubble-delta` `{ assistantID, turnID, bubbleIndex, delta, done, occurredAt }`. Raw completion tokens are **not** painted live — they are often chain-of-thought and would appear then vanish. A short spoken preamble (e.g. 我去找一下) may appear before a tool and is kept. Planning/CoT is stripped. No-tool replies emit each stripped bubble as `{ done:true }` after parse, with a short gap between bubbles so they arrive one beat at a time.
|
|
82
|
+
- Turn end: strip/parse/tools (existing), persist **assistant** bubbles + cards only (never re-insert the user row), bump + assistants-changed, broadcast `openchamber:contact-turn-end` `{ assistantID, turnID, status: 'complete'|'error', error?, occurredAt }`. On failure the admitted user row stays; clients must not spin forever. Completing a contact turn also sends the same completion notification path as a normal contact: SMS-style title (assistant name) + body (spoken bubbles), via desktop / UI SSE / push, gated by `notifyOnCompletion`.
|
|
83
|
+
- In-process tests use `whenContactTurnSettled(messageID)` (not part of the HTTP JSON body).
|
|
84
|
+
- Completions: public `POST /llm/chat/completions` still rejects `stream:true`. In-process contact harness passes `onTextDelta` + `globalEventHub` through `boundCompletion` → `createChatCompletion` → throwaway generate for real tokens only.
|
|
12
85
|
|
|
13
|
-
Composer messages carry
|
|
86
|
+
Composer messages carry a client message ID. The service accepts the existing OpenCode delivery parts (`{ type: 'text' }` and `{ type: 'file', mime, url, filename? }` data URLs — not a second attachment store), extracts text (`[attachment]` when the send is file-only), admits the user at 202, then runs the contact harness asynchronously (`runContactTurn` / pi-agent-core, thinking off, pi `read` / `write` / `edit` / `bash` in the assistant workspace, OpenChamber tools `new_conversation` / `list_projects` / `list_sessions` / `create_assistant` / `schedule_task` / `message_assistant` / `assign_session`, merged `.agents`/`.claude` skills, plus the registered-projects catalog each turn) through the OpenChamber completions gateway (public HTTP non-streaming; in-process `onTextDelta` for contact SSE). Assistant bubbles and tool cards persist at turn end. File parts survive `GET /:id/contact/messages`. The harness forwards those file parts to the completions gateway. Image data URLs reach `promptAsync` only when the connected catalog marks the model as vision-capable; otherwise generate keeps the `[image: …]` description and text-file bytes so a non-vision model cannot stall the contact turn. Gateway 502 responses include `{ error, message }` so the composer can show the OpenCode error string. OpenCode `promptAsync` is not the contact conversation engine; assign uses it only on the **worker** session. Share and queued delivery still use it on Assistant bindings. OpenCode `204` is an admitted empty response. A 404 restores one generation-scoped binding before retrying. Ambiguous network errors preserve the client message ID without another send. Every committed Assistant revision publishes `openchamber:assistants-changed` only after its SQLite transaction completes; clients use the tip to reload the authoritative snapshot after worker-driven stateless binding changes. `POST /:id/session/abort` requires the same `{ sessionID, sessionGeneration }`, returns the binding and `aborted: true`, reports a changed binding as `revision_conflict`, and preserves upstream missing-session `not_found` semantics.
|
|
14
87
|
|
|
15
88
|
`GET /api/openchamber/assistants/:assistantID/messages?before=&limit=` returns archived and current bindings as `{ entries: [{ sessionID, directory, info, parts }], nextCursor, complete }`. It reads assistant-owned mirror rows in chronological session and message order, pages toward older rows with an opaque stable cursor, and preserves the raw OpenCode `info` and `part` JSON. `directory` is the effective workspace for that source session and is nullable for legacy archived rows with an unknown workspace. `complete === (nextCursor === null)` marks arrival at the oldest available history. The service reads the latest persisted `limit + 1` rows first, then demand-backfills one page at a time from the newest incomplete archived session covered by the cursor; a request performs at most three upstream pages and reuses persisted pages without upstream access. Backfill page commits are independent. Each `session.messages` page attempt retries only transient 5xx/network-class SDK failures up to three times with short backoff; persistent failures still throw `upstream_error`. An authoritative OpenCode 404 for an archived or old binding marks that session's backfill complete via the `session-missing` reducer path, deletes only its uncovered event mirrors, preserves covered/admitted rows, and continues scanning other incomplete sessions so one deleted session cannot permanently block Assistant history. Successful backfill pages upsert authoritative rows without clearing provisional mirrors missing from that snapshot. Concurrent ensure that replaces a missing current binding archives the old ID and creates a new one; 404 completion never deletes covered history under either identity. A required-page failure surfaces as an upstream error only when the current request's `pageRows()` is empty; when the current range already has covered or provisional rows, the service returns that partial result and keeps the incomplete cursor retryable. Provisional event mirrors stay readable as `covered=0` fallbacks until a shrink-whitelist signal or a later authoritative upsert elevates them. Current-session live sync overrides the matching SQLite message identity in the UI.
|
|
16
89
|
|
|
@@ -20,4 +93,4 @@ Share operations persist operation identity, Assistant identity, payload hash, p
|
|
|
20
93
|
|
|
21
94
|
Schema v2 migrates legacy Inbox bindings once. It prefers the legacy Inbox session, then the latest successful operation or turn session. Schema v5 makes `workspace_path` nullable and migrates stored managed paths under `dataDir/assistant-workspaces/<assistantID>` to `NULL`. Schema v7 adds `mode` (`continuous` | `stateless`, default `continuous`) and preserves legacy `assistant.mode` during Inbox migration. Schema v10 establishes demand-driven history backfill semantics; existing mirror rows remain partial until a complete backfill state is persisted. New records use `assistant_v2` and carry no legacy topic field. Legacy topic, turn, operation, and assistant tables remain migration sources and compatibility backups. Runtime code never reads or writes them. Migration retains OpenCode sessions and messages.
|
|
22
95
|
|
|
23
|
-
Assistant workspaces resolve only under the Assistant workspace root or configured project roots. DTO serialization resolves and creates the managed target through the same containment checks, so project-configured Assistants expose a ready canonical destination for a future managed switch. Managed workspace creation resolves and creates `dataDir/assistant-workspaces/<assistantID>` before a session switch creates its OpenCode session and commits the configuration CAS. Route registration refreshes the authoritative project roots for each request. Each OpenCode session, prompt, message lookup, and skill discovery uses the effective workspace directory, so OpenCode applies its standard workspace skill discovery and configuration.
|
|
96
|
+
Assistant workspaces resolve only under the Assistant workspace root or configured project roots. DTO serialization resolves and creates the managed target through the same containment checks, so project-configured Assistants expose a ready canonical destination for a future managed switch. Managed workspace creation resolves and creates `dataDir/assistant-workspaces/<assistantID>` before a session switch creates its OpenCode session and commits the configuration CAS. Route registration refreshes the authoritative project roots for each request. Each OpenCode session, prompt, message lookup, and skill discovery uses the effective workspace directory, so OpenCode applies its standard workspace skill discovery and configuration. Contact turns additionally merge `~/.claude/skills`, `~/.agents/skills`, and the workspace `.claude/skills` plus `.agents/skills` into the pi system prompt (project wins on name) and append `defaultPrompt`. `skill_roots` is a legacy schema field; migration removes it from `assistant_v2`, and runtime DTOs, input payloads, and writes omit it. `close()` clears the share-retention scheduler and closes SQLite.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export class AssignError extends Error {
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'AssignError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ASSIGN_CODES = Object.freeze({
|
|
14
|
+
PROJECT_REQUIRED: 'project_required',
|
|
15
|
+
WORKSPACE_FORBIDDEN: 'workspace_forbidden',
|
|
16
|
+
WORKTREE_NOT_FOUND: 'worktree_not_found',
|
|
17
|
+
VALIDATION: 'validation_error',
|
|
18
|
+
UPSTREAM: 'upstream_error',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const PROJECT_REQUIRED_MESSAGE = 'No registered project is configured. Add a project in Settings before assigning work. Do not use assistant-workspaces.';
|
|
22
|
+
|
|
23
|
+
const contained = (candidate, root) => candidate === root || candidate.startsWith(`${root}${path.sep}`);
|
|
24
|
+
|
|
25
|
+
const trim = (value, max = 10_000) => {
|
|
26
|
+
if (typeof value !== 'string') return null;
|
|
27
|
+
const next = value.trim();
|
|
28
|
+
if (!next || next.length > max) return null;
|
|
29
|
+
return next;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const promptAdmitted = (result) => (
|
|
33
|
+
!result?.error
|
|
34
|
+
&& (result?.response?.status === 204 || result?.status === 204 || result?.data !== undefined || result?.response?.ok === true)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
function normalizeProjectRoots(allowedRoots = []) {
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
const roots = [];
|
|
40
|
+
for (const root of allowedRoots) {
|
|
41
|
+
if (typeof root !== 'string' || !root.trim()) continue;
|
|
42
|
+
const resolved = path.resolve(root.trim());
|
|
43
|
+
if (seen.has(resolved)) continue;
|
|
44
|
+
seen.add(resolved);
|
|
45
|
+
roots.push(resolved);
|
|
46
|
+
}
|
|
47
|
+
return roots;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isManagedAssistantWorkspace(candidate, managedWorkspaceRoot) {
|
|
51
|
+
if (!candidate || !managedWorkspaceRoot) return false;
|
|
52
|
+
return contained(path.resolve(candidate), path.resolve(managedWorkspaceRoot));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function resolveAssignDirectory({
|
|
56
|
+
projectPath,
|
|
57
|
+
directory,
|
|
58
|
+
branch,
|
|
59
|
+
allowedRoots,
|
|
60
|
+
managedWorkspaceRoot,
|
|
61
|
+
defaultProjectPath = null,
|
|
62
|
+
worktrees = [],
|
|
63
|
+
}) {
|
|
64
|
+
const roots = normalizeProjectRoots(allowedRoots);
|
|
65
|
+
if (roots.length === 0) {
|
|
66
|
+
throw new AssignError(ASSIGN_CODES.PROJECT_REQUIRED, PROJECT_REQUIRED_MESSAGE);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const rejectManaged = (resolved) => {
|
|
70
|
+
if (isManagedAssistantWorkspace(resolved, managedWorkspaceRoot)) {
|
|
71
|
+
throw new AssignError(
|
|
72
|
+
ASSIGN_CODES.WORKSPACE_FORBIDDEN,
|
|
73
|
+
'Assign cannot use assistant-workspaces. Choose a registered project path.',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const underRoots = (resolved) => roots.some((root) => contained(resolved, root));
|
|
79
|
+
|
|
80
|
+
const resolveExisting = (candidate) => {
|
|
81
|
+
const raw = trim(candidate, 4096);
|
|
82
|
+
if (!raw) return null;
|
|
83
|
+
const resolved = path.resolve(raw);
|
|
84
|
+
rejectManaged(resolved);
|
|
85
|
+
if (!underRoots(resolved)) {
|
|
86
|
+
throw new AssignError(
|
|
87
|
+
ASSIGN_CODES.WORKSPACE_FORBIDDEN,
|
|
88
|
+
'That path is not a registered project. Add it in Settings, then assign again.',
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
|
|
92
|
+
throw new AssignError(ASSIGN_CODES.VALIDATION, 'The project directory does not exist.');
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return fs.realpathSync(resolved);
|
|
96
|
+
} catch {
|
|
97
|
+
throw new AssignError(ASSIGN_CODES.VALIDATION, 'The project directory does not exist.');
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
let target = resolveExisting(directory) || resolveExisting(projectPath) || resolveExisting(defaultProjectPath);
|
|
102
|
+
|
|
103
|
+
if (!target && roots.length === 1) {
|
|
104
|
+
rejectManaged(roots[0]);
|
|
105
|
+
if (fs.existsSync(roots[0]) && fs.statSync(roots[0]).isDirectory()) {
|
|
106
|
+
target = fs.realpathSync(roots[0]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!target) {
|
|
111
|
+
throw new AssignError(
|
|
112
|
+
ASSIGN_CODES.PROJECT_REQUIRED,
|
|
113
|
+
'Choose a registered project path. Several projects are configured; assign cannot guess.',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const branchName = trim(branch, 256);
|
|
118
|
+
if (!branchName) return target;
|
|
119
|
+
|
|
120
|
+
const match = (Array.isArray(worktrees) ? worktrees : []).find((entry) => {
|
|
121
|
+
const entryBranch = trim(entry?.branch, 256);
|
|
122
|
+
if (!entryBranch) return false;
|
|
123
|
+
return entryBranch === branchName || entryBranch === `refs/heads/${branchName}`;
|
|
124
|
+
});
|
|
125
|
+
if (!match?.path) {
|
|
126
|
+
throw new AssignError(
|
|
127
|
+
ASSIGN_CODES.WORKTREE_NOT_FOUND,
|
|
128
|
+
`No existing worktree is checked out on ${branchName}. Create that worktree in Chat first, then assign again.`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return resolveExisting(match.path);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function assignSession(input = {}) {
|
|
135
|
+
const prompt = trim(input.prompt, 200_000);
|
|
136
|
+
if (!prompt) {
|
|
137
|
+
throw new AssignError(ASSIGN_CODES.VALIDATION, 'assign_session requires a coding prompt for the worker session.');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const projectDirectory = resolveAssignDirectory({
|
|
141
|
+
projectPath: input.projectPath,
|
|
142
|
+
directory: input.directory,
|
|
143
|
+
branch: null,
|
|
144
|
+
allowedRoots: input.allowedRoots,
|
|
145
|
+
managedWorkspaceRoot: input.managedWorkspaceRoot,
|
|
146
|
+
defaultProjectPath: input.defaultProjectPath,
|
|
147
|
+
worktrees: [],
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
let directory = projectDirectory;
|
|
151
|
+
const branchName = trim(input.branch, 256);
|
|
152
|
+
if (branchName && !trim(input.directory, 4096)) {
|
|
153
|
+
const worktrees = typeof input.listWorktrees === 'function'
|
|
154
|
+
? await input.listWorktrees(projectDirectory)
|
|
155
|
+
: (input.worktrees || []);
|
|
156
|
+
directory = resolveAssignDirectory({
|
|
157
|
+
projectPath: projectDirectory,
|
|
158
|
+
directory: projectDirectory,
|
|
159
|
+
branch: branchName,
|
|
160
|
+
allowedRoots: input.allowedRoots,
|
|
161
|
+
managedWorkspaceRoot: input.managedWorkspaceRoot,
|
|
162
|
+
worktrees,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const assistant = input.assistant && typeof input.assistant === 'object' ? input.assistant : {};
|
|
167
|
+
const providerID = trim(assistant.providerID, 256);
|
|
168
|
+
const modelID = trim(assistant.modelID, 256);
|
|
169
|
+
if (!providerID || !modelID) {
|
|
170
|
+
throw new AssignError(ASSIGN_CODES.VALIDATION, 'Assistant is missing a connected provider/model.');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const sessionID = trim(input.sessionID, 256);
|
|
174
|
+
const title = trim(input.title, 256) || prompt.slice(0, 80);
|
|
175
|
+
const messageID = trim(input.messageID, 256) || `msg_assign_${crypto.randomUUID()}`;
|
|
176
|
+
const model = { providerID, modelID };
|
|
177
|
+
const parts = [{ type: 'text', text: prompt }];
|
|
178
|
+
// Subscriber identity lives on `assigned`, not `assistant`.
|
|
179
|
+
// `openchamber.assistant.assistantID` marks a hidden system binding.
|
|
180
|
+
const metadata = {
|
|
181
|
+
openchamber: {
|
|
182
|
+
assigned: {
|
|
183
|
+
from: 'contact',
|
|
184
|
+
assistantID: assistant.id || assistant.assistantID || null,
|
|
185
|
+
name: assistant.name || null,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
const promptInput = {
|
|
190
|
+
directory,
|
|
191
|
+
messageID,
|
|
192
|
+
model,
|
|
193
|
+
parts,
|
|
194
|
+
...(trim(assistant.agent, 256) ? { agent: trim(assistant.agent, 256) } : {}),
|
|
195
|
+
...(trim(assistant.variant, 256) ? { variant: trim(assistant.variant, 256) } : {}),
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (sessionID) {
|
|
199
|
+
if (typeof input.promptExisting !== 'function') {
|
|
200
|
+
throw new AssignError(ASSIGN_CODES.UPSTREAM, 'Worker prompt is unavailable.');
|
|
201
|
+
}
|
|
202
|
+
const prompted = await input.promptExisting({ ...promptInput, sessionID });
|
|
203
|
+
if (!promptAdmitted(prompted)) {
|
|
204
|
+
throw new AssignError(ASSIGN_CODES.UPSTREAM, 'Failed to submit the prompt to the existing session.');
|
|
205
|
+
}
|
|
206
|
+
return { sessionID, directory, title, status: 'busy', reused: true, messageID, branch: branchName };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (typeof input.createSession !== 'function' || typeof input.promptExisting !== 'function') {
|
|
210
|
+
throw new AssignError(ASSIGN_CODES.UPSTREAM, 'Worker session APIs are unavailable.');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const created = await input.createSession({ directory, title, metadata });
|
|
214
|
+
const createdID = created?.data?.id || created?.id;
|
|
215
|
+
if (created?.error || !createdID) {
|
|
216
|
+
throw new AssignError(ASSIGN_CODES.UPSTREAM, 'Failed to create a coding session.');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const prompted = await input.promptExisting({ ...promptInput, sessionID: createdID });
|
|
220
|
+
if (!promptAdmitted(prompted)) {
|
|
221
|
+
throw new AssignError(ASSIGN_CODES.UPSTREAM, 'Failed to submit the prompt to the new session.');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { sessionID: createdID, directory, title, status: 'busy', reused: false, messageID, branch: branchName };
|
|
225
|
+
}
|