@hua-labs/tap 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -558,7 +558,7 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
558
558
  }
559
559
  if (!sourcePath) {
560
560
  issues.push(
561
- "tap-comms MCP server entry not found. Reinstall @hua-labs/tap or run from a repo with packages/tap-plugin/channels/ available."
561
+ "tap MCP server entry not found. Reinstall @hua-labs/tap or run from a repo with packages/tap-plugin/channels/ available."
562
562
  );
563
563
  return { command: null, args: [], env, sourcePath, warnings, issues };
564
564
  }
@@ -588,7 +588,7 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
588
588
  }
589
589
  if (!command) {
590
590
  issues.push(
591
- "bun is required to run the repo-local tap-comms MCP server (.ts source). Install bun: https://bun.sh"
591
+ "bun is required to run the repo-local tap MCP server (.ts source). Install bun: https://bun.sh"
592
592
  );
593
593
  return { command: null, args: [], env, sourcePath, warnings, issues };
594
594
  }
@@ -782,6 +782,7 @@ var init_runtime = __esm({
782
782
  // src/engine/bridge.ts
783
783
  import * as fs7 from "fs";
784
784
  import * as net from "net";
785
+ import * as os2 from "os";
785
786
  import * as path7 from "path";
786
787
  import { randomBytes } from "crypto";
787
788
  import { spawn, spawnSync as spawnSync2, execSync as execSync2 } from "child_process";
@@ -822,12 +823,66 @@ function removeFileIfExists(filePath) {
822
823
  } catch {
823
824
  }
824
825
  }
826
+ function toPowerShellSingleQuotedString(value) {
827
+ return `'${value.replace(/'/g, "''")}'`;
828
+ }
829
+ function toPowerShellStringArrayLiteral(values) {
830
+ return `@(${values.map(toPowerShellSingleQuotedString).join(", ")})`;
831
+ }
832
+ function cleanupStaleWindowsSpawnWrappers(now = Date.now()) {
833
+ let entries;
834
+ try {
835
+ entries = fs7.readdirSync(os2.tmpdir());
836
+ } catch {
837
+ return;
838
+ }
839
+ for (const entry of entries) {
840
+ if (!entry.startsWith(WINDOWS_SPAWN_WRAPPER_PREFIX) || !/\.(cmd|ps1)$/i.test(entry)) {
841
+ continue;
842
+ }
843
+ const wrapperPath = path7.join(os2.tmpdir(), entry);
844
+ try {
845
+ const stats = fs7.statSync(wrapperPath);
846
+ if (now - stats.mtimeMs < WINDOWS_SPAWN_WRAPPER_STALE_MS) {
847
+ continue;
848
+ }
849
+ fs7.unlinkSync(wrapperPath);
850
+ } catch {
851
+ }
852
+ }
853
+ }
854
+ function buildWindowsDetachedWrapperScript(command, args, logPath, stderrLogPath, env) {
855
+ const lines = ["$ErrorActionPreference = 'Stop'"];
856
+ for (const [key, value] of Object.entries(env)) {
857
+ if (value !== void 0 && value !== process.env[key]) {
858
+ lines.push(
859
+ `[Environment]::SetEnvironmentVariable(${toPowerShellSingleQuotedString(key)}, ${toPowerShellSingleQuotedString(value)}, 'Process')`
860
+ );
861
+ }
862
+ }
863
+ lines.push(
864
+ `$logPath = ${toPowerShellSingleQuotedString(logPath)}`,
865
+ `$stderrLogPath = ${toPowerShellSingleQuotedString(stderrLogPath)}`,
866
+ `$commandPath = ${toPowerShellSingleQuotedString(command)}`,
867
+ `$commandArgs = ${toPowerShellStringArrayLiteral(args)}`,
868
+ "$exitCode = 1",
869
+ "try {",
870
+ " & $commandPath @commandArgs >> $logPath 2>> $stderrLogPath",
871
+ " $exitCode = if ($null -ne $LASTEXITCODE) { $LASTEXITCODE } else { 0 }",
872
+ "} finally {",
873
+ " Remove-Item -LiteralPath $PSCommandPath -Force -ErrorAction SilentlyContinue",
874
+ "}",
875
+ "exit $exitCode"
876
+ );
877
+ return `${lines.join("\r\n")}\r
878
+ `;
879
+ }
825
880
  function getWebSocketCtor() {
826
881
  const candidate = globalThis.WebSocket;
827
882
  return typeof candidate === "function" ? candidate : null;
828
883
  }
829
884
  function delay(ms) {
830
- return new Promise((resolve8) => setTimeout(resolve8, ms));
885
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
831
886
  }
832
887
  function isLoopbackHost(hostname) {
833
888
  return hostname === "127.0.0.1" || hostname === "localhost";
@@ -879,7 +934,7 @@ function getBridgeRuntimeStateDir(repoRoot, instanceId) {
879
934
  }
880
935
  async function allocateLoopbackPort(hostname) {
881
936
  const bindHost = hostname === "localhost" ? "127.0.0.1" : hostname;
882
- return await new Promise((resolve8, reject) => {
937
+ return await new Promise((resolve9, reject) => {
883
938
  const server = net.createServer();
884
939
  server.unref();
885
940
  server.once("error", reject);
@@ -897,7 +952,7 @@ async function allocateLoopbackPort(hostname) {
897
952
  reject(error);
898
953
  return;
899
954
  }
900
- resolve8(port);
955
+ resolve9(port);
901
956
  });
902
957
  });
903
958
  });
@@ -1080,35 +1135,50 @@ function findReusableManagedAppServer(stateDir, publicUrl) {
1080
1135
  return null;
1081
1136
  }
1082
1137
  function startWindowsDetachedProcess(command, args, repoRoot, logPath, env = process.env) {
1083
- const ext = path7.extname(command).toLowerCase();
1084
1138
  const stderrLogPath = stderrLogFilePath(logPath);
1085
- const stdoutFd = fs7.openSync(logPath, "a");
1086
- const stderrFd = fs7.openSync(stderrLogPath, "a");
1087
- try {
1088
- const child = ext === ".ps1" ? spawn(
1089
- resolvePowerShellCommand(),
1090
- ["-NoLogo", "-NoProfile", "-File", command, ...args],
1091
- {
1092
- cwd: repoRoot,
1093
- detached: true,
1094
- stdio: ["ignore", stdoutFd, stderrFd],
1095
- env,
1096
- windowsHide: true
1097
- }
1098
- ) : spawn(command, args, {
1099
- cwd: repoRoot,
1100
- detached: true,
1101
- stdio: ["ignore", stdoutFd, stderrFd],
1102
- env,
1103
- windowsHide: true,
1104
- shell: ext === ".cmd" || ext === ".bat"
1105
- });
1106
- child.unref();
1107
- return child.pid ?? null;
1108
- } finally {
1109
- fs7.closeSync(stdoutFd);
1110
- fs7.closeSync(stderrFd);
1139
+ const powerShellCommand = resolvePowerShellCommand();
1140
+ cleanupStaleWindowsSpawnWrappers();
1141
+ const wrapperPath = path7.join(
1142
+ os2.tmpdir(),
1143
+ `${WINDOWS_SPAWN_WRAPPER_PREFIX}${randomBytes(4).toString("hex")}.ps1`
1144
+ );
1145
+ fs7.writeFileSync(
1146
+ wrapperPath,
1147
+ buildWindowsDetachedWrapperScript(
1148
+ command,
1149
+ args,
1150
+ logPath,
1151
+ stderrLogPath,
1152
+ env
1153
+ )
1154
+ );
1155
+ const psCommand = [
1156
+ "$p = Start-Process",
1157
+ `-FilePath ${toPowerShellSingleQuotedString(powerShellCommand)}`,
1158
+ `-ArgumentList ${toPowerShellStringArrayLiteral(["-NoLogo", "-NoProfile", "-File", wrapperPath])}`,
1159
+ `-WorkingDirectory ${toPowerShellSingleQuotedString(repoRoot)}`,
1160
+ "-WindowStyle Hidden",
1161
+ "-PassThru",
1162
+ "; Write-Output $p.Id"
1163
+ ].join(" ");
1164
+ const result = spawnSync2(
1165
+ powerShellCommand,
1166
+ ["-NoLogo", "-NoProfile", "-Command", psCommand],
1167
+ {
1168
+ encoding: "utf-8",
1169
+ windowsHide: true
1170
+ }
1171
+ );
1172
+ if (result.status !== 0) {
1173
+ removeFileIfExists(wrapperPath);
1174
+ return null;
1111
1175
  }
1176
+ const pid = parseInt(result.stdout.trim(), 10);
1177
+ if (!Number.isFinite(pid)) {
1178
+ removeFileIfExists(wrapperPath);
1179
+ return null;
1180
+ }
1181
+ return pid;
1112
1182
  }
1113
1183
  function startWindowsCodexAppServer(command, url, repoRoot, logPath) {
1114
1184
  return startWindowsDetachedProcess(
@@ -1170,12 +1240,12 @@ function resolveAppServerUrl(baseUrl, port) {
1170
1240
  }
1171
1241
  async function isTcpPortAvailable(hostname, port) {
1172
1242
  const bindHost = hostname === "localhost" ? "127.0.0.1" : hostname;
1173
- return await new Promise((resolve8) => {
1243
+ return await new Promise((resolve9) => {
1174
1244
  const server = net.createServer();
1175
1245
  server.unref();
1176
- server.once("error", () => resolve8(false));
1246
+ server.once("error", () => resolve9(false));
1177
1247
  server.listen(port, bindHost, () => {
1178
- server.close((error) => resolve8(!error));
1248
+ server.close((error) => resolve9(!error));
1179
1249
  });
1180
1250
  });
1181
1251
  }
@@ -1210,7 +1280,7 @@ async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_M
1210
1280
  if (!WebSocket) {
1211
1281
  return false;
1212
1282
  }
1213
- return new Promise((resolve8) => {
1283
+ return new Promise((resolve9) => {
1214
1284
  let settled = false;
1215
1285
  let socket = null;
1216
1286
  const finish = (healthy) => {
@@ -1223,7 +1293,7 @@ async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_M
1223
1293
  socket?.close();
1224
1294
  } catch {
1225
1295
  }
1226
- resolve8(healthy);
1296
+ resolve9(healthy);
1227
1297
  };
1228
1298
  const timer = setTimeout(() => finish(false), timeoutMs);
1229
1299
  try {
@@ -1547,7 +1617,11 @@ function logFilePath(stateDir, instanceId) {
1547
1617
  function runtimeHeartbeatFilePath(runtimeStateDir) {
1548
1618
  return path7.join(runtimeStateDir, "heartbeat.json");
1549
1619
  }
1550
- function loadRuntimeHeartbeatTimestamp(runtimeStateDir) {
1620
+ function runtimeThreadStateFilePath(runtimeStateDir) {
1621
+ return path7.join(runtimeStateDir, "thread.json");
1622
+ }
1623
+ function loadRuntimeBridgeHeartbeat(bridgeState) {
1624
+ const runtimeStateDir = bridgeState?.runtimeStateDir;
1551
1625
  if (!runtimeStateDir) {
1552
1626
  return null;
1553
1627
  }
@@ -1556,13 +1630,35 @@ function loadRuntimeHeartbeatTimestamp(runtimeStateDir) {
1556
1630
  return null;
1557
1631
  }
1558
1632
  try {
1559
- const raw = fs7.readFileSync(heartbeatPath, "utf-8");
1560
- const parsed = JSON.parse(raw);
1561
- return typeof parsed.updatedAt === "string" ? parsed.updatedAt : null;
1633
+ return JSON.parse(
1634
+ fs7.readFileSync(heartbeatPath, "utf-8")
1635
+ );
1636
+ } catch {
1637
+ return null;
1638
+ }
1639
+ }
1640
+ function loadRuntimeBridgeThreadState(bridgeState) {
1641
+ const runtimeStateDir = bridgeState?.runtimeStateDir;
1642
+ if (!runtimeStateDir) {
1643
+ return null;
1644
+ }
1645
+ const threadPath = runtimeThreadStateFilePath(runtimeStateDir);
1646
+ if (!fs7.existsSync(threadPath)) {
1647
+ return null;
1648
+ }
1649
+ try {
1650
+ const parsed = JSON.parse(
1651
+ fs7.readFileSync(threadPath, "utf-8")
1652
+ );
1653
+ return parsed.threadId ? parsed : null;
1562
1654
  } catch {
1563
1655
  return null;
1564
1656
  }
1565
1657
  }
1658
+ function loadRuntimeHeartbeatTimestamp(runtimeStateDir) {
1659
+ const heartbeat = loadRuntimeBridgeHeartbeat({ runtimeStateDir });
1660
+ return typeof heartbeat?.updatedAt === "string" ? heartbeat.updatedAt : null;
1661
+ }
1566
1662
  function resolveHeartbeatTimestamp(state) {
1567
1663
  return loadRuntimeHeartbeatTimestamp(state?.runtimeStateDir) ?? state?.lastHeartbeat ?? null;
1568
1664
  }
@@ -1732,6 +1828,7 @@ async function startBridge(options) {
1732
1828
  options.messageLookbackMinutes
1733
1829
  )
1734
1830
  } : {},
1831
+ ...process.env.TAP_COLD_START_WARMUP === "true" ? { TAP_COLD_START_WARMUP: "true" } : {},
1735
1832
  ...options.threadId ? { TAP_THREAD_ID: options.threadId } : {},
1736
1833
  ...options.ephemeral ? { TAP_EPHEMERAL: "true" } : {},
1737
1834
  ...options.processExistingMessages ? { TAP_PROCESS_EXISTING: "true" } : {}
@@ -1868,7 +1965,7 @@ function getBridgeStatus(stateDir, instanceId) {
1868
1965
  }
1869
1966
  return "running";
1870
1967
  }
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;
1968
+ var DEFAULT_APP_SERVER_URL2, APP_SERVER_HEALTH_TIMEOUT_MS, APP_SERVER_START_TIMEOUT_MS, APP_SERVER_GATEWAY_START_TIMEOUT_MS, APP_SERVER_HEALTH_RETRY_MS, AUTH_SUBPROTOCOL_PREFIX, APP_SERVER_AUTH_FILE_MODE, WINDOWS_SPAWN_WRAPPER_PREFIX, WINDOWS_SPAWN_WRAPPER_STALE_MS;
1872
1969
  var init_bridge = __esm({
1873
1970
  "src/engine/bridge.ts"() {
1874
1971
  "use strict";
@@ -1882,6 +1979,8 @@ var init_bridge = __esm({
1882
1979
  APP_SERVER_HEALTH_RETRY_MS = 250;
1883
1980
  AUTH_SUBPROTOCOL_PREFIX = "tap-auth-";
1884
1981
  APP_SERVER_AUTH_FILE_MODE = 384;
1982
+ WINDOWS_SPAWN_WRAPPER_PREFIX = "tap-spawn-";
1983
+ WINDOWS_SPAWN_WRAPPER_STALE_MS = 60 * 60 * 1e3;
1885
1984
  }
1886
1985
  });
1887
1986
 
@@ -2082,13 +2181,14 @@ function setNestedKey(obj, keyPath, value) {
2082
2181
  function normalizeTapCommsDir(value) {
2083
2182
  return typeof value === "string" ? path9.resolve(value).replace(/\\/g, "/") : "";
2084
2183
  }
2085
- var MCP_SERVER_KEY, claudeAdapter;
2184
+ var MCP_SERVER_KEY, OLD_MCP_SERVER_KEY, claudeAdapter;
2086
2185
  var init_claude = __esm({
2087
2186
  "src/adapters/claude.ts"() {
2088
2187
  "use strict";
2089
2188
  init_state();
2090
2189
  init_common();
2091
- MCP_SERVER_KEY = "tap-comms";
2190
+ MCP_SERVER_KEY = "tap";
2191
+ OLD_MCP_SERVER_KEY = "tap-comms";
2092
2192
  claudeAdapter = {
2093
2193
  runtime: "claude",
2094
2194
  async probe(ctx) {
@@ -2145,6 +2245,11 @@ var init_claude = __esm({
2145
2245
  `Existing "${MCP_SERVER_KEY}" entry in .mcp.json will be overwritten.`
2146
2246
  );
2147
2247
  }
2248
+ if (config.mcpServers?.[OLD_MCP_SERVER_KEY]) {
2249
+ conflicts.push(
2250
+ `Legacy "${OLD_MCP_SERVER_KEY}" entry will be migrated to "${MCP_SERVER_KEY}".`
2251
+ );
2252
+ }
2148
2253
  } catch {
2149
2254
  warnings.push(
2150
2255
  ".mcp.json exists but is not valid JSON. Will be overwritten."
@@ -2154,7 +2259,7 @@ var init_claude = __esm({
2154
2259
  const serverEntry = buildMcpServerEntry(ctx);
2155
2260
  if (!serverEntry) {
2156
2261
  warnings.push(
2157
- "tap-comms MCP server entry not found. Skipping .mcp.json patch. Reinstall @hua-labs/tap or run from a repo with packages/tap-plugin/channels/ available."
2262
+ "tap MCP server entry not found. Skipping .mcp.json patch. Reinstall @hua-labs/tap or run from a repo with packages/tap-plugin/channels/ available."
2158
2263
  );
2159
2264
  return {
2160
2265
  runtime: "claude",
@@ -2207,6 +2312,10 @@ var init_claude = __esm({
2207
2312
  );
2208
2313
  }
2209
2314
  }
2315
+ const servers = config.mcpServers;
2316
+ if (servers?.[OLD_MCP_SERVER_KEY]) {
2317
+ delete servers[OLD_MCP_SERVER_KEY];
2318
+ }
2210
2319
  if (op.key) {
2211
2320
  setNestedKey(config, op.key, op.value);
2212
2321
  }
@@ -2255,7 +2364,7 @@ var init_claude = __esm({
2255
2364
  checks.push({ name: "Config is valid JSON", passed: true });
2256
2365
  const entry = config.mcpServers?.[MCP_SERVER_KEY];
2257
2366
  checks.push({
2258
- name: "tap-comms entry present",
2367
+ name: "tap entry present",
2259
2368
  passed: !!entry,
2260
2369
  message: entry ? void 0 : `mcpServers.${MCP_SERVER_KEY} not found`
2261
2370
  });
@@ -2364,6 +2473,14 @@ function extractTomlTable(content, selector) {
2364
2473
  return `${lines.slice(range.start, range.end).join("\n")}
2365
2474
  `;
2366
2475
  }
2476
+ function removeTomlTable(content, selector) {
2477
+ const lines = splitLines(content);
2478
+ const range = findTableRange(lines, selector);
2479
+ if (!range) return content;
2480
+ const next = [...lines.slice(0, range.start), ...lines.slice(range.end)];
2481
+ return `${trimTomlDocument(next.join("\n"))}
2482
+ `;
2483
+ }
2367
2484
  function replaceTomlTable(content, selector, replacement) {
2368
2485
  const lines = splitLines(content);
2369
2486
  const range = findTableRange(lines, selector);
@@ -2489,12 +2606,12 @@ function verifyManagedToml(content, ctx, configPath) {
2489
2606
  message: fs11.existsSync(configPath) ? void 0 : `${configPath} not found`
2490
2607
  });
2491
2608
  checks.push({
2492
- name: "tap-comms MCP table present",
2609
+ name: "tap MCP table present",
2493
2610
  passed: !!mainTable,
2494
2611
  message: mainTable ? void 0 : `${MCP_SELECTOR} not found`
2495
2612
  });
2496
2613
  checks.push({
2497
- name: "tap-comms env table present",
2614
+ name: "tap env table present",
2498
2615
  passed: !!envTable,
2499
2616
  message: envTable ? void 0 : `${ENV_SELECTOR} not found`
2500
2617
  });
@@ -2514,12 +2631,12 @@ function verifyManagedToml(content, ctx, configPath) {
2514
2631
  passed: mainTable.includes(
2515
2632
  `command = "${managed.command.replace(/\\/g, "\\\\")}"`
2516
2633
  ) && mainTable.includes(`args = [${expectedArgs}]`),
2517
- message: "Managed tap-comms command/args do not match expected values"
2634
+ message: "Managed tap command/args do not match expected values"
2518
2635
  });
2519
2636
  }
2520
2637
  return checks;
2521
2638
  }
2522
- var MCP_SELECTOR, ENV_SELECTOR, codexAdapter;
2639
+ var MCP_SELECTOR, ENV_SELECTOR, OLD_MCP_SELECTOR, OLD_ENV_SELECTOR, codexAdapter;
2523
2640
  var init_codex = __esm({
2524
2641
  "src/adapters/codex.ts"() {
2525
2642
  "use strict";
@@ -2527,8 +2644,10 @@ var init_codex = __esm({
2527
2644
  init_artifact_backups();
2528
2645
  init_toml();
2529
2646
  init_common();
2530
- MCP_SELECTOR = "mcp_servers.tap-comms";
2531
- ENV_SELECTOR = "mcp_servers.tap-comms.env";
2647
+ MCP_SELECTOR = "mcp_servers.tap";
2648
+ ENV_SELECTOR = "mcp_servers.tap.env";
2649
+ OLD_MCP_SELECTOR = "mcp_servers.tap-comms";
2650
+ OLD_ENV_SELECTOR = "mcp_servers.tap-comms.env";
2532
2651
  codexAdapter = {
2533
2652
  runtime: "codex",
2534
2653
  async probe(ctx) {
@@ -2574,6 +2693,11 @@ var init_codex = __esm({
2574
2693
  if (extractTomlTable(content, MCP_SELECTOR)) {
2575
2694
  conflicts.push(`Existing ${MCP_SELECTOR} table will be updated.`);
2576
2695
  }
2696
+ if (extractTomlTable(content, OLD_MCP_SELECTOR)) {
2697
+ conflicts.push(
2698
+ `Legacy ${OLD_MCP_SELECTOR} table will be migrated to ${MCP_SELECTOR}.`
2699
+ );
2700
+ }
2577
2701
  if (extractTomlTable(content, ENV_SELECTOR)) {
2578
2702
  conflicts.push(`Existing ${ENV_SELECTOR} table will be updated.`);
2579
2703
  }
@@ -2639,6 +2763,12 @@ var init_codex = __esm({
2639
2763
  return { ...artifact, backupPath };
2640
2764
  });
2641
2765
  let nextContent = existingContent;
2766
+ if (extractTomlTable(nextContent, OLD_ENV_SELECTOR)) {
2767
+ nextContent = removeTomlTable(nextContent, OLD_ENV_SELECTOR);
2768
+ }
2769
+ if (extractTomlTable(nextContent, OLD_MCP_SELECTOR)) {
2770
+ nextContent = removeTomlTable(nextContent, OLD_MCP_SELECTOR);
2771
+ }
2642
2772
  nextContent = replaceTomlTable(
2643
2773
  nextContent,
2644
2774
  MCP_SELECTOR,
@@ -2815,7 +2945,7 @@ function verifyGeminiConfig(config, configPath, ctx) {
2815
2945
  message: fs12.existsSync(configPath) ? void 0 : `${configPath} not found`
2816
2946
  });
2817
2947
  checks.push({
2818
- name: "tap-comms entry present",
2948
+ name: "tap entry present",
2819
2949
  passed: !!entry,
2820
2950
  message: entry ? void 0 : `${GEMINI_SELECTOR} not found`
2821
2951
  });
@@ -2833,14 +2963,15 @@ function verifyGeminiConfig(config, configPath, ctx) {
2833
2963
  }
2834
2964
  return checks;
2835
2965
  }
2836
- var GEMINI_SELECTOR, geminiAdapter;
2966
+ var GEMINI_SELECTOR, OLD_GEMINI_SELECTOR, geminiAdapter;
2837
2967
  var init_gemini = __esm({
2838
2968
  "src/adapters/gemini.ts"() {
2839
2969
  "use strict";
2840
2970
  init_state();
2841
2971
  init_artifact_backups();
2842
2972
  init_common();
2843
- GEMINI_SELECTOR = "mcpServers.tap-comms";
2973
+ GEMINI_SELECTOR = "mcpServers.tap";
2974
+ OLD_GEMINI_SELECTOR = "mcpServers.tap-comms";
2844
2975
  geminiAdapter = {
2845
2976
  runtime: "gemini",
2846
2977
  async probe(ctx) {
@@ -2889,6 +3020,11 @@ var init_gemini = __esm({
2889
3020
  if (readNestedKey(config, GEMINI_SELECTOR) !== void 0) {
2890
3021
  conflicts.push(`Existing ${GEMINI_SELECTOR} entry will be updated.`);
2891
3022
  }
3023
+ if (readNestedKey(config, OLD_GEMINI_SELECTOR) !== void 0) {
3024
+ conflicts.push(
3025
+ `Legacy ${OLD_GEMINI_SELECTOR} entry will be migrated to ${GEMINI_SELECTOR}.`
3026
+ );
3027
+ }
2892
3028
  } catch {
2893
3029
  warnings.push(
2894
3030
  `${configPath} exists but is not valid JSON. It will be replaced.`
@@ -2956,6 +3092,13 @@ var init_gemini = __esm({
2956
3092
  existed: previousValue !== void 0,
2957
3093
  value: previousValue
2958
3094
  });
3095
+ const oldValue = readNestedKey(config, OLD_GEMINI_SELECTOR);
3096
+ if (oldValue !== void 0) {
3097
+ const servers = config.mcpServers;
3098
+ if (servers) {
3099
+ delete servers["tap-comms"];
3100
+ }
3101
+ }
2959
3102
  setNestedKey2(config, GEMINI_SELECTOR, {
2960
3103
  command: managed.command,
2961
3104
  args: managed.args,
@@ -3073,6 +3216,21 @@ function redactProtectedUrl(url) {
3073
3216
  function loadCurrentBridgeState(stateDir, instanceId, fallback) {
3074
3217
  return loadBridgeState(stateDir, instanceId) ?? fallback ?? null;
3075
3218
  }
3219
+ function formatThreadSummary(threadId, cwd) {
3220
+ if (!threadId) {
3221
+ return "-";
3222
+ }
3223
+ return cwd ? `${threadId} (${cwd})` : threadId;
3224
+ }
3225
+ function normalizeComparablePath(value) {
3226
+ return path13.resolve(value).replace(/\\/g, "/").toLowerCase();
3227
+ }
3228
+ function sameOptionalPath(left, right) {
3229
+ if (!left || !right) {
3230
+ return left === right;
3231
+ }
3232
+ return normalizeComparablePath(left) === normalizeComparablePath(right);
3233
+ }
3076
3234
  function getSharedAppServerUsers(state, stateDir, currentInstanceId, appServerUrl) {
3077
3235
  const shared = [];
3078
3236
  for (const [id, inst] of Object.entries(state.instances)) {
@@ -3685,12 +3843,18 @@ function bridgeStatusAll() {
3685
3843
  pid: null,
3686
3844
  port: inst.port,
3687
3845
  lastHeartbeat: null,
3846
+ threadId: null,
3847
+ threadCwd: null,
3848
+ savedThreadId: null,
3849
+ savedThreadCwd: null,
3688
3850
  appServer: null
3689
3851
  };
3690
3852
  continue;
3691
3853
  }
3692
3854
  const status = getBridgeStatus(stateDir, instanceId);
3693
3855
  const bridgeState = loadBridgeState(stateDir, instanceId);
3856
+ const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(bridgeState);
3857
+ const savedThread = loadRuntimeBridgeThreadState(bridgeState);
3694
3858
  const age = getHeartbeatAge(stateDir, instanceId);
3695
3859
  const pid = bridgeState?.pid ?? null;
3696
3860
  const heartbeat = getBridgeHeartbeatTimestamp(stateDir, instanceId);
@@ -3712,12 +3876,26 @@ function bridgeStatusAll() {
3712
3876
  );
3713
3877
  }
3714
3878
  }
3879
+ if (runtimeHeartbeat?.threadId) {
3880
+ log(
3881
+ ` Thread: ${formatThreadSummary(runtimeHeartbeat.threadId, runtimeHeartbeat.threadCwd)}`
3882
+ );
3883
+ }
3884
+ if (savedThread?.threadId && (savedThread.threadId !== runtimeHeartbeat?.threadId || !sameOptionalPath(savedThread.cwd, runtimeHeartbeat?.threadCwd))) {
3885
+ log(
3886
+ ` Saved: ${formatThreadSummary(savedThread.threadId, savedThread.cwd)}`
3887
+ );
3888
+ }
3715
3889
  bridges[instanceId] = {
3716
3890
  status,
3717
3891
  runtime: inst.runtime,
3718
3892
  pid,
3719
3893
  port: inst.port,
3720
3894
  lastHeartbeat: heartbeat,
3895
+ threadId: runtimeHeartbeat?.threadId ?? null,
3896
+ threadCwd: runtimeHeartbeat?.threadCwd ?? null,
3897
+ savedThreadId: savedThread?.threadId ?? null,
3898
+ savedThreadCwd: savedThread?.cwd ?? null,
3721
3899
  appServer: bridgeState?.appServer ?? null
3722
3900
  };
3723
3901
  }
@@ -3793,6 +3971,10 @@ function bridgeStatusOne(identifier) {
3793
3971
  pid: null,
3794
3972
  port: inst.port,
3795
3973
  lastHeartbeat: null,
3974
+ threadId: null,
3975
+ threadCwd: null,
3976
+ savedThreadId: null,
3977
+ savedThreadCwd: null,
3796
3978
  appServer: null
3797
3979
  }
3798
3980
  };
@@ -3801,6 +3983,8 @@ function bridgeStatusOne(identifier) {
3801
3983
  const stateDir = resolvedCfg2.stateDir;
3802
3984
  const status = getBridgeStatus(stateDir, instanceId);
3803
3985
  const bridgeState = loadBridgeState(stateDir, instanceId);
3986
+ const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(bridgeState);
3987
+ const savedThread = loadRuntimeBridgeThreadState(bridgeState);
3804
3988
  const age = getHeartbeatAge(stateDir, instanceId);
3805
3989
  const heartbeat = getBridgeHeartbeatTimestamp(stateDir, instanceId);
3806
3990
  log(`Status: ${status}`);
@@ -3809,6 +3993,16 @@ function bridgeStatusOne(identifier) {
3809
3993
  log(
3810
3994
  `Heartbeat: ${heartbeat ?? "-"}${age !== null ? ` (${formatAge(age)})` : ""}`
3811
3995
  );
3996
+ if (runtimeHeartbeat?.threadId) {
3997
+ log(
3998
+ `Thread: ${formatThreadSummary(runtimeHeartbeat.threadId, runtimeHeartbeat.threadCwd)}`
3999
+ );
4000
+ }
4001
+ if (savedThread?.threadId && (savedThread.threadId !== runtimeHeartbeat?.threadId || !sameOptionalPath(savedThread.cwd, runtimeHeartbeat?.threadCwd))) {
4002
+ log(
4003
+ `Saved: ${formatThreadSummary(savedThread.threadId, savedThread.cwd)}`
4004
+ );
4005
+ }
3812
4006
  log(
3813
4007
  `Log: ${path13.join(stateDir, "logs", `bridge-${instanceId}.log`)}`
3814
4008
  );
@@ -3857,6 +4051,10 @@ function bridgeStatusOne(identifier) {
3857
4051
  pid: bridgeState?.pid ?? null,
3858
4052
  port: inst.port,
3859
4053
  lastHeartbeat: heartbeat,
4054
+ threadId: runtimeHeartbeat?.threadId ?? null,
4055
+ threadCwd: runtimeHeartbeat?.threadCwd ?? null,
4056
+ savedThreadId: savedThread?.threadId ?? null,
4057
+ savedThreadCwd: savedThread?.cwd ?? null,
3860
4058
  appServer: bridgeState?.appServer ?? null
3861
4059
  }
3862
4060
  };
@@ -4084,7 +4282,7 @@ var init_bridge2 = __esm({
4084
4282
  init_utils();
4085
4283
  BRIDGE_HELP = `
4086
4284
  Usage:
4087
- tap-comms bridge <subcommand> [instance] [options]
4285
+ tap bridge <subcommand> [instance] [options]
4088
4286
 
4089
4287
  Subcommands:
4090
4288
  start <instance> Start bridge for an instance (e.g. codex, codex-reviewer)
@@ -4142,7 +4340,18 @@ async function upCommand(args) {
4142
4340
  };
4143
4341
  }
4144
4342
  const repoRoot = findRepoRoot();
4145
- const result = await bridgeCommand(["start", "--all", ...args]);
4343
+ const previousColdStartWarmup = process.env.TAP_COLD_START_WARMUP;
4344
+ process.env.TAP_COLD_START_WARMUP = "true";
4345
+ let result;
4346
+ try {
4347
+ result = await bridgeCommand(["start", "--all", ...args]);
4348
+ } finally {
4349
+ if (previousColdStartWarmup === void 0) {
4350
+ delete process.env.TAP_COLD_START_WARMUP;
4351
+ } else {
4352
+ process.env.TAP_COLD_START_WARMUP = previousColdStartWarmup;
4353
+ }
4354
+ }
4146
4355
  const snapshot = collectDashboardSnapshot(repoRoot);
4147
4356
  const activeBridges = snapshot.bridges.filter(
4148
4357
  (bridge) => bridge.status === "running"
@@ -4178,7 +4387,7 @@ var init_up = __esm({
4178
4387
  init_utils();
4179
4388
  UP_HELP = `
4180
4389
  Usage:
4181
- tap-comms up [bridge-start options]
4390
+ tap up [bridge-start options]
4182
4391
 
4183
4392
  Description:
4184
4393
  Start all registered app-server bridge daemons with one command.
@@ -4243,7 +4452,7 @@ var init_down = __esm({
4243
4452
  init_utils();
4244
4453
  DOWN_HELP = `
4245
4454
  Usage:
4246
- tap-comms down
4455
+ tap down
4247
4456
 
4248
4457
  Description:
4249
4458
  Stop all running bridge daemons and managed app-servers.
@@ -4297,14 +4506,14 @@ async function* streamEvents(options) {
4297
4506
  const repoRoot = options?.repoRoot ?? findRepoRoot();
4298
4507
  while (!options?.signal?.aborted) {
4299
4508
  yield collectDashboardSnapshot(repoRoot, options?.commsDir);
4300
- await new Promise((resolve8) => {
4509
+ await new Promise((resolve9) => {
4301
4510
  const onAbort = () => {
4302
4511
  clearTimeout(timer);
4303
- resolve8();
4512
+ resolve9();
4304
4513
  };
4305
4514
  const timer = setTimeout(() => {
4306
4515
  options?.signal?.removeEventListener("abort", onAbort);
4307
- resolve8();
4516
+ resolve9();
4308
4517
  }, intervalMs);
4309
4518
  options?.signal?.addEventListener("abort", onAbort, { once: true });
4310
4519
  });
@@ -4545,18 +4754,18 @@ async function startHttpServer(options) {
4545
4754
  }
4546
4755
  }
4547
4756
  );
4548
- await new Promise((resolve8, reject) => {
4757
+ await new Promise((resolve9, reject) => {
4549
4758
  server.once("error", reject);
4550
4759
  server.listen(port, host, () => {
4551
4760
  server.removeListener("error", reject);
4552
- resolve8();
4761
+ resolve9();
4553
4762
  });
4554
4763
  });
4555
4764
  return {
4556
4765
  port,
4557
4766
  token,
4558
- close: () => new Promise((resolve8, reject) => {
4559
- server.close((err) => err ? reject(err) : resolve8());
4767
+ close: () => new Promise((resolve9, reject) => {
4768
+ server.close((err) => err ? reject(err) : resolve9());
4560
4769
  })
4561
4770
  };
4562
4771
  }