@vantran-se/openchamber-web 2.0.2 → 2.0.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 +20 -28
- package/bin/cli.js +3 -1
- package/bin/cli.test.js +13 -4
- package/bin/lib/DOCUMENTATION.md +4 -1
- package/bin/lib/commands-serve.js +9 -0
- package/dist/assets/{CodeMirrorCommentWidgets-B82Oye3V.js → CodeMirrorCommentWidgets-DZF8ntaq.js} +1 -1
- package/dist/assets/{DiffView-DJLmWn6Z.js → DiffView-D2p4Ar0H.js} +1 -1
- package/dist/assets/{FilesView-DjksmZRo.js → FilesView-BeTtbsip.js} +1 -1
- package/dist/assets/{GitView-DiUsUIgr.js → GitView-DwQwqDvu.js} +1 -1
- package/dist/assets/{GuestAttachDialog-CYwWYLr3.js → GuestAttachDialog-CoXM0gh_.js} +1 -1
- package/dist/assets/{GuestSurfacePane-CmctyRI4.js → GuestSurfacePane-DIJvkkM-.js} +1 -1
- package/dist/assets/{JsonTreeView-DOHHcbgJ.js → JsonTreeView-Cq_27v0y.js} +1 -1
- package/dist/assets/{LinearIssuesView-DaqnylcV.js → LinearIssuesView-CsSSRMhn.js} +1 -1
- package/dist/assets/{MarkdownImageGallery-HJNvWt7L.js → MarkdownImageGallery-CY0dWTaE.js} +1 -1
- package/dist/assets/{MarkdownRendererImpl-nDZRGg2I.js → MarkdownRendererImpl-ILdXzLrt.js} +1 -1
- package/dist/assets/{OnboardingScreen-C2B5Bsof.js → OnboardingScreen-ClatMQAX.js} +1 -1
- package/dist/assets/{PatchDiff-C4FLBjpt.js → PatchDiff-DOwlrIK9.js} +1 -1
- package/dist/assets/{PierreDiffViewer-C_1S9c9n.js → PierreDiffViewer-CrcGqqD_.js} +1 -1
- package/dist/assets/{PlanView-_eZvb-G5.js → PlanView-BGLsMgQk.js} +1 -1
- package/dist/assets/{PluginPane-sM8fcZIV.js → PluginPane-B39JEUL8.js} +1 -1
- package/dist/assets/{SettingsView-BRfr46hb.js → SettingsView-13B2_G-A.js} +1 -1
- package/dist/assets/{SettingsWindow-CcUDwQHg.js → SettingsWindow-B0BAy-f9.js} +1 -1
- package/dist/assets/{SubmoduleDiffSummary-CrN1ZGwl.js → SubmoduleDiffSummary-fcdDzCSW.js} +1 -1
- package/dist/assets/{ToolOutputDialog-Cij4jI09.js → ToolOutputDialog-B7xaUz2H.js} +1 -1
- package/dist/assets/{ToolPartDiffPreview-BXzwQ-JY.js → ToolPartDiffPreview-CkWCaUmZ.js} +1 -1
- package/dist/assets/{VirtualizedFileDiff-DSWTLJNH.js → VirtualizedFileDiff-DONC4eI2.js} +1 -1
- package/dist/assets/{WalkthroughView-CpUUgY8H.js → WalkthroughView-CXDImOXW.js} +1 -1
- package/dist/assets/{appThemeRegistry-B5tHTkR8.js → appThemeRegistry-Br2TLSlb.js} +1 -1
- package/dist/assets/{commitAndPush-D7D8qV8p.js → commitAndPush-CcI4ol7c.js} +1 -1
- package/dist/assets/{computeVirtualFileMetrics-cq9uQ30i.js → computeVirtualFileMetrics-_-20umCs.js} +1 -1
- package/dist/assets/{de-CUO1-KqF.js → de-BtYnUgII.js} +1 -1
- package/dist/assets/{es-BlbC6sGJ.js → es-crP5qPye.js} +1 -1
- package/dist/assets/{fr-D2nwe_0W.js → fr-CLSRuALk.js} +1 -1
- package/dist/assets/{index-BQOoYIOu.js → index-OKDYjcMF.js} +1 -1
- package/dist/assets/{isDiffPlainText-D0obJ2p8.js → isDiffPlainText-D77M6q1f.js} +1 -1
- package/dist/assets/{isFilePlainText-DB3_rjyW.js → isFilePlainText-C54B4Mns.js} +1 -1
- package/dist/assets/{ja-CA7vnDsZ.js → ja-SZ1-ucLV.js} +1 -1
- package/dist/assets/{ko-Chy6a9l-.js → ko-HthbT_Ve.js} +1 -1
- package/dist/assets/{main-B9JFK3vF.js → main-BJklC7Cl.js} +4 -4
- package/dist/assets/{main-Bhiqnrhd.js → main-CGEvLMd2.js} +2 -2
- package/dist/assets/{markdownCore-tfqOFaBd.js → markdownCore-BzQuETm0.js} +1 -1
- package/dist/assets/{miniChat-B0UOL5zA.js → miniChat-BSxkRQLj.js} +2 -2
- package/dist/assets/{mobile-BJppFIEo.js → mobile-C5KRTNOB.js} +2 -2
- package/dist/assets/{patchFileDiff-Db6KuwCP.js → patchFileDiff-DGw2KryM.js} +1 -1
- package/dist/assets/{pl-CTR5A_1P.js → pl-C8eYfjL1.js} +1 -1
- package/dist/assets/{pt-BR-BE1O1KEx.js → pt-BR-cYOrZdxS.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-Yz5sfoFh.js → renderElectronMiniChatApp-CnkAQV6o.js} +2 -2
- package/dist/assets/{renderMobileApp-QQ-yB8aY.js → renderMobileApp-CMxjwisj.js} +3 -3
- package/dist/assets/{runtimeEndpointReset-DKDdzRzm.js → runtimeEndpointReset-C45jrFq4.js} +3 -3
- package/dist/assets/{scheduledTasksApi-C5sCuNqf.js → scheduledTasksApi-CkK1F9s2.js} +1 -1
- package/dist/assets/{shikiHighlight-xURoo5t_.js → shikiHighlight-4O0ZM14D.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-BZLGuYTU.js → textMateThemeFromAppTheme-yNq7b0DZ.js} +1 -1
- package/dist/assets/{tr-Bh5vK6cW.js → tr-B4IQ5t-T.js} +1 -1
- package/dist/assets/{uk-CLDkZFy_.js → uk-DCoXG6ve.js} +1 -1
- package/dist/assets/{useAppFontEffects-BxrtCnfR.js → useAppFontEffects-Bzyszv2N.js} +48 -48
- package/dist/assets/{useInlineCommentController-CeXuR-SR.js → useInlineCommentController-zOf6ORAq.js} +1 -1
- package/dist/assets/{useRootScrollLock-DgWxezc1.js → useRootScrollLock-DCaZ4BWi.js} +2 -2
- package/dist/assets/{vendor-codemirror-language-data-DOuaQUZ0.js → vendor-codemirror-language-data-6cEFPNUR.js} +2 -2
- package/dist/assets/{web-components-63H1h21j.js → web-components-BEo4-ISu.js} +1 -1
- package/dist/assets/{zh-CN-DODgcmYj.js → zh-CN-BXBoxsFk.js} +1 -1
- package/dist/assets/{zh-TW-ByNfIwLA.js → zh-TW-C6oelpnL.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/mini-chat.html +1 -1
- package/dist/mobile.html +1 -1
- package/package.json +1 -1
- package/server/index.d.ts +2 -0
- package/server/index.js +114 -52
- package/server/lib/agent-tool/DOCUMENTATION.md +47 -127
- package/server/lib/agent-tool/runtime.js +66 -47
- package/server/lib/agent-tool/runtime.test.js +2 -0
- package/server/lib/event-stream/DOCUMENTATION.md +2 -2
- package/server/lib/event-stream/global-hub.js +22 -6
- package/server/lib/event-stream/global-hub.test.js +93 -2
- package/server/lib/event-stream/rebind.test.js +1 -0
- package/server/lib/event-stream/runtime.js +1 -2
- package/server/lib/opencode/DOCUMENTATION.md +23 -23
- package/server/lib/opencode/lifecycle.js +126 -0
- package/server/lib/opencode/lifecycle.test.js +228 -1
- package/server/lib/opencode/network-runtime.js +16 -7
- package/server/lib/opencode/network-runtime.test.js +29 -1
- package/server/lib/opencode/proxy.js +15 -14
- package/server/lib/opencode/server-startup-runtime.js +25 -0
- package/server/lib/opencode/server-startup-runtime.test.js +67 -1
- package/server/lib/opencode/shared-service-runtime.js +155 -0
- package/server/lib/opencode/shared-service-runtime.test.js +180 -0
- package/server/lib/opencode/shutdown-runtime.js +11 -5
- package/server/lib/opencode/shutdown-runtime.test.js +77 -2
- package/server/lib/opencode/startup-pipeline-runtime.js +5 -4
- package/server/lib/opencode/startup-pipeline-runtime.test.js +18 -7
- package/server/lib/opencode/watcher.test.js +54 -0
- package/server/opencode-proxy.test.js +158 -0
- package/server/proxy-headers.js +8 -2
- package/server/proxy-headers.test.js +20 -0
package/server/index.js
CHANGED
|
@@ -51,7 +51,13 @@ import {
|
|
|
51
51
|
UPSTREAM_STALL_TIMEOUT_CONCURRENT_MS,
|
|
52
52
|
} from './lib/event-stream/index.js';
|
|
53
53
|
import { createFsSearchRuntime as createFsSearchRuntimeFactory } from './lib/fs/search.js';
|
|
54
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
createOpenCodeLifecycleControls,
|
|
56
|
+
createOpenCodeLifecycleRuntime,
|
|
57
|
+
createOpenCodeRecoveryCallbacks,
|
|
58
|
+
createOpenCodeServerComposition,
|
|
59
|
+
} from './lib/opencode/lifecycle.js';
|
|
60
|
+
import { createSharedOpenCodeServiceRuntime } from './lib/opencode/shared-service-runtime.js';
|
|
55
61
|
import { createOpenCodeEnvRuntime } from './lib/opencode/env-runtime.js';
|
|
56
62
|
import { providedLoginShellEnvSnapshot } from './lib/opencode/login-shell-env.js';
|
|
57
63
|
import { resolveOpenCodeEnvConfig } from './lib/opencode/env-config.js';
|
|
@@ -655,6 +661,8 @@ const initialOpenCodeAuthState = hmrStateRuntime.resolveOpenCodeAuthFromState({
|
|
|
655
661
|
});
|
|
656
662
|
let openCodeAuthPassword = initialOpenCodeAuthState.openCodeAuthPassword;
|
|
657
663
|
let openCodeAuthSource = initialOpenCodeAuthState.openCodeAuthSource;
|
|
664
|
+
let openCodeConnectionAdapter = null;
|
|
665
|
+
let sharedOpenCodeServiceRuntime = null;
|
|
658
666
|
|
|
659
667
|
// Sync helper - call after modifying any HMR state variable
|
|
660
668
|
const syncToHmrState = () => {
|
|
@@ -735,7 +743,23 @@ const openCodeAuthStateRuntime = createOpenCodeAuthStateRuntime({
|
|
|
735
743
|
syncToHmrState,
|
|
736
744
|
});
|
|
737
745
|
|
|
738
|
-
const
|
|
746
|
+
const getManagedOpenCodeAuthHeaders = (...args) => openCodeAuthStateRuntime.getOpenCodeAuthHeaders(...args);
|
|
747
|
+
const getConnectionOpenCodeAuthHeaders = (...args) => (
|
|
748
|
+
openCodeConnectionAdapter?.getOpenCodeAuthHeaders(...args) ?? getManagedOpenCodeAuthHeaders(...args)
|
|
749
|
+
);
|
|
750
|
+
const getOpenCodeBaseUrl = () => openCodeConnectionAdapter?.getOpenCodeBaseUrl() ?? openCodeBaseUrl;
|
|
751
|
+
const getOpenCodeConnectionKind = () => (
|
|
752
|
+
openCodeConnectionAdapter?.getOpenCodeConnectionKind() ?? 'managed-owned'
|
|
753
|
+
);
|
|
754
|
+
const ownsOpenCodeProcess = () => openCodeConnectionAdapter?.ownsOpenCodeProcess() ?? true;
|
|
755
|
+
const setSharedOpenCodeConnection = (connection) => {
|
|
756
|
+
openCodeBaseUrl = connection.endpoint.url;
|
|
757
|
+
const parsedUrl = new URL(connection.endpoint.url);
|
|
758
|
+
const parsedPort = parsedUrl.port
|
|
759
|
+
? Number.parseInt(parsedUrl.port, 10)
|
|
760
|
+
: (parsedUrl.protocol === 'https:' ? 443 : 80);
|
|
761
|
+
openCodePort = Number.isFinite(parsedPort) ? parsedPort : null;
|
|
762
|
+
};
|
|
739
763
|
const isOpenCodeConnectionSecure = (...args) => openCodeAuthStateRuntime.isOpenCodeConnectionSecure(...args);
|
|
740
764
|
const ensureLocalOpenCodeServerPassword = (...args) => openCodeAuthStateRuntime.ensureLocalOpenCodeServerPassword(...args);
|
|
741
765
|
|
|
@@ -750,10 +774,12 @@ Object.defineProperties(openCodeNetworkState, {
|
|
|
750
774
|
|
|
751
775
|
const openCodeNetworkRuntime = createOpenCodeNetworkRuntime({
|
|
752
776
|
state: openCodeNetworkState,
|
|
753
|
-
|
|
777
|
+
getOpenCodeBaseUrl,
|
|
778
|
+
getOpenCodeAuthHeaders: getConnectionOpenCodeAuthHeaders,
|
|
754
779
|
configuredOpenCodeHostname: ENV_CONFIGURED_OPENCODE_HOSTNAME,
|
|
755
780
|
});
|
|
756
781
|
|
|
782
|
+
const getOpenCodeAuthHeaders = (...args) => openCodeNetworkRuntime.getOpenCodeAuthHeaders(...args);
|
|
757
783
|
const waitForReady = (...args) => openCodeNetworkRuntime.waitForReady(...args);
|
|
758
784
|
const normalizeApiPrefix = (...args) => openCodeNetworkRuntime.normalizeApiPrefix(...args);
|
|
759
785
|
const setDetectedOpenCodeApiPrefix = (...args) => openCodeNetworkRuntime.setDetectedOpenCodeApiPrefix(...args);
|
|
@@ -1113,7 +1139,7 @@ const serverUtilsRuntime = createServerUtilsRuntime({
|
|
|
1113
1139
|
longRequestTimeoutMs: LONG_REQUEST_TIMEOUT_MS,
|
|
1114
1140
|
getRuntime: () => ({
|
|
1115
1141
|
openCodePort,
|
|
1116
|
-
openCodeBaseUrl,
|
|
1142
|
+
openCodeBaseUrl: getOpenCodeBaseUrl(),
|
|
1117
1143
|
openCodeNotReadySince,
|
|
1118
1144
|
isOpenCodeReady,
|
|
1119
1145
|
isRestartingOpenCode,
|
|
@@ -1260,6 +1286,19 @@ Object.defineProperties(openCodeLifecycleState, {
|
|
|
1260
1286
|
resolvedWslDistro: { get: () => resolvedWslDistro, set: (value) => { resolvedWslDistro = value; } },
|
|
1261
1287
|
});
|
|
1262
1288
|
|
|
1289
|
+
const openCodeRecoveryCallbacks = createOpenCodeRecoveryCallbacks({
|
|
1290
|
+
resetOpenCodeRuntimeProviders,
|
|
1291
|
+
rebindUpstream: () => {
|
|
1292
|
+
if (messageStreamRuntime) {
|
|
1293
|
+
messageStreamRuntime.rebindUpstream();
|
|
1294
|
+
} else {
|
|
1295
|
+
globalMessageStreamHub.rebind();
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
interruptBusySessionsAfterRestart: () => sessionRuntime.interruptBusySessionsAfterRestart(),
|
|
1299
|
+
broadcastUiNotification,
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1263
1302
|
const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({
|
|
1264
1303
|
state: openCodeLifecycleState,
|
|
1265
1304
|
env: {
|
|
@@ -1314,41 +1353,16 @@ const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({
|
|
|
1314
1353
|
// so the UI keeps receiving events instead of staying pinned to the old
|
|
1315
1354
|
// process (#2638). The runtime is created later by the startup pipeline;
|
|
1316
1355
|
// by the time any restart runs, it is assigned.
|
|
1317
|
-
onOpenCodeRestarted: () =>
|
|
1318
|
-
// A restart reloads plugins: provider ports, credentials and the provider
|
|
1319
|
-
// list itself can all differ from what was cached.
|
|
1320
|
-
resetOpenCodeRuntimeProviders();
|
|
1321
|
-
try {
|
|
1322
|
-
messageStreamRuntime?.rebindUpstream();
|
|
1323
|
-
} catch (error) {
|
|
1324
|
-
console.warn('Failed to rebind message stream after OpenCode restart:', error?.message ?? error);
|
|
1325
|
-
}
|
|
1326
|
-
try {
|
|
1327
|
-
const { sessionIds } = sessionRuntime.interruptBusySessionsAfterRestart();
|
|
1328
|
-
if (sessionIds.length > 0) {
|
|
1329
|
-
const multiple = sessionIds.length > 1;
|
|
1330
|
-
broadcastUiNotification({
|
|
1331
|
-
title: multiple ? 'Chats interrupted' : 'Chat interrupted',
|
|
1332
|
-
body: multiple
|
|
1333
|
-
? 'OpenCode restarted during running responses. Send a message in each chat to continue.'
|
|
1334
|
-
: 'OpenCode restarted during a running response. Send a message to continue.',
|
|
1335
|
-
tag: 'opencode-restart-interrupted',
|
|
1336
|
-
kind: 'opencode-restart-interrupted',
|
|
1337
|
-
sessionId: sessionIds[0],
|
|
1338
|
-
});
|
|
1339
|
-
}
|
|
1340
|
-
} catch (error) {
|
|
1341
|
-
console.warn('Failed to reconcile sessions after OpenCode restart:', error?.message ?? error);
|
|
1342
|
-
}
|
|
1343
|
-
},
|
|
1356
|
+
onOpenCodeRestarted: () => openCodeRecoveryCallbacks.onManagedRestarted(),
|
|
1344
1357
|
getManagedOpenCodeEnv: async () => (managedConfigRuntime ? managedConfigRuntime.buildManagedChildEnv() : {}),
|
|
1345
1358
|
});
|
|
1346
1359
|
|
|
1347
1360
|
const getOpenCodeCompatibility = async () => {
|
|
1348
|
-
if (
|
|
1349
|
-
const base = ENV_CONFIGURED_OPENCODE_HOST?.origin ||
|
|
1361
|
+
if (getOpenCodeConnectionKind() !== 'managed-owned' || ENV_SKIP_OPENCODE_START) {
|
|
1362
|
+
const base = ENV_CONFIGURED_OPENCODE_HOST?.origin || getOpenCodeBaseUrl() || `http://127.0.0.1:${openCodePort || ENV_EFFECTIVE_PORT}`;
|
|
1350
1363
|
const version = await readExternalOpenCodeVersion(base, getOpenCodeAuthHeaders()).catch(() => null);
|
|
1351
|
-
|
|
1364
|
+
const installation = getOpenCodeConnectionKind() === 'shared-local' ? 'shared' : 'external';
|
|
1365
|
+
return describeOpenCodeCompatibility(version, installation, false);
|
|
1352
1366
|
}
|
|
1353
1367
|
const binary = ensureOpencodeCliEnv();
|
|
1354
1368
|
const installation = isBundledOpenCodeCliPath(binary) ? 'bundled' : 'managed';
|
|
@@ -1357,6 +1371,14 @@ const getOpenCodeCompatibility = async () => {
|
|
|
1357
1371
|
};
|
|
1358
1372
|
|
|
1359
1373
|
const getOpenCodeUpgradeCapability = () => {
|
|
1374
|
+
if (getOpenCodeConnectionKind() === 'shared-local') {
|
|
1375
|
+
return resolveOpenCodeUpgradeCapability({
|
|
1376
|
+
isExternal: true,
|
|
1377
|
+
hasManagedProcess: false,
|
|
1378
|
+
activeBinary: null,
|
|
1379
|
+
isBundledBinary: isBundledOpenCodeCliPath,
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1360
1382
|
const activeBinary = lastOpenCodeLaunchDiagnostics?.sourceBinary
|
|
1361
1383
|
|| lastOpenCodeLaunchDiagnostics?.binary
|
|
1362
1384
|
|| resolvedOpencodeBinary;
|
|
@@ -1368,12 +1390,19 @@ const getOpenCodeUpgradeCapability = () => {
|
|
|
1368
1390
|
});
|
|
1369
1391
|
};
|
|
1370
1392
|
|
|
1371
|
-
const
|
|
1393
|
+
const openCodeLifecycleControls = createOpenCodeLifecycleControls({
|
|
1394
|
+
getKind: getOpenCodeConnectionKind,
|
|
1395
|
+
restartLifecycle: (...args) => openCodeLifecycleRuntime.restartOpenCode(...args),
|
|
1396
|
+
triggerLifecycleHealthCheck: (...args) => openCodeLifecycleRuntime.triggerHealthCheck(...args),
|
|
1397
|
+
recoverShared: () => sharedOpenCodeServiceRuntime.recover(),
|
|
1398
|
+
onSharedRecovered: openCodeRecoveryCallbacks.onSharedRecovered,
|
|
1399
|
+
});
|
|
1400
|
+
const restartOpenCode = (...args) => openCodeLifecycleControls.restart(...args);
|
|
1372
1401
|
const waitForOpenCodeReady = (...args) => openCodeLifecycleRuntime.waitForOpenCodeReady(...args);
|
|
1373
1402
|
const waitForAgentPresence = (...args) => openCodeLifecycleRuntime.waitForAgentPresence(...args);
|
|
1374
1403
|
const refreshOpenCodeAfterConfigChange = (...args) => openCodeLifecycleRuntime.refreshOpenCodeAfterConfigChange(...args);
|
|
1375
1404
|
const startHealthMonitoring = () => openCodeLifecycleRuntime.startHealthMonitoring(HEALTH_CHECK_INTERVAL);
|
|
1376
|
-
const triggerHealthCheck = () =>
|
|
1405
|
+
const triggerHealthCheck = (...args) => openCodeLifecycleControls.triggerHealthCheck(...args);
|
|
1377
1406
|
const scheduledTasksRuntime = createScheduledTasksRuntime({
|
|
1378
1407
|
projectConfigRuntime,
|
|
1379
1408
|
listProjects: async () => {
|
|
@@ -1595,9 +1624,19 @@ const ensureGlobalWatcherStarted = async () => {
|
|
|
1595
1624
|
return globalWatcherStartPromise;
|
|
1596
1625
|
};
|
|
1597
1626
|
const bootstrapOpenCodeAtStartup = async (...args) => {
|
|
1598
|
-
|
|
1627
|
+
if (getOpenCodeConnectionKind() === 'shared-local') {
|
|
1628
|
+
const connection = await sharedOpenCodeServiceRuntime.connect();
|
|
1629
|
+
setSharedOpenCodeConnection(connection);
|
|
1630
|
+
isOpenCodeReady = true;
|
|
1631
|
+
isExternalOpenCode = true;
|
|
1632
|
+
lastOpenCodeError = null;
|
|
1633
|
+
openCodeNotReadySince = 0;
|
|
1634
|
+
syncToHmrState();
|
|
1635
|
+
} else {
|
|
1636
|
+
await openCodeLifecycleRuntime.bootstrapOpenCodeAtStartup(...args);
|
|
1637
|
+
}
|
|
1599
1638
|
scheduleOpenCodeApiDetection();
|
|
1600
|
-
if (openCodeLifecycleState.openCodeProcess && !openCodeLifecycleState.isExternalOpenCode) {
|
|
1639
|
+
if (ownsOpenCodeProcess() && openCodeLifecycleState.openCodeProcess && !openCodeLifecycleState.isExternalOpenCode) {
|
|
1601
1640
|
startHealthMonitoring();
|
|
1602
1641
|
}
|
|
1603
1642
|
// The global watcher used to start only for desktop notifications; the
|
|
@@ -1634,6 +1673,7 @@ const gracefulShutdownRuntime = createGracefulShutdownRuntime({
|
|
|
1634
1673
|
contextObligatoryRuntime,
|
|
1635
1674
|
messageQueueRuntime,
|
|
1636
1675
|
sessionRuntime,
|
|
1676
|
+
globalEventHub: globalMessageStreamHub,
|
|
1637
1677
|
getHealthCheckInterval: () => healthCheckInterval,
|
|
1638
1678
|
clearHealthCheckInterval: (value) => clearInterval(value),
|
|
1639
1679
|
getTerminalRuntime: () => terminalRuntime,
|
|
@@ -1644,7 +1684,7 @@ const gracefulShutdownRuntime = createGracefulShutdownRuntime({
|
|
|
1644
1684
|
setMessageStreamRuntime: (value) => {
|
|
1645
1685
|
messageStreamRuntime = value;
|
|
1646
1686
|
},
|
|
1647
|
-
|
|
1687
|
+
getOpenCodeConnectionKind,
|
|
1648
1688
|
getOpenCodePort: () => openCodePort,
|
|
1649
1689
|
getOpenCodeProcess: () => openCodeProcess,
|
|
1650
1690
|
setOpenCodeProcess: (value) => {
|
|
@@ -1652,6 +1692,7 @@ const gracefulShutdownRuntime = createGracefulShutdownRuntime({
|
|
|
1652
1692
|
},
|
|
1653
1693
|
killProcessOnPort,
|
|
1654
1694
|
waitForPortRelease,
|
|
1695
|
+
disposeSharedOpenCodeService: () => sharedOpenCodeServiceRuntime?.dispose(),
|
|
1655
1696
|
getServer: () => server,
|
|
1656
1697
|
getUiAuthController: () => uiAuthController,
|
|
1657
1698
|
setUiAuthController: (value) => {
|
|
@@ -1676,6 +1717,11 @@ const gracefulShutdown = (...args) => gracefulShutdownRuntime.gracefulShutdown(.
|
|
|
1676
1717
|
|
|
1677
1718
|
async function main(options = {}) {
|
|
1678
1719
|
beginGuestServiceHost();
|
|
1720
|
+
const runtimeName = process.env.OPENCHAMBER_RUNTIME || 'web';
|
|
1721
|
+
const lifecycleMode = options.openCodeLifecycleMode === 'managed'
|
|
1722
|
+
|| options.openCodeLifecycleMode === 'shared'
|
|
1723
|
+
? options.openCodeLifecycleMode
|
|
1724
|
+
: undefined;
|
|
1679
1725
|
const port = Number.isFinite(options.port) && options.port >= 0 ? Math.trunc(options.port) : DEFAULT_PORT;
|
|
1680
1726
|
const host = typeof options.host === 'string' && options.host.length > 0 ? options.host : undefined;
|
|
1681
1727
|
const effectiveBindHost = host
|
|
@@ -1687,7 +1733,6 @@ async function main(options = {}) {
|
|
|
1687
1733
|
fsPromises,
|
|
1688
1734
|
path,
|
|
1689
1735
|
dataDir: OPENCHAMBER_DATA_DIR,
|
|
1690
|
-
env: process.env,
|
|
1691
1736
|
executeAction: (...args) => openChamberControlService.execute(...args),
|
|
1692
1737
|
// A v2 tool call carries no directory, only the session it runs in.
|
|
1693
1738
|
resolveSessionDirectory: (sessionID) => openChamberControlService.resolveSessionDirectory(sessionID),
|
|
@@ -1698,15 +1743,31 @@ async function main(options = {}) {
|
|
|
1698
1743
|
// A pipe listener reports a string here, which has no address to bind back to.
|
|
1699
1744
|
getActiveHost: () => server?.address?.()?.address ?? null,
|
|
1700
1745
|
});
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1746
|
+
const openCodeComposition = createOpenCodeServerComposition({
|
|
1747
|
+
runtime: runtimeName,
|
|
1748
|
+
configuredHost: ENV_CONFIGURED_OPENCODE_HOST,
|
|
1749
|
+
lifecycleMode,
|
|
1750
|
+
createSharedRuntime: () => {
|
|
1751
|
+
const binary = ensureOpencodeCliEnv();
|
|
1752
|
+
return createSharedOpenCodeServiceRuntime({
|
|
1753
|
+
ensureOptions: binary ? { command: [binary, 'serve', '--service'] } : undefined,
|
|
1754
|
+
});
|
|
1755
|
+
},
|
|
1756
|
+
createManagedPluginRuntime: () => createManagedConfigRuntime({
|
|
1757
|
+
fsPromises,
|
|
1758
|
+
path,
|
|
1759
|
+
dataDir: OPENCHAMBER_DATA_DIR,
|
|
1760
|
+
env: process.env,
|
|
1761
|
+
agentToolRuntime,
|
|
1762
|
+
readSettings: () => readSettingsFromDiskMigrated(),
|
|
1763
|
+
isAgentMemoryAvailable: isAgentMemoryFeatureAvailable,
|
|
1764
|
+
}),
|
|
1765
|
+
getManagedBaseUrl: () => openCodeBaseUrl,
|
|
1766
|
+
getManagedAuthHeaders: getManagedOpenCodeAuthHeaders,
|
|
1709
1767
|
});
|
|
1768
|
+
sharedOpenCodeServiceRuntime = openCodeComposition.sharedRuntime;
|
|
1769
|
+
managedConfigRuntime = openCodeComposition.managedPluginRuntime;
|
|
1770
|
+
openCodeConnectionAdapter = openCodeComposition.connection;
|
|
1710
1771
|
|
|
1711
1772
|
// Pairing transports advertised to the create-device dialog. LAN reachability is
|
|
1712
1773
|
// derived from the SERVER's actual bind (a wildcard bind → the machine's LAN IP;
|
|
@@ -1903,7 +1964,7 @@ async function main(options = {}) {
|
|
|
1903
1964
|
const bootstrapResult = bootstrapRuntime.setupBaseRoutes(app, {
|
|
1904
1965
|
process,
|
|
1905
1966
|
openchamberVersion: OPENCHAMBER_VERSION,
|
|
1906
|
-
runtimeName
|
|
1967
|
+
runtimeName,
|
|
1907
1968
|
serverStartedAt,
|
|
1908
1969
|
gracefulShutdown,
|
|
1909
1970
|
getHealthSnapshot: () => {
|
|
@@ -2199,7 +2260,6 @@ async function main(options = {}) {
|
|
|
2199
2260
|
terminalRebindWindowMs: TERMINAL_INPUT_WS_REBIND_WINDOW_MS,
|
|
2200
2261
|
terminalMaxRebindsPerWindow: TERMINAL_INPUT_WS_MAX_REBINDS_PER_WINDOW,
|
|
2201
2262
|
setupProxy,
|
|
2202
|
-
scheduleOpenCodeApiDetection,
|
|
2203
2263
|
bootstrapOpenCodeAtStartup,
|
|
2204
2264
|
triggerHealthCheck,
|
|
2205
2265
|
staticRoutesRuntime,
|
|
@@ -2264,10 +2324,12 @@ async function main(options = {}) {
|
|
|
2264
2324
|
scheduledTasks: scheduledTasksRuntime.getStatus(),
|
|
2265
2325
|
}),
|
|
2266
2326
|
isReady: () => isOpenCodeReady,
|
|
2267
|
-
getManagedOpenCodePreflight: () =>
|
|
2327
|
+
getManagedOpenCodePreflight: () => ownsOpenCodeProcess()
|
|
2328
|
+
? openCodeLifecycleRuntime.getManagedOpenCodePreflight()
|
|
2329
|
+
: Promise.resolve(false),
|
|
2268
2330
|
restartOpenCode: () => restartOpenCode(),
|
|
2269
2331
|
getOpenCodeProcessInfo: () => {
|
|
2270
|
-
const managed = Boolean((openCodeProcess || openCodePort) && !ENV_SKIP_OPENCODE_START && !isExternalOpenCode);
|
|
2332
|
+
const managed = Boolean(ownsOpenCodeProcess() && (openCodeProcess || openCodePort) && !ENV_SKIP_OPENCODE_START && !isExternalOpenCode);
|
|
2271
2333
|
// Only ever expose pid/port for a server WE manage. The Electron-side
|
|
2272
2334
|
// killer kills by port (lsof + kill -KILL), so returning a port we don't
|
|
2273
2335
|
// own — e.g. an external/desktop OpenCode on 4096 we attached to — would
|
|
@@ -1,116 +1,55 @@
|
|
|
1
|
-
# Managed OpenChamber
|
|
1
|
+
# Managed OpenChamber agent tools
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
This module exposes OpenChamber
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- `openchamber`
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
3. `createChildEnv()` adds a random per-child token and the callback URL to the
|
|
40
|
-
managed OpenCode child environment. They are present even while every tool
|
|
41
|
-
is off, so a tool switched on later can call back without a restart. The URL
|
|
42
|
-
points at loopback, except when the listener is bound to one concrete
|
|
43
|
-
address (`--host <ip>`): that socket does not answer on loopback, so the URL
|
|
44
|
-
uses the bound address instead.
|
|
45
|
-
4. The plugin calls `POST /api/openchamber/agent-tool` with its typed input and
|
|
46
|
-
the session id OpenCode gives the tool; OpenChamber resolves the session's
|
|
47
|
-
directory on its own side.
|
|
48
|
-
5. The route delegates the fixed action allowlist directly to the shared
|
|
49
|
-
OpenChamber control service. The CLI uses the same service through its
|
|
50
|
-
authenticated HTTP adapter, so Goal Mode ordering, wait behavior,
|
|
51
|
-
partial-failure reporting, and scheduled-task contracts have one owner.
|
|
52
|
-
6. Each action definition owns a short presentation title and a separate
|
|
53
|
-
agent-facing description. The generated schema uses the description to state
|
|
54
|
-
required inputs or one non-obvious behavior, while completed calls use the
|
|
55
|
-
short title in native tool metadata.
|
|
56
|
-
|
|
57
|
-
## Agent context budget
|
|
58
|
-
|
|
59
|
-
- The tool exposes one shared parameter object rather than repeating parameters
|
|
60
|
-
in a large per-action union. Action descriptions carry only required inputs,
|
|
61
|
-
defaults, or one non-obvious semantic detail.
|
|
62
|
-
- The action schema carries `oneOf` and no `enum`. A node combining `enum` and
|
|
63
|
-
`oneOf` is valid JSON Schema, but some OpenAI-compatible gateways reject it
|
|
64
|
-
and answer with an empty completion instead of an error.
|
|
65
|
-
- Obvious fields rely on their names and JSON types. Parameter descriptions are
|
|
66
|
-
reserved for formats, dependencies, scope, and behavior that cannot be safely
|
|
67
|
-
inferred from the field name.
|
|
68
|
-
- Session dispatches do not wait by default. Agents are told to set `wait` only
|
|
69
|
-
when the user asks or the next step requires the completed result.
|
|
70
|
-
- The tool exposes only agent-relevant actions
|
|
71
|
-
(`OPENCHAMBER_AGENT_TOOL_ACTIONS`): `schedule.status` stays CLI-only because
|
|
72
|
-
`schedule.list` already returns scheduler status, and enable/disable are one
|
|
73
|
-
`schedule.toggle` action driven by the `disabled` boolean.
|
|
74
|
-
- The tool description frames intent: created sessions and scheduled tasks are
|
|
75
|
-
user-facing work the user follows up with. The agent must not decide on its
|
|
76
|
-
own to delegate parts of its current task, but an explicit user request to
|
|
77
|
-
create, send, or schedule always wins, even when it relates to the current
|
|
78
|
-
task (strict models otherwise read the old unconditional "never delegate" as
|
|
79
|
-
a hard ban and refused user-requested sends).
|
|
80
|
-
- Optional behavior switches (`worktree`, `goal`, `agent`, `variant`, `wait`)
|
|
81
|
-
state their default and an explicit "only when the user asks" rule so agents
|
|
82
|
-
do not invent worktrees, goal mode, or waits the user never requested.
|
|
83
|
-
- Detailed combination rules are enforced by the shared control service and
|
|
84
|
-
returned as actionable usage errors only after an invalid call. Per-action
|
|
85
|
-
examples and a repeated per-action parameter schema are intentionally omitted.
|
|
5
|
+
This module exposes OpenChamber actions as typed OpenCode custom tools for Electron Desktop's managed OpenCode child.
|
|
6
|
+
|
|
7
|
+
The main tools are:
|
|
8
|
+
|
|
9
|
+
- `openchamber` for projects, sessions, worktrees, scheduled tasks, and `file.open`
|
|
10
|
+
- `openchamber_web` for inspecting and interacting with the page in OpenChamber's browser panel
|
|
11
|
+
|
|
12
|
+
Their persisted settings control whether each tool is present. Tool definitions include only the actions and inputs enabled for that tool.
|
|
13
|
+
|
|
14
|
+
## Runtime boundary
|
|
15
|
+
|
|
16
|
+
Electron Desktop owns the managed integration:
|
|
17
|
+
|
|
18
|
+
1. `materializePlugin()` writes the generated plugin under `<openchamber-data-dir>/agent-tool/`.
|
|
19
|
+
2. The managed OpenCode config lists that plugin directory.
|
|
20
|
+
3. `createChildEnv()` gives the owned OpenCode child a random callback token and loopback callback URL.
|
|
21
|
+
4. The plugin calls `POST /api/openchamber/agent-tool` with its typed input and OpenCode session ID.
|
|
22
|
+
5. OpenChamber resolves the session directory and delegates the fixed action allowlist to the shared control service.
|
|
23
|
+
|
|
24
|
+
The callback token stays in the managed child environment. It is never persisted, logged, returned to the UI, or written into the generated plugin.
|
|
25
|
+
|
|
26
|
+
Ordinary Web does not install a plugin, publish callback registration, or advertise managed OpenChamber tools to the shared local OpenCode service. This keeps shared Web limited to sessions and live events. A valid `OPENCODE_HOST` is also outside managed-tool ownership, so OpenChamber does not guarantee tools on that endpoint.
|
|
27
|
+
|
|
28
|
+
VS Code keeps its separate lifecycle. Hosted and Capacitor clients do not run tools themselves. They may connect to a Desktop-owned backend that has managed tools, but an ordinary Web backend does not add them.
|
|
29
|
+
|
|
30
|
+
## Input contract
|
|
31
|
+
|
|
32
|
+
The plugin accepts action inputs inside `parameters` or beside `action`. An explicit `parameters` object wins when both forms provide the same field.
|
|
33
|
+
|
|
34
|
+
Each action definition has a short presentation title and a separate agent-facing description. The generated schema uses descriptions only for required inputs, defaults, or behavior that a field name cannot explain.
|
|
35
|
+
|
|
36
|
+
The schema uses one shared parameter object with `oneOf` and no `enum`. Some OpenAI-compatible gateways reject a node that combines both and return an empty completion.
|
|
37
|
+
|
|
38
|
+
Session dispatch does not wait by default. Optional switches such as `worktree`, `goal`, `agent`, `variant`, and `wait` state their defaults and tell the agent not to invent them.
|
|
86
39
|
|
|
87
40
|
## Security invariants
|
|
88
41
|
|
|
89
|
-
- The callback accepts same-machine requests only and requires the current
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- The
|
|
96
|
-
the materialized plugin.
|
|
97
|
-
- The plugin adds the callback host to `NO_PROXY`/`no_proxy` inside the managed
|
|
98
|
-
child when it loads. Without that, an `HTTP_PROXY` in the child's environment
|
|
99
|
-
would receive a non-loopback callback, token included, because `fetch` has no
|
|
100
|
-
per-request way to skip the environment proxy.
|
|
101
|
-
- Inputs map to a fixed action and parameter allowlist. There is no arbitrary
|
|
102
|
-
CLI, shell, route, or URL forwarding.
|
|
103
|
-
- Session/worktree deletion and project-path registration are not exposed.
|
|
104
|
-
- A cancelled turn aborts the session's in-flight actions. OpenCode 2 gives a
|
|
105
|
-
plugin tool no abort signal and the plugin's request stays open, so the
|
|
106
|
-
server tracks in-flight actions per session and `abortSession(sessionID)` is
|
|
107
|
-
called from the event dispatch in `server/index.js` when the stream reports
|
|
108
|
-
`session.idle` with `aborted: true`; the abort signal then reaches the
|
|
109
|
-
shared service as before. A dropped HTTP request still aborts as well.
|
|
42
|
+
- The callback accepts same-machine requests only and requires the current managed-child bearer token with a timing-safe comparison.
|
|
43
|
+
- A concrete listener address may accept a source equal to that address. A wildcard listener remains loopback-only.
|
|
44
|
+
- The plugin adds its callback host to `NO_PROXY` and `no_proxy` in the managed child so an environment proxy cannot receive the callback token.
|
|
45
|
+
- Inputs map to fixed tool, action, and parameter allowlists. There is no arbitrary CLI, shell, route, or URL forwarding.
|
|
46
|
+
- Project-path registration and session or worktree deletion are not exposed.
|
|
47
|
+
- A dropped callback request aborts its action.
|
|
48
|
+
- OpenCode 2 does not give plugin tools an abort signal. The server therefore tracks actions by session and aborts them when the event stream reports an aborted `session.idle` transition.
|
|
110
49
|
|
|
111
50
|
## Result contract
|
|
112
51
|
|
|
113
|
-
|
|
52
|
+
Completed calls return JSON:
|
|
114
53
|
|
|
115
54
|
```json
|
|
116
55
|
{
|
|
@@ -121,29 +60,10 @@ Every completed call returns JSON:
|
|
|
121
60
|
}
|
|
122
61
|
```
|
|
123
62
|
|
|
124
|
-
Command and operational failures use the same envelope with `ok: false` and
|
|
125
|
-
an `error` object. OpenCode-level cancellation can still produce a native tool
|
|
126
|
-
error state.
|
|
127
|
-
|
|
128
|
-
## Runtime parity
|
|
129
|
-
|
|
130
|
-
- Web and Desktop managed OpenCode: injected automatically.
|
|
131
|
-
- External OpenCode selected with `OPENCODE_HOST` or skip-start: not injected,
|
|
132
|
-
because OpenChamber does not control that process environment.
|
|
133
|
-
- VS Code: not injected; the extension owns a separate OpenCode lifecycle.
|
|
134
|
-
- Hosted and Capacitor mobile clients use the server's managed OpenCode tool
|
|
135
|
-
when connected to such a server; no tool runs in the client runtime.
|
|
63
|
+
Command and operational failures use the same envelope with `ok: false` and an `error` object. OpenCode-level cancellation can still produce a native tool error.
|
|
136
64
|
|
|
137
|
-
##
|
|
65
|
+
## Action resolution
|
|
138
66
|
|
|
139
|
-
Each generated tool sends its own name with every callback.
|
|
140
|
-
drop the namespace their tool's name appears to supply — `openchamber_memory`
|
|
141
|
-
asked for `memory.read` gets called as `read` — and resolving the bare name
|
|
142
|
-
inside the calling tool's action set makes that unambiguous even where it is not
|
|
143
|
-
globally (`delete` belongs to both schedule and memory).
|
|
67
|
+
Each generated tool sends its own name with every callback. Action resolution stays within that tool's allowlist. For example, a bare `open` requested by a memory tool cannot drive the browser.
|
|
144
68
|
|
|
145
|
-
|
|
146
|
-
tool fails rather than driving the browser. An unresolvable action answers with
|
|
147
|
-
the actions that tool actually has, because an error that only says
|
|
148
|
-
"unsupported" leaves the model to guess a second wrong name — which is exactly
|
|
149
|
-
what happened before this existed.
|
|
69
|
+
When an action name is invalid, the response lists the actions available to the calling tool. This lets the model correct the call without guessing across tool namespaces.
|