@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.
@@ -128,6 +128,7 @@ __export(tap_utils_exports, {
128
128
  debug: () => debug,
129
129
  demoteAgentName: () => demoteAgentName,
130
130
  getAgentId: () => getAgentId,
131
+ getAgentIdentitySnapshot: () => getAgentIdentitySnapshot,
131
132
  getAgentName: () => getAgentName,
132
133
  getLastActivityTime: () => getLastActivityTime,
133
134
  getLatestReviewDir: () => getLatestReviewDir,
@@ -137,6 +138,7 @@ __export(tap_utils_exports, {
137
138
  isForMe: () => isForMe,
138
139
  isIdLocked: () => isIdLocked,
139
140
  isNameConfirmed: () => isNameConfirmed,
141
+ loadStateInstances: () => loadStateInstances,
140
142
  normalizeSources: () => normalizeSources,
141
143
  parseFilename: () => parseFilename,
142
144
  parseFrontmatter: () => parseFrontmatter,
@@ -149,7 +151,7 @@ __export(tap_utils_exports, {
149
151
  updateActivityTime: () => updateActivityTime
150
152
  });
151
153
  import { existsSync, readFileSync, readdirSync, statSync } from "fs";
152
- import { join, resolve } from "path";
154
+ import { basename, join, resolve } from "path";
153
155
  function isConcreteIdentity(value) {
154
156
  return !isPlaceholderAgentValue(value);
155
157
  }
@@ -181,9 +183,70 @@ function resolveSingleCodexBootstrap() {
181
183
  agentName: typeof instance.agentName === "string" && !isPlaceholderAgentValue(instance.agentName) ? instance.agentName : null
182
184
  };
183
185
  }
184
- function resolveInitialId(stateBootstrap2) {
186
+ function resolveRuntimeInstanceId() {
187
+ const bridgeInstanceId = process.env.TAP_BRIDGE_INSTANCE_ID;
188
+ if (isConcreteIdentity(bridgeInstanceId)) {
189
+ return normalizeAgentId(bridgeInstanceId);
190
+ }
185
191
  const envId = process.env.TAP_AGENT_ID;
186
- if (isConcreteIdentity(envId)) return normalizeAgentId(envId);
192
+ if (isConcreteIdentity(envId)) {
193
+ return normalizeAgentId(envId);
194
+ }
195
+ return null;
196
+ }
197
+ function resolveRuntimeStateDir() {
198
+ const runtimeStateDir = process.env.TAP_RUNTIME_STATE_DIR;
199
+ if (!runtimeStateDir) return null;
200
+ return resolve(runtimeStateDir);
201
+ }
202
+ function resolveRuntimeStateInstanceId() {
203
+ const runtimeStateDir = resolveRuntimeStateDir();
204
+ if (!runtimeStateDir) return null;
205
+ const dirName = basename(runtimeStateDir);
206
+ if (!dirName.startsWith(BRIDGE_RUNTIME_STATE_DIR_PREFIX)) {
207
+ return null;
208
+ }
209
+ const instanceId = dirName.slice(BRIDGE_RUNTIME_STATE_DIR_PREFIX.length).trim();
210
+ if (!instanceId) return null;
211
+ return normalizeAgentId(instanceId);
212
+ }
213
+ function resolveRuntimeStateDisplayName() {
214
+ const runtimeStateDir = resolveRuntimeStateDir();
215
+ if (!runtimeStateDir) return null;
216
+ try {
217
+ const heartbeatPath = join(runtimeStateDir, "heartbeat.json");
218
+ if (existsSync(heartbeatPath)) {
219
+ const heartbeat = JSON.parse(readFileSync(heartbeatPath, "utf-8"));
220
+ const heartbeatAgent = heartbeat.agent ?? void 0;
221
+ if (isConcreteIdentity(heartbeatAgent)) {
222
+ return heartbeatAgent;
223
+ }
224
+ }
225
+ } catch {
226
+ }
227
+ try {
228
+ const agentNamePath = join(runtimeStateDir, "agent-name.txt");
229
+ if (!existsSync(agentNamePath)) return null;
230
+ const agentName = readFileSync(agentNamePath, "utf-8").trim();
231
+ return isConcreteIdentity(agentName) ? agentName : null;
232
+ } catch {
233
+ return null;
234
+ }
235
+ }
236
+ function resolveRuntimeDisplayName() {
237
+ const envName = process.env.TAP_AGENT_NAME;
238
+ if (isConcreteIdentity(envName)) return envName;
239
+ const codexName = process.env.CODEX_TAP_AGENT_NAME;
240
+ if (isConcreteIdentity(codexName)) return codexName;
241
+ const runtimeName = resolveRuntimeStateDisplayName();
242
+ if (runtimeName) return runtimeName;
243
+ return null;
244
+ }
245
+ function resolveInitialId(stateBootstrap2) {
246
+ const runtimeInstanceId = resolveRuntimeInstanceId();
247
+ if (runtimeInstanceId) return runtimeInstanceId;
248
+ const runtimeStateInstanceId = resolveRuntimeStateInstanceId();
249
+ if (runtimeStateInstanceId) return runtimeStateInstanceId;
187
250
  const envName = process.env.TAP_AGENT_NAME;
188
251
  if (isConcreteIdentity(envName)) return normalizeAgentId(envName);
189
252
  return stateBootstrap2?.agentId ?? "unknown";
@@ -202,10 +265,25 @@ function resolveNameFromState(agentId, stateBootstrap2) {
202
265
  return null;
203
266
  }
204
267
  }
268
+ function refreshUnknownIdentity() {
269
+ if (_idLocked) return;
270
+ const nextBootstrap = resolveSingleCodexBootstrap();
271
+ const nextId = resolveInitialId(nextBootstrap);
272
+ if (nextId === "unknown") return;
273
+ _agentId = nextId;
274
+ _idLocked = true;
275
+ const nextName = resolveNameFromState(nextId, nextBootstrap) ?? resolveRuntimeDisplayName();
276
+ if (nextName && !isPlaceholderAgentValue(nextName)) {
277
+ _agentName = nextName;
278
+ _nameConfirmed = true;
279
+ }
280
+ }
205
281
  function getAgentId() {
282
+ refreshUnknownIdentity();
206
283
  return _agentId;
207
284
  }
208
285
  function getAgentName() {
286
+ refreshUnknownIdentity();
209
287
  return _agentName;
210
288
  }
211
289
  function resolveKnownInstanceId(agentId, displayName) {
@@ -226,8 +304,31 @@ function resolveKnownInstanceId(agentId, displayName) {
226
304
  return matches.length === 1 ? matches[0][0] : null;
227
305
  }
228
306
  function resolveCurrentInstanceId() {
307
+ refreshUnknownIdentity();
229
308
  return resolveKnownInstanceId(_agentId, _agentName);
230
309
  }
310
+ function getAgentIdentitySnapshot() {
311
+ refreshUnknownIdentity();
312
+ const bootstrap = resolveSingleCodexBootstrap();
313
+ return {
314
+ agentId: _agentId,
315
+ agentName: _agentName,
316
+ idLocked: _idLocked,
317
+ nameConfirmed: _nameConfirmed,
318
+ runtimeEnv: {
319
+ bridgeInstanceId: process.env.TAP_BRIDGE_INSTANCE_ID ?? null,
320
+ agentId: process.env.TAP_AGENT_ID ?? null,
321
+ agentName: process.env.TAP_AGENT_NAME ?? null,
322
+ codexTapAgentName: process.env.CODEX_TAP_AGENT_NAME ?? null,
323
+ commsDir: process.env.TAP_COMMS_DIR ?? null,
324
+ stateDir: process.env.TAP_STATE_DIR ?? null,
325
+ runtimeStateDir: process.env.TAP_RUNTIME_STATE_DIR ?? null,
326
+ repoRoot: process.env.TAP_REPO_ROOT ?? null
327
+ },
328
+ bootstrap,
329
+ resolvedCurrentInstanceId: resolveKnownInstanceId(_agentId, _agentName)
330
+ };
331
+ }
231
332
  function buildHeartbeatConnectHash(instanceId, agentId) {
232
333
  return instanceId ? `instance:${instanceId}` : `session:${agentId}`;
233
334
  }
@@ -333,6 +434,7 @@ function canonicalizeAgentId2(id) {
333
434
  return canonicalizeAgentId(id);
334
435
  }
335
436
  function isForMe(to) {
437
+ refreshUnknownIdentity();
336
438
  return matchesAgentRecipient(to, _agentId, _agentName);
337
439
  }
338
440
  function normalizeSources(value) {
@@ -375,7 +477,7 @@ function getRecentSenders() {
375
477
  }
376
478
  return senders;
377
479
  }
378
- var RAW_COMMS_DIR, COMMS_DIR, INBOX_DIR, REVIEWS_DIR, FINDINGS_DIR, RECEIPTS_DIR, RECEIPTS_PATH, RECEIPTS_LOCK, HEARTBEATS_PATH, HEARTBEATS_LOCK, ARCHIVE_DIR, DB_PATH, SERVER_START, stateBootstrap, _agentId, _agentName, _idLocked, _nameConfirmed, _lastActivityTime;
480
+ var RAW_COMMS_DIR, COMMS_DIR, INBOX_DIR, REVIEWS_DIR, FINDINGS_DIR, RECEIPTS_DIR, RECEIPTS_PATH, RECEIPTS_LOCK, HEARTBEATS_PATH, HEARTBEATS_LOCK, ARCHIVE_DIR, DB_PATH, SERVER_START, BRIDGE_RUNTIME_STATE_DIR_PREFIX, stateBootstrap, _agentId, _agentName, _idLocked, _nameConfirmed, _lastActivityTime;
379
481
  var init_tap_utils = __esm({
380
482
  "packages/tap-plugin/channels/tap-utils.ts"() {
381
483
  "use strict";
@@ -399,9 +501,10 @@ var init_tap_utils = __esm({
399
501
  ARCHIVE_DIR = join(COMMS_DIR, "archive");
400
502
  DB_PATH = join(COMMS_DIR, "tap.db");
401
503
  SERVER_START = Date.now();
504
+ BRIDGE_RUNTIME_STATE_DIR_PREFIX = "codex-app-server-bridge-";
402
505
  stateBootstrap = resolveSingleCodexBootstrap();
403
506
  _agentId = resolveInitialId(stateBootstrap);
404
- _agentName = resolveNameFromState(_agentId, stateBootstrap) ?? (isConcreteIdentity(process.env.TAP_AGENT_NAME) ? process.env.TAP_AGENT_NAME : "unknown");
507
+ _agentName = resolveNameFromState(_agentId, stateBootstrap) ?? resolveRuntimeDisplayName() ?? "unknown";
405
508
  _idLocked = _agentId !== "unknown";
406
509
  _nameConfirmed = !isPlaceholderAgentValue(_agentName);
407
510
  _lastActivityTime = (/* @__PURE__ */ new Date()).toISOString();
@@ -661,7 +764,6 @@ var init_tap_io = __esm({
661
764
 
662
765
  // packages/tap-plugin/channels/tap-comms.ts
663
766
  init_tap_identity();
664
- init_tap_utils();
665
767
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
666
768
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
667
769
  import {
@@ -671,6 +773,80 @@ import {
671
773
  import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
672
774
  import { join as join7 } from "path";
673
775
 
776
+ // packages/tap-plugin/channels/tap-peer-dm-rate-limit.ts
777
+ init_tap_identity();
778
+ var PEER_DM_WINDOW_MS = 5 * 60 * 1e3;
779
+ var PEER_DM_MAX_MESSAGES = 3;
780
+ function normalizeAddress(value) {
781
+ return value?.trim() ?? "";
782
+ }
783
+ function matchesTowerAddress(value, towerName, towerId) {
784
+ const normalizedValue = normalizeAddress(value);
785
+ const normalizedTower = normalizeAddress(towerName);
786
+ const normalizedTowerId = normalizeAddress(towerId);
787
+ if (!normalizedValue) return false;
788
+ return !!normalizedTower && (normalizedValue === normalizedTower || sameRoutingAddress(normalizedValue, normalizedTower)) || !!normalizedTowerId && (normalizedValue === normalizedTowerId || sameRoutingAddress(normalizedValue, normalizedTowerId));
789
+ }
790
+ function resolveTargetAddress(route) {
791
+ const candidate = normalizeAddress(route.resolvedTo) || normalizeAddress(route.to);
792
+ return isBroadcastRecipient(candidate) ? "broadcast" : canonicalizeAgentId(candidate);
793
+ }
794
+ function isPeerDmRateLimitExempt(route) {
795
+ if (isBroadcastRecipient(normalizeAddress(route.to)) || isBroadcastRecipient(normalizeAddress(route.resolvedTo))) {
796
+ return true;
797
+ }
798
+ const towerName = normalizeAddress(route.towerName);
799
+ const towerId = normalizeAddress(route.towerId);
800
+ if (!towerName && !towerId) return false;
801
+ return matchesTowerAddress(route.fromId, towerName, towerId) || matchesTowerAddress(route.fromName, towerName, towerId) || matchesTowerAddress(route.to, towerName, towerId) || matchesTowerAddress(route.resolvedTo, towerName, towerId);
802
+ }
803
+ function pruneHistory(entries, nowMs, windowMs) {
804
+ if (!entries?.length) return [];
805
+ return entries.filter((timestamp) => nowMs - timestamp <= windowMs);
806
+ }
807
+ function getPeerDmRateLimitKey(route) {
808
+ if (isPeerDmRateLimitExempt(route)) {
809
+ return null;
810
+ }
811
+ const from = canonicalizeAgentId(normalizeAddress(route.fromId));
812
+ const to = resolveTargetAddress(route);
813
+ if (!from || !to || to === "broadcast") {
814
+ return null;
815
+ }
816
+ return `${from}->${to}`;
817
+ }
818
+ function checkPeerDmRateLimit(store, route, nowMs = Date.now(), maxMessages = PEER_DM_MAX_MESSAGES, windowMs = PEER_DM_WINDOW_MS) {
819
+ const key = getPeerDmRateLimitKey(route);
820
+ const target = resolveTargetAddress(route);
821
+ if (!key) {
822
+ return {
823
+ allowed: true,
824
+ exempt: true,
825
+ key: null,
826
+ target,
827
+ recentCount: 0
828
+ };
829
+ }
830
+ const recent = pruneHistory(store.get(key), nowMs, windowMs);
831
+ return {
832
+ allowed: recent.length < maxMessages,
833
+ exempt: false,
834
+ key,
835
+ target,
836
+ recentCount: recent.length
837
+ };
838
+ }
839
+ function recordPeerDm(store, route, nowMs = Date.now(), windowMs = PEER_DM_WINDOW_MS) {
840
+ const key = getPeerDmRateLimitKey(route);
841
+ if (!key) return;
842
+ const recent = pruneHistory(store.get(key), nowMs, windowMs);
843
+ recent.push(nowMs);
844
+ store.set(key, recent);
845
+ }
846
+
847
+ // packages/tap-plugin/channels/tap-comms.ts
848
+ init_tap_utils();
849
+
674
850
  // packages/tap-plugin/channels/tap-claims.ts
675
851
  init_tap_utils();
676
852
  import {
@@ -1555,6 +1731,18 @@ seedStartupFiles("inbox");
1555
1731
  seedStartupFiles("reviews");
1556
1732
  seedStartupFiles("findings");
1557
1733
  var ONBOARDING_TEASER_LINES = 10;
1734
+ var peerDmHistory = /* @__PURE__ */ new Map();
1735
+ function loadTowerNameFromConfig() {
1736
+ const repoRoot = process.env.TAP_REPO_ROOT ?? ".";
1737
+ try {
1738
+ const cfgPath = join7(repoRoot, "tap-config.json");
1739
+ if (!existsSync8(cfgPath)) return null;
1740
+ const cfg = JSON.parse(readFileSync7(cfgPath, "utf-8"));
1741
+ return cfg.towerName?.trim() || null;
1742
+ } catch {
1743
+ return null;
1744
+ }
1745
+ }
1558
1746
  function loadOnboardingTeaser() {
1559
1747
  const commsDir = process.env.TAP_COMMS_DIR;
1560
1748
  if (!commsDir) return "";
@@ -1782,6 +1970,11 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
1782
1970
  name: "tap_onboard",
1783
1971
  description: "Get the full onboarding guide for this project. Returns welcome.md + any additional onboarding docs from commsDir/onboarding/.",
1784
1972
  inputSchema: { type: "object", properties: {} }
1973
+ },
1974
+ {
1975
+ name: "tap_identity_probe",
1976
+ description: "Dump the current MCP-side identity/runtime env snapshot seen by tap tools.",
1977
+ inputSchema: { type: "object", properties: {} }
1785
1978
  }
1786
1979
  ]
1787
1980
  }));
@@ -1974,13 +2167,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
1974
2167
  }
1975
2168
  if (oldName === "unknown" || oldName === "unnamed") {
1976
2169
  try {
1977
- const repoRoot = process.env.TAP_REPO_ROOT ?? ".";
1978
- let towerName = null;
1979
- const cfgPath = join7(repoRoot, "tap-config.json");
1980
- if (existsSync8(cfgPath)) {
1981
- const cfg = JSON.parse(readFileSync7(cfgPath, "utf-8"));
1982
- towerName = cfg.towerName ?? null;
1983
- }
2170
+ const towerName = loadTowerNameFromConfig();
1984
2171
  let runtime = process.env.TAP_BRIDGE_RUNTIME ?? null;
1985
2172
  if (!runtime && stateDir) {
1986
2173
  try {
@@ -2082,7 +2269,9 @@ Recent active names: ${activeList}`;
2082
2269
  }
2083
2270
  const cc = normalizeRecipientList(rawCc, [to]);
2084
2271
  const recipientWarnings = [];
2272
+ const towerName = loadTowerNameFromConfig();
2085
2273
  const store = loadHeartbeats();
2274
+ const resolvedTowerId = towerName ? resolvePreferredRecipient(store, towerName).target : null;
2086
2275
  const knownAgents = /* @__PURE__ */ new Set();
2087
2276
  for (const [key, hb] of Object.entries(store)) {
2088
2277
  if (!isPlaceholderAgentValue(key)) knownAgents.add(key);
@@ -2112,10 +2301,57 @@ Recent active names: ${activeList}`;
2112
2301
  }
2113
2302
  }
2114
2303
  }
2304
+ const resolvedCc = cc?.map((recipient) => ({
2305
+ original: recipient,
2306
+ resolved: isBroadcastRecipient(recipient) ? recipient : resolveRecipient2(recipient).target
2307
+ }));
2115
2308
  const now = /* @__PURE__ */ new Date();
2309
+ const nowMs = now.getTime();
2116
2310
  const date = now.toISOString().slice(0, 10).replace(/-/g, "");
2117
2311
  const fromId = getAgentId();
2118
2312
  const fromName = getAgentName();
2313
+ const rateLimitRoutes = /* @__PURE__ */ new Map();
2314
+ const primaryRoute = {
2315
+ fromId,
2316
+ fromName,
2317
+ to,
2318
+ resolvedTo,
2319
+ towerName,
2320
+ towerId: resolvedTowerId
2321
+ };
2322
+ const primaryCheck = checkPeerDmRateLimit(peerDmHistory, primaryRoute, nowMs);
2323
+ if (!primaryCheck.exempt && primaryCheck.key) {
2324
+ rateLimitRoutes.set(primaryCheck.key, primaryRoute);
2325
+ }
2326
+ for (const recipient of resolvedCc ?? []) {
2327
+ const route = {
2328
+ fromId,
2329
+ fromName,
2330
+ to: recipient.original,
2331
+ resolvedTo: recipient.resolved,
2332
+ towerName,
2333
+ towerId: resolvedTowerId
2334
+ };
2335
+ const check = checkPeerDmRateLimit(peerDmHistory, route, nowMs);
2336
+ if (check.exempt || !check.key) continue;
2337
+ rateLimitRoutes.set(check.key, route);
2338
+ }
2339
+ for (const route of rateLimitRoutes.values()) {
2340
+ const check = checkPeerDmRateLimit(peerDmHistory, route, nowMs);
2341
+ if (!check.allowed) {
2342
+ return {
2343
+ content: [
2344
+ {
2345
+ type: "text",
2346
+ text: `Rate limited: too many messages between ${fromId}\u2192${check.target}. Route through tower instead.`
2347
+ }
2348
+ ]
2349
+ };
2350
+ }
2351
+ }
2352
+ for (const route of rateLimitRoutes.values()) {
2353
+ recordPeerDm(peerDmHistory, route, nowMs);
2354
+ }
2119
2355
  const filename = `${date}-${fromId}-${resolvedTo}-${subject}.md`;
2120
2356
  const filepath = join7(INBOX_DIR, filename);
2121
2357
  const ccHeader = cc?.length ? `> CC: ${cc.join(", ")}
@@ -2145,12 +2381,14 @@ Recent active names: ${activeList}`;
2145
2381
  const sent = [`Sent to ${to}: ${filename}`];
2146
2382
  if (cc?.length) {
2147
2383
  const writtenFiles = /* @__PURE__ */ new Set([filename]);
2148
- for (const recipient of cc) {
2384
+ for (const recipient of resolvedCc ?? []) {
2149
2385
  try {
2150
- const resolvedRecipient = isBroadcastRecipient(recipient) ? recipient : resolveRecipient2(recipient).target;
2386
+ const resolvedRecipient = recipient.resolved;
2151
2387
  const ccFilename = `${date}-${fromId}-${resolvedRecipient}-${subject}.md`;
2152
2388
  if (writtenFiles.has(ccFilename)) {
2153
- sent.push(`CC to ${recipient}: skipped (resolves to same target)`);
2389
+ sent.push(
2390
+ `CC to ${recipient.original}: skipped (resolves to same target)`
2391
+ );
2154
2392
  continue;
2155
2393
  }
2156
2394
  writtenFiles.add(ccFilename);
@@ -2160,7 +2398,7 @@ Recent active names: ${activeList}`;
2160
2398
  `from: ${fromId}`,
2161
2399
  `from_name: ${fromName}`,
2162
2400
  `to: ${resolvedRecipient}`,
2163
- `to_name: ${recipient}`,
2401
+ `to_name: ${recipient.original}`,
2164
2402
  `subject: ${subject}`,
2165
2403
  `sent_at: ${now.toISOString()}`,
2166
2404
  "---",
@@ -2181,10 +2419,10 @@ ${content}`,
2181
2419
  "inbox",
2182
2420
  Date.now()
2183
2421
  );
2184
- sent.push(`CC to ${recipient}: ${ccFilename}`);
2422
+ sent.push(`CC to ${recipient.original}: ${ccFilename}`);
2185
2423
  } catch (err) {
2186
2424
  sent.push(
2187
- `CC to ${recipient}: FAILED (${err instanceof Error ? err.message : String(err)})`
2425
+ `CC to ${recipient.original}: FAILED (${err instanceof Error ? err.message : String(err)})`
2188
2426
  );
2189
2427
  }
2190
2428
  }
@@ -2534,6 +2772,16 @@ ${content}`);
2534
2772
  content: [{ type: "text", text: prefix + docs.join("\n\n---\n\n") }]
2535
2773
  };
2536
2774
  }
2775
+ if (req.params.name === "tap_identity_probe") {
2776
+ return {
2777
+ content: [
2778
+ {
2779
+ type: "text",
2780
+ text: JSON.stringify(getAgentIdentitySnapshot(), null, 2)
2781
+ }
2782
+ ]
2783
+ };
2784
+ }
2537
2785
  throw new Error(`unknown tool: ${req.params.name}`);
2538
2786
  });
2539
2787
  await mcp.connect(new StdioServerTransport());