@1presence/bridge 0.65.0 → 0.66.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/auth.js CHANGED
@@ -197,11 +197,22 @@ async function refreshIdToken(refreshToken) {
197
197
  throw new Error('Token refresh returned no id_token');
198
198
  return data.id_token;
199
199
  }
200
- /** Returns auth with a fresh ID token. Refreshes in-memory if <10 minutes remain. */
200
+ /** Headroom below which ensureFreshToken mints a new ID token at turn start.
201
+ * Firebase ID tokens live 60 min; the token is baked into the MCP SSE config
202
+ * header at spawn (writeMcpConfig) and reused by that connection for the whole
203
+ * turn — it can't be re-tokened mid-turn without reconnecting the MCP server.
204
+ * So a turn must START with enough headroom to outlive itself. At 10 min a turn
205
+ * that began with ~12 min of life and ran 17 min lost its MCP OAuth AND its
206
+ * save-turn token mid-flight ("MCP OAuth not configured" + save-turn 401 — see
207
+ * the 2026-07-07 stuck-run bug). 30 min comfortably covers the 10-min bridge
208
+ * stage wall-clock and any normal chat turn; near-full-hour refresh would churn
209
+ * a token every turn for little gain. */
210
+ const TOKEN_REFRESH_HEADROOM_SEC = 30 * 60;
211
+ /** Returns auth with a fresh ID token. Refreshes in-memory if less than
212
+ * TOKEN_REFRESH_HEADROOM_SEC of validity remains. */
201
213
  export async function ensureFreshToken(auth) {
202
214
  const { exp } = parseJwt(auth.token);
203
- const tenMinutes = 10 * 60;
204
- if (exp && exp > Math.floor(Date.now() / 1000) + tenMinutes)
215
+ if (exp && exp > Math.floor(Date.now() / 1000) + TOKEN_REFRESH_HEADROOM_SEC)
205
216
  return auth;
206
217
  if (!auth.refreshToken)
207
218
  return auth; // no refresh token, use as-is
package/dist/index.js CHANGED
@@ -433,6 +433,22 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
433
433
  catch (err) {
434
434
  console.warn(`[bridge] spool write failed: ${err.message}`);
435
435
  }
436
+ // Refresh the token right before the POST — this fires at turn END, when the
437
+ // most time has elapsed. A long turn (started with a token that was fresh
438
+ // then) must not 401 on save-turn with a since-expired token — the failure
439
+ // that left a run stuck at 'running' on 2026-07-07. Best-effort: on refresh
440
+ // failure fall back to the current token; the spool file is retried on the
441
+ // next successful POST or on the next bridge startup.
442
+ try {
443
+ const fresh = await ensureFreshToken(activeAuth);
444
+ if (fresh.token !== activeAuth.token) {
445
+ activeAuth = fresh;
446
+ currentAuth = fresh;
447
+ }
448
+ }
449
+ catch (err) {
450
+ console.warn(`[bridge] pre-save-turn token refresh failed (using current token): ${err.message}`);
451
+ }
436
452
  const result = await postSaveTurn(GATEWAY_HTTP, activeAuth.token, record);
437
453
  if (result.ok) {
438
454
  deleteSpool(record.conversationId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.65.0",
3
+ "version": "0.66.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",