@1presence/bridge 0.35.0 → 0.37.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
@@ -6,6 +6,7 @@ exports.setDebug = setDebug;
6
6
  exports.paint = paint;
7
7
  exports.spawnClaude = spawnClaude;
8
8
  exports.killAll = killAll;
9
+ exports.cancelConversation = cancelConversation;
9
10
  const child_process_1 = require("child_process");
10
11
  const fs_1 = require("fs");
11
12
  const os_1 = require("os");
@@ -581,3 +582,26 @@ function killAll() {
581
582
  }
582
583
  active.clear();
583
584
  }
585
+ /**
586
+ * Stop one in-flight turn (the Stop button, relayed by the gateway as a
587
+ * `cancel` frame). Kills the running Claude Code process for this conversation
588
+ * and cancels any scheduled retry, so no further stream events are produced.
589
+ * Mirrors the supersede path in spawnClaude. Returns true if something was
590
+ * actually stopped. Mechanical only — no product logic lives here.
591
+ */
592
+ function cancelConversation(conversationId) {
593
+ let stopped = false;
594
+ const proc = active.get(conversationId);
595
+ if (proc) {
596
+ proc.kill('SIGTERM');
597
+ active.delete(conversationId);
598
+ stopped = true;
599
+ }
600
+ const pending = pendingRetries.get(conversationId);
601
+ if (pending) {
602
+ clearTimeout(pending);
603
+ pendingRetries.delete(conversationId);
604
+ stopped = true;
605
+ }
606
+ return stopped;
607
+ }
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ const config_1 = require("./config");
14
14
  const update_1 = require("./update");
15
15
  const accumulator_1 = require("./accumulator");
16
16
  const outbox_1 = require("./outbox");
17
+ const timer_1 = require("./timer");
17
18
  const package_json_1 = require("../package.json");
18
19
  // Published tarballs don't ship src/, so this fires only when running the
19
20
  // dist build from a live workspace checkout. Catches the trap where editing
@@ -149,6 +150,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
149
150
  // If it's already invalid, MCP calls will 401 mid-turn — fail fast instead.
150
151
  if (!(0, auth_1.isTokenValid)(auth.token)) {
151
152
  const message = 'Authentication expired and refresh failed — please restart the bridge to sign in again.';
153
+ (0, timer_1.stopTurnTimer)();
152
154
  console.error(`[bridge] ${message} (${err.message})`);
153
155
  if (currentWs?.readyState === ws_1.default.OPEN) {
154
156
  currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
@@ -169,6 +171,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
169
171
  }
170
172
  catch (err) {
171
173
  const message = `System prompt refresh failed: ${err.message}`;
174
+ (0, timer_1.stopTurnTimer)();
172
175
  console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message}`);
173
176
  if (currentWs?.readyState === ws_1.default.OPEN) {
174
177
  currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
@@ -253,12 +256,13 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
253
256
  }
254
257
  },
255
258
  onDone: (messageCount, costUsd, usage, model) => {
256
- const parts = [];
259
+ const elapsed = (0, timer_1.stopTurnTimer)();
260
+ const parts = [(0, timer_1.formatElapsed)(elapsed)];
257
261
  if (usage)
258
262
  parts.push(`in:${usage.input_tokens} out:${usage.output_tokens}`);
259
263
  const costStr = costUsd === 0 ? '$0.0000 (plan usage)' : `$${costUsd.toFixed(4)}`;
260
264
  parts.push(costStr);
261
- const suffix = parts.length ? ` ${parts.join(' ')}` : '';
265
+ const suffix = ` ${parts.join(' ')}`;
262
266
  console.log(`[${new Date().toLocaleTimeString()}] ✓ done${suffix}`);
263
267
  const mapped = toBridgeUsage(usage);
264
268
  if (currentWs?.readyState === ws_1.default.OPEN) {
@@ -275,7 +279,8 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
275
279
  void finalizeAndPost(buildSpoolRecord(mapped, model));
276
280
  },
277
281
  onError: (message, usage, model) => {
278
- console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message}`);
282
+ const elapsed = (0, timer_1.stopTurnTimer)();
283
+ console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message} (${(0, timer_1.formatElapsed)(elapsed)})`);
279
284
  const mapped = toBridgeUsage(usage);
280
285
  if (currentWs?.readyState === ws_1.default.OPEN) {
281
286
  currentWs.send(JSON.stringify({
@@ -386,13 +391,24 @@ function connect(auth, retryDelay = 1000) {
386
391
  }
387
392
  return;
388
393
  }
394
+ // Stop button: the gateway relays a cancel when the user abandons the turn
395
+ // (PWA→gateway connection dropped). Kill the local Claude Code process for
396
+ // this conversation so it stops generating instead of running to the end.
397
+ if (msg.type === 'cancel' && msg.conversationId) {
398
+ const cancelled = (0, claude_1.cancelConversation)(msg.conversationId);
399
+ if (cancelled)
400
+ console.log(`[bridge] ✕ stopped conversation ${msg.conversationId}`);
401
+ return;
402
+ }
389
403
  if (msg.type !== 'message' || !msg.conversationId || !msg.text)
390
404
  return;
391
405
  const { conversationId, text, sessionId, history, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug } = msg;
392
406
  const ts = new Date().toLocaleTimeString();
393
407
  const hist = Array.isArray(history) ? history : [];
394
408
  console.log(`[${ts}] ▶ ${text}${hist.length ? ` (history: ${hist.length} turn${hist.length === 1 ? '' : 's'})` : ''}`);
409
+ (0, timer_1.startTurnTimer)();
395
410
  handleMessage(conversationId, text, sessionId ?? null, hist, auth, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug).catch((err) => {
411
+ (0, timer_1.stopTurnTimer)();
396
412
  console.error(`[bridge] handleMessage error: ${err.message}`);
397
413
  });
398
414
  });
package/dist/timer.js ADDED
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ // Live elapsed-time indicator for the active turn. Writes `\r\x1b[K⏱ Xs`
3
+ // once per second; wraps console.log/error/warn so that any other output
4
+ // clears the timer line before printing, then redraws the timer on the new
5
+ // bottom line. Idempotent — stop is safe to call multiple times.
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.formatElapsed = formatElapsed;
8
+ exports.startTurnTimer = startTurnTimer;
9
+ exports.stopTurnTimer = stopTurnTimer;
10
+ let intervalId = null;
11
+ let startedAt = 0;
12
+ let originalLog = null;
13
+ let originalError = null;
14
+ let originalWarn = null;
15
+ function clearLine() {
16
+ process.stdout.write('\r\x1b[K');
17
+ }
18
+ function draw() {
19
+ const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);
20
+ process.stdout.write(`\r\x1b[K⏱ ${formatElapsed(elapsedSec)}`);
21
+ }
22
+ function formatElapsed(seconds) {
23
+ if (seconds < 60)
24
+ return `${seconds}s`;
25
+ const m = Math.floor(seconds / 60);
26
+ const s = seconds % 60;
27
+ return `${m}m ${s.toString().padStart(2, '0')}s`;
28
+ }
29
+ function startTurnTimer() {
30
+ if (intervalId !== null)
31
+ return;
32
+ startedAt = Date.now();
33
+ originalLog = console.log;
34
+ originalError = console.error;
35
+ originalWarn = console.warn;
36
+ console.log = (...args) => { clearLine(); originalLog(...args); draw(); };
37
+ console.error = (...args) => { clearLine(); originalError(...args); draw(); };
38
+ console.warn = (...args) => { clearLine(); originalWarn(...args); draw(); };
39
+ draw();
40
+ intervalId = setInterval(draw, 1000);
41
+ }
42
+ function stopTurnTimer() {
43
+ if (intervalId === null)
44
+ return 0;
45
+ clearInterval(intervalId);
46
+ intervalId = null;
47
+ clearLine();
48
+ if (originalLog)
49
+ console.log = originalLog;
50
+ if (originalError)
51
+ console.error = originalError;
52
+ if (originalWarn)
53
+ console.warn = originalWarn;
54
+ originalLog = originalError = originalWarn = null;
55
+ return Math.floor((Date.now() - startedAt) / 1000);
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.35.0",
3
+ "version": "0.37.0",
4
4
  "description": "Run 1Presence on your Mac and use your Claude.ai Pro subscription from any device",
5
5
  "bin": {
6
6
  "1presence-bridge": "dist/index.js"