@openchambery/web 2.0.0-beta.3 → 2.0.0-beta.5
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-B_JQoreg.js → AgentAvatar-BBmQXYDq.js} +1 -1
- package/dist/assets/AssistantView-BG8hK4y9.js +3 -0
- package/dist/assets/{DiagramView-BNnda8qO.js → DiagramView-CN0qWNU5.js} +1 -1
- package/dist/assets/{FadeInOnReveal-VYQTSAkr.js → FadeInOnReveal-w_BQ7K7K.js} +1 -1
- package/dist/assets/Icon-B9kZgYZn.js +1 -0
- package/dist/assets/{MarkdownRenderer-CJHSsWin.js → MarkdownRenderer-Ds61--LT.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-DwrHFGUp.js → MarkdownRendererImpl-DOMmiOH0.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-CSnbQMu3.js → MarkstreamRendererImpl-F7MGg5ER.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-BXr6AGLT.js → MobileDetailNavigation-DcmVOjkw.js} +1 -1
- package/dist/assets/{MobileShareBridge-D7OE9OP_.js → MobileShareBridge-C1Fc4ErB.js} +2 -2
- package/dist/assets/MobileSurface-BA7eS5Yd.js +2 -0
- package/dist/assets/{MultiRunWindow-DF3iIFCd.js → MultiRunWindow-BoTnH6Yp.js} +1 -1
- package/dist/assets/{OnboardingScreen-BjHmlwlw.js → OnboardingScreen-BxItVnnO.js} +1 -1
- package/dist/assets/{OverlayScrollbar-DLtWO_Gp.js → OverlayScrollbar-B4e0QD05.js} +1 -1
- package/dist/assets/SettingsView-CjesWNLw.js +25 -0
- package/dist/assets/{SettingsWindow-BI5swspb.js → SettingsWindow-CReMbg-w.js} +1 -1
- package/dist/assets/TerminalView-C64-QYOG.js +8 -0
- package/dist/assets/{ToolOutputDialog-BEWhBcRd.js → ToolOutputDialog-CJu8DNrt.js} +1 -1
- package/dist/assets/{appThemeRegistry-4MR0F7Jz.js → appThemeRegistry-CF4lIxNt.js} +1 -1
- package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
- package/dist/assets/{button-DKuz3L8l.js → button-CwXyux0a.js} +1 -1
- package/dist/assets/collapsible-BZ6_cYJd.js +1 -0
- package/dist/assets/context-menu-DHrRifPE.js +1 -0
- package/dist/assets/{desktopHosts-CoDkOnyS.js → desktopHosts-JGFqhlaM.js} +1 -1
- package/dist/assets/{desktopRelayRestore-DDlVKyOE.js → desktopRelayRestore-CHEwrI93.js} +1 -1
- package/dist/assets/{diagram-D7T8HQgj.js → diagram-BwnPNKWt.js} +1 -1
- package/dist/assets/{dialog-DQYl3W_o.js → dialog-Dp0lQv5K.js} +1 -1
- package/dist/assets/es-DicJ8lKD.js +15 -0
- package/dist/assets/{fileMentionTouchSelection-BZ6ovBYH.js → fileMentionTouchSelection-DPZRxNa2.js} +3 -4
- package/dist/assets/{fileQueries-JrpcsOCs.js → fileQueries-DN6gbLhM.js} +1 -1
- package/dist/assets/fr-lhgVcBW_.js +13 -0
- package/dist/assets/{gitApi-CM42qUed.js → gitApi-D8R7ic-T.js} +3 -3
- package/dist/assets/{imageSource-CIl0FL10.js → imageSource-BMztRu0c.js} +1 -1
- package/dist/assets/{input-CzQy8UhV.js → input-Biw8aNzR.js} +1 -1
- package/dist/assets/insertionBoundaries-DdwCD4S2.js +1 -0
- package/dist/assets/ja-B9EZwOcw.js +15 -0
- package/dist/assets/ko-Dxk-uJRb.js +15 -0
- package/dist/assets/main-BhlwwJ2c.js +169 -0
- package/dist/assets/main-D2BzpnUt.css +1 -0
- package/dist/assets/main-DSPCkT74.js +2 -0
- package/dist/assets/{markdownHeightCache-Cj1I5NDw.js → markdownHeightCache-BOOrGCuG.js} +1 -1
- package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
- package/dist/assets/{mcpQueries-D5mQymgM.js → mcpQueries-CE9tk-Jz.js} +1 -1
- package/dist/assets/miniChat-Ckk9ABql.js +2 -0
- package/dist/assets/mobile-cLMRZqI9.js +2 -0
- package/dist/assets/multirun-DVnFRb1V.js +4 -0
- package/dist/assets/persistence-D0y7226-.js +3 -0
- package/dist/assets/pl-CFhwWLJj.js +15 -0
- package/dist/assets/platform-D_c-T5nO.js +6 -0
- package/dist/assets/projectMeta-CSQgHequ.js +3 -0
- package/dist/assets/pt-BR-Ba6GfZ6J.js +15 -0
- package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-gGFkwoL_.js +2 -0
- package/dist/assets/renderMobileApp-B2w50kAc.js +28 -0
- package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
- package/dist/assets/{runtimeConfig-BIGBRGQI.js → runtimeConfig-BHH1hJDd.js} +1 -1
- package/dist/assets/runtimeEndpointReset-5uJ4sJk0.js +149 -0
- package/dist/assets/{sessionChangeSummary-eIbpSfrO.js → sessionChangeSummary-spf_atO8.js} +2 -2
- package/dist/assets/{sessionLookup-myHe5ltu.js → sessionLookup-BGjjwiCq.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-BQtmHx9r.js → sortable-tabs-strip-CTY_wG46.js} +1 -1
- package/dist/assets/src-BjEyU6if.css +2 -0
- package/dist/assets/store-DmowK-GO.js +15 -0
- package/dist/assets/{terminalApi-BxcdiduX.js → terminalApi-Dhqqp4xi.js} +1 -1
- package/dist/assets/textarea-QxsmcBGj.js +1 -0
- package/dist/assets/{tooltip-Cx5gTbdY.js → tooltip-CpMLl5_k.js} +1 -1
- package/dist/assets/uk-ChchBzy6.js +15 -0
- package/dist/assets/{url-CDHnjlsm.js → url-CGbJbiJq.js} +1 -1
- package/dist/assets/{useAppFontEffects-CNeo0aZs.js → useAppFontEffects-B1h7ab_J.js} +55 -50
- package/dist/assets/{useAssistantUIStore-asNG6K7n.js → useAssistantUIStore-Bz4tRflF.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-Cxsx84NO.js +1 -0
- package/dist/assets/{useEffectiveDirectory-B4LvR8Gt.js → useEffectiveDirectory-DsqhqsJR.js} +1 -1
- package/dist/assets/{useFontPreferences-CUnQ4TVm.js → useFontPreferences-Xw1vSkmC.js} +1 -1
- package/dist/assets/useMobileNavigationStore-B1axS4g-.js +414 -0
- package/dist/assets/{useRuntimeAPIs-DwLtO0c_.js → useRuntimeAPIs-DSLXJybW.js} +1 -1
- package/dist/assets/useSessionAutoCleanup-B5FTG_qf.js +2 -0
- package/dist/assets/{useThemeSystem-BlNUWH47.js → useThemeSystem-DMzWVJaW.js} +1 -1
- package/dist/assets/useUIStore-syVmbuDA.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-CJZbJDW3.js → useWorkerHighlightedLines-DC_Bc-RV.js} +1 -1
- package/dist/assets/{vendor-base-ui-CDjyofeD.js → vendor-base-ui-CnHfUmDN.js} +1 -1
- package/dist/assets/{vendor-cmdk-BFH0UpXX.js → vendor-cmdk-CeAGfrLo.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-accessibility-BrR3AKvM.js → vendor-dnd-kit-accessibility-BY5LhhUB.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-core-CNfdvk6H.js → vendor-dnd-kit-core-CvWxUPdb.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-sortable-DQUaTeGj.js → vendor-dnd-kit-sortable-hHdP9lLb.js} +1 -1
- package/dist/assets/{vendor-framer-motion-4dogsUk3.js → vendor-framer-motion-DFy_BSVd.js} +1 -1
- package/dist/assets/{vendor-legendapp-list-BU7MdPZR.js → vendor-legendapp-list-WE32lwNk.js} +1 -1
- package/dist/assets/{vendor-markstream-react-qHC2FykU.js → vendor-markstream-react-DX55XLGO.js} +3 -3
- package/dist/assets/vendor-opencode-client-C12gfjag.js +12 -0
- package/dist/assets/{vendor-pierre-diffs-CUfqC003.js → vendor-pierre-diffs-DX7XTPG3.js} +1 -1
- package/dist/assets/{vendor-react-C94A0AHl.js → vendor-react-DLPElpoQ.js} +1 -1
- package/dist/assets/{vendor-react-drawio-FVlWqKVE.js → vendor-react-drawio-ECtEwvbc.js} +1 -1
- package/dist/assets/{vendor-reactuses-core-C2A3EjUi.js → vendor-reactuses-core-B8parU1Y.js} +1 -1
- package/dist/assets/{vendor-sonner-DNqHmUQs.js → vendor-sonner-DVzioF3d.js} +1 -1
- package/dist/assets/{vendor-stream-diffs-CwbKsmlv.js → vendor-stream-diffs-DWy3xSmp.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-query-D0iFK57C.js → vendor-tanstack-react-query-D7pys_c9.js} +1 -1
- package/dist/assets/{vendor-tanstack-react-virtual-B2kV6cnT.js → vendor-tanstack-react-virtual-tlDgH6PG.js} +1 -1
- package/dist/assets/{vendor-zustand-BqLKf6ZY.js → vendor-zustand-B4dlGEM2.js} +1 -1
- package/dist/assets/zh-CN-Z_bkUQk5.js +15 -0
- package/dist/assets/zh-TW-CZeTm7kd.js +15 -0
- package/dist/index.html +14 -14
- package/dist/mini-chat.html +14 -14
- package/dist/mobile.html +14 -14
- package/package.json +3 -2
- package/server/index.js +38 -7
- package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
- package/server/lib/browser-provider/contract.js +312 -0
- package/server/lib/browser-provider/host.js +421 -0
- package/server/lib/browser-provider/host.test.js +304 -0
- package/server/lib/browser-provider/routes.js +122 -0
- package/server/lib/browser-provider/routes.test.js +120 -0
- package/server/lib/browser-provider/selection-store.js +43 -0
- package/server/lib/browser-provider/selection-store.test.js +35 -0
- package/server/lib/event-stream/DOCUMENTATION.md +3 -1
- package/server/lib/event-stream/reasoning-projection.js +16 -1
- package/server/lib/event-stream/reasoning-projection.test.js +15 -0
- package/server/lib/notifications/DOCUMENTATION.md +3 -1
- package/server/lib/notifications/runtime.js +128 -31
- package/server/lib/notifications/runtime.test.js +103 -0
- package/server/lib/notifications/template-runtime.js +130 -38
- package/server/lib/notifications/template-runtime.test.js +96 -0
- package/server/lib/opencode/DOCUMENTATION.md +8 -0
- package/server/lib/opencode/config-entity-routes.js +6 -1
- package/server/lib/opencode/config-entity-routes.test.js +25 -0
- package/server/lib/opencode/ensure-cli.d.ts +8 -0
- package/server/lib/opencode/feature-routes-runtime.js +10 -0
- package/server/lib/opencode/feature-routes-runtime.test.js +7 -0
- package/server/lib/opencode/proxy.js +6 -1
- package/server/lib/opencode/proxy.test.js +18 -3
- package/server/lib/opencode/routes.js +30 -0
- package/server/lib/opencode/upgrade-screen.d.ts +30 -0
- package/server/lib/opencode/upgrade-screen.js +299 -0
- package/server/lib/opencode/upgrade-screen.test.js +196 -0
- package/server/lib/session-metadata/DOCUMENTATION.md +27 -17
- package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
- package/server/lib/session-metadata/opencode-session-record.js +316 -0
- package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
- package/server/lib/session-metadata/routes.js +5 -1
- package/server/lib/session-metadata/session-archive.js +1 -1
- package/server/lib/session-metadata/session-metadata-store.d.ts +2 -0
- package/server/lib/session-metadata/session-metadata-store.js +63 -16
- package/server/lib/session-metadata/session-projection.js +26 -2
- package/dist/assets/AssistantView-ClQAfGOT.js +0 -3
- package/dist/assets/Icon-BiEQEcih.js +0 -1
- package/dist/assets/MobileSurface-D2jUkX9l.js +0 -2
- package/dist/assets/SettingsView-BRxELjow.js +0 -25
- package/dist/assets/TerminalView-BU3x8-fr.js +0 -8
- package/dist/assets/collapsible-DXbDynN4.js +0 -3
- package/dist/assets/context-menu-CqolsLCm.js +0 -1
- package/dist/assets/es-CN3E_zXj.js +0 -15
- package/dist/assets/fr-9zZvQZD2.js +0 -13
- package/dist/assets/insertionBoundaries-CtkZ9720.js +0 -1
- package/dist/assets/ja-Dnjk_82N.js +0 -15
- package/dist/assets/ko-BxCEE99C.js +0 -15
- package/dist/assets/main-BILgBI3B.js +0 -169
- package/dist/assets/main-BeveiW6F.js +0 -2
- package/dist/assets/main-qSeeJRW1.css +0 -1
- package/dist/assets/markdownTheme-CKac3wKC.js +0 -1
- package/dist/assets/miniChat-BgrzM3u7.js +0 -2
- package/dist/assets/mobile-DtYoQE8C.js +0 -2
- package/dist/assets/multirun-jy8dEI96.js +0 -4
- package/dist/assets/persistence-C9LQpiOS.js +0 -3
- package/dist/assets/pl-CNNsWnPM.js +0 -15
- package/dist/assets/platform-BQghHRfp.js +0 -6
- package/dist/assets/pt-BR-BZB2DJMp.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +0 -2
- package/dist/assets/renderMobileApp-u3c4GtFC.js +0 -28
- package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
- package/dist/assets/runtimeEndpointReset-D2savkuw.js +0 -147
- package/dist/assets/src-CLN7Z-zx.css +0 -2
- package/dist/assets/store-DLCiZaA6.js +0 -15
- package/dist/assets/textarea-CakKvqFw.js +0 -1
- package/dist/assets/uk-rwQrxeYM.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +0 -1
- package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +0 -414
- package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +0 -2
- package/dist/assets/useUIStore-CQYKJnrg.js +0 -1
- package/dist/assets/vendor-opencode-client-BOe2qpQb.js +0 -12
- package/dist/assets/zh-CN-BvmBDnkD.js +0 -15
- package/dist/assets/zh-TW-9wcHTX0y.js +0 -15
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup upgrade screen for OpenCode below 2.0.15.
|
|
3
|
+
*
|
|
4
|
+
* This module does not own process lifecycle. It classifies the version the
|
|
5
|
+
* existing startup path already detected, and installs through the owned-cache
|
|
6
|
+
* installer (`installPinnedOpenCode2Cli`) plus the caller's restart hook.
|
|
7
|
+
* Success is a verified running version, never a download that has not started.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
PINNED_OPENCODE2_VERSION,
|
|
12
|
+
isAcceptableOpenCode2HealthVersion,
|
|
13
|
+
isOpenCode1xVersion,
|
|
14
|
+
isOpenCode2VersionAtLeast,
|
|
15
|
+
npmPackageForOpenCode2,
|
|
16
|
+
} from './opencode2-pin.js';
|
|
17
|
+
|
|
18
|
+
/** Oldest OpenCode this OpenChamber build will enter a session against. */
|
|
19
|
+
export const REQUIRED_OPENCODE_VERSION = '2.0.15';
|
|
20
|
+
|
|
21
|
+
const normalizeVersion = (value) => {
|
|
22
|
+
if (typeof value !== 'string') return null;
|
|
23
|
+
const trimmed = value.trim().replace(/^v/i, '');
|
|
24
|
+
return trimmed || null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Install target is the pin when the pin already satisfies the screen, otherwise
|
|
29
|
+
* the screen minimum. A pin still at 2.0.12 must not be what this screen installs.
|
|
30
|
+
* @param {string} [pin]
|
|
31
|
+
*/
|
|
32
|
+
export function resolveUpgradeScreenTarget(pin = PINNED_OPENCODE2_VERSION) {
|
|
33
|
+
if (isOpenCode2VersionAtLeast(pin, REQUIRED_OPENCODE_VERSION)) return pin;
|
|
34
|
+
return REQUIRED_OPENCODE_VERSION;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 2.x at or above the screen minimum. A future major is not this contract. */
|
|
38
|
+
function isUpgradeScreenSatisfied(version) {
|
|
39
|
+
const normalized = normalizeVersion(version);
|
|
40
|
+
if (!normalized || !isAcceptableOpenCode2HealthVersion(normalized)) return false;
|
|
41
|
+
return isOpenCode2VersionAtLeast(normalized, REQUIRED_OPENCODE_VERSION);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isOlderThanUpgradeScreenMinimum(version) {
|
|
45
|
+
const normalized = normalizeVersion(version);
|
|
46
|
+
if (!normalized) return false;
|
|
47
|
+
if (isOpenCode1xVersion(normalized)) return true;
|
|
48
|
+
if (!isAcceptableOpenCode2HealthVersion(normalized)) return false;
|
|
49
|
+
return !isOpenCode2VersionAtLeast(normalized, REQUIRED_OPENCODE_VERSION);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {string | null | undefined} ownership
|
|
54
|
+
* @returns {'managed' | 'external' | 'bundled'}
|
|
55
|
+
*/
|
|
56
|
+
function classifyUpgradeScreenInstallation(ownership) {
|
|
57
|
+
if (ownership === 'external-serve') return 'external';
|
|
58
|
+
if (ownership === 'bundled') return 'bundled';
|
|
59
|
+
return 'managed';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function platformSupportsUpgradeScreenInstall(platform = process.platform, arch = process.arch) {
|
|
63
|
+
try {
|
|
64
|
+
npmPackageForOpenCode2(platform, arch);
|
|
65
|
+
return true;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const pickDetectedVersion = (serveVersion, cliVersion) => {
|
|
72
|
+
const serve = normalizeVersion(serveVersion);
|
|
73
|
+
const cli = normalizeVersion(cliVersion);
|
|
74
|
+
if (serve && !isUpgradeScreenSatisfied(serve)) return serve;
|
|
75
|
+
if (serve) return serve;
|
|
76
|
+
return cli;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {{
|
|
81
|
+
* version?: string | null,
|
|
82
|
+
* installation?: 'managed' | 'external' | 'bundled',
|
|
83
|
+
* platformCanInstall?: boolean,
|
|
84
|
+
* guidance?: string | null,
|
|
85
|
+
* pin?: string,
|
|
86
|
+
* }} input
|
|
87
|
+
*/
|
|
88
|
+
export function describeUpgradeScreen(input = {}) {
|
|
89
|
+
const version = normalizeVersion(input.version);
|
|
90
|
+
const installation = input.installation === 'external' || input.installation === 'bundled'
|
|
91
|
+
? input.installation
|
|
92
|
+
: 'managed';
|
|
93
|
+
const state = version == null
|
|
94
|
+
? 'unavailable'
|
|
95
|
+
: isUpgradeScreenSatisfied(version)
|
|
96
|
+
? 'compatible'
|
|
97
|
+
: 'incompatible';
|
|
98
|
+
const older = version != null && isOlderThanUpgradeScreenMinimum(version);
|
|
99
|
+
// External processes are not replaced. Bundled and managed locals can receive
|
|
100
|
+
// an owned-cache install; that does not overwrite the user's other binaries.
|
|
101
|
+
const canInstall = state === 'incompatible'
|
|
102
|
+
&& older
|
|
103
|
+
&& installation !== 'external'
|
|
104
|
+
&& input.platformCanInstall === true;
|
|
105
|
+
const guidance = canInstall
|
|
106
|
+
? null
|
|
107
|
+
: (typeof input.guidance === 'string' && input.guidance.trim() ? input.guidance.trim() : null);
|
|
108
|
+
return {
|
|
109
|
+
state,
|
|
110
|
+
version,
|
|
111
|
+
installation,
|
|
112
|
+
minimumVersion: REQUIRED_OPENCODE_VERSION,
|
|
113
|
+
targetVersion: resolveUpgradeScreenTarget(input.pin),
|
|
114
|
+
canInstall,
|
|
115
|
+
guidance,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A running serve below the minimum, or a missing version, is not an upgrade.
|
|
121
|
+
* @param {{ targetVersion?: string | null, serveVersion?: string | null }} input
|
|
122
|
+
*/
|
|
123
|
+
export function evaluateUpgradeScreenInstallResult(input = {}) {
|
|
124
|
+
const targetVersion = resolveUpgradeScreenTarget(input.targetVersion || REQUIRED_OPENCODE_VERSION);
|
|
125
|
+
const version = normalizeVersion(input.serveVersion);
|
|
126
|
+
if (!version || !isUpgradeScreenSatisfied(version) || !isOpenCode2VersionAtLeast(version, targetVersion)) {
|
|
127
|
+
const found = version || 'unknown';
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
upgraded: false,
|
|
131
|
+
version,
|
|
132
|
+
targetVersion,
|
|
133
|
+
errorCode: 'UPGRADE_SCREEN_NOT_VERIFIED',
|
|
134
|
+
error: `OpenCode is still ${found}. OpenChamber requires ${REQUIRED_OPENCODE_VERSION} or newer.`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
ok: true,
|
|
139
|
+
upgraded: true,
|
|
140
|
+
version,
|
|
141
|
+
targetVersion,
|
|
142
|
+
error: null,
|
|
143
|
+
errorCode: null,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function readUpgradeScreenStatus(deps) {
|
|
148
|
+
const ownership = typeof deps.resolveOwnership === 'function'
|
|
149
|
+
? await deps.resolveOwnership()
|
|
150
|
+
: { ownership: 'unknown', guidance: null };
|
|
151
|
+
const installation = classifyUpgradeScreenInstallation(ownership?.ownership);
|
|
152
|
+
const serveVersion = typeof deps.readServeVersion === 'function'
|
|
153
|
+
? await Promise.resolve(deps.readServeVersion()).catch(() => null)
|
|
154
|
+
: null;
|
|
155
|
+
const cliVersion = typeof deps.readCliVersion === 'function'
|
|
156
|
+
? await Promise.resolve(deps.readCliVersion()).catch(() => null)
|
|
157
|
+
: null;
|
|
158
|
+
return describeUpgradeScreen({
|
|
159
|
+
version: pickDetectedVersion(serveVersion, cliVersion),
|
|
160
|
+
installation,
|
|
161
|
+
platformCanInstall: typeof deps.platformCanInstall === 'boolean'
|
|
162
|
+
? deps.platformCanInstall
|
|
163
|
+
: platformSupportsUpgradeScreenInstall(deps.platform, deps.arch),
|
|
164
|
+
guidance: ownership?.guidance ?? null,
|
|
165
|
+
pin: deps.pin,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const failure = (message, code, status = 500) => {
|
|
170
|
+
const error = new Error(message);
|
|
171
|
+
error.code = code;
|
|
172
|
+
error.status = status;
|
|
173
|
+
return error;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Install the screen target into the owned cache and restart through the
|
|
178
|
+
* caller's existing lifecycle hooks. Restores the previous binary selection
|
|
179
|
+
* when verification fails. Never returns upgraded:true for a failed install.
|
|
180
|
+
* @param {object} deps
|
|
181
|
+
*/
|
|
182
|
+
export async function installRequiredOpenCode(deps) {
|
|
183
|
+
const status = await readUpgradeScreenStatus(deps);
|
|
184
|
+
if (status.state !== 'incompatible' || status.canInstall !== true) {
|
|
185
|
+
throw failure(
|
|
186
|
+
status.guidance || 'Automatic OpenCode installation is unavailable for this runtime.',
|
|
187
|
+
'UPGRADE_SCREEN_UNAVAILABLE',
|
|
188
|
+
409,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
const target = status.targetVersion;
|
|
192
|
+
const previousBinary = typeof deps.getResolvedBinary === 'function' ? deps.getResolvedBinary() : null;
|
|
193
|
+
const previousSource = typeof deps.getResolvedBinarySource === 'function' ? deps.getResolvedBinarySource() : null;
|
|
194
|
+
const previousSetting = typeof deps.readConfiguredBinary === 'function'
|
|
195
|
+
? await deps.readConfiguredBinary()
|
|
196
|
+
: undefined;
|
|
197
|
+
let persisted = false;
|
|
198
|
+
try {
|
|
199
|
+
if (typeof deps.install !== 'function') {
|
|
200
|
+
throw failure('OpenCode install is not configured.', 'UPGRADE_SCREEN_INSTALL_UNAVAILABLE', 500);
|
|
201
|
+
}
|
|
202
|
+
const installedPath = await deps.install({ version: target });
|
|
203
|
+
const diskVersion = typeof deps.readBinaryVersion === 'function'
|
|
204
|
+
? await Promise.resolve(deps.readBinaryVersion(installedPath))
|
|
205
|
+
: null;
|
|
206
|
+
if (diskVersion !== target) {
|
|
207
|
+
throw failure(
|
|
208
|
+
`Installed OpenCode version mismatch: expected ${target}, got ${diskVersion || 'unknown'}`,
|
|
209
|
+
'UPGRADE_SCREEN_BINARY_MISMATCH',
|
|
210
|
+
500,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (typeof deps.persistBinary === 'function') {
|
|
214
|
+
await deps.persistBinary(installedPath);
|
|
215
|
+
persisted = true;
|
|
216
|
+
}
|
|
217
|
+
if (typeof deps.forceBinary === 'function') {
|
|
218
|
+
deps.forceBinary(installedPath, 'installed');
|
|
219
|
+
}
|
|
220
|
+
if (typeof deps.restart === 'function') {
|
|
221
|
+
const shared = typeof deps.isSharedService === 'function' && deps.isSharedService() === true;
|
|
222
|
+
await deps.restart(shared ? { binaryPath: installedPath } : undefined);
|
|
223
|
+
}
|
|
224
|
+
if (typeof deps.waitReady === 'function') {
|
|
225
|
+
await deps.waitReady();
|
|
226
|
+
}
|
|
227
|
+
const serveVersion = typeof deps.readServeVersion === 'function'
|
|
228
|
+
? await deps.readServeVersion()
|
|
229
|
+
: null;
|
|
230
|
+
const verification = evaluateUpgradeScreenInstallResult({ targetVersion: target, serveVersion });
|
|
231
|
+
if (!verification.ok) {
|
|
232
|
+
throw failure(verification.error, verification.errorCode, 500);
|
|
233
|
+
}
|
|
234
|
+
return verification;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (persisted && typeof deps.persistBinary === 'function') {
|
|
237
|
+
try {
|
|
238
|
+
await deps.persistBinary(typeof previousSetting === 'string' ? previousSetting : '');
|
|
239
|
+
} catch {
|
|
240
|
+
// Keep the install error. A failed restore must not become success.
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (previousBinary && typeof deps.forceBinary === 'function') {
|
|
244
|
+
try {
|
|
245
|
+
deps.forceBinary(previousBinary, previousSource || 'discovered');
|
|
246
|
+
} catch {
|
|
247
|
+
// Same: report the install failure, not a restored-success.
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function registerUpgradeScreenRoutes(app, deps) {
|
|
255
|
+
app.get('/api/opencode/compatibility', async (_req, res) => {
|
|
256
|
+
try {
|
|
257
|
+
res.json(await readUpgradeScreenStatus(deps));
|
|
258
|
+
} catch (error) {
|
|
259
|
+
res.status(503).json({
|
|
260
|
+
error: error instanceof Error ? error.message : 'Could not check OpenCode compatibility',
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
let inFlight = null;
|
|
266
|
+
app.post('/api/opencode/install-required', async (_req, res) => {
|
|
267
|
+
try {
|
|
268
|
+
if (!inFlight) {
|
|
269
|
+
inFlight = installRequiredOpenCode(deps).finally(() => {
|
|
270
|
+
inFlight = null;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
const result = await inFlight;
|
|
274
|
+
if (!result || result.ok !== true || result.upgraded !== true || !result.version) {
|
|
275
|
+
return res.status(500).json({
|
|
276
|
+
success: false,
|
|
277
|
+
upgraded: false,
|
|
278
|
+
error: result?.error || 'OpenCode installation did not verify a new version',
|
|
279
|
+
errorCode: result?.errorCode || 'UPGRADE_SCREEN_NOT_VERIFIED',
|
|
280
|
+
version: result?.version ?? null,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return res.json({
|
|
284
|
+
success: true,
|
|
285
|
+
upgraded: true,
|
|
286
|
+
version: result.version,
|
|
287
|
+
targetVersion: result.targetVersion,
|
|
288
|
+
});
|
|
289
|
+
} catch (error) {
|
|
290
|
+
const status = Number.isInteger(error?.status) ? error.status : 500;
|
|
291
|
+
return res.status(status).json({
|
|
292
|
+
success: false,
|
|
293
|
+
upgraded: false,
|
|
294
|
+
error: error instanceof Error ? error.message : 'OpenCode installation failed',
|
|
295
|
+
errorCode: error?.code || 'UPGRADE_SCREEN_FAILED',
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
REQUIRED_OPENCODE_VERSION,
|
|
6
|
+
describeUpgradeScreen,
|
|
7
|
+
evaluateUpgradeScreenInstallResult,
|
|
8
|
+
installRequiredOpenCode,
|
|
9
|
+
registerUpgradeScreenRoutes,
|
|
10
|
+
resolveUpgradeScreenTarget,
|
|
11
|
+
} from './upgrade-screen.js';
|
|
12
|
+
|
|
13
|
+
describe('upgrade screen version gate', () => {
|
|
14
|
+
it('uses 2.0.15 even when the runtime pin is older', () => {
|
|
15
|
+
expect(REQUIRED_OPENCODE_VERSION).toBe('2.0.15');
|
|
16
|
+
expect(resolveUpgradeScreenTarget('2.0.12')).toBe('2.0.15');
|
|
17
|
+
expect(resolveUpgradeScreenTarget('2.0.16')).toBe('2.0.16');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('does not open the gate at or above 2.0.15', () => {
|
|
21
|
+
expect(describeUpgradeScreen({
|
|
22
|
+
version: '2.0.15',
|
|
23
|
+
installation: 'managed',
|
|
24
|
+
platformCanInstall: true,
|
|
25
|
+
})).toMatchObject({ state: 'compatible', canInstall: false, minimumVersion: '2.0.15' });
|
|
26
|
+
expect(describeUpgradeScreen({
|
|
27
|
+
version: 'v2.0.16',
|
|
28
|
+
installation: 'managed',
|
|
29
|
+
platformCanInstall: true,
|
|
30
|
+
}).state).toBe('compatible');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('opens the gate below 2.0.15 and offers install only when this runtime can switch', () => {
|
|
34
|
+
expect(describeUpgradeScreen({
|
|
35
|
+
version: '2.0.14',
|
|
36
|
+
installation: 'managed',
|
|
37
|
+
platformCanInstall: true,
|
|
38
|
+
})).toMatchObject({ state: 'incompatible', canInstall: true, version: '2.0.14' });
|
|
39
|
+
expect(describeUpgradeScreen({
|
|
40
|
+
version: '1.18.30',
|
|
41
|
+
installation: 'managed',
|
|
42
|
+
platformCanInstall: true,
|
|
43
|
+
})).toMatchObject({ state: 'incompatible', canInstall: true });
|
|
44
|
+
expect(describeUpgradeScreen({
|
|
45
|
+
version: '2.0.14',
|
|
46
|
+
installation: 'external',
|
|
47
|
+
platformCanInstall: true,
|
|
48
|
+
}).canInstall).toBe(false);
|
|
49
|
+
expect(describeUpgradeScreen({
|
|
50
|
+
version: '2.0.14',
|
|
51
|
+
installation: 'bundled',
|
|
52
|
+
platformCanInstall: true,
|
|
53
|
+
})).toMatchObject({ state: 'incompatible', canInstall: true, installation: 'bundled' });
|
|
54
|
+
expect(describeUpgradeScreen({
|
|
55
|
+
version: '2.0.14',
|
|
56
|
+
installation: 'managed',
|
|
57
|
+
platformCanInstall: false,
|
|
58
|
+
}).canInstall).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('does not treat a missing version as an old install', () => {
|
|
62
|
+
expect(describeUpgradeScreen({
|
|
63
|
+
version: null,
|
|
64
|
+
installation: 'managed',
|
|
65
|
+
platformCanInstall: true,
|
|
66
|
+
}).state).toBe('unavailable');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('does not report an unverified install as upgraded', () => {
|
|
70
|
+
expect(evaluateUpgradeScreenInstallResult({
|
|
71
|
+
targetVersion: '2.0.15',
|
|
72
|
+
serveVersion: '2.0.14',
|
|
73
|
+
})).toMatchObject({ ok: false, upgraded: false, version: '2.0.14' });
|
|
74
|
+
expect(evaluateUpgradeScreenInstallResult({
|
|
75
|
+
targetVersion: '2.0.15',
|
|
76
|
+
serveVersion: null,
|
|
77
|
+
}).upgraded).toBe(false);
|
|
78
|
+
expect(evaluateUpgradeScreenInstallResult({
|
|
79
|
+
targetVersion: '2.0.15',
|
|
80
|
+
serveVersion: '2.0.15',
|
|
81
|
+
})).toMatchObject({ ok: true, upgraded: true, version: '2.0.15' });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('installRequiredOpenCode', () => {
|
|
86
|
+
const createDeps = (overrides = {}) => {
|
|
87
|
+
const calls = [];
|
|
88
|
+
const deps = {
|
|
89
|
+
resolveOwnership: async () => ({ ownership: 'global-cli', guidance: null }),
|
|
90
|
+
readServeVersion: async () => '2.0.14',
|
|
91
|
+
readCliVersion: () => '2.0.14',
|
|
92
|
+
platformCanInstall: true,
|
|
93
|
+
install: vi.fn(async () => {
|
|
94
|
+
calls.push('install');
|
|
95
|
+
return '/cache/opencode';
|
|
96
|
+
}),
|
|
97
|
+
readBinaryVersion: vi.fn(() => '2.0.15'),
|
|
98
|
+
persistBinary: vi.fn(async () => {
|
|
99
|
+
calls.push('persist');
|
|
100
|
+
}),
|
|
101
|
+
forceBinary: vi.fn(() => {
|
|
102
|
+
calls.push('force');
|
|
103
|
+
}),
|
|
104
|
+
restart: vi.fn(async () => {
|
|
105
|
+
calls.push('restart');
|
|
106
|
+
}),
|
|
107
|
+
waitReady: vi.fn(async () => {
|
|
108
|
+
calls.push('ready');
|
|
109
|
+
}),
|
|
110
|
+
readConfiguredBinary: async () => null,
|
|
111
|
+
isSharedService: () => false,
|
|
112
|
+
...overrides,
|
|
113
|
+
};
|
|
114
|
+
return { deps, calls };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
it('installs 2.0.15 and only succeeds after the running serve is verified', async () => {
|
|
118
|
+
const { deps, calls } = createDeps({
|
|
119
|
+
readServeVersion: vi.fn()
|
|
120
|
+
.mockResolvedValueOnce('2.0.14')
|
|
121
|
+
.mockResolvedValueOnce('2.0.15'),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await expect(installRequiredOpenCode(deps)).resolves.toMatchObject({
|
|
125
|
+
ok: true,
|
|
126
|
+
upgraded: true,
|
|
127
|
+
version: '2.0.15',
|
|
128
|
+
targetVersion: '2.0.15',
|
|
129
|
+
});
|
|
130
|
+
expect(deps.install).toHaveBeenCalledWith({ version: '2.0.15' });
|
|
131
|
+
expect(calls).toEqual(['install', 'persist', 'force', 'restart', 'ready']);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('returns the failure reason and does not claim an upgrade when verification fails', async () => {
|
|
135
|
+
const { deps } = createDeps({
|
|
136
|
+
readServeVersion: vi.fn()
|
|
137
|
+
.mockResolvedValueOnce('1.18.30')
|
|
138
|
+
.mockResolvedValueOnce('1.18.30'),
|
|
139
|
+
readCliVersion: () => '1.18.30',
|
|
140
|
+
readBinaryVersion: () => '2.0.15',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await expect(installRequiredOpenCode(deps)).rejects.toThrow(/still 1\.18\.30/);
|
|
144
|
+
expect(deps.persistBinary).toHaveBeenCalledWith('');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('does not install when the runtime cannot switch', async () => {
|
|
148
|
+
const { deps } = createDeps({
|
|
149
|
+
resolveOwnership: async () => ({
|
|
150
|
+
ownership: 'external-serve',
|
|
151
|
+
guidance: 'Restart that server yourself.',
|
|
152
|
+
}),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await expect(installRequiredOpenCode(deps)).rejects.toMatchObject({
|
|
156
|
+
status: 409,
|
|
157
|
+
message: 'Restart that server yourself.',
|
|
158
|
+
});
|
|
159
|
+
expect(deps.install).not.toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('upgrade screen routes', () => {
|
|
164
|
+
it('hides the gate for 2.0.15 and reports install failure without upgraded:true', async () => {
|
|
165
|
+
const app = express();
|
|
166
|
+
app.use(express.json());
|
|
167
|
+
const readServeVersion = vi.fn()
|
|
168
|
+
.mockResolvedValueOnce('2.0.15')
|
|
169
|
+
.mockResolvedValueOnce('2.0.12')
|
|
170
|
+
.mockResolvedValueOnce('2.0.12');
|
|
171
|
+
registerUpgradeScreenRoutes(app, {
|
|
172
|
+
resolveOwnership: async () => ({ ownership: 'owned-cache', guidance: null }),
|
|
173
|
+
readServeVersion,
|
|
174
|
+
readCliVersion: () => '2.0.12',
|
|
175
|
+
platformCanInstall: true,
|
|
176
|
+
install: vi.fn(async () => {
|
|
177
|
+
throw new Error('registry unreachable');
|
|
178
|
+
}),
|
|
179
|
+
readBinaryVersion: () => '2.0.15',
|
|
180
|
+
persistBinary: vi.fn(),
|
|
181
|
+
forceBinary: vi.fn(),
|
|
182
|
+
restart: vi.fn(),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const compatible = await request(app).get('/api/opencode/compatibility').expect(200);
|
|
186
|
+
expect(compatible.body).toMatchObject({ state: 'compatible', version: '2.0.15', minimumVersion: '2.0.15' });
|
|
187
|
+
|
|
188
|
+
const failed = await request(app).post('/api/opencode/install-required').expect(500);
|
|
189
|
+
expect(failed.body).toMatchObject({
|
|
190
|
+
success: false,
|
|
191
|
+
upgraded: false,
|
|
192
|
+
error: 'registry unreachable',
|
|
193
|
+
});
|
|
194
|
+
expect(failed.body.upgraded).not.toBe(true);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -1,32 +1,42 @@
|
|
|
1
1
|
# Session metadata store
|
|
2
2
|
|
|
3
|
-
OpenChamber
|
|
4
|
-
|
|
3
|
+
OpenChamber session metadata whose durable copy lives on the OpenCode session
|
|
4
|
+
record. The side JSON file is the cache and the pre-migration source.
|
|
5
5
|
|
|
6
6
|
## Why
|
|
7
7
|
|
|
8
|
-
Session goal progress, Host archive stamps, and
|
|
9
|
-
`openchamber` namespace
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
Session goal progress, Host archive stamps, titles, and other Host state under
|
|
9
|
+
the `openchamber` namespace travel with the OpenCode session. `session.update`
|
|
10
|
+
(`PATCH /api/session/:id`, body `{ title?, metadata?, permissions? }`) replaces
|
|
11
|
+
`metadata` wholesale. Archive is `metadata.openchamber.archive.archivedAt` on
|
|
12
|
+
that record and is still projected onto `time.archived`. Title is the record's
|
|
13
|
+
`title` field and is not sent when only metadata changes.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
`opencode-session-record.js` is the only module that knows that call. A client
|
|
16
|
+
whose types omit `session.update` metadata still goes through that module;
|
|
17
|
+
a missing API throws and is never an empty write.
|
|
18
|
+
|
|
19
|
+
The side file remains until each row has been copied or OpenCode has confirmed
|
|
20
|
+
the session is gone. Until that load succeeds, the file is not an authoritative
|
|
21
|
+
empty catalog: a failed read stays unwritable and does not clear other sessions.
|
|
22
|
+
A session OpenCode still has is listed even when the side file has no row.
|
|
23
|
+
One failed copy does not delete the other rows or the other OpenCode sessions.
|
|
24
|
+
|
|
25
|
+
Fork-specific fields (`goal`, `assistant`, `scheduledTask`, `llm`, `smallModel`,
|
|
26
|
+
and the rest of `openchamber`) are merged onto the record. They are not dropped
|
|
27
|
+
to match a narrower upstream metadata shape.
|
|
16
28
|
|
|
17
29
|
| OpenCode client | Session metadata update |
|
|
18
30
|
|---|---|
|
|
19
31
|
| Earlier 2.x releases used during the migration | `metadata` accepted at create time only |
|
|
20
|
-
| 2.0.14 | `PATCH /api/session/:id` with body `{ title, metadata, permissions }` (`session.update`) |
|
|
21
|
-
|
|
22
|
-
Adopting the upstream update API for Host-owned fields requires a separate
|
|
23
|
-
decision about authority, conditional writes, and archive protection.
|
|
32
|
+
| 2.0.14+ / 2.0.15 | `PATCH /api/session/:id` with body `{ title, metadata, permissions }` (`session.update`) |
|
|
24
33
|
|
|
25
34
|
## Layout
|
|
26
35
|
|
|
27
36
|
| File | Role |
|
|
28
37
|
|---|---|
|
|
29
|
-
| `session-
|
|
38
|
+
| `opencode-session-record.js` | Read/write title and metadata on the OpenCode session record; migrate the side file without treating a failed load as empty or dropping other sessions |
|
|
39
|
+
| `session-metadata-store.js` | Side-file cache and migration source: `{ [sessionID]: metadata }`, RFC 7386 merge patch, full RMW serialization, refuse-failed-read refuses write/read-as-empty. When a record reader/writer is wired, commits the merged object onto the OpenCode session first |
|
|
30
40
|
| `session-projection.js` | Pure Host authority projection (metadata deep-merge + archive → `time.archived`) |
|
|
31
41
|
| `session-archive.js` | Domain archive/unarchive: validate upstream session + directory, persist, project, best-effort index/broadcast |
|
|
32
42
|
| `system-session.js` | Isolation patches for system session creators: `buildScheduledTaskMetadata`, `buildLlmSessionMetadata`, `buildAssistantSessionMetadata`. Title prefixes are labels and never ownership. |
|
|
@@ -59,19 +69,19 @@ decision about authority, conditional writes, and archive protection.
|
|
|
59
69
|
|
|
60
70
|
Wired from `packages/web/server/index.js` + `feature-routes-runtime.js`:
|
|
61
71
|
|
|
62
|
-
1. `createSessionMetadataStore({ dataDir
|
|
72
|
+
1. `createHttpSessionRecordClient` + `createSessionMetadataStore({ dataDir, recordReader, recordWriter })` + initial `load()`, then `startSideStoreMigration`
|
|
63
73
|
2. proxy `getStoredSessionMetadata` / `getStoredSessionMetadataSync` → list/get + direct SSE overlay
|
|
64
74
|
3. global hub `projectOutboundSessionPayload` → live + replay session lifecycle authority
|
|
65
75
|
4. session-index sync `projectSessions` + event-ingest `projectSession` / `onSessionDeleted`
|
|
66
76
|
5. `createSessionArchiveService` after session index exists
|
|
67
77
|
6. session-goal `readSessionMetadata` / `mutateSessionMetadata` / `persistSessionGoal`
|
|
68
78
|
7. `registerSessionMetadataRoutes` (metadata + archive)
|
|
69
|
-
8. assistants `archiveSessionHost` → shared Host archive
|
|
79
|
+
8. assistants `archiveSessionHost` → shared Host archive, which write-throughs `metadata` onto the OpenCode session record without clearing `title`
|
|
70
80
|
9. system session creators persist isolation through `persistSessionMetadata`
|
|
71
81
|
(`setSessionMetadata` + `openchamber:session-metadata` broadcast) and then
|
|
72
82
|
`onSystemSessionPersisted` (index upsert that removes the row). Wired from
|
|
73
83
|
`index.js` into scheduled-task runtime, LLM attachment generate, and
|
|
74
|
-
Assistant binding create.
|
|
84
|
+
Assistant binding create. Create-time `metadata` is merged onto the session record; it is not a second authority.
|
|
75
85
|
|
|
76
86
|
Capability is open (`supported: true`). Manual UI metadata writes, session-goal
|
|
77
87
|
routes, and scheduled-task goal create all persist `openchamber.goal` through
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export function mergeOwnedMetadata(
|
|
2
|
+
openCodeMetadata: unknown,
|
|
3
|
+
sideMetadata: unknown,
|
|
4
|
+
): Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export function writeSessionRecordViaClient(
|
|
7
|
+
client: { session?: { update?: (input: unknown, requestOptions?: unknown) => Promise<unknown> } } | null | undefined,
|
|
8
|
+
input: {
|
|
9
|
+
sessionID: string;
|
|
10
|
+
metadata?: Record<string, unknown>;
|
|
11
|
+
title?: string;
|
|
12
|
+
requestOptions?: object;
|
|
13
|
+
},
|
|
14
|
+
): Promise<void>;
|
|
15
|
+
|
|
16
|
+
export function readSessionRecordViaClient(
|
|
17
|
+
client: { session?: { get?: (input: unknown, requestOptions?: unknown) => Promise<unknown> } } | null | undefined,
|
|
18
|
+
input: { sessionID: string; requestOptions?: object },
|
|
19
|
+
): Promise<Record<string, unknown> | null>;
|
|
20
|
+
|
|
21
|
+
export function createHttpSessionRecordClient(options: {
|
|
22
|
+
buildSessionUrl: (sessionID: string, directory?: string | null) => string;
|
|
23
|
+
getHeaders?: () => Record<string, string>;
|
|
24
|
+
fetchFn?: typeof fetch;
|
|
25
|
+
}): {
|
|
26
|
+
read: (sessionID: string, directory?: string | null) => Promise<Record<string, unknown> | null>;
|
|
27
|
+
write: (input: {
|
|
28
|
+
sessionID: string;
|
|
29
|
+
metadata?: Record<string, unknown>;
|
|
30
|
+
title?: string;
|
|
31
|
+
directory?: string | null;
|
|
32
|
+
}) => Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function migrateLoadedSideStore(input: {
|
|
36
|
+
loadResult: { ok?: boolean; entries?: Record<string, object> | null } | null;
|
|
37
|
+
readSession: (sessionID: string) => Promise<object | null>;
|
|
38
|
+
writeMetadata: (sessionID: string, metadata: object, record: object) => Promise<void>;
|
|
39
|
+
}): Promise<{
|
|
40
|
+
status: 'unavailable' | 'partial' | 'complete';
|
|
41
|
+
migrated: string[];
|
|
42
|
+
failed: Array<{ id: string; error: string }>;
|
|
43
|
+
absent: string[];
|
|
44
|
+
}>;
|
|
45
|
+
|
|
46
|
+
export function retainOpenCodeSessions(
|
|
47
|
+
openCodeSessions: unknown,
|
|
48
|
+
sideById: Record<string, object> | null | undefined,
|
|
49
|
+
): unknown[];
|
|
50
|
+
|
|
51
|
+
export function startSideStoreMigration(options: {
|
|
52
|
+
load: () => Promise<{ ok?: boolean; entries?: Record<string, object> }>;
|
|
53
|
+
readSession: (sessionID: string) => Promise<object | null>;
|
|
54
|
+
writeMetadata: (sessionID: string, metadata: object, record: object) => Promise<void>;
|
|
55
|
+
setTimer?: typeof setTimeout;
|
|
56
|
+
clearTimer?: typeof clearTimeout;
|
|
57
|
+
}): () => void;
|