@hua-labs/tap 0.5.0 → 0.5.1

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/cli.mjs CHANGED
@@ -2930,13 +2930,14 @@ function startWindowsDetachedProcess(command, args, repoRoot, logPath, env = pro
2930
2930
  }
2931
2931
  return pid;
2932
2932
  }
2933
- function startWindowsCodexAppServer(command, url, repoRoot, logPath) {
2933
+ function startWindowsCodexAppServer(command, url, repoRoot, logPath, env = process.env) {
2934
2934
  const { command: exe, prefixArgs } = splitResolvedCommand(command);
2935
2935
  return startWindowsDetachedProcess(
2936
2936
  exe,
2937
2937
  [...prefixArgs, "app-server", "--listen", url],
2938
2938
  repoRoot,
2939
- logPath
2939
+ logPath,
2940
+ env
2940
2941
  );
2941
2942
  }
2942
2943
  function findListeningProcessId(url, platform) {
@@ -3046,14 +3047,14 @@ function startUnixDetachedProcess(command, args, repoRoot, logPath, env = proces
3046
3047
  }
3047
3048
  }
3048
3049
  }
3049
- function startUnixCodexAppServer(command, url, repoRoot, logPath, platform = DEFAULT_UNIX_PLATFORM) {
3050
+ function startUnixCodexAppServer(command, url, repoRoot, logPath, env = process.env, platform = DEFAULT_UNIX_PLATFORM) {
3050
3051
  const { command: exe, prefixArgs } = splitResolvedCommand(command);
3051
3052
  return startUnixDetachedProcess(
3052
3053
  exe,
3053
3054
  [...prefixArgs, "app-server", "--listen", url],
3054
3055
  repoRoot,
3055
3056
  logPath,
3056
- process.env,
3057
+ env,
3057
3058
  platform
3058
3059
  );
3059
3060
  }
@@ -4022,6 +4023,19 @@ import * as path21 from "path";
4022
4023
  var DEFAULT_APP_SERVER_URL3 = "ws://127.0.0.1:4501";
4023
4024
  var APP_SERVER_START_TIMEOUT_MS = 2e4;
4024
4025
  var APP_SERVER_GATEWAY_START_TIMEOUT_MS = 5e3;
4026
+ function buildCodexAppServerEnv(options) {
4027
+ return {
4028
+ ...process.env,
4029
+ TAP_COMMS_DIR: options.commsDir,
4030
+ TAP_STATE_DIR: options.stateDir,
4031
+ TAP_RUNTIME_STATE_DIR: options.runtimeStateDir,
4032
+ TAP_REPO_ROOT: options.repoRoot,
4033
+ TAP_BRIDGE_INSTANCE_ID: options.instanceId,
4034
+ TAP_AGENT_ID: options.instanceId,
4035
+ TAP_AGENT_NAME: options.agentName,
4036
+ CODEX_TAP_AGENT_NAME: options.agentName
4037
+ };
4038
+ }
4025
4039
  function isAppServerUsedByOtherBridge(stateDir, excludeInstanceId, appServer) {
4026
4040
  const pidDir = path21.join(stateDir, "pids");
4027
4041
  if (!fs24.existsSync(pidDir)) return false;
@@ -4125,6 +4139,7 @@ Start the app-server manually:
4125
4139
  const logPath = appServerLogFilePath(options.stateDir, options.instanceId);
4126
4140
  fs24.mkdirSync(path21.dirname(logPath), { recursive: true });
4127
4141
  rotateLog(logPath);
4142
+ const appServerEnv = buildCodexAppServerEnv(options);
4128
4143
  if (options.noAuth) {
4129
4144
  const manualCommand2 = formatCodexAppServerCommand("codex", effectiveUrl);
4130
4145
  let pid2;
@@ -4134,7 +4149,8 @@ Start the app-server manually:
4134
4149
  resolvedCommand,
4135
4150
  effectiveUrl,
4136
4151
  options.repoRoot,
4137
- logPath
4152
+ logPath,
4153
+ appServerEnv
4138
4154
  );
4139
4155
  } catch (err) {
4140
4156
  throw new Error(
@@ -4151,6 +4167,7 @@ Start it manually:
4151
4167
  effectiveUrl,
4152
4168
  options.repoRoot,
4153
4169
  logPath,
4170
+ appServerEnv,
4154
4171
  options.platform
4155
4172
  );
4156
4173
  } catch (err) {
@@ -4211,7 +4228,8 @@ Or start it manually:
4211
4228
  resolvedCommand,
4212
4229
  auth.upstreamUrl,
4213
4230
  options.repoRoot,
4214
- logPath
4231
+ logPath,
4232
+ appServerEnv
4215
4233
  );
4216
4234
  } catch (err) {
4217
4235
  if (auth.gatewayPid != null) {
@@ -4232,6 +4250,7 @@ Start it manually:
4232
4250
  auth.upstreamUrl,
4233
4251
  options.repoRoot,
4234
4252
  logPath,
4253
+ appServerEnv,
4235
4254
  options.platform
4236
4255
  );
4237
4256
  } catch (err) {
@@ -4444,9 +4463,12 @@ async function startBridge(options) {
4444
4463
  appServer = await ensureCodexAppServer({
4445
4464
  instanceId,
4446
4465
  stateDir,
4466
+ runtimeStateDir,
4467
+ commsDir,
4447
4468
  repoRoot,
4448
4469
  platform: options.platform,
4449
4470
  appServerUrl: effectiveAppServerUrl,
4471
+ agentName: resolvedAgent,
4450
4472
  existingAppServer: previousAppServer,
4451
4473
  noAuth: options.noAuth
4452
4474
  });
@@ -4467,7 +4489,9 @@ async function startBridge(options) {
4467
4489
  const bridgeEnv = {
4468
4490
  ...runtimeEnv,
4469
4491
  TAP_COMMS_DIR: commsDir,
4470
- TAP_STATE_DIR: runtimeStateDir,
4492
+ TAP_STATE_DIR: stateDir,
4493
+ TAP_RUNTIME_STATE_DIR: runtimeStateDir,
4494
+ TAP_REPO_ROOT: repoRoot,
4471
4495
  TAP_BRIDGE_RUNTIME: runtime,
4472
4496
  TAP_BRIDGE_INSTANCE_ID: instanceId,
4473
4497
  TAP_AGENT_ID: instanceId,
@@ -5051,6 +5075,53 @@ async function addCommand(args) {
5051
5075
  };
5052
5076
  }
5053
5077
 
5078
+ // src/engine/health-monitor.ts
5079
+ import * as fs27 from "fs";
5080
+ import * as path24 from "path";
5081
+ var DISPATCH_EVIDENCE_FRESH_THRESHOLD_MS = 2 * 60 * 1e3;
5082
+ function getHeartbeatActivityMs2(record) {
5083
+ const timestamp = new Date(record.lastActivity ?? record.timestamp ?? 0).getTime();
5084
+ return Number.isFinite(timestamp) ? timestamp : null;
5085
+ }
5086
+ function isSameInstanceHeartbeat2(key, heartbeat, instanceId) {
5087
+ if (heartbeat.instanceId === instanceId) return true;
5088
+ if (heartbeat.connectHash === `instance:${instanceId}`) return true;
5089
+ return key === instanceId || key.replace(/_/g, "-") === instanceId || key.replace(/-/g, "_") === instanceId;
5090
+ }
5091
+ function loadLiveDispatchEvidence(commsDir, instanceId) {
5092
+ const heartbeatsPath = path24.join(commsDir, "heartbeats.json");
5093
+ if (!fs27.existsSync(heartbeatsPath)) return null;
5094
+ try {
5095
+ const store = JSON.parse(
5096
+ fs27.readFileSync(heartbeatsPath, "utf-8")
5097
+ );
5098
+ let best = null;
5099
+ let bestActivityMs = -1;
5100
+ for (const [key, heartbeat] of Object.entries(store)) {
5101
+ if (!isSameInstanceHeartbeat2(key, heartbeat, instanceId)) continue;
5102
+ if (heartbeat.source !== "bridge-dispatch") continue;
5103
+ if (heartbeat.bridgePid == null || !isProcessAlive(heartbeat.bridgePid)) {
5104
+ continue;
5105
+ }
5106
+ const activityMs = getHeartbeatActivityMs2(heartbeat);
5107
+ if (activityMs == null || Date.now() - activityMs > DISPATCH_EVIDENCE_FRESH_THRESHOLD_MS) {
5108
+ continue;
5109
+ }
5110
+ if (activityMs > bestActivityMs) {
5111
+ bestActivityMs = activityMs;
5112
+ best = {
5113
+ bridgePid: heartbeat.bridgePid,
5114
+ lastActivity: heartbeat.lastActivity ?? heartbeat.timestamp ?? new Date(activityMs).toISOString()
5115
+ };
5116
+ }
5117
+ }
5118
+ return best;
5119
+ } catch {
5120
+ return null;
5121
+ }
5122
+ }
5123
+ var HEARTBEAT_FRESH_THRESHOLD_MS = 2 * 60 * 1e3;
5124
+
5054
5125
  // src/commands/status.ts
5055
5126
  init_config();
5056
5127
  init_utils();
@@ -5064,12 +5135,13 @@ Description:
5064
5135
  Examples:
5065
5136
  npx @hua-labs/tap status
5066
5137
  `.trim();
5067
- function resolveStatus(inst, stateDir) {
5138
+ function resolveStatus(inst, stateDir, commsDir) {
5068
5139
  if (!inst.installed) {
5069
5140
  return {
5070
5141
  status: "not installed",
5071
5142
  lifecycle: null,
5072
- session: null
5143
+ session: null,
5144
+ warnings: []
5073
5145
  };
5074
5146
  }
5075
5147
  switch (inst.bridgeMode) {
@@ -5078,9 +5150,11 @@ function resolveStatus(inst, stateDir) {
5078
5150
  return {
5079
5151
  status: inst.lastVerifiedAt ? "active" : "configured",
5080
5152
  lifecycle: null,
5081
- session: null
5153
+ session: null,
5154
+ warnings: []
5082
5155
  };
5083
5156
  case "app-server": {
5157
+ let staleLifecycle = null;
5084
5158
  if (inst.bridge) {
5085
5159
  const lifecycle = resolveBridgeLifecycleSnapshot(
5086
5160
  stateDir,
@@ -5088,21 +5162,40 @@ function resolveStatus(inst, stateDir) {
5088
5162
  inst.bridge
5089
5163
  );
5090
5164
  if (lifecycle.status === "bridge-stale") {
5165
+ staleLifecycle = lifecycle;
5091
5166
  inst.bridge = null;
5167
+ } else {
5168
+ const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(inst.bridge);
5092
5169
  return {
5093
- status: inst.lastVerifiedAt ? "configured" : "installed",
5170
+ status: "active",
5094
5171
  lifecycle,
5095
- session: null
5172
+ session: deriveCodexSessionState({
5173
+ runtimeHeartbeat,
5174
+ runtimeStateDir: inst.bridge.runtimeStateDir ?? null
5175
+ }),
5176
+ warnings: []
5096
5177
  };
5097
5178
  }
5098
- const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(inst.bridge);
5179
+ }
5180
+ const liveDispatch = loadLiveDispatchEvidence(commsDir, inst.instanceId);
5181
+ if (liveDispatch) {
5099
5182
  return {
5100
- status: "active",
5101
- lifecycle,
5102
- session: deriveCodexSessionState({
5103
- runtimeHeartbeat,
5104
- runtimeStateDir: inst.bridge.runtimeStateDir ?? null
5105
- })
5183
+ status: "dispatch-live",
5184
+ lifecycle: deriveBridgeLifecycleState({
5185
+ bridgeStatus: "stopped"
5186
+ }),
5187
+ session: deriveCodexSessionState({ runtimeHeartbeat: null }),
5188
+ warnings: [
5189
+ `fresh bridge-dispatch heartbeat from PID ${liveDispatch.bridgePid} without bridge pid state`
5190
+ ]
5191
+ };
5192
+ }
5193
+ if (staleLifecycle) {
5194
+ return {
5195
+ status: inst.lastVerifiedAt ? "configured" : "installed",
5196
+ lifecycle: staleLifecycle,
5197
+ session: null,
5198
+ warnings: []
5106
5199
  };
5107
5200
  }
5108
5201
  return {
@@ -5110,25 +5203,27 @@ function resolveStatus(inst, stateDir) {
5110
5203
  lifecycle: deriveBridgeLifecycleState({
5111
5204
  bridgeStatus: "stopped"
5112
5205
  }),
5113
- session: deriveCodexSessionState({ runtimeHeartbeat: null })
5206
+ session: deriveCodexSessionState({ runtimeHeartbeat: null }),
5207
+ warnings: []
5114
5208
  };
5115
5209
  }
5116
5210
  default:
5117
5211
  return {
5118
5212
  status: "installed",
5119
5213
  lifecycle: null,
5120
- session: null
5214
+ session: null,
5215
+ warnings: []
5121
5216
  };
5122
5217
  }
5123
5218
  }
5124
- function instanceStatusLine(inst, status, lifecycle, session) {
5219
+ function instanceStatusLine(inst, status, lifecycle, session, warnings) {
5125
5220
  const bridgeInfo = inst.bridge ? ` (pid: ${inst.bridge.pid})` : "";
5126
5221
  const lifecycleStr = lifecycle?.status ?? "-";
5127
5222
  const sessionStr = session?.status ?? "-";
5128
5223
  const mode = inst.bridgeMode;
5129
5224
  const portStr = inst.port ? ` port:${inst.port}` : "";
5130
5225
  const restart = inst.restartRequired ? " [restart required]" : "";
5131
- const warns = inst.warnings.length > 0 ? ` [${inst.warnings.length} warning(s)]` : "";
5226
+ const warns = warnings.length > 0 ? ` [${warnings.length} warning(s)]` : "";
5132
5227
  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
5228
  }
5134
5229
  async function statusCommand(args) {
@@ -5181,10 +5276,15 @@ async function statusCommand(args) {
5181
5276
  for (const id of installed) {
5182
5277
  const inst = state.instances[id];
5183
5278
  if (inst) {
5184
- const { status, lifecycle, session } = resolveStatus(inst, stateDir);
5185
- log(instanceStatusLine(inst, status, lifecycle, session));
5186
- if (inst.warnings.length > 0) {
5187
- for (const w of inst.warnings) {
5279
+ const { status, lifecycle, session, warnings } = resolveStatus(
5280
+ inst,
5281
+ stateDir,
5282
+ state.commsDir
5283
+ );
5284
+ const mergedWarnings = [...inst.warnings, ...warnings];
5285
+ log(instanceStatusLine(inst, status, lifecycle, session, mergedWarnings));
5286
+ if (mergedWarnings.length > 0) {
5287
+ for (const w of mergedWarnings) {
5188
5288
  logWarn(` ${w}`);
5189
5289
  }
5190
5290
  }
@@ -5196,7 +5296,7 @@ async function statusCommand(args) {
5196
5296
  bridgeMode: inst.bridgeMode,
5197
5297
  bridge: inst.bridge,
5198
5298
  port: inst.port,
5199
- warnings: inst.warnings
5299
+ warnings: mergedWarnings
5200
5300
  };
5201
5301
  }
5202
5302
  }
@@ -5227,7 +5327,7 @@ async function statusCommand(args) {
5227
5327
  init_utils();
5228
5328
 
5229
5329
  // src/engine/rollback.ts
5230
- import * as fs27 from "fs";
5330
+ import * as fs28 from "fs";
5231
5331
  async function rollbackRuntime(_instanceId, runtimeState) {
5232
5332
  const errors = [];
5233
5333
  const restoredFiles = [];
@@ -5256,7 +5356,7 @@ async function rollbackRuntime(_instanceId, runtimeState) {
5256
5356
  };
5257
5357
  }
5258
5358
  function rollbackArtifact(artifact) {
5259
- if (!fs27.existsSync(artifact.path)) {
5359
+ if (!fs28.existsSync(artifact.path)) {
5260
5360
  return { restored: false, error: `File not found: ${artifact.path}` };
5261
5361
  }
5262
5362
  switch (artifact.kind) {
@@ -5274,7 +5374,7 @@ function rollbackArtifact(artifact) {
5274
5374
  }
5275
5375
  }
5276
5376
  function rollbackJsonPath(artifact) {
5277
- const raw = fs27.readFileSync(artifact.path, "utf-8");
5377
+ const raw = fs28.readFileSync(artifact.path, "utf-8");
5278
5378
  let config;
5279
5379
  try {
5280
5380
  config = JSON.parse(raw);
@@ -5300,18 +5400,18 @@ function rollbackJsonPath(artifact) {
5300
5400
  cleanEmptyParents(config, artifact.selector);
5301
5401
  }
5302
5402
  const tmp = `${artifact.path}.tmp.${process.pid}`;
5303
- fs27.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", "utf-8");
5304
- fs27.renameSync(tmp, artifact.path);
5403
+ fs28.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", "utf-8");
5404
+ fs28.renameSync(tmp, artifact.path);
5305
5405
  return { restored: true };
5306
5406
  }
5307
5407
  function rollbackTomlTable(artifact) {
5308
- const content = fs27.readFileSync(artifact.path, "utf-8");
5408
+ const content = fs28.readFileSync(artifact.path, "utf-8");
5309
5409
  const backup = artifact.backupPath ? readArtifactBackup(artifact.backupPath) : null;
5310
5410
  if (backup?.kind === "toml-table" && backup.selector === artifact.selector) {
5311
5411
  const nextContent = backup.existed ? replaceTomlTable(content, artifact.selector, backup.content ?? "") : removeTomlTable(content, artifact.selector);
5312
5412
  const tmp2 = `${artifact.path}.tmp.${process.pid}`;
5313
- fs27.writeFileSync(tmp2, nextContent, "utf-8");
5314
- fs27.renameSync(tmp2, artifact.path);
5413
+ fs28.writeFileSync(tmp2, nextContent, "utf-8");
5414
+ fs28.renameSync(tmp2, artifact.path);
5315
5415
  return { restored: true };
5316
5416
  }
5317
5417
  if (!extractTomlTable(content, artifact.selector)) {
@@ -5321,13 +5421,13 @@ function rollbackTomlTable(artifact) {
5321
5421
  };
5322
5422
  }
5323
5423
  const tmp = `${artifact.path}.tmp.${process.pid}`;
5324
- fs27.writeFileSync(tmp, removeTomlTable(content, artifact.selector), "utf-8");
5325
- fs27.renameSync(tmp, artifact.path);
5424
+ fs28.writeFileSync(tmp, removeTomlTable(content, artifact.selector), "utf-8");
5425
+ fs28.renameSync(tmp, artifact.path);
5326
5426
  return { restored: true };
5327
5427
  }
5328
5428
  function rollbackFile(artifact) {
5329
- if (fs27.existsSync(artifact.path)) {
5330
- fs27.unlinkSync(artifact.path);
5429
+ if (fs28.existsSync(artifact.path)) {
5430
+ fs28.unlinkSync(artifact.path);
5331
5431
  return { restored: true };
5332
5432
  }
5333
5433
  return { restored: false, error: `File not found: ${artifact.path}` };
@@ -5502,13 +5602,13 @@ async function removeCommand(args) {
5502
5602
  init_utils();
5503
5603
 
5504
5604
  // src/commands/bridge-start.ts
5505
- import * as path26 from "path";
5605
+ import * as path27 from "path";
5506
5606
  init_instance_config();
5507
5607
  init_config();
5508
5608
  init_utils();
5509
5609
 
5510
5610
  // src/commands/bridge-helpers.ts
5511
- import * as path24 from "path";
5611
+ import * as path25 from "path";
5512
5612
  function formatAge(seconds) {
5513
5613
  if (seconds < 60) return `${seconds}s ago`;
5514
5614
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
@@ -5538,8 +5638,24 @@ function resolveTuiConnectUrl(appServer) {
5538
5638
  function quoteCliArg(value) {
5539
5639
  return `"${value.replace(/"/g, '\\"')}"`;
5540
5640
  }
5541
- function formatCodexTuiAttachCommand(tuiConnectUrl, cwd) {
5542
- return `codex --enable tui_app_server --remote ${quoteCliArg(tuiConnectUrl)} --cd ${quoteCliArg(cwd)}`;
5641
+ function quoteShellEnvValue(value) {
5642
+ if (process.platform === "win32") {
5643
+ return `'${value.replace(/'/g, "''")}'`;
5644
+ }
5645
+ return `'${value.replace(/'/g, `'\\''`)}'`;
5646
+ }
5647
+ function formatCodexTuiAttachCommand(tuiConnectUrl, cwd, env = {}) {
5648
+ const base = `codex --enable tui_app_server --remote ${quoteCliArg(tuiConnectUrl)} --cd ${quoteCliArg(cwd)}`;
5649
+ const entries = Object.entries(env).filter(([, value]) => value.length > 0);
5650
+ if (entries.length === 0) {
5651
+ return base;
5652
+ }
5653
+ if (process.platform === "win32") {
5654
+ const envPrefix2 = entries.map(([key, value]) => `$env:${key} = ${quoteShellEnvValue(value)}`).join("; ");
5655
+ return `${envPrefix2}; ${base}`;
5656
+ }
5657
+ const envPrefix = entries.map(([key, value]) => `${key}=${quoteShellEnvValue(value)}`).join(" ");
5658
+ return `${envPrefix} ${base}`;
5543
5659
  }
5544
5660
  function resolveTuiAttachCwd(repoRoot, stateRepoRoot, runtimeThreadCwd, savedThreadCwd) {
5545
5661
  return runtimeThreadCwd ?? savedThreadCwd ?? stateRepoRoot ?? repoRoot;
@@ -5554,7 +5670,7 @@ function formatThreadSummary(threadId, cwd) {
5554
5670
  return cwd ? `${threadId} (${cwd})` : threadId;
5555
5671
  }
5556
5672
  function normalizeComparablePath(value) {
5557
- return path24.resolve(value).replace(/\\/g, "/").toLowerCase();
5673
+ return path25.resolve(value).replace(/\\/g, "/").toLowerCase();
5558
5674
  }
5559
5675
  function sameOptionalPath(left, right) {
5560
5676
  if (!left || !right) {
@@ -5626,22 +5742,22 @@ function transferManagedAppServerOwnership(state, stateDir, recipientId, appServ
5626
5742
  }
5627
5743
 
5628
5744
  // src/commands/bridge-heartbeat.ts
5629
- import { existsSync as existsSync25, readFileSync as readFileSync21, renameSync as renameSync13, writeFileSync as writeFileSync14 } from "fs";
5630
- import * as path25 from "path";
5745
+ import { existsSync as existsSync26, readFileSync as readFileSync22, renameSync as renameSync13, writeFileSync as writeFileSync14 } from "fs";
5746
+ import * as path26 from "path";
5631
5747
  var BRIDGE_UP_ACTIVE_HEARTBEAT_WINDOW_MS = 10 * 60 * 1e3;
5632
5748
  var BRIDGE_UP_ORPHAN_HEARTBEAT_WINDOW_MS = 24 * 60 * 60 * 1e3;
5633
5749
  var BRIDGE_UP_SIGNING_OFF_HEARTBEAT_WINDOW_MS = 5 * 60 * 1e3;
5634
5750
  function loadBridgeHeartbeatStore(commsDir) {
5635
- const heartbeatsPath = path25.join(commsDir, "heartbeats.json");
5636
- if (!existsSync25(heartbeatsPath)) return {};
5751
+ const heartbeatsPath = path26.join(commsDir, "heartbeats.json");
5752
+ if (!existsSync26(heartbeatsPath)) return {};
5637
5753
  try {
5638
- return JSON.parse(readFileSync21(heartbeatsPath, "utf-8"));
5754
+ return JSON.parse(readFileSync22(heartbeatsPath, "utf-8"));
5639
5755
  } catch {
5640
5756
  return null;
5641
5757
  }
5642
5758
  }
5643
5759
  function saveBridgeHeartbeatStore(commsDir, store) {
5644
- const heartbeatsPath = path25.join(commsDir, "heartbeats.json");
5760
+ const heartbeatsPath = path26.join(commsDir, "heartbeats.json");
5645
5761
  const tmp = `${heartbeatsPath}.tmp.${process.pid}`;
5646
5762
  writeFileSync14(tmp, JSON.stringify(store, null, 2), "utf-8");
5647
5763
  renameSync13(tmp, heartbeatsPath);
@@ -5948,7 +6064,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
5948
6064
  }
5949
6065
  }
5950
6066
  logSuccess(`Bridge started (PID: ${bridge.pid})`);
5951
- log(`Log: ${path26.join(ctx.stateDir, "logs", `bridge-${instanceId}.log`)}`);
6067
+ log(`Log: ${path27.join(ctx.stateDir, "logs", `bridge-${instanceId}.log`)}`);
5952
6068
  if (bridge.appServer) {
5953
6069
  log(`App server: ${formatAppServerState(bridge.appServer)}`);
5954
6070
  if (bridge.appServer.logPath) {
@@ -6073,7 +6189,7 @@ async function bridgeStartAll(flags = {}) {
6073
6189
  warnings.push(msg);
6074
6190
  continue;
6075
6191
  }
6076
- const stateDir = path26.join(repoRoot, ".tap-comms");
6192
+ const stateDir = path27.join(repoRoot, ".tap-comms");
6077
6193
  const currentBridgeState = loadBridgeState(stateDir, instanceId);
6078
6194
  const { manageAppServer, noAuth } = inferRestartMode(
6079
6195
  currentBridgeState,
@@ -6492,7 +6608,7 @@ async function bridgeWatch(_intervalSeconds, stuckThresholdSeconds) {
6492
6608
  }
6493
6609
 
6494
6610
  // src/commands/bridge-status.ts
6495
- import * as path27 from "path";
6611
+ import * as path28 from "path";
6496
6612
  init_config();
6497
6613
  init_utils();
6498
6614
  function bridgeStatusAll() {
@@ -6543,23 +6659,26 @@ function bridgeStatusAll() {
6543
6659
  };
6544
6660
  continue;
6545
6661
  }
6546
- const status = getBridgeStatus(stateDir, instanceId);
6662
+ const rawStatus = getBridgeStatus(stateDir, instanceId);
6547
6663
  const bridgeState = loadBridgeState(stateDir, instanceId) ?? inst.bridge;
6548
- const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(bridgeState);
6549
- const savedThread = loadRuntimeBridgeThreadState(bridgeState);
6550
- const lifecycle = deriveBridgeLifecycleState({
6551
- bridgeStatus: status,
6664
+ const liveDispatch = rawStatus === "running" ? null : loadLiveDispatchEvidence(state.commsDir, instanceId);
6665
+ const surfaceBridgeState = liveDispatch ? null : bridgeState;
6666
+ const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(surfaceBridgeState);
6667
+ const savedThread = loadRuntimeBridgeThreadState(surfaceBridgeState);
6668
+ const status = liveDispatch ? "dispatch-live" : rawStatus;
6669
+ const lifecycle = liveDispatch ? deriveBridgeLifecycleState({ bridgeStatus: "stopped" }) : deriveBridgeLifecycleState({
6670
+ bridgeStatus: rawStatus,
6552
6671
  bridgeState,
6553
6672
  runtimeHeartbeat,
6554
6673
  savedThread,
6555
6674
  persistedLifecycle: inst.bridgeLifecycle ?? bridgeState?.lifecycle ?? null
6556
6675
  });
6557
- const session = status === "running" ? deriveCodexSessionState({
6676
+ const session = rawStatus === "running" || liveDispatch ? deriveCodexSessionState({
6558
6677
  runtimeHeartbeat,
6559
- runtimeStateDir: bridgeState?.runtimeStateDir ?? null
6678
+ runtimeStateDir: surfaceBridgeState?.runtimeStateDir ?? null
6560
6679
  }) : null;
6561
- const age = getHeartbeatAge(stateDir, instanceId);
6562
- if (lifecycle.status === "bridge-stale" && inst.bridge) {
6680
+ const age = liveDispatch ? null : getHeartbeatAge(stateDir, instanceId);
6681
+ if (rawStatus === "stale" && inst.bridge) {
6563
6682
  state.instances[instanceId] = {
6564
6683
  ...inst,
6565
6684
  bridge: null,
@@ -6571,22 +6690,22 @@ function bridgeStatusAll() {
6571
6690
  };
6572
6691
  stateChanged = true;
6573
6692
  }
6574
- const pid = bridgeState?.pid ?? null;
6575
- const heartbeat = getBridgeHeartbeatTimestamp(stateDir, instanceId);
6693
+ const pid = surfaceBridgeState?.pid ?? null;
6694
+ const heartbeat = liveDispatch ? null : getBridgeHeartbeatTimestamp(stateDir, instanceId);
6576
6695
  const pidStr = pid ? String(pid) : "-";
6577
6696
  const portStr = inst.port ? String(inst.port) : "-";
6578
6697
  const ageStr = age !== null ? formatAge(age) : "-";
6579
6698
  log(
6580
6699
  `${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
6700
  );
6582
- if (bridgeState?.appServer) {
6583
- log(` App server: ${formatAppServerState(bridgeState.appServer)}`);
6584
- if (bridgeState.appServer.logPath) {
6585
- log(` Server log: ${bridgeState.appServer.logPath}`);
6701
+ if (surfaceBridgeState?.appServer) {
6702
+ log(` App server: ${formatAppServerState(surfaceBridgeState.appServer)}`);
6703
+ if (surfaceBridgeState.appServer.logPath) {
6704
+ log(` Server log: ${surfaceBridgeState.appServer.logPath}`);
6586
6705
  }
6587
- if (bridgeState.appServer.auth) {
6706
+ if (surfaceBridgeState.appServer.auth) {
6588
6707
  log(
6589
- ` Protected: ${redactProtectedUrl(bridgeState.appServer.auth.protectedUrl)}`
6708
+ ` Protected: ${redactProtectedUrl(surfaceBridgeState.appServer.auth.protectedUrl)}`
6590
6709
  );
6591
6710
  }
6592
6711
  }
@@ -6604,6 +6723,11 @@ function bridgeStatusAll() {
6604
6723
  if (transition) {
6605
6724
  log(` Transition: ${transition}`);
6606
6725
  }
6726
+ if (liveDispatch) {
6727
+ log(
6728
+ ` Drift: fresh bridge-dispatch heartbeat from PID ${liveDispatch.bridgePid} without bridge pid state`
6729
+ );
6730
+ }
6607
6731
  const turnInfo = getTurnInfo(stateDir, instanceId);
6608
6732
  if (turnInfo?.activeTurnId) {
6609
6733
  const ageStr2 = turnInfo.ageSeconds != null ? formatAge(turnInfo.ageSeconds) : "?";
@@ -6629,7 +6753,7 @@ function bridgeStatusAll() {
6629
6753
  threadCwd: runtimeHeartbeat?.threadCwd ?? null,
6630
6754
  savedThreadId: savedThread?.threadId ?? null,
6631
6755
  savedThreadCwd: savedThread?.cwd ?? null,
6632
- appServer: bridgeState?.appServer ?? null
6756
+ appServer: surfaceBridgeState?.appServer ?? null
6633
6757
  };
6634
6758
  }
6635
6759
  if (instanceIds.length === 0) {
@@ -6726,14 +6850,17 @@ function bridgeStatusOne(identifier) {
6726
6850
  }
6727
6851
  const { config: resolvedCfg2 } = resolveConfig({}, repoRoot);
6728
6852
  const stateDir = resolvedCfg2.stateDir;
6729
- const status = getBridgeStatus(stateDir, instanceId);
6853
+ const rawStatus = getBridgeStatus(stateDir, instanceId);
6730
6854
  const bridgeState = loadBridgeState(stateDir, instanceId) ?? inst.bridge;
6731
- const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(bridgeState);
6732
- const savedThread = loadRuntimeBridgeThreadState(bridgeState);
6733
- const age = getHeartbeatAge(stateDir, instanceId);
6734
- const heartbeat = getBridgeHeartbeatTimestamp(stateDir, instanceId);
6735
- const lifecycle = deriveBridgeLifecycleState({
6736
- bridgeStatus: status,
6855
+ const liveDispatch = rawStatus === "running" ? null : loadLiveDispatchEvidence(state.commsDir, instanceId);
6856
+ const surfaceBridgeState = liveDispatch ? null : bridgeState;
6857
+ const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(surfaceBridgeState);
6858
+ const savedThread = loadRuntimeBridgeThreadState(surfaceBridgeState);
6859
+ const age = liveDispatch ? null : getHeartbeatAge(stateDir, instanceId);
6860
+ const heartbeat = liveDispatch ? null : getBridgeHeartbeatTimestamp(stateDir, instanceId);
6861
+ const status = liveDispatch ? "dispatch-live" : rawStatus;
6862
+ const lifecycle = liveDispatch ? deriveBridgeLifecycleState({ bridgeStatus: "stopped" }) : deriveBridgeLifecycleState({
6863
+ bridgeStatus: rawStatus,
6737
6864
  bridgeState,
6738
6865
  runtimeHeartbeat,
6739
6866
  savedThread,
@@ -6741,13 +6868,25 @@ function bridgeStatusOne(identifier) {
6741
6868
  });
6742
6869
  const session = deriveCodexSessionState({
6743
6870
  runtimeHeartbeat,
6744
- runtimeStateDir: bridgeState?.runtimeStateDir ?? null
6871
+ runtimeStateDir: surfaceBridgeState?.runtimeStateDir ?? null
6745
6872
  });
6746
6873
  log(`Status: ${status}`);
6747
6874
  log(`Lifecycle: ${lifecycle.summary}`);
6748
6875
  log(`Session: ${session.summary}`);
6749
- if (bridgeState) {
6750
- log(`PID: ${bridgeState.pid}`);
6876
+ if (rawStatus === "stale" && inst.bridge) {
6877
+ state.instances[instanceId] = {
6878
+ ...inst,
6879
+ bridge: null,
6880
+ bridgeLifecycle: transitionBridgeLifecycle(
6881
+ inst.bridgeLifecycle ?? inst.bridge?.lifecycle ?? null,
6882
+ "crashed",
6883
+ "bridge pid not alive"
6884
+ )
6885
+ };
6886
+ saveState(repoRoot, state);
6887
+ }
6888
+ if (surfaceBridgeState) {
6889
+ log(`PID: ${surfaceBridgeState.pid}`);
6751
6890
  log(
6752
6891
  `Heartbeat: ${heartbeat ?? "-"}${age !== null ? ` (${formatAge(age)})` : ""}`
6753
6892
  );
@@ -6762,35 +6901,35 @@ function bridgeStatusOne(identifier) {
6762
6901
  );
6763
6902
  }
6764
6903
  log(
6765
- `Log: ${path27.join(stateDir, "logs", `bridge-${instanceId}.log`)}`
6904
+ `Log: ${path28.join(stateDir, "logs", `bridge-${instanceId}.log`)}`
6766
6905
  );
6767
- if (bridgeState.appServer) {
6768
- log(`App server: ${bridgeState.appServer.url}`);
6769
- log(`Server PID: ${bridgeState.appServer.pid ?? "-"}`);
6906
+ if (surfaceBridgeState.appServer) {
6907
+ log(`App server: ${surfaceBridgeState.appServer.url}`);
6908
+ log(`Server PID: ${surfaceBridgeState.appServer.pid ?? "-"}`);
6770
6909
  log(
6771
- `Server mode: ${bridgeState.appServer.managed ? "managed" : "external"}`
6910
+ `Server mode: ${surfaceBridgeState.appServer.managed ? "managed" : "external"}`
6772
6911
  );
6773
6912
  log(
6774
- `Health: ${bridgeState.appServer.healthy ? "healthy" : "unhealthy"}`
6913
+ `Health: ${surfaceBridgeState.appServer.healthy ? "healthy" : "unhealthy"}`
6775
6914
  );
6776
- log(`Checked: ${bridgeState.appServer.lastCheckedAt}`);
6777
- if (bridgeState.appServer.logPath) {
6778
- log(`Server log: ${bridgeState.appServer.logPath}`);
6915
+ log(`Checked: ${surfaceBridgeState.appServer.lastCheckedAt}`);
6916
+ if (surfaceBridgeState.appServer.logPath) {
6917
+ log(`Server log: ${surfaceBridgeState.appServer.logPath}`);
6779
6918
  }
6780
- if (bridgeState.appServer.auth) {
6781
- log(`Auth: ${bridgeState.appServer.auth.mode}`);
6919
+ if (surfaceBridgeState.appServer.auth) {
6920
+ log(`Auth: ${surfaceBridgeState.appServer.auth.mode}`);
6782
6921
  log(
6783
- `Protected: ${redactProtectedUrl(bridgeState.appServer.auth.protectedUrl)}`
6922
+ `Protected: ${redactProtectedUrl(surfaceBridgeState.appServer.auth.protectedUrl)}`
6784
6923
  );
6785
- log(`Upstream: ${bridgeState.appServer.auth.upstreamUrl}`);
6786
- log(`TUI connect: ${bridgeState.appServer.auth.upstreamUrl}`);
6787
- log(`Gateway PID: ${bridgeState.appServer.auth.gatewayPid ?? "-"}`);
6788
- if (bridgeState.appServer.auth.gatewayLogPath) {
6789
- log(`Gateway log: ${bridgeState.appServer.auth.gatewayLogPath}`);
6924
+ log(`Upstream: ${surfaceBridgeState.appServer.auth.upstreamUrl}`);
6925
+ log(`TUI connect: ${surfaceBridgeState.appServer.auth.upstreamUrl}`);
6926
+ log(`Gateway PID: ${surfaceBridgeState.appServer.auth.gatewayPid ?? "-"}`);
6927
+ if (surfaceBridgeState.appServer.auth.gatewayLogPath) {
6928
+ log(`Gateway log: ${surfaceBridgeState.appServer.auth.gatewayLogPath}`);
6790
6929
  }
6791
- } else if (bridgeState.appServer.managed) {
6930
+ } else if (surfaceBridgeState.appServer.managed) {
6792
6931
  log(`Auth: none (--no-auth)`);
6793
- log(`TUI connect: ${bridgeState.appServer.url}`);
6932
+ log(`TUI connect: ${surfaceBridgeState.appServer.url}`);
6794
6933
  }
6795
6934
  }
6796
6935
  }
@@ -6798,6 +6937,11 @@ function bridgeStatusOne(identifier) {
6798
6937
  if (transition) {
6799
6938
  log(`Transition: ${transition}`);
6800
6939
  }
6940
+ if (liveDispatch) {
6941
+ log(
6942
+ `Drift: fresh bridge-dispatch heartbeat from PID ${liveDispatch.bridgePid} without bridge pid state`
6943
+ );
6944
+ }
6801
6945
  log("");
6802
6946
  return {
6803
6947
  ok: true,
@@ -6827,14 +6971,14 @@ function bridgeStatusOne(identifier) {
6827
6971
  lastDispatchAt: session.lastDispatchAt
6828
6972
  },
6829
6973
  bridgeMode: inst.bridgeMode,
6830
- pid: bridgeState?.pid ?? null,
6974
+ pid: surfaceBridgeState?.pid ?? null,
6831
6975
  port: inst.port,
6832
6976
  lastHeartbeat: heartbeat,
6833
6977
  threadId: runtimeHeartbeat?.threadId ?? null,
6834
6978
  threadCwd: runtimeHeartbeat?.threadCwd ?? null,
6835
6979
  savedThreadId: savedThread?.threadId ?? null,
6836
6980
  savedThreadCwd: savedThread?.cwd ?? null,
6837
- appServer: bridgeState?.appServer ?? null
6981
+ appServer: surfaceBridgeState?.appServer ?? null
6838
6982
  }
6839
6983
  };
6840
6984
  }
@@ -6929,7 +7073,23 @@ function bridgeTuiOne(identifier) {
6929
7073
  runtimeHeartbeat?.threadCwd,
6930
7074
  savedThread?.cwd
6931
7075
  );
6932
- const attachCommand = formatCodexTuiAttachCommand(tuiConnectUrl, attachCwd);
7076
+ const attachEnv = {
7077
+ TAP_BRIDGE_INSTANCE_ID: instanceId,
7078
+ TAP_AGENT_ID: instanceId,
7079
+ TAP_COMMS_DIR: resolvedConfig.commsDir,
7080
+ TAP_STATE_DIR: stateDir,
7081
+ TAP_RUNTIME_STATE_DIR: bridgeState?.runtimeStateDir ?? getBridgeRuntimeStateDir(repoRoot, instanceId),
7082
+ TAP_REPO_ROOT: repoRoot
7083
+ };
7084
+ if (typeof inst.agentName === "string" && inst.agentName.trim()) {
7085
+ attachEnv.TAP_AGENT_NAME = inst.agentName;
7086
+ attachEnv.CODEX_TAP_AGENT_NAME = inst.agentName;
7087
+ }
7088
+ const attachCommand = formatCodexTuiAttachCommand(
7089
+ tuiConnectUrl,
7090
+ attachCwd,
7091
+ attachEnv
7092
+ );
6933
7093
  const warnings = appServer.auth != null ? [
6934
7094
  "Use the upstream TUI URL, not the protected gateway URL. The protected URL is bridge-only."
6935
7095
  ] : [];
@@ -6954,6 +7114,7 @@ function bridgeTuiOne(identifier) {
6954
7114
  tuiConnectUrl,
6955
7115
  attachCwd,
6956
7116
  attachCommand,
7117
+ attachEnv,
6957
7118
  appServer
6958
7119
  }
6959
7120
  };
@@ -7266,8 +7427,8 @@ async function bridgeCommand(args) {
7266
7427
 
7267
7428
  // src/engine/dashboard.ts
7268
7429
  init_config();
7269
- import * as fs28 from "fs";
7270
- import * as path28 from "path";
7430
+ import * as fs29 from "fs";
7431
+ import * as path29 from "path";
7271
7432
  import { execSync as execSync4 } from "child_process";
7272
7433
  function formatAgentLabel(agentIdOrName, displayName) {
7273
7434
  const normalizedId = agentIdOrName.trim();
@@ -7300,10 +7461,10 @@ function resolveHeartbeatInstanceId(heartbeatId, displayName, state) {
7300
7461
  return matches.length === 1 ? matches[0].instanceId : null;
7301
7462
  }
7302
7463
  function collectAgents(commsDir, state, bridges) {
7303
- const heartbeatsPath = path28.join(commsDir, "heartbeats.json");
7304
- if (!fs28.existsSync(heartbeatsPath)) return [];
7464
+ const heartbeatsPath = path29.join(commsDir, "heartbeats.json");
7465
+ if (!fs29.existsSync(heartbeatsPath)) return [];
7305
7466
  try {
7306
- const raw = fs28.readFileSync(heartbeatsPath, "utf-8");
7467
+ const raw = fs29.readFileSync(heartbeatsPath, "utf-8");
7307
7468
  const data = JSON.parse(raw);
7308
7469
  return Object.entries(data).map(([agentId, info]) => {
7309
7470
  const instanceId = resolveHeartbeatInstanceId(
@@ -7363,22 +7524,22 @@ function collectBridges(repoRoot) {
7363
7524
  });
7364
7525
  }
7365
7526
  }
7366
- const tmpDir = path28.join(repoRoot, ".tmp");
7367
- if (fs28.existsSync(tmpDir)) {
7527
+ const tmpDir = path29.join(repoRoot, ".tmp");
7528
+ if (fs29.existsSync(tmpDir)) {
7368
7529
  try {
7369
- const dirs = fs28.readdirSync(tmpDir).filter((d) => d.startsWith("codex-app-server-bridge"));
7530
+ const dirs = fs29.readdirSync(tmpDir).filter((d) => d.startsWith("codex-app-server-bridge"));
7370
7531
  for (const dir of dirs) {
7371
- const daemonPath = path28.join(tmpDir, dir, "bridge-daemon.json");
7372
- if (!fs28.existsSync(daemonPath)) continue;
7532
+ const daemonPath = path29.join(tmpDir, dir, "bridge-daemon.json");
7533
+ if (!fs29.existsSync(daemonPath)) continue;
7373
7534
  try {
7374
- const raw = fs28.readFileSync(daemonPath, "utf-8");
7535
+ const raw = fs29.readFileSync(daemonPath, "utf-8");
7375
7536
  const daemon = JSON.parse(raw);
7376
7537
  const alreadyCovered = bridges.some(
7377
7538
  (b) => b.pid === daemon.pid && b.pid !== null
7378
7539
  );
7379
7540
  if (alreadyCovered) continue;
7380
- const agentFile = path28.join(tmpDir, dir, "agent-name.txt");
7381
- const agentName = fs28.existsSync(agentFile) ? fs28.readFileSync(agentFile, "utf-8").trim() : dir;
7541
+ const agentFile = path29.join(tmpDir, dir, "agent-name.txt");
7542
+ const agentName = fs29.existsSync(agentFile) ? fs29.readFileSync(agentFile, "utf-8").trim() : dir;
7382
7543
  const running = daemon.pid ? isProcessAlive(daemon.pid) : false;
7383
7544
  const portMatch = daemon.appServerUrl?.match(/:(\d+)/);
7384
7545
  const port = portMatch ? parseInt(portMatch[1], 10) : null;
@@ -7613,7 +7774,7 @@ async function downCommand(args) {
7613
7774
  }
7614
7775
 
7615
7776
  // src/commands/serve.ts
7616
- import * as path29 from "path";
7777
+ import * as path30 from "path";
7617
7778
  import { spawn as spawn2 } from "child_process";
7618
7779
  init_utils();
7619
7780
  init_config();
@@ -7649,10 +7810,10 @@ async function serveCommand(args) {
7649
7810
  let commsDir;
7650
7811
  const commsDirIdx = args.indexOf("--comms-dir");
7651
7812
  if (commsDirIdx !== -1 && args[commsDirIdx + 1]) {
7652
- commsDir = path29.resolve(normalizeTapPath(args[commsDirIdx + 1]));
7813
+ commsDir = path30.resolve(normalizeTapPath(args[commsDirIdx + 1]));
7653
7814
  }
7654
7815
  if (!commsDir && process.env.TAP_COMMS_DIR) {
7655
- commsDir = path29.resolve(normalizeTapPath(process.env.TAP_COMMS_DIR));
7816
+ commsDir = path30.resolve(normalizeTapPath(process.env.TAP_COMMS_DIR));
7656
7817
  }
7657
7818
  if (!commsDir) {
7658
7819
  const state = loadState(repoRoot);
@@ -7719,8 +7880,8 @@ async function serveCommand(args) {
7719
7880
  // src/commands/init-worktree.ts
7720
7881
  init_config();
7721
7882
  init_utils();
7722
- import * as fs29 from "fs";
7723
- import * as path30 from "path";
7883
+ import * as fs30 from "fs";
7884
+ import * as path31 from "path";
7724
7885
  import { execSync as execSync5 } from "child_process";
7725
7886
  var INIT_WORKTREE_HELP = `
7726
7887
  Usage:
@@ -7757,7 +7918,7 @@ function run(cmd, opts) {
7757
7918
  }
7758
7919
  }
7759
7920
  function toAbsolute(p) {
7760
- const resolved = path30.resolve(p);
7921
+ const resolved = path31.resolve(p);
7761
7922
  return resolved.replace(/\\/g, "/");
7762
7923
  }
7763
7924
  function probeBun(candidate) {
@@ -7788,18 +7949,18 @@ function findBun() {
7788
7949
  }
7789
7950
  }
7790
7951
  const home = process.env.HOME || process.env.USERPROFILE || "";
7791
- const bunHome = path30.join(
7952
+ const bunHome = path31.join(
7792
7953
  home,
7793
7954
  ".bun",
7794
7955
  "bin",
7795
7956
  process.platform === "win32" ? "bun.exe" : "bun"
7796
7957
  );
7797
- if (fs29.existsSync(bunHome) && probeBun(bunHome)) return bunHome;
7958
+ if (fs30.existsSync(bunHome) && probeBun(bunHome)) return bunHome;
7798
7959
  return null;
7799
7960
  }
7800
7961
  function step1CreateWorktree(opts) {
7801
7962
  log("Step 1/9: Creating worktree...");
7802
- if (fs29.existsSync(opts.worktreePath)) {
7963
+ if (fs30.existsSync(opts.worktreePath)) {
7803
7964
  logWarn(`Directory already exists: ${opts.worktreePath}`);
7804
7965
  try {
7805
7966
  run("git rev-parse --git-dir", { cwd: opts.worktreePath });
@@ -7861,22 +8022,22 @@ function step2MergeMain(opts, warnings) {
7861
8022
  }
7862
8023
  function step3CopyPermissions(opts, warnings) {
7863
8024
  log("Step 3/9: Copying permissions...");
7864
- const srcSettings = path30.join(
8025
+ const srcSettings = path31.join(
7865
8026
  opts.repoRoot,
7866
8027
  ".claude",
7867
8028
  "settings.local.json"
7868
8029
  );
7869
- const destDir = path30.join(opts.worktreePath, ".claude");
7870
- const destSettings = path30.join(destDir, "settings.local.json");
7871
- if (!fs29.existsSync(srcSettings)) {
8030
+ const destDir = path31.join(opts.worktreePath, ".claude");
8031
+ const destSettings = path31.join(destDir, "settings.local.json");
8032
+ if (!fs30.existsSync(srcSettings)) {
7872
8033
  warn(
7873
8034
  warnings,
7874
8035
  "No .claude/settings.local.json found in main repo. Skipping."
7875
8036
  );
7876
8037
  return;
7877
8038
  }
7878
- fs29.mkdirSync(destDir, { recursive: true });
7879
- fs29.copyFileSync(srcSettings, destSettings);
8039
+ fs30.mkdirSync(destDir, { recursive: true });
8040
+ fs30.copyFileSync(srcSettings, destSettings);
7880
8041
  logSuccess("Copied settings.local.json");
7881
8042
  try {
7882
8043
  run("git update-index --skip-worktree .claude/settings.local.json", {
@@ -7901,7 +8062,7 @@ function step4GenerateMcpJson(opts, warnings) {
7901
8062
  const wtAbs = toAbsolute(opts.worktreePath);
7902
8063
  const bunAbs = toAbsolute(bunPath);
7903
8064
  const commsAbs = toAbsolute(opts.commsDir);
7904
- const channelEntry = path30.join(
8065
+ const channelEntry = path31.join(
7905
8066
  wtAbs,
7906
8067
  "packages/tap-plugin/channels/tap-comms.ts"
7907
8068
  );
@@ -7918,8 +8079,8 @@ function step4GenerateMcpJson(opts, warnings) {
7918
8079
  }
7919
8080
  }
7920
8081
  };
7921
- const mcpPath = path30.join(opts.worktreePath, ".mcp.json");
7922
- fs29.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
8082
+ const mcpPath = path31.join(opts.worktreePath, ".mcp.json");
8083
+ fs30.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
7923
8084
  logSuccess(`.mcp.json generated (absolute paths + cwd)`);
7924
8085
  log(` bun: ${bunAbs}`);
7925
8086
  log(` comms: ${commsAbs}`);
@@ -7957,16 +8118,16 @@ function step6BuildEslintPlugin(opts, warnings) {
7957
8118
  }
7958
8119
  function step7VerifyComms(opts, warnings) {
7959
8120
  log("Step 7/9: Verifying comms directory...");
7960
- if (!fs29.existsSync(opts.commsDir)) {
8121
+ if (!fs30.existsSync(opts.commsDir)) {
7961
8122
  warn(warnings, `Comms directory not found: ${opts.commsDir}`);
7962
8123
  warn(warnings, "Create it or run: npx @hua-labs/tap init");
7963
8124
  return;
7964
8125
  }
7965
8126
  const requiredDirs = ["inbox", "findings", "reviews", "letters"];
7966
8127
  for (const dir of requiredDirs) {
7967
- const dirPath = path30.join(opts.commsDir, dir);
7968
- if (!fs29.existsSync(dirPath)) {
7969
- fs29.mkdirSync(dirPath, { recursive: true });
8128
+ const dirPath = path31.join(opts.commsDir, dir);
8129
+ if (!fs30.existsSync(dirPath)) {
8130
+ fs30.mkdirSync(dirPath, { recursive: true });
7970
8131
  logSuccess(`Created ${dir}/`);
7971
8132
  }
7972
8133
  }
@@ -8025,17 +8186,17 @@ async function initWorktreeCommand(args) {
8025
8186
  }
8026
8187
  const repoRoot = findRepoRoot();
8027
8188
  const { config } = resolveConfig({}, repoRoot);
8028
- const branch = typeof flags["branch"] === "string" ? flags["branch"] : path30.basename(path30.resolve(worktreePath));
8189
+ const branch = typeof flags["branch"] === "string" ? flags["branch"] : path31.basename(path31.resolve(worktreePath));
8029
8190
  const base = typeof flags["base"] === "string" ? flags["base"] : "origin/main";
8030
8191
  const mission = typeof flags["mission"] === "string" ? flags["mission"] : void 0;
8031
8192
  const commsDir = typeof flags["comms-dir"] === "string" ? flags["comms-dir"] : config.commsDir;
8032
8193
  const skipInstall = flags["skip-install"] === true;
8033
8194
  const opts = {
8034
- worktreePath: path30.resolve(worktreePath),
8195
+ worktreePath: path31.resolve(worktreePath),
8035
8196
  branch,
8036
8197
  base,
8037
8198
  mission,
8038
- commsDir: path30.resolve(commsDir),
8199
+ commsDir: path31.resolve(commsDir),
8039
8200
  skipInstall,
8040
8201
  repoRoot
8041
8202
  };
@@ -8260,10 +8421,10 @@ async function dashboardCommand(args) {
8260
8421
 
8261
8422
  // src/commands/doctor.ts
8262
8423
  import {
8263
- existsSync as existsSync28,
8424
+ existsSync as existsSync29,
8264
8425
  mkdirSync as mkdirSync14,
8265
8426
  readdirSync as readdirSync8,
8266
- readFileSync as readFileSync23,
8427
+ readFileSync as readFileSync24,
8267
8428
  renameSync as renameSync14,
8268
8429
  statSync as statSync3,
8269
8430
  unlinkSync as unlinkSync8,
@@ -8271,7 +8432,7 @@ import {
8271
8432
  } from "fs";
8272
8433
  import { homedir as homedir3 } from "os";
8273
8434
  import { spawnSync as spawnSync6 } from "child_process";
8274
- import { dirname as dirname15, join as join27, resolve as resolve14 } from "path";
8435
+ import { dirname as dirname15, join as join28, resolve as resolve14 } from "path";
8275
8436
  init_config();
8276
8437
  init_drift_detector();
8277
8438
  init_utils();
@@ -8306,7 +8467,7 @@ function appendWarningMessage(message, extra) {
8306
8467
  return message.includes(extra) ? message : `${message}; ${extra}`;
8307
8468
  }
8308
8469
  function findCodexConfigPath3() {
8309
- return join27(homedir3(), ".codex", "config.toml");
8470
+ return join28(homedir3(), ".codex", "config.toml");
8310
8471
  }
8311
8472
  function canonicalizeTrustPath3(targetPath) {
8312
8473
  let resolved = resolve14(targetPath).replace(/\//g, "\\");
@@ -8376,7 +8537,7 @@ function repairCodexConfig(repoRoot, commsDir) {
8376
8537
  spec.managed.issues[0] ?? "Unable to resolve the managed tap MCP server for Codex."
8377
8538
  );
8378
8539
  }
8379
- const existingContent = existsSync28(spec.configPath) ? readFileSync23(spec.configPath, "utf-8") : "";
8540
+ const existingContent = existsSync29(spec.configPath) ? readFileSync24(spec.configPath, "utf-8") : "";
8380
8541
  const existingTapEnvTable = extractTomlTable(
8381
8542
  existingContent,
8382
8543
  "mcp_servers.tap.env"
@@ -8443,7 +8604,7 @@ function repairCodexConfig(repoRoot, commsDir) {
8443
8604
  return `Repaired Codex config at ${spec.configPath}. Restart Codex to reload MCP settings.`;
8444
8605
  }
8445
8606
  function countFiles(dir, ext = ".md") {
8446
- if (!existsSync28(dir)) return 0;
8607
+ if (!existsSync29(dir)) return 0;
8447
8608
  try {
8448
8609
  return readdirSync8(dir).filter((f) => f.endsWith(ext)).length;
8449
8610
  } catch {
@@ -8451,14 +8612,14 @@ function countFiles(dir, ext = ".md") {
8451
8612
  }
8452
8613
  }
8453
8614
  function recentFileCount(dir, withinMs) {
8454
- if (!existsSync28(dir)) return 0;
8615
+ if (!existsSync29(dir)) return 0;
8455
8616
  const cutoff = Date.now() - withinMs;
8456
8617
  let count = 0;
8457
8618
  try {
8458
8619
  for (const f of readdirSync8(dir)) {
8459
8620
  if (!f.endsWith(".md")) continue;
8460
8621
  try {
8461
- if (statSync3(join27(dir, f)).mtimeMs > cutoff) count++;
8622
+ if (statSync3(join28(dir, f)).mtimeMs > cutoff) count++;
8462
8623
  } catch {
8463
8624
  }
8464
8625
  }
@@ -8467,16 +8628,16 @@ function recentFileCount(dir, withinMs) {
8467
8628
  return count;
8468
8629
  }
8469
8630
  function loadDoctorHeartbeatStore(commsDir) {
8470
- const heartbeatsPath = join27(commsDir, "heartbeats.json");
8471
- if (!existsSync28(heartbeatsPath)) return null;
8631
+ const heartbeatsPath = join28(commsDir, "heartbeats.json");
8632
+ if (!existsSync29(heartbeatsPath)) return null;
8472
8633
  try {
8473
- return JSON.parse(readFileSync23(heartbeatsPath, "utf-8"));
8634
+ return JSON.parse(readFileSync24(heartbeatsPath, "utf-8"));
8474
8635
  } catch {
8475
8636
  return null;
8476
8637
  }
8477
8638
  }
8478
8639
  function saveDoctorHeartbeatStore(commsDir, store) {
8479
- const heartbeatsPath = join27(commsDir, "heartbeats.json");
8640
+ const heartbeatsPath = join28(commsDir, "heartbeats.json");
8480
8641
  const tmp = `${heartbeatsPath}.tmp.${process.pid}`;
8481
8642
  writeFileSync16(tmp, JSON.stringify(store, null, 2), "utf-8");
8482
8643
  renameSync14(tmp, heartbeatsPath);
@@ -8542,9 +8703,9 @@ function checkComms(commsDir) {
8542
8703
  const checks = [];
8543
8704
  checks.push({
8544
8705
  name: "comms directory",
8545
- status: existsSync28(commsDir) ? PASS : FAIL,
8546
- message: existsSync28(commsDir) ? commsDir : `Not found: ${commsDir}`,
8547
- fix: existsSync28(commsDir) ? void 0 : () => {
8706
+ status: existsSync29(commsDir) ? PASS : FAIL,
8707
+ message: existsSync29(commsDir) ? commsDir : `Not found: ${commsDir}`,
8708
+ fix: existsSync29(commsDir) ? void 0 : () => {
8548
8709
  mkdirSync14(commsDir, { recursive: true });
8549
8710
  return `Created ${commsDir}`;
8550
8711
  }
@@ -8554,8 +8715,8 @@ function checkComms(commsDir) {
8554
8715
  ["reviews", false],
8555
8716
  ["findings", false]
8556
8717
  ]) {
8557
- const dir = join27(commsDir, subdir);
8558
- const exists = existsSync28(dir);
8718
+ const dir = join28(commsDir, subdir);
8719
+ const exists = existsSync29(dir);
8559
8720
  checks.push({
8560
8721
  name: `${subdir} directory`,
8561
8722
  status: exists ? PASS : required ? FAIL : WARN,
@@ -8566,10 +8727,10 @@ function checkComms(commsDir) {
8566
8727
  }
8567
8728
  });
8568
8729
  }
8569
- const heartbeats = join27(commsDir, "heartbeats.json");
8570
- if (existsSync28(heartbeats)) {
8730
+ const heartbeats = join28(commsDir, "heartbeats.json");
8731
+ if (existsSync29(heartbeats)) {
8571
8732
  try {
8572
- const store = JSON.parse(readFileSync23(heartbeats, "utf-8"));
8733
+ const store = JSON.parse(readFileSync24(heartbeats, "utf-8"));
8573
8734
  const agents = Object.keys(store);
8574
8735
  const now = Date.now();
8575
8736
  const active = agents.filter((a) => {
@@ -8683,7 +8844,7 @@ function checkInstances(repoRoot, stateDir, commsDir) {
8683
8844
  }
8684
8845
  }
8685
8846
  }
8686
- const pidPath = join27(stateDir, "pids", `bridge-${id}.json`);
8847
+ const pidPath = join28(stateDir, "pids", `bridge-${id}.json`);
8687
8848
  try {
8688
8849
  unlinkSync8(pidPath);
8689
8850
  } catch {
@@ -8745,8 +8906,8 @@ function checkInstances(repoRoot, stateDir, commsDir) {
8745
8906
  }
8746
8907
  function checkMessageLifecycle(commsDir) {
8747
8908
  const checks = [];
8748
- const inbox = join27(commsDir, "inbox");
8749
- if (!existsSync28(inbox)) {
8909
+ const inbox = join28(commsDir, "inbox");
8910
+ if (!existsSync29(inbox)) {
8750
8911
  checks.push({
8751
8912
  name: "message flow",
8752
8913
  status: FAIL,
@@ -8762,10 +8923,10 @@ function checkMessageLifecycle(commsDir) {
8762
8923
  status: recent10m > 0 ? PASS : total > 0 ? WARN : FAIL,
8763
8924
  message: `${total} total, ${recent1h} in last 1h, ${recent10m} in last 10m`
8764
8925
  });
8765
- const receiptsPath = join27(commsDir, "receipts", "receipts.json");
8766
- if (existsSync28(receiptsPath)) {
8926
+ const receiptsPath = join28(commsDir, "receipts", "receipts.json");
8927
+ if (existsSync29(receiptsPath)) {
8767
8928
  try {
8768
- const receipts = JSON.parse(readFileSync23(receiptsPath, "utf-8"));
8929
+ const receipts = JSON.parse(readFileSync24(receiptsPath, "utf-8"));
8769
8930
  const receiptCount = Object.keys(receipts).length;
8770
8931
  checks.push({
8771
8932
  name: "read receipts",
@@ -8784,8 +8945,8 @@ function checkMessageLifecycle(commsDir) {
8784
8945
  }
8785
8946
  function checkMcpServer(repoRoot) {
8786
8947
  const checks = [];
8787
- const mcpJson = join27(repoRoot, ".mcp.json");
8788
- if (!existsSync28(mcpJson)) {
8948
+ const mcpJson = join28(repoRoot, ".mcp.json");
8949
+ if (!existsSync29(mcpJson)) {
8789
8950
  checks.push({
8790
8951
  name: "MCP config (.mcp.json)",
8791
8952
  status: WARN,
@@ -8795,7 +8956,7 @@ function checkMcpServer(repoRoot) {
8795
8956
  }
8796
8957
  let config;
8797
8958
  try {
8798
- config = JSON.parse(readFileSync23(mcpJson, "utf-8"));
8959
+ config = JSON.parse(readFileSync24(mcpJson, "utf-8"));
8799
8960
  } catch {
8800
8961
  checks.push({
8801
8962
  name: "MCP config (.mcp.json)",
@@ -8838,7 +8999,7 @@ function checkMcpServer(repoRoot) {
8838
8999
  });
8839
9000
  if (hasTapComms.command) {
8840
9001
  const cmd = hasTapComms.command;
8841
- let cmdAvailable = existsSync28(cmd);
9002
+ let cmdAvailable = existsSync29(cmd);
8842
9003
  if (!cmdAvailable) {
8843
9004
  try {
8844
9005
  const result = spawnSync6(cmd, ["--version"], {
@@ -8860,8 +9021,8 @@ function checkMcpServer(repoRoot) {
8860
9021
  const mcpScript = hasTapComms.args[0];
8861
9022
  checks.push({
8862
9023
  name: "MCP server script",
8863
- status: existsSync28(mcpScript) ? PASS : FAIL,
8864
- message: existsSync28(mcpScript) ? mcpScript : `Not found: ${mcpScript}`
9024
+ status: existsSync29(mcpScript) ? PASS : FAIL,
9025
+ message: existsSync29(mcpScript) ? mcpScript : `Not found: ${mcpScript}`
8865
9026
  });
8866
9027
  if (mcpScript.endsWith(".mjs") && hasTapComms.command && !hasTapComms.command.includes("bun")) {
8867
9028
  checks.push({
@@ -8894,8 +9055,8 @@ function checkMcpServer(repoRoot) {
8894
9055
  } else {
8895
9056
  checks.push({
8896
9057
  name: "MCP TAP_COMMS_DIR",
8897
- status: existsSync28(envCommsDir) ? PASS : FAIL,
8898
- message: existsSync28(envCommsDir) ? envCommsDir : `Directory not found: ${envCommsDir}`
9058
+ status: existsSync29(envCommsDir) ? PASS : FAIL,
9059
+ message: existsSync29(envCommsDir) ? envCommsDir : `Directory not found: ${envCommsDir}`
8899
9060
  });
8900
9061
  }
8901
9062
  checks.push({
@@ -8912,7 +9073,7 @@ function checkCodexConfig(repoRoot, commsDir) {
8912
9073
  }
8913
9074
  const checks = [];
8914
9075
  const fixHint = 'Run "tap doctor --fix" or "tap add codex --force".';
8915
- if (!existsSync28(spec.configPath)) {
9076
+ if (!existsSync29(spec.configPath)) {
8916
9077
  checks.push({
8917
9078
  name: "MCP config (~/.codex/config.toml)",
8918
9079
  status: WARN,
@@ -8921,7 +9082,7 @@ function checkCodexConfig(repoRoot, commsDir) {
8921
9082
  });
8922
9083
  return checks;
8923
9084
  }
8924
- const content = readFileSync23(spec.configPath, "utf-8");
9085
+ const content = readFileSync24(spec.configPath, "utf-8");
8925
9086
  const tapTable = extractTomlTable(content, "mcp_servers.tap");
8926
9087
  const tapEnvTable = extractTomlTable(content, "mcp_servers.tap.env");
8927
9088
  const legacyTable = extractTomlTable(content, "mcp_servers.tap-comms");
@@ -9005,8 +9166,8 @@ function checkCodexConfig(repoRoot, commsDir) {
9005
9166
  }
9006
9167
  function checkBridgeTurnHealth(repoRoot) {
9007
9168
  const checks = [];
9008
- const tmpDir = join27(repoRoot, ".tmp");
9009
- if (!existsSync28(tmpDir)) return checks;
9169
+ const tmpDir = join28(repoRoot, ".tmp");
9170
+ if (!existsSync29(tmpDir)) return checks;
9010
9171
  const state = loadState(repoRoot);
9011
9172
  const activeMatchers = /* @__PURE__ */ new Set();
9012
9173
  if (state) {
@@ -9032,11 +9193,11 @@ function checkBridgeTurnHealth(repoRoot) {
9032
9193
  return checks;
9033
9194
  }
9034
9195
  for (const dir of dirs) {
9035
- const heartbeatPath = join27(tmpDir, dir, "heartbeat.json");
9036
- if (!existsSync28(heartbeatPath)) continue;
9196
+ const heartbeatPath = join28(tmpDir, dir, "heartbeat.json");
9197
+ if (!existsSync29(heartbeatPath)) continue;
9037
9198
  let heartbeat;
9038
9199
  try {
9039
- heartbeat = JSON.parse(readFileSync23(heartbeatPath, "utf-8"));
9200
+ heartbeat = JSON.parse(readFileSync24(heartbeatPath, "utf-8"));
9040
9201
  } catch {
9041
9202
  checks.push({
9042
9203
  name: `turn: ${dir}`,
@@ -9209,9 +9370,9 @@ async function doctorCommand(args) {
9209
9370
  inst.agentName = instConfig.agentName;
9210
9371
  inst.port = instConfig.port;
9211
9372
  inst.configHash = instConfig.configHash;
9212
- inst.configSourceFile = inst.configSourceFile || join27(config.stateDir, "instances", `${result.instanceId}.json`);
9373
+ inst.configSourceFile = inst.configSourceFile || join28(config.stateDir, "instances", `${result.instanceId}.json`);
9213
9374
  saveState(repoRoot, state);
9214
- if (inst.configPath && existsSync28(inst.configPath)) {
9375
+ if (inst.configPath && existsSync29(inst.configPath)) {
9215
9376
  const currentHash = hashFile(inst.configPath);
9216
9377
  if (instConfig.runtimeConfigHash !== currentHash) {
9217
9378
  instConfig.runtimeConfigHash = currentHash;
@@ -9332,8 +9493,8 @@ async function doctorCommand(args) {
9332
9493
  // src/commands/comms.ts
9333
9494
  init_utils();
9334
9495
  import { execSync as execSync6, spawnSync as spawnSync7 } from "child_process";
9335
- import * as fs30 from "fs";
9336
- import * as path31 from "path";
9496
+ import * as fs31 from "fs";
9497
+ import * as path32 from "path";
9337
9498
  var COMMS_HELP = `
9338
9499
  Usage:
9339
9500
  tap comms <subcommand>
@@ -9347,7 +9508,7 @@ Examples:
9347
9508
  npx @hua-labs/tap comms push
9348
9509
  `.trim();
9349
9510
  function isGitRepo(dir) {
9350
- return fs30.existsSync(path31.join(dir, ".git"));
9511
+ return fs31.existsSync(path32.join(dir, ".git"));
9351
9512
  }
9352
9513
  function commsPull(commsDir) {
9353
9514
  logHeader("tap comms pull");
@@ -9606,8 +9767,8 @@ async function watchCommand(args) {
9606
9767
  import * as http from "http";
9607
9768
 
9608
9769
  // src/engine/missions.ts
9609
- import * as fs31 from "fs";
9610
- import * as path32 from "path";
9770
+ import * as fs32 from "fs";
9771
+ import * as path33 from "path";
9611
9772
  function parseStatus(raw) {
9612
9773
  const trimmed = raw.trim();
9613
9774
  if (trimmed.includes("active")) return "active";
@@ -9633,10 +9794,10 @@ function parseRow(line) {
9633
9794
  return { id: id.toUpperCase(), title, branch, status, owner };
9634
9795
  }
9635
9796
  function parseMissionsFile(repoRoot) {
9636
- const missionsPath = path32.join(repoRoot, "docs", "missions", "MISSIONS.md");
9797
+ const missionsPath = path33.join(repoRoot, "docs", "missions", "MISSIONS.md");
9637
9798
  let content;
9638
9799
  try {
9639
- content = fs31.readFileSync(missionsPath, "utf-8");
9800
+ content = fs32.readFileSync(missionsPath, "utf-8");
9640
9801
  } catch {
9641
9802
  return [];
9642
9803
  }