@hua-labs/tap 0.5.0 → 0.5.2
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 +24 -12
- package/dist/bridges/codex-app-server-bridge.d.mts +236 -5
- package/dist/bridges/codex-app-server-bridge.mjs +1168 -722
- package/dist/bridges/codex-app-server-bridge.mjs.map +1 -1
- package/dist/bridges/codex-bridge-runner.d.mts +2 -1
- package/dist/bridges/codex-bridge-runner.mjs +10 -2
- package/dist/bridges/codex-bridge-runner.mjs.map +1 -1
- package/dist/bridges/gemini-ide-companion-runner.mjs +0 -0
- package/dist/cli.mjs +415 -219
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +225 -82
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-server.mjs +267 -19
- package/dist/mcp-server.mjs.map +1 -1
- package/package.json +3 -2
package/dist/cli.mjs
CHANGED
|
@@ -1432,16 +1432,22 @@ import * as os2 from "os";
|
|
|
1432
1432
|
import * as path8 from "path";
|
|
1433
1433
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1434
1434
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1435
|
+
function resolveProbeCommand(candidate) {
|
|
1436
|
+
return resolveCommandPath(candidate) ?? candidate;
|
|
1437
|
+
}
|
|
1438
|
+
function probeCommandVersion(command) {
|
|
1439
|
+
return spawnSync2(command, ["--version"], {
|
|
1440
|
+
encoding: "utf-8",
|
|
1441
|
+
windowsHide: true
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1435
1444
|
function probeCommand(candidates) {
|
|
1436
1445
|
for (const candidate of candidates) {
|
|
1437
|
-
const
|
|
1438
|
-
|
|
1439
|
-
shell: process.platform === "win32"
|
|
1440
|
-
});
|
|
1446
|
+
const resolvedCommand = resolveProbeCommand(candidate);
|
|
1447
|
+
const result = probeCommandVersion(resolvedCommand);
|
|
1441
1448
|
if (result.status === 0) {
|
|
1442
1449
|
const version2 = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim() || null;
|
|
1443
|
-
|
|
1444
|
-
return { command: absolutePath ?? candidate, version: version2 };
|
|
1450
|
+
return { command: resolvedCommand, version: version2 };
|
|
1445
1451
|
}
|
|
1446
1452
|
}
|
|
1447
1453
|
return { command: null, version: null };
|
|
@@ -1543,19 +1549,16 @@ function findPreferredBunCommand() {
|
|
|
1543
1549
|
const candidates = process.platform === "win32" ? [path8.join(home, ".bun", "bin", "bun.exe"), "bun", "bun.cmd"] : [path8.join(home, ".bun", "bin", "bun"), "bun"];
|
|
1544
1550
|
for (const candidate of candidates) {
|
|
1545
1551
|
if (path8.isAbsolute(candidate) && !fs9.existsSync(candidate)) continue;
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1548
|
-
shell: process.platform === "win32"
|
|
1549
|
-
});
|
|
1552
|
+
const resolvedCommand = resolveProbeCommand(candidate);
|
|
1553
|
+
const result = probeCommandVersion(resolvedCommand);
|
|
1550
1554
|
if (result.status === 0) {
|
|
1551
|
-
return path8.isAbsolute(
|
|
1555
|
+
return path8.isAbsolute(resolvedCommand) ? toForwardSlashPath(resolvedCommand) : resolvedCommand;
|
|
1552
1556
|
}
|
|
1553
1557
|
}
|
|
1554
1558
|
return null;
|
|
1555
1559
|
}
|
|
1556
1560
|
function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
1557
1561
|
const sourcePath = findTapCommsServerEntry(ctx);
|
|
1558
|
-
const bunCommand = findPreferredBunCommand();
|
|
1559
1562
|
const warnings = [];
|
|
1560
1563
|
const issues = [];
|
|
1561
1564
|
const env = {
|
|
@@ -1575,7 +1578,7 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
1575
1578
|
}
|
|
1576
1579
|
const isBundled = sourcePath.endsWith(".mjs");
|
|
1577
1580
|
const isEphemeralSource = isEphemeralPath(sourcePath);
|
|
1578
|
-
let command
|
|
1581
|
+
let command;
|
|
1579
1582
|
let args = [toForwardSlashPath(sourcePath)];
|
|
1580
1583
|
if (isEphemeralSource && isBundled) {
|
|
1581
1584
|
command = "npx";
|
|
@@ -1589,7 +1592,7 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
1589
1592
|
);
|
|
1590
1593
|
command = nodeProbe.command ?? "node";
|
|
1591
1594
|
} else {
|
|
1592
|
-
command =
|
|
1595
|
+
command = findPreferredBunCommand();
|
|
1593
1596
|
}
|
|
1594
1597
|
if (!command) {
|
|
1595
1598
|
issues.push(
|
|
@@ -2930,13 +2933,14 @@ function startWindowsDetachedProcess(command, args, repoRoot, logPath, env = pro
|
|
|
2930
2933
|
}
|
|
2931
2934
|
return pid;
|
|
2932
2935
|
}
|
|
2933
|
-
function startWindowsCodexAppServer(command, url, repoRoot, logPath) {
|
|
2936
|
+
function startWindowsCodexAppServer(command, url, repoRoot, logPath, env = process.env) {
|
|
2934
2937
|
const { command: exe, prefixArgs } = splitResolvedCommand(command);
|
|
2935
2938
|
return startWindowsDetachedProcess(
|
|
2936
2939
|
exe,
|
|
2937
2940
|
[...prefixArgs, "app-server", "--listen", url],
|
|
2938
2941
|
repoRoot,
|
|
2939
|
-
logPath
|
|
2942
|
+
logPath,
|
|
2943
|
+
env
|
|
2940
2944
|
);
|
|
2941
2945
|
}
|
|
2942
2946
|
function findListeningProcessId(url, platform) {
|
|
@@ -3046,14 +3050,14 @@ function startUnixDetachedProcess(command, args, repoRoot, logPath, env = proces
|
|
|
3046
3050
|
}
|
|
3047
3051
|
}
|
|
3048
3052
|
}
|
|
3049
|
-
function startUnixCodexAppServer(command, url, repoRoot, logPath, platform = DEFAULT_UNIX_PLATFORM) {
|
|
3053
|
+
function startUnixCodexAppServer(command, url, repoRoot, logPath, env = process.env, platform = DEFAULT_UNIX_PLATFORM) {
|
|
3050
3054
|
const { command: exe, prefixArgs } = splitResolvedCommand(command);
|
|
3051
3055
|
return startUnixDetachedProcess(
|
|
3052
3056
|
exe,
|
|
3053
3057
|
[...prefixArgs, "app-server", "--listen", url],
|
|
3054
3058
|
repoRoot,
|
|
3055
3059
|
logPath,
|
|
3056
|
-
|
|
3060
|
+
env,
|
|
3057
3061
|
platform
|
|
3058
3062
|
);
|
|
3059
3063
|
}
|
|
@@ -4022,6 +4026,19 @@ import * as path21 from "path";
|
|
|
4022
4026
|
var DEFAULT_APP_SERVER_URL3 = "ws://127.0.0.1:4501";
|
|
4023
4027
|
var APP_SERVER_START_TIMEOUT_MS = 2e4;
|
|
4024
4028
|
var APP_SERVER_GATEWAY_START_TIMEOUT_MS = 5e3;
|
|
4029
|
+
function buildCodexAppServerEnv(options) {
|
|
4030
|
+
return {
|
|
4031
|
+
...process.env,
|
|
4032
|
+
TAP_COMMS_DIR: options.commsDir,
|
|
4033
|
+
TAP_STATE_DIR: options.stateDir,
|
|
4034
|
+
TAP_RUNTIME_STATE_DIR: options.runtimeStateDir,
|
|
4035
|
+
TAP_REPO_ROOT: options.repoRoot,
|
|
4036
|
+
TAP_BRIDGE_INSTANCE_ID: options.instanceId,
|
|
4037
|
+
TAP_AGENT_ID: options.instanceId,
|
|
4038
|
+
TAP_AGENT_NAME: options.agentName,
|
|
4039
|
+
CODEX_TAP_AGENT_NAME: options.agentName
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
4025
4042
|
function isAppServerUsedByOtherBridge(stateDir, excludeInstanceId, appServer) {
|
|
4026
4043
|
const pidDir = path21.join(stateDir, "pids");
|
|
4027
4044
|
if (!fs24.existsSync(pidDir)) return false;
|
|
@@ -4125,6 +4142,7 @@ Start the app-server manually:
|
|
|
4125
4142
|
const logPath = appServerLogFilePath(options.stateDir, options.instanceId);
|
|
4126
4143
|
fs24.mkdirSync(path21.dirname(logPath), { recursive: true });
|
|
4127
4144
|
rotateLog(logPath);
|
|
4145
|
+
const appServerEnv = buildCodexAppServerEnv(options);
|
|
4128
4146
|
if (options.noAuth) {
|
|
4129
4147
|
const manualCommand2 = formatCodexAppServerCommand("codex", effectiveUrl);
|
|
4130
4148
|
let pid2;
|
|
@@ -4134,7 +4152,8 @@ Start the app-server manually:
|
|
|
4134
4152
|
resolvedCommand,
|
|
4135
4153
|
effectiveUrl,
|
|
4136
4154
|
options.repoRoot,
|
|
4137
|
-
logPath
|
|
4155
|
+
logPath,
|
|
4156
|
+
appServerEnv
|
|
4138
4157
|
);
|
|
4139
4158
|
} catch (err) {
|
|
4140
4159
|
throw new Error(
|
|
@@ -4151,6 +4170,7 @@ Start it manually:
|
|
|
4151
4170
|
effectiveUrl,
|
|
4152
4171
|
options.repoRoot,
|
|
4153
4172
|
logPath,
|
|
4173
|
+
appServerEnv,
|
|
4154
4174
|
options.platform
|
|
4155
4175
|
);
|
|
4156
4176
|
} catch (err) {
|
|
@@ -4211,7 +4231,8 @@ Or start it manually:
|
|
|
4211
4231
|
resolvedCommand,
|
|
4212
4232
|
auth.upstreamUrl,
|
|
4213
4233
|
options.repoRoot,
|
|
4214
|
-
logPath
|
|
4234
|
+
logPath,
|
|
4235
|
+
appServerEnv
|
|
4215
4236
|
);
|
|
4216
4237
|
} catch (err) {
|
|
4217
4238
|
if (auth.gatewayPid != null) {
|
|
@@ -4232,6 +4253,7 @@ Start it manually:
|
|
|
4232
4253
|
auth.upstreamUrl,
|
|
4233
4254
|
options.repoRoot,
|
|
4234
4255
|
logPath,
|
|
4256
|
+
appServerEnv,
|
|
4235
4257
|
options.platform
|
|
4236
4258
|
);
|
|
4237
4259
|
} catch (err) {
|
|
@@ -4444,9 +4466,12 @@ async function startBridge(options) {
|
|
|
4444
4466
|
appServer = await ensureCodexAppServer({
|
|
4445
4467
|
instanceId,
|
|
4446
4468
|
stateDir,
|
|
4469
|
+
runtimeStateDir,
|
|
4470
|
+
commsDir,
|
|
4447
4471
|
repoRoot,
|
|
4448
4472
|
platform: options.platform,
|
|
4449
4473
|
appServerUrl: effectiveAppServerUrl,
|
|
4474
|
+
agentName: resolvedAgent,
|
|
4450
4475
|
existingAppServer: previousAppServer,
|
|
4451
4476
|
noAuth: options.noAuth
|
|
4452
4477
|
});
|
|
@@ -4467,7 +4492,9 @@ async function startBridge(options) {
|
|
|
4467
4492
|
const bridgeEnv = {
|
|
4468
4493
|
...runtimeEnv,
|
|
4469
4494
|
TAP_COMMS_DIR: commsDir,
|
|
4470
|
-
TAP_STATE_DIR:
|
|
4495
|
+
TAP_STATE_DIR: stateDir,
|
|
4496
|
+
TAP_RUNTIME_STATE_DIR: runtimeStateDir,
|
|
4497
|
+
TAP_REPO_ROOT: repoRoot,
|
|
4471
4498
|
TAP_BRIDGE_RUNTIME: runtime,
|
|
4472
4499
|
TAP_BRIDGE_INSTANCE_ID: instanceId,
|
|
4473
4500
|
TAP_AGENT_ID: instanceId,
|
|
@@ -5051,6 +5078,53 @@ async function addCommand(args) {
|
|
|
5051
5078
|
};
|
|
5052
5079
|
}
|
|
5053
5080
|
|
|
5081
|
+
// src/engine/health-monitor.ts
|
|
5082
|
+
import * as fs27 from "fs";
|
|
5083
|
+
import * as path24 from "path";
|
|
5084
|
+
var DISPATCH_EVIDENCE_FRESH_THRESHOLD_MS = 2 * 60 * 1e3;
|
|
5085
|
+
function getHeartbeatActivityMs2(record) {
|
|
5086
|
+
const timestamp = new Date(record.lastActivity ?? record.timestamp ?? 0).getTime();
|
|
5087
|
+
return Number.isFinite(timestamp) ? timestamp : null;
|
|
5088
|
+
}
|
|
5089
|
+
function isSameInstanceHeartbeat2(key, heartbeat, instanceId) {
|
|
5090
|
+
if (heartbeat.instanceId === instanceId) return true;
|
|
5091
|
+
if (heartbeat.connectHash === `instance:${instanceId}`) return true;
|
|
5092
|
+
return key === instanceId || key.replace(/_/g, "-") === instanceId || key.replace(/-/g, "_") === instanceId;
|
|
5093
|
+
}
|
|
5094
|
+
function loadLiveDispatchEvidence(commsDir, instanceId) {
|
|
5095
|
+
const heartbeatsPath = path24.join(commsDir, "heartbeats.json");
|
|
5096
|
+
if (!fs27.existsSync(heartbeatsPath)) return null;
|
|
5097
|
+
try {
|
|
5098
|
+
const store = JSON.parse(
|
|
5099
|
+
fs27.readFileSync(heartbeatsPath, "utf-8")
|
|
5100
|
+
);
|
|
5101
|
+
let best = null;
|
|
5102
|
+
let bestActivityMs = -1;
|
|
5103
|
+
for (const [key, heartbeat] of Object.entries(store)) {
|
|
5104
|
+
if (!isSameInstanceHeartbeat2(key, heartbeat, instanceId)) continue;
|
|
5105
|
+
if (heartbeat.source !== "bridge-dispatch") continue;
|
|
5106
|
+
if (heartbeat.bridgePid == null || !isProcessAlive(heartbeat.bridgePid)) {
|
|
5107
|
+
continue;
|
|
5108
|
+
}
|
|
5109
|
+
const activityMs = getHeartbeatActivityMs2(heartbeat);
|
|
5110
|
+
if (activityMs == null || Date.now() - activityMs > DISPATCH_EVIDENCE_FRESH_THRESHOLD_MS) {
|
|
5111
|
+
continue;
|
|
5112
|
+
}
|
|
5113
|
+
if (activityMs > bestActivityMs) {
|
|
5114
|
+
bestActivityMs = activityMs;
|
|
5115
|
+
best = {
|
|
5116
|
+
bridgePid: heartbeat.bridgePid,
|
|
5117
|
+
lastActivity: heartbeat.lastActivity ?? heartbeat.timestamp ?? new Date(activityMs).toISOString()
|
|
5118
|
+
};
|
|
5119
|
+
}
|
|
5120
|
+
}
|
|
5121
|
+
return best;
|
|
5122
|
+
} catch {
|
|
5123
|
+
return null;
|
|
5124
|
+
}
|
|
5125
|
+
}
|
|
5126
|
+
var HEARTBEAT_FRESH_THRESHOLD_MS = 2 * 60 * 1e3;
|
|
5127
|
+
|
|
5054
5128
|
// src/commands/status.ts
|
|
5055
5129
|
init_config();
|
|
5056
5130
|
init_utils();
|
|
@@ -5064,12 +5138,13 @@ Description:
|
|
|
5064
5138
|
Examples:
|
|
5065
5139
|
npx @hua-labs/tap status
|
|
5066
5140
|
`.trim();
|
|
5067
|
-
function resolveStatus(inst, stateDir) {
|
|
5141
|
+
function resolveStatus(inst, stateDir, commsDir) {
|
|
5068
5142
|
if (!inst.installed) {
|
|
5069
5143
|
return {
|
|
5070
5144
|
status: "not installed",
|
|
5071
5145
|
lifecycle: null,
|
|
5072
|
-
session: null
|
|
5146
|
+
session: null,
|
|
5147
|
+
warnings: []
|
|
5073
5148
|
};
|
|
5074
5149
|
}
|
|
5075
5150
|
switch (inst.bridgeMode) {
|
|
@@ -5078,9 +5153,11 @@ function resolveStatus(inst, stateDir) {
|
|
|
5078
5153
|
return {
|
|
5079
5154
|
status: inst.lastVerifiedAt ? "active" : "configured",
|
|
5080
5155
|
lifecycle: null,
|
|
5081
|
-
session: null
|
|
5156
|
+
session: null,
|
|
5157
|
+
warnings: []
|
|
5082
5158
|
};
|
|
5083
5159
|
case "app-server": {
|
|
5160
|
+
let staleLifecycle = null;
|
|
5084
5161
|
if (inst.bridge) {
|
|
5085
5162
|
const lifecycle = resolveBridgeLifecycleSnapshot(
|
|
5086
5163
|
stateDir,
|
|
@@ -5088,21 +5165,40 @@ function resolveStatus(inst, stateDir) {
|
|
|
5088
5165
|
inst.bridge
|
|
5089
5166
|
);
|
|
5090
5167
|
if (lifecycle.status === "bridge-stale") {
|
|
5168
|
+
staleLifecycle = lifecycle;
|
|
5091
5169
|
inst.bridge = null;
|
|
5170
|
+
} else {
|
|
5171
|
+
const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(inst.bridge);
|
|
5092
5172
|
return {
|
|
5093
|
-
status:
|
|
5173
|
+
status: "active",
|
|
5094
5174
|
lifecycle,
|
|
5095
|
-
session:
|
|
5175
|
+
session: deriveCodexSessionState({
|
|
5176
|
+
runtimeHeartbeat,
|
|
5177
|
+
runtimeStateDir: inst.bridge.runtimeStateDir ?? null
|
|
5178
|
+
}),
|
|
5179
|
+
warnings: []
|
|
5096
5180
|
};
|
|
5097
5181
|
}
|
|
5098
|
-
|
|
5182
|
+
}
|
|
5183
|
+
const liveDispatch = loadLiveDispatchEvidence(commsDir, inst.instanceId);
|
|
5184
|
+
if (liveDispatch) {
|
|
5099
5185
|
return {
|
|
5100
|
-
status: "
|
|
5101
|
-
lifecycle
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5186
|
+
status: "dispatch-live",
|
|
5187
|
+
lifecycle: deriveBridgeLifecycleState({
|
|
5188
|
+
bridgeStatus: "stopped"
|
|
5189
|
+
}),
|
|
5190
|
+
session: deriveCodexSessionState({ runtimeHeartbeat: null }),
|
|
5191
|
+
warnings: [
|
|
5192
|
+
`fresh bridge-dispatch heartbeat from PID ${liveDispatch.bridgePid} without bridge pid state`
|
|
5193
|
+
]
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
if (staleLifecycle) {
|
|
5197
|
+
return {
|
|
5198
|
+
status: inst.lastVerifiedAt ? "configured" : "installed",
|
|
5199
|
+
lifecycle: staleLifecycle,
|
|
5200
|
+
session: null,
|
|
5201
|
+
warnings: []
|
|
5106
5202
|
};
|
|
5107
5203
|
}
|
|
5108
5204
|
return {
|
|
@@ -5110,25 +5206,27 @@ function resolveStatus(inst, stateDir) {
|
|
|
5110
5206
|
lifecycle: deriveBridgeLifecycleState({
|
|
5111
5207
|
bridgeStatus: "stopped"
|
|
5112
5208
|
}),
|
|
5113
|
-
session: deriveCodexSessionState({ runtimeHeartbeat: null })
|
|
5209
|
+
session: deriveCodexSessionState({ runtimeHeartbeat: null }),
|
|
5210
|
+
warnings: []
|
|
5114
5211
|
};
|
|
5115
5212
|
}
|
|
5116
5213
|
default:
|
|
5117
5214
|
return {
|
|
5118
5215
|
status: "installed",
|
|
5119
5216
|
lifecycle: null,
|
|
5120
|
-
session: null
|
|
5217
|
+
session: null,
|
|
5218
|
+
warnings: []
|
|
5121
5219
|
};
|
|
5122
5220
|
}
|
|
5123
5221
|
}
|
|
5124
|
-
function instanceStatusLine(inst, status, lifecycle, session) {
|
|
5222
|
+
function instanceStatusLine(inst, status, lifecycle, session, warnings) {
|
|
5125
5223
|
const bridgeInfo = inst.bridge ? ` (pid: ${inst.bridge.pid})` : "";
|
|
5126
5224
|
const lifecycleStr = lifecycle?.status ?? "-";
|
|
5127
5225
|
const sessionStr = session?.status ?? "-";
|
|
5128
5226
|
const mode = inst.bridgeMode;
|
|
5129
5227
|
const portStr = inst.port ? ` port:${inst.port}` : "";
|
|
5130
5228
|
const restart = inst.restartRequired ? " [restart required]" : "";
|
|
5131
|
-
const warns =
|
|
5229
|
+
const warns = warnings.length > 0 ? ` [${warnings.length} warning(s)]` : "";
|
|
5132
5230
|
return `${inst.instanceId.padEnd(20)} ${inst.runtime.padEnd(8)} ${status.padEnd(14)} ${lifecycleStr.padEnd(20)} ${sessionStr.padEnd(18)} ${mode.padEnd(14)}${bridgeInfo}${portStr}${restart}${warns}`;
|
|
5133
5231
|
}
|
|
5134
5232
|
async function statusCommand(args) {
|
|
@@ -5181,10 +5279,15 @@ async function statusCommand(args) {
|
|
|
5181
5279
|
for (const id of installed) {
|
|
5182
5280
|
const inst = state.instances[id];
|
|
5183
5281
|
if (inst) {
|
|
5184
|
-
const { status, lifecycle, session } = resolveStatus(
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5282
|
+
const { status, lifecycle, session, warnings } = resolveStatus(
|
|
5283
|
+
inst,
|
|
5284
|
+
stateDir,
|
|
5285
|
+
state.commsDir
|
|
5286
|
+
);
|
|
5287
|
+
const mergedWarnings = [...inst.warnings, ...warnings];
|
|
5288
|
+
log(instanceStatusLine(inst, status, lifecycle, session, mergedWarnings));
|
|
5289
|
+
if (mergedWarnings.length > 0) {
|
|
5290
|
+
for (const w of mergedWarnings) {
|
|
5188
5291
|
logWarn(` ${w}`);
|
|
5189
5292
|
}
|
|
5190
5293
|
}
|
|
@@ -5196,7 +5299,7 @@ async function statusCommand(args) {
|
|
|
5196
5299
|
bridgeMode: inst.bridgeMode,
|
|
5197
5300
|
bridge: inst.bridge,
|
|
5198
5301
|
port: inst.port,
|
|
5199
|
-
warnings:
|
|
5302
|
+
warnings: mergedWarnings
|
|
5200
5303
|
};
|
|
5201
5304
|
}
|
|
5202
5305
|
}
|
|
@@ -5227,7 +5330,7 @@ async function statusCommand(args) {
|
|
|
5227
5330
|
init_utils();
|
|
5228
5331
|
|
|
5229
5332
|
// src/engine/rollback.ts
|
|
5230
|
-
import * as
|
|
5333
|
+
import * as fs28 from "fs";
|
|
5231
5334
|
async function rollbackRuntime(_instanceId, runtimeState) {
|
|
5232
5335
|
const errors = [];
|
|
5233
5336
|
const restoredFiles = [];
|
|
@@ -5256,7 +5359,7 @@ async function rollbackRuntime(_instanceId, runtimeState) {
|
|
|
5256
5359
|
};
|
|
5257
5360
|
}
|
|
5258
5361
|
function rollbackArtifact(artifact) {
|
|
5259
|
-
if (!
|
|
5362
|
+
if (!fs28.existsSync(artifact.path)) {
|
|
5260
5363
|
return { restored: false, error: `File not found: ${artifact.path}` };
|
|
5261
5364
|
}
|
|
5262
5365
|
switch (artifact.kind) {
|
|
@@ -5274,7 +5377,7 @@ function rollbackArtifact(artifact) {
|
|
|
5274
5377
|
}
|
|
5275
5378
|
}
|
|
5276
5379
|
function rollbackJsonPath(artifact) {
|
|
5277
|
-
const raw =
|
|
5380
|
+
const raw = fs28.readFileSync(artifact.path, "utf-8");
|
|
5278
5381
|
let config;
|
|
5279
5382
|
try {
|
|
5280
5383
|
config = JSON.parse(raw);
|
|
@@ -5300,18 +5403,18 @@ function rollbackJsonPath(artifact) {
|
|
|
5300
5403
|
cleanEmptyParents(config, artifact.selector);
|
|
5301
5404
|
}
|
|
5302
5405
|
const tmp = `${artifact.path}.tmp.${process.pid}`;
|
|
5303
|
-
|
|
5304
|
-
|
|
5406
|
+
fs28.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
5407
|
+
fs28.renameSync(tmp, artifact.path);
|
|
5305
5408
|
return { restored: true };
|
|
5306
5409
|
}
|
|
5307
5410
|
function rollbackTomlTable(artifact) {
|
|
5308
|
-
const content =
|
|
5411
|
+
const content = fs28.readFileSync(artifact.path, "utf-8");
|
|
5309
5412
|
const backup = artifact.backupPath ? readArtifactBackup(artifact.backupPath) : null;
|
|
5310
5413
|
if (backup?.kind === "toml-table" && backup.selector === artifact.selector) {
|
|
5311
5414
|
const nextContent = backup.existed ? replaceTomlTable(content, artifact.selector, backup.content ?? "") : removeTomlTable(content, artifact.selector);
|
|
5312
5415
|
const tmp2 = `${artifact.path}.tmp.${process.pid}`;
|
|
5313
|
-
|
|
5314
|
-
|
|
5416
|
+
fs28.writeFileSync(tmp2, nextContent, "utf-8");
|
|
5417
|
+
fs28.renameSync(tmp2, artifact.path);
|
|
5315
5418
|
return { restored: true };
|
|
5316
5419
|
}
|
|
5317
5420
|
if (!extractTomlTable(content, artifact.selector)) {
|
|
@@ -5321,13 +5424,13 @@ function rollbackTomlTable(artifact) {
|
|
|
5321
5424
|
};
|
|
5322
5425
|
}
|
|
5323
5426
|
const tmp = `${artifact.path}.tmp.${process.pid}`;
|
|
5324
|
-
|
|
5325
|
-
|
|
5427
|
+
fs28.writeFileSync(tmp, removeTomlTable(content, artifact.selector), "utf-8");
|
|
5428
|
+
fs28.renameSync(tmp, artifact.path);
|
|
5326
5429
|
return { restored: true };
|
|
5327
5430
|
}
|
|
5328
5431
|
function rollbackFile(artifact) {
|
|
5329
|
-
if (
|
|
5330
|
-
|
|
5432
|
+
if (fs28.existsSync(artifact.path)) {
|
|
5433
|
+
fs28.unlinkSync(artifact.path);
|
|
5331
5434
|
return { restored: true };
|
|
5332
5435
|
}
|
|
5333
5436
|
return { restored: false, error: `File not found: ${artifact.path}` };
|
|
@@ -5502,13 +5605,13 @@ async function removeCommand(args) {
|
|
|
5502
5605
|
init_utils();
|
|
5503
5606
|
|
|
5504
5607
|
// src/commands/bridge-start.ts
|
|
5505
|
-
import * as
|
|
5608
|
+
import * as path27 from "path";
|
|
5506
5609
|
init_instance_config();
|
|
5507
5610
|
init_config();
|
|
5508
5611
|
init_utils();
|
|
5509
5612
|
|
|
5510
5613
|
// src/commands/bridge-helpers.ts
|
|
5511
|
-
import * as
|
|
5614
|
+
import * as path25 from "path";
|
|
5512
5615
|
function formatAge(seconds) {
|
|
5513
5616
|
if (seconds < 60) return `${seconds}s ago`;
|
|
5514
5617
|
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
@@ -5538,8 +5641,24 @@ function resolveTuiConnectUrl(appServer) {
|
|
|
5538
5641
|
function quoteCliArg(value) {
|
|
5539
5642
|
return `"${value.replace(/"/g, '\\"')}"`;
|
|
5540
5643
|
}
|
|
5541
|
-
function
|
|
5542
|
-
|
|
5644
|
+
function quoteShellEnvValue(value) {
|
|
5645
|
+
if (process.platform === "win32") {
|
|
5646
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
5647
|
+
}
|
|
5648
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
5649
|
+
}
|
|
5650
|
+
function formatCodexTuiAttachCommand(tuiConnectUrl, cwd, env = {}) {
|
|
5651
|
+
const base = `codex --enable tui_app_server --remote ${quoteCliArg(tuiConnectUrl)} --cd ${quoteCliArg(cwd)}`;
|
|
5652
|
+
const entries = Object.entries(env).filter(([, value]) => value.length > 0);
|
|
5653
|
+
if (entries.length === 0) {
|
|
5654
|
+
return base;
|
|
5655
|
+
}
|
|
5656
|
+
if (process.platform === "win32") {
|
|
5657
|
+
const envPrefix2 = entries.map(([key, value]) => `$env:${key} = ${quoteShellEnvValue(value)}`).join("; ");
|
|
5658
|
+
return `${envPrefix2}; ${base}`;
|
|
5659
|
+
}
|
|
5660
|
+
const envPrefix = entries.map(([key, value]) => `${key}=${quoteShellEnvValue(value)}`).join(" ");
|
|
5661
|
+
return `${envPrefix} ${base}`;
|
|
5543
5662
|
}
|
|
5544
5663
|
function resolveTuiAttachCwd(repoRoot, stateRepoRoot, runtimeThreadCwd, savedThreadCwd) {
|
|
5545
5664
|
return runtimeThreadCwd ?? savedThreadCwd ?? stateRepoRoot ?? repoRoot;
|
|
@@ -5554,7 +5673,7 @@ function formatThreadSummary(threadId, cwd) {
|
|
|
5554
5673
|
return cwd ? `${threadId} (${cwd})` : threadId;
|
|
5555
5674
|
}
|
|
5556
5675
|
function normalizeComparablePath(value) {
|
|
5557
|
-
return
|
|
5676
|
+
return path25.resolve(value).replace(/\\/g, "/").toLowerCase();
|
|
5558
5677
|
}
|
|
5559
5678
|
function sameOptionalPath(left, right) {
|
|
5560
5679
|
if (!left || !right) {
|
|
@@ -5626,22 +5745,22 @@ function transferManagedAppServerOwnership(state, stateDir, recipientId, appServ
|
|
|
5626
5745
|
}
|
|
5627
5746
|
|
|
5628
5747
|
// src/commands/bridge-heartbeat.ts
|
|
5629
|
-
import { existsSync as
|
|
5630
|
-
import * as
|
|
5748
|
+
import { existsSync as existsSync26, readFileSync as readFileSync22, renameSync as renameSync13, writeFileSync as writeFileSync14 } from "fs";
|
|
5749
|
+
import * as path26 from "path";
|
|
5631
5750
|
var BRIDGE_UP_ACTIVE_HEARTBEAT_WINDOW_MS = 10 * 60 * 1e3;
|
|
5632
5751
|
var BRIDGE_UP_ORPHAN_HEARTBEAT_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
5633
5752
|
var BRIDGE_UP_SIGNING_OFF_HEARTBEAT_WINDOW_MS = 5 * 60 * 1e3;
|
|
5634
5753
|
function loadBridgeHeartbeatStore(commsDir) {
|
|
5635
|
-
const heartbeatsPath =
|
|
5636
|
-
if (!
|
|
5754
|
+
const heartbeatsPath = path26.join(commsDir, "heartbeats.json");
|
|
5755
|
+
if (!existsSync26(heartbeatsPath)) return {};
|
|
5637
5756
|
try {
|
|
5638
|
-
return JSON.parse(
|
|
5757
|
+
return JSON.parse(readFileSync22(heartbeatsPath, "utf-8"));
|
|
5639
5758
|
} catch {
|
|
5640
5759
|
return null;
|
|
5641
5760
|
}
|
|
5642
5761
|
}
|
|
5643
5762
|
function saveBridgeHeartbeatStore(commsDir, store) {
|
|
5644
|
-
const heartbeatsPath =
|
|
5763
|
+
const heartbeatsPath = path26.join(commsDir, "heartbeats.json");
|
|
5645
5764
|
const tmp = `${heartbeatsPath}.tmp.${process.pid}`;
|
|
5646
5765
|
writeFileSync14(tmp, JSON.stringify(store, null, 2), "utf-8");
|
|
5647
5766
|
renameSync13(tmp, heartbeatsPath);
|
|
@@ -5948,7 +6067,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
5948
6067
|
}
|
|
5949
6068
|
}
|
|
5950
6069
|
logSuccess(`Bridge started (PID: ${bridge.pid})`);
|
|
5951
|
-
log(`Log: ${
|
|
6070
|
+
log(`Log: ${path27.join(ctx.stateDir, "logs", `bridge-${instanceId}.log`)}`);
|
|
5952
6071
|
if (bridge.appServer) {
|
|
5953
6072
|
log(`App server: ${formatAppServerState(bridge.appServer)}`);
|
|
5954
6073
|
if (bridge.appServer.logPath) {
|
|
@@ -6073,7 +6192,7 @@ async function bridgeStartAll(flags = {}) {
|
|
|
6073
6192
|
warnings.push(msg);
|
|
6074
6193
|
continue;
|
|
6075
6194
|
}
|
|
6076
|
-
const stateDir =
|
|
6195
|
+
const stateDir = path27.join(repoRoot, ".tap-comms");
|
|
6077
6196
|
const currentBridgeState = loadBridgeState(stateDir, instanceId);
|
|
6078
6197
|
const { manageAppServer, noAuth } = inferRestartMode(
|
|
6079
6198
|
currentBridgeState,
|
|
@@ -6492,7 +6611,7 @@ async function bridgeWatch(_intervalSeconds, stuckThresholdSeconds) {
|
|
|
6492
6611
|
}
|
|
6493
6612
|
|
|
6494
6613
|
// src/commands/bridge-status.ts
|
|
6495
|
-
import * as
|
|
6614
|
+
import * as path28 from "path";
|
|
6496
6615
|
init_config();
|
|
6497
6616
|
init_utils();
|
|
6498
6617
|
function bridgeStatusAll() {
|
|
@@ -6543,23 +6662,26 @@ function bridgeStatusAll() {
|
|
|
6543
6662
|
};
|
|
6544
6663
|
continue;
|
|
6545
6664
|
}
|
|
6546
|
-
const
|
|
6665
|
+
const rawStatus = getBridgeStatus(stateDir, instanceId);
|
|
6547
6666
|
const bridgeState = loadBridgeState(stateDir, instanceId) ?? inst.bridge;
|
|
6548
|
-
const
|
|
6549
|
-
const
|
|
6550
|
-
const
|
|
6551
|
-
|
|
6667
|
+
const liveDispatch = rawStatus === "running" ? null : loadLiveDispatchEvidence(state.commsDir, instanceId);
|
|
6668
|
+
const surfaceBridgeState = liveDispatch ? null : bridgeState;
|
|
6669
|
+
const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(surfaceBridgeState);
|
|
6670
|
+
const savedThread = loadRuntimeBridgeThreadState(surfaceBridgeState);
|
|
6671
|
+
const status = liveDispatch ? "dispatch-live" : rawStatus;
|
|
6672
|
+
const lifecycle = liveDispatch ? deriveBridgeLifecycleState({ bridgeStatus: "stopped" }) : deriveBridgeLifecycleState({
|
|
6673
|
+
bridgeStatus: rawStatus,
|
|
6552
6674
|
bridgeState,
|
|
6553
6675
|
runtimeHeartbeat,
|
|
6554
6676
|
savedThread,
|
|
6555
6677
|
persistedLifecycle: inst.bridgeLifecycle ?? bridgeState?.lifecycle ?? null
|
|
6556
6678
|
});
|
|
6557
|
-
const session =
|
|
6679
|
+
const session = rawStatus === "running" || liveDispatch ? deriveCodexSessionState({
|
|
6558
6680
|
runtimeHeartbeat,
|
|
6559
|
-
runtimeStateDir:
|
|
6681
|
+
runtimeStateDir: surfaceBridgeState?.runtimeStateDir ?? null
|
|
6560
6682
|
}) : null;
|
|
6561
|
-
const age = getHeartbeatAge(stateDir, instanceId);
|
|
6562
|
-
if (
|
|
6683
|
+
const age = liveDispatch ? null : getHeartbeatAge(stateDir, instanceId);
|
|
6684
|
+
if (rawStatus === "stale" && inst.bridge) {
|
|
6563
6685
|
state.instances[instanceId] = {
|
|
6564
6686
|
...inst,
|
|
6565
6687
|
bridge: null,
|
|
@@ -6571,22 +6693,22 @@ function bridgeStatusAll() {
|
|
|
6571
6693
|
};
|
|
6572
6694
|
stateChanged = true;
|
|
6573
6695
|
}
|
|
6574
|
-
const pid =
|
|
6575
|
-
const heartbeat = getBridgeHeartbeatTimestamp(stateDir, instanceId);
|
|
6696
|
+
const pid = surfaceBridgeState?.pid ?? null;
|
|
6697
|
+
const heartbeat = liveDispatch ? null : getBridgeHeartbeatTimestamp(stateDir, instanceId);
|
|
6576
6698
|
const pidStr = pid ? String(pid) : "-";
|
|
6577
6699
|
const portStr = inst.port ? String(inst.port) : "-";
|
|
6578
6700
|
const ageStr = age !== null ? formatAge(age) : "-";
|
|
6579
6701
|
log(
|
|
6580
6702
|
`${instanceId.padEnd(20)} ${inst.runtime.padEnd(8)} ${status.padEnd(10)} ${lifecycle.status.padEnd(20)} ${(session?.status ?? "-").padEnd(18)} ${pidStr.padEnd(8)} ${portStr.padEnd(6)} ${ageStr}`
|
|
6581
6703
|
);
|
|
6582
|
-
if (
|
|
6583
|
-
log(` App server: ${formatAppServerState(
|
|
6584
|
-
if (
|
|
6585
|
-
log(` Server log: ${
|
|
6704
|
+
if (surfaceBridgeState?.appServer) {
|
|
6705
|
+
log(` App server: ${formatAppServerState(surfaceBridgeState.appServer)}`);
|
|
6706
|
+
if (surfaceBridgeState.appServer.logPath) {
|
|
6707
|
+
log(` Server log: ${surfaceBridgeState.appServer.logPath}`);
|
|
6586
6708
|
}
|
|
6587
|
-
if (
|
|
6709
|
+
if (surfaceBridgeState.appServer.auth) {
|
|
6588
6710
|
log(
|
|
6589
|
-
` Protected: ${redactProtectedUrl(
|
|
6711
|
+
` Protected: ${redactProtectedUrl(surfaceBridgeState.appServer.auth.protectedUrl)}`
|
|
6590
6712
|
);
|
|
6591
6713
|
}
|
|
6592
6714
|
}
|
|
@@ -6604,6 +6726,11 @@ function bridgeStatusAll() {
|
|
|
6604
6726
|
if (transition) {
|
|
6605
6727
|
log(` Transition: ${transition}`);
|
|
6606
6728
|
}
|
|
6729
|
+
if (liveDispatch) {
|
|
6730
|
+
log(
|
|
6731
|
+
` Drift: fresh bridge-dispatch heartbeat from PID ${liveDispatch.bridgePid} without bridge pid state`
|
|
6732
|
+
);
|
|
6733
|
+
}
|
|
6607
6734
|
const turnInfo = getTurnInfo(stateDir, instanceId);
|
|
6608
6735
|
if (turnInfo?.activeTurnId) {
|
|
6609
6736
|
const ageStr2 = turnInfo.ageSeconds != null ? formatAge(turnInfo.ageSeconds) : "?";
|
|
@@ -6629,7 +6756,7 @@ function bridgeStatusAll() {
|
|
|
6629
6756
|
threadCwd: runtimeHeartbeat?.threadCwd ?? null,
|
|
6630
6757
|
savedThreadId: savedThread?.threadId ?? null,
|
|
6631
6758
|
savedThreadCwd: savedThread?.cwd ?? null,
|
|
6632
|
-
appServer:
|
|
6759
|
+
appServer: surfaceBridgeState?.appServer ?? null
|
|
6633
6760
|
};
|
|
6634
6761
|
}
|
|
6635
6762
|
if (instanceIds.length === 0) {
|
|
@@ -6726,14 +6853,17 @@ function bridgeStatusOne(identifier) {
|
|
|
6726
6853
|
}
|
|
6727
6854
|
const { config: resolvedCfg2 } = resolveConfig({}, repoRoot);
|
|
6728
6855
|
const stateDir = resolvedCfg2.stateDir;
|
|
6729
|
-
const
|
|
6856
|
+
const rawStatus = getBridgeStatus(stateDir, instanceId);
|
|
6730
6857
|
const bridgeState = loadBridgeState(stateDir, instanceId) ?? inst.bridge;
|
|
6731
|
-
const
|
|
6732
|
-
const
|
|
6733
|
-
const
|
|
6734
|
-
const
|
|
6735
|
-
const
|
|
6736
|
-
|
|
6858
|
+
const liveDispatch = rawStatus === "running" ? null : loadLiveDispatchEvidence(state.commsDir, instanceId);
|
|
6859
|
+
const surfaceBridgeState = liveDispatch ? null : bridgeState;
|
|
6860
|
+
const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(surfaceBridgeState);
|
|
6861
|
+
const savedThread = loadRuntimeBridgeThreadState(surfaceBridgeState);
|
|
6862
|
+
const age = liveDispatch ? null : getHeartbeatAge(stateDir, instanceId);
|
|
6863
|
+
const heartbeat = liveDispatch ? null : getBridgeHeartbeatTimestamp(stateDir, instanceId);
|
|
6864
|
+
const status = liveDispatch ? "dispatch-live" : rawStatus;
|
|
6865
|
+
const lifecycle = liveDispatch ? deriveBridgeLifecycleState({ bridgeStatus: "stopped" }) : deriveBridgeLifecycleState({
|
|
6866
|
+
bridgeStatus: rawStatus,
|
|
6737
6867
|
bridgeState,
|
|
6738
6868
|
runtimeHeartbeat,
|
|
6739
6869
|
savedThread,
|
|
@@ -6741,13 +6871,25 @@ function bridgeStatusOne(identifier) {
|
|
|
6741
6871
|
});
|
|
6742
6872
|
const session = deriveCodexSessionState({
|
|
6743
6873
|
runtimeHeartbeat,
|
|
6744
|
-
runtimeStateDir:
|
|
6874
|
+
runtimeStateDir: surfaceBridgeState?.runtimeStateDir ?? null
|
|
6745
6875
|
});
|
|
6746
6876
|
log(`Status: ${status}`);
|
|
6747
6877
|
log(`Lifecycle: ${lifecycle.summary}`);
|
|
6748
6878
|
log(`Session: ${session.summary}`);
|
|
6749
|
-
if (
|
|
6750
|
-
|
|
6879
|
+
if (rawStatus === "stale" && inst.bridge) {
|
|
6880
|
+
state.instances[instanceId] = {
|
|
6881
|
+
...inst,
|
|
6882
|
+
bridge: null,
|
|
6883
|
+
bridgeLifecycle: transitionBridgeLifecycle(
|
|
6884
|
+
inst.bridgeLifecycle ?? inst.bridge?.lifecycle ?? null,
|
|
6885
|
+
"crashed",
|
|
6886
|
+
"bridge pid not alive"
|
|
6887
|
+
)
|
|
6888
|
+
};
|
|
6889
|
+
saveState(repoRoot, state);
|
|
6890
|
+
}
|
|
6891
|
+
if (surfaceBridgeState) {
|
|
6892
|
+
log(`PID: ${surfaceBridgeState.pid}`);
|
|
6751
6893
|
log(
|
|
6752
6894
|
`Heartbeat: ${heartbeat ?? "-"}${age !== null ? ` (${formatAge(age)})` : ""}`
|
|
6753
6895
|
);
|
|
@@ -6762,35 +6904,35 @@ function bridgeStatusOne(identifier) {
|
|
|
6762
6904
|
);
|
|
6763
6905
|
}
|
|
6764
6906
|
log(
|
|
6765
|
-
`Log: ${
|
|
6907
|
+
`Log: ${path28.join(stateDir, "logs", `bridge-${instanceId}.log`)}`
|
|
6766
6908
|
);
|
|
6767
|
-
if (
|
|
6768
|
-
log(`App server: ${
|
|
6769
|
-
log(`Server PID: ${
|
|
6909
|
+
if (surfaceBridgeState.appServer) {
|
|
6910
|
+
log(`App server: ${surfaceBridgeState.appServer.url}`);
|
|
6911
|
+
log(`Server PID: ${surfaceBridgeState.appServer.pid ?? "-"}`);
|
|
6770
6912
|
log(
|
|
6771
|
-
`Server mode: ${
|
|
6913
|
+
`Server mode: ${surfaceBridgeState.appServer.managed ? "managed" : "external"}`
|
|
6772
6914
|
);
|
|
6773
6915
|
log(
|
|
6774
|
-
`Health: ${
|
|
6916
|
+
`Health: ${surfaceBridgeState.appServer.healthy ? "healthy" : "unhealthy"}`
|
|
6775
6917
|
);
|
|
6776
|
-
log(`Checked: ${
|
|
6777
|
-
if (
|
|
6778
|
-
log(`Server log: ${
|
|
6918
|
+
log(`Checked: ${surfaceBridgeState.appServer.lastCheckedAt}`);
|
|
6919
|
+
if (surfaceBridgeState.appServer.logPath) {
|
|
6920
|
+
log(`Server log: ${surfaceBridgeState.appServer.logPath}`);
|
|
6779
6921
|
}
|
|
6780
|
-
if (
|
|
6781
|
-
log(`Auth: ${
|
|
6922
|
+
if (surfaceBridgeState.appServer.auth) {
|
|
6923
|
+
log(`Auth: ${surfaceBridgeState.appServer.auth.mode}`);
|
|
6782
6924
|
log(
|
|
6783
|
-
`Protected: ${redactProtectedUrl(
|
|
6925
|
+
`Protected: ${redactProtectedUrl(surfaceBridgeState.appServer.auth.protectedUrl)}`
|
|
6784
6926
|
);
|
|
6785
|
-
log(`Upstream: ${
|
|
6786
|
-
log(`TUI connect: ${
|
|
6787
|
-
log(`Gateway PID: ${
|
|
6788
|
-
if (
|
|
6789
|
-
log(`Gateway log: ${
|
|
6927
|
+
log(`Upstream: ${surfaceBridgeState.appServer.auth.upstreamUrl}`);
|
|
6928
|
+
log(`TUI connect: ${surfaceBridgeState.appServer.auth.upstreamUrl}`);
|
|
6929
|
+
log(`Gateway PID: ${surfaceBridgeState.appServer.auth.gatewayPid ?? "-"}`);
|
|
6930
|
+
if (surfaceBridgeState.appServer.auth.gatewayLogPath) {
|
|
6931
|
+
log(`Gateway log: ${surfaceBridgeState.appServer.auth.gatewayLogPath}`);
|
|
6790
6932
|
}
|
|
6791
|
-
} else if (
|
|
6933
|
+
} else if (surfaceBridgeState.appServer.managed) {
|
|
6792
6934
|
log(`Auth: none (--no-auth)`);
|
|
6793
|
-
log(`TUI connect: ${
|
|
6935
|
+
log(`TUI connect: ${surfaceBridgeState.appServer.url}`);
|
|
6794
6936
|
}
|
|
6795
6937
|
}
|
|
6796
6938
|
}
|
|
@@ -6798,6 +6940,11 @@ function bridgeStatusOne(identifier) {
|
|
|
6798
6940
|
if (transition) {
|
|
6799
6941
|
log(`Transition: ${transition}`);
|
|
6800
6942
|
}
|
|
6943
|
+
if (liveDispatch) {
|
|
6944
|
+
log(
|
|
6945
|
+
`Drift: fresh bridge-dispatch heartbeat from PID ${liveDispatch.bridgePid} without bridge pid state`
|
|
6946
|
+
);
|
|
6947
|
+
}
|
|
6801
6948
|
log("");
|
|
6802
6949
|
return {
|
|
6803
6950
|
ok: true,
|
|
@@ -6827,14 +6974,14 @@ function bridgeStatusOne(identifier) {
|
|
|
6827
6974
|
lastDispatchAt: session.lastDispatchAt
|
|
6828
6975
|
},
|
|
6829
6976
|
bridgeMode: inst.bridgeMode,
|
|
6830
|
-
pid:
|
|
6977
|
+
pid: surfaceBridgeState?.pid ?? null,
|
|
6831
6978
|
port: inst.port,
|
|
6832
6979
|
lastHeartbeat: heartbeat,
|
|
6833
6980
|
threadId: runtimeHeartbeat?.threadId ?? null,
|
|
6834
6981
|
threadCwd: runtimeHeartbeat?.threadCwd ?? null,
|
|
6835
6982
|
savedThreadId: savedThread?.threadId ?? null,
|
|
6836
6983
|
savedThreadCwd: savedThread?.cwd ?? null,
|
|
6837
|
-
appServer:
|
|
6984
|
+
appServer: surfaceBridgeState?.appServer ?? null
|
|
6838
6985
|
}
|
|
6839
6986
|
};
|
|
6840
6987
|
}
|
|
@@ -6929,7 +7076,23 @@ function bridgeTuiOne(identifier) {
|
|
|
6929
7076
|
runtimeHeartbeat?.threadCwd,
|
|
6930
7077
|
savedThread?.cwd
|
|
6931
7078
|
);
|
|
6932
|
-
const
|
|
7079
|
+
const attachEnv = {
|
|
7080
|
+
TAP_BRIDGE_INSTANCE_ID: instanceId,
|
|
7081
|
+
TAP_AGENT_ID: instanceId,
|
|
7082
|
+
TAP_COMMS_DIR: resolvedConfig.commsDir,
|
|
7083
|
+
TAP_STATE_DIR: stateDir,
|
|
7084
|
+
TAP_RUNTIME_STATE_DIR: bridgeState?.runtimeStateDir ?? getBridgeRuntimeStateDir(repoRoot, instanceId),
|
|
7085
|
+
TAP_REPO_ROOT: repoRoot
|
|
7086
|
+
};
|
|
7087
|
+
if (typeof inst.agentName === "string" && inst.agentName.trim()) {
|
|
7088
|
+
attachEnv.TAP_AGENT_NAME = inst.agentName;
|
|
7089
|
+
attachEnv.CODEX_TAP_AGENT_NAME = inst.agentName;
|
|
7090
|
+
}
|
|
7091
|
+
const attachCommand = formatCodexTuiAttachCommand(
|
|
7092
|
+
tuiConnectUrl,
|
|
7093
|
+
attachCwd,
|
|
7094
|
+
attachEnv
|
|
7095
|
+
);
|
|
6933
7096
|
const warnings = appServer.auth != null ? [
|
|
6934
7097
|
"Use the upstream TUI URL, not the protected gateway URL. The protected URL is bridge-only."
|
|
6935
7098
|
] : [];
|
|
@@ -6954,6 +7117,7 @@ function bridgeTuiOne(identifier) {
|
|
|
6954
7117
|
tuiConnectUrl,
|
|
6955
7118
|
attachCwd,
|
|
6956
7119
|
attachCommand,
|
|
7120
|
+
attachEnv,
|
|
6957
7121
|
appServer
|
|
6958
7122
|
}
|
|
6959
7123
|
};
|
|
@@ -7266,8 +7430,8 @@ async function bridgeCommand(args) {
|
|
|
7266
7430
|
|
|
7267
7431
|
// src/engine/dashboard.ts
|
|
7268
7432
|
init_config();
|
|
7269
|
-
import * as
|
|
7270
|
-
import * as
|
|
7433
|
+
import * as fs29 from "fs";
|
|
7434
|
+
import * as path29 from "path";
|
|
7271
7435
|
import { execSync as execSync4 } from "child_process";
|
|
7272
7436
|
function formatAgentLabel(agentIdOrName, displayName) {
|
|
7273
7437
|
const normalizedId = agentIdOrName.trim();
|
|
@@ -7300,10 +7464,10 @@ function resolveHeartbeatInstanceId(heartbeatId, displayName, state) {
|
|
|
7300
7464
|
return matches.length === 1 ? matches[0].instanceId : null;
|
|
7301
7465
|
}
|
|
7302
7466
|
function collectAgents(commsDir, state, bridges) {
|
|
7303
|
-
const heartbeatsPath =
|
|
7304
|
-
if (!
|
|
7467
|
+
const heartbeatsPath = path29.join(commsDir, "heartbeats.json");
|
|
7468
|
+
if (!fs29.existsSync(heartbeatsPath)) return [];
|
|
7305
7469
|
try {
|
|
7306
|
-
const raw =
|
|
7470
|
+
const raw = fs29.readFileSync(heartbeatsPath, "utf-8");
|
|
7307
7471
|
const data = JSON.parse(raw);
|
|
7308
7472
|
return Object.entries(data).map(([agentId, info]) => {
|
|
7309
7473
|
const instanceId = resolveHeartbeatInstanceId(
|
|
@@ -7363,22 +7527,22 @@ function collectBridges(repoRoot) {
|
|
|
7363
7527
|
});
|
|
7364
7528
|
}
|
|
7365
7529
|
}
|
|
7366
|
-
const tmpDir =
|
|
7367
|
-
if (
|
|
7530
|
+
const tmpDir = path29.join(repoRoot, ".tmp");
|
|
7531
|
+
if (fs29.existsSync(tmpDir)) {
|
|
7368
7532
|
try {
|
|
7369
|
-
const dirs =
|
|
7533
|
+
const dirs = fs29.readdirSync(tmpDir).filter((d) => d.startsWith("codex-app-server-bridge"));
|
|
7370
7534
|
for (const dir of dirs) {
|
|
7371
|
-
const daemonPath =
|
|
7372
|
-
if (!
|
|
7535
|
+
const daemonPath = path29.join(tmpDir, dir, "bridge-daemon.json");
|
|
7536
|
+
if (!fs29.existsSync(daemonPath)) continue;
|
|
7373
7537
|
try {
|
|
7374
|
-
const raw =
|
|
7538
|
+
const raw = fs29.readFileSync(daemonPath, "utf-8");
|
|
7375
7539
|
const daemon = JSON.parse(raw);
|
|
7376
7540
|
const alreadyCovered = bridges.some(
|
|
7377
7541
|
(b) => b.pid === daemon.pid && b.pid !== null
|
|
7378
7542
|
);
|
|
7379
7543
|
if (alreadyCovered) continue;
|
|
7380
|
-
const agentFile =
|
|
7381
|
-
const agentName =
|
|
7544
|
+
const agentFile = path29.join(tmpDir, dir, "agent-name.txt");
|
|
7545
|
+
const agentName = fs29.existsSync(agentFile) ? fs29.readFileSync(agentFile, "utf-8").trim() : dir;
|
|
7382
7546
|
const running = daemon.pid ? isProcessAlive(daemon.pid) : false;
|
|
7383
7547
|
const portMatch = daemon.appServerUrl?.match(/:(\d+)/);
|
|
7384
7548
|
const port = portMatch ? parseInt(portMatch[1], 10) : null;
|
|
@@ -7613,7 +7777,7 @@ async function downCommand(args) {
|
|
|
7613
7777
|
}
|
|
7614
7778
|
|
|
7615
7779
|
// src/commands/serve.ts
|
|
7616
|
-
import * as
|
|
7780
|
+
import * as path30 from "path";
|
|
7617
7781
|
import { spawn as spawn2 } from "child_process";
|
|
7618
7782
|
init_utils();
|
|
7619
7783
|
init_config();
|
|
@@ -7649,10 +7813,10 @@ async function serveCommand(args) {
|
|
|
7649
7813
|
let commsDir;
|
|
7650
7814
|
const commsDirIdx = args.indexOf("--comms-dir");
|
|
7651
7815
|
if (commsDirIdx !== -1 && args[commsDirIdx + 1]) {
|
|
7652
|
-
commsDir =
|
|
7816
|
+
commsDir = path30.resolve(normalizeTapPath(args[commsDirIdx + 1]));
|
|
7653
7817
|
}
|
|
7654
7818
|
if (!commsDir && process.env.TAP_COMMS_DIR) {
|
|
7655
|
-
commsDir =
|
|
7819
|
+
commsDir = path30.resolve(normalizeTapPath(process.env.TAP_COMMS_DIR));
|
|
7656
7820
|
}
|
|
7657
7821
|
if (!commsDir) {
|
|
7658
7822
|
const state = loadState(repoRoot);
|
|
@@ -7719,8 +7883,8 @@ async function serveCommand(args) {
|
|
|
7719
7883
|
// src/commands/init-worktree.ts
|
|
7720
7884
|
init_config();
|
|
7721
7885
|
init_utils();
|
|
7722
|
-
import * as
|
|
7723
|
-
import * as
|
|
7886
|
+
import * as fs30 from "fs";
|
|
7887
|
+
import * as path31 from "path";
|
|
7724
7888
|
import { execSync as execSync5 } from "child_process";
|
|
7725
7889
|
var INIT_WORKTREE_HELP = `
|
|
7726
7890
|
Usage:
|
|
@@ -7757,7 +7921,7 @@ function run(cmd, opts) {
|
|
|
7757
7921
|
}
|
|
7758
7922
|
}
|
|
7759
7923
|
function toAbsolute(p) {
|
|
7760
|
-
const resolved =
|
|
7924
|
+
const resolved = path31.resolve(p);
|
|
7761
7925
|
return resolved.replace(/\\/g, "/");
|
|
7762
7926
|
}
|
|
7763
7927
|
function probeBun(candidate) {
|
|
@@ -7788,18 +7952,18 @@ function findBun() {
|
|
|
7788
7952
|
}
|
|
7789
7953
|
}
|
|
7790
7954
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
7791
|
-
const bunHome =
|
|
7955
|
+
const bunHome = path31.join(
|
|
7792
7956
|
home,
|
|
7793
7957
|
".bun",
|
|
7794
7958
|
"bin",
|
|
7795
7959
|
process.platform === "win32" ? "bun.exe" : "bun"
|
|
7796
7960
|
);
|
|
7797
|
-
if (
|
|
7961
|
+
if (fs30.existsSync(bunHome) && probeBun(bunHome)) return bunHome;
|
|
7798
7962
|
return null;
|
|
7799
7963
|
}
|
|
7800
7964
|
function step1CreateWorktree(opts) {
|
|
7801
7965
|
log("Step 1/9: Creating worktree...");
|
|
7802
|
-
if (
|
|
7966
|
+
if (fs30.existsSync(opts.worktreePath)) {
|
|
7803
7967
|
logWarn(`Directory already exists: ${opts.worktreePath}`);
|
|
7804
7968
|
try {
|
|
7805
7969
|
run("git rev-parse --git-dir", { cwd: opts.worktreePath });
|
|
@@ -7861,22 +8025,22 @@ function step2MergeMain(opts, warnings) {
|
|
|
7861
8025
|
}
|
|
7862
8026
|
function step3CopyPermissions(opts, warnings) {
|
|
7863
8027
|
log("Step 3/9: Copying permissions...");
|
|
7864
|
-
const srcSettings =
|
|
8028
|
+
const srcSettings = path31.join(
|
|
7865
8029
|
opts.repoRoot,
|
|
7866
8030
|
".claude",
|
|
7867
8031
|
"settings.local.json"
|
|
7868
8032
|
);
|
|
7869
|
-
const destDir =
|
|
7870
|
-
const destSettings =
|
|
7871
|
-
if (!
|
|
8033
|
+
const destDir = path31.join(opts.worktreePath, ".claude");
|
|
8034
|
+
const destSettings = path31.join(destDir, "settings.local.json");
|
|
8035
|
+
if (!fs30.existsSync(srcSettings)) {
|
|
7872
8036
|
warn(
|
|
7873
8037
|
warnings,
|
|
7874
8038
|
"No .claude/settings.local.json found in main repo. Skipping."
|
|
7875
8039
|
);
|
|
7876
8040
|
return;
|
|
7877
8041
|
}
|
|
7878
|
-
|
|
7879
|
-
|
|
8042
|
+
fs30.mkdirSync(destDir, { recursive: true });
|
|
8043
|
+
fs30.copyFileSync(srcSettings, destSettings);
|
|
7880
8044
|
logSuccess("Copied settings.local.json");
|
|
7881
8045
|
try {
|
|
7882
8046
|
run("git update-index --skip-worktree .claude/settings.local.json", {
|
|
@@ -7901,7 +8065,7 @@ function step4GenerateMcpJson(opts, warnings) {
|
|
|
7901
8065
|
const wtAbs = toAbsolute(opts.worktreePath);
|
|
7902
8066
|
const bunAbs = toAbsolute(bunPath);
|
|
7903
8067
|
const commsAbs = toAbsolute(opts.commsDir);
|
|
7904
|
-
const channelEntry =
|
|
8068
|
+
const channelEntry = path31.join(
|
|
7905
8069
|
wtAbs,
|
|
7906
8070
|
"packages/tap-plugin/channels/tap-comms.ts"
|
|
7907
8071
|
);
|
|
@@ -7918,8 +8082,8 @@ function step4GenerateMcpJson(opts, warnings) {
|
|
|
7918
8082
|
}
|
|
7919
8083
|
}
|
|
7920
8084
|
};
|
|
7921
|
-
const mcpPath =
|
|
7922
|
-
|
|
8085
|
+
const mcpPath = path31.join(opts.worktreePath, ".mcp.json");
|
|
8086
|
+
fs30.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
|
|
7923
8087
|
logSuccess(`.mcp.json generated (absolute paths + cwd)`);
|
|
7924
8088
|
log(` bun: ${bunAbs}`);
|
|
7925
8089
|
log(` comms: ${commsAbs}`);
|
|
@@ -7957,16 +8121,16 @@ function step6BuildEslintPlugin(opts, warnings) {
|
|
|
7957
8121
|
}
|
|
7958
8122
|
function step7VerifyComms(opts, warnings) {
|
|
7959
8123
|
log("Step 7/9: Verifying comms directory...");
|
|
7960
|
-
if (!
|
|
8124
|
+
if (!fs30.existsSync(opts.commsDir)) {
|
|
7961
8125
|
warn(warnings, `Comms directory not found: ${opts.commsDir}`);
|
|
7962
8126
|
warn(warnings, "Create it or run: npx @hua-labs/tap init");
|
|
7963
8127
|
return;
|
|
7964
8128
|
}
|
|
7965
8129
|
const requiredDirs = ["inbox", "findings", "reviews", "letters"];
|
|
7966
8130
|
for (const dir of requiredDirs) {
|
|
7967
|
-
const dirPath =
|
|
7968
|
-
if (!
|
|
7969
|
-
|
|
8131
|
+
const dirPath = path31.join(opts.commsDir, dir);
|
|
8132
|
+
if (!fs30.existsSync(dirPath)) {
|
|
8133
|
+
fs30.mkdirSync(dirPath, { recursive: true });
|
|
7970
8134
|
logSuccess(`Created ${dir}/`);
|
|
7971
8135
|
}
|
|
7972
8136
|
}
|
|
@@ -8025,17 +8189,17 @@ async function initWorktreeCommand(args) {
|
|
|
8025
8189
|
}
|
|
8026
8190
|
const repoRoot = findRepoRoot();
|
|
8027
8191
|
const { config } = resolveConfig({}, repoRoot);
|
|
8028
|
-
const branch = typeof flags["branch"] === "string" ? flags["branch"] :
|
|
8192
|
+
const branch = typeof flags["branch"] === "string" ? flags["branch"] : path31.basename(path31.resolve(worktreePath));
|
|
8029
8193
|
const base = typeof flags["base"] === "string" ? flags["base"] : "origin/main";
|
|
8030
8194
|
const mission = typeof flags["mission"] === "string" ? flags["mission"] : void 0;
|
|
8031
8195
|
const commsDir = typeof flags["comms-dir"] === "string" ? flags["comms-dir"] : config.commsDir;
|
|
8032
8196
|
const skipInstall = flags["skip-install"] === true;
|
|
8033
8197
|
const opts = {
|
|
8034
|
-
worktreePath:
|
|
8198
|
+
worktreePath: path31.resolve(worktreePath),
|
|
8035
8199
|
branch,
|
|
8036
8200
|
base,
|
|
8037
8201
|
mission,
|
|
8038
|
-
commsDir:
|
|
8202
|
+
commsDir: path31.resolve(commsDir),
|
|
8039
8203
|
skipInstall,
|
|
8040
8204
|
repoRoot
|
|
8041
8205
|
};
|
|
@@ -8260,10 +8424,10 @@ async function dashboardCommand(args) {
|
|
|
8260
8424
|
|
|
8261
8425
|
// src/commands/doctor.ts
|
|
8262
8426
|
import {
|
|
8263
|
-
existsSync as
|
|
8427
|
+
existsSync as existsSync29,
|
|
8264
8428
|
mkdirSync as mkdirSync14,
|
|
8265
8429
|
readdirSync as readdirSync8,
|
|
8266
|
-
readFileSync as
|
|
8430
|
+
readFileSync as readFileSync24,
|
|
8267
8431
|
renameSync as renameSync14,
|
|
8268
8432
|
statSync as statSync3,
|
|
8269
8433
|
unlinkSync as unlinkSync8,
|
|
@@ -8271,7 +8435,7 @@ import {
|
|
|
8271
8435
|
} from "fs";
|
|
8272
8436
|
import { homedir as homedir3 } from "os";
|
|
8273
8437
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
8274
|
-
import { dirname as dirname15, join as
|
|
8438
|
+
import { dirname as dirname15, join as join28, resolve as resolve14 } from "path";
|
|
8275
8439
|
init_config();
|
|
8276
8440
|
init_drift_detector();
|
|
8277
8441
|
init_utils();
|
|
@@ -8302,11 +8466,43 @@ function sameCommandToken(left, right) {
|
|
|
8302
8466
|
function sameStringArray(left, right) {
|
|
8303
8467
|
return left.length === right.length && left.every((value, index) => sameCommandToken(value, right[index] ?? ""));
|
|
8304
8468
|
}
|
|
8469
|
+
function normalizeCommandBasename(command) {
|
|
8470
|
+
const token = command.split(/[\\/]/).pop() ?? command;
|
|
8471
|
+
return token.toLowerCase().replace(/\.(cmd|exe|ps1|bat)$/i, "");
|
|
8472
|
+
}
|
|
8473
|
+
function findFirstLauncherTarget(args) {
|
|
8474
|
+
for (const arg of args) {
|
|
8475
|
+
if (!arg || arg === "--" || arg.startsWith("-")) {
|
|
8476
|
+
continue;
|
|
8477
|
+
}
|
|
8478
|
+
return arg;
|
|
8479
|
+
}
|
|
8480
|
+
return null;
|
|
8481
|
+
}
|
|
8482
|
+
function looksLikePackageSpecifier(value) {
|
|
8483
|
+
const normalized = value.trim();
|
|
8484
|
+
if (!normalized || /^[A-Za-z]:[\\/]/.test(normalized) || normalized.startsWith("/") || normalized.startsWith("\\") || normalized.startsWith(".") || /\.(?:[cm]?js|tsx?|json|ps1|cmd|exe)$/i.test(normalized)) {
|
|
8485
|
+
return false;
|
|
8486
|
+
}
|
|
8487
|
+
return /^(?:@[^/\\]+\/)?[A-Za-z0-9][A-Za-z0-9._-]*(?:@[A-Za-z0-9][A-Za-z0-9._.-]*)?$/.test(
|
|
8488
|
+
normalized
|
|
8489
|
+
);
|
|
8490
|
+
}
|
|
8491
|
+
function getNpxPackageLauncher(command, args) {
|
|
8492
|
+
if (normalizeCommandBasename(command) !== "npx") {
|
|
8493
|
+
return null;
|
|
8494
|
+
}
|
|
8495
|
+
const packageName = findFirstLauncherTarget(args);
|
|
8496
|
+
if (!packageName || !looksLikePackageSpecifier(packageName)) {
|
|
8497
|
+
return null;
|
|
8498
|
+
}
|
|
8499
|
+
return [command, ...args].join(" ");
|
|
8500
|
+
}
|
|
8305
8501
|
function appendWarningMessage(message, extra) {
|
|
8306
8502
|
return message.includes(extra) ? message : `${message}; ${extra}`;
|
|
8307
8503
|
}
|
|
8308
8504
|
function findCodexConfigPath3() {
|
|
8309
|
-
return
|
|
8505
|
+
return join28(homedir3(), ".codex", "config.toml");
|
|
8310
8506
|
}
|
|
8311
8507
|
function canonicalizeTrustPath3(targetPath) {
|
|
8312
8508
|
let resolved = resolve14(targetPath).replace(/\//g, "\\");
|
|
@@ -8376,7 +8572,7 @@ function repairCodexConfig(repoRoot, commsDir) {
|
|
|
8376
8572
|
spec.managed.issues[0] ?? "Unable to resolve the managed tap MCP server for Codex."
|
|
8377
8573
|
);
|
|
8378
8574
|
}
|
|
8379
|
-
const existingContent =
|
|
8575
|
+
const existingContent = existsSync29(spec.configPath) ? readFileSync24(spec.configPath, "utf-8") : "";
|
|
8380
8576
|
const existingTapEnvTable = extractTomlTable(
|
|
8381
8577
|
existingContent,
|
|
8382
8578
|
"mcp_servers.tap.env"
|
|
@@ -8443,7 +8639,7 @@ function repairCodexConfig(repoRoot, commsDir) {
|
|
|
8443
8639
|
return `Repaired Codex config at ${spec.configPath}. Restart Codex to reload MCP settings.`;
|
|
8444
8640
|
}
|
|
8445
8641
|
function countFiles(dir, ext = ".md") {
|
|
8446
|
-
if (!
|
|
8642
|
+
if (!existsSync29(dir)) return 0;
|
|
8447
8643
|
try {
|
|
8448
8644
|
return readdirSync8(dir).filter((f) => f.endsWith(ext)).length;
|
|
8449
8645
|
} catch {
|
|
@@ -8451,14 +8647,14 @@ function countFiles(dir, ext = ".md") {
|
|
|
8451
8647
|
}
|
|
8452
8648
|
}
|
|
8453
8649
|
function recentFileCount(dir, withinMs) {
|
|
8454
|
-
if (!
|
|
8650
|
+
if (!existsSync29(dir)) return 0;
|
|
8455
8651
|
const cutoff = Date.now() - withinMs;
|
|
8456
8652
|
let count = 0;
|
|
8457
8653
|
try {
|
|
8458
8654
|
for (const f of readdirSync8(dir)) {
|
|
8459
8655
|
if (!f.endsWith(".md")) continue;
|
|
8460
8656
|
try {
|
|
8461
|
-
if (statSync3(
|
|
8657
|
+
if (statSync3(join28(dir, f)).mtimeMs > cutoff) count++;
|
|
8462
8658
|
} catch {
|
|
8463
8659
|
}
|
|
8464
8660
|
}
|
|
@@ -8467,16 +8663,16 @@ function recentFileCount(dir, withinMs) {
|
|
|
8467
8663
|
return count;
|
|
8468
8664
|
}
|
|
8469
8665
|
function loadDoctorHeartbeatStore(commsDir) {
|
|
8470
|
-
const heartbeatsPath =
|
|
8471
|
-
if (!
|
|
8666
|
+
const heartbeatsPath = join28(commsDir, "heartbeats.json");
|
|
8667
|
+
if (!existsSync29(heartbeatsPath)) return null;
|
|
8472
8668
|
try {
|
|
8473
|
-
return JSON.parse(
|
|
8669
|
+
return JSON.parse(readFileSync24(heartbeatsPath, "utf-8"));
|
|
8474
8670
|
} catch {
|
|
8475
8671
|
return null;
|
|
8476
8672
|
}
|
|
8477
8673
|
}
|
|
8478
8674
|
function saveDoctorHeartbeatStore(commsDir, store) {
|
|
8479
|
-
const heartbeatsPath =
|
|
8675
|
+
const heartbeatsPath = join28(commsDir, "heartbeats.json");
|
|
8480
8676
|
const tmp = `${heartbeatsPath}.tmp.${process.pid}`;
|
|
8481
8677
|
writeFileSync16(tmp, JSON.stringify(store, null, 2), "utf-8");
|
|
8482
8678
|
renameSync14(tmp, heartbeatsPath);
|
|
@@ -8542,9 +8738,9 @@ function checkComms(commsDir) {
|
|
|
8542
8738
|
const checks = [];
|
|
8543
8739
|
checks.push({
|
|
8544
8740
|
name: "comms directory",
|
|
8545
|
-
status:
|
|
8546
|
-
message:
|
|
8547
|
-
fix:
|
|
8741
|
+
status: existsSync29(commsDir) ? PASS : FAIL,
|
|
8742
|
+
message: existsSync29(commsDir) ? commsDir : `Not found: ${commsDir}`,
|
|
8743
|
+
fix: existsSync29(commsDir) ? void 0 : () => {
|
|
8548
8744
|
mkdirSync14(commsDir, { recursive: true });
|
|
8549
8745
|
return `Created ${commsDir}`;
|
|
8550
8746
|
}
|
|
@@ -8554,8 +8750,8 @@ function checkComms(commsDir) {
|
|
|
8554
8750
|
["reviews", false],
|
|
8555
8751
|
["findings", false]
|
|
8556
8752
|
]) {
|
|
8557
|
-
const dir =
|
|
8558
|
-
const exists =
|
|
8753
|
+
const dir = join28(commsDir, subdir);
|
|
8754
|
+
const exists = existsSync29(dir);
|
|
8559
8755
|
checks.push({
|
|
8560
8756
|
name: `${subdir} directory`,
|
|
8561
8757
|
status: exists ? PASS : required ? FAIL : WARN,
|
|
@@ -8566,10 +8762,10 @@ function checkComms(commsDir) {
|
|
|
8566
8762
|
}
|
|
8567
8763
|
});
|
|
8568
8764
|
}
|
|
8569
|
-
const heartbeats =
|
|
8570
|
-
if (
|
|
8765
|
+
const heartbeats = join28(commsDir, "heartbeats.json");
|
|
8766
|
+
if (existsSync29(heartbeats)) {
|
|
8571
8767
|
try {
|
|
8572
|
-
const store = JSON.parse(
|
|
8768
|
+
const store = JSON.parse(readFileSync24(heartbeats, "utf-8"));
|
|
8573
8769
|
const agents = Object.keys(store);
|
|
8574
8770
|
const now = Date.now();
|
|
8575
8771
|
const active = agents.filter((a) => {
|
|
@@ -8683,7 +8879,7 @@ function checkInstances(repoRoot, stateDir, commsDir) {
|
|
|
8683
8879
|
}
|
|
8684
8880
|
}
|
|
8685
8881
|
}
|
|
8686
|
-
const pidPath =
|
|
8882
|
+
const pidPath = join28(stateDir, "pids", `bridge-${id}.json`);
|
|
8687
8883
|
try {
|
|
8688
8884
|
unlinkSync8(pidPath);
|
|
8689
8885
|
} catch {
|
|
@@ -8745,8 +8941,8 @@ function checkInstances(repoRoot, stateDir, commsDir) {
|
|
|
8745
8941
|
}
|
|
8746
8942
|
function checkMessageLifecycle(commsDir) {
|
|
8747
8943
|
const checks = [];
|
|
8748
|
-
const inbox =
|
|
8749
|
-
if (!
|
|
8944
|
+
const inbox = join28(commsDir, "inbox");
|
|
8945
|
+
if (!existsSync29(inbox)) {
|
|
8750
8946
|
checks.push({
|
|
8751
8947
|
name: "message flow",
|
|
8752
8948
|
status: FAIL,
|
|
@@ -8757,15 +8953,16 @@ function checkMessageLifecycle(commsDir) {
|
|
|
8757
8953
|
const total = countFiles(inbox);
|
|
8758
8954
|
const recent1h = recentFileCount(inbox, 60 * 60 * 1e3);
|
|
8759
8955
|
const recent10m = recentFileCount(inbox, 10 * 60 * 1e3);
|
|
8956
|
+
const messageSummary = `${total} total, ${recent1h} in last 1h, ${recent10m} in last 10m`;
|
|
8760
8957
|
checks.push({
|
|
8761
8958
|
name: "message flow",
|
|
8762
|
-
status: recent10m > 0 ? PASS :
|
|
8763
|
-
message:
|
|
8959
|
+
status: recent10m > 0 ? PASS : WARN,
|
|
8960
|
+
message: total === 0 ? `${messageSummary} (expected before first exchange)` : messageSummary
|
|
8764
8961
|
});
|
|
8765
|
-
const receiptsPath =
|
|
8766
|
-
if (
|
|
8962
|
+
const receiptsPath = join28(commsDir, "receipts", "receipts.json");
|
|
8963
|
+
if (existsSync29(receiptsPath)) {
|
|
8767
8964
|
try {
|
|
8768
|
-
const receipts = JSON.parse(
|
|
8965
|
+
const receipts = JSON.parse(readFileSync24(receiptsPath, "utf-8"));
|
|
8769
8966
|
const receiptCount = Object.keys(receipts).length;
|
|
8770
8967
|
checks.push({
|
|
8771
8968
|
name: "read receipts",
|
|
@@ -8784,8 +8981,8 @@ function checkMessageLifecycle(commsDir) {
|
|
|
8784
8981
|
}
|
|
8785
8982
|
function checkMcpServer(repoRoot) {
|
|
8786
8983
|
const checks = [];
|
|
8787
|
-
const mcpJson =
|
|
8788
|
-
if (!
|
|
8984
|
+
const mcpJson = join28(repoRoot, ".mcp.json");
|
|
8985
|
+
if (!existsSync29(mcpJson)) {
|
|
8789
8986
|
checks.push({
|
|
8790
8987
|
name: "MCP config (.mcp.json)",
|
|
8791
8988
|
status: WARN,
|
|
@@ -8795,7 +8992,7 @@ function checkMcpServer(repoRoot) {
|
|
|
8795
8992
|
}
|
|
8796
8993
|
let config;
|
|
8797
8994
|
try {
|
|
8798
|
-
config = JSON.parse(
|
|
8995
|
+
config = JSON.parse(readFileSync24(mcpJson, "utf-8"));
|
|
8799
8996
|
} catch {
|
|
8800
8997
|
checks.push({
|
|
8801
8998
|
name: "MCP config (.mcp.json)",
|
|
@@ -8838,17 +9035,9 @@ function checkMcpServer(repoRoot) {
|
|
|
8838
9035
|
});
|
|
8839
9036
|
if (hasTapComms.command) {
|
|
8840
9037
|
const cmd = hasTapComms.command;
|
|
8841
|
-
let cmdAvailable =
|
|
9038
|
+
let cmdAvailable = existsSync29(cmd);
|
|
8842
9039
|
if (!cmdAvailable) {
|
|
8843
|
-
|
|
8844
|
-
const result = spawnSync6(cmd, ["--version"], {
|
|
8845
|
-
stdio: "pipe",
|
|
8846
|
-
timeout: 5e3,
|
|
8847
|
-
shell: process.platform === "win32"
|
|
8848
|
-
});
|
|
8849
|
-
cmdAvailable = result.status === 0;
|
|
8850
|
-
} catch {
|
|
8851
|
-
}
|
|
9040
|
+
cmdAvailable = probeCommand([cmd]).command !== null;
|
|
8852
9041
|
}
|
|
8853
9042
|
checks.push({
|
|
8854
9043
|
name: "MCP command binary",
|
|
@@ -8856,12 +9045,19 @@ function checkMcpServer(repoRoot) {
|
|
|
8856
9045
|
message: cmdAvailable ? cmd : `Not found: ${cmd} (checked PATH and absolute)`
|
|
8857
9046
|
});
|
|
8858
9047
|
}
|
|
8859
|
-
|
|
9048
|
+
const npxPackageLauncher = hasTapComms.command && hasTapComms.args ? getNpxPackageLauncher(hasTapComms.command, hasTapComms.args) : null;
|
|
9049
|
+
if (npxPackageLauncher) {
|
|
9050
|
+
checks.push({
|
|
9051
|
+
name: "MCP server script",
|
|
9052
|
+
status: PASS,
|
|
9053
|
+
message: `Package launcher: ${npxPackageLauncher}`
|
|
9054
|
+
});
|
|
9055
|
+
} else if (hasTapComms.args?.[0]) {
|
|
8860
9056
|
const mcpScript = hasTapComms.args[0];
|
|
8861
9057
|
checks.push({
|
|
8862
9058
|
name: "MCP server script",
|
|
8863
|
-
status:
|
|
8864
|
-
message:
|
|
9059
|
+
status: existsSync29(mcpScript) ? PASS : FAIL,
|
|
9060
|
+
message: existsSync29(mcpScript) ? mcpScript : `Not found: ${mcpScript}`
|
|
8865
9061
|
});
|
|
8866
9062
|
if (mcpScript.endsWith(".mjs") && hasTapComms.command && !hasTapComms.command.includes("bun")) {
|
|
8867
9063
|
checks.push({
|
|
@@ -8894,8 +9090,8 @@ function checkMcpServer(repoRoot) {
|
|
|
8894
9090
|
} else {
|
|
8895
9091
|
checks.push({
|
|
8896
9092
|
name: "MCP TAP_COMMS_DIR",
|
|
8897
|
-
status:
|
|
8898
|
-
message:
|
|
9093
|
+
status: existsSync29(envCommsDir) ? PASS : FAIL,
|
|
9094
|
+
message: existsSync29(envCommsDir) ? envCommsDir : `Directory not found: ${envCommsDir}`
|
|
8899
9095
|
});
|
|
8900
9096
|
}
|
|
8901
9097
|
checks.push({
|
|
@@ -8912,7 +9108,7 @@ function checkCodexConfig(repoRoot, commsDir) {
|
|
|
8912
9108
|
}
|
|
8913
9109
|
const checks = [];
|
|
8914
9110
|
const fixHint = 'Run "tap doctor --fix" or "tap add codex --force".';
|
|
8915
|
-
if (!
|
|
9111
|
+
if (!existsSync29(spec.configPath)) {
|
|
8916
9112
|
checks.push({
|
|
8917
9113
|
name: "MCP config (~/.codex/config.toml)",
|
|
8918
9114
|
status: WARN,
|
|
@@ -8921,7 +9117,7 @@ function checkCodexConfig(repoRoot, commsDir) {
|
|
|
8921
9117
|
});
|
|
8922
9118
|
return checks;
|
|
8923
9119
|
}
|
|
8924
|
-
const content =
|
|
9120
|
+
const content = readFileSync24(spec.configPath, "utf-8");
|
|
8925
9121
|
const tapTable = extractTomlTable(content, "mcp_servers.tap");
|
|
8926
9122
|
const tapEnvTable = extractTomlTable(content, "mcp_servers.tap.env");
|
|
8927
9123
|
const legacyTable = extractTomlTable(content, "mcp_servers.tap-comms");
|
|
@@ -9005,8 +9201,8 @@ function checkCodexConfig(repoRoot, commsDir) {
|
|
|
9005
9201
|
}
|
|
9006
9202
|
function checkBridgeTurnHealth(repoRoot) {
|
|
9007
9203
|
const checks = [];
|
|
9008
|
-
const tmpDir =
|
|
9009
|
-
if (!
|
|
9204
|
+
const tmpDir = join28(repoRoot, ".tmp");
|
|
9205
|
+
if (!existsSync29(tmpDir)) return checks;
|
|
9010
9206
|
const state = loadState(repoRoot);
|
|
9011
9207
|
const activeMatchers = /* @__PURE__ */ new Set();
|
|
9012
9208
|
if (state) {
|
|
@@ -9032,11 +9228,11 @@ function checkBridgeTurnHealth(repoRoot) {
|
|
|
9032
9228
|
return checks;
|
|
9033
9229
|
}
|
|
9034
9230
|
for (const dir of dirs) {
|
|
9035
|
-
const heartbeatPath =
|
|
9036
|
-
if (!
|
|
9231
|
+
const heartbeatPath = join28(tmpDir, dir, "heartbeat.json");
|
|
9232
|
+
if (!existsSync29(heartbeatPath)) continue;
|
|
9037
9233
|
let heartbeat;
|
|
9038
9234
|
try {
|
|
9039
|
-
heartbeat = JSON.parse(
|
|
9235
|
+
heartbeat = JSON.parse(readFileSync24(heartbeatPath, "utf-8"));
|
|
9040
9236
|
} catch {
|
|
9041
9237
|
checks.push({
|
|
9042
9238
|
name: `turn: ${dir}`,
|
|
@@ -9209,9 +9405,9 @@ async function doctorCommand(args) {
|
|
|
9209
9405
|
inst.agentName = instConfig.agentName;
|
|
9210
9406
|
inst.port = instConfig.port;
|
|
9211
9407
|
inst.configHash = instConfig.configHash;
|
|
9212
|
-
inst.configSourceFile = inst.configSourceFile ||
|
|
9408
|
+
inst.configSourceFile = inst.configSourceFile || join28(config.stateDir, "instances", `${result.instanceId}.json`);
|
|
9213
9409
|
saveState(repoRoot, state);
|
|
9214
|
-
if (inst.configPath &&
|
|
9410
|
+
if (inst.configPath && existsSync29(inst.configPath)) {
|
|
9215
9411
|
const currentHash = hashFile(inst.configPath);
|
|
9216
9412
|
if (instConfig.runtimeConfigHash !== currentHash) {
|
|
9217
9413
|
instConfig.runtimeConfigHash = currentHash;
|
|
@@ -9322,7 +9518,7 @@ async function doctorCommand(args) {
|
|
|
9322
9518
|
message: `${passes} passed, ${warns} warnings, ${fails} failures`,
|
|
9323
9519
|
warnings: finalChecks.filter((c) => c.status === "warn").map((c) => `${c.name}: ${c.message}`),
|
|
9324
9520
|
data: {
|
|
9325
|
-
checks: finalChecks.map(({ fix, ...rest }) => rest),
|
|
9521
|
+
checks: finalChecks.map(({ fix: _fix, ...rest }) => rest),
|
|
9326
9522
|
summary: { total: finalChecks.length, passes, warns, fails },
|
|
9327
9523
|
fixed
|
|
9328
9524
|
}
|
|
@@ -9332,8 +9528,8 @@ async function doctorCommand(args) {
|
|
|
9332
9528
|
// src/commands/comms.ts
|
|
9333
9529
|
init_utils();
|
|
9334
9530
|
import { execSync as execSync6, spawnSync as spawnSync7 } from "child_process";
|
|
9335
|
-
import * as
|
|
9336
|
-
import * as
|
|
9531
|
+
import * as fs31 from "fs";
|
|
9532
|
+
import * as path32 from "path";
|
|
9337
9533
|
var COMMS_HELP = `
|
|
9338
9534
|
Usage:
|
|
9339
9535
|
tap comms <subcommand>
|
|
@@ -9347,7 +9543,7 @@ Examples:
|
|
|
9347
9543
|
npx @hua-labs/tap comms push
|
|
9348
9544
|
`.trim();
|
|
9349
9545
|
function isGitRepo(dir) {
|
|
9350
|
-
return
|
|
9546
|
+
return fs31.existsSync(path32.join(dir, ".git"));
|
|
9351
9547
|
}
|
|
9352
9548
|
function commsPull(commsDir) {
|
|
9353
9549
|
logHeader("tap comms pull");
|
|
@@ -9606,8 +9802,8 @@ async function watchCommand(args) {
|
|
|
9606
9802
|
import * as http from "http";
|
|
9607
9803
|
|
|
9608
9804
|
// src/engine/missions.ts
|
|
9609
|
-
import * as
|
|
9610
|
-
import * as
|
|
9805
|
+
import * as fs32 from "fs";
|
|
9806
|
+
import * as path33 from "path";
|
|
9611
9807
|
function parseStatus(raw) {
|
|
9612
9808
|
const trimmed = raw.trim();
|
|
9613
9809
|
if (trimmed.includes("active")) return "active";
|
|
@@ -9633,10 +9829,10 @@ function parseRow(line) {
|
|
|
9633
9829
|
return { id: id.toUpperCase(), title, branch, status, owner };
|
|
9634
9830
|
}
|
|
9635
9831
|
function parseMissionsFile(repoRoot) {
|
|
9636
|
-
const missionsPath =
|
|
9832
|
+
const missionsPath = path33.join(repoRoot, "docs", "missions", "MISSIONS.md");
|
|
9637
9833
|
let content;
|
|
9638
9834
|
try {
|
|
9639
|
-
content =
|
|
9835
|
+
content = fs32.readFileSync(missionsPath, "utf-8");
|
|
9640
9836
|
} catch {
|
|
9641
9837
|
return [];
|
|
9642
9838
|
}
|