@openchambery/web 2.1.10-beta.8 → 2.1.10
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-CSTBp5FT.js → AgentAvatar-VWQTTR4M.js} +1 -1
- package/dist/assets/AssistantView-pFHs1mFy.js +3 -0
- package/dist/assets/DiagramView-DvtLUBju.js +1 -0
- package/dist/assets/{FadeInOnReveal-C-zbv53H.js → FadeInOnReveal-fsguD-xX.js} +1 -1
- package/dist/assets/Icon-CEQUcqTJ.js +1 -0
- package/dist/assets/{ImageLoadingPlaceholder-CN33uB-A.js → ImageLoadingPlaceholder-Bel8eqgg.js} +1 -1
- package/dist/assets/{LoadingImage-BR82n0vB.js → LoadingImage-DjkSZxMq.js} +1 -1
- package/dist/assets/MarkdownRenderer-DMk_HgEW.js +2 -0
- package/dist/assets/MarkdownRendererImpl-BD3MLIXK.js +10 -0
- package/dist/assets/MarkstreamRendererImpl-Bmq_rlW8.js +3 -0
- package/dist/assets/MobileDetailNavigation-BEKXW98F.js +1 -0
- package/dist/assets/MobileShareBridge-7uwkwoDd.js +4 -0
- package/dist/assets/MobileTabPageHeader-Bv5dbFRY.js +2 -0
- package/dist/assets/{MultiRunWindow-Dl6X54iv.js → MultiRunWindow-YPpInxnV.js} +1 -1
- package/dist/assets/OnboardingScreen-CQuSd1SL.js +1 -0
- package/dist/assets/{OverlayScrollbar-BY5HaNrb.js → OverlayScrollbar-DI271QYq.js} +1 -1
- package/dist/assets/{SettingsView-DJZEQaSF.js → SettingsView-rMvVrvoc.js} +11 -11
- package/dist/assets/{SettingsWindow-TvlhBd6w.js → SettingsWindow-Bu0pJmO4.js} +1 -1
- package/dist/assets/TerminalView-B7wbbWmJ.js +8 -0
- package/dist/assets/ToolOutputDialog-C7usOU7_.css +1 -0
- package/dist/assets/ToolOutputDialog-kGRZ3zBv.js +10 -0
- package/dist/assets/{appThemeRegistry-Du5KFmTd.js → appThemeRegistry-COjSA5s9.js} +1 -1
- package/dist/assets/assistant-attachment-cache-BWe8KqRS.js +1 -0
- package/dist/assets/{button-CWSs9Baz.js → button-9V3jJ3gG.js} +1 -1
- package/dist/assets/{collapsible-ngtoKADo.js → collapsible-Cx3lMy_W.js} +1 -1
- package/dist/assets/{desktopHosts-C0e7Nes2.js → desktopHosts-C4ejHJ9p.js} +1 -1
- package/dist/assets/desktopRelayRestore-UTCV51RC.js +1 -0
- package/dist/assets/{diagram-DmlTCkmV.js → diagram-Bpt5dSrL.js} +1 -1
- package/dist/assets/{dialog-BMa16qrs.js → dialog-Yxau54Tq.js} +1 -1
- package/dist/assets/dropdown-menu.styles-CtzReuqO.js +1 -0
- package/dist/assets/{es-ChfgOskU.js → es-DsJ_HQyU.js} +1 -1
- package/dist/assets/{fileMentionTouchSelection-DZvVbBP-.js → fileMentionTouchSelection-Hibjg1ml.js} +1 -1
- package/dist/assets/{fr-DnKqN4E3.js → fr-Dfkjku77.js} +1 -1
- package/dist/assets/{gitApi-DdM2FGUu.js → gitApi-HQKLs8JU.js} +3 -3
- package/dist/assets/image-loading-ClZBSNMo.js +1 -0
- package/dist/assets/{input-CALwaZEB.js → input-BqE6rFfR.js} +1 -1
- package/dist/assets/insertionBoundaries-I_znkRIX.js +1 -0
- package/dist/assets/{ja-DH-a_ThB.js → ja-C71g0jYh.js} +1 -1
- package/dist/assets/{ko-BXABOKd9.js → ko-32s8rv0M.js} +1 -1
- package/dist/assets/main-BwNzKZuT.js +169 -0
- package/dist/assets/main-DfS2IaSq.js +2 -0
- package/dist/assets/{markdownHeightCache-DXM_5jTe.js → markdownHeightCache-lTJfaoDe.js} +1 -1
- package/dist/assets/mcpQueries-MXNO55dw.js +1 -0
- package/dist/assets/miniChat-CMt8PqCU.js +2 -0
- package/dist/assets/mobile-BaUwTAK3.js +2 -0
- package/dist/assets/{mobileBackNavigation-DYaEgNKl.js → mobileBackNavigation-DgrGA2Qw.js} +1 -1
- package/dist/assets/multirun-DN46tuiy.js +3 -0
- package/dist/assets/persistence-CAKBi4bp.js +3 -0
- package/dist/assets/{pl-D-KNROrB.js → pl-eAbyJFO9.js} +1 -1
- package/dist/assets/platform-C5jNCIbB.js +7 -0
- package/dist/assets/projectMeta-BV2S2dYM.js +1 -0
- package/dist/assets/{pt-BR-UDs5YUzv.js → pt-BR-nVxduPt0.js} +1 -1
- package/dist/assets/queryRuntime-DRiRiAgZ.js +1 -0
- package/dist/assets/renderElectronMiniChatApp-BOMvMWpo.js +2 -0
- package/dist/assets/renderMobileApp-BGP-Q03v.css +1 -0
- package/dist/assets/renderMobileApp-DX0VWU7B.js +28 -0
- package/dist/assets/runtime-file-download-CEzr1Pfg.js +1 -0
- package/dist/assets/runtime-file-resource-BUFMRmIF.js +2 -0
- package/dist/assets/runtimeConfig-BTc8J1kY.js +2 -0
- package/dist/assets/runtimeEndpointReset-BiSu_pxJ.js +149 -0
- package/dist/assets/{scheduledTaskNavigation-DQcLqQ52.js → scheduledTaskNavigation-j6F6n0ym.js} +1 -1
- package/dist/assets/sessionChangeSummary-Bz22Q8e1.js +1 -0
- package/dist/assets/{sessionLookup-Dayma9hk.js → sessionLookup-5rpUSv3O.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-CscHgCaq.js → sortable-tabs-strip-DFi-Mck5.js} +1 -1
- package/dist/assets/speechSettingsQueries-C8efV361.js +4 -0
- package/dist/assets/src-DzBIReeh.css +2 -0
- package/dist/assets/{store-CG7Ytsg9.js → store-D4rR0svj.js} +2 -2
- package/dist/assets/terminalApi-B8arordk.js +1 -0
- package/dist/assets/{textarea-CAOC2ViF.js → textarea-DoWm-pRV.js} +1 -1
- package/dist/assets/{tooltip-CV6dvLBp.js → tooltip-DWlA7PRY.js} +1 -1
- package/dist/assets/{uk-CkliYeRs.js → uk-DOvCFanR.js} +1 -1
- package/dist/assets/{url-nBOUbtV_.js → url-BD7mCWgV.js} +1 -1
- package/dist/assets/{useAppFontEffects-Cf-BIyAh.js → useAppFontEffects-DAI7VfQG.js} +51 -51
- package/dist/assets/{useDesktopWindowControlsLayout-BgLopOdv.js → useDesktopWindowControlsLayout-BjvIMnxi.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-BljJHzlP.js → useEffectiveDirectory-CuXlnig5.js} +1 -1
- package/dist/assets/{useFontPreferences-DqEIKMEW.js → useFontPreferences-Ctm4A_2D.js} +1 -1
- package/dist/assets/useMobileNavigationStore-D47T6Oi2.js +417 -0
- package/dist/assets/{useWorkerHighlightedLines-r5Bij0Em.js → useWorkerHighlightedLines-m2onDaI2.js} +1 -1
- package/dist/assets/{zh-CN-BH4APVTu.js → zh-CN-CuYFLeS4.js} +2 -2
- package/dist/assets/{zh-TW-BEi6Yf50.js → zh-TW-DKJIyd5A.js} +2 -2
- package/dist/index.html +13 -10
- package/dist/mini-chat.html +13 -10
- package/dist/mobile.html +13 -10
- package/package.json +1 -1
- package/server/lib/content-search/DOCUMENTATION.md +5 -1
- package/server/lib/content-search/runtime.test.js +6 -2
- package/server/lib/content-search/service.js +7 -2
- package/server/lib/content-search/service.test.js +20 -0
- package/server/lib/fs/DOCUMENTATION.md +4 -1
- package/server/lib/fs/image-fingerprint.d.ts +2 -0
- package/server/lib/fs/image-fingerprint.js +43 -0
- package/server/lib/fs/image-fingerprint.test.js +54 -0
- package/server/lib/fs/raw-video.test.js +34 -0
- package/server/lib/fs/routes.js +16 -6
- package/server/lib/opencode/proxy.js +2 -1
- package/server/lib/opencode/proxy.test.js +13 -0
- package/server/lib/quota/DOCUMENTATION.md +2 -0
- package/server/lib/quota/magpie.js +3 -0
- package/server/lib/quota/magpie.test.js +14 -0
- package/server/lib/relay/relay-server.e2e.test.ts +8 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +8 -0
- package/server/lib/session-turn-pages/native-payload.d.ts +2 -0
- package/server/lib/session-turn-pages/native-payload.js +38 -0
- package/server/lib/session-turn-pages/native-payload.test.js +34 -0
- package/server/lib/session-turn-pages/routes.js +2 -1
- package/server/lib/session-turn-pages/routes.test.js +18 -0
- package/dist/assets/AssistantView-sg_gJwXp.js +0 -3
- package/dist/assets/DiagramView-BW-3spOQ.js +0 -1
- package/dist/assets/Icon-BCFeHDWx.js +0 -1
- package/dist/assets/MarkdownRenderer-LEXHIyuj.js +0 -2
- package/dist/assets/MarkdownRendererImpl-Xs4gJB0s.js +0 -10
- package/dist/assets/MarkstreamRendererImpl-RXTIFS1c.js +0 -3
- package/dist/assets/MobileDetailNavigation-Bx1Vbirv.js +0 -1
- package/dist/assets/MobileShareBridge-DKsBX4J6.js +0 -4
- package/dist/assets/MobileTabPageHeader-BIbvqv8n.js +0 -2
- package/dist/assets/OnboardingScreen-CqcZEptL.js +0 -1
- package/dist/assets/TerminalView-By3D_hLv.js +0 -8
- package/dist/assets/ToolOutputDialog-DesCcdFD.css +0 -1
- package/dist/assets/ToolOutputDialog-Dt49fiUz.js +0 -10
- package/dist/assets/assistant-attachment-cache-CecNXB7z.js +0 -1
- package/dist/assets/desktopRelayRestore-DckkHKsi.js +0 -1
- package/dist/assets/dropdown-menu.styles-BNwZmBRp.js +0 -1
- package/dist/assets/fileQueries-DVpZC5ha.js +0 -1
- package/dist/assets/image-loading-DImQz00b.js +0 -1
- package/dist/assets/insertionBoundaries-D5hR7Bnk.js +0 -1
- package/dist/assets/main-C4f3ftkw.js +0 -2
- package/dist/assets/main-M6NuXmS9.js +0 -169
- package/dist/assets/mcpQueries-BJQRGKlF.js +0 -1
- package/dist/assets/miniChat-haVuHJtE.js +0 -2
- package/dist/assets/mobile-AgEB6KUi.js +0 -2
- package/dist/assets/multirun-CLywRk_h.js +0 -3
- package/dist/assets/persistence-BKszKaoQ.js +0 -3
- package/dist/assets/platform-CgwxwfA4.js +0 -7
- package/dist/assets/projectMeta-CmY-ALGU.js +0 -1
- package/dist/assets/relay-image-stream-CqgxIcA5.js +0 -2
- package/dist/assets/renderElectronMiniChatApp-G3oei-lb.js +0 -2
- package/dist/assets/renderMobileApp-CLKJYLRa.js +0 -28
- package/dist/assets/runtime-fetch-WgH0pROs.js +0 -1
- package/dist/assets/runtimeConfig-wXAjg8Wj.js +0 -2
- package/dist/assets/runtimeEndpointReset-BcpbVtkr.js +0 -149
- package/dist/assets/sessionChangeSummary-DIIJkyzk.js +0 -1
- package/dist/assets/speechSettingsQueries-F67f52-o.js +0 -4
- package/dist/assets/src-azLZheh6.css +0 -2
- package/dist/assets/terminalApi-rxsMWL0o.js +0 -1
- package/dist/assets/useMobileNavigationStore-Bc3q1mPg.js +0 -417
- package/dist/assets/useRuntimeAPIs-Cx8S1HLz.js +0 -1
- package/dist/assets/vendor-legendapp-list-CNxU_ALy.js +0 -1
package/dist/index.html
CHANGED
|
@@ -552,28 +552,31 @@
|
|
|
552
552
|
opacity: 0.72;
|
|
553
553
|
}
|
|
554
554
|
</style>
|
|
555
|
-
<script type="module" crossorigin src="/assets/main-
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-DfS2IaSq.js"></script>
|
|
556
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
557
557
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-BpCQ-dPr.js">
|
|
558
558
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-CMiMO5Yj.js">
|
|
559
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
559
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
560
|
+
<link rel="modulepreload" crossorigin href="/assets/queryRuntime-DRiRiAgZ.js">
|
|
560
561
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CdzJ6My_.js">
|
|
561
562
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-o69pgTEK.js">
|
|
562
563
|
<link rel="modulepreload" crossorigin href="/assets/useUIStore-CEqU9Ujv.js">
|
|
563
564
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BP2cRVqX.js">
|
|
564
565
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
565
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
566
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
567
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
568
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-DckkHKsi.js">
|
|
566
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-CAKBi4bp.js">
|
|
567
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-C4ejHJ9p.js">
|
|
568
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-UTCV51RC.js">
|
|
569
569
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-MF2GBrFx.js">
|
|
570
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-react-query-CdqCAt40.js">
|
|
570
571
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-xol_6BsB.js">
|
|
571
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
572
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
572
|
+
<link rel="modulepreload" crossorigin href="/assets/runtime-file-resource-BUFMRmIF.js">
|
|
573
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-C5jNCIbB.js">
|
|
574
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-B8arordk.js">
|
|
575
|
+
<link rel="modulepreload" crossorigin href="/assets/runtime-file-download-CEzr1Pfg.js">
|
|
573
576
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
574
577
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-KNLHDj7G.js">
|
|
575
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
576
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
578
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BTc8J1kY.js">
|
|
579
|
+
<link rel="stylesheet" crossorigin href="/assets/src-DzBIReeh.css">
|
|
577
580
|
</head>
|
|
578
581
|
<body class="h-full bg-background text-foreground">
|
|
579
582
|
<!-- Keep the startup layer outside #root. React replaces every child of
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,28 +74,31 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-CMt8PqCU.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-BpCQ-dPr.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-CMiMO5Yj.js">
|
|
81
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
81
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
82
|
+
<link rel="modulepreload" crossorigin href="/assets/queryRuntime-DRiRiAgZ.js">
|
|
82
83
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CdzJ6My_.js">
|
|
83
84
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-o69pgTEK.js">
|
|
84
85
|
<link rel="modulepreload" crossorigin href="/assets/useUIStore-CEqU9Ujv.js">
|
|
85
86
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BP2cRVqX.js">
|
|
86
87
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
87
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
88
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
89
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
90
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-DckkHKsi.js">
|
|
88
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-CAKBi4bp.js">
|
|
89
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-C4ejHJ9p.js">
|
|
90
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-UTCV51RC.js">
|
|
91
91
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-MF2GBrFx.js">
|
|
92
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-react-query-CdqCAt40.js">
|
|
92
93
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-xol_6BsB.js">
|
|
93
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
94
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
94
|
+
<link rel="modulepreload" crossorigin href="/assets/runtime-file-resource-BUFMRmIF.js">
|
|
95
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-C5jNCIbB.js">
|
|
96
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-B8arordk.js">
|
|
97
|
+
<link rel="modulepreload" crossorigin href="/assets/runtime-file-download-CEzr1Pfg.js">
|
|
95
98
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
96
99
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-KNLHDj7G.js">
|
|
97
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
98
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
100
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BTc8J1kY.js">
|
|
101
|
+
<link rel="stylesheet" crossorigin href="/assets/src-DzBIReeh.css">
|
|
99
102
|
</head>
|
|
100
103
|
<body class="h-full bg-background text-foreground">
|
|
101
104
|
<div id="root" class="h-full">
|
package/dist/mobile.html
CHANGED
|
@@ -63,28 +63,31 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-BaUwTAK3.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-BpCQ-dPr.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-CMiMO5Yj.js">
|
|
70
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
70
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
71
|
+
<link rel="modulepreload" crossorigin href="/assets/queryRuntime-DRiRiAgZ.js">
|
|
71
72
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CdzJ6My_.js">
|
|
72
73
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-o69pgTEK.js">
|
|
73
74
|
<link rel="modulepreload" crossorigin href="/assets/useUIStore-CEqU9Ujv.js">
|
|
74
75
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BP2cRVqX.js">
|
|
75
76
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
76
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
77
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
78
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
79
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-DckkHKsi.js">
|
|
77
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-CAKBi4bp.js">
|
|
78
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-C4ejHJ9p.js">
|
|
79
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-UTCV51RC.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-client-MF2GBrFx.js">
|
|
81
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-react-query-CdqCAt40.js">
|
|
81
82
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-xol_6BsB.js">
|
|
82
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
83
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
83
|
+
<link rel="modulepreload" crossorigin href="/assets/runtime-file-resource-BUFMRmIF.js">
|
|
84
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-C5jNCIbB.js">
|
|
85
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-B8arordk.js">
|
|
86
|
+
<link rel="modulepreload" crossorigin href="/assets/runtime-file-download-CEzr1Pfg.js">
|
|
84
87
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
85
88
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-KNLHDj7G.js">
|
|
86
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
87
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
89
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BTc8J1kY.js">
|
|
90
|
+
<link rel="stylesheet" crossorigin href="/assets/src-DzBIReeh.css">
|
|
88
91
|
</head>
|
|
89
92
|
<body class="h-full bg-background text-foreground">
|
|
90
93
|
<div id="root" class="h-full"></div>
|
package/package.json
CHANGED
|
@@ -76,7 +76,11 @@ active pass before closing SQLite.
|
|
|
76
76
|
|
|
77
77
|
`GET /api/openchamber/content-search?q=...&limit=30` returns `{hits,index}`.
|
|
78
78
|
Each hit includes authoritative session title and directory plus message identity,
|
|
79
|
-
role, created time and a plain snippet.
|
|
79
|
+
role, created time and a plain snippet. Each session contributes only its newest
|
|
80
|
+
matching message (created time descending, message ID descending for ties).
|
|
81
|
+
Deduplication happens before `limit`, which counts sessions rather than messages;
|
|
82
|
+
results are ordered by that matching message's recency, then session ID for ties.
|
|
83
|
+
The optional `directory` narrows searches;
|
|
80
84
|
omitting it searches every project. The endpoint uses the existing Host auth and
|
|
81
85
|
rejects preview-proxy credentials. Errors are not converted to empty successes.
|
|
82
86
|
An empty query returns index coverage/progress without hits. Index phase is
|
|
@@ -111,7 +111,9 @@ test('expands existing seven-day checkpoints to thirty days even when sessions a
|
|
|
111
111
|
},
|
|
112
112
|
});
|
|
113
113
|
await runtime.sync();
|
|
114
|
-
expect(service.search('host', 'history').map((hit) => hit.messageID)).toEqual(['m10'
|
|
114
|
+
expect(service.search('host', 'history').map((hit) => hit.messageID)).toEqual(['m10']);
|
|
115
|
+
expect(service.search('host', 'history 29').map((hit) => hit.messageID)).toEqual(['m29']);
|
|
116
|
+
expect(service.search('host', 'history 31')).toEqual([]);
|
|
115
117
|
expect(runtime.status().since).toBe(now - 30 * day);
|
|
116
118
|
await runtime.sync();
|
|
117
119
|
expect(reads).toBe(1);
|
|
@@ -131,7 +133,9 @@ test('pages older messages, excludes expired text, and reuses checkpoints after
|
|
|
131
133
|
};
|
|
132
134
|
const runtime = createContentSearchRuntime(dependencies);
|
|
133
135
|
await runtime.sync();
|
|
134
|
-
expect(service.search('host', 'needle').map((hit) => hit.messageID)).toEqual(['latest'
|
|
136
|
+
expect(service.search('host', 'needle').map((hit) => hit.messageID)).toEqual(['latest']);
|
|
137
|
+
expect(service.search('host', 'needle older').map((hit) => hit.messageID)).toEqual(['older']);
|
|
138
|
+
expect(service.search('host', 'needle expired')).toEqual([]);
|
|
135
139
|
expect(reads).toBe(3);
|
|
136
140
|
await runtime.stop();
|
|
137
141
|
const restarted = createContentSearchRuntime(dependencies);
|
|
@@ -93,9 +93,14 @@ export function createContentSearchService({ dbPath }) {
|
|
|
93
93
|
if (directory) { conditions.push('s.directory=?'); args.push(directory); }
|
|
94
94
|
if (match) { conditions.push('m.id IN (SELECT rowid FROM search_fts WHERE search_fts MATCH ?)'); args.push(match); }
|
|
95
95
|
args.push(Math.min(50, Math.max(1, limit)));
|
|
96
|
-
return db.prepare(`
|
|
96
|
+
return db.prepare(`WITH matches AS (
|
|
97
|
+
SELECT m.id, ROW_NUMBER() OVER (PARTITION BY m.session ORDER BY m.created DESC,m.message DESC) AS position
|
|
97
98
|
FROM search_message m JOIN search_session s ON s.runtime=m.runtime AND s.id=m.session
|
|
98
|
-
WHERE ${conditions.join(' AND ')}
|
|
99
|
+
WHERE ${conditions.join(' AND ')}
|
|
100
|
+
) SELECT m.session AS sessionID,m.message AS messageID,m.role,m.created AS createdAt,m.body,s.title,s.directory
|
|
101
|
+
FROM matches JOIN search_message m ON m.id=matches.id
|
|
102
|
+
JOIN search_session s ON s.runtime=m.runtime AND s.id=m.session
|
|
103
|
+
WHERE matches.position=1 ORDER BY m.created DESC,m.message DESC,m.session DESC LIMIT ?`).all(...args).map(({ body, ...hit }) => {
|
|
99
104
|
const start = Math.max(0, Math.min(...terms.map((term) => body.toLowerCase().indexOf(term))) - 60);
|
|
100
105
|
const end = Math.min(body.length, start + 300);
|
|
101
106
|
return { ...hit, snippet: (start ? '…' : '') + body.slice(start, end) + (end < body.length ? '…' : '') };
|
|
@@ -8,6 +8,26 @@ import path from 'node:path';
|
|
|
8
8
|
const services = [];
|
|
9
9
|
afterEach(() => services.splice(0).forEach((service) => service.close()));
|
|
10
10
|
|
|
11
|
+
test.each(['needle', '短词'])('limits unique sessions and selects their newest exact match: %s', (query) => {
|
|
12
|
+
const service = createContentSearchService({ dbPath: ':memory:' });
|
|
13
|
+
services.push(service);
|
|
14
|
+
const record = (id, created, text) => ({
|
|
15
|
+
info: { id, role: 'user', time: { created } }, parts: [{ type: 'text', text }],
|
|
16
|
+
});
|
|
17
|
+
service.replaceSession('host', { id: 'busy', directory: '/one' }, [
|
|
18
|
+
...Array.from({ length: 200 }, (_, i) => record(`m-${i}`, 100 + i, query)),
|
|
19
|
+
record('z-tie', 299, `${query} newest matching message`),
|
|
20
|
+
record('unmatched', 400, 'unrelated'),
|
|
21
|
+
]);
|
|
22
|
+
service.replaceSession('host', { id: 'other', directory: '/two' }, [record('older', 1, query)]);
|
|
23
|
+
service.replaceSession('foreign', { id: 'busy', directory: '/one' }, [record('foreign', 500, query)]);
|
|
24
|
+
const hits = service.search('host', query, { limit: 2 });
|
|
25
|
+
expect(hits.map((hit) => [hit.sessionID, hit.messageID])).toEqual([['busy', 'z-tie'], ['other', 'older']]);
|
|
26
|
+
expect(hits[0].snippet).toContain('newest matching message');
|
|
27
|
+
expect(service.search('host', query, { limit: 1 })).toEqual(hits.slice(0, 1));
|
|
28
|
+
expect(service.search('host', query, { directory: '/two' })).toEqual(hits.slice(1));
|
|
29
|
+
});
|
|
30
|
+
|
|
11
31
|
test.each([false, true])('upgrades old checkpoints (existing boundary: %s) without inventing coverage and persists initialization across database reopen', (hasBoundary) => {
|
|
12
32
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'content-index-'));
|
|
13
33
|
const dbPath = path.join(directory, 'index.sqlite');
|
|
@@ -13,7 +13,7 @@ Own filesystem API behavior for the web server runtime, including file operation
|
|
|
13
13
|
- `GET /api/fs/home`
|
|
14
14
|
- `POST /api/fs/mkdir`
|
|
15
15
|
- `GET /api/fs/read`
|
|
16
|
-
- `GET /api/fs/stat` (includes an `isBinary` classification from an 8 KiB file sample)
|
|
16
|
+
- `GET /api/fs/stat` (includes an `isBinary` classification from an 8 KiB file sample). Opt-in `fingerprint=true` returns content `sha256` for files up to 32 MiB, after the existing read policy and realpath checks. `image-fingerprint.js` owns a bounded 128-path memo keyed by device/inode/size/mtimeNs/ctimeNs; hashing uses serialized 256 KiB reads and rejects mutation during the read. Same-size writes restoring mtime still change ctime. Responses are `no-store`; callers must authorize/stat again before using durable bytes. Hashing is local filesystem work, not a second network body download. VS Code reuses this helper after its own path authorization.
|
|
17
17
|
- `GET /api/fs/raw`
|
|
18
18
|
- `GET /api/fs/serve/:path(*)`
|
|
19
19
|
- `POST /api/fs/write`
|
|
@@ -35,6 +35,9 @@ Own filesystem API behavior for the web server runtime, including file operation
|
|
|
35
35
|
- `index.js` no longer owns FS route handlers or FS exec job state.
|
|
36
36
|
|
|
37
37
|
## Notes for contributors
|
|
38
|
+
- Stat includes `mimeType` and `filename`; opt-in SHA-256 is omitted above 32 MiB rather than rejecting otherwise valid files. Empty files can be fingerprinted.
|
|
39
|
+
- `/api/fs/serve` retains its original URL/body/MIME and relative-resource behavior. After existing authorization it emits `Cache-Control: private, no-cache`, `Vary: Authorization, Cookie`, and a weak size/mtime/ctime ETag; `sendFile` streams the body and handles conditional requests. This is browser-private revalidation, not a durable-cache guarantee. Tests cover relative CSS/JS, 304, updates and deletion.
|
|
40
|
+
- `/api/fs/raw` also streams non-video bodies above 32 MiB. Client complete-body persistence is bounded independently; cold Range responses are not published as complete entries.
|
|
38
41
|
- `/api/fs/raw` serves video MIME types (MP4/M4V/MOV/WebM/OGV) using Express's
|
|
39
42
|
streamed `sendFile` after the existing path authorization/realpath checks.
|
|
40
43
|
Explicit Range requests and HEAD use the same path: 206/Content-Range,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const MAX_BYTES = 32 * 1024 * 1024;
|
|
4
|
+
const versionOf = (stat) => [stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].join(':');
|
|
5
|
+
|
|
6
|
+
/** Called only after the owning route has authorized and canonicalized the path. */
|
|
7
|
+
export const createImageFingerprintReader = (fs) => {
|
|
8
|
+
const entries = new Map();
|
|
9
|
+
let tail = Promise.resolve();
|
|
10
|
+
return async (file) => {
|
|
11
|
+
const before = await fs.stat(file, { bigint: true });
|
|
12
|
+
if (!before.isFile() || before.size < 0 || before.size > MAX_BYTES) throw new Error('Invalid file fingerprint size');
|
|
13
|
+
const version = versionOf(before);
|
|
14
|
+
const previous = entries.get(file);
|
|
15
|
+
if (previous?.version === version) return previous.promise;
|
|
16
|
+
const promise = tail.then(async () => {
|
|
17
|
+
const handle = await fs.open(file, 'r');
|
|
18
|
+
try {
|
|
19
|
+
if (versionOf(await handle.stat({ bigint: true })) !== version) throw new Error('Image changed during fingerprint read');
|
|
20
|
+
const hash = createHash('sha256');
|
|
21
|
+
const buffer = Buffer.allocUnsafe(256 * 1024);
|
|
22
|
+
let total = 0;
|
|
23
|
+
for (;;) {
|
|
24
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
|
|
25
|
+
if (!bytesRead) break;
|
|
26
|
+
total += bytesRead;
|
|
27
|
+
if (total > MAX_BYTES) throw new Error('Image exceeds fingerprint limit');
|
|
28
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
29
|
+
}
|
|
30
|
+
const after = await fs.stat(file, { bigint: true });
|
|
31
|
+
if (versionOf(after) !== version || total !== Number(before.size)) throw new Error('Image changed during fingerprint read');
|
|
32
|
+
return hash.digest('hex');
|
|
33
|
+
} finally { await handle.close(); }
|
|
34
|
+
});
|
|
35
|
+
tail = promise.then(() => undefined, () => undefined);
|
|
36
|
+
const entry = { version, promise };
|
|
37
|
+
entries.delete(file);
|
|
38
|
+
entries.set(file, entry);
|
|
39
|
+
while (entries.size > 128) entries.delete(entries.keys().next().value);
|
|
40
|
+
try { return await promise; }
|
|
41
|
+
catch (error) { if (entries.get(file) === entry) entries.delete(file); throw error; }
|
|
42
|
+
};
|
|
43
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { afterEach, expect, test } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import express from 'express';
|
|
7
|
+
import { registerFsRoutes } from './routes.js';
|
|
8
|
+
import { createImageFingerprintReader } from './image-fingerprint.js';
|
|
9
|
+
|
|
10
|
+
const cleanups = [];
|
|
11
|
+
afterEach(async () => { for (const cleanup of cleanups.splice(0).reverse()) await cleanup(); });
|
|
12
|
+
const fixture = async () => {
|
|
13
|
+
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-image-fingerprint-'));
|
|
14
|
+
cleanups.push(() => fs.rm(directory, { recursive: true, force: true }));
|
|
15
|
+
const file = path.join(directory, 'a.png');
|
|
16
|
+
await fs.writeFile(file, 'first');
|
|
17
|
+
return { directory, file };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
test('fingerprints are content hashes; unchanged files avoid rehash; same-size preserved-mtime writes invalidate', async () => {
|
|
21
|
+
const { file, directory } = await fixture();
|
|
22
|
+
let reads = 0;
|
|
23
|
+
const reader = createImageFingerprintReader({ ...fs, open: (...args) => { reads++; return fs.open(...args); } });
|
|
24
|
+
const first = await reader(file);
|
|
25
|
+
expect(first).toBe(createHash('sha256').update('first').digest('hex'));
|
|
26
|
+
expect(await reader(file)).toBe(first);
|
|
27
|
+
expect(reads).toBe(1);
|
|
28
|
+
const duplicate = path.join(directory, 'copy.png');
|
|
29
|
+
await fs.copyFile(file, duplicate);
|
|
30
|
+
expect(await reader(duplicate)).toBe(first);
|
|
31
|
+
const stat = await fs.stat(file);
|
|
32
|
+
await fs.writeFile(file, 'other');
|
|
33
|
+
await fs.utimes(file, stat.atime, stat.mtime);
|
|
34
|
+
expect(await reader(file)).toBe(createHash('sha256').update('other').digest('hex'));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('real stat route returns authorized opt-in SHA-256 metadata without image body', async () => {
|
|
38
|
+
const { file, directory } = await fixture();
|
|
39
|
+
const app = express();
|
|
40
|
+
registerFsRoutes({ get: (route, handler) => { if (route === '/api/fs/stat') app.get(route, handler); }, post() {}, put() {} }, {
|
|
41
|
+
os, path, fsPromises: fs, spawn() {}, crypto: globalThis.crypto,
|
|
42
|
+
normalizeDirectoryPath: value => value, resolveProjectDirectory: async () => ({ directory }),
|
|
43
|
+
buildAugmentedPath: () => '', resolveGitBinaryForSpawn: () => 'git', openchamberUserConfigRoot: directory,
|
|
44
|
+
});
|
|
45
|
+
const server = app.listen(0, '127.0.0.1');
|
|
46
|
+
await new Promise(resolve => server.once('listening', resolve));
|
|
47
|
+
cleanups.push(() => new Promise(resolve => { server.close(resolve); server.closeAllConnections(); }));
|
|
48
|
+
const url = `http://127.0.0.1:${server.address().port}/api/fs/stat?path=${encodeURIComponent(file)}`;
|
|
49
|
+
expect((await (await fetch(url)).json()).sha256).toBeUndefined();
|
|
50
|
+
const response = await fetch(`${url}&fingerprint=true`);
|
|
51
|
+
expect(response.headers.get('cache-control')).toBe('no-store');
|
|
52
|
+
expect((await response.json()).sha256).toBe(createHash('sha256').update('first').digest('hex'));
|
|
53
|
+
expect((await fetch(`${url}-missing&fingerprint=true`)).status).toBe(404);
|
|
54
|
+
});
|
|
@@ -8,6 +8,40 @@ import { registerFsRoutes } from './routes.js';
|
|
|
8
8
|
const cleanups = [];
|
|
9
9
|
afterEach(async () => { for (const cleanup of cleanups.splice(0).reverse()) await cleanup(); });
|
|
10
10
|
|
|
11
|
+
test('HTML and relative assets retain URLs and privately revalidate after file authorization', async () => {
|
|
12
|
+
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-html-revalidate-'));
|
|
13
|
+
cleanups.push(() => fs.rm(directory, { recursive: true, force: true }));
|
|
14
|
+
const html = '<!doctype html><link rel="stylesheet" href="style.css"><script src="app.js"></script><p>original</p>';
|
|
15
|
+
await fs.writeFile(path.join(directory, 'index.html'), html);
|
|
16
|
+
await fs.writeFile(path.join(directory, 'style.css'), 'p { color: red }');
|
|
17
|
+
await fs.writeFile(path.join(directory, 'app.js'), 'window.relativeScriptLoaded = true');
|
|
18
|
+
const app = express();
|
|
19
|
+
registerFsRoutes({ get(route, handler) { if (route instanceof RegExp) app.get(route, handler); }, post() {}, put() {} }, {
|
|
20
|
+
os, path, fsPromises: fs, spawn() {}, crypto: globalThis.crypto,
|
|
21
|
+
normalizeDirectoryPath: value => value, resolveProjectDirectory: async () => ({ directory }),
|
|
22
|
+
buildAugmentedPath: () => '', resolveGitBinaryForSpawn: () => 'git', openchamberUserConfigRoot: directory,
|
|
23
|
+
});
|
|
24
|
+
const server = app.listen(0, '127.0.0.1');
|
|
25
|
+
await new Promise(resolve => server.once('listening', resolve));
|
|
26
|
+
cleanups.push(() => new Promise(resolve => { server.close(resolve); server.closeAllConnections(); }));
|
|
27
|
+
const url = `http://127.0.0.1:${server.address().port}/api/fs/serve${directory}/index.html`;
|
|
28
|
+
const first = await fetch(url);
|
|
29
|
+
expect(first.headers.get('cache-control')).toBe('private, no-cache');
|
|
30
|
+
expect(first.headers.get('vary')).toContain('Authorization');
|
|
31
|
+
expect(first.headers.get('vary')).toContain('Cookie');
|
|
32
|
+
expect(await first.text()).toBe(html);
|
|
33
|
+
const etag = first.headers.get('etag');
|
|
34
|
+
const hit = await fetch(url, { headers: { 'If-None-Match': etag, 'Cache-Control': 'max-age=0' } });
|
|
35
|
+
expect(hit.status).toBe(304);
|
|
36
|
+
expect(await hit.text()).toBe('');
|
|
37
|
+
expect(await (await fetch(new URL('style.css', url))).text()).toBe('p { color: red }');
|
|
38
|
+
expect(await (await fetch(new URL('app.js', url))).text()).toContain('relativeScriptLoaded');
|
|
39
|
+
await fs.writeFile(path.join(directory, 'index.html'), html.replace('original', 'modified'));
|
|
40
|
+
expect((await fetch(url, { headers: { 'If-None-Match': etag } })).status).toBe(200);
|
|
41
|
+
await fs.unlink(path.join(directory, 'index.html'));
|
|
42
|
+
expect((await fetch(url, { headers: { 'If-None-Match': etag } })).status).toBe(404);
|
|
43
|
+
});
|
|
44
|
+
|
|
11
45
|
test('raw video supports first-byte probing, tail seeking, HEAD and unsatisfiable ranges', async () => {
|
|
12
46
|
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-video-range-'));
|
|
13
47
|
cleanups.push(() => fs.rm(directory, { recursive: true, force: true }));
|
package/server/lib/fs/routes.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createImageFingerprintReader } from './image-fingerprint.js';
|
|
2
3
|
import { createRealpathCache } from '../path-realpath-cache.js';
|
|
3
4
|
import nodeFsPromises from 'node:fs/promises';
|
|
4
5
|
import nodePath from 'node:path';
|
|
@@ -17,6 +18,7 @@ const EXEC_JOB_TTL_MS = 30 * 60 * 1000;
|
|
|
17
18
|
const OUTSIDE_FILE_GRANT_TTL_MS = 10 * 60 * 1000;
|
|
18
19
|
|
|
19
20
|
const outsideFileGrants = new Map();
|
|
21
|
+
const readImageFingerprint = createImageFingerprintReader(nodeFsPromises);
|
|
20
22
|
|
|
21
23
|
const pruneOutsideFileGrants = () => {
|
|
22
24
|
const now = Date.now();
|
|
@@ -745,7 +747,11 @@ export const registerFsRoutes = (app, dependencies) => {
|
|
|
745
747
|
}
|
|
746
748
|
|
|
747
749
|
const isBinary = await isBinaryFile(canonicalPath, fsPromises);
|
|
748
|
-
|
|
750
|
+
const sha256 = req.query.fingerprint === 'true' && stats.size <= 32 * 1024 * 1024 ? await readImageFingerprint(canonicalPath) : undefined;
|
|
751
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
752
|
+
return res.json({ path: canonicalPath, isFile: true, size: stats.size, mtimeMs: stats.mtimeMs, isBinary,
|
|
753
|
+
mimeType: FILE_MIME_MAP[path.extname(canonicalPath).toLowerCase()] || 'application/octet-stream', filename: path.basename(canonicalPath),
|
|
754
|
+
...(sha256 ? { sha256 } : {}) });
|
|
749
755
|
} catch (error) {
|
|
750
756
|
const err = error;
|
|
751
757
|
if (err && typeof err === 'object' && err.code === 'ENOENT') {
|
|
@@ -895,7 +901,7 @@ export const registerFsRoutes = (app, dependencies) => {
|
|
|
895
901
|
'.webm': 'video/webm',
|
|
896
902
|
'.ogv': 'video/ogg',
|
|
897
903
|
};
|
|
898
|
-
const mimeType = mimeMap[ext] || 'application/octet-stream';
|
|
904
|
+
const mimeType = mimeMap[ext] || FILE_MIME_MAP[ext] || 'application/octet-stream';
|
|
899
905
|
|
|
900
906
|
const download = req.query.download === 'true';
|
|
901
907
|
if (download) {
|
|
@@ -913,7 +919,7 @@ export const registerFsRoutes = (app, dependencies) => {
|
|
|
913
919
|
if (resolved.granted) {
|
|
914
920
|
res.setHeader('Referrer-Policy', 'no-referrer');
|
|
915
921
|
}
|
|
916
|
-
if (mimeType.startsWith('video/') || req.headers?.range || req.method === 'HEAD') {
|
|
922
|
+
if (stats.size > 32 * 1024 * 1024 || mimeType.startsWith('video/') || req.headers?.range || req.method === 'HEAD') {
|
|
917
923
|
// Path authorization above remains authoritative; Express owns range parsing,
|
|
918
924
|
// 206/416, stream backpressure and cancellation when the player seeks away.
|
|
919
925
|
return res.type(mimeType).sendFile(canonicalPath, { acceptRanges: true, dotfiles: 'allow', cacheControl: false });
|
|
@@ -977,10 +983,14 @@ export const registerFsRoutes = (app, dependencies) => {
|
|
|
977
983
|
|
|
978
984
|
const ext = path.extname(canonicalPath).toLowerCase();
|
|
979
985
|
const mimeType = FILE_MIME_MAP[ext] || 'application/octet-stream';
|
|
980
|
-
|
|
981
|
-
|
|
986
|
+
// Revalidate after the read policy above, including cookie/bearer changes.
|
|
987
|
+
// This is browser-private HTTP reuse, not the client's durable file store.
|
|
988
|
+
res.setHeader('Cache-Control', 'private, no-cache');
|
|
989
|
+
res.vary('Authorization');
|
|
990
|
+
res.vary('Cookie');
|
|
991
|
+
res.setHeader('ETag', `W/"${stats.size}-${stats.mtimeMs}-${stats.ctimeMs}"`);
|
|
982
992
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
983
|
-
return res.type(mimeType).
|
|
993
|
+
return res.type(mimeType).sendFile(canonicalPath, { acceptRanges: true, dotfiles: 'allow', cacheControl: false });
|
|
984
994
|
} catch (error) {
|
|
985
995
|
const err = error;
|
|
986
996
|
if (err && typeof err === 'object' && err.code === 'ENOENT') {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
2
2
|
import { isSensitiveCredentialResponse } from './credential-boundary.js';
|
|
3
3
|
import { registerSessionDeliveryRoute } from './session-delivery-runtime.js';
|
|
4
|
+
import { stripSessionMessageNativePayload } from '../session-turn-pages/native-payload.js';
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
applyForwardProxyResponseHeaders,
|
|
@@ -927,7 +928,7 @@ export const registerOpenCodeProxy = (app, deps) => {
|
|
|
927
928
|
}
|
|
928
929
|
|
|
929
930
|
res.setHeader('content-type', result.contentType);
|
|
930
|
-
res.json(projectMessagesPayloadForReasoning(result.payload, includeReasoning));
|
|
931
|
+
res.json(projectMessagesPayloadForReasoning(stripSessionMessageNativePayload(result.payload), includeReasoning));
|
|
931
932
|
} catch (error) {
|
|
932
933
|
if (isAbortError(error)) {
|
|
933
934
|
return;
|
|
@@ -412,6 +412,19 @@ describe('registerOpenCodeProxy runtime contract gate (ticket 11)', () => {
|
|
|
412
412
|
});
|
|
413
413
|
|
|
414
414
|
describe('registerOpenCodeProxy reasoning projection routes', () => {
|
|
415
|
+
it.each(['message', 'context'])('strips native replay fields from %s without hiding reasoning', async (surface) => {
|
|
416
|
+
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
|
|
417
|
+
data: [{ id: 'm', type: 'assistant', native: { replay: 'opaque' }, content: [
|
|
418
|
+
{ type: 'reasoning', text: 'keep', native: { reasoningEncryptedContent: 'encrypted' } },
|
|
419
|
+
{ type: 'tool', native: { itemId: 'upstream' }, input: { native: 'business' } },
|
|
420
|
+
] }], cursor: { next: 'older' },
|
|
421
|
+
}), { headers: { 'content-type': 'application/json' } }));
|
|
422
|
+
const res = await request(mountProxy()).get(`/api/session/ses_1/${surface}`);
|
|
423
|
+
expect(res.status).toBe(200);
|
|
424
|
+
expect(res.body).toEqual({ data: [{ id: 'm', type: 'assistant', content: [
|
|
425
|
+
{ type: 'reasoning', text: 'keep' }, { type: 'tool', input: { native: 'business' } },
|
|
426
|
+
] }], cursor: { next: 'older' } });
|
|
427
|
+
});
|
|
415
428
|
it('strips includeReasoning from SSE upstream and drops reasoning events when false', async () => {
|
|
416
429
|
const upstreamUrls = [];
|
|
417
430
|
globalThis.fetch = vi.fn(async (url) => {
|
|
@@ -70,6 +70,8 @@ Each upstream provider gets a namespaced `magpie:<source>:<provider>` result, in
|
|
|
70
70
|
`usage.accounts` retains separate account windows, errors and reading timestamps. `usage.windows`
|
|
71
71
|
is the account-keyed projection for existing header/mobile/tray consumers. Compact surfaces display
|
|
72
72
|
`window.label` rather than the account-bearing key; account detail remains on the full Usage page.
|
|
73
|
+
The known ZCode gift-plan sign-in hint is retained only in account details: upstream serializes
|
|
74
|
+
it as a zero-usage window without its internal `Aside` marker, but it is not a quota metric.
|
|
73
75
|
Opening a usage menu checks the full refresh timestamp, not whether any results exist: a discovered
|
|
74
76
|
Magpie snapshot must not suppress the initial built-in provider fetch. Percentages, balance
|
|
75
77
|
labels, unlimited windows and absolute reset timestamps are preserved. Window duration remains unknown
|
|
@@ -49,6 +49,9 @@ export function projectMagpieQuotas(payload, source, now = Date.now()) {
|
|
|
49
49
|
label: w.name, disablePace: true,
|
|
50
50
|
};
|
|
51
51
|
account.windows[`${i}:${w.name}`] = window;
|
|
52
|
+
// Magpie serializes this informational window as used: 0 without its
|
|
53
|
+
// internal Aside flag. Keep it in details, not the compact quota projection.
|
|
54
|
+
if (q.provider === 'zcode' && w.name === "ZCode gift plans (-Trial models) need ZCode's sign-in") continue;
|
|
52
55
|
const label = `${account.name} · ${w.name}`;
|
|
53
56
|
const key = Object.hasOwn(result.usage.windows, label) ? `${label} (${result.usage.accounts.length + 1}.${i + 1})` : label;
|
|
54
57
|
result.usage.windows[key] = window;
|
|
@@ -54,6 +54,20 @@ describe('Magpie discovery and quota adapter', () => {
|
|
|
54
54
|
expect(Object.values(results[1].usage.windows)[0]).toMatchObject({ valueLabel: '$12.34', usedPercent: null });
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
it('keeps the ZCode sign-in hint in account details instead of compact quota metrics', () => {
|
|
58
|
+
const hint = { name: "ZCode gift plans (-Trial models) need ZCode's sign-in", used: 0,
|
|
59
|
+
display: 'not read with an API key: add the account with a ZCode sign-in' };
|
|
60
|
+
const [result] = projectMagpieQuotas({ object: 'list', data: [{
|
|
61
|
+
provider: 'zcode', name: 'ZCode', kind: 'subscription', windows: [
|
|
62
|
+
{ name: '5 hours', used: 0 },
|
|
63
|
+
{ name: 'MCP · Month', used: 0, display: '0 / 1000', limit: 1000 }, hint,
|
|
64
|
+
],
|
|
65
|
+
}] }, source);
|
|
66
|
+
expect(Object.values(result.usage.windows).map((window) => window.label)).toEqual(['5 hours', 'MCP · Month']);
|
|
67
|
+
expect(Object.values(result.usage.accounts[0].windows).at(-1).valueLabel).toBe(hint.display);
|
|
68
|
+
expect(result.ok).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
57
71
|
it('isolates account errors and distinguishes unlimited, stale and malformed data', () => {
|
|
58
72
|
const data = structuredClone(report);
|
|
59
73
|
data.data[1].error = 'sensitive upstream response';
|
|
@@ -25,6 +25,7 @@ import { configureRuntimeUrlResolver, getRuntimeUrlResolver, setRuntimeUrlResolv
|
|
|
25
25
|
import { setRuntimeBearerToken } from '../../../../ui/src/lib/runtime-auth.ts';
|
|
26
26
|
import { adoptRelayTunnel, deactivateRelayTunnel } from '../../../../ui/src/lib/relay/runtime-tunnel.ts';
|
|
27
27
|
import { acquireRuntimeImage } from '../../../../ui/src/lib/runtime-image-cache.ts';
|
|
28
|
+
import { materializeImageBlob } from '../../../../ui/src/components/chat/imageSave.ts';
|
|
28
29
|
import { NATIVE_RELAY_ASSET_BRIDGE_KEY } from '../../../../ui/src/lib/relay/native-asset-bridge.ts';
|
|
29
30
|
import { readRelayImageDisplayBlob, resetRelayImageStreamForTests } from '../../../../ui/src/lib/relay/relay-image-stream.ts';
|
|
30
31
|
import { registerFsRoutes } from '../fs/routes.js';
|
|
@@ -634,6 +635,13 @@ describe('private relay compiled-process boundary', () => {
|
|
|
634
635
|
expect(response.headers.get('content-type')).toBe('video/mp4');
|
|
635
636
|
expect(await response.text()).toBe(bytes);
|
|
636
637
|
}
|
|
638
|
+
const downloadProgress: Array<number | null> = [];
|
|
639
|
+
const savedVideo = await materializeImageBlob({ kind: 'video', sourceUrl: `file://${origin.videoPath}` }, undefined, percent => downloadProgress.push(percent));
|
|
640
|
+
expect(await savedVideo.blob.text()).toBe('0123456789');
|
|
641
|
+
expect(downloadProgress[0]).toBe(0);
|
|
642
|
+
expect(downloadProgress).toContain(99);
|
|
643
|
+
expect(downloadProgress.at(-1)).toBe(100);
|
|
644
|
+
expect(downloadProgress).not.toContain(null);
|
|
637
645
|
} finally {
|
|
638
646
|
resetRelayImageStreamForTests();
|
|
639
647
|
Reflect.deleteProperty(globalThis, NATIVE_RELAY_ASSET_BRIDGE_KEY);
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
+
HTTP message list/context and exact-message responses strip provider replay
|
|
6
|
+
payloads through the shared `native-payload.js` projection. Only message-level
|
|
7
|
+
`native` (including the `info` carrier of exact records) and `content[]` item-level
|
|
8
|
+
`native` are removed. Tool input/output, metadata, pagination envelopes and cursors
|
|
9
|
+
are opaque and preserved; upstream objects are never mutated. Web proxy and VS
|
|
10
|
+
Code bridge share this helper. Reasoning text and token totals remain governed
|
|
11
|
+
by the existing reasoning projection. SSE events retain their existing payloads.
|
|
12
|
+
|
|
5
13
|
OpenChamber-owned turn-window pagination and anchor reconcile for session messages.
|
|
6
14
|
|
|
7
15
|
1. **Turn pages** — clients request the last N **authored user turns** (default 3) rather than a raw message count. The host loops OpenCode v2 `message.list` pages (first page `limit` + `order=desc`; continuation `limit` + `cursor` from the previous body `cursor.next` — upstream rejects `order` together with `cursor` with 400) until the turn budget is met or history is exhausted.
|