@hua-labs/tap 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +1 -0
- package/dist/bridges/codex-app-server-bridge.mjs +16 -13
- package/dist/bridges/codex-app-server-bridge.mjs.map +1 -1
- package/dist/bridges/codex-bridge-runner.mjs +21 -17
- package/dist/bridges/codex-bridge-runner.mjs.map +1 -1
- package/dist/cli.mjs +483 -146
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +172 -75
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-server.mjs +185 -185
- package/dist/mcp-server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -88,7 +88,7 @@ interface HeadlessConfig {
|
|
|
88
88
|
qualitySeverityFloor: "critical" | "high" | "medium";
|
|
89
89
|
}
|
|
90
90
|
interface AppServerAuthState {
|
|
91
|
-
mode: "query-token";
|
|
91
|
+
mode: "subprotocol" | "query-token";
|
|
92
92
|
protectedUrl: string;
|
|
93
93
|
upstreamUrl: string;
|
|
94
94
|
tokenPath: string;
|
|
@@ -442,6 +442,8 @@ declare function getConfig(options?: StateApiOptions): {
|
|
|
442
442
|
interface HttpServerOptions extends StateApiOptions {
|
|
443
443
|
/** Port to listen on (default: 4580) */
|
|
444
444
|
port?: number;
|
|
445
|
+
/** Pre-set API token (default: auto-generated) */
|
|
446
|
+
token?: string;
|
|
445
447
|
}
|
|
446
448
|
/**
|
|
447
449
|
* Start a localhost-only HTTP server for the tap State API.
|
|
@@ -449,6 +451,7 @@ interface HttpServerOptions extends StateApiOptions {
|
|
|
449
451
|
*/
|
|
450
452
|
declare function startHttpServer(options?: HttpServerOptions): Promise<{
|
|
451
453
|
port: number;
|
|
454
|
+
token: string;
|
|
452
455
|
close: () => Promise<void>;
|
|
453
456
|
}>;
|
|
454
457
|
|
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();
|
|
@@ -838,8 +845,11 @@ function resolvePowerShellCommand() {
|
|
|
838
845
|
function resolveAuthGatewayScript(repoRoot) {
|
|
839
846
|
const moduleDir = path7.dirname(fileURLToPath3(import.meta.url));
|
|
840
847
|
const candidates = [
|
|
841
|
-
|
|
842
|
-
path7.join(moduleDir, "
|
|
848
|
+
// Bundled: dist/bridges/ sibling (npm install / built package)
|
|
849
|
+
path7.join(moduleDir, "bridges", "codex-app-server-auth-gateway.mjs"),
|
|
850
|
+
// Source: src/bridges/ sibling (monorepo dev with ts runner)
|
|
851
|
+
path7.join(moduleDir, "bridges", "codex-app-server-auth-gateway.ts"),
|
|
852
|
+
// Monorepo dist fallback
|
|
843
853
|
path7.join(
|
|
844
854
|
repoRoot,
|
|
845
855
|
"packages",
|
|
@@ -892,10 +902,8 @@ async function allocateLoopbackPort(hostname) {
|
|
|
892
902
|
});
|
|
893
903
|
});
|
|
894
904
|
}
|
|
895
|
-
function buildProtectedAppServerUrl(publicUrl,
|
|
896
|
-
|
|
897
|
-
url.searchParams.set(APP_SERVER_AUTH_QUERY_PARAM, token);
|
|
898
|
-
return url.toString().replace(/\/(?=\?|$)/, "");
|
|
905
|
+
function buildProtectedAppServerUrl(publicUrl, _token) {
|
|
906
|
+
return publicUrl;
|
|
899
907
|
}
|
|
900
908
|
function readGatewayTokenFromPath(tokenPath) {
|
|
901
909
|
return fs7.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -1010,7 +1018,7 @@ async function createManagedAppServerAuth(options) {
|
|
|
1010
1018
|
throw new Error("Failed to spawn app-server auth gateway");
|
|
1011
1019
|
}
|
|
1012
1020
|
return {
|
|
1013
|
-
mode: "
|
|
1021
|
+
mode: "subprotocol",
|
|
1014
1022
|
protectedUrl,
|
|
1015
1023
|
upstreamUrl: upstreamUrl.toString().replace(/\/$/, ""),
|
|
1016
1024
|
tokenPath,
|
|
@@ -1197,7 +1205,7 @@ async function findNextAvailableAppServerPort(state, baseUrl, basePort = 4501, e
|
|
|
1197
1205
|
`Failed to find a free app-server port starting at ${basePort}`
|
|
1198
1206
|
);
|
|
1199
1207
|
}
|
|
1200
|
-
async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_MS) {
|
|
1208
|
+
async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_MS, gatewayToken) {
|
|
1201
1209
|
const WebSocket = getWebSocketCtor();
|
|
1202
1210
|
if (!WebSocket) {
|
|
1203
1211
|
return false;
|
|
@@ -1219,7 +1227,8 @@ async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_M
|
|
|
1219
1227
|
};
|
|
1220
1228
|
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
1221
1229
|
try {
|
|
1222
|
-
|
|
1230
|
+
const protocols = gatewayToken ? [`${AUTH_SUBPROTOCOL_PREFIX}${gatewayToken}`] : void 0;
|
|
1231
|
+
socket = new WebSocket(url, protocols);
|
|
1223
1232
|
socket.addEventListener("open", () => finish(true), { once: true });
|
|
1224
1233
|
socket.addEventListener("error", () => finish(false), { once: true });
|
|
1225
1234
|
socket.addEventListener("close", () => finish(false), { once: true });
|
|
@@ -1228,10 +1237,14 @@ async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_M
|
|
|
1228
1237
|
}
|
|
1229
1238
|
});
|
|
1230
1239
|
}
|
|
1231
|
-
async function waitForAppServerHealth(url, timeoutMs) {
|
|
1240
|
+
async function waitForAppServerHealth(url, timeoutMs, gatewayToken) {
|
|
1232
1241
|
const deadline = Date.now() + timeoutMs;
|
|
1233
1242
|
while (Date.now() < deadline) {
|
|
1234
|
-
if (await checkAppServerHealth(
|
|
1243
|
+
if (await checkAppServerHealth(
|
|
1244
|
+
url,
|
|
1245
|
+
APP_SERVER_HEALTH_TIMEOUT_MS,
|
|
1246
|
+
gatewayToken
|
|
1247
|
+
)) {
|
|
1235
1248
|
return true;
|
|
1236
1249
|
}
|
|
1237
1250
|
await delay(APP_SERVER_HEALTH_RETRY_MS);
|
|
@@ -1496,8 +1509,9 @@ Or start it manually:
|
|
|
1496
1509
|
throw new Error("Tap auth gateway token is missing after startup.");
|
|
1497
1510
|
}
|
|
1498
1511
|
const gatewayHealthy = await waitForAppServerHealth(
|
|
1499
|
-
|
|
1500
|
-
APP_SERVER_GATEWAY_START_TIMEOUT_MS
|
|
1512
|
+
effectiveUrl,
|
|
1513
|
+
APP_SERVER_GATEWAY_START_TIMEOUT_MS,
|
|
1514
|
+
gatewayToken
|
|
1501
1515
|
);
|
|
1502
1516
|
if (!gatewayHealthy) {
|
|
1503
1517
|
await terminateProcess(pid, options.platform);
|
|
@@ -1854,7 +1868,7 @@ function getBridgeStatus(stateDir, instanceId) {
|
|
|
1854
1868
|
}
|
|
1855
1869
|
return "running";
|
|
1856
1870
|
}
|
|
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,
|
|
1871
|
+
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;
|
|
1858
1872
|
var init_bridge = __esm({
|
|
1859
1873
|
"src/engine/bridge.ts"() {
|
|
1860
1874
|
"use strict";
|
|
@@ -1866,7 +1880,7 @@ var init_bridge = __esm({
|
|
|
1866
1880
|
APP_SERVER_START_TIMEOUT_MS = 2e4;
|
|
1867
1881
|
APP_SERVER_GATEWAY_START_TIMEOUT_MS = 5e3;
|
|
1868
1882
|
APP_SERVER_HEALTH_RETRY_MS = 250;
|
|
1869
|
-
|
|
1883
|
+
AUTH_SUBPROTOCOL_PREFIX = "tap-auth-";
|
|
1870
1884
|
APP_SERVER_AUTH_FILE_MODE = 384;
|
|
1871
1885
|
}
|
|
1872
1886
|
});
|
|
@@ -3049,11 +3063,11 @@ function redactProtectedUrl(url) {
|
|
|
3049
3063
|
try {
|
|
3050
3064
|
const parsed = new URL(url);
|
|
3051
3065
|
if (parsed.searchParams.has("tap_token")) {
|
|
3052
|
-
parsed.searchParams.
|
|
3066
|
+
parsed.searchParams.delete("tap_token");
|
|
3053
3067
|
}
|
|
3054
3068
|
return parsed.toString().replace(/\/$/, "");
|
|
3055
3069
|
} catch {
|
|
3056
|
-
return url.replace(/tap_token=[^&]+/g, "
|
|
3070
|
+
return url.replace(/[?&]tap_token=[^&]+/g, "");
|
|
3057
3071
|
}
|
|
3058
3072
|
}
|
|
3059
3073
|
function loadCurrentBridgeState(stateDir, instanceId, fallback) {
|
|
@@ -3136,37 +3150,37 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3136
3150
|
};
|
|
3137
3151
|
}
|
|
3138
3152
|
const instanceId = resolved.instanceId;
|
|
3139
|
-
let
|
|
3140
|
-
if (!
|
|
3153
|
+
let instance2 = state.instances[instanceId];
|
|
3154
|
+
if (!instance2?.installed) {
|
|
3141
3155
|
return {
|
|
3142
3156
|
ok: false,
|
|
3143
3157
|
command: "bridge",
|
|
3144
3158
|
instanceId,
|
|
3145
|
-
runtime:
|
|
3159
|
+
runtime: instance2?.runtime,
|
|
3146
3160
|
code: "TAP_INSTANCE_NOT_FOUND",
|
|
3147
|
-
message: `${instanceId} is not installed. Run: npx @hua-labs/tap add ${
|
|
3161
|
+
message: `${instanceId} is not installed. Run: npx @hua-labs/tap add ${instance2?.runtime ?? identifier}`,
|
|
3148
3162
|
warnings: [],
|
|
3149
3163
|
data: {}
|
|
3150
3164
|
};
|
|
3151
3165
|
}
|
|
3152
|
-
const adapter = getAdapter(
|
|
3166
|
+
const adapter = getAdapter(instance2.runtime);
|
|
3153
3167
|
const mode = adapter.bridgeMode();
|
|
3154
3168
|
if (mode !== "app-server") {
|
|
3155
3169
|
return {
|
|
3156
3170
|
ok: true,
|
|
3157
3171
|
command: "bridge",
|
|
3158
3172
|
instanceId,
|
|
3159
|
-
runtime:
|
|
3173
|
+
runtime: instance2.runtime,
|
|
3160
3174
|
code: "TAP_NO_OP",
|
|
3161
3175
|
message: `${instanceId} uses ${mode} mode \u2014 no bridge needed.`,
|
|
3162
3176
|
warnings: [],
|
|
3163
3177
|
data: { bridgeMode: mode }
|
|
3164
3178
|
};
|
|
3165
3179
|
}
|
|
3166
|
-
const resolvedAgentName = agentName ??
|
|
3167
|
-
if (agentName && agentName !==
|
|
3168
|
-
|
|
3169
|
-
const updatedState = updateInstanceState(state, instanceId,
|
|
3180
|
+
const resolvedAgentName = agentName ?? instance2.agentName ?? void 0;
|
|
3181
|
+
if (agentName && agentName !== instance2.agentName) {
|
|
3182
|
+
instance2 = { ...instance2, agentName };
|
|
3183
|
+
const updatedState = updateInstanceState(state, instanceId, instance2);
|
|
3170
3184
|
saveState(repoRoot, updatedState);
|
|
3171
3185
|
state = updatedState;
|
|
3172
3186
|
}
|
|
@@ -3177,7 +3191,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3177
3191
|
ok: false,
|
|
3178
3192
|
command: "bridge",
|
|
3179
3193
|
instanceId,
|
|
3180
|
-
runtime:
|
|
3194
|
+
runtime: instance2.runtime,
|
|
3181
3195
|
code: "TAP_BRIDGE_SCRIPT_MISSING",
|
|
3182
3196
|
message: `Bridge script not found for ${instanceId}. Ensure the runtime is properly configured.`,
|
|
3183
3197
|
warnings: [],
|
|
@@ -3186,8 +3200,8 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3186
3200
|
}
|
|
3187
3201
|
const { config: resolvedConfig } = resolveConfig({}, repoRoot);
|
|
3188
3202
|
const runtimeCommand = resolvedConfig.runtimeCommand;
|
|
3189
|
-
const manageAppServer =
|
|
3190
|
-
let effectivePort =
|
|
3203
|
+
const manageAppServer = instance2.runtime === "codex" && flags["no-server"] !== true;
|
|
3204
|
+
let effectivePort = instance2.port;
|
|
3191
3205
|
if (effectivePort == null && manageAppServer) {
|
|
3192
3206
|
effectivePort = await findNextAvailableAppServerPort(
|
|
3193
3207
|
state,
|
|
@@ -3195,8 +3209,8 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3195
3209
|
4501,
|
|
3196
3210
|
instanceId
|
|
3197
3211
|
);
|
|
3198
|
-
|
|
3199
|
-
const updatedState = updateInstanceState(state, instanceId,
|
|
3212
|
+
instance2 = { ...instance2, port: effectivePort };
|
|
3213
|
+
const updatedState = updateInstanceState(state, instanceId, instance2);
|
|
3200
3214
|
saveState(repoRoot, updatedState);
|
|
3201
3215
|
state = updatedState;
|
|
3202
3216
|
}
|
|
@@ -3212,19 +3226,19 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3212
3226
|
if (effectivePort != null) log(`Port: ${effectivePort}`);
|
|
3213
3227
|
if (resolvedAgentName) log(`Agent name: ${resolvedAgentName}`);
|
|
3214
3228
|
const noAuth = flags["no-auth"] === true;
|
|
3215
|
-
if (!manageAppServer &&
|
|
3229
|
+
if (!manageAppServer && instance2.runtime === "codex") {
|
|
3216
3230
|
log("Auto server: disabled (--no-server)");
|
|
3217
3231
|
}
|
|
3218
3232
|
if (noAuth && manageAppServer) {
|
|
3219
3233
|
log("Auth gateway: disabled (--no-auth)");
|
|
3220
3234
|
}
|
|
3221
|
-
const willBeHeadless = flags["headless"] === true ||
|
|
3235
|
+
const willBeHeadless = flags["headless"] === true || instance2.headless?.enabled;
|
|
3222
3236
|
if (willBeHeadless) {
|
|
3223
|
-
const role = (typeof flags["role"] === "string" ? flags["role"] : null) ??
|
|
3237
|
+
const role = (typeof flags["role"] === "string" ? flags["role"] : null) ?? instance2.headless?.role ?? "reviewer";
|
|
3224
3238
|
log(`Headless: ${role}`);
|
|
3225
3239
|
}
|
|
3226
3240
|
try {
|
|
3227
|
-
if (!manageAppServer &&
|
|
3241
|
+
if (!manageAppServer && instance2.runtime === "codex") {
|
|
3228
3242
|
log("Checking app-server health...");
|
|
3229
3243
|
const healthy = await checkAppServerHealth(appServerUrl);
|
|
3230
3244
|
if (healthy) {
|
|
@@ -3235,7 +3249,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3235
3249
|
ok: false,
|
|
3236
3250
|
command: "bridge",
|
|
3237
3251
|
instanceId,
|
|
3238
|
-
runtime:
|
|
3252
|
+
runtime: instance2.runtime,
|
|
3239
3253
|
code: "TAP_BRIDGE_START_FAILED",
|
|
3240
3254
|
message: `App server not reachable at ${appServerUrl}. Start it first: codex app-server --listen ${appServerUrl}`,
|
|
3241
3255
|
warnings: [],
|
|
@@ -3249,7 +3263,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3249
3263
|
ok: false,
|
|
3250
3264
|
command: "bridge",
|
|
3251
3265
|
instanceId,
|
|
3252
|
-
runtime:
|
|
3266
|
+
runtime: instance2.runtime,
|
|
3253
3267
|
code: "TAP_INVALID_ARGUMENT",
|
|
3254
3268
|
message: `Invalid --busy-mode: ${String(busyModeRaw)}. Must be "steer" or "wait".`,
|
|
3255
3269
|
warnings: [],
|
|
@@ -3257,9 +3271,38 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3257
3271
|
};
|
|
3258
3272
|
}
|
|
3259
3273
|
const busyMode = busyModeRaw;
|
|
3260
|
-
const
|
|
3261
|
-
const
|
|
3262
|
-
const
|
|
3274
|
+
const pollSecondsRaw = typeof flags["poll-seconds"] === "string" ? flags["poll-seconds"] : void 0;
|
|
3275
|
+
const reconnectSecondsRaw = typeof flags["reconnect-seconds"] === "string" ? flags["reconnect-seconds"] : void 0;
|
|
3276
|
+
const lookbackRaw = typeof flags["message-lookback-minutes"] === "string" ? flags["message-lookback-minutes"] : void 0;
|
|
3277
|
+
let pollSeconds;
|
|
3278
|
+
let reconnectSeconds;
|
|
3279
|
+
let messageLookbackMinutes;
|
|
3280
|
+
try {
|
|
3281
|
+
pollSeconds = parseIntFlag(pollSecondsRaw, "--poll-seconds", 1, 3600);
|
|
3282
|
+
reconnectSeconds = parseIntFlag(
|
|
3283
|
+
reconnectSecondsRaw,
|
|
3284
|
+
"--reconnect-seconds",
|
|
3285
|
+
1,
|
|
3286
|
+
3600
|
|
3287
|
+
);
|
|
3288
|
+
messageLookbackMinutes = parseIntFlag(
|
|
3289
|
+
lookbackRaw,
|
|
3290
|
+
"--message-lookback-minutes",
|
|
3291
|
+
1,
|
|
3292
|
+
10080
|
|
3293
|
+
);
|
|
3294
|
+
} catch (err) {
|
|
3295
|
+
return {
|
|
3296
|
+
ok: false,
|
|
3297
|
+
command: "bridge",
|
|
3298
|
+
instanceId,
|
|
3299
|
+
runtime: instance2.runtime,
|
|
3300
|
+
code: "TAP_INVALID_ARGUMENT",
|
|
3301
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3302
|
+
warnings: [],
|
|
3303
|
+
data: {}
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3263
3306
|
const threadId = typeof flags["thread-id"] === "string" ? flags["thread-id"] : void 0;
|
|
3264
3307
|
const ephemeral = flags["ephemeral"] === true;
|
|
3265
3308
|
const processExistingMessages = flags["process-existing-messages"] === true;
|
|
@@ -3271,7 +3314,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3271
3314
|
ok: false,
|
|
3272
3315
|
command: "bridge",
|
|
3273
3316
|
instanceId,
|
|
3274
|
-
runtime:
|
|
3317
|
+
runtime: instance2.runtime,
|
|
3275
3318
|
code: "TAP_INVALID_ARGUMENT",
|
|
3276
3319
|
message: `Invalid --role: ${roleArg}. Must be: ${validRoles.join(", ")}`,
|
|
3277
3320
|
warnings: [],
|
|
@@ -3283,10 +3326,10 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3283
3326
|
role: roleArg ?? "reviewer",
|
|
3284
3327
|
maxRounds: 5,
|
|
3285
3328
|
qualitySeverityFloor: "high"
|
|
3286
|
-
} :
|
|
3329
|
+
} : instance2.headless;
|
|
3287
3330
|
const bridge = await startBridge({
|
|
3288
3331
|
instanceId,
|
|
3289
|
-
runtime:
|
|
3332
|
+
runtime: instance2.runtime,
|
|
3290
3333
|
stateDir: ctx.stateDir,
|
|
3291
3334
|
commsDir: ctx.commsDir,
|
|
3292
3335
|
bridgeScript,
|
|
@@ -3327,14 +3370,14 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3327
3370
|
log(`TUI connect: ${bridge.appServer.url}`);
|
|
3328
3371
|
}
|
|
3329
3372
|
}
|
|
3330
|
-
const updated = { ...
|
|
3373
|
+
const updated = { ...instance2, bridge, manageAppServer, noAuth };
|
|
3331
3374
|
const newState = updateInstanceState(state, instanceId, updated);
|
|
3332
3375
|
saveState(repoRoot, newState);
|
|
3333
3376
|
return {
|
|
3334
3377
|
ok: true,
|
|
3335
3378
|
command: "bridge",
|
|
3336
3379
|
instanceId,
|
|
3337
|
-
runtime:
|
|
3380
|
+
runtime: instance2.runtime,
|
|
3338
3381
|
code: "TAP_BRIDGE_START_OK",
|
|
3339
3382
|
message: `Bridge for ${instanceId} started (PID: ${bridge.pid})`,
|
|
3340
3383
|
warnings: [],
|
|
@@ -3347,7 +3390,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3347
3390
|
ok: false,
|
|
3348
3391
|
command: "bridge",
|
|
3349
3392
|
instanceId,
|
|
3350
|
-
runtime:
|
|
3393
|
+
runtime: instance2.runtime,
|
|
3351
3394
|
code: "TAP_BRIDGE_START_FAILED",
|
|
3352
3395
|
message: msg,
|
|
3353
3396
|
warnings: [],
|
|
@@ -3449,11 +3492,11 @@ async function bridgeStopOne(identifier) {
|
|
|
3449
3492
|
}
|
|
3450
3493
|
const instanceId = resolved.instanceId;
|
|
3451
3494
|
const ctx = createAdapterContext(state.commsDir, repoRoot);
|
|
3452
|
-
const
|
|
3495
|
+
const instance2 = state.instances[instanceId];
|
|
3453
3496
|
const bridgeState = loadCurrentBridgeState(
|
|
3454
3497
|
ctx.stateDir,
|
|
3455
3498
|
instanceId,
|
|
3456
|
-
|
|
3499
|
+
instance2?.bridge
|
|
3457
3500
|
);
|
|
3458
3501
|
const appServer = bridgeState?.appServer ?? null;
|
|
3459
3502
|
logHeader(`@hua-labs/tap bridge stop ${instanceId}`);
|
|
@@ -3501,8 +3544,8 @@ async function bridgeStopOne(identifier) {
|
|
|
3501
3544
|
}
|
|
3502
3545
|
}
|
|
3503
3546
|
}
|
|
3504
|
-
if (
|
|
3505
|
-
const updated = { ...
|
|
3547
|
+
if (instance2) {
|
|
3548
|
+
const updated = { ...instance2, bridge: null };
|
|
3506
3549
|
const newState = updateInstanceState(state, instanceId, updated);
|
|
3507
3550
|
saveState(repoRoot, newState);
|
|
3508
3551
|
}
|
|
@@ -3574,9 +3617,9 @@ async function bridgeStopAll() {
|
|
|
3574
3617
|
logSuccess(`Stopped bridge for ${instanceId}`);
|
|
3575
3618
|
stopped.push(instanceId);
|
|
3576
3619
|
}
|
|
3577
|
-
const
|
|
3578
|
-
if (
|
|
3579
|
-
state.instances[instanceId] = { ...
|
|
3620
|
+
const instance2 = state.instances[instanceId];
|
|
3621
|
+
if (instance2?.bridge) {
|
|
3622
|
+
state.instances[instanceId] = { ...instance2, bridge: null };
|
|
3580
3623
|
stateChanged = true;
|
|
3581
3624
|
}
|
|
3582
3625
|
}
|
|
@@ -3872,8 +3915,22 @@ async function bridgeRestart(identifier, flags) {
|
|
|
3872
3915
|
};
|
|
3873
3916
|
}
|
|
3874
3917
|
const { config: resolvedConfig } = resolveConfig({}, repoRoot);
|
|
3875
|
-
const drainStr = typeof flags["drain-timeout"] === "string" ? flags["drain-timeout"] :
|
|
3876
|
-
|
|
3918
|
+
const drainStr = typeof flags["drain-timeout"] === "string" ? flags["drain-timeout"] : void 0;
|
|
3919
|
+
let drainTimeout;
|
|
3920
|
+
try {
|
|
3921
|
+
drainTimeout = parseIntFlag(drainStr, "--drain-timeout", 1, 300) ?? 30;
|
|
3922
|
+
} catch (err) {
|
|
3923
|
+
return {
|
|
3924
|
+
ok: false,
|
|
3925
|
+
command: "bridge",
|
|
3926
|
+
instanceId,
|
|
3927
|
+
runtime: instance.runtime,
|
|
3928
|
+
code: "TAP_INVALID_ARGUMENT",
|
|
3929
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3930
|
+
warnings: [],
|
|
3931
|
+
data: {}
|
|
3932
|
+
};
|
|
3933
|
+
}
|
|
3877
3934
|
logHeader(`@hua-labs/tap bridge restart ${instanceId}`);
|
|
3878
3935
|
log(`Drain timeout: ${drainTimeout}s`);
|
|
3879
3936
|
try {
|
|
@@ -4039,7 +4096,7 @@ Subcommands:
|
|
|
4039
4096
|
|
|
4040
4097
|
Options:
|
|
4041
4098
|
--agent-name <name> Agent identity for bridge (or set TAP_AGENT_NAME env)
|
|
4042
|
-
|
|
4099
|
+
Overrides the stored name from 'tap add' when needed
|
|
4043
4100
|
--all Start all registered app-server instances
|
|
4044
4101
|
--busy-mode <steer|wait> How to handle active turns (default: steer)
|
|
4045
4102
|
--poll-seconds <n> Inbox poll interval (default: 5)
|
|
@@ -4346,11 +4403,38 @@ function getConfig(options) {
|
|
|
4346
4403
|
import {
|
|
4347
4404
|
createServer as createServer2
|
|
4348
4405
|
} from "http";
|
|
4406
|
+
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
4349
4407
|
var CORS_HEADERS = {
|
|
4350
4408
|
"Access-Control-Allow-Origin": "http://localhost:3000",
|
|
4351
4409
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
4352
|
-
"Access-Control-Allow-Headers": "Content-Type"
|
|
4410
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
4353
4411
|
};
|
|
4412
|
+
function tokensMatch(presentedToken, expectedToken) {
|
|
4413
|
+
if (!presentedToken) {
|
|
4414
|
+
return false;
|
|
4415
|
+
}
|
|
4416
|
+
const presented = Buffer.from(presentedToken, "utf8");
|
|
4417
|
+
const expected = Buffer.from(expectedToken, "utf8");
|
|
4418
|
+
if (presented.length !== expected.length) {
|
|
4419
|
+
return false;
|
|
4420
|
+
}
|
|
4421
|
+
return timingSafeEqual(presented, expected);
|
|
4422
|
+
}
|
|
4423
|
+
function verifyBearerToken(req, expectedToken) {
|
|
4424
|
+
const header = req.headers.authorization;
|
|
4425
|
+
if (!header?.startsWith("Bearer ")) {
|
|
4426
|
+
return false;
|
|
4427
|
+
}
|
|
4428
|
+
return tokensMatch(header.slice(7), expectedToken);
|
|
4429
|
+
}
|
|
4430
|
+
function verifySseToken(req, expectedToken, serverUrl) {
|
|
4431
|
+
if (verifyBearerToken(req, expectedToken)) {
|
|
4432
|
+
return true;
|
|
4433
|
+
}
|
|
4434
|
+
const url = new URL(req.url ?? "/", serverUrl);
|
|
4435
|
+
const queryToken = url.searchParams.get("token");
|
|
4436
|
+
return tokensMatch(queryToken, expectedToken);
|
|
4437
|
+
}
|
|
4354
4438
|
function jsonResponse(res, data, status = 200) {
|
|
4355
4439
|
res.writeHead(status, {
|
|
4356
4440
|
"Content-Type": "application/json",
|
|
@@ -4393,6 +4477,7 @@ function handleHealth(res, apiOptions) {
|
|
|
4393
4477
|
async function startHttpServer(options) {
|
|
4394
4478
|
const port = options?.port ?? 4580;
|
|
4395
4479
|
const host = "127.0.0.1";
|
|
4480
|
+
const token = options?.token ?? randomBytes2(24).toString("base64url");
|
|
4396
4481
|
const apiOptions = {
|
|
4397
4482
|
repoRoot: options?.repoRoot,
|
|
4398
4483
|
commsDir: options?.commsDir
|
|
@@ -4406,21 +4491,32 @@ async function startHttpServer(options) {
|
|
|
4406
4491
|
res.end();
|
|
4407
4492
|
return;
|
|
4408
4493
|
}
|
|
4494
|
+
if (req.method === "GET" && pathname === "/health") {
|
|
4495
|
+
handleHealth(res, apiOptions);
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4498
|
+
if (req.method === "GET" && pathname === "/api/events") {
|
|
4499
|
+
const serverUrl = `http://${host}:${port}`;
|
|
4500
|
+
if (!verifySseToken(req, token, serverUrl)) {
|
|
4501
|
+
jsonResponse(res, { error: "Unauthorized" }, 401);
|
|
4502
|
+
return;
|
|
4503
|
+
}
|
|
4504
|
+
await handleEvents(req, res, apiOptions);
|
|
4505
|
+
return;
|
|
4506
|
+
}
|
|
4507
|
+
if (!verifyBearerToken(req, token)) {
|
|
4508
|
+
jsonResponse(res, { error: "Unauthorized" }, 401);
|
|
4509
|
+
return;
|
|
4510
|
+
}
|
|
4409
4511
|
try {
|
|
4410
4512
|
if (req.method === "GET") {
|
|
4411
4513
|
switch (pathname) {
|
|
4412
4514
|
case "/api/snapshot":
|
|
4413
4515
|
handleSnapshot(res, apiOptions);
|
|
4414
4516
|
return;
|
|
4415
|
-
case "/api/events":
|
|
4416
|
-
await handleEvents(req, res, apiOptions);
|
|
4417
|
-
return;
|
|
4418
4517
|
case "/api/config":
|
|
4419
4518
|
handleConfig(res, apiOptions);
|
|
4420
4519
|
return;
|
|
4421
|
-
case "/health":
|
|
4422
|
-
handleHealth(res, apiOptions);
|
|
4423
|
-
return;
|
|
4424
4520
|
}
|
|
4425
4521
|
}
|
|
4426
4522
|
if (req.method === "POST") {
|
|
@@ -4458,6 +4554,7 @@ async function startHttpServer(options) {
|
|
|
4458
4554
|
});
|
|
4459
4555
|
return {
|
|
4460
4556
|
port,
|
|
4557
|
+
token,
|
|
4461
4558
|
close: () => new Promise((resolve8, reject) => {
|
|
4462
4559
|
server.close((err) => err ? reject(err) : resolve8());
|
|
4463
4560
|
})
|