@openchambery/web 2.0.0-beta.1 → 2.0.0-beta.4
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 +15 -11
- package/bin/cli-opencode.test.js +4 -4
- package/bin/cli.js +1 -1
- package/bin/lib/cli-startup.js +1 -1
- package/bin/lib/cli-startup.test.js +18 -4
- package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-Q1SmFRx2.js} +1 -1
- package/dist/assets/AssistantView-CwXkiCAd.js +3 -0
- package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-BW92schO.js} +1 -1
- package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-BZmwl-zE.js} +1 -1
- package/dist/assets/{Icon-DD-BuMfZ.js → Icon-AQPvxMiU.js} +1 -1
- package/dist/assets/{MarkdownRenderer-bc_p1H7l.js → MarkdownRenderer-Bv05iDZZ.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-C81xqYib.js} +3 -3
- package/dist/assets/MarkstreamRendererImpl-CXUCQuKJ.js +3 -0
- package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-BMgIu7zc.js} +1 -1
- package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-BOXGNHmC.js} +2 -2
- package/dist/assets/MobileSurface-xuc_z0_G.js +2 -0
- package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-B5At2Z95.js} +1 -1
- package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-bsPLgz4r.js} +1 -1
- package/dist/assets/OverlayScrollbar-4wp4migh.js +2 -0
- package/dist/assets/SettingsView-CJ6tcH5_.js +25 -0
- package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CMtX2bMl.js} +1 -1
- package/dist/assets/{TerminalView-Dh9MWCpS.js → TerminalView-Dvr-ysTS.js} +1 -1
- package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-uH1ZZDSS.js} +2 -2
- package/dist/assets/appThemeRegistry-DK7q2juD.js +1 -0
- package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
- package/dist/assets/{button-D-rATN7w.js → button-CcNJUxUd.js} +1 -1
- package/dist/assets/collapsible-ZmZ6rLWs.js +3 -0
- package/dist/assets/context-menu-B2LXJgXL.js +1 -0
- package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-C6HrP0jq.js} +1 -1
- package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DdVBp45U.js} +1 -1
- package/dist/assets/{diagram-DQf7Y_uR.js → diagram-CxTs9hCQ.js} +1 -1
- package/dist/assets/{dialog-bfbvR4VH.js → dialog-KMVL9QKF.js} +1 -1
- package/dist/assets/es-CN3E_zXj.js +15 -0
- package/dist/assets/{fileMentionTouchSelection-BvrNCroG.js → fileMentionTouchSelection-7RIwlpkB.js} +1 -1
- package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-CjdBRy8y.js} +1 -1
- package/dist/assets/fr-9zZvQZD2.js +13 -0
- package/dist/assets/gitApi-yuWg9BN4.js +10 -0
- package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-9vQBq3zp.js} +1 -1
- package/dist/assets/{input-8r6F_Fza.js → input-CrIvk3HZ.js} +1 -1
- package/dist/assets/insertionBoundaries-BiUCSkx-.js +1 -0
- package/dist/assets/ja-Dnjk_82N.js +15 -0
- package/dist/assets/ko-BxCEE99C.js +15 -0
- package/dist/assets/{main-CCE_4y5F.js → main-C9mIBo77.js} +2 -2
- package/dist/assets/main-DiL6w6KF.js +169 -0
- package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
- package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-BmXVY_gc.js} +1 -1
- package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
- package/dist/assets/mcpQueries-C7jknHV8.js +1 -0
- package/dist/assets/miniChat-CwtUaElU.js +2 -0
- package/dist/assets/mobile-DB-6--ws.js +2 -0
- package/dist/assets/multirun-TAsxFI8e.js +4 -0
- package/dist/assets/{persistence-Ce3AnpEr.js → persistence-56kq8Z4V.js} +2 -2
- package/dist/assets/pl-CNNsWnPM.js +15 -0
- package/dist/assets/platform-C13fMVEs.js +6 -0
- package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
- package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-CGj0MNRU.js +2 -0
- package/dist/assets/renderMobileApp-CGMMqbsO.js +28 -0
- package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
- package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-BEANkvOd.js} +1 -1
- package/dist/assets/runtimeEndpointReset-A_ZBUJFS.js +147 -0
- package/dist/assets/sessionChangeSummary-qOLrP9fw.js +2 -0
- package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-34XFmP9C.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-DyEV0Heg.js} +1 -1
- package/dist/assets/src-ubCWXZhR.css +2 -0
- package/dist/assets/store-DLCiZaA6.js +15 -0
- package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-CXaBEoa_.js} +1 -1
- package/dist/assets/textarea-BBOlRI9O.js +1 -0
- package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CPI4fr1a.js} +1 -1
- package/dist/assets/uk-rwQrxeYM.js +15 -0
- package/dist/assets/{url-_z7evzdK.js → url-DRDQXjyX.js} +1 -1
- package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-CiDe42I5.js} +57 -43
- package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-BlizAlOt.js +1 -0
- package/dist/assets/useEffectiveDirectory-C_CouL5v.js +1 -0
- package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-B0oUZAiB.js} +1 -1
- package/dist/assets/useMobileNavigationStore-DfPClaW-.js +414 -0
- package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
- package/dist/assets/useSessionAutoCleanup-DtrfbxAp.js +2 -0
- package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
- package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-Bqn4AiBL.js → useWorkerHighlightedLines-CPNYz6Wf.js} +2 -2
- package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
- package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
- package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
- package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
- package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
- package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
- package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
- package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
- package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
- package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
- package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
- package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
- package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
- package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
- package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
- package/dist/index.html +13 -13
- package/dist/mini-chat.html +13 -13
- package/dist/mobile.html +13 -13
- package/package.json +1 -1
- package/server/index.js +327 -62
- package/server/lib/assistants/DOCUMENTATION.md +14 -9
- package/server/lib/assistants/contracts.js +11 -1
- package/server/lib/assistants/projection-history.test.js +265 -18
- package/server/lib/assistants/routes.js +6 -0
- package/server/lib/assistants/service.js +417 -231
- package/server/lib/assistants/service.test.js +392 -148
- package/server/lib/conversations/DOCUMENTATION.md +1 -1
- package/server/lib/conversations/service.js +1 -0
- package/server/lib/conversations/service.test.js +16 -0
- package/server/lib/conversations/validation.js +11 -1
- package/server/lib/event-stream/DOCUMENTATION.md +3 -1
- package/server/lib/event-stream/directory-ws-bridge.js +13 -2
- package/server/lib/event-stream/global-hub.js +49 -4
- package/server/lib/event-stream/reasoning-projection.js +16 -1
- package/server/lib/event-stream/reasoning-projection.test.js +15 -0
- package/server/lib/event-stream/runtime.js +57 -20
- package/server/lib/event-stream/runtime.test.js +112 -0
- package/server/lib/llm/DOCUMENTATION.md +33 -9
- package/server/lib/llm/catalog.js +1 -1
- package/server/lib/llm/completions.js +5 -1
- package/server/lib/llm/completions.test.js +3 -3
- package/server/lib/llm/generate.js +237 -145
- package/server/lib/llm/generate.test.js +201 -46
- package/server/lib/llm/routes.js +2 -0
- package/server/lib/message-queue/runtime.js +15 -2
- package/server/lib/notifications/runtime.js +23 -5
- package/server/lib/notifications/runtime.test.js +11 -0
- package/server/lib/opencode/DOCUMENTATION.md +85 -17
- package/server/lib/opencode/agent-document.d.ts +28 -0
- package/server/lib/opencode/agent-document.js +280 -0
- package/server/lib/opencode/agent-document.test.js +137 -0
- package/server/lib/opencode/agents.js +96 -0
- package/server/lib/opencode/auth-state-runtime.js +5 -0
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/config-entity-routes.js +42 -99
- package/server/lib/opencode/config-entity-routes.test.js +107 -0
- package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
- package/server/lib/opencode/ensure-cli.js +1 -2
- package/server/lib/opencode/env-runtime.js +40 -26
- package/server/lib/opencode/env-runtime.test.js +10 -17
- package/server/lib/opencode/feature-routes-runtime.js +43 -4
- package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
- package/server/lib/opencode/http-server-connections.js +98 -0
- package/server/lib/opencode/http-server-connections.test.js +321 -0
- package/server/lib/opencode/lifecycle.js +349 -39
- package/server/lib/opencode/lifecycle.test.js +322 -0
- package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
- package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
- package/server/lib/opencode/openchamber-routes.js +7 -1
- package/server/lib/opencode/opencode2-pin.js +4 -4
- package/server/lib/opencode/opencode2-pin.test.js +1 -0
- package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
- package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
- package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
- package/server/lib/opencode/plugin-routes.js +3 -18
- package/server/lib/opencode/plugin-routes.test.js +9 -9
- package/server/lib/opencode/provider-catalog.js +58 -12
- package/server/lib/opencode/provider-catalog.test.js +48 -0
- package/server/lib/opencode/proxy.js +252 -46
- package/server/lib/opencode/proxy.test.js +235 -2
- package/server/lib/opencode/routes.js +296 -62
- package/server/lib/opencode/routes.test.js +278 -12
- package/server/lib/opencode/runtime-contract.js +401 -0
- package/server/lib/opencode/runtime-contract.test.js +214 -0
- package/server/lib/opencode/server-opencode-fetch.js +242 -0
- package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
- package/server/lib/opencode/server-utils-runtime.js +33 -0
- package/server/lib/opencode/shared-service.integration.test.js +66 -0
- package/server/lib/opencode/shared-service.js +147 -0
- package/server/lib/opencode/shared-service.test.js +93 -0
- package/server/lib/opencode/shared.js +3 -3
- package/server/lib/opencode/shutdown-runtime.js +15 -6
- package/server/lib/opencode/shutdown-runtime.test.js +49 -1
- package/server/lib/opencode/skill-routes.js +20 -33
- package/server/lib/opencode/skill-routes.test.js +35 -2
- package/server/lib/opencode/skills.test.js +15 -0
- package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
- package/server/lib/opencode/static-routes-runtime.js +3 -1
- package/server/lib/opencode/static-routes-runtime.test.js +33 -0
- package/server/lib/opencode/v2-client.js +14 -3
- package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
- package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
- package/server/lib/permission-auto-accept/runtime.js +14 -7
- package/server/lib/permission-auto-accept/runtime.test.js +27 -12
- package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -0
- package/server/lib/scheduled-tasks/runtime.js +52 -4
- package/server/lib/scheduled-tasks/runtime.test.js +80 -1
- package/server/lib/session-goal/DOCUMENTATION.md +113 -13
- package/server/lib/session-goal/routes.js +9 -0
- package/server/lib/session-goal/runtime.js +1579 -117
- package/server/lib/session-goal/runtime.test.js +1453 -84
- package/server/lib/session-index/DOCUMENTATION.md +18 -15
- package/server/lib/session-index/event-ingest.js +89 -16
- package/server/lib/session-index/event-ingest.test.js +73 -0
- package/server/lib/session-index/host-authority-snapshot.js +92 -0
- package/server/lib/session-index/routes.host-archive.test.js +201 -0
- package/server/lib/session-index/routes.js +63 -10
- package/server/lib/session-index/routes.test.js +8 -8
- package/server/lib/session-index/service.js +7 -1
- package/server/lib/session-index/sync-runtime.js +161 -34
- package/server/lib/session-index/sync-runtime.test.js +155 -1
- package/server/lib/session-metadata/DOCUMENTATION.md +61 -16
- package/server/lib/session-metadata/routes.js +108 -9
- package/server/lib/session-metadata/routes.test.js +47 -2
- package/server/lib/session-metadata/session-archive.d.ts +73 -0
- package/server/lib/session-metadata/session-archive.js +654 -0
- package/server/lib/session-metadata/session-archive.test.js +359 -0
- package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
- package/server/lib/session-metadata/session-metadata-store.js +212 -72
- package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
- package/server/lib/session-metadata/session-projection.d.ts +41 -0
- package/server/lib/session-metadata/session-projection.js +255 -0
- package/server/lib/session-metadata/session-projection.test.js +64 -0
- package/server/lib/session-metadata/system-session.js +55 -0
- package/server/lib/session-metadata/system-session.test.js +55 -0
- package/server/lib/session-title/DOCUMENTATION.md +40 -7
- package/server/lib/session-title/native-runtime.test.js +167 -0
- package/server/lib/session-title/runtime.js +67 -42
- package/server/lib/session-title/session-access.js +46 -0
- package/server/lib/session-title/session-access.test.js +91 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
- package/server/lib/session-turn-pages/routes.js +17 -102
- package/server/lib/session-turn-pages/routes.test.js +281 -0
- package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
- package/server/lib/session-turn-pages/session-message-projection.js +281 -0
- package/server/lib/small-model/DOCUMENTATION.md +101 -147
- package/server/lib/small-model/catalog.js +31 -51
- package/server/lib/small-model/catalog.test.js +65 -10
- package/server/lib/small-model/custom-api.js +68 -0
- package/server/lib/small-model/custom-api.test.js +64 -0
- package/server/lib/small-model/index.js +124 -240
- package/server/lib/small-model/resolve.js +35 -120
- package/server/lib/small-model/resolve.test.js +20 -110
- package/server/lib/small-model/routes.js +1 -3
- package/server/lib/small-model/summary-settings.test.js +157 -253
- package/server/opencode-proxy.test.js +2 -0
- package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
- package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
- package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
- package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
- package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
- package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
- package/dist/assets/collapsible-BXRd5_Hr.js +0 -1
- package/dist/assets/context-menu-jGtH6fR9.js +0 -1
- package/dist/assets/es-c52LTHHe.js +0 -15
- package/dist/assets/fr-PisFbUE8.js +0 -13
- package/dist/assets/gitApi-ButwzQ0N.js +0 -9
- package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
- package/dist/assets/ja-C8Ikun3Z.js +0 -15
- package/dist/assets/ko-ClFys5o2.js +0 -15
- package/dist/assets/main-Dn9rpsgl.js +0 -169
- package/dist/assets/markdown-worker-CptoICmq.js +0 -2
- package/dist/assets/miniChat-BpjxVqgg.js +0 -2
- package/dist/assets/mobile-nMJCaNq1.js +0 -2
- package/dist/assets/multirun-BvSG94mW.js +0 -4
- package/dist/assets/pl-CnaKWN5F.js +0 -15
- package/dist/assets/platform-CWTIp02q.js +0 -4
- package/dist/assets/projectMeta-DOe49dM_.js +0 -3
- package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
- package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
- package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
- package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
- package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
- package/dist/assets/src-BzX5UI-c.css +0 -2
- package/dist/assets/store-DJczmczv.js +0 -15
- package/dist/assets/textarea-3XR8sUfy.js +0 -1
- package/dist/assets/uk-CaX0iqkB.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
- package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
- package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
- package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
- package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
- package/dist/assets/zh-CN-20MYEjha.js +0 -15
- package/dist/assets/zh-TW-DiN04qTw.js +0 -15
- package/server/lib/small-model/call.js +0 -427
- package/server/lib/small-model/call.test.js +0 -382
- package/server/lib/small-model/opencode-session.js +0 -183
- package/server/lib/small-model/opencode-session.test.js +0 -147
|
@@ -2,7 +2,10 @@ import { spawn, spawnSync } from 'node:child_process';
|
|
|
2
2
|
import net from 'node:net';
|
|
3
3
|
import { registerManagedProcess, unregisterManagedProcess, reapOrphanedProcesses } from './managed-process-registry.js';
|
|
4
4
|
import { evaluateOpenCodeHealthBody } from './opencode2-pin.js';
|
|
5
|
+
import { readOpenCode2BinaryVersion } from './ensure-cli.js';
|
|
6
|
+
import { createRevokedRuntimeContract, evaluateRuntimeContract } from './runtime-contract.js';
|
|
5
7
|
import { OPENCODE_V1_MIGRATION_PATH, fetchV1MigrationGate } from './v1-migration-gate.js';
|
|
8
|
+
import { isProcessAlive } from './shared-service.js';
|
|
6
9
|
|
|
7
10
|
const parsePositiveInt = (value, fallback) => {
|
|
8
11
|
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
@@ -20,6 +23,10 @@ const HEALTH_CHECK_RESULT_CACHE_MS = parsePositiveInt(process.env.OPENCHAMBER_OP
|
|
|
20
23
|
const OPENCODE_HEALTH_PATH = '/api/info';
|
|
21
24
|
const OPENCODE_HEALTH_FALLBACK_PATH = '/global/health';
|
|
22
25
|
const DEFAULT_OPENCODE_SERVE_PORT = 4096;
|
|
26
|
+
// Polite SIGTERM window before SIGKILL for managed children (restart/OAuth/reload
|
|
27
|
+
// need enough time for clean flush; do not shorten globally for Quit latency).
|
|
28
|
+
const OPENCODE_CHILD_SIGTERM_GRACE_MS = 2500;
|
|
29
|
+
const OPENCODE_CHILD_SIGKILL_WAIT_MS = 1000;
|
|
23
30
|
// Last-used directory plus recently opened projects — deeper tails are unlikely
|
|
24
31
|
// to be the user's first click and just add background work.
|
|
25
32
|
const WARMUP_DIRECTORY_LIMIT = 4;
|
|
@@ -51,23 +58,50 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
51
58
|
managedCapabilitiesRuntime = null,
|
|
52
59
|
getActiveSessionCount = () => 0,
|
|
53
60
|
getWarmupDirectories = async () => [],
|
|
61
|
+
// Official background service (shared-service.js). null keeps the private
|
|
62
|
+
// managed `opencode serve` path.
|
|
63
|
+
sharedService = null,
|
|
64
|
+
adoptOpenCodeServerPassword = null,
|
|
54
65
|
} = deps;
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
// Shared services outlive this runtime. Recovery re-ensures them; only an
|
|
68
|
+
// explicit binary upgrade replaces them through the official service CLI.
|
|
69
|
+
let sharedServiceAttached = false;
|
|
70
|
+
let sharedServicePid = null;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Force-signal only an owned managed OpenCode pid / process group.
|
|
74
|
+
* Never lsof-kill by port: that can murder clients still connected to the port
|
|
75
|
+
* or a newly bound external/shared `opencode serve` that we do not own.
|
|
76
|
+
* `port` is accepted for call-site compatibility and diagnostics only.
|
|
77
|
+
*/
|
|
78
|
+
const killProcessOnPort = (port, ownedPid = null) => {
|
|
79
|
+
const pid = Number(ownedPid);
|
|
80
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (pid === process.pid) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (process.platform === 'win32') {
|
|
88
|
+
try {
|
|
89
|
+
spawnSync('taskkill', ['/pid', String(pid), '/f', '/t'], {
|
|
90
|
+
stdio: 'ignore',
|
|
91
|
+
timeout: 5000,
|
|
92
|
+
windowsHide: true,
|
|
93
|
+
});
|
|
94
|
+
} catch {
|
|
70
95
|
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
process.kill(-pid, 'SIGKILL');
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
process.kill(pid, 'SIGKILL');
|
|
71
105
|
} catch {
|
|
72
106
|
}
|
|
73
107
|
};
|
|
@@ -216,13 +250,13 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
216
250
|
|
|
217
251
|
signalProcessTree('SIGTERM');
|
|
218
252
|
|
|
219
|
-
if (await waitForChildProcessClose(child,
|
|
253
|
+
if (await waitForChildProcessClose(child, OPENCODE_CHILD_SIGTERM_GRACE_MS)) {
|
|
220
254
|
return;
|
|
221
255
|
}
|
|
222
256
|
|
|
223
257
|
signalProcessTree('SIGKILL');
|
|
224
258
|
|
|
225
|
-
await waitForChildProcessClose(child,
|
|
259
|
+
await waitForChildProcessClose(child, OPENCODE_CHILD_SIGKILL_WAIT_MS);
|
|
226
260
|
};
|
|
227
261
|
|
|
228
262
|
const closeManagedOpenCodeChild = async (child) => {
|
|
@@ -447,14 +481,18 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
447
481
|
// v2 readiness lives at /api/info (ServerInfo.version); /global/health remains
|
|
448
482
|
// a probe fallback for older sidecars. Both require Basic auth from
|
|
449
483
|
// getOpenCodeAuthHeaders(). Version admission rejects 1.x / missing / noise.
|
|
450
|
-
|
|
484
|
+
// Health ok is reachability only — execution semantics live on runtimeContract.
|
|
485
|
+
const fetchOpenCodeHealthResult = async (urlForPath, signal, options = {}) => {
|
|
451
486
|
const headerCandidates = [
|
|
452
|
-
{ Accept: 'application/json', ...getOpenCodeAuthHeaders() },
|
|
487
|
+
{ Accept: 'application/json', ...getOpenCodeAuthHeaders(), authenticated: true },
|
|
453
488
|
];
|
|
454
489
|
if (options.allowUnauthenticated) {
|
|
455
|
-
headerCandidates.push({ Accept: 'application/json' });
|
|
490
|
+
headerCandidates.push({ Accept: 'application/json', authenticated: false });
|
|
456
491
|
}
|
|
457
|
-
|
|
492
|
+
let sawUnauthorized = false;
|
|
493
|
+
let lastGate = null;
|
|
494
|
+
for (const headerCandidate of headerCandidates) {
|
|
495
|
+
const { authenticated, ...headers } = headerCandidate;
|
|
458
496
|
for (const healthPath of [OPENCODE_HEALTH_PATH, OPENCODE_HEALTH_FALLBACK_PATH]) {
|
|
459
497
|
try {
|
|
460
498
|
const response = await fetch(urlForPath(healthPath), {
|
|
@@ -462,27 +500,115 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
462
500
|
headers,
|
|
463
501
|
signal,
|
|
464
502
|
});
|
|
503
|
+
if (response.status === 401 || response.status === 403) {
|
|
504
|
+
sawUnauthorized = true;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
465
507
|
if (!response.ok) continue;
|
|
466
508
|
const body = await response.json().catch(() => null);
|
|
467
509
|
const gate = evaluateOpenCodeHealthBody(body);
|
|
468
|
-
|
|
510
|
+
lastGate = gate;
|
|
511
|
+
if (gate.ok) {
|
|
512
|
+
return {
|
|
513
|
+
ok: true,
|
|
514
|
+
version: gate.version,
|
|
515
|
+
authenticated: authenticated !== false,
|
|
516
|
+
healthOk: true,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
469
519
|
} catch {
|
|
470
520
|
}
|
|
471
521
|
}
|
|
472
522
|
}
|
|
473
|
-
|
|
523
|
+
if (sawUnauthorized && !lastGate?.ok) {
|
|
524
|
+
return { ok: false, version: null, authenticated: false, healthOk: false, reason: 'auth-failed' };
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
ok: false,
|
|
528
|
+
version: lastGate?.version ?? null,
|
|
529
|
+
authenticated: sawUnauthorized ? false : null,
|
|
530
|
+
healthOk: false,
|
|
531
|
+
reason: lastGate?.reason || 'unhealthy',
|
|
532
|
+
};
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const fetchOpenCodeHealthOk = async (urlForPath, signal, options = {}) => {
|
|
536
|
+
const result = await fetchOpenCodeHealthResult(urlForPath, signal, options);
|
|
537
|
+
return result.ok === true;
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Invalidate any prior execution permit. Start / restart / target changes must
|
|
542
|
+
* bump generation so in-flight async health probes cannot publish for a newer
|
|
543
|
+
* instance, and Host/proxy gates stop holding a stale `executionAllowed: true`.
|
|
544
|
+
* @param {string} [reason]
|
|
545
|
+
*/
|
|
546
|
+
const revokeRuntimeExecutionPermit = (reason = 'instance-revoked') => {
|
|
547
|
+
const nextGeneration = (Number(state.runtimeContractGeneration) || 0) + 1;
|
|
548
|
+
state.runtimeContractGeneration = nextGeneration;
|
|
549
|
+
state.openCodeServeVersion = null;
|
|
550
|
+
state.openCodeCliVersion = null;
|
|
551
|
+
state.runtimeContract = createRevokedRuntimeContract(reason, {
|
|
552
|
+
instanceGeneration: nextGeneration,
|
|
553
|
+
});
|
|
554
|
+
return nextGeneration;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Publish contract only for the generation that started the probe.
|
|
559
|
+
* @param {object | null | undefined} probe
|
|
560
|
+
* @param {object | null | undefined} migrationGate
|
|
561
|
+
* @param {number | null | undefined} expectedGeneration
|
|
562
|
+
*/
|
|
563
|
+
const refreshRuntimeContractFromProbe = (
|
|
564
|
+
probe,
|
|
565
|
+
migrationGate = state.v1Migration,
|
|
566
|
+
expectedGeneration = state.runtimeContractGeneration,
|
|
567
|
+
) => {
|
|
568
|
+
const currentGeneration = Number(state.runtimeContractGeneration) || 0;
|
|
569
|
+
if (
|
|
570
|
+
expectedGeneration != null
|
|
571
|
+
&& Number(expectedGeneration) !== currentGeneration
|
|
572
|
+
) {
|
|
573
|
+
// Stale async health / migration result for a superseded instance.
|
|
574
|
+
return state.runtimeContract;
|
|
575
|
+
}
|
|
576
|
+
const binaryPath = typeof process.env.OPENCODE_BINARY === 'string'
|
|
577
|
+
? process.env.OPENCODE_BINARY.trim()
|
|
578
|
+
: '';
|
|
579
|
+
const cliVersion = binaryPath ? (readOpenCode2BinaryVersion(binaryPath) || null) : null;
|
|
580
|
+
const contract = evaluateRuntimeContract({
|
|
581
|
+
serveVersion: probe?.version ?? null,
|
|
582
|
+
cliVersion,
|
|
583
|
+
reachable: probe?.ok === true || Boolean(state.openCodePort),
|
|
584
|
+
authenticated: probe?.authenticated ?? null,
|
|
585
|
+
healthOk: probe?.healthOk ?? probe?.ok ?? null,
|
|
586
|
+
migrationAdmitTranscript: migrationGate?.admitTranscript ?? null,
|
|
587
|
+
migrationPhase: migrationGate?.phase ?? null,
|
|
588
|
+
migrationError: migrationGate?.error ?? null,
|
|
589
|
+
});
|
|
590
|
+
contract.instanceGeneration = currentGeneration;
|
|
591
|
+
state.openCodeServeVersion = contract.serveVersion;
|
|
592
|
+
state.openCodeCliVersion = contract.cliVersion;
|
|
593
|
+
state.runtimeContract = contract;
|
|
594
|
+
return contract;
|
|
474
595
|
};
|
|
475
596
|
|
|
476
597
|
const isOpenCodeProcessHealthy = async () => {
|
|
477
|
-
if (!state.openCodeProcess || !state.openCodePort) {
|
|
598
|
+
if ((!state.openCodeProcess && !sharedServiceAttached) || !state.openCodePort) {
|
|
478
599
|
return false;
|
|
479
600
|
}
|
|
480
601
|
|
|
602
|
+
const generationAtStart = Number(state.runtimeContractGeneration) || 0;
|
|
481
603
|
try {
|
|
482
|
-
|
|
604
|
+
const result = await fetchOpenCodeHealthResult(
|
|
483
605
|
(healthPath) => buildOpenCodeUrl(healthPath, ''),
|
|
484
606
|
AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS)
|
|
485
607
|
);
|
|
608
|
+
if (result.ok) {
|
|
609
|
+
refreshRuntimeContractFromProbe(result, state.v1Migration, generationAtStart);
|
|
610
|
+
}
|
|
611
|
+
return result.ok;
|
|
486
612
|
} catch {
|
|
487
613
|
return false;
|
|
488
614
|
}
|
|
@@ -493,17 +619,21 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
493
619
|
return false;
|
|
494
620
|
}
|
|
495
621
|
|
|
622
|
+
const generationAtStart = Number(state.runtimeContractGeneration) || 0;
|
|
496
623
|
try {
|
|
497
624
|
const controller = new AbortController();
|
|
498
625
|
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
499
626
|
const base = origin ?? `http://127.0.0.1:${port}`;
|
|
500
|
-
const
|
|
627
|
+
const result = await fetchOpenCodeHealthResult(
|
|
501
628
|
(healthPath) => `${base}${healthPath}`,
|
|
502
629
|
controller.signal,
|
|
503
630
|
{ allowUnauthenticated: true },
|
|
504
631
|
);
|
|
505
632
|
clearTimeout(timeout);
|
|
506
|
-
|
|
633
|
+
if (result.ok) {
|
|
634
|
+
refreshRuntimeContractFromProbe(result, state.v1Migration, generationAtStart);
|
|
635
|
+
}
|
|
636
|
+
return result.ok;
|
|
507
637
|
} catch {
|
|
508
638
|
return false;
|
|
509
639
|
}
|
|
@@ -525,11 +655,114 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
525
655
|
throw new Error('Timed out waiting for OpenCode port');
|
|
526
656
|
};
|
|
527
657
|
|
|
658
|
+
/**
|
|
659
|
+
* Ensure the official service is running and point this runtime at its
|
|
660
|
+
* registration (origin, port, password). Throws when the CLI cannot start it
|
|
661
|
+
* or the registration is missing/malformed; the caller decides rollback.
|
|
662
|
+
*/
|
|
663
|
+
const connectSharedOpenCodeService = async ({ binaryPath } = {}) => {
|
|
664
|
+
if (!binaryPath) {
|
|
665
|
+
await applyOpencodeBinaryFromSettings({ strict: true });
|
|
666
|
+
if (typeof ensurePinnedOpenCode2CliEnv === 'function') {
|
|
667
|
+
await ensurePinnedOpenCode2CliEnv();
|
|
668
|
+
}
|
|
669
|
+
ensureOpencodeCliEnv();
|
|
670
|
+
}
|
|
671
|
+
let binary = binaryPath || (process.env.OPENCODE_BINARY || 'opencode').trim() || 'opencode';
|
|
672
|
+
let args = [];
|
|
673
|
+
if (process.platform === 'win32') {
|
|
674
|
+
const launchSpec = resolveManagedOpenCodeLaunchSpec(binary);
|
|
675
|
+
if (launchSpec?.binary) {
|
|
676
|
+
binary = launchSpec.binary;
|
|
677
|
+
args = Array.isArray(launchSpec.args) ? launchSpec.args : [];
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const envPath = typeof buildManagedOpenCodePath === 'function'
|
|
681
|
+
? buildManagedOpenCodePath()
|
|
682
|
+
: typeof buildAugmentedPath === 'function'
|
|
683
|
+
? buildAugmentedPath()
|
|
684
|
+
: process.env.PATH;
|
|
685
|
+
const shellEnv = typeof getManagedOpenCodeShellEnvSnapshot === 'function'
|
|
686
|
+
? getManagedOpenCodeShellEnvSnapshot() || {}
|
|
687
|
+
: {};
|
|
688
|
+
await sharedService.start({
|
|
689
|
+
binary,
|
|
690
|
+
args,
|
|
691
|
+
replace: Boolean(binaryPath),
|
|
692
|
+
env: sharedService.buildStartEnv({ ...shellEnv, ...process.env, PATH: envPath }),
|
|
693
|
+
});
|
|
694
|
+
const registration = await sharedService.readRegistration();
|
|
695
|
+
if (!registration) {
|
|
696
|
+
throw new Error(`OpenCode service started but ${sharedService.registrationFile} is missing or invalid`);
|
|
697
|
+
}
|
|
698
|
+
adoptOpenCodeServerPassword?.(registration.password, 'shared-service');
|
|
699
|
+
state.openCodeBaseUrl = registration.origin;
|
|
700
|
+
setOpenCodePort(registration.port);
|
|
701
|
+
sharedServicePid = registration.pid;
|
|
702
|
+
return registration;
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Attach to the shared official service instead of spawning a private serve.
|
|
707
|
+
* Failure leaves integration unavailable and retryable, never silently
|
|
708
|
+
* starting a private server with different lifecycle semantics.
|
|
709
|
+
*/
|
|
710
|
+
const attachSharedOpenCodeService = async () => {
|
|
711
|
+
if (!sharedService) return false;
|
|
712
|
+
revokeRuntimeExecutionPermit('shared-service-attach');
|
|
713
|
+
state.isOpenCodeReady = false;
|
|
714
|
+
state.openCodeNotReadySince = Date.now();
|
|
715
|
+
try {
|
|
716
|
+
const registration = await connectSharedOpenCodeService();
|
|
717
|
+
if (!(await probeExternalOpenCode(registration.port, registration.origin))) {
|
|
718
|
+
throw new Error(`OpenCode service at ${registration.origin} did not pass the health/version check`);
|
|
719
|
+
}
|
|
720
|
+
if (state.openCodeProcess && !state.isExternalOpenCode) {
|
|
721
|
+
await state.openCodeProcess.close();
|
|
722
|
+
state.openCodeProcess = null;
|
|
723
|
+
}
|
|
724
|
+
sharedServiceAttached = true;
|
|
725
|
+
state.isExternalOpenCode = true;
|
|
726
|
+
state.lastOpenCodeError = null;
|
|
727
|
+
state.v1Migration = null;
|
|
728
|
+
state.managedCapabilityIdentity = null;
|
|
729
|
+
managedCapabilitiesRuntime?.setCapabilityIdentity(null);
|
|
730
|
+
syncToHmrState();
|
|
731
|
+
console.log(`[OpenCode] Attached to shared OpenCode service at ${registration.origin} (v${registration.version ?? 'unknown'})`);
|
|
732
|
+
return true;
|
|
733
|
+
} catch (error) {
|
|
734
|
+
sharedServiceAttached = false;
|
|
735
|
+
sharedServicePid = null;
|
|
736
|
+
state.openCodeBaseUrl = null;
|
|
737
|
+
state.openCodePort = null;
|
|
738
|
+
adoptOpenCodeServerPassword?.(null, null);
|
|
739
|
+
syncToHmrState();
|
|
740
|
+
throw error;
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
// `location.reload` rebuilds every loaded location in place: running
|
|
745
|
+
// sessions continue at the next step boundary and clients get
|
|
746
|
+
// `location.shutdown` to refetch location services.
|
|
747
|
+
const reloadSharedOpenCodeLocations = async () => {
|
|
748
|
+
const response = await fetch(buildOpenCodeUrl('/api/location/reload', ''), {
|
|
749
|
+
method: 'POST',
|
|
750
|
+
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
|
|
751
|
+
signal: AbortSignal.timeout(120_000),
|
|
752
|
+
});
|
|
753
|
+
if (!response.ok) {
|
|
754
|
+
const body = await response.text().catch(() => '');
|
|
755
|
+
throw new Error(`OpenCode location reload failed (HTTP ${response.status})${body ? `: ${body.slice(0, 300)}` : ''}`);
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
|
|
528
759
|
const START_OPEN_CODE_MAX_ATTEMPTS = 2;
|
|
529
760
|
|
|
530
761
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
531
762
|
|
|
532
763
|
const startOpenCodeOnce = async () => {
|
|
764
|
+
// New managed instance — drop any prior permit before spawn/probes.
|
|
765
|
+
const generationAtStart = revokeRuntimeExecutionPermit('managed-start');
|
|
533
766
|
const requestedPort = env.ENV_CONFIGURED_OPENCODE_PORT;
|
|
534
767
|
const defaultPortFree = requestedPort
|
|
535
768
|
? false
|
|
@@ -589,8 +822,15 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
589
822
|
setOpenCodePort(port);
|
|
590
823
|
setDetectedOpenCodeApiPrefix(prefix);
|
|
591
824
|
|
|
825
|
+
// Evaluate contract for THIS generation from live health + migration.
|
|
826
|
+
const healthResult = await fetchOpenCodeHealthResult(
|
|
827
|
+
(healthPath) => buildOpenCodeUrl(healthPath, ''),
|
|
828
|
+
AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS),
|
|
829
|
+
);
|
|
592
830
|
const gate = await probeV1MigrationGate(AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS));
|
|
831
|
+
refreshRuntimeContractFromProbe(healthResult, gate, generationAtStart);
|
|
593
832
|
if (applyV1MigrationGate(gate)) {
|
|
833
|
+
refreshRuntimeContractFromProbe(healthResult, gate, generationAtStart);
|
|
594
834
|
state.lastOpenCodeError = null;
|
|
595
835
|
}
|
|
596
836
|
|
|
@@ -606,6 +846,14 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
606
846
|
const message = error instanceof Error ? error.message : String(error);
|
|
607
847
|
state.lastOpenCodeError = message;
|
|
608
848
|
state.openCodePort = null;
|
|
849
|
+
// Keep revoked/pending contract — do not resurrect the prior permit.
|
|
850
|
+
if ((Number(state.runtimeContractGeneration) || 0) === generationAtStart) {
|
|
851
|
+
refreshRuntimeContractFromProbe(
|
|
852
|
+
{ ok: false, version: null, authenticated: null, healthOk: false },
|
|
853
|
+
state.v1Migration,
|
|
854
|
+
generationAtStart,
|
|
855
|
+
);
|
|
856
|
+
}
|
|
609
857
|
syncToHmrState();
|
|
610
858
|
console.error(`Failed to start OpenCode: ${message}`);
|
|
611
859
|
throw error;
|
|
@@ -639,20 +887,35 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
639
887
|
throw lastError;
|
|
640
888
|
};
|
|
641
889
|
|
|
642
|
-
const restartOpenCode = async () => {
|
|
890
|
+
const restartOpenCode = async (options = {}) => {
|
|
643
891
|
if (state.isShuttingDown) return;
|
|
644
892
|
if (state.currentRestartPromise) {
|
|
645
893
|
await state.currentRestartPromise;
|
|
646
|
-
return;
|
|
894
|
+
if (!options.binaryPath) return;
|
|
647
895
|
}
|
|
648
896
|
|
|
649
897
|
state.currentRestartPromise = (async () => {
|
|
650
898
|
state.isRestartingOpenCode = true;
|
|
651
899
|
state.isOpenCodeReady = false;
|
|
652
900
|
state.openCodeNotReadySince = Date.now();
|
|
901
|
+
// Drop prior instance permit before any probe / respawn.
|
|
902
|
+
const restartGeneration = revokeRuntimeExecutionPermit('managed-restart');
|
|
653
903
|
console.log('Restarting OpenCode process...');
|
|
654
904
|
|
|
655
905
|
if (state.isExternalOpenCode) {
|
|
906
|
+
if (sharedServiceAttached) {
|
|
907
|
+
// The service may have been stopped or replaced by another client
|
|
908
|
+
// (new pid, possibly new port/password); recovery only rediscovers it.
|
|
909
|
+
// An explicit upgrade supplies a verified owned-cache binary.
|
|
910
|
+
console.log('Re-ensuring shared OpenCode service...');
|
|
911
|
+
try {
|
|
912
|
+
await connectSharedOpenCodeService(options);
|
|
913
|
+
} catch (error) {
|
|
914
|
+
state.lastOpenCodeError = error instanceof Error ? error.message : String(error);
|
|
915
|
+
syncToHmrState();
|
|
916
|
+
throw error;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
656
919
|
console.log('Re-probing external OpenCode server...');
|
|
657
920
|
const probePort = state.openCodePort || env.ENV_CONFIGURED_OPENCODE_PORT || 4096;
|
|
658
921
|
const probeOrigin = state.openCodeBaseUrl ?? env.ENV_CONFIGURED_OPENCODE_HOST?.origin;
|
|
@@ -660,8 +923,14 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
660
923
|
if (healthy) {
|
|
661
924
|
console.log(`External OpenCode server on port ${probePort} is healthy`);
|
|
662
925
|
setOpenCodePort(probePort);
|
|
926
|
+
const healthResult = await fetchOpenCodeHealthResult(
|
|
927
|
+
(healthPath) => buildOpenCodeUrl(healthPath, ''),
|
|
928
|
+
AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS),
|
|
929
|
+
);
|
|
663
930
|
const gate = await probeV1MigrationGate(AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS));
|
|
931
|
+
refreshRuntimeContractFromProbe(healthResult, gate, restartGeneration);
|
|
664
932
|
if (applyV1MigrationGate(gate)) {
|
|
933
|
+
refreshRuntimeContractFromProbe(healthResult, gate, restartGeneration);
|
|
665
934
|
state.lastOpenCodeError = null;
|
|
666
935
|
}
|
|
667
936
|
syncToHmrState();
|
|
@@ -679,11 +948,13 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
679
948
|
return;
|
|
680
949
|
}
|
|
681
950
|
|
|
682
|
-
const
|
|
951
|
+
const portToRelease = state.openCodePort;
|
|
683
952
|
|
|
684
953
|
if (state.openCodeProcess) {
|
|
685
954
|
console.log('Stopping existing OpenCode process...');
|
|
686
955
|
try {
|
|
956
|
+
// close() owns the full SIGTERM→SIGKILL escalation on this child handle.
|
|
957
|
+
// Do not SIGKILL a pre-captured pid afterward — the OS may have recycled it.
|
|
687
958
|
await state.openCodeProcess.close();
|
|
688
959
|
} catch (error) {
|
|
689
960
|
console.warn('Error closing OpenCode process:', error);
|
|
@@ -692,9 +963,9 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
692
963
|
syncToHmrState();
|
|
693
964
|
}
|
|
694
965
|
|
|
695
|
-
|
|
696
|
-
if (!(await waitForPortRelease(
|
|
697
|
-
console.warn(`Timed out waiting for OpenCode port ${
|
|
966
|
+
// Probe only: never mass-kill whoever holds the port (clients / external serve).
|
|
967
|
+
if (!(await waitForPortRelease(portToRelease, 5000))) {
|
|
968
|
+
console.warn(`Timed out waiting for OpenCode port ${portToRelease} to be released`);
|
|
698
969
|
}
|
|
699
970
|
|
|
700
971
|
if (env.ENV_CONFIGURED_OPENCODE_PORT) {
|
|
@@ -751,28 +1022,42 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
751
1022
|
|
|
752
1023
|
const deadline = Date.now() + timeoutMs;
|
|
753
1024
|
let lastError = null;
|
|
1025
|
+
// Pin generation for this wait loop so a concurrent restart cannot have our
|
|
1026
|
+
// late probe overwrite the newer instance contract.
|
|
1027
|
+
const generationAtStart = Number(state.runtimeContractGeneration) || 0;
|
|
754
1028
|
|
|
755
1029
|
while (Date.now() < deadline) {
|
|
756
1030
|
let timeout = null;
|
|
757
1031
|
try {
|
|
1032
|
+
if ((Number(state.runtimeContractGeneration) || 0) !== generationAtStart) {
|
|
1033
|
+
throw new Error('OpenCode instance changed while waiting for ready');
|
|
1034
|
+
}
|
|
758
1035
|
// One timer covers health + migration for this attempt. Clearing after
|
|
759
1036
|
// health alone let a hung migration outrun HEALTH_CHECK_TIMEOUT_MS and
|
|
760
1037
|
// the outer deadline when the probe never settled.
|
|
761
1038
|
const controller = new AbortController();
|
|
762
1039
|
timeout = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);
|
|
763
|
-
const
|
|
1040
|
+
const healthResult = await fetchOpenCodeHealthResult(
|
|
764
1041
|
(healthPath) => buildOpenCodeUrl(healthPath, ''),
|
|
765
1042
|
controller.signal
|
|
766
1043
|
);
|
|
767
1044
|
|
|
768
|
-
if (!
|
|
769
|
-
|
|
1045
|
+
if (!healthResult.ok) {
|
|
1046
|
+
refreshRuntimeContractFromProbe(healthResult, state.v1Migration, generationAtStart);
|
|
1047
|
+
lastError = new Error(
|
|
1048
|
+
healthResult.reason === 'auth-failed'
|
|
1049
|
+
? 'OpenCode health endpoint rejected authentication'
|
|
1050
|
+
: 'OpenCode health endpoint returned unhealthy response',
|
|
1051
|
+
);
|
|
770
1052
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
771
1053
|
continue;
|
|
772
1054
|
}
|
|
773
1055
|
|
|
774
1056
|
const gate = await probeV1MigrationGate(controller.signal);
|
|
1057
|
+
refreshRuntimeContractFromProbe(healthResult, gate, generationAtStart);
|
|
775
1058
|
if (applyV1MigrationGate(gate)) {
|
|
1059
|
+
// Re-publish contract after admitTranscript flips ready.
|
|
1060
|
+
refreshRuntimeContractFromProbe(healthResult, gate, generationAtStart);
|
|
776
1061
|
state.lastOpenCodeError = null;
|
|
777
1062
|
return;
|
|
778
1063
|
}
|
|
@@ -833,6 +1118,16 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
833
1118
|
const { agentName } = options;
|
|
834
1119
|
|
|
835
1120
|
console.log(`Refreshing OpenCode after ${reason}`);
|
|
1121
|
+
|
|
1122
|
+
if (sharedServiceAttached) {
|
|
1123
|
+
// Shared with other clients: reload in place instead of restarting.
|
|
1124
|
+
await reloadSharedOpenCodeLocations();
|
|
1125
|
+
if (agentName) {
|
|
1126
|
+
await waitForAgentPresence(agentName);
|
|
1127
|
+
}
|
|
1128
|
+
return { reloaded: true, external: true };
|
|
1129
|
+
}
|
|
1130
|
+
|
|
836
1131
|
clearResolvedOpenCodeBinary();
|
|
837
1132
|
await applyOpencodeBinaryFromSettings();
|
|
838
1133
|
|
|
@@ -901,12 +1196,19 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
901
1196
|
state.openCodeBaseUrl = env.ENV_CONFIGURED_OPENCODE_HOST?.origin ?? null;
|
|
902
1197
|
setOpenCodePort(env.ENV_EFFECTIVE_PORT);
|
|
903
1198
|
// External attach is not ready until version + V1 migration admit.
|
|
1199
|
+
revokeRuntimeExecutionPermit('external-attach');
|
|
904
1200
|
state.isOpenCodeReady = false;
|
|
905
1201
|
state.openCodeNotReadySince = Date.now();
|
|
906
1202
|
state.isExternalOpenCode = true;
|
|
907
1203
|
state.lastOpenCodeError = null;
|
|
908
1204
|
state.v1Migration = null;
|
|
909
1205
|
syncToHmrState();
|
|
1206
|
+
} else if (
|
|
1207
|
+
sharedService
|
|
1208
|
+
&& !env.ENV_CONFIGURED_OPENCODE_PORT
|
|
1209
|
+
&& !env.ENV_CONFIGURED_OPENCODE_HOST
|
|
1210
|
+
) {
|
|
1211
|
+
await attachSharedOpenCodeService();
|
|
910
1212
|
} else if (await isOpenCodeProcessHealthy() && (managedCapabilitiesRuntime ? managedCapabilitiesRuntime.hasValidIdentity() : true)) {
|
|
911
1213
|
console.log(`[HMR] Reusing existing OpenCode process on port ${state.openCodePort}`);
|
|
912
1214
|
} else if (state.openCodeProcess && !state.isExternalOpenCode && await isOpenCodeProcessHealthy()) {
|
|
@@ -1119,15 +1421,20 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
1119
1421
|
|
|
1120
1422
|
const runHealthCheckCycle = async (source) => {
|
|
1121
1423
|
// External serve is read-only mount: never restart or spawn a second copy
|
|
1122
|
-
// because the user's instance looks unhealthy from our probe.
|
|
1123
|
-
|
|
1124
|
-
if (
|
|
1424
|
+
// because the user's instance looks unhealthy from our probe. The shared
|
|
1425
|
+
// service is the exception: "restart" only re-runs `service start`.
|
|
1426
|
+
if (state.isExternalOpenCode && !sharedServiceAttached) return;
|
|
1427
|
+
if (!sharedServiceAttached && !state.openCodeProcess) return;
|
|
1428
|
+
if (state.isShuttingDown || state.isRestartingOpenCode) return;
|
|
1125
1429
|
if (healthCheckCyclePromise) return healthCheckCyclePromise;
|
|
1126
1430
|
|
|
1127
1431
|
healthCheckCyclePromise = (async () => {
|
|
1128
1432
|
const healthy = await probeOpenCodeHealth();
|
|
1129
1433
|
if (!healthy) {
|
|
1130
|
-
|
|
1434
|
+
const processAlive = sharedServiceAttached
|
|
1435
|
+
? isProcessAlive(sharedServicePid)
|
|
1436
|
+
: isManagedOpenCodeProcessAlive();
|
|
1437
|
+
if (!processAlive) {
|
|
1131
1438
|
console.log(`[lifecycle] ${source} health check: OpenCode process exited, restarting...`);
|
|
1132
1439
|
consecutiveHealthFailures = 0;
|
|
1133
1440
|
lastHealthProbeResult = null;
|
|
@@ -1185,6 +1492,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
1185
1492
|
|
|
1186
1493
|
return {
|
|
1187
1494
|
killProcessOnPort,
|
|
1495
|
+
isSharedOpenCodeService: () => sharedServiceAttached,
|
|
1188
1496
|
startOpenCode,
|
|
1189
1497
|
restartOpenCode,
|
|
1190
1498
|
waitForOpenCodeReady,
|
|
@@ -1195,5 +1503,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
1195
1503
|
startHealthMonitoring,
|
|
1196
1504
|
triggerHealthCheck,
|
|
1197
1505
|
waitForPortRelease,
|
|
1506
|
+
revokeRuntimeExecutionPermit,
|
|
1507
|
+
refreshRuntimeContractFromProbe,
|
|
1198
1508
|
};
|
|
1199
1509
|
};
|