@openchamber/web 1.4.5 → 1.4.6
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/{ToolOutputDialog-DYNTzpsQ.js → ToolOutputDialog-Bk6Uzhck.js} +1 -1
- package/dist/assets/index-Cxzt1pIT.css +1 -0
- package/dist/assets/{index-g64tpi5J.js → index-_QJSNcFo.js} +2 -2
- package/dist/assets/main-COr_R8wu.js +128 -0
- package/dist/assets/{vendor-.bun-B2HtLj-d.js → vendor-.bun-C07YQe9X.js} +240 -240
- package/dist/index.html +3 -3
- package/package.json +2 -2
- package/server/index.js +70 -355
- package/dist/assets/index-hru9kOov.css +0 -1
- package/dist/assets/main-BucgrSbb.js +0 -128
package/dist/index.html
CHANGED
|
@@ -160,10 +160,10 @@
|
|
|
160
160
|
pointer-events: none;
|
|
161
161
|
}
|
|
162
162
|
</style>
|
|
163
|
-
<script type="module" crossorigin src="/assets/index-
|
|
164
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-.bun-
|
|
163
|
+
<script type="module" crossorigin src="/assets/index-_QJSNcFo.js"></script>
|
|
164
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-.bun-C07YQe9X.js">
|
|
165
165
|
<link rel="stylesheet" crossorigin href="/assets/vendor--Jn2c0Clh.css">
|
|
166
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
166
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Cxzt1pIT.css">
|
|
167
167
|
</head>
|
|
168
168
|
<body class="h-full bg-background text-foreground">
|
|
169
169
|
<div id="root" class="h-full">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openchamber/web",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./server/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@fontsource/ibm-plex-mono": "^5.2.7",
|
|
27
27
|
"@fontsource/ibm-plex-sans": "^5.1.1",
|
|
28
28
|
"@ibm/plex": "^6.4.1",
|
|
29
|
-
"@opencode-ai/sdk": "^1.1.
|
|
29
|
+
"@opencode-ai/sdk": "^1.1.8",
|
|
30
30
|
"@radix-ui/react-collapsible": "^1.1.12",
|
|
31
31
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
32
32
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
package/server/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { spawn
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import http from 'http';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
@@ -9,12 +9,12 @@ import os from 'os';
|
|
|
9
9
|
import crypto from 'crypto';
|
|
10
10
|
import { createUiAuth } from './lib/ui-auth.js';
|
|
11
11
|
import { startCloudflareTunnel, printTunnelWarning, checkCloudflaredAvailable } from './lib/cloudflare-tunnel.js';
|
|
12
|
+
import { createOpencodeServer } from '@opencode-ai/sdk/server';
|
|
12
13
|
|
|
13
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
15
|
const __dirname = path.dirname(__filename);
|
|
15
16
|
|
|
16
17
|
const DEFAULT_PORT = 3000;
|
|
17
|
-
const DEFAULT_OPENCODE_PORT = 0;
|
|
18
18
|
const HEALTH_CHECK_INTERVAL = 30000;
|
|
19
19
|
const SHUTDOWN_TIMEOUT = 10000;
|
|
20
20
|
const MODELS_DEV_API_URL = 'https://models.dev/api.json';
|
|
@@ -848,7 +848,6 @@ let openCodeApiDetectionTimer = null;
|
|
|
848
848
|
let isDetectingApiPrefix = false;
|
|
849
849
|
let openCodeApiDetectionPromise = null;
|
|
850
850
|
let lastOpenCodeError = null;
|
|
851
|
-
let openCodePortWaiters = [];
|
|
852
851
|
let isOpenCodeReady = false;
|
|
853
852
|
let openCodeNotReadySince = 0;
|
|
854
853
|
let exitOnShutdown = true;
|
|
@@ -890,12 +889,7 @@ async function isOpenCodeProcessHealthy() {
|
|
|
890
889
|
return false;
|
|
891
890
|
}
|
|
892
891
|
|
|
893
|
-
//
|
|
894
|
-
if (openCodeProcess.exitCode !== null || openCodeProcess.signalCode !== null) {
|
|
895
|
-
return false;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// Health check via HTTP
|
|
892
|
+
// Health check via HTTP since SDK object doesn't expose exitCode
|
|
899
893
|
try {
|
|
900
894
|
const response = await fetch(`http://127.0.0.1:${openCodePort}/session`, {
|
|
901
895
|
method: 'GET',
|
|
@@ -907,105 +901,6 @@ async function isOpenCodeProcessHealthy() {
|
|
|
907
901
|
}
|
|
908
902
|
}
|
|
909
903
|
|
|
910
|
-
const OPENCODE_BINARY_ENV =
|
|
911
|
-
process.env.OPENCODE_BINARY ||
|
|
912
|
-
process.env.OPENCHAMBER_BINARY ||
|
|
913
|
-
process.env.OPENCODE_PATH ||
|
|
914
|
-
process.env.OPENCHAMBER_OPENCODE_PATH ||
|
|
915
|
-
null;
|
|
916
|
-
|
|
917
|
-
function buildAugmentedPath() {
|
|
918
|
-
const augmented = new Set();
|
|
919
|
-
|
|
920
|
-
const loginShellPath = getLoginShellPath();
|
|
921
|
-
if (loginShellPath) {
|
|
922
|
-
for (const segment of loginShellPath.split(path.delimiter)) {
|
|
923
|
-
if (segment) {
|
|
924
|
-
augmented.add(segment);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
const current = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
|
|
930
|
-
for (const segment of current) {
|
|
931
|
-
augmented.add(segment);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
return Array.from(augmented).join(path.delimiter);
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
function getLoginShellPath() {
|
|
938
|
-
if (process.platform === 'win32') {
|
|
939
|
-
return null;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
const shell = process.env.SHELL || '/bin/zsh';
|
|
943
|
-
const shellName = path.basename(shell);
|
|
944
|
-
|
|
945
|
-
// Nushell requires different flag syntax and PATH access
|
|
946
|
-
const isNushell = shellName === 'nu' || shellName === 'nushell';
|
|
947
|
-
const args = isNushell
|
|
948
|
-
? ['-l', '-i', '-c', '$env.PATH | str join (char esep)']
|
|
949
|
-
: ['-lic', 'echo -n "$PATH"'];
|
|
950
|
-
|
|
951
|
-
try {
|
|
952
|
-
const result = spawnSync(shell, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
953
|
-
if (result.status === 0 && typeof result.stdout === 'string') {
|
|
954
|
-
const value = result.stdout.trim();
|
|
955
|
-
if (value) {
|
|
956
|
-
return value;
|
|
957
|
-
}
|
|
958
|
-
} else if (result.stderr) {
|
|
959
|
-
console.warn(`Failed to read PATH from login shell (${shell}): ${result.stderr}`);
|
|
960
|
-
}
|
|
961
|
-
} catch (error) {
|
|
962
|
-
console.warn(`Error executing login shell (${shell}) for PATH detection: ${error.message}`);
|
|
963
|
-
}
|
|
964
|
-
return null;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
function resolveBinaryFromPath(binaryName, searchPath) {
|
|
968
|
-
if (!binaryName) {
|
|
969
|
-
return null;
|
|
970
|
-
}
|
|
971
|
-
if (path.isAbsolute(binaryName)) {
|
|
972
|
-
return fs.existsSync(binaryName) ? binaryName : null;
|
|
973
|
-
}
|
|
974
|
-
const directories = searchPath.split(path.delimiter).filter(Boolean);
|
|
975
|
-
for (const directory of directories) {
|
|
976
|
-
try {
|
|
977
|
-
const candidate = path.join(directory, binaryName);
|
|
978
|
-
if (fs.existsSync(candidate)) {
|
|
979
|
-
const stats = fs.statSync(candidate);
|
|
980
|
-
if (stats.isFile()) {
|
|
981
|
-
return candidate;
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
} catch {
|
|
985
|
-
// Ignore resolution errors, continue searching
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
return null;
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
function getOpencodeSpawnConfig() {
|
|
992
|
-
const envPath = buildAugmentedPath();
|
|
993
|
-
const resolvedEnv = { ...process.env, PATH: envPath };
|
|
994
|
-
|
|
995
|
-
if (OPENCODE_BINARY_ENV) {
|
|
996
|
-
const explicit = resolveBinaryFromPath(OPENCODE_BINARY_ENV, envPath);
|
|
997
|
-
if (explicit) {
|
|
998
|
-
console.log(`Using OpenCode binary from OPENCODE_BINARY: ${explicit}`);
|
|
999
|
-
return { command: explicit, env: resolvedEnv };
|
|
1000
|
-
}
|
|
1001
|
-
console.warn(
|
|
1002
|
-
`OPENCODE_BINARY path "${OPENCODE_BINARY_ENV}" not found. Falling back to search.`
|
|
1003
|
-
);
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
return { command: 'opencode', env: resolvedEnv };
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
904
|
const ENV_CONFIGURED_OPENCODE_PORT = (() => {
|
|
1010
905
|
const raw =
|
|
1011
906
|
process.env.OPENCODE_PORT ||
|
|
@@ -1048,42 +943,31 @@ function setOpenCodePort(port) {
|
|
|
1048
943
|
}
|
|
1049
944
|
|
|
1050
945
|
lastOpenCodeError = null;
|
|
1051
|
-
|
|
1052
|
-
if (openCodePortWaiters.length > 0) {
|
|
1053
|
-
const waiters = openCodePortWaiters;
|
|
1054
|
-
openCodePortWaiters = [];
|
|
1055
|
-
for (const notify of waiters) {
|
|
1056
|
-
try {
|
|
1057
|
-
notify(numericPort);
|
|
1058
|
-
} catch (error) {
|
|
1059
|
-
console.warn('Failed to notify OpenCode port waiter:', error);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
946
|
}
|
|
1064
947
|
|
|
1065
|
-
|
|
1066
|
-
if (openCodePort !== null) {
|
|
1067
|
-
return openCodePort;
|
|
1068
|
-
}
|
|
948
|
+
const API_PREFIX_CANDIDATES = ['', '/api']; // Simplified - only check root and /api
|
|
1069
949
|
|
|
1070
|
-
|
|
1071
|
-
|
|
950
|
+
async function waitForReady(url, timeoutMs = 10000) {
|
|
951
|
+
const start = Date.now();
|
|
952
|
+
while (Date.now() - start < timeoutMs) {
|
|
953
|
+
try {
|
|
954
|
+
const controller = new AbortController();
|
|
955
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
956
|
+
const res = await fetch(`${url.replace(/\/+$/, '')}/config`, {
|
|
957
|
+
method: 'GET',
|
|
958
|
+
headers: { Accept: 'application/json' },
|
|
959
|
+
signal: controller.signal
|
|
960
|
+
});
|
|
1072
961
|
clearTimeout(timeout);
|
|
1073
|
-
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
openCodePortWaiters.push(onPortDetected);
|
|
1082
|
-
});
|
|
962
|
+
if (res.ok) return true;
|
|
963
|
+
} catch {
|
|
964
|
+
// ignore
|
|
965
|
+
}
|
|
966
|
+
await new Promise(r => setTimeout(r, 100));
|
|
967
|
+
}
|
|
968
|
+
return false;
|
|
1083
969
|
}
|
|
1084
970
|
|
|
1085
|
-
const API_PREFIX_CANDIDATES = ['', '/api']; // Simplified - only check root and /api
|
|
1086
|
-
|
|
1087
971
|
function normalizeApiPrefix(prefix) {
|
|
1088
972
|
if (!prefix) {
|
|
1089
973
|
return '';
|
|
@@ -1119,51 +1003,6 @@ function setDetectedOpenCodeApiPrefix(prefix) {
|
|
|
1119
1003
|
}
|
|
1120
1004
|
}
|
|
1121
1005
|
|
|
1122
|
-
function detectPortFromLogMessage(message) {
|
|
1123
|
-
if (openCodePort && ENV_CONFIGURED_OPENCODE_PORT) {
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
const regex = /https?:\/\/[^:\s]+:(\d+)/gi;
|
|
1128
|
-
let match;
|
|
1129
|
-
while ((match = regex.exec(message)) !== null) {
|
|
1130
|
-
const port = parseInt(match[1], 10);
|
|
1131
|
-
if (Number.isFinite(port) && port > 0) {
|
|
1132
|
-
setOpenCodePort(port);
|
|
1133
|
-
return;
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
const fallbackMatch = /(?:^|\s)(?:127\.0\.0\.1|localhost):(\d+)/i.exec(message);
|
|
1138
|
-
if (fallbackMatch) {
|
|
1139
|
-
const port = parseInt(fallbackMatch[1], 10);
|
|
1140
|
-
if (Number.isFinite(port) && port > 0) {
|
|
1141
|
-
setOpenCodePort(port);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
function detectPrefixFromLogMessage(message) {
|
|
1147
|
-
if (!openCodePort) {
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
const urlRegex = /https?:\/\/[^:\s]+:(\d+)(\/[^\s"']*)?/gi;
|
|
1152
|
-
let match;
|
|
1153
|
-
|
|
1154
|
-
while ((match = urlRegex.exec(message)) !== null) {
|
|
1155
|
-
const portMatch = parseInt(match[1], 10);
|
|
1156
|
-
if (portMatch !== openCodePort) {
|
|
1157
|
-
continue;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
const path = match[2] || '';
|
|
1161
|
-
const normalized = normalizeApiPrefix(path);
|
|
1162
|
-
setDetectedOpenCodeApiPrefix(normalized);
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
1006
|
function getCandidateApiPrefixes() {
|
|
1168
1007
|
if (openCodeApiPrefixDetected) {
|
|
1169
1008
|
return [openCodeApiPrefix];
|
|
@@ -1495,136 +1334,57 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
1495
1334
|
}
|
|
1496
1335
|
|
|
1497
1336
|
async function startOpenCode() {
|
|
1498
|
-
const desiredPort = ENV_CONFIGURED_OPENCODE_PORT ??
|
|
1337
|
+
const desiredPort = ENV_CONFIGURED_OPENCODE_PORT ?? 0;
|
|
1499
1338
|
console.log(
|
|
1500
|
-
desiredPort
|
|
1339
|
+
desiredPort > 0
|
|
1501
1340
|
? `Starting OpenCode on requested port ${desiredPort}...`
|
|
1502
1341
|
: 'Starting OpenCode with dynamic port assignment...'
|
|
1503
1342
|
);
|
|
1504
|
-
|
|
1343
|
+
// Note: SDK starts in current process CWD. openCodeWorkingDirectory is tracked but not used for spawn in SDK.
|
|
1505
1344
|
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
openCodeNotReadySince = Date.now();
|
|
1345
|
+
try {
|
|
1346
|
+
const serverInstance = await createOpencodeServer({
|
|
1347
|
+
hostname: '127.0.0.1',
|
|
1348
|
+
port: desiredPort,
|
|
1349
|
+
timeout: 30000,
|
|
1350
|
+
env: {
|
|
1351
|
+
...process.env,
|
|
1352
|
+
// Pass minimal config to avoid pollution, but inherit PATH etc
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1517
1355
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
firstSignalResolver = resolve;
|
|
1521
|
-
});
|
|
1522
|
-
let firstSignalSettled = false;
|
|
1523
|
-
const settleFirstSignal = () => {
|
|
1524
|
-
if (firstSignalSettled) {
|
|
1525
|
-
return;
|
|
1526
|
-
}
|
|
1527
|
-
firstSignalSettled = true;
|
|
1528
|
-
clearTimeout(firstSignalTimer);
|
|
1529
|
-
child.stdout.off('data', settleFirstSignal);
|
|
1530
|
-
child.stderr.off('data', settleFirstSignal);
|
|
1531
|
-
child.off('exit', settleFirstSignal);
|
|
1532
|
-
if (firstSignalResolver) {
|
|
1533
|
-
firstSignalResolver();
|
|
1356
|
+
if (!serverInstance || !serverInstance.url) {
|
|
1357
|
+
throw new Error('OpenCode server started but URL is missing');
|
|
1534
1358
|
}
|
|
1535
|
-
};
|
|
1536
|
-
const firstSignalTimer = setTimeout(settleFirstSignal, 750);
|
|
1537
|
-
|
|
1538
|
-
child.stdout.once('data', settleFirstSignal);
|
|
1539
|
-
child.stderr.once('data', settleFirstSignal);
|
|
1540
|
-
child.once('exit', settleFirstSignal);
|
|
1541
|
-
|
|
1542
|
-
child.stdout.on('data', (data) => {
|
|
1543
|
-
const text = data.toString();
|
|
1544
|
-
console.log(`OpenCode: ${text.trim()}`);
|
|
1545
|
-
detectPortFromLogMessage(text);
|
|
1546
|
-
detectPrefixFromLogMessage(text);
|
|
1547
|
-
settleFirstSignal();
|
|
1548
|
-
});
|
|
1549
1359
|
|
|
1550
|
-
|
|
1551
|
-
const
|
|
1552
|
-
|
|
1553
|
-
console.error(`OpenCode Error: ${lastOpenCodeError}`);
|
|
1554
|
-
detectPortFromLogMessage(text);
|
|
1555
|
-
detectPrefixFromLogMessage(text);
|
|
1556
|
-
settleFirstSignal();
|
|
1557
|
-
});
|
|
1360
|
+
const url = new URL(serverInstance.url);
|
|
1361
|
+
const port = parseInt(url.port, 10);
|
|
1362
|
+
const prefix = normalizeApiPrefix(url.pathname);
|
|
1558
1363
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
child.off('spawn', onSpawn);
|
|
1567
|
-
reject(error);
|
|
1568
|
-
};
|
|
1364
|
+
if (await waitForReady(serverInstance.url, 10000)) {
|
|
1365
|
+
setOpenCodePort(port);
|
|
1366
|
+
setDetectedOpenCodeApiPrefix(prefix); // SDK URL typically includes the prefix if any
|
|
1367
|
+
|
|
1368
|
+
isOpenCodeReady = true;
|
|
1369
|
+
lastOpenCodeError = null;
|
|
1370
|
+
openCodeNotReadySince = 0;
|
|
1569
1371
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1372
|
+
return serverInstance;
|
|
1373
|
+
} else {
|
|
1374
|
+
try {
|
|
1375
|
+
serverInstance.close();
|
|
1376
|
+
} catch {
|
|
1377
|
+
// ignore
|
|
1378
|
+
}
|
|
1379
|
+
throw new Error('Server started but health check failed (timeout)');
|
|
1380
|
+
}
|
|
1381
|
+
} catch (error) {
|
|
1573
1382
|
lastOpenCodeError = error.message;
|
|
1574
1383
|
openCodePort = null;
|
|
1575
1384
|
syncToHmrState();
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
});
|
|
1579
|
-
|
|
1580
|
-
if (startupError) {
|
|
1581
|
-
if (startupError.code === 'ENOENT') {
|
|
1582
|
-
const enhanced = new Error(
|
|
1583
|
-
`Failed to start OpenCode – executable "${command}" not found. ` +
|
|
1584
|
-
'Set OPENCODE_BINARY to the full path of the opencode CLI or ensure it is on PATH.'
|
|
1585
|
-
);
|
|
1586
|
-
enhanced.code = startupError.code;
|
|
1587
|
-
startupError = enhanced;
|
|
1588
|
-
}
|
|
1589
|
-
throw startupError;
|
|
1385
|
+
console.error(`Failed to start OpenCode: ${error.message}`);
|
|
1386
|
+
throw error;
|
|
1590
1387
|
}
|
|
1591
|
-
|
|
1592
|
-
child.on('exit', (code, signal) => {
|
|
1593
|
-
lastOpenCodeError = `OpenCode exited with code ${code}, signal ${signal ?? 'null'}`;
|
|
1594
|
-
isOpenCodeReady = false;
|
|
1595
|
-
openCodeNotReadySince = Date.now();
|
|
1596
|
-
|
|
1597
|
-
if (!isShuttingDown && !isRestartingOpenCode) {
|
|
1598
|
-
console.log(`OpenCode process exited with code ${code}, signal ${signal}`);
|
|
1599
|
-
|
|
1600
|
-
setTimeout(() => {
|
|
1601
|
-
restartOpenCode().catch((err) => {
|
|
1602
|
-
console.error('Failed to restart OpenCode after exit:', err);
|
|
1603
|
-
});
|
|
1604
|
-
}, 5000);
|
|
1605
|
-
} else if (isRestartingOpenCode) {
|
|
1606
|
-
console.log('OpenCode exit during controlled restart, not triggering auto-restart');
|
|
1607
|
-
}
|
|
1608
|
-
});
|
|
1609
|
-
|
|
1610
|
-
child.on('error', (error) => {
|
|
1611
|
-
lastOpenCodeError = error.message;
|
|
1612
|
-
isOpenCodeReady = false;
|
|
1613
|
-
openCodeNotReadySince = Date.now();
|
|
1614
|
-
console.error(`OpenCode process error: ${error.message}`);
|
|
1615
|
-
if (!isShuttingDown) {
|
|
1616
|
-
|
|
1617
|
-
setTimeout(() => {
|
|
1618
|
-
restartOpenCode().catch((err) => {
|
|
1619
|
-
console.error('Failed to restart OpenCode after error:', err);
|
|
1620
|
-
});
|
|
1621
|
-
}, 5000);
|
|
1622
|
-
}
|
|
1623
|
-
});
|
|
1624
|
-
|
|
1625
|
-
await firstSignalPromise;
|
|
1626
|
-
|
|
1627
|
-
return child;
|
|
1628
1388
|
}
|
|
1629
1389
|
|
|
1630
1390
|
async function restartOpenCode() {
|
|
@@ -1641,60 +1401,16 @@ async function restartOpenCode() {
|
|
|
1641
1401
|
console.log('Restarting OpenCode process...');
|
|
1642
1402
|
|
|
1643
1403
|
if (openCodeProcess) {
|
|
1644
|
-
console.log('
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
processToTerminate.kill('SIGTERM');
|
|
1650
|
-
|
|
1651
|
-
await new Promise((resolve) => {
|
|
1652
|
-
let resolved = false;
|
|
1653
|
-
|
|
1654
|
-
const cleanup = () => {
|
|
1655
|
-
processToTerminate.off('exit', onExit);
|
|
1656
|
-
clearTimeout(forceKillTimer);
|
|
1657
|
-
clearTimeout(hardStopTimer);
|
|
1658
|
-
if (!resolved) {
|
|
1659
|
-
resolved = true;
|
|
1660
|
-
resolve();
|
|
1661
|
-
}
|
|
1662
|
-
};
|
|
1663
|
-
|
|
1664
|
-
const onExit = () => {
|
|
1665
|
-
cleanup();
|
|
1666
|
-
};
|
|
1667
|
-
|
|
1668
|
-
const forceKillTimer = setTimeout(() => {
|
|
1669
|
-
if (resolved) {
|
|
1670
|
-
return;
|
|
1671
|
-
}
|
|
1672
|
-
forcedTermination = true;
|
|
1673
|
-
console.warn('OpenCode process did not exit after SIGTERM, sending SIGKILL');
|
|
1674
|
-
processToTerminate.kill('SIGKILL');
|
|
1675
|
-
}, 3000);
|
|
1676
|
-
|
|
1677
|
-
const hardStopTimer = setTimeout(() => {
|
|
1678
|
-
if (resolved) {
|
|
1679
|
-
return;
|
|
1680
|
-
}
|
|
1681
|
-
console.warn('OpenCode process unresponsive after SIGKILL, continuing restart');
|
|
1682
|
-
cleanup();
|
|
1683
|
-
}, 5000);
|
|
1684
|
-
|
|
1685
|
-
processToTerminate.once('exit', onExit);
|
|
1686
|
-
});
|
|
1687
|
-
|
|
1688
|
-
if (forcedTermination) {
|
|
1689
|
-
console.log('OpenCode process terminated forcefully during restart');
|
|
1690
|
-
}
|
|
1691
|
-
} else {
|
|
1692
|
-
console.log('OpenCode process already stopped before restart command');
|
|
1404
|
+
console.log('Stopping existing OpenCode process...');
|
|
1405
|
+
try {
|
|
1406
|
+
openCodeProcess.close();
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
console.warn('Error closing OpenCode process:', error);
|
|
1693
1409
|
}
|
|
1694
|
-
|
|
1695
1410
|
openCodeProcess = null;
|
|
1696
1411
|
syncToHmrState();
|
|
1697
|
-
|
|
1412
|
+
|
|
1413
|
+
// Brief delay to allow port release
|
|
1698
1414
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
1699
1415
|
}
|
|
1700
1416
|
|
|
@@ -1705,6 +1421,8 @@ async function restartOpenCode() {
|
|
|
1705
1421
|
openCodePort = null;
|
|
1706
1422
|
syncToHmrState();
|
|
1707
1423
|
}
|
|
1424
|
+
|
|
1425
|
+
// Reset detection state
|
|
1708
1426
|
openCodeApiPrefixDetected = false;
|
|
1709
1427
|
if (openCodeApiDetectionTimer) {
|
|
1710
1428
|
clearTimeout(openCodeApiDetectionTimer);
|
|
@@ -1716,13 +1434,10 @@ async function restartOpenCode() {
|
|
|
1716
1434
|
openCodeProcess = await startOpenCode();
|
|
1717
1435
|
syncToHmrState();
|
|
1718
1436
|
|
|
1719
|
-
if (!ENV_CONFIGURED_OPENCODE_PORT) {
|
|
1720
|
-
await waitForOpenCodePort();
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
1437
|
if (expressApp) {
|
|
1724
1438
|
setupProxy(expressApp);
|
|
1725
|
-
|
|
1439
|
+
// Ensure prefix is set correctly (SDK usually handles this, but just in case)
|
|
1440
|
+
ensureOpenCodeApiPrefix();
|
|
1726
1441
|
}
|
|
1727
1442
|
})();
|
|
1728
1443
|
|