@1presence/bridge 0.95.0 → 0.97.0

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/claude.js CHANGED
@@ -140,13 +140,17 @@ export function isPermissionTransportFailure(text) {
140
140
  export function isLiveTurnResult(result, producedRealOutput) {
141
141
  return producedRealOutput || result.is_error === true;
142
142
  }
143
- const MCP_TOOL_UNREGISTERED_RE = new RegExp(`No such tool available:\\s*(?:${MCP_TOOL_PREFIX}|${LEGACY_MCP_TOOL_PREFIX})`, 'i');
143
+ const MCP_TOOL_UNREGISTERED_RE = new RegExp(`No such tool available:\\s*((?:${MCP_TOOL_PREFIX}|${LEGACY_MCP_TOOL_PREFIX})[A-Za-z0-9_]+)`, 'i');
144
144
  export function shouldReconnectRemoteMcp(args) {
145
- const { toolName, isError, text } = args;
145
+ const { toolName, isError, text, served } = args;
146
146
  if (MCP_SESSION_EXPIRED_RE.test(text))
147
147
  return true;
148
- if (MCP_TOOL_UNREGISTERED_RE.test(text))
148
+ const unregistered = MCP_TOOL_UNREGISTERED_RE.exec(text);
149
+ if (unregistered) {
150
+ if (served && served.size > 0 && !served.has(unregistered[1]))
151
+ return false;
149
152
  return true;
153
+ }
150
154
  if (!isError || !isPresenceToolName(toolName))
151
155
  return false;
152
156
  if (OUTBOUND_NETWORK_TOOLS.has(toolName))
@@ -241,7 +245,9 @@ export function makeGatedPromptStream(messages) {
241
245
  };
242
246
  }
243
247
  export function spawnClaude(params) {
244
- const { conversationId, presenceSessionId, text, uid, history, vaultFileOpen, clientCapabilities, syncedFolders, model: perTurnModel, onEvent, onDone, onError, onNotice } = params;
248
+ const { conversationId, presenceSessionId, chatSessionId, text, uid, history, vaultFileOpen, clientCapabilities, syncedFolders, model: perTurnModel, onEvent, onDone, onError, onNotice } = params;
249
+ const sessionTag = ` · session ${chatSessionId ?? conversationId}`;
250
+ const errLine = (text) => paint(SECTION_COLORS.error, `${text}${sessionTag}`);
245
251
  const systemPromptPath = join(tmpdir(), `agent-${uid}.md`);
246
252
  const mcpConfigPath = join(tmpdir(), `mcp-${uid}.json`);
247
253
  if (verbose) {
@@ -266,7 +272,7 @@ export function spawnClaude(params) {
266
272
  debugBlock('user · this turn', SECTION_COLORS.user, text);
267
273
  }
268
274
  else {
269
- process.stderr.write(`[bridge] session ${presenceSessionId}\n`);
275
+ process.stderr.write(`[bridge] session ${chatSessionId ?? '(none)'} turn ${conversationId}\n`);
270
276
  }
271
277
  void vaultFileOpen;
272
278
  void clientCapabilities;
@@ -388,7 +394,7 @@ export function spawnClaude(params) {
388
394
  if (blockText && TOOL_CALL_XML_RE.test(blockText)) {
389
395
  const cleaned = stripToolCallXml(blockText);
390
396
  if (cleaned !== blockText) {
391
- process.stderr.write(paint(SECTION_COLORS.error, `[bridge] suppressed confabulated tool-call XML in assistant text`) + '\n');
397
+ process.stderr.write(errLine(`[bridge] suppressed confabulated tool-call XML in assistant text`) + '\n');
392
398
  block['text'] = cleaned;
393
399
  blockText = cleaned;
394
400
  }
@@ -402,7 +408,7 @@ export function spawnClaude(params) {
402
408
  apiErrorText = blockText.trim();
403
409
  if (isAuthFailure)
404
410
  sawAuthFailure = true;
405
- process.stderr.write(paint(SECTION_COLORS.error, `[bridge] ${blockText.replace(/\n+/g, ' ')}`) + '\n');
411
+ process.stderr.write(errLine(`[bridge] ${blockText.replace(/\n+/g, ' ')}`) + '\n');
406
412
  return false;
407
413
  }
408
414
  producedRealOutput = true;
@@ -433,7 +439,7 @@ export function spawnClaude(params) {
433
439
  if (block['is_error']) {
434
440
  const detail = toolResultText(out).replace(/\s+/g, ' ').trim();
435
441
  const shown = detail.length > 400 ? `${detail.slice(0, 400)}…` : detail;
436
- process.stderr.write(paint(SECTION_COLORS.error, `[bridge] ✗ tool error ← ${name}: ${shown || 'no detail'}`) + '\n');
442
+ process.stderr.write(errLine(`[bridge] ✗ tool error ← ${name}: ${shown || 'no detail'}`) + '\n');
437
443
  }
438
444
  if (!verbose && !debug)
439
445
  continue;
@@ -460,7 +466,7 @@ export function spawnClaude(params) {
460
466
  const detail = joinErrorDetail(event['result'], event['errors']);
461
467
  apiErrorText = joinErrorDetail(apiErrorText, detail) || apiErrorText;
462
468
  const subtype = event['subtype'] ? ` subtype=${event['subtype']}` : '';
463
- process.stderr.write(paint(SECTION_COLORS.error, `[bridge] result error${status != null ? ` (${status})` : ''}${subtype}: ${apiErrorText || 'unknown'}`) + '\n');
469
+ process.stderr.write(errLine(`[bridge] result error${status != null ? ` (${status})` : ''}${subtype}: ${apiErrorText || 'unknown'}`) + '\n');
464
470
  if (!detail) {
465
471
  let raw;
466
472
  try {
@@ -469,7 +475,7 @@ export function spawnClaude(params) {
469
475
  catch {
470
476
  raw = String(event);
471
477
  }
472
- process.stderr.write(paint(SECTION_COLORS.error, `[bridge] result raw: ${raw.slice(0, 2000)}`) + '\n');
478
+ process.stderr.write(errLine(`[bridge] result raw: ${raw.slice(0, 2000)}`) + '\n');
473
479
  }
474
480
  }
475
481
  }
@@ -541,6 +547,7 @@ export function spawnClaude(params) {
541
547
  inputReleaseTimer.unref?.();
542
548
  let lastMcpReconnectAt = 0;
543
549
  let mcpReconnecting = false;
550
+ let servedPresenceTools = new Set();
544
551
  const MCP_RECONNECT_THROTTLE_MS = 10_000;
545
552
  const maybeReconnectRemoteMcp = (q) => {
546
553
  const now = Date.now();
@@ -548,11 +555,11 @@ export function spawnClaude(params) {
548
555
  return;
549
556
  mcpReconnecting = true;
550
557
  lastMcpReconnectAt = now;
551
- process.stderr.write(paint(SECTION_COLORS.error, '[bridge] remote MCP connection lost — reconnecting and retrying') + '\n');
558
+ process.stderr.write(errLine('[bridge] remote MCP connection lost — reconnecting and retrying') + '\n');
552
559
  onNotice?.('Reconnecting to 1Presence tools…');
553
560
  void q.reconnectMcpServer(REMOTE_MCP_SERVER_NAME)
554
561
  .then(() => process.stderr.write(paint(SECTION_COLORS.result, '[bridge] remote MCP reconnected') + '\n'))
555
- .catch((err) => process.stderr.write(paint(SECTION_COLORS.error, `[bridge] remote MCP reconnect failed: ${err.message}`) + '\n'))
562
+ .catch((err) => process.stderr.write(errLine(`[bridge] remote MCP reconnect failed: ${err.message}`) + '\n'))
556
563
  .finally(() => { mcpReconnecting = false; });
557
564
  };
558
565
  try {
@@ -566,9 +573,10 @@ export function spawnClaude(params) {
566
573
  const subtype = m.subtype;
567
574
  if (subtype === 'init') {
568
575
  const init = m;
569
- const event = { type: 'system', subtype: 'init', model: init.model, apiKeySource: init.apiKeySource, claude_code_version: init.claude_code_version };
576
+ const event = { type: 'system', subtype: 'init', model: init.model, apiKeySource: init.apiKeySource, claude_code_version: init.claude_code_version, session_id: init.session_id };
570
577
  if (handleEvent(event))
571
578
  onEvent(event);
579
+ servedPresenceTools = new Set((init.tools ?? []).filter((t) => isPresenceToolName(t)));
572
580
  const surface = assessRemoteMcpSurface(m);
573
581
  const surfaceLine = `[bridge] mcp surface: ${REMOTE_MCP_SERVER_NAME}=${surface.status}, ${surface.toolCount} tool(s)`;
574
582
  if (surfaceLine !== lastSurfaceLog) {
@@ -584,9 +592,9 @@ export function spawnClaude(params) {
584
592
  detail = ` — ${remote.error.replace(/\s+/g, ' ').trim().slice(0, 300)}`;
585
593
  }
586
594
  catch { }
587
- process.stderr.write(paint(SECTION_COLORS.error, `[bridge] FATAL no 1Presence tools this turn (${surface.status}) — ending the turn rather than answering blind${detail}`) + '\n');
595
+ process.stderr.write(errLine(`[bridge] FATAL no 1Presence tools this turn (${surface.status}) — ending the turn rather than answering blind${detail}`) + '\n');
588
596
  if (surface.status === 'needs-auth' && !detail) {
589
- process.stderr.write(paint(SECTION_COLORS.error, '[bridge] ↳ the server asked for OAuth, which Local Mode does not use — this is NOT a stale local token, and restarting will not clear it. The gateway is rejecting this account\'s token on /mcp.') + '\n');
597
+ process.stderr.write(errLine('[bridge] ↳ the server asked for OAuth, which Local Mode does not use — this is NOT a stale local token, and restarting will not clear it. The gateway is rejecting this account\'s token on /mcp.') + '\n');
590
598
  }
591
599
  active.delete(conversationId);
592
600
  clearTimeout(inputReleaseTimer);
@@ -598,7 +606,7 @@ export function spawnClaude(params) {
598
606
  }
599
607
  else if (subtype === 'api_retry') {
600
608
  const r = m;
601
- process.stderr.write(paint(SECTION_COLORS.error, `[bridge] api retry ${r.attempt ?? '?'}/${r.max_retries ?? '?'}${r.error_status != null ? ` (${r.error_status})` : ''}: ${r.error ?? 'unknown'}${r.retry_delay_ms != null ? `, next in ${r.retry_delay_ms}ms` : ''}`) + '\n');
609
+ process.stderr.write(errLine(`[bridge] api retry ${r.attempt ?? '?'}/${r.max_retries ?? '?'}${r.error_status != null ? ` (${r.error_status})` : ''}: ${r.error ?? 'unknown'}${r.retry_delay_ms != null ? `, next in ${r.retry_delay_ms}ms` : ''}`) + '\n');
602
610
  const attempt = r.attempt ?? 0;
603
611
  if (attempt >= 2 || (r.retry_delay_ms ?? 0) >= 2000) {
604
612
  onNotice?.(formatRetryNotice(attempt, r.max_retries ?? 0, r.retry_delay_ms));
@@ -623,7 +631,7 @@ export function spawnClaude(params) {
623
631
  const full = joinErrorDetail(`API Error: ${am.error}`, msgText, rid);
624
632
  if (!apiErrorText || /^API Error: \w+$/.test(apiErrorText))
625
633
  apiErrorText = full;
626
- process.stderr.write(paint(SECTION_COLORS.error, `[bridge] assistant error: ${joinErrorDetail(am.error, msgText, rid)}`) + '\n');
634
+ process.stderr.write(errLine(`[bridge] assistant error: ${joinErrorDetail(am.error, msgText, rid)}`) + '\n');
627
635
  break;
628
636
  }
629
637
  const event = { type: 'assistant', message: am.message, error: am.error };
@@ -646,7 +654,7 @@ export function spawnClaude(params) {
646
654
  const resultText = toolResultText(b['content']);
647
655
  if (b['is_error'] === true && isPermissionTransportFailure(resultText)) {
648
656
  const detail = resultText.replace(/\s+/g, ' ').trim().slice(0, 300);
649
- process.stderr.write(paint(SECTION_COLORS.error, `[bridge] FATAL permission transport lost — ending the turn rather than answering blind: ${detail}`) + '\n');
657
+ process.stderr.write(errLine(`[bridge] FATAL permission transport lost — ending the turn rather than answering blind: ${detail}`) + '\n');
650
658
  active.delete(conversationId);
651
659
  clearTimeout(inputReleaseTimer);
652
660
  input.release();
@@ -658,6 +666,7 @@ export function spawnClaude(params) {
658
666
  toolName: toolNames.get(b['tool_use_id'] ?? '') ?? '',
659
667
  isError: b['is_error'] === true,
660
668
  text: resultText,
669
+ served: servedPresenceTools,
661
670
  })) {
662
671
  maybeReconnectRemoteMcp(q);
663
672
  break;
@@ -735,7 +744,7 @@ export function spawnClaude(params) {
735
744
  return;
736
745
  const message = err?.message ?? String(err);
737
746
  const stack = err?.stack;
738
- process.stderr.write(paint(SECTION_COLORS.error, `[bridge] query() threw: ${stack || message}`) + '\n');
747
+ process.stderr.write(errLine(`[bridge] query() threw: ${stack || message}`) + '\n');
739
748
  if (/40[13]\b|unauthor|invalid (api key|authentication)|please run \/login/i.test(message)) {
740
749
  sawAuthFailure = true;
741
750
  }
package/dist/index.js CHANGED
@@ -280,7 +280,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
280
280
  if (!isTokenValid(auth.token)) {
281
281
  const message = 'Authentication expired and refresh failed — please restart the bridge to sign in again.';
282
282
  stopTurnTimer();
283
- console.error(paint(SECTION_COLORS.error, `[bridge] ${message} (${err.message})`));
283
+ console.error(paint(SECTION_COLORS.error, `[bridge] ${message} (${err.message}) · session ${sessionId ?? conversationId}`));
284
284
  if (currentWs?.readyState === WebSocket.OPEN) {
285
285
  currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
286
286
  }
@@ -294,7 +294,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
294
294
  catch (err) {
295
295
  const message = `System prompt refresh failed: ${err.message}`;
296
296
  stopTurnTimer();
297
- console.error(paint(SECTION_COLORS.error, `[${new Date().toLocaleTimeString()}] ✗ ${message}`));
297
+ console.error(paint(SECTION_COLORS.error, `[${new Date().toLocaleTimeString()}] ✗ ${message} · session ${sessionId ?? conversationId}`));
298
298
  if (currentWs?.readyState === WebSocket.OPEN) {
299
299
  currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
300
300
  }
@@ -352,6 +352,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
352
352
  spawnClaude({
353
353
  conversationId,
354
354
  presenceSessionId: claudePinnedSessionId,
355
+ chatSessionId: turnSessionId,
355
356
  text,
356
357
  uid: activeAuth.uid,
357
358
  history,
@@ -414,7 +415,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
414
415
  },
415
416
  onError: (message, usage, model) => {
416
417
  const elapsed = stopTurnTimer();
417
- console.error(paint(SECTION_COLORS.error, `[${new Date().toLocaleTimeString()}] ✗ ${message} (${formatElapsed(elapsed)})`));
418
+ console.error(paint(SECTION_COLORS.error, `[${new Date().toLocaleTimeString()}] ✗ ${message} (${formatElapsed(elapsed)}) · session ${turnSessionId}`));
418
419
  const mapped = toBridgeUsage(usage);
419
420
  if (currentWs?.readyState === WebSocket.OPEN) {
420
421
  currentWs.send(JSON.stringify({
@@ -555,7 +556,7 @@ function connect(auth, retryDelay = 1000) {
555
556
  startTurnTimer();
556
557
  handleMessage(conversationId, text, sessionId ?? null, hist, auth, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug, model).catch((err) => {
557
558
  stopTurnTimer();
558
- console.error(paint(SECTION_COLORS.error, `[bridge] handleMessage error: ${err.message}`));
559
+ console.error(paint(SECTION_COLORS.error, `[bridge] handleMessage error: ${err.message} · session ${sessionId ?? conversationId}`));
559
560
  });
560
561
  });
561
562
  ws.on('close', (code) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.95.0",
3
+ "version": "0.97.0",
4
4
  "description": "Run 1Presence on your Mac and use your Claude.ai Pro subscription from any device",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",