@vantran-se/openchamber-web 1.22.1 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/bin/cli.js +11 -1
- package/bin/cli.test.js +92 -0
- package/bin/lib/cli-startup.js +49 -0
- package/bin/lib/commands-startup.js +41 -6
- package/dist/assets/{CodeMirrorCommentWidgets-C53Q7pbX.js → CodeMirrorCommentWidgets-DFg2wnpo.js} +1 -1
- package/dist/assets/DiffView-3Dn4QHbu.js +4 -0
- package/dist/assets/DirtyBranchSwitchDialog-CwOerqVi.js +1 -0
- package/dist/assets/FilesView-BdTOpew9.js +17 -0
- package/dist/assets/GitView-Dqf9vHJh.js +6 -0
- package/dist/assets/{JsonTreeView-Bl0lp9Rs.js → JsonTreeView-BKdsjg5B.js} +1 -1
- package/dist/assets/LinearIssuesView-CmbeL7I6.js +1 -0
- package/dist/assets/MarkdownImageGallery-_5gEVr0X.js +1 -0
- package/dist/assets/MarkdownRendererImpl-wVZlx4dd.js +12 -0
- package/dist/assets/OnboardingScreen-DdP4_nuc.js +1 -0
- package/dist/assets/{PatchDiff-D6NXAfJk.js → PatchDiff-am3SAss2.js} +1 -1
- package/dist/assets/PierreDiffViewer-DCsydlKs.js +161 -0
- package/dist/assets/PlanView-BoJxdRZe.js +5 -0
- package/dist/assets/SettingsView-CcM8pF5O.js +28 -0
- package/dist/assets/{SettingsWindow-BiWDBRvb.js → SettingsWindow-DVTdDSol.js} +1 -1
- package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
- package/dist/assets/{ToolOutputDialog-BQexMQIm.js → ToolOutputDialog-BgfTbDZ4.js} +6 -6
- package/dist/assets/ToolPartDiffPreview-C7WLk5i9.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-Dpz2ra8h.js → VirtualizedFileDiff-DcdgkH-x.js} +1 -1
- package/dist/assets/WalkthroughView-BEVUDxuw.js +4 -0
- package/dist/assets/{computeVirtualFileMetrics-DtyozJdt.js → computeVirtualFileMetrics-C8byMnRh.js} +2 -2
- package/dist/assets/contextPanelEmbeddedChat-CirXEPJq.js +1 -0
- package/dist/assets/de-C8jV7J9y.js +10 -0
- package/dist/assets/es-BI6J1M5R.js +16 -0
- package/dist/assets/fr-D6oDFt7E.js +14 -0
- package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
- package/dist/assets/{index-DmhujXbT.js → index-BB7ryz8y.js} +1 -1
- package/dist/assets/index-BqhiCmYS.js +39 -0
- package/dist/assets/index-FqSOBz0J.css +1 -0
- package/dist/assets/{isDiffPlainText-XyulMHxx.js → isDiffPlainText-45ujMPGb.js} +1 -1
- package/dist/assets/{isFilePlainText-B19Ddps1.js → isFilePlainText-Cc4KFSYi.js} +1 -1
- package/dist/assets/ja-C_4i5EQu.js +16 -0
- package/dist/assets/ko-BFbOTpbq.js +16 -0
- package/dist/assets/{main-B2a_JfKZ.js → main-C9ETLKFc.js} +2 -2
- package/dist/assets/main-CJmb2dF8.js +930 -0
- package/dist/assets/markdownCore-DVuZpnli.js +5 -0
- package/dist/assets/{miniChat-B3CSU_Ps.js → miniChat-B4Ytoq2n.js} +2 -2
- package/dist/assets/{mobile-QNcFIVCW.js → mobile-BUT6-lo1.js} +2 -2
- package/dist/assets/patchFileDiff-8j1apICU.js +13 -0
- package/dist/assets/pl-COFvT56l.js +16 -0
- package/dist/assets/pt-BR-DtVOO2Lj.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-B6vUG-vF.js +2 -0
- package/dist/assets/renderMobileApp-BNgHraq6.js +25 -0
- package/dist/assets/runtimeEndpointReset-B8LYpWvr.js +17 -0
- package/dist/assets/scheduledTasksApi-GC4epIYA.js +1 -0
- package/dist/assets/{shikiHighlight-DE-OwqGw.js → shikiHighlight-BJISeZsQ.js} +1 -1
- package/dist/assets/surface-BvRzS58U.js +1 -0
- package/dist/assets/tr-B8Q0Go2l.js +7 -0
- package/dist/assets/uk-Dk4a-Fn-.js +16 -0
- package/dist/assets/{useAppFontEffects-COAeIX-D.js → useAppFontEffects-DpFNefUA.js} +153 -179
- package/dist/assets/useGitComparison-Dfxaqd0s.js +1 -0
- package/dist/assets/useInlineCommentController-CscsE4t-.js +2 -0
- package/dist/assets/useRootScrollLock-BaCvuRlT.js +1 -0
- package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-BG3v1Ghq.js → vendor-codemirror-language-data-DzlSIu_W.js} +2 -2
- package/dist/assets/vendor-legendapp-list-BuBPBC3R.js +1 -0
- package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
- package/dist/assets/{web-components-BNMwa0qm.js → web-components-CdGfR2XJ.js} +1 -1
- package/dist/assets/zh-CN-B_BXxG1Q.js +16 -0
- package/dist/assets/zh-TW-DLrl1SPZ.js +16 -0
- package/dist/index.html +5 -43
- package/dist/mini-chat.html +4 -4
- package/dist/mobile.html +4 -4
- package/package.json +8 -5
- package/server/index.d.ts +15 -0
- package/server/index.js +82 -13
- package/server/lib/agent-tool/runtime.js +2 -19
- package/server/lib/changelog/update-notes.js +82 -0
- package/server/lib/changelog/update-notes.test.js +59 -0
- package/server/lib/data-dir-migration.js +40 -0
- package/server/lib/data-dir-migration.test.js +59 -0
- package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
- package/server/lib/dev-tunnel/runtime.js +4 -4
- package/server/lib/dev-tunnel/tunnel.test.js +20 -0
- package/server/lib/event-stream/runtime.test.js +12 -12
- package/server/lib/fs/DOCUMENTATION.md +5 -0
- package/server/lib/fs/routes.js +63 -21
- package/server/lib/fs/routes.test.js +187 -1
- package/server/lib/git/DOCUMENTATION.md +32 -6
- package/server/lib/git/routes.js +40 -4
- package/server/lib/git/service.js +291 -215
- package/server/lib/git/service.test.js +593 -25
- package/server/lib/inherited-env.js +16 -9
- package/server/lib/inherited-env.test.js +8 -9
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
- package/server/lib/mcp-reconnect/runtime.js +122 -0
- package/server/lib/mcp-reconnect/runtime.test.js +140 -0
- package/server/lib/message-queue/DOCUMENTATION.md +161 -0
- package/server/lib/message-queue/runtime.js +870 -0
- package/server/lib/message-queue/runtime.test.js +482 -0
- package/server/lib/network-defaults.js +23 -0
- package/server/lib/network-defaults.test.js +52 -0
- package/server/lib/openchamber-sessions/routes.js +102 -0
- package/server/lib/openchamber-sessions/routes.test.js +88 -0
- package/server/lib/opencode/DOCUMENTATION.md +30 -6
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/config-paths.test.js +116 -0
- package/server/lib/opencode/core-routes.js +1 -0
- package/server/lib/opencode/feature-routes-runtime.js +7 -0
- package/server/lib/opencode/input-history-scope.js +14 -0
- package/server/lib/opencode/managed-plugin-config.js +35 -0
- package/server/lib/opencode/openchamber-routes.js +64 -4
- package/server/lib/opencode/openchamber-routes.test.js +130 -3
- package/server/lib/opencode/plugins.js +2 -2
- package/server/lib/opencode/providers.js +44 -2
- package/server/lib/opencode/providers.test.js +131 -4
- package/server/lib/opencode/routes.js +9 -7
- package/server/lib/opencode/server-startup-runtime.js +1 -1
- package/server/lib/opencode/settings-files.js +214 -0
- package/server/lib/opencode/settings-helpers.js +113 -41
- package/server/lib/opencode/settings-helpers.test.js +342 -0
- package/server/lib/opencode/settings-registry.json +747 -0
- package/server/lib/opencode/settings-runtime.js +141 -20
- package/server/lib/opencode/settings-runtime.test.js +191 -4
- package/server/lib/opencode/shared.js +4 -1
- package/server/lib/opencode/shutdown-runtime.js +2 -0
- package/server/lib/opencode/skill-routes.js +2 -1
- package/server/lib/opencode/snippets.js +1 -2
- package/server/lib/package-manager.js +4 -29
- package/server/lib/package-manager.test.js +2 -2
- package/server/lib/project-context/DOCUMENTATION.md +25 -1
- package/server/lib/project-context/routes.http.test.js +18 -0
- package/server/lib/project-context/routes.js +15 -0
- package/server/lib/project-context/runtime.js +248 -23
- package/server/lib/project-context/runtime.test.js +106 -3
- package/server/lib/projects/DOCUMENTATION.md +76 -0
- package/server/lib/projects/project-config.js +142 -9
- package/server/lib/projects/project-id.js +17 -0
- package/server/lib/projects/project-setup.js +409 -0
- package/server/lib/projects/project-setup.test.js +557 -0
- package/server/lib/projects/routes.js +59 -0
- package/server/lib/quota/DOCUMENTATION.md +40 -2
- package/server/lib/quota/credentials/providers.js +4 -0
- package/server/lib/quota/credentials/store.js +1 -1
- package/server/lib/quota/credentials/store.test.js +4 -4
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/cline-pass.js +125 -0
- package/server/lib/quota/providers/cline-pass.test.js +103 -0
- package/server/lib/quota/providers/exe-dev.js +75 -0
- package/server/lib/quota/providers/exe-dev.test.js +47 -0
- package/server/lib/quota/providers/hyper.js +118 -0
- package/server/lib/quota/providers/hyper.test.js +126 -0
- package/server/lib/quota/providers/index.js +22 -0
- package/server/lib/quota/providers/neuralwatt.js +3 -6
- package/server/lib/quota/providers/neuralwatt.test.js +7 -7
- package/server/lib/quota/providers/ollama-cloud.js +31 -0
- package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
- package/server/lib/quota/providers/opencode-go.js +2 -1
- package/server/lib/quota/providers/opencode-go.test.js +31 -2
- package/server/lib/quota/providers/openrouter.js +119 -24
- package/server/lib/quota/providers/openrouter.test.js +330 -0
- package/server/lib/quota/routes.js +2 -0
- package/server/lib/quota/utils/auth.js +1 -1
- package/server/lib/relay/DOCUMENTATION.md +56 -1
- package/server/lib/relay/cross-compat.test.js +26 -0
- package/server/lib/relay/downstream-scheduler.js +154 -0
- package/server/lib/relay/downstream-scheduler.test.js +162 -0
- package/server/lib/relay/e2ee.js +10 -8
- package/server/lib/relay/flow-control.test.js +177 -0
- package/server/lib/relay/host-client.js +37 -8
- package/server/lib/relay/host-client.test.js +9 -1
- package/server/lib/relay/tunnel-codec.js +9 -0
- package/server/lib/relay/tunnel-host.js +69 -18
- package/server/lib/relay/tunnel-host.test.js +9 -1
- package/server/lib/security/request-security.js +21 -6
- package/server/lib/security/request-security.test.js +42 -8
- package/server/lib/session-assist/runtime.js +7 -10
- package/server/lib/session-goal/DOCUMENTATION.md +20 -7
- package/server/lib/session-goal/create.js +3 -1
- package/server/lib/session-goal/runtime.js +81 -20
- package/server/lib/session-goal/runtime.test.js +426 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
- package/server/lib/skills-catalog/install.js +3 -2
- package/server/lib/small-model/DOCUMENTATION.md +7 -2
- package/server/lib/small-model/call.js +17 -6
- package/server/lib/small-model/call.test.js +52 -0
- package/server/lib/small-model/index.js +7 -11
- package/server/lib/small-model/routes.js +2 -1
- package/server/lib/small-model/runtime-providers.js +15 -7
- package/server/lib/small-model/runtime-providers.test.js +7 -1
- package/server/lib/system-prompt/runtime.js +2 -21
- package/server/lib/terminal/DOCUMENTATION.md +14 -10
- package/server/lib/terminal/runtime.js +190 -31
- package/server/lib/terminal/runtime.test.js +571 -37
- package/server/lib/terminal/shells.js +17 -0
- package/server/lib/terminal/shells.test.js +32 -1
- package/server/lib/ui-auth/ui-auth.js +4 -1
- package/server/lib/ui-auth/ui-auth.test.js +16 -0
- package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
- package/server/lib/walkthrough/index.js +1 -0
- package/server/lib/walkthrough/model-settings.js +8 -12
- package/server/lib/walkthrough/prompt.js +3 -1
- package/server/lib/walkthrough/routes.test.js +26 -5
- package/server/lib/walkthrough/sources.js +23 -2
- package/dist/assets/CommitSection-X-f2RgYr.js +0 -1
- package/dist/assets/DiffView-Dh1ey2x9.js +0 -4
- package/dist/assets/FilesView-3ZYP72ki.js +0 -17
- package/dist/assets/GitView-CMuN7z2Z.js +0 -6
- package/dist/assets/LinearIssuesView-CQiUDU8b.js +0 -1
- package/dist/assets/MarkdownImageGallery-bA0PS-tn.js +0 -1
- package/dist/assets/MarkdownRendererImpl-BGQi2PLq.js +0 -12
- package/dist/assets/OnboardingScreen-D3kYJIb6.js +0 -1
- package/dist/assets/PierreDiffViewer-Ct2A13NO.js +0 -157
- package/dist/assets/PlanView-6AUjzNQn.js +0 -5
- package/dist/assets/SettingsView-CHI8fFaK.js +0 -28
- package/dist/assets/ToolPartDiffPreview-DPAATNn6.js +0 -8
- package/dist/assets/WalkthroughView-AX-u2pFg.js +0 -4
- package/dist/assets/contextPanelEmbeddedChat-C9EcRSDF.js +0 -1
- package/dist/assets/de-DqVn5vme.js +0 -9
- package/dist/assets/es-hj43Z53i.js +0 -15
- package/dist/assets/fr-BmkZt2mF.js +0 -13
- package/dist/assets/index-L_ncV-6H.css +0 -1
- package/dist/assets/index-qES19dlJ.js +0 -7
- package/dist/assets/ja-DioMIEGF.js +0 -15
- package/dist/assets/ko-BqPgaQJb.js +0 -15
- package/dist/assets/main-CYgSu6dx.js +0 -928
- package/dist/assets/markdownCore-CWRCqSH2.js +0 -3
- package/dist/assets/patchFileDiff-BpMQZnYu.js +0 -10
- package/dist/assets/pl-slULBF4R.js +0 -15
- package/dist/assets/pt-BR-BqH8fuCH.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-D77bmLqo.js +0 -2
- package/dist/assets/renderMobileApp-BQ3Kdm-d.js +0 -25
- package/dist/assets/runtimeEndpointReset-CRGn1gVj.js +0 -16
- package/dist/assets/scheduledTasksApi-BSRCtP6T.js +0 -1
- package/dist/assets/tr-CjEbw1Aw.js +0 -6
- package/dist/assets/uk-BkkfxbOs.js +0 -15
- package/dist/assets/useInlineCommentController-JsBboQwt.js +0 -2
- package/dist/assets/useRootScrollLock-C0N9mCwz.js +0 -1
- package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
- package/dist/assets/vendor-legendapp-list-gg7YLRW7.js +0 -1
- package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
- package/dist/assets/zh-CN-C6FMdVgv.js +0 -15
- package/dist/assets/zh-TW-CdcMjDxv.js +0 -15
package/dist/index.html
CHANGED
|
@@ -509,16 +509,16 @@
|
|
|
509
509
|
pointer-events: none;
|
|
510
510
|
}
|
|
511
511
|
</style>
|
|
512
|
-
<script type="module" crossorigin src="/assets/main-
|
|
512
|
+
<script type="module" crossorigin src="/assets/main-C9ETLKFc.js"></script>
|
|
513
513
|
<link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
|
|
514
514
|
<link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
|
|
515
515
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
|
|
516
516
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
|
|
517
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-zod-
|
|
517
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-zod-BPAANqvH.js">
|
|
518
518
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-BJ73u-bH.js">
|
|
519
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
520
|
-
<link rel="modulepreload" crossorigin href="/assets/contextPanelEmbeddedChat-
|
|
521
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
519
|
+
<link rel="modulepreload" crossorigin href="/assets/index-BqhiCmYS.js">
|
|
520
|
+
<link rel="modulepreload" crossorigin href="/assets/contextPanelEmbeddedChat-CirXEPJq.js">
|
|
521
|
+
<link rel="stylesheet" crossorigin href="/assets/index-FqSOBz0J.css">
|
|
522
522
|
</head>
|
|
523
523
|
<body class="h-full bg-background text-foreground">
|
|
524
524
|
<div id="root" class="h-full">
|
|
@@ -603,44 +603,6 @@
|
|
|
603
603
|
}, 10000);
|
|
604
604
|
</script>
|
|
605
605
|
|
|
606
|
-
<!-- CSS Font Loading API for reliable Nerd Font loading. The ~2 MB of
|
|
607
|
-
Nerd Fonts are only used by the terminal viewport, so they are no
|
|
608
|
-
longer downloaded on every cold start: the terminal requests them on
|
|
609
|
-
first mount through this idempotent hook. -->
|
|
610
|
-
<script>
|
|
611
|
-
(function() {
|
|
612
|
-
let nerdFontsPromise = null;
|
|
613
|
-
window.__openchamberEnsureNerdFonts = function() {
|
|
614
|
-
if (nerdFontsPromise) {
|
|
615
|
-
return nerdFontsPromise;
|
|
616
|
-
}
|
|
617
|
-
const fonts = [
|
|
618
|
-
{
|
|
619
|
-
name: 'JetBrainsMono Nerd Font',
|
|
620
|
-
url: 'https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/JetBrainsMonoNerdFont-Regular.woff2'
|
|
621
|
-
},
|
|
622
|
-
{
|
|
623
|
-
name: 'FiraCode Nerd Font',
|
|
624
|
-
url: 'https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/FiraCodeNerdFont-Regular.woff2'
|
|
625
|
-
}
|
|
626
|
-
];
|
|
627
|
-
|
|
628
|
-
const fontPromises = fonts.map(font => {
|
|
629
|
-
const fontFace = new FontFace(font.name, `url(${font.url}) format('woff2')`);
|
|
630
|
-
document.fonts.add(fontFace);
|
|
631
|
-
return fontFace.load().catch(err => {
|
|
632
|
-
console.warn(`Failed to load font: ${font.name}`, err);
|
|
633
|
-
});
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
nerdFontsPromise = Promise.allSettled(fontPromises).then(() => {
|
|
637
|
-
document.documentElement.classList.add('fonts-loaded');
|
|
638
|
-
});
|
|
639
|
-
return nerdFontsPromise;
|
|
640
|
-
};
|
|
641
|
-
})();
|
|
642
|
-
</script>
|
|
643
|
-
|
|
644
606
|
<!-- Polyfill for process before loading React -->
|
|
645
607
|
<script>
|
|
646
608
|
if (typeof process === 'undefined') {
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,15 +74,15 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-B4Ytoq2n.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
|
|
81
81
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
|
|
82
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-zod-
|
|
82
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-zod-BPAANqvH.js">
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-BJ73u-bH.js">
|
|
84
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
85
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
84
|
+
<link rel="modulepreload" crossorigin href="/assets/index-BqhiCmYS.js">
|
|
85
|
+
<link rel="stylesheet" crossorigin href="/assets/index-FqSOBz0J.css">
|
|
86
86
|
</head>
|
|
87
87
|
<body class="h-full bg-background text-foreground">
|
|
88
88
|
<div id="root" class="h-full">
|
package/dist/mobile.html
CHANGED
|
@@ -63,15 +63,15 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-BUT6-lo1.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
|
|
70
70
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
|
|
71
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-zod-
|
|
71
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-zod-BPAANqvH.js">
|
|
72
72
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-BJ73u-bH.js">
|
|
73
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
74
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
73
|
+
<link rel="modulepreload" crossorigin href="/assets/index-BqhiCmYS.js">
|
|
74
|
+
<link rel="stylesheet" crossorigin href="/assets/index-FqSOBz0J.css">
|
|
75
75
|
</head>
|
|
76
76
|
<body class="h-full bg-background text-foreground">
|
|
77
77
|
<div id="root" class="h-full"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vantran-se/openchamber-web",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./server/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"dev": "bun run build:watch",
|
|
16
16
|
"dev:server": "OPENCHAMBER_RELAY_HOST=${OPENCHAMBER_RELAY_HOST:-off} bun server/index.js --port ${OPENCHAMBER_PORT:-3001}",
|
|
17
|
-
"dev:server:watch": "
|
|
17
|
+
"dev:server:watch": "node ./scripts/dev-server-watch.mjs",
|
|
18
18
|
"build": "vite build",
|
|
19
19
|
"build:watch": "vite build --watch",
|
|
20
20
|
"type-check": "tsc --noEmit",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@clack/prompts": "^1.1.0",
|
|
27
27
|
"@octokit/rest": "^22.0.1",
|
|
28
|
-
"@opencode-ai/sdk": "1.18.
|
|
28
|
+
"@opencode-ai/sdk": "1.18.30",
|
|
29
29
|
"@simplewebauthn/server": "13.3.1",
|
|
30
30
|
"bun-pty": "^0.4.5",
|
|
31
31
|
"compression": "^1.8.1",
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"eslint": "^9.33.0",
|
|
78
78
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
79
79
|
"eslint-plugin-react-refresh": "^0.5.0",
|
|
80
|
-
"ghostty-web": "0.4.0",
|
|
81
80
|
"globals": "^16.3.0",
|
|
82
81
|
"next-themes": "^0.4.6",
|
|
83
82
|
"nodemon": "^3.1.7",
|
|
@@ -107,5 +106,9 @@
|
|
|
107
106
|
"public",
|
|
108
107
|
"package.json",
|
|
109
108
|
"README.md"
|
|
110
|
-
]
|
|
109
|
+
],
|
|
110
|
+
"repository": {
|
|
111
|
+
"type": "git",
|
|
112
|
+
"url": "git+https://github.com/vantran-se/openchamber.git"
|
|
113
|
+
}
|
|
111
114
|
}
|
package/server/index.d.ts
CHANGED
|
@@ -11,12 +11,27 @@ export interface WebUiServerController {
|
|
|
11
11
|
stop: (options?: { exitProcess?: boolean }) => Promise<void>;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export interface DesktopUpdateInfo {
|
|
15
|
+
available: boolean;
|
|
16
|
+
currentVersion?: string;
|
|
17
|
+
version?: string | null;
|
|
18
|
+
body?: string | null;
|
|
19
|
+
date?: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DesktopUpdater {
|
|
23
|
+
check: () => Promise<DesktopUpdateInfo>;
|
|
24
|
+
install: () => Promise<DesktopUpdateInfo>;
|
|
25
|
+
restart: () => Promise<void> | void;
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
export interface StartWebUiServerOptions {
|
|
15
29
|
port?: number;
|
|
16
30
|
host?: string;
|
|
17
31
|
attachSignals?: boolean;
|
|
18
32
|
exitOnShutdown?: boolean;
|
|
19
33
|
uiPassword?: string | null;
|
|
34
|
+
desktopUpdater?: DesktopUpdater;
|
|
20
35
|
}
|
|
21
36
|
|
|
22
37
|
export declare function startWebUiServer(
|
package/server/index.js
CHANGED
|
@@ -91,8 +91,10 @@ import { createPushRuntime } from './lib/notifications/push-runtime.js';
|
|
|
91
91
|
import { createApnsRuntime } from './lib/notifications/apns-runtime.js';
|
|
92
92
|
import { createNotificationTemplateRuntime } from './lib/notifications/template-runtime.js';
|
|
93
93
|
import { createPermissionAutoAcceptRuntime } from './lib/permission-auto-accept/runtime.js';
|
|
94
|
+
import { createMessageQueueRuntime } from './lib/message-queue/runtime.js';
|
|
94
95
|
import { createGracefulShutdownRuntime } from './lib/opencode/shutdown-runtime.js';
|
|
95
96
|
import { createProjectConfigRuntime } from './lib/projects/project-config.js';
|
|
97
|
+
import { migrateLegacyUserDirs } from './lib/data-dir-migration.js';
|
|
96
98
|
import { createProjectContextRuntime } from './lib/project-context/runtime.js';
|
|
97
99
|
import { createAgentMemoryRuntime } from './lib/agent-memory/runtime.js';
|
|
98
100
|
import { createAgentMemoryActions } from './lib/agent-memory/actions.js';
|
|
@@ -110,11 +112,16 @@ import { createDevServerScanner } from './lib/dev-servers/routes.js';
|
|
|
110
112
|
import { createDevTunnelRuntime } from './lib/dev-tunnel/runtime.js';
|
|
111
113
|
import { registerBrowserControlRoutes } from './lib/browser-control/routes.js';
|
|
112
114
|
import { createSystemPromptRuntime } from './lib/system-prompt/runtime.js';
|
|
115
|
+
import { createMcpReconnectRuntime } from './lib/mcp-reconnect/runtime.js';
|
|
113
116
|
import { createOpenChamberSessionService } from './lib/openchamber-sessions/routes.js';
|
|
114
117
|
import { createScheduledTaskService } from './lib/scheduled-tasks/service.js';
|
|
115
118
|
import { createOpenChamberControlService } from './lib/openchamber-control/service.js';
|
|
116
119
|
import { OpenChamberControlError } from './lib/openchamber-control/error.js';
|
|
117
120
|
import webPush from 'web-push';
|
|
121
|
+
import { applyConnectAttemptTimeout } from './lib/network-defaults.js';
|
|
122
|
+
|
|
123
|
+
// Background CLI launches enter here in a fresh process, without CLI defaults.
|
|
124
|
+
applyConnectAttemptTimeout();
|
|
118
125
|
|
|
119
126
|
const __filename = fileURLToPath(import.meta.url);
|
|
120
127
|
const __dirname = path.dirname(__filename);
|
|
@@ -257,16 +264,27 @@ const normalizeManagedRemoteTunnelPresets = (...args) =>
|
|
|
257
264
|
const normalizeManagedRemoteTunnelPresetTokens = (...args) =>
|
|
258
265
|
settingsNormalizationRuntime.normalizeManagedRemoteTunnelPresetTokens(...args);
|
|
259
266
|
const isUnsafeSkillRelativePath = (...args) => settingsNormalizationRuntime.isUnsafeSkillRelativePath(...args);
|
|
260
|
-
const sanitizeTypographySizesPartial = (...args) =>
|
|
261
|
-
settingsNormalizationRuntime.sanitizeTypographySizesPartial(...args);
|
|
262
267
|
const normalizeStringArray = (...args) => settingsNormalizationRuntime.normalizeStringArray(...args);
|
|
263
268
|
const sanitizeModelRefs = (...args) => settingsNormalizationRuntime.sanitizeModelRefs(...args);
|
|
264
269
|
const sanitizeSkillCatalogs = (...args) => settingsNormalizationRuntime.sanitizeSkillCatalogs(...args);
|
|
265
270
|
const sanitizeProjects = (...args) => settingsNormalizationRuntime.sanitizeProjects(...args);
|
|
266
271
|
|
|
267
|
-
|
|
272
|
+
// Every OpenChamber-owned file and folder hangs off one root: the default
|
|
273
|
+
// `~/.config/openchamber`, or `OPENCHAMBER_DATA_DIR` when set. The user
|
|
274
|
+
// folders (`projects/`, `themes/`, `speech-models/`) are copied into a custom
|
|
275
|
+
// root once at startup (`migrateLegacyUserDirs`), because they used to ignore
|
|
276
|
+
// the variable.
|
|
277
|
+
const OPENCHAMBER_DEFAULT_CONFIG_ROOT = path.join(os.homedir(), '.config', 'openchamber');
|
|
278
|
+
const OPENCHAMBER_USER_CONFIG_ROOT = process.env.OPENCHAMBER_DATA_DIR
|
|
279
|
+
? path.resolve(process.env.OPENCHAMBER_DATA_DIR)
|
|
280
|
+
: OPENCHAMBER_DEFAULT_CONFIG_ROOT;
|
|
268
281
|
const OPENCHAMBER_USER_THEMES_DIR = path.join(OPENCHAMBER_USER_CONFIG_ROOT, 'themes');
|
|
269
282
|
const OPENCHAMBER_PROJECTS_CONFIG_DIR = path.join(OPENCHAMBER_USER_CONFIG_ROOT, 'projects');
|
|
283
|
+
// OPENCHAMBER_CHATS_DIR relocates managed chat worktrees — needed when the
|
|
284
|
+
// OpenCode server runs as a separate user that cannot traverse $HOME.
|
|
285
|
+
const OPENCHAMBER_CHATS_DIR = process.env.OPENCHAMBER_CHATS_DIR && process.env.OPENCHAMBER_CHATS_DIR.trim()
|
|
286
|
+
? path.resolve(process.env.OPENCHAMBER_CHATS_DIR.trim())
|
|
287
|
+
: path.join(OPENCHAMBER_USER_CONFIG_ROOT, 'chats');
|
|
270
288
|
|
|
271
289
|
const MAX_THEME_JSON_BYTES = 512 * 1024;
|
|
272
290
|
|
|
@@ -284,6 +302,7 @@ const readCustomThemesFromDisk = (...args) => themeRuntime.readCustomThemesFromD
|
|
|
284
302
|
let notificationTemplateRuntime = null;
|
|
285
303
|
let agentToolRuntime = null;
|
|
286
304
|
let systemPromptRuntime = null;
|
|
305
|
+
let mcpReconnectRuntime = null;
|
|
287
306
|
|
|
288
307
|
const createTimeoutSignal = (...args) => notificationTemplateRuntime.createTimeoutSignal(...args);
|
|
289
308
|
const formatProjectLabel = (...args) => notificationTemplateRuntime.formatProjectLabel(...args);
|
|
@@ -297,9 +316,7 @@ const maybeCacheSessionInfoFromEvent = (...args) => notificationTemplateRuntime.
|
|
|
297
316
|
const buildTemplateVariables = (...args) => notificationTemplateRuntime.buildTemplateVariables(...args);
|
|
298
317
|
const getCachedZenModels = (...args) => notificationTemplateRuntime.getCachedZenModels(...args);
|
|
299
318
|
|
|
300
|
-
const OPENCHAMBER_DATA_DIR =
|
|
301
|
-
? path.resolve(process.env.OPENCHAMBER_DATA_DIR)
|
|
302
|
-
: path.join(os.homedir(), '.config', 'openchamber');
|
|
319
|
+
const OPENCHAMBER_DATA_DIR = OPENCHAMBER_USER_CONFIG_ROOT;
|
|
303
320
|
const SETTINGS_FILE_PATH = path.join(OPENCHAMBER_DATA_DIR, 'settings.json');
|
|
304
321
|
const PUSH_SUBSCRIPTIONS_FILE_PATH = path.join(OPENCHAMBER_DATA_DIR, 'push-subscriptions.json');
|
|
305
322
|
const APNS_TOKENS_FILE_PATH = path.join(OPENCHAMBER_DATA_DIR, 'apns-tokens.json');
|
|
@@ -337,7 +354,6 @@ const settingsHelpers = createSettingsHelpers({
|
|
|
337
354
|
normalizeManagedRemoteTunnelHostname,
|
|
338
355
|
normalizeManagedRemoteTunnelPresets,
|
|
339
356
|
normalizeManagedRemoteTunnelPresetTokens,
|
|
340
|
-
sanitizeTypographySizesPartial,
|
|
341
357
|
normalizeStringArray,
|
|
342
358
|
sanitizeModelRefs,
|
|
343
359
|
sanitizeSkillCatalogs,
|
|
@@ -476,6 +492,17 @@ const getUpstreamStallTimeoutMs = () => (
|
|
|
476
492
|
: DEFAULT_UPSTREAM_STALL_TIMEOUT_MS
|
|
477
493
|
);
|
|
478
494
|
|
|
495
|
+
const movedUserDirs = await migrateLegacyUserDirs({
|
|
496
|
+
fsPromises,
|
|
497
|
+
path,
|
|
498
|
+
dataDir: OPENCHAMBER_USER_CONFIG_ROOT,
|
|
499
|
+
legacyRoot: OPENCHAMBER_DEFAULT_CONFIG_ROOT,
|
|
500
|
+
warn: (message) => console.warn(`[data-dir] ${message}`),
|
|
501
|
+
});
|
|
502
|
+
if (movedUserDirs.length > 0) {
|
|
503
|
+
console.log(`[data-dir] Copied ${movedUserDirs.join(', ')} into ${OPENCHAMBER_USER_CONFIG_ROOT}`);
|
|
504
|
+
}
|
|
505
|
+
|
|
479
506
|
const projectConfigRuntime = createProjectConfigRuntime({
|
|
480
507
|
fsPromises,
|
|
481
508
|
path,
|
|
@@ -486,6 +513,7 @@ const projectContextRuntime = createProjectContextRuntime({
|
|
|
486
513
|
fsPromises,
|
|
487
514
|
path,
|
|
488
515
|
projectsDirPath: OPENCHAMBER_PROJECTS_CONFIG_DIR,
|
|
516
|
+
resolveSharedPlansDir: (projectId) => projectConfigRuntime.resolveSharedPlansDir(projectId),
|
|
489
517
|
});
|
|
490
518
|
|
|
491
519
|
const agentMemoryRuntime = createAgentMemoryRuntime({
|
|
@@ -878,6 +906,25 @@ notificationTriggerRuntime.setGetIsSessionAutoAccepting(
|
|
|
878
906
|
(sessionId, directory) => permissionAutoAcceptRuntime.isSessionAutoAccepting(sessionId, directory),
|
|
879
907
|
);
|
|
880
908
|
|
|
909
|
+
// Queued follow-up messages are delivered by the server so a closed tab or a
|
|
910
|
+
// dropped connection no longer strands them (VS Code keeps its UI-side queue).
|
|
911
|
+
const messageQueueRuntime = createMessageQueueRuntime({
|
|
912
|
+
globalEventHub: globalMessageStreamHub,
|
|
913
|
+
buildOpenCodeUrl,
|
|
914
|
+
getOpenCodeAuthHeaders,
|
|
915
|
+
sessionKnowledgeRuntime,
|
|
916
|
+
// OpenCode's /global/event SSE proxy cannot carry OpenChamber-owned events.
|
|
917
|
+
// Use the shared control stream for SSE clients and the existing WS fan-out.
|
|
918
|
+
broadcastGlobalUiEvent: createGlobalUiEventBroadcaster({
|
|
919
|
+
sseClients: uiOpenChamberEventClients,
|
|
920
|
+
wsClients: uiNotificationWsClients,
|
|
921
|
+
writeSseEvent,
|
|
922
|
+
}),
|
|
923
|
+
onPromptSent: (sessionId) => sessionRuntime.markUserMessageSent(sessionId),
|
|
924
|
+
dataDir: OPENCHAMBER_DATA_DIR,
|
|
925
|
+
});
|
|
926
|
+
messageQueueRuntime.start();
|
|
927
|
+
|
|
881
928
|
const openCodeWatcherRuntime = createOpenCodeWatcherRuntime({
|
|
882
929
|
waitForOpenCodePort: (...args) => waitForOpenCodePort(...args),
|
|
883
930
|
buildOpenCodeUrl,
|
|
@@ -1208,11 +1255,15 @@ const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({
|
|
|
1208
1255
|
const managedEnv = includeControl || includeWeb || includeMemory
|
|
1209
1256
|
? await (agentToolRuntime?.prepareManagedOpenCodeEnv({ includeControl, includeWeb, includeMemory }) || {})
|
|
1210
1257
|
: {};
|
|
1211
|
-
if (settings?.optimizeSystemPrompt !== true) return managedEnv;
|
|
1212
1258
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1259
|
+
// Each managed plugin appends itself to the config the previous one produced.
|
|
1260
|
+
let configContent = managedEnv.OPENCODE_CONFIG_CONTENT ?? process.env.OPENCODE_CONFIG_CONTENT;
|
|
1261
|
+
if (settings?.optimizeSystemPrompt === true) {
|
|
1262
|
+
({ OPENCODE_CONFIG_CONTENT: configContent } = await systemPromptRuntime.prepareManagedOpenCodeEnv(configContent));
|
|
1263
|
+
}
|
|
1264
|
+
// Always on for managed OpenCode: it only retries servers OpenCode gave up on.
|
|
1265
|
+
const mcpReconnectEnv = await mcpReconnectRuntime.prepareManagedOpenCodeEnv(configContent);
|
|
1266
|
+
return { ...managedEnv, ...mcpReconnectEnv };
|
|
1216
1267
|
},
|
|
1217
1268
|
});
|
|
1218
1269
|
|
|
@@ -1295,7 +1346,7 @@ const resolveMemoryProjectId = createMemoryProjectResolver({
|
|
|
1295
1346
|
return sanitizeProjects(settings?.projects || []).map((project) => project.path);
|
|
1296
1347
|
},
|
|
1297
1348
|
resolvePrimaryWorktreeRoot,
|
|
1298
|
-
managedProjectRoots: [path.join(OPENCHAMBER_USER_CONFIG_ROOT, 'chats')],
|
|
1349
|
+
managedProjectRoots: [...new Set([path.join(OPENCHAMBER_USER_CONFIG_ROOT, 'chats'), OPENCHAMBER_CHATS_DIR])],
|
|
1299
1350
|
});
|
|
1300
1351
|
|
|
1301
1352
|
/**
|
|
@@ -1427,6 +1478,7 @@ const gracefulShutdownRuntime = createGracefulShutdownRuntime({
|
|
|
1427
1478
|
sessionAssistRuntime,
|
|
1428
1479
|
sessionGoalRuntime,
|
|
1429
1480
|
contextObligatoryRuntime,
|
|
1481
|
+
messageQueueRuntime,
|
|
1430
1482
|
sessionRuntime,
|
|
1431
1483
|
getHealthCheckInterval: () => healthCheckInterval,
|
|
1432
1484
|
clearHealthCheckInterval: (value) => clearInterval(value),
|
|
@@ -1485,6 +1537,11 @@ async function main(options = {}) {
|
|
|
1485
1537
|
path,
|
|
1486
1538
|
dataDir: OPENCHAMBER_DATA_DIR,
|
|
1487
1539
|
});
|
|
1540
|
+
mcpReconnectRuntime = createMcpReconnectRuntime({
|
|
1541
|
+
fsPromises,
|
|
1542
|
+
path,
|
|
1543
|
+
dataDir: OPENCHAMBER_DATA_DIR,
|
|
1544
|
+
});
|
|
1488
1545
|
|
|
1489
1546
|
// Pairing transports advertised to the create-device dialog. LAN reachability is
|
|
1490
1547
|
// derived from the SERVER's actual bind (a wildcard bind → the machine's LAN IP;
|
|
@@ -1611,6 +1668,12 @@ async function main(options = {}) {
|
|
|
1611
1668
|
const getDesktopRuntimeConfig = typeof options.getDesktopRuntimeConfig === 'function'
|
|
1612
1669
|
? options.getDesktopRuntimeConfig
|
|
1613
1670
|
: null;
|
|
1671
|
+
const desktopUpdater = options.desktopUpdater
|
|
1672
|
+
&& typeof options.desktopUpdater.check === 'function'
|
|
1673
|
+
&& typeof options.desktopUpdater.install === 'function'
|
|
1674
|
+
&& typeof options.desktopUpdater.restart === 'function'
|
|
1675
|
+
? options.desktopUpdater
|
|
1676
|
+
: null;
|
|
1614
1677
|
|
|
1615
1678
|
console.log(`Starting OpenChamber on port ${port === 0 ? 'auto' : port}`);
|
|
1616
1679
|
|
|
@@ -1645,7 +1708,10 @@ async function main(options = {}) {
|
|
|
1645
1708
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
1646
1709
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
1647
1710
|
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
|
|
1648
|
-
|
|
1711
|
+
// The packaged desktop UI (openchamber-ui://) and the dev UI sit on a
|
|
1712
|
+
// different origin, so every custom request header must be listed here or
|
|
1713
|
+
// the browser refuses the request at preflight, before it reaches a route.
|
|
1714
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization,Accept,X-Requested-With,Cache-Control,X-OpenCode-Directory,X-OpenCode-Directory-Encoding,Ngrok-Skip-Browser-Warning,X-OpenChamber-Surface');
|
|
1649
1715
|
res.setHeader('Access-Control-Expose-Headers', 'x-next-cursor');
|
|
1650
1716
|
res.setHeader('Vary', 'Origin');
|
|
1651
1717
|
if (req.method === 'OPTIONS') {
|
|
@@ -1786,6 +1852,7 @@ async function main(options = {}) {
|
|
|
1786
1852
|
getCachedZenModels,
|
|
1787
1853
|
setAutoAcceptSession,
|
|
1788
1854
|
agentToolRuntime,
|
|
1855
|
+
desktopUpdater,
|
|
1789
1856
|
});
|
|
1790
1857
|
uiAuthController = bootstrapResult.uiAuthController;
|
|
1791
1858
|
realtimeProxyRuntime = attachRealtimeProxy({
|
|
@@ -1877,6 +1944,7 @@ async function main(options = {}) {
|
|
|
1877
1944
|
createFsSearchRuntime: createFsSearchRuntimeFactory,
|
|
1878
1945
|
openchamberDataDir: OPENCHAMBER_DATA_DIR,
|
|
1879
1946
|
openchamberUserConfigRoot: OPENCHAMBER_USER_CONFIG_ROOT,
|
|
1947
|
+
managedChatsRoot: OPENCHAMBER_CHATS_DIR,
|
|
1880
1948
|
normalizeDirectoryPath,
|
|
1881
1949
|
resolveProjectDirectory,
|
|
1882
1950
|
resolveOptionalProjectDirectory,
|
|
@@ -1914,6 +1982,7 @@ async function main(options = {}) {
|
|
|
1914
1982
|
getOpenChamberEventClients: () => uiOpenChamberEventClients,
|
|
1915
1983
|
writeSseEvent,
|
|
1916
1984
|
permissionAutoAcceptRuntime,
|
|
1985
|
+
messageQueueRuntime,
|
|
1917
1986
|
});
|
|
1918
1987
|
|
|
1919
1988
|
const startupPipelineResult = await startupPipelineRuntime.run({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { parse as parseJsonc } from 'jsonc-parser';
|
|
2
1
|
import { pathToFileURL } from 'node:url';
|
|
2
|
+
import { appendManagedPlugin } from '../opencode/managed-plugin-config.js';
|
|
3
3
|
import {
|
|
4
4
|
OPENCHAMBER_AGENT_TOOL_ACTION_DEFINITIONS,
|
|
5
5
|
OPENCHAMBER_AGENT_TOOL_ACTIONS,
|
|
@@ -252,23 +252,6 @@ ${entries.join('')} },
|
|
|
252
252
|
`;
|
|
253
253
|
};
|
|
254
254
|
|
|
255
|
-
const mergePluginConfig = (rawConfig, pluginUrl) => {
|
|
256
|
-
const errors = [];
|
|
257
|
-
const parsed = asNonEmptyString(rawConfig) ? parseJsonc(rawConfig, errors, { allowTrailingComma: true }) : {};
|
|
258
|
-
if (errors.length > 0 || !parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
259
|
-
throw new Error('OPENCODE_CONFIG_CONTENT must contain a valid JSON object before OpenChamber can inject its managed tool');
|
|
260
|
-
}
|
|
261
|
-
if (parsed.plugin !== undefined && !Array.isArray(parsed.plugin)) {
|
|
262
|
-
throw new Error('OPENCODE_CONFIG_CONTENT plugin must be an array before OpenChamber can inject its managed tool');
|
|
263
|
-
}
|
|
264
|
-
const configured = Array.isArray(parsed.plugin) ? parsed.plugin : [];
|
|
265
|
-
parsed.plugin = [
|
|
266
|
-
...configured.filter((value) => value !== pluginUrl && (!Array.isArray(value) || value[0] !== pluginUrl)),
|
|
267
|
-
pluginUrl,
|
|
268
|
-
];
|
|
269
|
-
return JSON.stringify(parsed);
|
|
270
|
-
};
|
|
271
|
-
|
|
272
255
|
export const createAgentToolRuntime = (dependencies) => {
|
|
273
256
|
const {
|
|
274
257
|
crypto,
|
|
@@ -296,7 +279,7 @@ export const createAgentToolRuntime = (dependencies) => {
|
|
|
296
279
|
activeToken = crypto.randomBytes(32).toString('base64url');
|
|
297
280
|
const pluginUrl = pathToFileURL(pluginPath).href;
|
|
298
281
|
return {
|
|
299
|
-
OPENCODE_CONFIG_CONTENT:
|
|
282
|
+
OPENCODE_CONFIG_CONTENT: appendManagedPlugin(env.OPENCODE_CONFIG_CONTENT, pluginUrl, 'managed tool'),
|
|
300
283
|
OPENCHAMBER_AGENT_TOOL_URL: `http://127.0.0.1:${port}/api/openchamber/agent-tool`,
|
|
301
284
|
OPENCHAMBER_AGENT_TOOL_TOKEN: activeToken,
|
|
302
285
|
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Release notes for the update dialog.
|
|
2
|
+
//
|
|
3
|
+
// The repo publishes `changelog/index.json` on `main`: one object per release,
|
|
4
|
+
// newest first, with the app notes grouped as New / Improvements / Fixes /
|
|
5
|
+
// Misc. This module fetches it and renders the releases between the installed
|
|
6
|
+
// version (exclusive) and the offered one (inclusive) as the Markdown the
|
|
7
|
+
// dialog already understands: a `## [x.y.z] - YYYY-MM-DD` header per release,
|
|
8
|
+
// the release title in bold, the intro, then the groups.
|
|
9
|
+
//
|
|
10
|
+
// Any failure (network, 404, unexpected shape) yields null: the update is
|
|
11
|
+
// still offered, only without notes.
|
|
12
|
+
|
|
13
|
+
export const CHANGELOG_INDEX_URL = 'https://raw.githubusercontent.com/openchamber/openchamber/main/changelog/index.json';
|
|
14
|
+
|
|
15
|
+
const GROUPS = [
|
|
16
|
+
['new', 'New'],
|
|
17
|
+
['improvements', 'Improvements'],
|
|
18
|
+
['fixes', 'Fixes'],
|
|
19
|
+
['misc', 'Misc'],
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
23
|
+
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
24
|
+
|
|
25
|
+
// The file crosses a network boundary with no schema library on this side, so
|
|
26
|
+
// each field is coerced into its domain shape here; a release without a valid
|
|
27
|
+
// version and date is dropped.
|
|
28
|
+
const text = (value) => (value === null || value === undefined ? null : String(value).trim() || null);
|
|
29
|
+
const bulletList = (value) => (Array.isArray(value) ? value.map((item) => String(item)) : []);
|
|
30
|
+
|
|
31
|
+
const parseRelease = (entry) => {
|
|
32
|
+
if (entry === null || entry === undefined) return null;
|
|
33
|
+
const version = text(entry.version);
|
|
34
|
+
const date = text(entry.date);
|
|
35
|
+
if (!version || !VERSION_PATTERN.test(version) || !date || !DATE_PATTERN.test(date)) return null;
|
|
36
|
+
const app = entry.app ?? {};
|
|
37
|
+
return {
|
|
38
|
+
version,
|
|
39
|
+
date,
|
|
40
|
+
title: text(entry.title),
|
|
41
|
+
intro: text(entry.intro),
|
|
42
|
+
groups: GROUPS.map(([key, heading]) => ({ heading, bullets: bulletList(app[key]) })),
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const renderRelease = (release) => {
|
|
47
|
+
const lines = [`## [${release.version}] - ${release.date}`, ''];
|
|
48
|
+
if (release.title) lines.push(`**${release.title}**`, '');
|
|
49
|
+
if (release.intro) lines.push(release.intro, '');
|
|
50
|
+
for (const { heading, bullets } of release.groups) {
|
|
51
|
+
if (bullets.length === 0) continue;
|
|
52
|
+
lines.push(`### ${heading}`, '', ...bullets.map((bullet) => `- ${bullet}`), '');
|
|
53
|
+
}
|
|
54
|
+
return lines.join('\n').trim();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Markdown for the releases in (fromVersion, toVersion], newest first, or
|
|
59
|
+
* null when the index holds none of them.
|
|
60
|
+
*/
|
|
61
|
+
export function renderUpdateNotes(index, fromVersion, toVersion, compareVersions) {
|
|
62
|
+
if (!Array.isArray(index)) return null;
|
|
63
|
+
const relevant = index
|
|
64
|
+
.map(parseRelease)
|
|
65
|
+
.filter((release) => release !== null)
|
|
66
|
+
.filter((release) => compareVersions(release.version, fromVersion) > 0 && compareVersions(release.version, toVersion) <= 0)
|
|
67
|
+
.sort((a, b) => compareVersions(b.version, a.version));
|
|
68
|
+
if (relevant.length === 0) return null;
|
|
69
|
+
return relevant.map(renderRelease).join('\n\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Fetch the index and render the notes for one update; null on any failure. */
|
|
73
|
+
export async function fetchUpdateNotes(fromVersion, toVersion, compareVersions, options = {}) {
|
|
74
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetchImpl(CHANGELOG_INDEX_URL, { signal: AbortSignal.timeout(options.timeoutMs ?? 10_000) });
|
|
77
|
+
if (!response.ok) return null;
|
|
78
|
+
return renderUpdateNotes(await response.json(), fromVersion, toVersion, compareVersions);
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { fetchUpdateNotes, renderUpdateNotes } from './update-notes.js';
|
|
3
|
+
|
|
4
|
+
const compare = (a, b) => {
|
|
5
|
+
const pa = a.split('.').map(Number);
|
|
6
|
+
const pb = b.split('.').map(Number);
|
|
7
|
+
for (let i = 0; i < 3; i += 1) if (pa[i] !== pb[i]) return pa[i] - pb[i];
|
|
8
|
+
return 0;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const index = [
|
|
12
|
+
{ version: '1.2.3', date: '2026-03-03', title: 'Comments everywhere', intro: 'A short intro.', app: { new: ['**Comments:** on code.'], improvements: [], fixes: ['Chat: no freeze.'], misc: [] }, vscode: null },
|
|
13
|
+
{ version: '1.2.2', date: '2026-03-02', title: null, intro: null, app: { new: [], improvements: ['Faster.'], fixes: [], misc: [] }, vscode: null },
|
|
14
|
+
{ version: '1.2.1', date: '2026-03-01', title: 'Old', intro: null, app: { new: ['Older.'], improvements: [], fixes: [], misc: [] }, vscode: null },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
describe('renderUpdateNotes', () => {
|
|
18
|
+
it('renders the releases after the installed version up to the offered one, newest first', () => {
|
|
19
|
+
expect(renderUpdateNotes(index, '1.2.1', '1.2.3', compare)).toBe(`## [1.2.3] - 2026-03-03
|
|
20
|
+
|
|
21
|
+
**Comments everywhere**
|
|
22
|
+
|
|
23
|
+
A short intro.
|
|
24
|
+
|
|
25
|
+
### New
|
|
26
|
+
|
|
27
|
+
- **Comments:** on code.
|
|
28
|
+
|
|
29
|
+
### Fixes
|
|
30
|
+
|
|
31
|
+
- Chat: no freeze.
|
|
32
|
+
|
|
33
|
+
## [1.2.2] - 2026-03-02
|
|
34
|
+
|
|
35
|
+
### Improvements
|
|
36
|
+
|
|
37
|
+
- Faster.`);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('returns null when nothing lies in the range or the payload is not an index', () => {
|
|
41
|
+
expect(renderUpdateNotes(index, '1.2.3', '1.2.3', compare)).toBe(null);
|
|
42
|
+
expect(renderUpdateNotes({ entries: [] }, '1.0.0', '9.9.9', compare)).toBe(null);
|
|
43
|
+
expect(renderUpdateNotes([{ version: 'nope' }, { version: '1.2.2', date: '2026' }], '1.0.0', '9.9.9', compare)).toBe(null);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('fetchUpdateNotes', () => {
|
|
48
|
+
it('turns a failed request or a thrown fetch into null', async () => {
|
|
49
|
+
const notFound = async () => ({ ok: false });
|
|
50
|
+
expect(await fetchUpdateNotes('1.0.0', '2.0.0', compare, { fetch: notFound })).toBe(null);
|
|
51
|
+
const throwing = async () => { throw new Error('offline'); };
|
|
52
|
+
expect(await fetchUpdateNotes('1.0.0', '2.0.0', compare, { fetch: throwing })).toBe(null);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders a successful response', async () => {
|
|
56
|
+
const ok = async () => ({ ok: true, json: async () => index });
|
|
57
|
+
expect(await fetchUpdateNotes('1.2.2', '1.2.3', compare, { fetch: ok })).toMatch(/^## \[1\.2\.3\] - 2026-03-03\n\n\*\*Comments everywhere\*\*/);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// One-time copy of the user-authored folders into a custom data directory.
|
|
2
|
+
//
|
|
3
|
+
// `OPENCHAMBER_DATA_DIR` is documented as "the OpenChamber data directory",
|
|
4
|
+
// but for a long time only the flat files (settings, auth, push tokens)
|
|
5
|
+
// followed it while `projects/`, `themes/`, and `speech-models/` stayed under
|
|
6
|
+
// `~/.config/openchamber`. Now every folder hangs off the data directory, so an
|
|
7
|
+
// instance that already ran with a custom directory finds those folders in
|
|
8
|
+
// the old place. This copies each one over once: only when the new location
|
|
9
|
+
// does not exist yet and the old one does. It copies rather than moves
|
|
10
|
+
// because a second instance on the same machine (a custom directory next to
|
|
11
|
+
// the default one) must not strip the default instance of its projects.
|
|
12
|
+
|
|
13
|
+
const USER_DIR_ENTRIES = Object.freeze(['projects', 'themes', 'speech-models']);
|
|
14
|
+
|
|
15
|
+
const exists = async (fsPromises, target) => fsPromises.access(target).then(() => true, () => false);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Copy the user folders from `legacyRoot` into `dataDir`. Returns the entries
|
|
19
|
+
* copied. A no-op when the two roots are the same directory. A folder whose
|
|
20
|
+
* copy fails is reported through `warn` and its partial copy removed; the
|
|
21
|
+
* server keeps starting, reading the (empty) new location.
|
|
22
|
+
*/
|
|
23
|
+
export const migrateLegacyUserDirs = async ({ fsPromises, path, dataDir, legacyRoot, warn = () => {}, entries = USER_DIR_ENTRIES }) => {
|
|
24
|
+
if (path.resolve(dataDir) === path.resolve(legacyRoot)) return [];
|
|
25
|
+
const moved = [];
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const from = path.join(legacyRoot, entry);
|
|
28
|
+
const to = path.join(dataDir, entry);
|
|
29
|
+
if (await exists(fsPromises, to) || !(await exists(fsPromises, from))) continue;
|
|
30
|
+
try {
|
|
31
|
+
await fsPromises.mkdir(dataDir, { recursive: true });
|
|
32
|
+
await fsPromises.cp(from, to, { recursive: true, errorOnExist: true, force: false });
|
|
33
|
+
moved.push(entry);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
await fsPromises.rm(to, { recursive: true, force: true }).catch(() => {});
|
|
36
|
+
warn(`Failed to copy ${from} to ${to}: ${error instanceof Error ? error.message : String(error)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return moved;
|
|
40
|
+
};
|