@hua-labs/tap 0.3.0 → 0.4.0
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 +0 -9
- package/dist/bridges/codex-app-server-auth-gateway.d.mts +9 -1
- package/dist/bridges/codex-app-server-auth-gateway.mjs +183 -14
- package/dist/bridges/codex-app-server-auth-gateway.mjs.map +1 -1
- package/dist/bridges/codex-app-server-bridge.d.mts +224 -5
- package/dist/bridges/codex-app-server-bridge.mjs +1138 -683
- package/dist/bridges/codex-app-server-bridge.mjs.map +1 -1
- package/dist/bridges/codex-bridge-runner.mjs +17 -2
- package/dist/bridges/codex-bridge-runner.mjs.map +1 -1
- package/dist/cli.mjs +703 -95
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +502 -57
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-server.mjs +329 -72
- package/dist/mcp-server.mjs.map +1 -1
- package/package.json +3 -2
- package/LICENSE +0 -21
package/dist/index.mjs
CHANGED
|
@@ -71,8 +71,8 @@ function findRepoRoot(startDir = process.cwd()) {
|
|
|
71
71
|
function createAdapterContext(commsDir, repoRoot) {
|
|
72
72
|
const { config: config2 } = resolveConfig({}, repoRoot);
|
|
73
73
|
return {
|
|
74
|
-
commsDir: path.resolve(commsDir),
|
|
75
|
-
repoRoot: path.resolve(repoRoot),
|
|
74
|
+
commsDir: path.resolve(normalizeTapPath(commsDir)),
|
|
75
|
+
repoRoot: path.resolve(normalizeTapPath(repoRoot)),
|
|
76
76
|
stateDir: config2.stateDir,
|
|
77
77
|
platform: detectPlatform()
|
|
78
78
|
};
|
|
@@ -7414,7 +7414,7 @@ var init_bridge_port_network = __esm({
|
|
|
7414
7414
|
});
|
|
7415
7415
|
|
|
7416
7416
|
// src/engine/bridge-process-control.ts
|
|
7417
|
-
import { execSync } from "child_process";
|
|
7417
|
+
import { execSync, spawnSync } from "child_process";
|
|
7418
7418
|
function isProcessAlive(pid) {
|
|
7419
7419
|
try {
|
|
7420
7420
|
process.kill(pid, 0);
|
|
@@ -7423,6 +7423,25 @@ function isProcessAlive(pid) {
|
|
|
7423
7423
|
return false;
|
|
7424
7424
|
}
|
|
7425
7425
|
}
|
|
7426
|
+
function getUnixProcessGroupId(pid) {
|
|
7427
|
+
const result = spawnSync("ps", ["-o", "pgid=", "-p", String(pid)], {
|
|
7428
|
+
encoding: "utf-8",
|
|
7429
|
+
windowsHide: true
|
|
7430
|
+
});
|
|
7431
|
+
if (!result || result.status !== 0) {
|
|
7432
|
+
return null;
|
|
7433
|
+
}
|
|
7434
|
+
const parsed = Number.parseInt((result.stdout ?? "").trim(), 10);
|
|
7435
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
7436
|
+
}
|
|
7437
|
+
function isUnixProcessGroupAlive(processGroupId) {
|
|
7438
|
+
try {
|
|
7439
|
+
process.kill(-processGroupId, 0);
|
|
7440
|
+
return true;
|
|
7441
|
+
} catch {
|
|
7442
|
+
return false;
|
|
7443
|
+
}
|
|
7444
|
+
}
|
|
7426
7445
|
async function terminateProcess(pid, platform) {
|
|
7427
7446
|
if (!isProcessAlive(pid)) {
|
|
7428
7447
|
return false;
|
|
@@ -7431,11 +7450,16 @@ async function terminateProcess(pid, platform) {
|
|
|
7431
7450
|
if (platform === "win32") {
|
|
7432
7451
|
execSync(`taskkill /PID ${pid} /F /T`, { stdio: "pipe" });
|
|
7433
7452
|
} else {
|
|
7434
|
-
|
|
7453
|
+
const processGroupId = getUnixProcessGroupId(pid);
|
|
7454
|
+
const signalTarget = processGroupId != null ? -processGroupId : pid;
|
|
7455
|
+
const isTargetAlive = () => processGroupId != null ? isUnixProcessGroupAlive(processGroupId) : isProcessAlive(pid);
|
|
7456
|
+
process.kill(signalTarget, "SIGTERM");
|
|
7435
7457
|
await delay(2e3);
|
|
7436
|
-
if (
|
|
7437
|
-
process.kill(
|
|
7458
|
+
if (isTargetAlive()) {
|
|
7459
|
+
process.kill(signalTarget, "SIGKILL");
|
|
7460
|
+
await delay(500);
|
|
7438
7461
|
}
|
|
7462
|
+
return !isTargetAlive();
|
|
7439
7463
|
}
|
|
7440
7464
|
} catch {
|
|
7441
7465
|
}
|
|
@@ -7619,11 +7643,11 @@ var init_bridge_observability = __esm({
|
|
|
7619
7643
|
import * as fs9 from "fs";
|
|
7620
7644
|
import * as os3 from "os";
|
|
7621
7645
|
import * as path9 from "path";
|
|
7622
|
-
import { spawnSync } from "child_process";
|
|
7646
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
7623
7647
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7624
7648
|
function probeCommand(candidates) {
|
|
7625
7649
|
for (const candidate of candidates) {
|
|
7626
|
-
const result =
|
|
7650
|
+
const result = spawnSync2(candidate, ["--version"], {
|
|
7627
7651
|
encoding: "utf-8",
|
|
7628
7652
|
shell: process.platform === "win32"
|
|
7629
7653
|
});
|
|
@@ -7639,7 +7663,7 @@ function resolveCommandPath(command) {
|
|
|
7639
7663
|
if (path9.isAbsolute(command)) return command;
|
|
7640
7664
|
const whichCmd = process.platform === "win32" ? "where.exe" : "which";
|
|
7641
7665
|
try {
|
|
7642
|
-
const result =
|
|
7666
|
+
const result = spawnSync2(whichCmd, [command], {
|
|
7643
7667
|
encoding: "utf-8",
|
|
7644
7668
|
windowsHide: true
|
|
7645
7669
|
});
|
|
@@ -7732,7 +7756,7 @@ function findPreferredBunCommand() {
|
|
|
7732
7756
|
const candidates = process.platform === "win32" ? [path9.join(home, ".bun", "bin", "bun.exe"), "bun", "bun.cmd"] : [path9.join(home, ".bun", "bin", "bun"), "bun"];
|
|
7733
7757
|
for (const candidate of candidates) {
|
|
7734
7758
|
if (path9.isAbsolute(candidate) && !fs9.existsSync(candidate)) continue;
|
|
7735
|
-
const result =
|
|
7759
|
+
const result = spawnSync2(candidate, ["--version"], {
|
|
7736
7760
|
encoding: "utf-8",
|
|
7737
7761
|
shell: process.platform === "win32"
|
|
7738
7762
|
});
|
|
@@ -7894,7 +7918,7 @@ import * as fs11 from "fs";
|
|
|
7894
7918
|
import * as os4 from "os";
|
|
7895
7919
|
import * as path11 from "path";
|
|
7896
7920
|
import { randomBytes } from "crypto";
|
|
7897
|
-
import { spawnSync as
|
|
7921
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7898
7922
|
function cleanupStaleWindowsSpawnWrappers(now = Date.now()) {
|
|
7899
7923
|
let entries;
|
|
7900
7924
|
try {
|
|
@@ -7970,7 +7994,7 @@ function startWindowsDetachedProcess(command, args, repoRoot, logPath, env = pro
|
|
|
7970
7994
|
"-PassThru",
|
|
7971
7995
|
"; Write-Output $p.Id"
|
|
7972
7996
|
].join(" ");
|
|
7973
|
-
const result =
|
|
7997
|
+
const result = spawnSync3(
|
|
7974
7998
|
powerShellCommand,
|
|
7975
7999
|
["-NoLogo", "-NoProfile", "-Command", psCommand],
|
|
7976
8000
|
{
|
|
@@ -8012,7 +8036,7 @@ function findListeningProcessId(url2, platform) {
|
|
|
8012
8036
|
if (port == null || !Number.isFinite(port)) {
|
|
8013
8037
|
return null;
|
|
8014
8038
|
}
|
|
8015
|
-
const result =
|
|
8039
|
+
const result = spawnSync3(
|
|
8016
8040
|
resolvePowerShellCommand(),
|
|
8017
8041
|
[
|
|
8018
8042
|
"-NoLogo",
|
|
@@ -8049,15 +8073,55 @@ var init_bridge_windows_spawn = __esm({
|
|
|
8049
8073
|
|
|
8050
8074
|
// src/engine/bridge-unix-spawn.ts
|
|
8051
8075
|
import * as fs12 from "fs";
|
|
8052
|
-
import { spawn, spawnSync as
|
|
8053
|
-
function
|
|
8076
|
+
import { spawn, spawnSync as spawnSync4 } from "child_process";
|
|
8077
|
+
function resolveUnixSpawnCommand(command, args, platform) {
|
|
8078
|
+
if (platform === "linux") {
|
|
8079
|
+
return {
|
|
8080
|
+
command: "nohup",
|
|
8081
|
+
args: [command, ...args]
|
|
8082
|
+
};
|
|
8083
|
+
}
|
|
8084
|
+
return { command, args };
|
|
8085
|
+
}
|
|
8086
|
+
function findListeningPidWithLsof(port) {
|
|
8087
|
+
const result = spawnSync4(
|
|
8088
|
+
"lsof",
|
|
8089
|
+
["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"],
|
|
8090
|
+
{
|
|
8091
|
+
encoding: "utf-8",
|
|
8092
|
+
windowsHide: true
|
|
8093
|
+
}
|
|
8094
|
+
);
|
|
8095
|
+
if (!result || result.status !== 0) {
|
|
8096
|
+
return null;
|
|
8097
|
+
}
|
|
8098
|
+
const parsedPid = Number.parseInt((result.stdout ?? "").trim(), 10);
|
|
8099
|
+
return Number.isFinite(parsedPid) ? parsedPid : null;
|
|
8100
|
+
}
|
|
8101
|
+
function findListeningPidWithSs(port) {
|
|
8102
|
+
const result = spawnSync4("ss", ["-ltnpH", `sport = :${port}`], {
|
|
8103
|
+
encoding: "utf-8",
|
|
8104
|
+
windowsHide: true
|
|
8105
|
+
});
|
|
8106
|
+
if (!result || result.status !== 0) {
|
|
8107
|
+
return null;
|
|
8108
|
+
}
|
|
8109
|
+
const match = (result.stdout ?? "").match(/\bpid=(\d+)\b/);
|
|
8110
|
+
if (!match) {
|
|
8111
|
+
return null;
|
|
8112
|
+
}
|
|
8113
|
+
const parsedPid = Number.parseInt(match[1], 10);
|
|
8114
|
+
return Number.isFinite(parsedPid) ? parsedPid : null;
|
|
8115
|
+
}
|
|
8116
|
+
function startUnixDetachedProcess(command, args, repoRoot, logPath, env = process.env, platform = DEFAULT_UNIX_PLATFORM) {
|
|
8054
8117
|
const stderrPath = stderrLogFilePath(logPath);
|
|
8055
8118
|
let logFd = null;
|
|
8056
8119
|
let stderrFd = null;
|
|
8057
8120
|
try {
|
|
8058
8121
|
logFd = fs12.openSync(logPath, "a");
|
|
8059
8122
|
stderrFd = fs12.openSync(stderrPath, "a");
|
|
8060
|
-
const
|
|
8123
|
+
const launch = resolveUnixSpawnCommand(command, args, platform);
|
|
8124
|
+
const child = spawn(launch.command, launch.args, {
|
|
8061
8125
|
cwd: repoRoot,
|
|
8062
8126
|
detached: true,
|
|
8063
8127
|
stdio: ["ignore", logFd, stderrFd],
|
|
@@ -8075,13 +8139,15 @@ function startUnixDetachedProcess(command, args, repoRoot, logPath, env = proces
|
|
|
8075
8139
|
}
|
|
8076
8140
|
}
|
|
8077
8141
|
}
|
|
8078
|
-
function startUnixCodexAppServer(command, url2, repoRoot, logPath) {
|
|
8142
|
+
function startUnixCodexAppServer(command, url2, repoRoot, logPath, platform = DEFAULT_UNIX_PLATFORM) {
|
|
8079
8143
|
const { command: exe, prefixArgs } = splitResolvedCommand(command);
|
|
8080
8144
|
return startUnixDetachedProcess(
|
|
8081
8145
|
exe,
|
|
8082
8146
|
[...prefixArgs, "app-server", "--listen", url2],
|
|
8083
8147
|
repoRoot,
|
|
8084
|
-
logPath
|
|
8148
|
+
logPath,
|
|
8149
|
+
process.env,
|
|
8150
|
+
platform
|
|
8085
8151
|
);
|
|
8086
8152
|
}
|
|
8087
8153
|
function findUnixListeningProcessId(url2, platform) {
|
|
@@ -8098,25 +8164,21 @@ function findUnixListeningProcessId(url2, platform) {
|
|
|
8098
8164
|
if (port == null || !Number.isFinite(port)) {
|
|
8099
8165
|
return null;
|
|
8100
8166
|
}
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
encoding: "utf-8",
|
|
8106
|
-
windowsHide: true
|
|
8167
|
+
if (platform === "linux") {
|
|
8168
|
+
const ssPid = findListeningPidWithSs(port);
|
|
8169
|
+
if (ssPid != null) {
|
|
8170
|
+
return ssPid;
|
|
8107
8171
|
}
|
|
8108
|
-
);
|
|
8109
|
-
if (!result || result.status !== 0) {
|
|
8110
|
-
return null;
|
|
8111
8172
|
}
|
|
8112
|
-
|
|
8113
|
-
return Number.isFinite(parsedPid) ? parsedPid : null;
|
|
8173
|
+
return findListeningPidWithLsof(port);
|
|
8114
8174
|
}
|
|
8175
|
+
var DEFAULT_UNIX_PLATFORM;
|
|
8115
8176
|
var init_bridge_unix_spawn = __esm({
|
|
8116
8177
|
"src/engine/bridge-unix-spawn.ts"() {
|
|
8117
8178
|
"use strict";
|
|
8118
8179
|
init_bridge_codex_command();
|
|
8119
8180
|
init_bridge_paths();
|
|
8181
|
+
DEFAULT_UNIX_PLATFORM = process.platform === "darwin" ? "darwin" : "linux";
|
|
8120
8182
|
}
|
|
8121
8183
|
});
|
|
8122
8184
|
|
|
@@ -8165,6 +8227,7 @@ var init_bridge_config = __esm({
|
|
|
8165
8227
|
});
|
|
8166
8228
|
|
|
8167
8229
|
// src/engine/bridge-app-server-health.ts
|
|
8230
|
+
import * as net2 from "net";
|
|
8168
8231
|
async function checkAppServerHealth(url2, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_MS, gatewayToken) {
|
|
8169
8232
|
const WebSocket = getWebSocketCtor();
|
|
8170
8233
|
if (!WebSocket) {
|
|
@@ -8197,10 +8260,100 @@ async function checkAppServerHealth(url2, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_
|
|
|
8197
8260
|
}
|
|
8198
8261
|
});
|
|
8199
8262
|
}
|
|
8200
|
-
|
|
8263
|
+
function buildAppServerReadyzUrl(url2) {
|
|
8264
|
+
let parsed;
|
|
8265
|
+
try {
|
|
8266
|
+
parsed = new URL(url2);
|
|
8267
|
+
} catch {
|
|
8268
|
+
return null;
|
|
8269
|
+
}
|
|
8270
|
+
if (parsed.protocol === "ws:") {
|
|
8271
|
+
parsed.protocol = "http:";
|
|
8272
|
+
} else if (parsed.protocol === "wss:") {
|
|
8273
|
+
parsed.protocol = "https:";
|
|
8274
|
+
} else if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
8275
|
+
return null;
|
|
8276
|
+
}
|
|
8277
|
+
parsed.pathname = APP_SERVER_READYZ_PATH;
|
|
8278
|
+
parsed.search = "";
|
|
8279
|
+
parsed.hash = "";
|
|
8280
|
+
return parsed.toString();
|
|
8281
|
+
}
|
|
8282
|
+
async function checkAppServerReadyz(url2, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_MS) {
|
|
8283
|
+
const readyzUrl = buildAppServerReadyzUrl(url2);
|
|
8284
|
+
if (!readyzUrl) {
|
|
8285
|
+
return "unsupported";
|
|
8286
|
+
}
|
|
8287
|
+
const controller = new AbortController();
|
|
8288
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
8289
|
+
try {
|
|
8290
|
+
const response = await fetch(readyzUrl, {
|
|
8291
|
+
method: "GET",
|
|
8292
|
+
signal: controller.signal,
|
|
8293
|
+
headers: {
|
|
8294
|
+
accept: "application/json"
|
|
8295
|
+
}
|
|
8296
|
+
});
|
|
8297
|
+
if (response.ok) {
|
|
8298
|
+
return "ready";
|
|
8299
|
+
}
|
|
8300
|
+
if (response.status === 400 || response.status === 404 || response.status === 405 || response.status === 426 || response.status === 501) {
|
|
8301
|
+
return "unsupported";
|
|
8302
|
+
}
|
|
8303
|
+
return "not-ready";
|
|
8304
|
+
} catch {
|
|
8305
|
+
return "not-ready";
|
|
8306
|
+
} finally {
|
|
8307
|
+
clearTimeout(timer);
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
8310
|
+
async function checkTcpPortListening(url2, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_MS) {
|
|
8311
|
+
let hostname3;
|
|
8312
|
+
let port;
|
|
8313
|
+
try {
|
|
8314
|
+
const parsed = new URL(url2.replace(/^ws/, "http"));
|
|
8315
|
+
hostname3 = parsed.hostname;
|
|
8316
|
+
port = parseInt(parsed.port, 10);
|
|
8317
|
+
} catch {
|
|
8318
|
+
return false;
|
|
8319
|
+
}
|
|
8320
|
+
if (!port || !Number.isFinite(port)) return false;
|
|
8321
|
+
return new Promise((resolve11) => {
|
|
8322
|
+
const socket = net2.createConnection({ host: hostname3, port });
|
|
8323
|
+
const timer = setTimeout(() => {
|
|
8324
|
+
socket.destroy();
|
|
8325
|
+
resolve11(false);
|
|
8326
|
+
}, timeoutMs);
|
|
8327
|
+
socket.once("connect", () => {
|
|
8328
|
+
clearTimeout(timer);
|
|
8329
|
+
socket.destroy();
|
|
8330
|
+
resolve11(true);
|
|
8331
|
+
});
|
|
8332
|
+
socket.once("error", () => {
|
|
8333
|
+
clearTimeout(timer);
|
|
8334
|
+
socket.destroy();
|
|
8335
|
+
resolve11(false);
|
|
8336
|
+
});
|
|
8337
|
+
});
|
|
8338
|
+
}
|
|
8339
|
+
async function checkManagedAppServerReady(url2, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_MS) {
|
|
8340
|
+
const readyzStatus = await checkAppServerReadyz(url2, timeoutMs);
|
|
8341
|
+
if (readyzStatus === "ready") {
|
|
8342
|
+
return true;
|
|
8343
|
+
}
|
|
8344
|
+
if (readyzStatus === "unsupported") {
|
|
8345
|
+
return checkTcpPortListening(url2, timeoutMs);
|
|
8346
|
+
}
|
|
8347
|
+
return false;
|
|
8348
|
+
}
|
|
8349
|
+
async function waitForManagedAppServerReady(url2, timeoutMs) {
|
|
8201
8350
|
const deadline = Date.now() + timeoutMs;
|
|
8202
8351
|
while (Date.now() < deadline) {
|
|
8203
|
-
|
|
8352
|
+
const remaining = Math.max(
|
|
8353
|
+
1,
|
|
8354
|
+
Math.min(APP_SERVER_HEALTH_TIMEOUT_MS, deadline - Date.now())
|
|
8355
|
+
);
|
|
8356
|
+
if (await checkManagedAppServerReady(url2, remaining)) {
|
|
8204
8357
|
return true;
|
|
8205
8358
|
}
|
|
8206
8359
|
await delay(APP_SERVER_HEALTH_RETRY_MS);
|
|
@@ -8216,13 +8369,14 @@ function markAppServerHealthy(appServer) {
|
|
|
8216
8369
|
lastHealthyAt: checkedAt
|
|
8217
8370
|
};
|
|
8218
8371
|
}
|
|
8219
|
-
var APP_SERVER_HEALTH_TIMEOUT_MS, APP_SERVER_HEALTH_RETRY_MS, AUTH_SUBPROTOCOL_PREFIX;
|
|
8372
|
+
var APP_SERVER_HEALTH_TIMEOUT_MS, APP_SERVER_HEALTH_RETRY_MS, APP_SERVER_READYZ_PATH, AUTH_SUBPROTOCOL_PREFIX;
|
|
8220
8373
|
var init_bridge_app_server_health = __esm({
|
|
8221
8374
|
"src/engine/bridge-app-server-health.ts"() {
|
|
8222
8375
|
"use strict";
|
|
8223
8376
|
init_bridge_port_network();
|
|
8224
8377
|
APP_SERVER_HEALTH_TIMEOUT_MS = 1500;
|
|
8225
8378
|
APP_SERVER_HEALTH_RETRY_MS = 250;
|
|
8379
|
+
APP_SERVER_READYZ_PATH = "/readyz";
|
|
8226
8380
|
AUTH_SUBPROTOCOL_PREFIX = "tap-auth-";
|
|
8227
8381
|
}
|
|
8228
8382
|
});
|
|
@@ -8494,7 +8648,8 @@ async function createManagedAppServerAuth(options) {
|
|
|
8494
8648
|
gatewayArgs,
|
|
8495
8649
|
options.repoRoot,
|
|
8496
8650
|
gatewayLogPath,
|
|
8497
|
-
gatewayEnv
|
|
8651
|
+
gatewayEnv,
|
|
8652
|
+
options.platform
|
|
8498
8653
|
);
|
|
8499
8654
|
} catch (error2) {
|
|
8500
8655
|
removeFileIfExists2(tokenPath);
|
|
@@ -8680,7 +8835,8 @@ Start it manually:
|
|
|
8680
8835
|
resolvedCommand,
|
|
8681
8836
|
effectiveUrl,
|
|
8682
8837
|
options.repoRoot,
|
|
8683
|
-
logPath
|
|
8838
|
+
logPath,
|
|
8839
|
+
options.platform
|
|
8684
8840
|
);
|
|
8685
8841
|
} catch (err) {
|
|
8686
8842
|
throw new Error(
|
|
@@ -8698,7 +8854,7 @@ Start it manually:
|
|
|
8698
8854
|
${manualCommand2}`
|
|
8699
8855
|
);
|
|
8700
8856
|
}
|
|
8701
|
-
const healthy2 = await
|
|
8857
|
+
const healthy2 = await waitForManagedAppServerReady(
|
|
8702
8858
|
effectiveUrl,
|
|
8703
8859
|
APP_SERVER_START_TIMEOUT_MS
|
|
8704
8860
|
);
|
|
@@ -8760,7 +8916,8 @@ Start it manually:
|
|
|
8760
8916
|
resolvedCommand,
|
|
8761
8917
|
auth.upstreamUrl,
|
|
8762
8918
|
options.repoRoot,
|
|
8763
|
-
logPath
|
|
8919
|
+
logPath,
|
|
8920
|
+
options.platform
|
|
8764
8921
|
);
|
|
8765
8922
|
} catch (err) {
|
|
8766
8923
|
if (auth.gatewayPid != null) {
|
|
@@ -8786,7 +8943,7 @@ Start it manually:
|
|
|
8786
8943
|
${manualCommand}`
|
|
8787
8944
|
);
|
|
8788
8945
|
}
|
|
8789
|
-
const healthy = await
|
|
8946
|
+
const healthy = await waitForManagedAppServerReady(
|
|
8790
8947
|
auth.upstreamUrl,
|
|
8791
8948
|
APP_SERVER_START_TIMEOUT_MS
|
|
8792
8949
|
);
|
|
@@ -8812,10 +8969,9 @@ Or start it manually:
|
|
|
8812
8969
|
removeFileIfExists2(auth.tokenPath);
|
|
8813
8970
|
throw new Error("Tap auth gateway token is missing after startup.");
|
|
8814
8971
|
}
|
|
8815
|
-
const gatewayHealthy = await
|
|
8972
|
+
const gatewayHealthy = await waitForManagedAppServerReady(
|
|
8816
8973
|
effectiveUrl,
|
|
8817
|
-
APP_SERVER_GATEWAY_START_TIMEOUT_MS
|
|
8818
|
-
gatewayToken
|
|
8974
|
+
APP_SERVER_GATEWAY_START_TIMEOUT_MS
|
|
8819
8975
|
);
|
|
8820
8976
|
if (!gatewayHealthy) {
|
|
8821
8977
|
await terminateProcess(pid, options.platform);
|
|
@@ -8981,7 +9137,8 @@ async function startBridge(options) {
|
|
|
8981
9137
|
[bridgeScript],
|
|
8982
9138
|
repoRoot,
|
|
8983
9139
|
logPath,
|
|
8984
|
-
bridgeEnv
|
|
9140
|
+
bridgeEnv,
|
|
9141
|
+
options.platform
|
|
8985
9142
|
);
|
|
8986
9143
|
if (!bridgePid) {
|
|
8987
9144
|
throw new Error(`Failed to spawn bridge process for ${instanceId}`);
|
|
@@ -9710,9 +9867,26 @@ function writeTomlFile(filePath, content) {
|
|
|
9710
9867
|
fs22.writeFileSync(tmp, content, "utf-8");
|
|
9711
9868
|
fs22.renameSync(tmp, filePath);
|
|
9712
9869
|
}
|
|
9870
|
+
function buildSessionNeutralCodexSpec(ctx) {
|
|
9871
|
+
const managed = buildManagedMcpServerSpec(ctx);
|
|
9872
|
+
const env = {
|
|
9873
|
+
...managed.env,
|
|
9874
|
+
TAP_AGENT_NAME: SESSION_NEUTRAL_AGENT_NAME
|
|
9875
|
+
};
|
|
9876
|
+
delete env.TAP_AGENT_ID;
|
|
9877
|
+
return { ...managed, env };
|
|
9878
|
+
}
|
|
9879
|
+
function buildCodexEnvEntries(existingTable, managedEnv) {
|
|
9880
|
+
const preservedEnv = parseTomlAssignments(existingTable ?? "");
|
|
9881
|
+
delete preservedEnv.TAP_AGENT_ID;
|
|
9882
|
+
return {
|
|
9883
|
+
...preservedEnv,
|
|
9884
|
+
...managedEnv
|
|
9885
|
+
};
|
|
9886
|
+
}
|
|
9713
9887
|
function verifyManagedToml(content, ctx, configPath) {
|
|
9714
9888
|
const checks = [];
|
|
9715
|
-
const managed =
|
|
9889
|
+
const managed = buildSessionNeutralCodexSpec(ctx);
|
|
9716
9890
|
const mainTable = extractTomlTable(content, MCP_SELECTOR);
|
|
9717
9891
|
const envTable = extractTomlTable(content, ENV_SELECTOR);
|
|
9718
9892
|
checks.push({
|
|
@@ -9749,9 +9923,22 @@ function verifyManagedToml(content, ctx, configPath) {
|
|
|
9749
9923
|
message: "Managed tap command/args do not match expected values"
|
|
9750
9924
|
});
|
|
9751
9925
|
}
|
|
9926
|
+
if (envTable) {
|
|
9927
|
+
const envValues = parseTomlAssignments(envTable);
|
|
9928
|
+
checks.push({
|
|
9929
|
+
name: "Managed TAP_AGENT_NAME is session-neutral",
|
|
9930
|
+
passed: envValues.TAP_AGENT_NAME === managed.env.TAP_AGENT_NAME,
|
|
9931
|
+
message: `TAP_AGENT_NAME should be "${SESSION_NEUTRAL_AGENT_NAME}"`
|
|
9932
|
+
});
|
|
9933
|
+
checks.push({
|
|
9934
|
+
name: "Managed TAP_AGENT_ID is omitted",
|
|
9935
|
+
passed: typeof envValues.TAP_AGENT_ID !== "string",
|
|
9936
|
+
message: "TAP_AGENT_ID should not be persisted in Codex config"
|
|
9937
|
+
});
|
|
9938
|
+
}
|
|
9752
9939
|
return checks;
|
|
9753
9940
|
}
|
|
9754
|
-
var MCP_SELECTOR, ENV_SELECTOR, OLD_MCP_SELECTOR, OLD_ENV_SELECTOR, codexAdapter;
|
|
9941
|
+
var MCP_SELECTOR, ENV_SELECTOR, SESSION_NEUTRAL_AGENT_NAME, OLD_MCP_SELECTOR, OLD_ENV_SELECTOR, codexAdapter;
|
|
9755
9942
|
var init_codex = __esm({
|
|
9756
9943
|
"src/adapters/codex.ts"() {
|
|
9757
9944
|
"use strict";
|
|
@@ -9761,6 +9948,7 @@ var init_codex = __esm({
|
|
|
9761
9948
|
init_common();
|
|
9762
9949
|
MCP_SELECTOR = "mcp_servers.tap";
|
|
9763
9950
|
ENV_SELECTOR = "mcp_servers.tap.env";
|
|
9951
|
+
SESSION_NEUTRAL_AGENT_NAME = "<set-per-session>";
|
|
9764
9952
|
OLD_MCP_SELECTOR = "mcp_servers.tap-comms";
|
|
9765
9953
|
OLD_ENV_SELECTOR = "mcp_servers.tap-comms.env";
|
|
9766
9954
|
codexAdapter = {
|
|
@@ -9844,7 +10032,7 @@ var init_codex = __esm({
|
|
|
9844
10032
|
const configPath = plan.operations[0]?.path ?? findCodexConfigPath();
|
|
9845
10033
|
const warnings = [];
|
|
9846
10034
|
const changedFiles = [];
|
|
9847
|
-
const managed =
|
|
10035
|
+
const managed = buildSessionNeutralCodexSpec(ctx);
|
|
9848
10036
|
warnings.push(...managed.warnings);
|
|
9849
10037
|
if (managed.issues.length > 0 || !managed.command) {
|
|
9850
10038
|
return {
|
|
@@ -9901,8 +10089,10 @@ var init_codex = __esm({
|
|
|
9901
10089
|
ENV_SELECTOR,
|
|
9902
10090
|
renderTomlTable(
|
|
9903
10091
|
ENV_SELECTOR,
|
|
9904
|
-
|
|
9905
|
-
|
|
10092
|
+
buildCodexEnvEntries(
|
|
10093
|
+
extractTomlTable(existingContent, ENV_SELECTOR),
|
|
10094
|
+
managed.env
|
|
10095
|
+
)
|
|
9906
10096
|
)
|
|
9907
10097
|
);
|
|
9908
10098
|
for (const target of getTrustTargets(ctx)) {
|
|
@@ -10304,12 +10494,73 @@ var init_adapters = __esm({
|
|
|
10304
10494
|
});
|
|
10305
10495
|
|
|
10306
10496
|
// src/commands/bridge.ts
|
|
10497
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15, renameSync as renameSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
10307
10498
|
import * as path23 from "path";
|
|
10308
10499
|
function formatAge(seconds) {
|
|
10309
10500
|
if (seconds < 60) return `${seconds}s ago`;
|
|
10310
10501
|
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
10311
10502
|
return `${Math.floor(seconds / 3600)}h ${Math.floor(seconds % 3600 / 60)}m ago`;
|
|
10312
10503
|
}
|
|
10504
|
+
function loadBridgeHeartbeatStore(commsDir) {
|
|
10505
|
+
const heartbeatsPath = path23.join(commsDir, "heartbeats.json");
|
|
10506
|
+
if (!existsSync19(heartbeatsPath)) return {};
|
|
10507
|
+
try {
|
|
10508
|
+
return JSON.parse(readFileSync15(heartbeatsPath, "utf-8"));
|
|
10509
|
+
} catch {
|
|
10510
|
+
return null;
|
|
10511
|
+
}
|
|
10512
|
+
}
|
|
10513
|
+
function saveBridgeHeartbeatStore(commsDir, store) {
|
|
10514
|
+
const heartbeatsPath = path23.join(commsDir, "heartbeats.json");
|
|
10515
|
+
const tmp = `${heartbeatsPath}.tmp.${process.pid}`;
|
|
10516
|
+
writeFileSync10(tmp, JSON.stringify(store, null, 2), "utf-8");
|
|
10517
|
+
renameSync9(tmp, heartbeatsPath);
|
|
10518
|
+
}
|
|
10519
|
+
function parseBridgeHeartbeatAgeMs(record2, now) {
|
|
10520
|
+
const raw = record2.lastActivity ?? record2.timestamp;
|
|
10521
|
+
if (!raw) return Number.POSITIVE_INFINITY;
|
|
10522
|
+
const parsed = new Date(raw).getTime();
|
|
10523
|
+
if (!Number.isFinite(parsed)) return Number.POSITIVE_INFINITY;
|
|
10524
|
+
return Math.max(0, now - parsed);
|
|
10525
|
+
}
|
|
10526
|
+
function resolveBridgeHeartbeatInstanceId(state, heartbeatId) {
|
|
10527
|
+
if (state.instances[heartbeatId]) return heartbeatId;
|
|
10528
|
+
const hyphenated = heartbeatId.replace(/_/g, "-");
|
|
10529
|
+
if (state.instances[hyphenated]) return hyphenated;
|
|
10530
|
+
const underscored = heartbeatId.replace(/-/g, "_");
|
|
10531
|
+
if (state.instances[underscored]) return underscored;
|
|
10532
|
+
return null;
|
|
10533
|
+
}
|
|
10534
|
+
function pruneStaleHeartbeatsForBridgeUp(state, stateDir, commsDir) {
|
|
10535
|
+
const store = loadBridgeHeartbeatStore(commsDir);
|
|
10536
|
+
if (store === null) {
|
|
10537
|
+
return {
|
|
10538
|
+
removed: 0,
|
|
10539
|
+
warning: "Auto-clean skipped \u2014 heartbeats.json unreadable"
|
|
10540
|
+
};
|
|
10541
|
+
}
|
|
10542
|
+
const now = Date.now();
|
|
10543
|
+
let removed = 0;
|
|
10544
|
+
for (const [heartbeatId, heartbeat] of Object.entries(store)) {
|
|
10545
|
+
const ageMs = parseBridgeHeartbeatAgeMs(heartbeat, now);
|
|
10546
|
+
const instanceId = resolveBridgeHeartbeatInstanceId(state, heartbeatId);
|
|
10547
|
+
const instance = instanceId ? state.instances[instanceId] : null;
|
|
10548
|
+
const bridgeBacked = instance?.bridgeMode === "app-server";
|
|
10549
|
+
const bridgeRunning = bridgeBacked && instanceId ? getBridgeStatus(stateDir, instanceId) === "running" : false;
|
|
10550
|
+
const status = heartbeat.status ?? "active";
|
|
10551
|
+
const staleByStatus = status === "signing-off" && ageMs >= BRIDGE_UP_SIGNING_OFF_HEARTBEAT_WINDOW_MS;
|
|
10552
|
+
const staleByDeadBridge = bridgeBacked && !bridgeRunning && ageMs >= BRIDGE_UP_ACTIVE_HEARTBEAT_WINDOW_MS;
|
|
10553
|
+
const staleByAge = !bridgeRunning && ageMs >= BRIDGE_UP_ORPHAN_HEARTBEAT_WINDOW_MS;
|
|
10554
|
+
if (staleByStatus || staleByDeadBridge || staleByAge) {
|
|
10555
|
+
delete store[heartbeatId];
|
|
10556
|
+
removed += 1;
|
|
10557
|
+
}
|
|
10558
|
+
}
|
|
10559
|
+
if (removed > 0) {
|
|
10560
|
+
saveBridgeHeartbeatStore(commsDir, store);
|
|
10561
|
+
}
|
|
10562
|
+
return { removed };
|
|
10563
|
+
}
|
|
10313
10564
|
function formatAppServerState(appServer) {
|
|
10314
10565
|
const ownership = appServer.managed ? "managed" : "external";
|
|
10315
10566
|
const pid = appServer.pid != null ? ` pid:${appServer.pid}` : "";
|
|
@@ -10328,6 +10579,18 @@ function redactProtectedUrl(url2) {
|
|
|
10328
10579
|
return url2.replace(/[?&]tap_token=[^&]+/g, "");
|
|
10329
10580
|
}
|
|
10330
10581
|
}
|
|
10582
|
+
function resolveTuiConnectUrl(appServer) {
|
|
10583
|
+
return appServer.auth?.upstreamUrl ?? appServer.url;
|
|
10584
|
+
}
|
|
10585
|
+
function quoteCliArg(value) {
|
|
10586
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
10587
|
+
}
|
|
10588
|
+
function formatCodexTuiAttachCommand(tuiConnectUrl, cwd) {
|
|
10589
|
+
return `codex --enable tui_app_server --remote ${quoteCliArg(tuiConnectUrl)} --cd ${quoteCliArg(cwd)}`;
|
|
10590
|
+
}
|
|
10591
|
+
function resolveTuiAttachCwd(repoRoot, stateRepoRoot, runtimeThreadCwd, savedThreadCwd) {
|
|
10592
|
+
return runtimeThreadCwd ?? savedThreadCwd ?? stateRepoRoot ?? repoRoot;
|
|
10593
|
+
}
|
|
10331
10594
|
function loadCurrentBridgeState(stateDir, instanceId, fallback) {
|
|
10332
10595
|
return loadBridgeState(stateDir, instanceId) ?? fallback ?? null;
|
|
10333
10596
|
}
|
|
@@ -10695,6 +10958,26 @@ async function bridgeStartAll(flags = {}) {
|
|
|
10695
10958
|
data: {}
|
|
10696
10959
|
};
|
|
10697
10960
|
}
|
|
10961
|
+
const ctx = createAdapterContext(state.commsDir, repoRoot);
|
|
10962
|
+
const warnings = [];
|
|
10963
|
+
let prunedHeartbeats = 0;
|
|
10964
|
+
if (flags["auto-prune-heartbeats"] === true) {
|
|
10965
|
+
const cleanup = pruneStaleHeartbeatsForBridgeUp(
|
|
10966
|
+
state,
|
|
10967
|
+
ctx.stateDir,
|
|
10968
|
+
ctx.commsDir
|
|
10969
|
+
);
|
|
10970
|
+
prunedHeartbeats = cleanup.removed;
|
|
10971
|
+
if (cleanup.warning) {
|
|
10972
|
+
warnings.push(cleanup.warning);
|
|
10973
|
+
log(cleanup.warning);
|
|
10974
|
+
}
|
|
10975
|
+
if (prunedHeartbeats > 0) {
|
|
10976
|
+
log(
|
|
10977
|
+
`Auto-clean: pruned ${prunedHeartbeats} stale heartbeat entr${prunedHeartbeats === 1 ? "y" : "ies"}`
|
|
10978
|
+
);
|
|
10979
|
+
}
|
|
10980
|
+
}
|
|
10698
10981
|
const instanceIds = Object.keys(state.instances);
|
|
10699
10982
|
const appServerInstances = instanceIds.filter((id) => {
|
|
10700
10983
|
const inst = state.instances[id];
|
|
@@ -10703,13 +10986,14 @@ async function bridgeStartAll(flags = {}) {
|
|
|
10703
10986
|
return adapter.bridgeMode() === "app-server";
|
|
10704
10987
|
});
|
|
10705
10988
|
if (appServerInstances.length === 0) {
|
|
10989
|
+
const cleanupSuffix2 = prunedHeartbeats > 0 ? ` Auto-clean pruned ${prunedHeartbeats} stale heartbeat entr${prunedHeartbeats === 1 ? "y" : "ies"}.` : "";
|
|
10706
10990
|
return {
|
|
10707
10991
|
ok: true,
|
|
10708
10992
|
command: "bridge",
|
|
10709
10993
|
code: "TAP_NO_OP",
|
|
10710
|
-
message:
|
|
10711
|
-
warnings
|
|
10712
|
-
data: {}
|
|
10994
|
+
message: `No app-server instances found to start.${cleanupSuffix2}`,
|
|
10995
|
+
warnings,
|
|
10996
|
+
data: { prunedHeartbeats }
|
|
10713
10997
|
};
|
|
10714
10998
|
}
|
|
10715
10999
|
logHeader("@hua-labs/tap bridge start --all");
|
|
@@ -10719,7 +11003,6 @@ async function bridgeStartAll(flags = {}) {
|
|
|
10719
11003
|
log("");
|
|
10720
11004
|
const started = [];
|
|
10721
11005
|
const failed = [];
|
|
10722
|
-
const warnings = [];
|
|
10723
11006
|
for (const instanceId of appServerInstances) {
|
|
10724
11007
|
const inst = state.instances[instanceId];
|
|
10725
11008
|
const storedName = inst?.agentName ?? void 0;
|
|
@@ -10729,8 +11012,26 @@ async function bridgeStartAll(flags = {}) {
|
|
|
10729
11012
|
warnings.push(msg);
|
|
10730
11013
|
continue;
|
|
10731
11014
|
}
|
|
11015
|
+
const stateDir = path23.join(repoRoot, ".tap-comms");
|
|
11016
|
+
const currentBridgeState = loadBridgeState(stateDir, instanceId);
|
|
11017
|
+
const { manageAppServer, noAuth } = inferRestartMode(
|
|
11018
|
+
currentBridgeState,
|
|
11019
|
+
{
|
|
11020
|
+
noServer: flags["no-server"] === true ? true : void 0,
|
|
11021
|
+
noAuth: flags["no-auth"] === true ? true : void 0
|
|
11022
|
+
},
|
|
11023
|
+
{
|
|
11024
|
+
manageAppServer: inst.manageAppServer,
|
|
11025
|
+
noAuth: inst.noAuth
|
|
11026
|
+
}
|
|
11027
|
+
);
|
|
11028
|
+
const mergedFlags = {
|
|
11029
|
+
...flags,
|
|
11030
|
+
...manageAppServer === false ? { "no-server": true } : {},
|
|
11031
|
+
...noAuth === true ? { "no-auth": true } : {}
|
|
11032
|
+
};
|
|
10732
11033
|
log(`Starting ${instanceId} (agent: ${storedName})...`);
|
|
10733
|
-
const result = await bridgeStart(instanceId, storedName,
|
|
11034
|
+
const result = await bridgeStart(instanceId, storedName, mergedFlags);
|
|
10734
11035
|
if (result.ok) {
|
|
10735
11036
|
started.push(instanceId);
|
|
10736
11037
|
logSuccess(`${instanceId} started`);
|
|
@@ -10741,13 +11042,14 @@ async function bridgeStartAll(flags = {}) {
|
|
|
10741
11042
|
log("");
|
|
10742
11043
|
}
|
|
10743
11044
|
const message = started.length > 0 ? `Started ${started.length}/${appServerInstances.length} bridge(s): ${started.join(", ")}` + (failed.length > 0 ? `. Failed: ${failed.join(", ")}` : "") : `No bridges started. Failed: ${failed.join(", ")}`;
|
|
11045
|
+
const cleanupSuffix = prunedHeartbeats > 0 ? ` Auto-clean pruned ${prunedHeartbeats} stale heartbeat entr${prunedHeartbeats === 1 ? "y" : "ies"}.` : "";
|
|
10744
11046
|
return {
|
|
10745
11047
|
ok: failed.length === 0 && started.length > 0,
|
|
10746
11048
|
command: "bridge",
|
|
10747
11049
|
code: started.length > 0 ? "TAP_BRIDGE_START_OK" : "TAP_BRIDGE_START_FAILED",
|
|
10748
|
-
message
|
|
11050
|
+
message: `${message}${cleanupSuffix}`,
|
|
10749
11051
|
warnings,
|
|
10750
|
-
data: { started, failed }
|
|
11052
|
+
data: { started, failed, prunedHeartbeats }
|
|
10751
11053
|
};
|
|
10752
11054
|
}
|
|
10753
11055
|
async function bridgeStopOne(identifier) {
|
|
@@ -11329,6 +11631,122 @@ function bridgeStatusOne(identifier) {
|
|
|
11329
11631
|
}
|
|
11330
11632
|
};
|
|
11331
11633
|
}
|
|
11634
|
+
function bridgeTuiOne(identifier) {
|
|
11635
|
+
const repoRoot = findRepoRoot();
|
|
11636
|
+
const state = loadState(repoRoot);
|
|
11637
|
+
if (!state) {
|
|
11638
|
+
return {
|
|
11639
|
+
ok: false,
|
|
11640
|
+
command: "bridge",
|
|
11641
|
+
code: "TAP_NOT_INITIALIZED",
|
|
11642
|
+
message: "Not initialized. Run: npx @hua-labs/tap init",
|
|
11643
|
+
warnings: [],
|
|
11644
|
+
data: {}
|
|
11645
|
+
};
|
|
11646
|
+
}
|
|
11647
|
+
const resolved = resolveInstanceId(identifier, state);
|
|
11648
|
+
if (!resolved.ok) {
|
|
11649
|
+
return {
|
|
11650
|
+
ok: false,
|
|
11651
|
+
command: "bridge",
|
|
11652
|
+
code: resolved.code,
|
|
11653
|
+
message: resolved.message,
|
|
11654
|
+
warnings: [],
|
|
11655
|
+
data: {}
|
|
11656
|
+
};
|
|
11657
|
+
}
|
|
11658
|
+
const instanceId = resolved.instanceId;
|
|
11659
|
+
const inst = state.instances[instanceId];
|
|
11660
|
+
if (!inst?.installed) {
|
|
11661
|
+
return {
|
|
11662
|
+
ok: false,
|
|
11663
|
+
command: "bridge",
|
|
11664
|
+
instanceId,
|
|
11665
|
+
code: "TAP_INSTANCE_NOT_FOUND",
|
|
11666
|
+
message: `${instanceId} is not installed.`,
|
|
11667
|
+
warnings: [],
|
|
11668
|
+
data: {}
|
|
11669
|
+
};
|
|
11670
|
+
}
|
|
11671
|
+
if (inst.runtime !== "codex" || inst.bridgeMode !== "app-server") {
|
|
11672
|
+
return {
|
|
11673
|
+
ok: false,
|
|
11674
|
+
command: "bridge",
|
|
11675
|
+
instanceId,
|
|
11676
|
+
runtime: inst.runtime,
|
|
11677
|
+
code: "TAP_INVALID_ARGUMENT",
|
|
11678
|
+
message: `${instanceId} does not support Codex TUI attach. Use a Codex app-server bridge instance.`,
|
|
11679
|
+
warnings: [],
|
|
11680
|
+
data: {}
|
|
11681
|
+
};
|
|
11682
|
+
}
|
|
11683
|
+
const { config: resolvedConfig } = resolveConfig({}, repoRoot);
|
|
11684
|
+
const stateDir = resolvedConfig.stateDir;
|
|
11685
|
+
const status = getBridgeStatus(stateDir, instanceId);
|
|
11686
|
+
if (status !== "running") {
|
|
11687
|
+
return {
|
|
11688
|
+
ok: false,
|
|
11689
|
+
command: "bridge",
|
|
11690
|
+
instanceId,
|
|
11691
|
+
runtime: inst.runtime,
|
|
11692
|
+
code: "TAP_BRIDGE_NOT_RUNNING",
|
|
11693
|
+
message: `${instanceId} bridge is ${status}. Start it first with: npx @hua-labs/tap bridge start ${instanceId}`,
|
|
11694
|
+
warnings: [],
|
|
11695
|
+
data: { status }
|
|
11696
|
+
};
|
|
11697
|
+
}
|
|
11698
|
+
const bridgeState = loadBridgeState(stateDir, instanceId);
|
|
11699
|
+
const appServer = bridgeState?.appServer;
|
|
11700
|
+
const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(bridgeState);
|
|
11701
|
+
const savedThread = loadRuntimeBridgeThreadState(bridgeState);
|
|
11702
|
+
if (!appServer) {
|
|
11703
|
+
return {
|
|
11704
|
+
ok: false,
|
|
11705
|
+
command: "bridge",
|
|
11706
|
+
instanceId,
|
|
11707
|
+
runtime: inst.runtime,
|
|
11708
|
+
code: "TAP_BRIDGE_NOT_RUNNING",
|
|
11709
|
+
message: `${instanceId} app-server state is missing. Restart the bridge first.`,
|
|
11710
|
+
warnings: [],
|
|
11711
|
+
data: { status }
|
|
11712
|
+
};
|
|
11713
|
+
}
|
|
11714
|
+
const tuiConnectUrl = resolveTuiConnectUrl(appServer);
|
|
11715
|
+
const attachCwd = resolveTuiAttachCwd(
|
|
11716
|
+
repoRoot,
|
|
11717
|
+
state.repoRoot,
|
|
11718
|
+
runtimeHeartbeat?.threadCwd,
|
|
11719
|
+
savedThread?.cwd
|
|
11720
|
+
);
|
|
11721
|
+
const attachCommand = formatCodexTuiAttachCommand(tuiConnectUrl, attachCwd);
|
|
11722
|
+
const warnings = appServer.auth != null ? [
|
|
11723
|
+
"Use the upstream TUI URL, not the protected gateway URL. The protected URL is bridge-only."
|
|
11724
|
+
] : [];
|
|
11725
|
+
logHeader(`@hua-labs/tap bridge tui ${instanceId}`);
|
|
11726
|
+
if (appServer.auth) {
|
|
11727
|
+
log(`Protected: ${redactProtectedUrl(appServer.auth.protectedUrl)}`);
|
|
11728
|
+
log(`Upstream: ${appServer.auth.upstreamUrl}`);
|
|
11729
|
+
}
|
|
11730
|
+
log(`Using: ${tuiConnectUrl}`);
|
|
11731
|
+
log(`Attach: ${attachCommand}`);
|
|
11732
|
+
log("");
|
|
11733
|
+
return {
|
|
11734
|
+
ok: true,
|
|
11735
|
+
command: "bridge",
|
|
11736
|
+
instanceId,
|
|
11737
|
+
runtime: inst.runtime,
|
|
11738
|
+
code: "TAP_BRIDGE_STATUS_OK",
|
|
11739
|
+
message: `${instanceId} TUI attach command ready`,
|
|
11740
|
+
warnings,
|
|
11741
|
+
data: {
|
|
11742
|
+
status,
|
|
11743
|
+
tuiConnectUrl,
|
|
11744
|
+
attachCwd,
|
|
11745
|
+
attachCommand,
|
|
11746
|
+
appServer
|
|
11747
|
+
}
|
|
11748
|
+
};
|
|
11749
|
+
}
|
|
11332
11750
|
async function bridgeRestart(identifier, flags) {
|
|
11333
11751
|
const repoRoot = findRepoRoot();
|
|
11334
11752
|
const state = loadState(repoRoot);
|
|
@@ -11528,6 +11946,19 @@ async function bridgeCommand(args) {
|
|
|
11528
11946
|
}
|
|
11529
11947
|
return bridgeStatusAll();
|
|
11530
11948
|
}
|
|
11949
|
+
case "tui": {
|
|
11950
|
+
if (!identifierArg) {
|
|
11951
|
+
return {
|
|
11952
|
+
ok: false,
|
|
11953
|
+
command: "bridge",
|
|
11954
|
+
code: "TAP_INVALID_ARGUMENT",
|
|
11955
|
+
message: "Missing instance. Usage: npx @hua-labs/tap bridge tui <instance>",
|
|
11956
|
+
warnings: [],
|
|
11957
|
+
data: {}
|
|
11958
|
+
};
|
|
11959
|
+
}
|
|
11960
|
+
return bridgeTuiOne(identifierArg);
|
|
11961
|
+
}
|
|
11531
11962
|
case "watch": {
|
|
11532
11963
|
const intervalStr = typeof flags["interval"] === "string" ? flags["interval"] : void 0;
|
|
11533
11964
|
const interval = intervalStr ? parseInt(intervalStr, 10) : 30;
|
|
@@ -11553,13 +11984,13 @@ async function bridgeCommand(args) {
|
|
|
11553
11984
|
ok: false,
|
|
11554
11985
|
command: "bridge",
|
|
11555
11986
|
code: "TAP_INVALID_ARGUMENT",
|
|
11556
|
-
message: `Unknown bridge subcommand: ${subcommand}. Use: start, stop, restart, status`,
|
|
11987
|
+
message: `Unknown bridge subcommand: ${subcommand}. Use: start, stop, restart, status, tui`,
|
|
11557
11988
|
warnings: [],
|
|
11558
11989
|
data: {}
|
|
11559
11990
|
};
|
|
11560
11991
|
}
|
|
11561
11992
|
}
|
|
11562
|
-
var BRIDGE_HELP;
|
|
11993
|
+
var BRIDGE_UP_ACTIVE_HEARTBEAT_WINDOW_MS, BRIDGE_UP_ORPHAN_HEARTBEAT_WINDOW_MS, BRIDGE_UP_SIGNING_OFF_HEARTBEAT_WINDOW_MS, BRIDGE_HELP;
|
|
11563
11994
|
var init_bridge2 = __esm({
|
|
11564
11995
|
"src/commands/bridge.ts"() {
|
|
11565
11996
|
"use strict";
|
|
@@ -11568,6 +11999,9 @@ var init_bridge2 = __esm({
|
|
|
11568
11999
|
init_config();
|
|
11569
12000
|
init_adapters();
|
|
11570
12001
|
init_utils();
|
|
12002
|
+
BRIDGE_UP_ACTIVE_HEARTBEAT_WINDOW_MS = 10 * 60 * 1e3;
|
|
12003
|
+
BRIDGE_UP_ORPHAN_HEARTBEAT_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
12004
|
+
BRIDGE_UP_SIGNING_OFF_HEARTBEAT_WINDOW_MS = 5 * 60 * 1e3;
|
|
11571
12005
|
BRIDGE_HELP = `
|
|
11572
12006
|
Usage:
|
|
11573
12007
|
tap bridge <subcommand> [instance] [options]
|
|
@@ -11579,6 +12013,7 @@ Subcommands:
|
|
|
11579
12013
|
stop Stop all running bridges
|
|
11580
12014
|
status Show bridge status for all instances
|
|
11581
12015
|
status <instance> Show bridge status for a specific instance
|
|
12016
|
+
tui <instance> Show the safe Codex TUI attach command for a running bridge
|
|
11582
12017
|
watch Monitor bridges and auto-restart stuck/stale ones
|
|
11583
12018
|
|
|
11584
12019
|
Options:
|
|
@@ -11607,6 +12042,7 @@ Examples:
|
|
|
11607
12042
|
npx @hua-labs/tap bridge stop codex
|
|
11608
12043
|
npx @hua-labs/tap bridge stop
|
|
11609
12044
|
npx @hua-labs/tap bridge status
|
|
12045
|
+
npx @hua-labs/tap bridge tui codex
|
|
11610
12046
|
`.trim();
|
|
11611
12047
|
}
|
|
11612
12048
|
});
|
|
@@ -11633,7 +12069,12 @@ async function upCommand(args) {
|
|
|
11633
12069
|
process.env.TAP_COLD_START_WARMUP = "true";
|
|
11634
12070
|
let result;
|
|
11635
12071
|
try {
|
|
11636
|
-
result = await bridgeCommand([
|
|
12072
|
+
result = await bridgeCommand([
|
|
12073
|
+
"start",
|
|
12074
|
+
"--all",
|
|
12075
|
+
"--auto-prune-heartbeats",
|
|
12076
|
+
...args
|
|
12077
|
+
]);
|
|
11637
12078
|
} finally {
|
|
11638
12079
|
if (previousColdStartWarmup === void 0) {
|
|
11639
12080
|
delete process.env.TAP_COLD_START_WARMUP;
|
|
@@ -11681,6 +12122,7 @@ Usage:
|
|
|
11681
12122
|
Description:
|
|
11682
12123
|
Start all registered app-server bridge daemons with one command.
|
|
11683
12124
|
This is the orchestration entrypoint for headless/background TAP operation.
|
|
12125
|
+
tap up auto-prunes stale heartbeat entries before bridge startup.
|
|
11684
12126
|
|
|
11685
12127
|
Examples:
|
|
11686
12128
|
npx @hua-labs/tap up
|
|
@@ -27719,8 +28161,10 @@ async function startHttpServer(options) {
|
|
|
27719
28161
|
resolve11();
|
|
27720
28162
|
});
|
|
27721
28163
|
});
|
|
28164
|
+
const addr = server.address();
|
|
28165
|
+
const actualPort = typeof addr === "object" && addr ? addr.port : port;
|
|
27722
28166
|
return {
|
|
27723
|
-
port,
|
|
28167
|
+
port: actualPort,
|
|
27724
28168
|
token,
|
|
27725
28169
|
close: () => new Promise((resolve11, reject) => {
|
|
27726
28170
|
server.close((err) => err ? reject(err) : resolve11());
|
|
@@ -27744,6 +28188,7 @@ export {
|
|
|
27744
28188
|
loadLocalConfig,
|
|
27745
28189
|
loadSharedConfig,
|
|
27746
28190
|
loadState,
|
|
28191
|
+
normalizeTapPath,
|
|
27747
28192
|
probeFnmNode,
|
|
27748
28193
|
readNodeVersion,
|
|
27749
28194
|
resolveConfig,
|