@1presence/bridge 0.65.0 → 0.67.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/README.md +21 -0
- package/dist/auth.js +14 -3
- package/dist/index.js +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,27 @@ On first run, a browser window will open to sign in to your 1Presence account. Y
|
|
|
22
22
|
|
|
23
23
|
Once connected, your 1Presence app on any device automatically routes to your local Claude Code session. When you stop the bridge, 1Presence falls back to platform mode.
|
|
24
24
|
|
|
25
|
+
## Admin: running the bridge as another user
|
|
26
|
+
|
|
27
|
+
If your 1Presence account is an **admin**, the sign-in screen shows a user picker
|
|
28
|
+
instead of connecting immediately. Choose which 1Presence user this bridge runs
|
|
29
|
+
as — it defaults to **you** — and click Connect. Picking another user lets you
|
|
30
|
+
drive their account (their vault, connectors, and history) through your local
|
|
31
|
+
Claude Code, which is handy for onboarding testers who don't run the bridge
|
|
32
|
+
themselves. You never need their password; the picker mints a scoped token for
|
|
33
|
+
the selected user server-side.
|
|
34
|
+
|
|
35
|
+
The target user must have **Local Claude Code access** granted in the admin panel
|
|
36
|
+
(the same permission that gates your own bridge and also lets bridge turns run
|
|
37
|
+
free on your Claude subscription). The picker flags anyone who isn't eligible yet.
|
|
38
|
+
|
|
39
|
+
### Multiple sessions at once
|
|
40
|
+
|
|
41
|
+
Because every session is keyed by the connected user, you can run several bridges
|
|
42
|
+
side by side — each in its own terminal, each impersonating a **different** user —
|
|
43
|
+
and they won't interfere. Running two bridges for the *same* user at once is not
|
|
44
|
+
supported (the second displaces the first).
|
|
45
|
+
|
|
25
46
|
## How it works
|
|
26
47
|
|
|
27
48
|
The bridge connects outbound to the 1Presence gateway as a persistent WebSocket. When you send a message from the app, the gateway relays it to the bridge, which spawns a `claude` subprocess with your personal system prompt and all 1Presence tools (vault, Gmail, Drive, Calendar, MemPalace, and more) wired in via MCP. Responses stream back in real time.
|
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
|
-
/**
|
|
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
|
-
|
|
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);
|