@hua-labs/tap 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -194
- package/dist/bridges/codex-app-server-auth-gateway.mjs +11 -3
- package/dist/bridges/codex-app-server-auth-gateway.mjs.map +1 -1
- package/dist/bridges/codex-app-server-bridge.d.mts +11 -1
- package/dist/bridges/codex-app-server-bridge.mjs +109 -50
- package/dist/bridges/codex-app-server-bridge.mjs.map +1 -1
- package/dist/bridges/codex-bridge-runner.mjs +23 -17
- package/dist/bridges/codex-bridge-runner.mjs.map +1 -1
- package/dist/cli.mjs +1063 -237
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +446 -140
- package/dist/index.mjs.map +1 -1
- package/package.json +65 -65
package/dist/index.mjs
CHANGED
|
@@ -27,8 +27,8 @@ function findRepoRoot(startDir = process.cwd()) {
|
|
|
27
27
|
if (fs.existsSync(path.join(dir, "package.json"))) {
|
|
28
28
|
if (!_noGitWarned) {
|
|
29
29
|
_setNoGitWarned();
|
|
30
|
-
|
|
31
|
-
"No .git directory found. Resolved
|
|
30
|
+
log(
|
|
31
|
+
"No .git directory found. Resolved tap root via package.json. That's fine outside git; use --comms-dir to choose a different comms location."
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
return dir;
|
|
@@ -39,8 +39,8 @@ function findRepoRoot(startDir = process.cwd()) {
|
|
|
39
39
|
}
|
|
40
40
|
if (!_noGitWarned) {
|
|
41
41
|
_setNoGitWarned();
|
|
42
|
-
|
|
43
|
-
"No git repository or package.json found. Using current directory as root.
|
|
42
|
+
log(
|
|
43
|
+
"No git repository or package.json found. Using the current directory as tap root. That's fine outside git; use --comms-dir to choose a different comms location."
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
46
|
return process.cwd();
|
|
@@ -82,9 +82,6 @@ function log(message) {
|
|
|
82
82
|
function logSuccess(message) {
|
|
83
83
|
if (!_jsonMode) console.log(` + ${message}`);
|
|
84
84
|
}
|
|
85
|
-
function logWarn(message) {
|
|
86
|
-
if (!_jsonMode) console.log(` ! ${message}`);
|
|
87
|
-
}
|
|
88
85
|
function logError(message) {
|
|
89
86
|
if (!_jsonMode) console.error(` x ${message}`);
|
|
90
87
|
}
|
|
@@ -93,6 +90,16 @@ function logHeader(message) {
|
|
|
93
90
|
${message}
|
|
94
91
|
`);
|
|
95
92
|
}
|
|
93
|
+
function parseIntFlag(value, name, min, max) {
|
|
94
|
+
if (value === void 0) return void 0;
|
|
95
|
+
const parsed = Number(value);
|
|
96
|
+
if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
|
|
97
|
+
throw new RangeError(
|
|
98
|
+
`Invalid ${name}: ${value}. Must be an integer between ${min} and ${max}.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return parsed;
|
|
102
|
+
}
|
|
96
103
|
function resolveInstanceId(identifier, state) {
|
|
97
104
|
if (state.instances[identifier]) {
|
|
98
105
|
return { ok: true, instanceId: identifier };
|
|
@@ -140,8 +147,8 @@ function findRepoRoot2(startDir = process.cwd()) {
|
|
|
140
147
|
if (fs2.existsSync(path2.join(dir, "package.json"))) {
|
|
141
148
|
if (!_noGitWarned) {
|
|
142
149
|
_setNoGitWarned();
|
|
143
|
-
|
|
144
|
-
"
|
|
150
|
+
log(
|
|
151
|
+
"No .git directory found. Resolved tap root via package.json. That's fine outside git; use --comms-dir to choose a different comms location."
|
|
145
152
|
);
|
|
146
153
|
}
|
|
147
154
|
return dir;
|
|
@@ -152,8 +159,8 @@ function findRepoRoot2(startDir = process.cwd()) {
|
|
|
152
159
|
}
|
|
153
160
|
if (!_noGitWarned) {
|
|
154
161
|
_setNoGitWarned();
|
|
155
|
-
|
|
156
|
-
"
|
|
162
|
+
log(
|
|
163
|
+
"No git repository or package.json found. Using the current directory as tap root. That's fine outside git; use --comms-dir to choose a different comms location."
|
|
157
164
|
);
|
|
158
165
|
}
|
|
159
166
|
return process.cwd();
|
|
@@ -551,7 +558,7 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
551
558
|
}
|
|
552
559
|
if (!sourcePath) {
|
|
553
560
|
issues.push(
|
|
554
|
-
"tap
|
|
561
|
+
"tap MCP server entry not found. Reinstall @hua-labs/tap or run from a repo with packages/tap-plugin/channels/ available."
|
|
555
562
|
);
|
|
556
563
|
return { command: null, args: [], env, sourcePath, warnings, issues };
|
|
557
564
|
}
|
|
@@ -581,7 +588,7 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
581
588
|
}
|
|
582
589
|
if (!command) {
|
|
583
590
|
issues.push(
|
|
584
|
-
"bun is required to run the repo-local tap
|
|
591
|
+
"bun is required to run the repo-local tap MCP server (.ts source). Install bun: https://bun.sh"
|
|
585
592
|
);
|
|
586
593
|
return { command: null, args: [], env, sourcePath, warnings, issues };
|
|
587
594
|
}
|
|
@@ -775,6 +782,7 @@ var init_runtime = __esm({
|
|
|
775
782
|
// src/engine/bridge.ts
|
|
776
783
|
import * as fs7 from "fs";
|
|
777
784
|
import * as net from "net";
|
|
785
|
+
import * as os2 from "os";
|
|
778
786
|
import * as path7 from "path";
|
|
779
787
|
import { randomBytes } from "crypto";
|
|
780
788
|
import { spawn, spawnSync as spawnSync2, execSync as execSync2 } from "child_process";
|
|
@@ -815,12 +823,66 @@ function removeFileIfExists(filePath) {
|
|
|
815
823
|
} catch {
|
|
816
824
|
}
|
|
817
825
|
}
|
|
826
|
+
function toPowerShellSingleQuotedString(value) {
|
|
827
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
828
|
+
}
|
|
829
|
+
function toPowerShellStringArrayLiteral(values) {
|
|
830
|
+
return `@(${values.map(toPowerShellSingleQuotedString).join(", ")})`;
|
|
831
|
+
}
|
|
832
|
+
function cleanupStaleWindowsSpawnWrappers(now = Date.now()) {
|
|
833
|
+
let entries;
|
|
834
|
+
try {
|
|
835
|
+
entries = fs7.readdirSync(os2.tmpdir());
|
|
836
|
+
} catch {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
for (const entry of entries) {
|
|
840
|
+
if (!entry.startsWith(WINDOWS_SPAWN_WRAPPER_PREFIX) || !/\.(cmd|ps1)$/i.test(entry)) {
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
const wrapperPath = path7.join(os2.tmpdir(), entry);
|
|
844
|
+
try {
|
|
845
|
+
const stats = fs7.statSync(wrapperPath);
|
|
846
|
+
if (now - stats.mtimeMs < WINDOWS_SPAWN_WRAPPER_STALE_MS) {
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
fs7.unlinkSync(wrapperPath);
|
|
850
|
+
} catch {
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
function buildWindowsDetachedWrapperScript(command, args, logPath, stderrLogPath, env) {
|
|
855
|
+
const lines = ["$ErrorActionPreference = 'Stop'"];
|
|
856
|
+
for (const [key, value] of Object.entries(env)) {
|
|
857
|
+
if (value !== void 0 && value !== process.env[key]) {
|
|
858
|
+
lines.push(
|
|
859
|
+
`[Environment]::SetEnvironmentVariable(${toPowerShellSingleQuotedString(key)}, ${toPowerShellSingleQuotedString(value)}, 'Process')`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
lines.push(
|
|
864
|
+
`$logPath = ${toPowerShellSingleQuotedString(logPath)}`,
|
|
865
|
+
`$stderrLogPath = ${toPowerShellSingleQuotedString(stderrLogPath)}`,
|
|
866
|
+
`$commandPath = ${toPowerShellSingleQuotedString(command)}`,
|
|
867
|
+
`$commandArgs = ${toPowerShellStringArrayLiteral(args)}`,
|
|
868
|
+
"$exitCode = 1",
|
|
869
|
+
"try {",
|
|
870
|
+
" & $commandPath @commandArgs >> $logPath 2>> $stderrLogPath",
|
|
871
|
+
" $exitCode = if ($null -ne $LASTEXITCODE) { $LASTEXITCODE } else { 0 }",
|
|
872
|
+
"} finally {",
|
|
873
|
+
" Remove-Item -LiteralPath $PSCommandPath -Force -ErrorAction SilentlyContinue",
|
|
874
|
+
"}",
|
|
875
|
+
"exit $exitCode"
|
|
876
|
+
);
|
|
877
|
+
return `${lines.join("\r\n")}\r
|
|
878
|
+
`;
|
|
879
|
+
}
|
|
818
880
|
function getWebSocketCtor() {
|
|
819
881
|
const candidate = globalThis.WebSocket;
|
|
820
882
|
return typeof candidate === "function" ? candidate : null;
|
|
821
883
|
}
|
|
822
884
|
function delay(ms) {
|
|
823
|
-
return new Promise((
|
|
885
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
824
886
|
}
|
|
825
887
|
function isLoopbackHost(hostname) {
|
|
826
888
|
return hostname === "127.0.0.1" || hostname === "localhost";
|
|
@@ -838,8 +900,11 @@ function resolvePowerShellCommand() {
|
|
|
838
900
|
function resolveAuthGatewayScript(repoRoot) {
|
|
839
901
|
const moduleDir = path7.dirname(fileURLToPath3(import.meta.url));
|
|
840
902
|
const candidates = [
|
|
841
|
-
|
|
842
|
-
path7.join(moduleDir, "
|
|
903
|
+
// Bundled: dist/bridges/ sibling (npm install / built package)
|
|
904
|
+
path7.join(moduleDir, "bridges", "codex-app-server-auth-gateway.mjs"),
|
|
905
|
+
// Source: src/bridges/ sibling (monorepo dev with ts runner)
|
|
906
|
+
path7.join(moduleDir, "bridges", "codex-app-server-auth-gateway.ts"),
|
|
907
|
+
// Monorepo dist fallback
|
|
843
908
|
path7.join(
|
|
844
909
|
repoRoot,
|
|
845
910
|
"packages",
|
|
@@ -869,7 +934,7 @@ function getBridgeRuntimeStateDir(repoRoot, instanceId) {
|
|
|
869
934
|
}
|
|
870
935
|
async function allocateLoopbackPort(hostname) {
|
|
871
936
|
const bindHost = hostname === "localhost" ? "127.0.0.1" : hostname;
|
|
872
|
-
return await new Promise((
|
|
937
|
+
return await new Promise((resolve9, reject) => {
|
|
873
938
|
const server = net.createServer();
|
|
874
939
|
server.unref();
|
|
875
940
|
server.once("error", reject);
|
|
@@ -887,15 +952,13 @@ async function allocateLoopbackPort(hostname) {
|
|
|
887
952
|
reject(error);
|
|
888
953
|
return;
|
|
889
954
|
}
|
|
890
|
-
|
|
955
|
+
resolve9(port);
|
|
891
956
|
});
|
|
892
957
|
});
|
|
893
958
|
});
|
|
894
959
|
}
|
|
895
|
-
function buildProtectedAppServerUrl(publicUrl,
|
|
896
|
-
|
|
897
|
-
url.searchParams.set(APP_SERVER_AUTH_QUERY_PARAM, token);
|
|
898
|
-
return url.toString().replace(/\/(?=\?|$)/, "");
|
|
960
|
+
function buildProtectedAppServerUrl(publicUrl, _token) {
|
|
961
|
+
return publicUrl;
|
|
899
962
|
}
|
|
900
963
|
function readGatewayTokenFromPath(tokenPath) {
|
|
901
964
|
return fs7.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -1010,7 +1073,7 @@ async function createManagedAppServerAuth(options) {
|
|
|
1010
1073
|
throw new Error("Failed to spawn app-server auth gateway");
|
|
1011
1074
|
}
|
|
1012
1075
|
return {
|
|
1013
|
-
mode: "
|
|
1076
|
+
mode: "subprotocol",
|
|
1014
1077
|
protectedUrl,
|
|
1015
1078
|
upstreamUrl: upstreamUrl.toString().replace(/\/$/, ""),
|
|
1016
1079
|
tokenPath,
|
|
@@ -1072,35 +1135,50 @@ function findReusableManagedAppServer(stateDir, publicUrl) {
|
|
|
1072
1135
|
return null;
|
|
1073
1136
|
}
|
|
1074
1137
|
function startWindowsDetachedProcess(command, args, repoRoot, logPath, env = process.env) {
|
|
1075
|
-
const ext = path7.extname(command).toLowerCase();
|
|
1076
1138
|
const stderrLogPath = stderrLogFilePath(logPath);
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1139
|
+
const powerShellCommand = resolvePowerShellCommand();
|
|
1140
|
+
cleanupStaleWindowsSpawnWrappers();
|
|
1141
|
+
const wrapperPath = path7.join(
|
|
1142
|
+
os2.tmpdir(),
|
|
1143
|
+
`${WINDOWS_SPAWN_WRAPPER_PREFIX}${randomBytes(4).toString("hex")}.ps1`
|
|
1144
|
+
);
|
|
1145
|
+
fs7.writeFileSync(
|
|
1146
|
+
wrapperPath,
|
|
1147
|
+
buildWindowsDetachedWrapperScript(
|
|
1148
|
+
command,
|
|
1149
|
+
args,
|
|
1150
|
+
logPath,
|
|
1151
|
+
stderrLogPath,
|
|
1152
|
+
env
|
|
1153
|
+
)
|
|
1154
|
+
);
|
|
1155
|
+
const psCommand = [
|
|
1156
|
+
"$p = Start-Process",
|
|
1157
|
+
`-FilePath ${toPowerShellSingleQuotedString(powerShellCommand)}`,
|
|
1158
|
+
`-ArgumentList ${toPowerShellStringArrayLiteral(["-NoLogo", "-NoProfile", "-File", wrapperPath])}`,
|
|
1159
|
+
`-WorkingDirectory ${toPowerShellSingleQuotedString(repoRoot)}`,
|
|
1160
|
+
"-WindowStyle Hidden",
|
|
1161
|
+
"-PassThru",
|
|
1162
|
+
"; Write-Output $p.Id"
|
|
1163
|
+
].join(" ");
|
|
1164
|
+
const result = spawnSync2(
|
|
1165
|
+
powerShellCommand,
|
|
1166
|
+
["-NoLogo", "-NoProfile", "-Command", psCommand],
|
|
1167
|
+
{
|
|
1168
|
+
encoding: "utf-8",
|
|
1169
|
+
windowsHide: true
|
|
1170
|
+
}
|
|
1171
|
+
);
|
|
1172
|
+
if (result.status !== 0) {
|
|
1173
|
+
removeFileIfExists(wrapperPath);
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
const pid = parseInt(result.stdout.trim(), 10);
|
|
1177
|
+
if (!Number.isFinite(pid)) {
|
|
1178
|
+
removeFileIfExists(wrapperPath);
|
|
1179
|
+
return null;
|
|
1103
1180
|
}
|
|
1181
|
+
return pid;
|
|
1104
1182
|
}
|
|
1105
1183
|
function startWindowsCodexAppServer(command, url, repoRoot, logPath) {
|
|
1106
1184
|
return startWindowsDetachedProcess(
|
|
@@ -1162,12 +1240,12 @@ function resolveAppServerUrl(baseUrl, port) {
|
|
|
1162
1240
|
}
|
|
1163
1241
|
async function isTcpPortAvailable(hostname, port) {
|
|
1164
1242
|
const bindHost = hostname === "localhost" ? "127.0.0.1" : hostname;
|
|
1165
|
-
return await new Promise((
|
|
1243
|
+
return await new Promise((resolve9) => {
|
|
1166
1244
|
const server = net.createServer();
|
|
1167
1245
|
server.unref();
|
|
1168
|
-
server.once("error", () =>
|
|
1246
|
+
server.once("error", () => resolve9(false));
|
|
1169
1247
|
server.listen(port, bindHost, () => {
|
|
1170
|
-
server.close((error) =>
|
|
1248
|
+
server.close((error) => resolve9(!error));
|
|
1171
1249
|
});
|
|
1172
1250
|
});
|
|
1173
1251
|
}
|
|
@@ -1197,12 +1275,12 @@ async function findNextAvailableAppServerPort(state, baseUrl, basePort = 4501, e
|
|
|
1197
1275
|
`Failed to find a free app-server port starting at ${basePort}`
|
|
1198
1276
|
);
|
|
1199
1277
|
}
|
|
1200
|
-
async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_MS) {
|
|
1278
|
+
async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_MS, gatewayToken) {
|
|
1201
1279
|
const WebSocket = getWebSocketCtor();
|
|
1202
1280
|
if (!WebSocket) {
|
|
1203
1281
|
return false;
|
|
1204
1282
|
}
|
|
1205
|
-
return new Promise((
|
|
1283
|
+
return new Promise((resolve9) => {
|
|
1206
1284
|
let settled = false;
|
|
1207
1285
|
let socket = null;
|
|
1208
1286
|
const finish = (healthy) => {
|
|
@@ -1215,11 +1293,12 @@ async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_M
|
|
|
1215
1293
|
socket?.close();
|
|
1216
1294
|
} catch {
|
|
1217
1295
|
}
|
|
1218
|
-
|
|
1296
|
+
resolve9(healthy);
|
|
1219
1297
|
};
|
|
1220
1298
|
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
1221
1299
|
try {
|
|
1222
|
-
|
|
1300
|
+
const protocols = gatewayToken ? [`${AUTH_SUBPROTOCOL_PREFIX}${gatewayToken}`] : void 0;
|
|
1301
|
+
socket = new WebSocket(url, protocols);
|
|
1223
1302
|
socket.addEventListener("open", () => finish(true), { once: true });
|
|
1224
1303
|
socket.addEventListener("error", () => finish(false), { once: true });
|
|
1225
1304
|
socket.addEventListener("close", () => finish(false), { once: true });
|
|
@@ -1228,10 +1307,14 @@ async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_M
|
|
|
1228
1307
|
}
|
|
1229
1308
|
});
|
|
1230
1309
|
}
|
|
1231
|
-
async function waitForAppServerHealth(url, timeoutMs) {
|
|
1310
|
+
async function waitForAppServerHealth(url, timeoutMs, gatewayToken) {
|
|
1232
1311
|
const deadline = Date.now() + timeoutMs;
|
|
1233
1312
|
while (Date.now() < deadline) {
|
|
1234
|
-
if (await checkAppServerHealth(
|
|
1313
|
+
if (await checkAppServerHealth(
|
|
1314
|
+
url,
|
|
1315
|
+
APP_SERVER_HEALTH_TIMEOUT_MS,
|
|
1316
|
+
gatewayToken
|
|
1317
|
+
)) {
|
|
1235
1318
|
return true;
|
|
1236
1319
|
}
|
|
1237
1320
|
await delay(APP_SERVER_HEALTH_RETRY_MS);
|
|
@@ -1496,8 +1579,9 @@ Or start it manually:
|
|
|
1496
1579
|
throw new Error("Tap auth gateway token is missing after startup.");
|
|
1497
1580
|
}
|
|
1498
1581
|
const gatewayHealthy = await waitForAppServerHealth(
|
|
1499
|
-
|
|
1500
|
-
APP_SERVER_GATEWAY_START_TIMEOUT_MS
|
|
1582
|
+
effectiveUrl,
|
|
1583
|
+
APP_SERVER_GATEWAY_START_TIMEOUT_MS,
|
|
1584
|
+
gatewayToken
|
|
1501
1585
|
);
|
|
1502
1586
|
if (!gatewayHealthy) {
|
|
1503
1587
|
await terminateProcess(pid, options.platform);
|
|
@@ -1533,7 +1617,11 @@ function logFilePath(stateDir, instanceId) {
|
|
|
1533
1617
|
function runtimeHeartbeatFilePath(runtimeStateDir) {
|
|
1534
1618
|
return path7.join(runtimeStateDir, "heartbeat.json");
|
|
1535
1619
|
}
|
|
1536
|
-
function
|
|
1620
|
+
function runtimeThreadStateFilePath(runtimeStateDir) {
|
|
1621
|
+
return path7.join(runtimeStateDir, "thread.json");
|
|
1622
|
+
}
|
|
1623
|
+
function loadRuntimeBridgeHeartbeat(bridgeState) {
|
|
1624
|
+
const runtimeStateDir = bridgeState?.runtimeStateDir;
|
|
1537
1625
|
if (!runtimeStateDir) {
|
|
1538
1626
|
return null;
|
|
1539
1627
|
}
|
|
@@ -1542,13 +1630,35 @@ function loadRuntimeHeartbeatTimestamp(runtimeStateDir) {
|
|
|
1542
1630
|
return null;
|
|
1543
1631
|
}
|
|
1544
1632
|
try {
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1633
|
+
return JSON.parse(
|
|
1634
|
+
fs7.readFileSync(heartbeatPath, "utf-8")
|
|
1635
|
+
);
|
|
1548
1636
|
} catch {
|
|
1549
1637
|
return null;
|
|
1550
1638
|
}
|
|
1551
1639
|
}
|
|
1640
|
+
function loadRuntimeBridgeThreadState(bridgeState) {
|
|
1641
|
+
const runtimeStateDir = bridgeState?.runtimeStateDir;
|
|
1642
|
+
if (!runtimeStateDir) {
|
|
1643
|
+
return null;
|
|
1644
|
+
}
|
|
1645
|
+
const threadPath = runtimeThreadStateFilePath(runtimeStateDir);
|
|
1646
|
+
if (!fs7.existsSync(threadPath)) {
|
|
1647
|
+
return null;
|
|
1648
|
+
}
|
|
1649
|
+
try {
|
|
1650
|
+
const parsed = JSON.parse(
|
|
1651
|
+
fs7.readFileSync(threadPath, "utf-8")
|
|
1652
|
+
);
|
|
1653
|
+
return parsed.threadId ? parsed : null;
|
|
1654
|
+
} catch {
|
|
1655
|
+
return null;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
function loadRuntimeHeartbeatTimestamp(runtimeStateDir) {
|
|
1659
|
+
const heartbeat = loadRuntimeBridgeHeartbeat({ runtimeStateDir });
|
|
1660
|
+
return typeof heartbeat?.updatedAt === "string" ? heartbeat.updatedAt : null;
|
|
1661
|
+
}
|
|
1552
1662
|
function resolveHeartbeatTimestamp(state) {
|
|
1553
1663
|
return loadRuntimeHeartbeatTimestamp(state?.runtimeStateDir) ?? state?.lastHeartbeat ?? null;
|
|
1554
1664
|
}
|
|
@@ -1718,6 +1828,7 @@ async function startBridge(options) {
|
|
|
1718
1828
|
options.messageLookbackMinutes
|
|
1719
1829
|
)
|
|
1720
1830
|
} : {},
|
|
1831
|
+
...process.env.TAP_COLD_START_WARMUP === "true" ? { TAP_COLD_START_WARMUP: "true" } : {},
|
|
1721
1832
|
...options.threadId ? { TAP_THREAD_ID: options.threadId } : {},
|
|
1722
1833
|
...options.ephemeral ? { TAP_EPHEMERAL: "true" } : {},
|
|
1723
1834
|
...options.processExistingMessages ? { TAP_PROCESS_EXISTING: "true" } : {}
|
|
@@ -1854,7 +1965,7 @@ function getBridgeStatus(stateDir, instanceId) {
|
|
|
1854
1965
|
}
|
|
1855
1966
|
return "running";
|
|
1856
1967
|
}
|
|
1857
|
-
var DEFAULT_APP_SERVER_URL2, APP_SERVER_HEALTH_TIMEOUT_MS, APP_SERVER_START_TIMEOUT_MS, APP_SERVER_GATEWAY_START_TIMEOUT_MS, APP_SERVER_HEALTH_RETRY_MS,
|
|
1968
|
+
var DEFAULT_APP_SERVER_URL2, APP_SERVER_HEALTH_TIMEOUT_MS, APP_SERVER_START_TIMEOUT_MS, APP_SERVER_GATEWAY_START_TIMEOUT_MS, APP_SERVER_HEALTH_RETRY_MS, AUTH_SUBPROTOCOL_PREFIX, APP_SERVER_AUTH_FILE_MODE, WINDOWS_SPAWN_WRAPPER_PREFIX, WINDOWS_SPAWN_WRAPPER_STALE_MS;
|
|
1858
1969
|
var init_bridge = __esm({
|
|
1859
1970
|
"src/engine/bridge.ts"() {
|
|
1860
1971
|
"use strict";
|
|
@@ -1866,8 +1977,10 @@ var init_bridge = __esm({
|
|
|
1866
1977
|
APP_SERVER_START_TIMEOUT_MS = 2e4;
|
|
1867
1978
|
APP_SERVER_GATEWAY_START_TIMEOUT_MS = 5e3;
|
|
1868
1979
|
APP_SERVER_HEALTH_RETRY_MS = 250;
|
|
1869
|
-
|
|
1980
|
+
AUTH_SUBPROTOCOL_PREFIX = "tap-auth-";
|
|
1870
1981
|
APP_SERVER_AUTH_FILE_MODE = 384;
|
|
1982
|
+
WINDOWS_SPAWN_WRAPPER_PREFIX = "tap-spawn-";
|
|
1983
|
+
WINDOWS_SPAWN_WRAPPER_STALE_MS = 60 * 60 * 1e3;
|
|
1871
1984
|
}
|
|
1872
1985
|
});
|
|
1873
1986
|
|
|
@@ -2068,13 +2181,14 @@ function setNestedKey(obj, keyPath, value) {
|
|
|
2068
2181
|
function normalizeTapCommsDir(value) {
|
|
2069
2182
|
return typeof value === "string" ? path9.resolve(value).replace(/\\/g, "/") : "";
|
|
2070
2183
|
}
|
|
2071
|
-
var MCP_SERVER_KEY, claudeAdapter;
|
|
2184
|
+
var MCP_SERVER_KEY, OLD_MCP_SERVER_KEY, claudeAdapter;
|
|
2072
2185
|
var init_claude = __esm({
|
|
2073
2186
|
"src/adapters/claude.ts"() {
|
|
2074
2187
|
"use strict";
|
|
2075
2188
|
init_state();
|
|
2076
2189
|
init_common();
|
|
2077
|
-
MCP_SERVER_KEY = "tap
|
|
2190
|
+
MCP_SERVER_KEY = "tap";
|
|
2191
|
+
OLD_MCP_SERVER_KEY = "tap-comms";
|
|
2078
2192
|
claudeAdapter = {
|
|
2079
2193
|
runtime: "claude",
|
|
2080
2194
|
async probe(ctx) {
|
|
@@ -2131,6 +2245,11 @@ var init_claude = __esm({
|
|
|
2131
2245
|
`Existing "${MCP_SERVER_KEY}" entry in .mcp.json will be overwritten.`
|
|
2132
2246
|
);
|
|
2133
2247
|
}
|
|
2248
|
+
if (config.mcpServers?.[OLD_MCP_SERVER_KEY]) {
|
|
2249
|
+
conflicts.push(
|
|
2250
|
+
`Legacy "${OLD_MCP_SERVER_KEY}" entry will be migrated to "${MCP_SERVER_KEY}".`
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2134
2253
|
} catch {
|
|
2135
2254
|
warnings.push(
|
|
2136
2255
|
".mcp.json exists but is not valid JSON. Will be overwritten."
|
|
@@ -2140,7 +2259,7 @@ var init_claude = __esm({
|
|
|
2140
2259
|
const serverEntry = buildMcpServerEntry(ctx);
|
|
2141
2260
|
if (!serverEntry) {
|
|
2142
2261
|
warnings.push(
|
|
2143
|
-
"tap
|
|
2262
|
+
"tap MCP server entry not found. Skipping .mcp.json patch. Reinstall @hua-labs/tap or run from a repo with packages/tap-plugin/channels/ available."
|
|
2144
2263
|
);
|
|
2145
2264
|
return {
|
|
2146
2265
|
runtime: "claude",
|
|
@@ -2193,6 +2312,10 @@ var init_claude = __esm({
|
|
|
2193
2312
|
);
|
|
2194
2313
|
}
|
|
2195
2314
|
}
|
|
2315
|
+
const servers = config.mcpServers;
|
|
2316
|
+
if (servers?.[OLD_MCP_SERVER_KEY]) {
|
|
2317
|
+
delete servers[OLD_MCP_SERVER_KEY];
|
|
2318
|
+
}
|
|
2196
2319
|
if (op.key) {
|
|
2197
2320
|
setNestedKey(config, op.key, op.value);
|
|
2198
2321
|
}
|
|
@@ -2241,7 +2364,7 @@ var init_claude = __esm({
|
|
|
2241
2364
|
checks.push({ name: "Config is valid JSON", passed: true });
|
|
2242
2365
|
const entry = config.mcpServers?.[MCP_SERVER_KEY];
|
|
2243
2366
|
checks.push({
|
|
2244
|
-
name: "tap
|
|
2367
|
+
name: "tap entry present",
|
|
2245
2368
|
passed: !!entry,
|
|
2246
2369
|
message: entry ? void 0 : `mcpServers.${MCP_SERVER_KEY} not found`
|
|
2247
2370
|
});
|
|
@@ -2350,6 +2473,14 @@ function extractTomlTable(content, selector) {
|
|
|
2350
2473
|
return `${lines.slice(range.start, range.end).join("\n")}
|
|
2351
2474
|
`;
|
|
2352
2475
|
}
|
|
2476
|
+
function removeTomlTable(content, selector) {
|
|
2477
|
+
const lines = splitLines(content);
|
|
2478
|
+
const range = findTableRange(lines, selector);
|
|
2479
|
+
if (!range) return content;
|
|
2480
|
+
const next = [...lines.slice(0, range.start), ...lines.slice(range.end)];
|
|
2481
|
+
return `${trimTomlDocument(next.join("\n"))}
|
|
2482
|
+
`;
|
|
2483
|
+
}
|
|
2353
2484
|
function replaceTomlTable(content, selector, replacement) {
|
|
2354
2485
|
const lines = splitLines(content);
|
|
2355
2486
|
const range = findTableRange(lines, selector);
|
|
@@ -2475,12 +2606,12 @@ function verifyManagedToml(content, ctx, configPath) {
|
|
|
2475
2606
|
message: fs11.existsSync(configPath) ? void 0 : `${configPath} not found`
|
|
2476
2607
|
});
|
|
2477
2608
|
checks.push({
|
|
2478
|
-
name: "tap
|
|
2609
|
+
name: "tap MCP table present",
|
|
2479
2610
|
passed: !!mainTable,
|
|
2480
2611
|
message: mainTable ? void 0 : `${MCP_SELECTOR} not found`
|
|
2481
2612
|
});
|
|
2482
2613
|
checks.push({
|
|
2483
|
-
name: "tap
|
|
2614
|
+
name: "tap env table present",
|
|
2484
2615
|
passed: !!envTable,
|
|
2485
2616
|
message: envTable ? void 0 : `${ENV_SELECTOR} not found`
|
|
2486
2617
|
});
|
|
@@ -2500,12 +2631,12 @@ function verifyManagedToml(content, ctx, configPath) {
|
|
|
2500
2631
|
passed: mainTable.includes(
|
|
2501
2632
|
`command = "${managed.command.replace(/\\/g, "\\\\")}"`
|
|
2502
2633
|
) && mainTable.includes(`args = [${expectedArgs}]`),
|
|
2503
|
-
message: "Managed tap
|
|
2634
|
+
message: "Managed tap command/args do not match expected values"
|
|
2504
2635
|
});
|
|
2505
2636
|
}
|
|
2506
2637
|
return checks;
|
|
2507
2638
|
}
|
|
2508
|
-
var MCP_SELECTOR, ENV_SELECTOR, codexAdapter;
|
|
2639
|
+
var MCP_SELECTOR, ENV_SELECTOR, OLD_MCP_SELECTOR, OLD_ENV_SELECTOR, codexAdapter;
|
|
2509
2640
|
var init_codex = __esm({
|
|
2510
2641
|
"src/adapters/codex.ts"() {
|
|
2511
2642
|
"use strict";
|
|
@@ -2513,8 +2644,10 @@ var init_codex = __esm({
|
|
|
2513
2644
|
init_artifact_backups();
|
|
2514
2645
|
init_toml();
|
|
2515
2646
|
init_common();
|
|
2516
|
-
MCP_SELECTOR = "mcp_servers.tap
|
|
2517
|
-
ENV_SELECTOR = "mcp_servers.tap
|
|
2647
|
+
MCP_SELECTOR = "mcp_servers.tap";
|
|
2648
|
+
ENV_SELECTOR = "mcp_servers.tap.env";
|
|
2649
|
+
OLD_MCP_SELECTOR = "mcp_servers.tap-comms";
|
|
2650
|
+
OLD_ENV_SELECTOR = "mcp_servers.tap-comms.env";
|
|
2518
2651
|
codexAdapter = {
|
|
2519
2652
|
runtime: "codex",
|
|
2520
2653
|
async probe(ctx) {
|
|
@@ -2560,6 +2693,11 @@ var init_codex = __esm({
|
|
|
2560
2693
|
if (extractTomlTable(content, MCP_SELECTOR)) {
|
|
2561
2694
|
conflicts.push(`Existing ${MCP_SELECTOR} table will be updated.`);
|
|
2562
2695
|
}
|
|
2696
|
+
if (extractTomlTable(content, OLD_MCP_SELECTOR)) {
|
|
2697
|
+
conflicts.push(
|
|
2698
|
+
`Legacy ${OLD_MCP_SELECTOR} table will be migrated to ${MCP_SELECTOR}.`
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2563
2701
|
if (extractTomlTable(content, ENV_SELECTOR)) {
|
|
2564
2702
|
conflicts.push(`Existing ${ENV_SELECTOR} table will be updated.`);
|
|
2565
2703
|
}
|
|
@@ -2625,6 +2763,12 @@ var init_codex = __esm({
|
|
|
2625
2763
|
return { ...artifact, backupPath };
|
|
2626
2764
|
});
|
|
2627
2765
|
let nextContent = existingContent;
|
|
2766
|
+
if (extractTomlTable(nextContent, OLD_ENV_SELECTOR)) {
|
|
2767
|
+
nextContent = removeTomlTable(nextContent, OLD_ENV_SELECTOR);
|
|
2768
|
+
}
|
|
2769
|
+
if (extractTomlTable(nextContent, OLD_MCP_SELECTOR)) {
|
|
2770
|
+
nextContent = removeTomlTable(nextContent, OLD_MCP_SELECTOR);
|
|
2771
|
+
}
|
|
2628
2772
|
nextContent = replaceTomlTable(
|
|
2629
2773
|
nextContent,
|
|
2630
2774
|
MCP_SELECTOR,
|
|
@@ -2801,7 +2945,7 @@ function verifyGeminiConfig(config, configPath, ctx) {
|
|
|
2801
2945
|
message: fs12.existsSync(configPath) ? void 0 : `${configPath} not found`
|
|
2802
2946
|
});
|
|
2803
2947
|
checks.push({
|
|
2804
|
-
name: "tap
|
|
2948
|
+
name: "tap entry present",
|
|
2805
2949
|
passed: !!entry,
|
|
2806
2950
|
message: entry ? void 0 : `${GEMINI_SELECTOR} not found`
|
|
2807
2951
|
});
|
|
@@ -2819,14 +2963,15 @@ function verifyGeminiConfig(config, configPath, ctx) {
|
|
|
2819
2963
|
}
|
|
2820
2964
|
return checks;
|
|
2821
2965
|
}
|
|
2822
|
-
var GEMINI_SELECTOR, geminiAdapter;
|
|
2966
|
+
var GEMINI_SELECTOR, OLD_GEMINI_SELECTOR, geminiAdapter;
|
|
2823
2967
|
var init_gemini = __esm({
|
|
2824
2968
|
"src/adapters/gemini.ts"() {
|
|
2825
2969
|
"use strict";
|
|
2826
2970
|
init_state();
|
|
2827
2971
|
init_artifact_backups();
|
|
2828
2972
|
init_common();
|
|
2829
|
-
GEMINI_SELECTOR = "mcpServers.tap
|
|
2973
|
+
GEMINI_SELECTOR = "mcpServers.tap";
|
|
2974
|
+
OLD_GEMINI_SELECTOR = "mcpServers.tap-comms";
|
|
2830
2975
|
geminiAdapter = {
|
|
2831
2976
|
runtime: "gemini",
|
|
2832
2977
|
async probe(ctx) {
|
|
@@ -2875,6 +3020,11 @@ var init_gemini = __esm({
|
|
|
2875
3020
|
if (readNestedKey(config, GEMINI_SELECTOR) !== void 0) {
|
|
2876
3021
|
conflicts.push(`Existing ${GEMINI_SELECTOR} entry will be updated.`);
|
|
2877
3022
|
}
|
|
3023
|
+
if (readNestedKey(config, OLD_GEMINI_SELECTOR) !== void 0) {
|
|
3024
|
+
conflicts.push(
|
|
3025
|
+
`Legacy ${OLD_GEMINI_SELECTOR} entry will be migrated to ${GEMINI_SELECTOR}.`
|
|
3026
|
+
);
|
|
3027
|
+
}
|
|
2878
3028
|
} catch {
|
|
2879
3029
|
warnings.push(
|
|
2880
3030
|
`${configPath} exists but is not valid JSON. It will be replaced.`
|
|
@@ -2942,6 +3092,13 @@ var init_gemini = __esm({
|
|
|
2942
3092
|
existed: previousValue !== void 0,
|
|
2943
3093
|
value: previousValue
|
|
2944
3094
|
});
|
|
3095
|
+
const oldValue = readNestedKey(config, OLD_GEMINI_SELECTOR);
|
|
3096
|
+
if (oldValue !== void 0) {
|
|
3097
|
+
const servers = config.mcpServers;
|
|
3098
|
+
if (servers) {
|
|
3099
|
+
delete servers["tap-comms"];
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
2945
3102
|
setNestedKey2(config, GEMINI_SELECTOR, {
|
|
2946
3103
|
command: managed.command,
|
|
2947
3104
|
args: managed.args,
|
|
@@ -3049,16 +3206,31 @@ function redactProtectedUrl(url) {
|
|
|
3049
3206
|
try {
|
|
3050
3207
|
const parsed = new URL(url);
|
|
3051
3208
|
if (parsed.searchParams.has("tap_token")) {
|
|
3052
|
-
parsed.searchParams.
|
|
3209
|
+
parsed.searchParams.delete("tap_token");
|
|
3053
3210
|
}
|
|
3054
3211
|
return parsed.toString().replace(/\/$/, "");
|
|
3055
3212
|
} catch {
|
|
3056
|
-
return url.replace(/tap_token=[^&]+/g, "
|
|
3213
|
+
return url.replace(/[?&]tap_token=[^&]+/g, "");
|
|
3057
3214
|
}
|
|
3058
3215
|
}
|
|
3059
3216
|
function loadCurrentBridgeState(stateDir, instanceId, fallback) {
|
|
3060
3217
|
return loadBridgeState(stateDir, instanceId) ?? fallback ?? null;
|
|
3061
3218
|
}
|
|
3219
|
+
function formatThreadSummary(threadId, cwd) {
|
|
3220
|
+
if (!threadId) {
|
|
3221
|
+
return "-";
|
|
3222
|
+
}
|
|
3223
|
+
return cwd ? `${threadId} (${cwd})` : threadId;
|
|
3224
|
+
}
|
|
3225
|
+
function normalizeComparablePath(value) {
|
|
3226
|
+
return path13.resolve(value).replace(/\\/g, "/").toLowerCase();
|
|
3227
|
+
}
|
|
3228
|
+
function sameOptionalPath(left, right) {
|
|
3229
|
+
if (!left || !right) {
|
|
3230
|
+
return left === right;
|
|
3231
|
+
}
|
|
3232
|
+
return normalizeComparablePath(left) === normalizeComparablePath(right);
|
|
3233
|
+
}
|
|
3062
3234
|
function getSharedAppServerUsers(state, stateDir, currentInstanceId, appServerUrl) {
|
|
3063
3235
|
const shared = [];
|
|
3064
3236
|
for (const [id, inst] of Object.entries(state.instances)) {
|
|
@@ -3136,37 +3308,37 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3136
3308
|
};
|
|
3137
3309
|
}
|
|
3138
3310
|
const instanceId = resolved.instanceId;
|
|
3139
|
-
let
|
|
3140
|
-
if (!
|
|
3311
|
+
let instance2 = state.instances[instanceId];
|
|
3312
|
+
if (!instance2?.installed) {
|
|
3141
3313
|
return {
|
|
3142
3314
|
ok: false,
|
|
3143
3315
|
command: "bridge",
|
|
3144
3316
|
instanceId,
|
|
3145
|
-
runtime:
|
|
3317
|
+
runtime: instance2?.runtime,
|
|
3146
3318
|
code: "TAP_INSTANCE_NOT_FOUND",
|
|
3147
|
-
message: `${instanceId} is not installed. Run: npx @hua-labs/tap add ${
|
|
3319
|
+
message: `${instanceId} is not installed. Run: npx @hua-labs/tap add ${instance2?.runtime ?? identifier}`,
|
|
3148
3320
|
warnings: [],
|
|
3149
3321
|
data: {}
|
|
3150
3322
|
};
|
|
3151
3323
|
}
|
|
3152
|
-
const adapter = getAdapter(
|
|
3324
|
+
const adapter = getAdapter(instance2.runtime);
|
|
3153
3325
|
const mode = adapter.bridgeMode();
|
|
3154
3326
|
if (mode !== "app-server") {
|
|
3155
3327
|
return {
|
|
3156
3328
|
ok: true,
|
|
3157
3329
|
command: "bridge",
|
|
3158
3330
|
instanceId,
|
|
3159
|
-
runtime:
|
|
3331
|
+
runtime: instance2.runtime,
|
|
3160
3332
|
code: "TAP_NO_OP",
|
|
3161
3333
|
message: `${instanceId} uses ${mode} mode \u2014 no bridge needed.`,
|
|
3162
3334
|
warnings: [],
|
|
3163
3335
|
data: { bridgeMode: mode }
|
|
3164
3336
|
};
|
|
3165
3337
|
}
|
|
3166
|
-
const resolvedAgentName = agentName ??
|
|
3167
|
-
if (agentName && agentName !==
|
|
3168
|
-
|
|
3169
|
-
const updatedState = updateInstanceState(state, instanceId,
|
|
3338
|
+
const resolvedAgentName = agentName ?? instance2.agentName ?? void 0;
|
|
3339
|
+
if (agentName && agentName !== instance2.agentName) {
|
|
3340
|
+
instance2 = { ...instance2, agentName };
|
|
3341
|
+
const updatedState = updateInstanceState(state, instanceId, instance2);
|
|
3170
3342
|
saveState(repoRoot, updatedState);
|
|
3171
3343
|
state = updatedState;
|
|
3172
3344
|
}
|
|
@@ -3177,7 +3349,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3177
3349
|
ok: false,
|
|
3178
3350
|
command: "bridge",
|
|
3179
3351
|
instanceId,
|
|
3180
|
-
runtime:
|
|
3352
|
+
runtime: instance2.runtime,
|
|
3181
3353
|
code: "TAP_BRIDGE_SCRIPT_MISSING",
|
|
3182
3354
|
message: `Bridge script not found for ${instanceId}. Ensure the runtime is properly configured.`,
|
|
3183
3355
|
warnings: [],
|
|
@@ -3186,8 +3358,8 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3186
3358
|
}
|
|
3187
3359
|
const { config: resolvedConfig } = resolveConfig({}, repoRoot);
|
|
3188
3360
|
const runtimeCommand = resolvedConfig.runtimeCommand;
|
|
3189
|
-
const manageAppServer =
|
|
3190
|
-
let effectivePort =
|
|
3361
|
+
const manageAppServer = instance2.runtime === "codex" && flags["no-server"] !== true;
|
|
3362
|
+
let effectivePort = instance2.port;
|
|
3191
3363
|
if (effectivePort == null && manageAppServer) {
|
|
3192
3364
|
effectivePort = await findNextAvailableAppServerPort(
|
|
3193
3365
|
state,
|
|
@@ -3195,8 +3367,8 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3195
3367
|
4501,
|
|
3196
3368
|
instanceId
|
|
3197
3369
|
);
|
|
3198
|
-
|
|
3199
|
-
const updatedState = updateInstanceState(state, instanceId,
|
|
3370
|
+
instance2 = { ...instance2, port: effectivePort };
|
|
3371
|
+
const updatedState = updateInstanceState(state, instanceId, instance2);
|
|
3200
3372
|
saveState(repoRoot, updatedState);
|
|
3201
3373
|
state = updatedState;
|
|
3202
3374
|
}
|
|
@@ -3212,19 +3384,19 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3212
3384
|
if (effectivePort != null) log(`Port: ${effectivePort}`);
|
|
3213
3385
|
if (resolvedAgentName) log(`Agent name: ${resolvedAgentName}`);
|
|
3214
3386
|
const noAuth = flags["no-auth"] === true;
|
|
3215
|
-
if (!manageAppServer &&
|
|
3387
|
+
if (!manageAppServer && instance2.runtime === "codex") {
|
|
3216
3388
|
log("Auto server: disabled (--no-server)");
|
|
3217
3389
|
}
|
|
3218
3390
|
if (noAuth && manageAppServer) {
|
|
3219
3391
|
log("Auth gateway: disabled (--no-auth)");
|
|
3220
3392
|
}
|
|
3221
|
-
const willBeHeadless = flags["headless"] === true ||
|
|
3393
|
+
const willBeHeadless = flags["headless"] === true || instance2.headless?.enabled;
|
|
3222
3394
|
if (willBeHeadless) {
|
|
3223
|
-
const role = (typeof flags["role"] === "string" ? flags["role"] : null) ??
|
|
3395
|
+
const role = (typeof flags["role"] === "string" ? flags["role"] : null) ?? instance2.headless?.role ?? "reviewer";
|
|
3224
3396
|
log(`Headless: ${role}`);
|
|
3225
3397
|
}
|
|
3226
3398
|
try {
|
|
3227
|
-
if (!manageAppServer &&
|
|
3399
|
+
if (!manageAppServer && instance2.runtime === "codex") {
|
|
3228
3400
|
log("Checking app-server health...");
|
|
3229
3401
|
const healthy = await checkAppServerHealth(appServerUrl);
|
|
3230
3402
|
if (healthy) {
|
|
@@ -3235,7 +3407,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3235
3407
|
ok: false,
|
|
3236
3408
|
command: "bridge",
|
|
3237
3409
|
instanceId,
|
|
3238
|
-
runtime:
|
|
3410
|
+
runtime: instance2.runtime,
|
|
3239
3411
|
code: "TAP_BRIDGE_START_FAILED",
|
|
3240
3412
|
message: `App server not reachable at ${appServerUrl}. Start it first: codex app-server --listen ${appServerUrl}`,
|
|
3241
3413
|
warnings: [],
|
|
@@ -3249,7 +3421,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3249
3421
|
ok: false,
|
|
3250
3422
|
command: "bridge",
|
|
3251
3423
|
instanceId,
|
|
3252
|
-
runtime:
|
|
3424
|
+
runtime: instance2.runtime,
|
|
3253
3425
|
code: "TAP_INVALID_ARGUMENT",
|
|
3254
3426
|
message: `Invalid --busy-mode: ${String(busyModeRaw)}. Must be "steer" or "wait".`,
|
|
3255
3427
|
warnings: [],
|
|
@@ -3257,9 +3429,38 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3257
3429
|
};
|
|
3258
3430
|
}
|
|
3259
3431
|
const busyMode = busyModeRaw;
|
|
3260
|
-
const
|
|
3261
|
-
const
|
|
3262
|
-
const
|
|
3432
|
+
const pollSecondsRaw = typeof flags["poll-seconds"] === "string" ? flags["poll-seconds"] : void 0;
|
|
3433
|
+
const reconnectSecondsRaw = typeof flags["reconnect-seconds"] === "string" ? flags["reconnect-seconds"] : void 0;
|
|
3434
|
+
const lookbackRaw = typeof flags["message-lookback-minutes"] === "string" ? flags["message-lookback-minutes"] : void 0;
|
|
3435
|
+
let pollSeconds;
|
|
3436
|
+
let reconnectSeconds;
|
|
3437
|
+
let messageLookbackMinutes;
|
|
3438
|
+
try {
|
|
3439
|
+
pollSeconds = parseIntFlag(pollSecondsRaw, "--poll-seconds", 1, 3600);
|
|
3440
|
+
reconnectSeconds = parseIntFlag(
|
|
3441
|
+
reconnectSecondsRaw,
|
|
3442
|
+
"--reconnect-seconds",
|
|
3443
|
+
1,
|
|
3444
|
+
3600
|
|
3445
|
+
);
|
|
3446
|
+
messageLookbackMinutes = parseIntFlag(
|
|
3447
|
+
lookbackRaw,
|
|
3448
|
+
"--message-lookback-minutes",
|
|
3449
|
+
1,
|
|
3450
|
+
10080
|
|
3451
|
+
);
|
|
3452
|
+
} catch (err) {
|
|
3453
|
+
return {
|
|
3454
|
+
ok: false,
|
|
3455
|
+
command: "bridge",
|
|
3456
|
+
instanceId,
|
|
3457
|
+
runtime: instance2.runtime,
|
|
3458
|
+
code: "TAP_INVALID_ARGUMENT",
|
|
3459
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3460
|
+
warnings: [],
|
|
3461
|
+
data: {}
|
|
3462
|
+
};
|
|
3463
|
+
}
|
|
3263
3464
|
const threadId = typeof flags["thread-id"] === "string" ? flags["thread-id"] : void 0;
|
|
3264
3465
|
const ephemeral = flags["ephemeral"] === true;
|
|
3265
3466
|
const processExistingMessages = flags["process-existing-messages"] === true;
|
|
@@ -3271,7 +3472,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3271
3472
|
ok: false,
|
|
3272
3473
|
command: "bridge",
|
|
3273
3474
|
instanceId,
|
|
3274
|
-
runtime:
|
|
3475
|
+
runtime: instance2.runtime,
|
|
3275
3476
|
code: "TAP_INVALID_ARGUMENT",
|
|
3276
3477
|
message: `Invalid --role: ${roleArg}. Must be: ${validRoles.join(", ")}`,
|
|
3277
3478
|
warnings: [],
|
|
@@ -3283,10 +3484,10 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3283
3484
|
role: roleArg ?? "reviewer",
|
|
3284
3485
|
maxRounds: 5,
|
|
3285
3486
|
qualitySeverityFloor: "high"
|
|
3286
|
-
} :
|
|
3487
|
+
} : instance2.headless;
|
|
3287
3488
|
const bridge = await startBridge({
|
|
3288
3489
|
instanceId,
|
|
3289
|
-
runtime:
|
|
3490
|
+
runtime: instance2.runtime,
|
|
3290
3491
|
stateDir: ctx.stateDir,
|
|
3291
3492
|
commsDir: ctx.commsDir,
|
|
3292
3493
|
bridgeScript,
|
|
@@ -3327,14 +3528,14 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3327
3528
|
log(`TUI connect: ${bridge.appServer.url}`);
|
|
3328
3529
|
}
|
|
3329
3530
|
}
|
|
3330
|
-
const updated = { ...
|
|
3531
|
+
const updated = { ...instance2, bridge, manageAppServer, noAuth };
|
|
3331
3532
|
const newState = updateInstanceState(state, instanceId, updated);
|
|
3332
3533
|
saveState(repoRoot, newState);
|
|
3333
3534
|
return {
|
|
3334
3535
|
ok: true,
|
|
3335
3536
|
command: "bridge",
|
|
3336
3537
|
instanceId,
|
|
3337
|
-
runtime:
|
|
3538
|
+
runtime: instance2.runtime,
|
|
3338
3539
|
code: "TAP_BRIDGE_START_OK",
|
|
3339
3540
|
message: `Bridge for ${instanceId} started (PID: ${bridge.pid})`,
|
|
3340
3541
|
warnings: [],
|
|
@@ -3347,7 +3548,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3347
3548
|
ok: false,
|
|
3348
3549
|
command: "bridge",
|
|
3349
3550
|
instanceId,
|
|
3350
|
-
runtime:
|
|
3551
|
+
runtime: instance2.runtime,
|
|
3351
3552
|
code: "TAP_BRIDGE_START_FAILED",
|
|
3352
3553
|
message: msg,
|
|
3353
3554
|
warnings: [],
|
|
@@ -3449,11 +3650,11 @@ async function bridgeStopOne(identifier) {
|
|
|
3449
3650
|
}
|
|
3450
3651
|
const instanceId = resolved.instanceId;
|
|
3451
3652
|
const ctx = createAdapterContext(state.commsDir, repoRoot);
|
|
3452
|
-
const
|
|
3653
|
+
const instance2 = state.instances[instanceId];
|
|
3453
3654
|
const bridgeState = loadCurrentBridgeState(
|
|
3454
3655
|
ctx.stateDir,
|
|
3455
3656
|
instanceId,
|
|
3456
|
-
|
|
3657
|
+
instance2?.bridge
|
|
3457
3658
|
);
|
|
3458
3659
|
const appServer = bridgeState?.appServer ?? null;
|
|
3459
3660
|
logHeader(`@hua-labs/tap bridge stop ${instanceId}`);
|
|
@@ -3501,8 +3702,8 @@ async function bridgeStopOne(identifier) {
|
|
|
3501
3702
|
}
|
|
3502
3703
|
}
|
|
3503
3704
|
}
|
|
3504
|
-
if (
|
|
3505
|
-
const updated = { ...
|
|
3705
|
+
if (instance2) {
|
|
3706
|
+
const updated = { ...instance2, bridge: null };
|
|
3506
3707
|
const newState = updateInstanceState(state, instanceId, updated);
|
|
3507
3708
|
saveState(repoRoot, newState);
|
|
3508
3709
|
}
|
|
@@ -3574,9 +3775,9 @@ async function bridgeStopAll() {
|
|
|
3574
3775
|
logSuccess(`Stopped bridge for ${instanceId}`);
|
|
3575
3776
|
stopped.push(instanceId);
|
|
3576
3777
|
}
|
|
3577
|
-
const
|
|
3578
|
-
if (
|
|
3579
|
-
state.instances[instanceId] = { ...
|
|
3778
|
+
const instance2 = state.instances[instanceId];
|
|
3779
|
+
if (instance2?.bridge) {
|
|
3780
|
+
state.instances[instanceId] = { ...instance2, bridge: null };
|
|
3580
3781
|
stateChanged = true;
|
|
3581
3782
|
}
|
|
3582
3783
|
}
|
|
@@ -3642,12 +3843,18 @@ function bridgeStatusAll() {
|
|
|
3642
3843
|
pid: null,
|
|
3643
3844
|
port: inst.port,
|
|
3644
3845
|
lastHeartbeat: null,
|
|
3846
|
+
threadId: null,
|
|
3847
|
+
threadCwd: null,
|
|
3848
|
+
savedThreadId: null,
|
|
3849
|
+
savedThreadCwd: null,
|
|
3645
3850
|
appServer: null
|
|
3646
3851
|
};
|
|
3647
3852
|
continue;
|
|
3648
3853
|
}
|
|
3649
3854
|
const status = getBridgeStatus(stateDir, instanceId);
|
|
3650
3855
|
const bridgeState = loadBridgeState(stateDir, instanceId);
|
|
3856
|
+
const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(bridgeState);
|
|
3857
|
+
const savedThread = loadRuntimeBridgeThreadState(bridgeState);
|
|
3651
3858
|
const age = getHeartbeatAge(stateDir, instanceId);
|
|
3652
3859
|
const pid = bridgeState?.pid ?? null;
|
|
3653
3860
|
const heartbeat = getBridgeHeartbeatTimestamp(stateDir, instanceId);
|
|
@@ -3669,12 +3876,26 @@ function bridgeStatusAll() {
|
|
|
3669
3876
|
);
|
|
3670
3877
|
}
|
|
3671
3878
|
}
|
|
3879
|
+
if (runtimeHeartbeat?.threadId) {
|
|
3880
|
+
log(
|
|
3881
|
+
` Thread: ${formatThreadSummary(runtimeHeartbeat.threadId, runtimeHeartbeat.threadCwd)}`
|
|
3882
|
+
);
|
|
3883
|
+
}
|
|
3884
|
+
if (savedThread?.threadId && (savedThread.threadId !== runtimeHeartbeat?.threadId || !sameOptionalPath(savedThread.cwd, runtimeHeartbeat?.threadCwd))) {
|
|
3885
|
+
log(
|
|
3886
|
+
` Saved: ${formatThreadSummary(savedThread.threadId, savedThread.cwd)}`
|
|
3887
|
+
);
|
|
3888
|
+
}
|
|
3672
3889
|
bridges[instanceId] = {
|
|
3673
3890
|
status,
|
|
3674
3891
|
runtime: inst.runtime,
|
|
3675
3892
|
pid,
|
|
3676
3893
|
port: inst.port,
|
|
3677
3894
|
lastHeartbeat: heartbeat,
|
|
3895
|
+
threadId: runtimeHeartbeat?.threadId ?? null,
|
|
3896
|
+
threadCwd: runtimeHeartbeat?.threadCwd ?? null,
|
|
3897
|
+
savedThreadId: savedThread?.threadId ?? null,
|
|
3898
|
+
savedThreadCwd: savedThread?.cwd ?? null,
|
|
3678
3899
|
appServer: bridgeState?.appServer ?? null
|
|
3679
3900
|
};
|
|
3680
3901
|
}
|
|
@@ -3750,6 +3971,10 @@ function bridgeStatusOne(identifier) {
|
|
|
3750
3971
|
pid: null,
|
|
3751
3972
|
port: inst.port,
|
|
3752
3973
|
lastHeartbeat: null,
|
|
3974
|
+
threadId: null,
|
|
3975
|
+
threadCwd: null,
|
|
3976
|
+
savedThreadId: null,
|
|
3977
|
+
savedThreadCwd: null,
|
|
3753
3978
|
appServer: null
|
|
3754
3979
|
}
|
|
3755
3980
|
};
|
|
@@ -3758,6 +3983,8 @@ function bridgeStatusOne(identifier) {
|
|
|
3758
3983
|
const stateDir = resolvedCfg2.stateDir;
|
|
3759
3984
|
const status = getBridgeStatus(stateDir, instanceId);
|
|
3760
3985
|
const bridgeState = loadBridgeState(stateDir, instanceId);
|
|
3986
|
+
const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(bridgeState);
|
|
3987
|
+
const savedThread = loadRuntimeBridgeThreadState(bridgeState);
|
|
3761
3988
|
const age = getHeartbeatAge(stateDir, instanceId);
|
|
3762
3989
|
const heartbeat = getBridgeHeartbeatTimestamp(stateDir, instanceId);
|
|
3763
3990
|
log(`Status: ${status}`);
|
|
@@ -3766,6 +3993,16 @@ function bridgeStatusOne(identifier) {
|
|
|
3766
3993
|
log(
|
|
3767
3994
|
`Heartbeat: ${heartbeat ?? "-"}${age !== null ? ` (${formatAge(age)})` : ""}`
|
|
3768
3995
|
);
|
|
3996
|
+
if (runtimeHeartbeat?.threadId) {
|
|
3997
|
+
log(
|
|
3998
|
+
`Thread: ${formatThreadSummary(runtimeHeartbeat.threadId, runtimeHeartbeat.threadCwd)}`
|
|
3999
|
+
);
|
|
4000
|
+
}
|
|
4001
|
+
if (savedThread?.threadId && (savedThread.threadId !== runtimeHeartbeat?.threadId || !sameOptionalPath(savedThread.cwd, runtimeHeartbeat?.threadCwd))) {
|
|
4002
|
+
log(
|
|
4003
|
+
`Saved: ${formatThreadSummary(savedThread.threadId, savedThread.cwd)}`
|
|
4004
|
+
);
|
|
4005
|
+
}
|
|
3769
4006
|
log(
|
|
3770
4007
|
`Log: ${path13.join(stateDir, "logs", `bridge-${instanceId}.log`)}`
|
|
3771
4008
|
);
|
|
@@ -3814,6 +4051,10 @@ function bridgeStatusOne(identifier) {
|
|
|
3814
4051
|
pid: bridgeState?.pid ?? null,
|
|
3815
4052
|
port: inst.port,
|
|
3816
4053
|
lastHeartbeat: heartbeat,
|
|
4054
|
+
threadId: runtimeHeartbeat?.threadId ?? null,
|
|
4055
|
+
threadCwd: runtimeHeartbeat?.threadCwd ?? null,
|
|
4056
|
+
savedThreadId: savedThread?.threadId ?? null,
|
|
4057
|
+
savedThreadCwd: savedThread?.cwd ?? null,
|
|
3817
4058
|
appServer: bridgeState?.appServer ?? null
|
|
3818
4059
|
}
|
|
3819
4060
|
};
|
|
@@ -3872,8 +4113,22 @@ async function bridgeRestart(identifier, flags) {
|
|
|
3872
4113
|
};
|
|
3873
4114
|
}
|
|
3874
4115
|
const { config: resolvedConfig } = resolveConfig({}, repoRoot);
|
|
3875
|
-
const drainStr = typeof flags["drain-timeout"] === "string" ? flags["drain-timeout"] :
|
|
3876
|
-
|
|
4116
|
+
const drainStr = typeof flags["drain-timeout"] === "string" ? flags["drain-timeout"] : void 0;
|
|
4117
|
+
let drainTimeout;
|
|
4118
|
+
try {
|
|
4119
|
+
drainTimeout = parseIntFlag(drainStr, "--drain-timeout", 1, 300) ?? 30;
|
|
4120
|
+
} catch (err) {
|
|
4121
|
+
return {
|
|
4122
|
+
ok: false,
|
|
4123
|
+
command: "bridge",
|
|
4124
|
+
instanceId,
|
|
4125
|
+
runtime: instance.runtime,
|
|
4126
|
+
code: "TAP_INVALID_ARGUMENT",
|
|
4127
|
+
message: err instanceof Error ? err.message : String(err),
|
|
4128
|
+
warnings: [],
|
|
4129
|
+
data: {}
|
|
4130
|
+
};
|
|
4131
|
+
}
|
|
3877
4132
|
logHeader(`@hua-labs/tap bridge restart ${instanceId}`);
|
|
3878
4133
|
log(`Drain timeout: ${drainTimeout}s`);
|
|
3879
4134
|
try {
|
|
@@ -4027,7 +4282,7 @@ var init_bridge2 = __esm({
|
|
|
4027
4282
|
init_utils();
|
|
4028
4283
|
BRIDGE_HELP = `
|
|
4029
4284
|
Usage:
|
|
4030
|
-
tap
|
|
4285
|
+
tap bridge <subcommand> [instance] [options]
|
|
4031
4286
|
|
|
4032
4287
|
Subcommands:
|
|
4033
4288
|
start <instance> Start bridge for an instance (e.g. codex, codex-reviewer)
|
|
@@ -4039,7 +4294,7 @@ Subcommands:
|
|
|
4039
4294
|
|
|
4040
4295
|
Options:
|
|
4041
4296
|
--agent-name <name> Agent identity for bridge (or set TAP_AGENT_NAME env)
|
|
4042
|
-
|
|
4297
|
+
Overrides the stored name from 'tap add' when needed
|
|
4043
4298
|
--all Start all registered app-server instances
|
|
4044
4299
|
--busy-mode <steer|wait> How to handle active turns (default: steer)
|
|
4045
4300
|
--poll-seconds <n> Inbox poll interval (default: 5)
|
|
@@ -4085,7 +4340,18 @@ async function upCommand(args) {
|
|
|
4085
4340
|
};
|
|
4086
4341
|
}
|
|
4087
4342
|
const repoRoot = findRepoRoot();
|
|
4088
|
-
const
|
|
4343
|
+
const previousColdStartWarmup = process.env.TAP_COLD_START_WARMUP;
|
|
4344
|
+
process.env.TAP_COLD_START_WARMUP = "true";
|
|
4345
|
+
let result;
|
|
4346
|
+
try {
|
|
4347
|
+
result = await bridgeCommand(["start", "--all", ...args]);
|
|
4348
|
+
} finally {
|
|
4349
|
+
if (previousColdStartWarmup === void 0) {
|
|
4350
|
+
delete process.env.TAP_COLD_START_WARMUP;
|
|
4351
|
+
} else {
|
|
4352
|
+
process.env.TAP_COLD_START_WARMUP = previousColdStartWarmup;
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4089
4355
|
const snapshot = collectDashboardSnapshot(repoRoot);
|
|
4090
4356
|
const activeBridges = snapshot.bridges.filter(
|
|
4091
4357
|
(bridge) => bridge.status === "running"
|
|
@@ -4121,7 +4387,7 @@ var init_up = __esm({
|
|
|
4121
4387
|
init_utils();
|
|
4122
4388
|
UP_HELP = `
|
|
4123
4389
|
Usage:
|
|
4124
|
-
tap
|
|
4390
|
+
tap up [bridge-start options]
|
|
4125
4391
|
|
|
4126
4392
|
Description:
|
|
4127
4393
|
Start all registered app-server bridge daemons with one command.
|
|
@@ -4186,7 +4452,7 @@ var init_down = __esm({
|
|
|
4186
4452
|
init_utils();
|
|
4187
4453
|
DOWN_HELP = `
|
|
4188
4454
|
Usage:
|
|
4189
|
-
tap
|
|
4455
|
+
tap down
|
|
4190
4456
|
|
|
4191
4457
|
Description:
|
|
4192
4458
|
Stop all running bridge daemons and managed app-servers.
|
|
@@ -4240,14 +4506,14 @@ async function* streamEvents(options) {
|
|
|
4240
4506
|
const repoRoot = options?.repoRoot ?? findRepoRoot();
|
|
4241
4507
|
while (!options?.signal?.aborted) {
|
|
4242
4508
|
yield collectDashboardSnapshot(repoRoot, options?.commsDir);
|
|
4243
|
-
await new Promise((
|
|
4509
|
+
await new Promise((resolve9) => {
|
|
4244
4510
|
const onAbort = () => {
|
|
4245
4511
|
clearTimeout(timer);
|
|
4246
|
-
|
|
4512
|
+
resolve9();
|
|
4247
4513
|
};
|
|
4248
4514
|
const timer = setTimeout(() => {
|
|
4249
4515
|
options?.signal?.removeEventListener("abort", onAbort);
|
|
4250
|
-
|
|
4516
|
+
resolve9();
|
|
4251
4517
|
}, intervalMs);
|
|
4252
4518
|
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
4253
4519
|
});
|
|
@@ -4346,11 +4612,38 @@ function getConfig(options) {
|
|
|
4346
4612
|
import {
|
|
4347
4613
|
createServer as createServer2
|
|
4348
4614
|
} from "http";
|
|
4615
|
+
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
4349
4616
|
var CORS_HEADERS = {
|
|
4350
4617
|
"Access-Control-Allow-Origin": "http://localhost:3000",
|
|
4351
4618
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
4352
|
-
"Access-Control-Allow-Headers": "Content-Type"
|
|
4619
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
4353
4620
|
};
|
|
4621
|
+
function tokensMatch(presentedToken, expectedToken) {
|
|
4622
|
+
if (!presentedToken) {
|
|
4623
|
+
return false;
|
|
4624
|
+
}
|
|
4625
|
+
const presented = Buffer.from(presentedToken, "utf8");
|
|
4626
|
+
const expected = Buffer.from(expectedToken, "utf8");
|
|
4627
|
+
if (presented.length !== expected.length) {
|
|
4628
|
+
return false;
|
|
4629
|
+
}
|
|
4630
|
+
return timingSafeEqual(presented, expected);
|
|
4631
|
+
}
|
|
4632
|
+
function verifyBearerToken(req, expectedToken) {
|
|
4633
|
+
const header = req.headers.authorization;
|
|
4634
|
+
if (!header?.startsWith("Bearer ")) {
|
|
4635
|
+
return false;
|
|
4636
|
+
}
|
|
4637
|
+
return tokensMatch(header.slice(7), expectedToken);
|
|
4638
|
+
}
|
|
4639
|
+
function verifySseToken(req, expectedToken, serverUrl) {
|
|
4640
|
+
if (verifyBearerToken(req, expectedToken)) {
|
|
4641
|
+
return true;
|
|
4642
|
+
}
|
|
4643
|
+
const url = new URL(req.url ?? "/", serverUrl);
|
|
4644
|
+
const queryToken = url.searchParams.get("token");
|
|
4645
|
+
return tokensMatch(queryToken, expectedToken);
|
|
4646
|
+
}
|
|
4354
4647
|
function jsonResponse(res, data, status = 200) {
|
|
4355
4648
|
res.writeHead(status, {
|
|
4356
4649
|
"Content-Type": "application/json",
|
|
@@ -4393,6 +4686,7 @@ function handleHealth(res, apiOptions) {
|
|
|
4393
4686
|
async function startHttpServer(options) {
|
|
4394
4687
|
const port = options?.port ?? 4580;
|
|
4395
4688
|
const host = "127.0.0.1";
|
|
4689
|
+
const token = options?.token ?? randomBytes2(24).toString("base64url");
|
|
4396
4690
|
const apiOptions = {
|
|
4397
4691
|
repoRoot: options?.repoRoot,
|
|
4398
4692
|
commsDir: options?.commsDir
|
|
@@ -4406,21 +4700,32 @@ async function startHttpServer(options) {
|
|
|
4406
4700
|
res.end();
|
|
4407
4701
|
return;
|
|
4408
4702
|
}
|
|
4703
|
+
if (req.method === "GET" && pathname === "/health") {
|
|
4704
|
+
handleHealth(res, apiOptions);
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4707
|
+
if (req.method === "GET" && pathname === "/api/events") {
|
|
4708
|
+
const serverUrl = `http://${host}:${port}`;
|
|
4709
|
+
if (!verifySseToken(req, token, serverUrl)) {
|
|
4710
|
+
jsonResponse(res, { error: "Unauthorized" }, 401);
|
|
4711
|
+
return;
|
|
4712
|
+
}
|
|
4713
|
+
await handleEvents(req, res, apiOptions);
|
|
4714
|
+
return;
|
|
4715
|
+
}
|
|
4716
|
+
if (!verifyBearerToken(req, token)) {
|
|
4717
|
+
jsonResponse(res, { error: "Unauthorized" }, 401);
|
|
4718
|
+
return;
|
|
4719
|
+
}
|
|
4409
4720
|
try {
|
|
4410
4721
|
if (req.method === "GET") {
|
|
4411
4722
|
switch (pathname) {
|
|
4412
4723
|
case "/api/snapshot":
|
|
4413
4724
|
handleSnapshot(res, apiOptions);
|
|
4414
4725
|
return;
|
|
4415
|
-
case "/api/events":
|
|
4416
|
-
await handleEvents(req, res, apiOptions);
|
|
4417
|
-
return;
|
|
4418
4726
|
case "/api/config":
|
|
4419
4727
|
handleConfig(res, apiOptions);
|
|
4420
4728
|
return;
|
|
4421
|
-
case "/health":
|
|
4422
|
-
handleHealth(res, apiOptions);
|
|
4423
|
-
return;
|
|
4424
4729
|
}
|
|
4425
4730
|
}
|
|
4426
4731
|
if (req.method === "POST") {
|
|
@@ -4449,17 +4754,18 @@ async function startHttpServer(options) {
|
|
|
4449
4754
|
}
|
|
4450
4755
|
}
|
|
4451
4756
|
);
|
|
4452
|
-
await new Promise((
|
|
4757
|
+
await new Promise((resolve9, reject) => {
|
|
4453
4758
|
server.once("error", reject);
|
|
4454
4759
|
server.listen(port, host, () => {
|
|
4455
4760
|
server.removeListener("error", reject);
|
|
4456
|
-
|
|
4761
|
+
resolve9();
|
|
4457
4762
|
});
|
|
4458
4763
|
});
|
|
4459
4764
|
return {
|
|
4460
4765
|
port,
|
|
4461
|
-
|
|
4462
|
-
|
|
4766
|
+
token,
|
|
4767
|
+
close: () => new Promise((resolve9, reject) => {
|
|
4768
|
+
server.close((err) => err ? reject(err) : resolve9());
|
|
4463
4769
|
})
|
|
4464
4770
|
};
|
|
4465
4771
|
}
|