@spinabot/brigade 1.16.0 → 1.17.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.
@@ -15,28 +15,121 @@
15
15
  import { AuthStorage } from "@earendil-works/pi-coding-agent";
16
16
  import { DEFAULT_AGENT_ID } from "../config/paths.js";
17
17
  import { PROVIDERS } from "../providers/catalog.js";
18
- import { readProfiles } from "../auth/profiles.js";
18
+ import { readProfiles, updateOAuthTokens } from "../auth/profiles.js";
19
+ /**
20
+ * An `AuthStorageBackend` that READS Brigade's resolved credential map and
21
+ * PERSISTS any OAuth refresh Pi performs back into auth-profiles.json.
22
+ *
23
+ * Pi's `AuthStorage` auto-refreshes an expired OAuth token on `getApiKey()` and
24
+ * persists the result through the backend's `withLock` — calling our `fn` with
25
+ * the current serialized credential map and handing back the refreshed map as
26
+ * `next`. Every subscription provider that matters (Anthropic / OpenAI-Codex /
27
+ * Google) ROTATES its refresh token on each refresh, so the refreshed map
28
+ * carries a NEW refresh token and the old one is now dead. We diff `next`
29
+ * against `current` and write each changed oauth credential back via
30
+ * `updateOAuthTokens`. Generic across providers — whatever Pi refreshed lands
31
+ * on disk, so the rotated token survives a gateway restart.
32
+ *
33
+ * Brigade previously used `AuthStorage.inMemory`, which kept refreshes only in
34
+ * the live process: after a restart it re-read the stale (rotated-out) on-disk
35
+ * refresh token, every turn 401'd, and the subscription login looked like the
36
+ * gateway "disconnecting" a day or two after onboarding. This backend closes
37
+ * that gap.
38
+ */
39
+ function persistentAuthBackend(agentId) {
40
+ const readCurrent = () => {
41
+ try {
42
+ return JSON.stringify(readBrigadeCredentials(agentId));
43
+ }
44
+ catch {
45
+ return "{}";
46
+ }
47
+ };
48
+ const persist = (next, current) => {
49
+ if (!next || next === current)
50
+ return;
51
+ let nextMap;
52
+ try {
53
+ nextMap = JSON.parse(next);
54
+ }
55
+ catch {
56
+ return;
57
+ }
58
+ let curMap = {};
59
+ try {
60
+ curMap = JSON.parse(current);
61
+ }
62
+ catch {
63
+ /* treat as empty — persist everything oauth in `next` */
64
+ }
65
+ for (const [provider, raw] of Object.entries(nextMap)) {
66
+ if (!raw || typeof raw !== "object")
67
+ continue;
68
+ const cred = raw;
69
+ if (cred.type !== "oauth")
70
+ continue; // only oauth creds refresh/rotate
71
+ // Only persist what actually changed — leave untouched providers alone.
72
+ if (JSON.stringify(curMap[provider]) === JSON.stringify(raw))
73
+ continue;
74
+ const { type: _type, access, refresh, expires, ...rest } = cred;
75
+ void _type;
76
+ try {
77
+ updateOAuthTokens(agentId, provider, {
78
+ access: typeof access === "string" ? access : undefined,
79
+ refresh: typeof refresh === "string" ? refresh : undefined,
80
+ expires: typeof expires === "number" ? expires : undefined,
81
+ metadata: Object.keys(rest).length > 0 ? rest : undefined,
82
+ });
83
+ }
84
+ catch {
85
+ /* best-effort — a write-back failure must never break the turn */
86
+ }
87
+ }
88
+ };
89
+ return {
90
+ withLock(fn) {
91
+ const current = readCurrent();
92
+ const out = fn(current);
93
+ try {
94
+ persist(out.next, current);
95
+ }
96
+ catch {
97
+ /* best-effort */
98
+ }
99
+ return out.result;
100
+ },
101
+ async withLockAsync(fn) {
102
+ const current = readCurrent();
103
+ const out = await fn(current);
104
+ try {
105
+ persist(out.next, current);
106
+ }
107
+ catch {
108
+ /* best-effort */
109
+ }
110
+ return out.result;
111
+ },
112
+ };
113
+ }
19
114
  /**
20
115
  * Build a Pi `AuthStorage` populated from Brigade's auth-profiles.json. Returns
21
116
  * an empty storage when the file is missing or unparseable so callers can
22
117
  * decide whether to surface "no key" themselves (chat re-onboards; gateway
23
118
  * throws a clean config error).
119
+ *
120
+ * Prefers a PERSISTENT backend (`fromStorage`) so Pi's OAuth refresh is written
121
+ * back to disk — see `persistentAuthBackend`. Falls back to `inMemory` only on a
122
+ * Pi build that lacks `fromStorage` (refreshes then live only for the process).
24
123
  */
25
124
  export function loadBrigadeAuthStorage(agentId = DEFAULT_AGENT_ID) {
26
- const credentials = readBrigadeCredentials(agentId);
27
125
  const Storage = AuthStorage;
28
- if (typeof Storage.inMemory === "function") {
29
- return Storage.inMemory(credentials);
30
- }
31
126
  if (typeof Storage.fromStorage === "function") {
32
- return Storage.fromStorage({
33
- withLock(update) {
34
- const { result } = update(JSON.stringify(credentials, null, 2));
35
- return result;
36
- },
37
- });
127
+ return Storage.fromStorage(persistentAuthBackend(agentId));
128
+ }
129
+ if (typeof Storage.inMemory === "function") {
130
+ return Storage.inMemory(readBrigadeCredentials(agentId));
38
131
  }
39
- throw new Error("Pi AuthStorage exposes neither inMemory nor fromStorage; pin to 0.70.x or update brigade.");
132
+ throw new Error("Pi AuthStorage exposes neither fromStorage nor inMemory; pin to 0.70.x or update brigade.");
40
133
  }
41
134
  export function readBrigadeCredentials(agentId) {
42
135
  const out = {};
@@ -1 +1 @@
1
- {"version":3,"file":"auth-bridge.js","sourceRoot":"","sources":["../../src/core/auth-bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-bridge.js","sourceRoot":"","sources":["../../src/core/auth-bridge.ts"],"names":[],"mappings":"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"}
@@ -1524,6 +1524,43 @@ async function continueBoot(args) {
1524
1524
  */
1525
1525
  const recentSystemEvents = new Map();
1526
1526
  const RECENT_SYSTEM_EVENTS_MAX = 30;
1527
+ // Cap how many transcript messages `resume` ships. A thread can grow to
1528
+ // thousands of messages; replaying ALL of them on every connect/reconnect/
1529
+ // resync would re-read + re-parse the whole JSONL synchronously and ship a
1530
+ // huge frame (risking the 32 MiB payload cap). Bound it to the recent tail —
1531
+ // the operator lands back in context without the cost scaling with thread
1532
+ // length. (Lazy-loading older history on scroll is a later enhancement.)
1533
+ const RESUME_TRANSCRIPT_MAX = 200;
1534
+ // Cap how many DISTINCT sessions we retain recovery state for. `seqCounters`
1535
+ // and `recentSystemEvents` would otherwise grow unbounded over a multi-day
1536
+ // daemon (every cron run, channel thread, sub-agent child key, and `/new`
1537
+ // mints a fresh key that never gets evicted). LRU-evict the coldest sessions
1538
+ // past this bound — safe because the durable transcript is the source of
1539
+ // truth: an evicted session simply rebuilds from disk on its next `resume`,
1540
+ // and a re-touched seq counter restarting at 0 only triggers a harmless
1541
+ // resync on any client still watching it.
1542
+ const RECOVERY_SESSION_MAX = 512;
1543
+ const evictColdRecoverySessions = () => {
1544
+ // JS Maps iterate in insertion order. The recentSystemEvents write below
1545
+ // moves a touched key to the end (delete+set), so its FIRST keys are the
1546
+ // least-recently-used; seqCounters evicts in creation order. Either way the
1547
+ // durable transcript is the source of truth, so eviction is safe — an
1548
+ // evicted session rebuilds from disk on its next `resume`, and a re-touched
1549
+ // seq counter restarting at 0 only makes a still-connected client issue one
1550
+ // harmless self-healing resync.
1551
+ while (recentSystemEvents.size > RECOVERY_SESSION_MAX) {
1552
+ const oldest = recentSystemEvents.keys().next().value;
1553
+ if (oldest === undefined)
1554
+ break;
1555
+ recentSystemEvents.delete(oldest);
1556
+ }
1557
+ while (seqCounters.size > RECOVERY_SESSION_MAX) {
1558
+ const oldest = seqCounters.keys().next().value;
1559
+ if (oldest === undefined)
1560
+ break;
1561
+ seqCounters.delete(oldest);
1562
+ }
1563
+ };
1527
1564
  /**
1528
1565
  * Process boot id (session generation / "epoch"). Constant for this gateway
1529
1566
  * process; a restart yields a new value. Advertised in `HelloOk` so a client
@@ -1586,13 +1623,21 @@ async function continueBoot(args) {
1586
1623
  event === "system-event";
1587
1624
  const seq = isOrderedFrame ? nextSeq(seqCounters, frameSessionId) : undefined;
1588
1625
  // Retain a bounded per-session tail of system-events for `resume` recovery.
1626
+ // delete+set moves this session to the end of the Map (LRU touch) so the
1627
+ // eviction sweep below drops the least-recently-active sessions first.
1589
1628
  if (event === "system-event" && frameSessionId) {
1590
1629
  const ring = recentSystemEvents.get(frameSessionId) ?? [];
1591
1630
  ring.push(payload);
1592
1631
  while (ring.length > RECENT_SYSTEM_EVENTS_MAX)
1593
1632
  ring.shift();
1633
+ recentSystemEvents.delete(frameSessionId);
1594
1634
  recentSystemEvents.set(frameSessionId, ring);
1595
1635
  }
1636
+ // Bound the recovery maps so a long-lived daemon that touches many
1637
+ // distinct session keys (cron runs, channel threads, sub-agent children,
1638
+ // `/new`) doesn't grow them without limit.
1639
+ if (isOrderedFrame && frameSessionId)
1640
+ evictColdRecoverySessions();
1596
1641
  const frame = seq !== undefined
1597
1642
  ? { type: "event", event, payload, seq }
1598
1643
  : { type: "event", event, payload };
@@ -2988,7 +3033,10 @@ async function continueBoot(args) {
2988
3033
  const p = (params ?? {});
2989
3034
  const targetAgentId = p.agentId?.trim() || agentId;
2990
3035
  const targetSessionKey = p.sessionKey?.trim() || defaultSessionKey(targetAgentId);
2991
- const messages = await readSessionTranscriptMessages({ sessionKey: targetSessionKey });
3036
+ const messages = await readSessionTranscriptMessages({
3037
+ sessionKey: targetSessionKey,
3038
+ limit: RESUME_TRANSCRIPT_MAX,
3039
+ });
2992
3040
  const headSeq = seqCounters.get(targetSessionKey) ?? 0;
2993
3041
  // Recovery for the two non-transcript event types so a (re)connecting
2994
3042
  // client loses NOTHING: tool-approval prompts still pending on this
@@ -3537,6 +3585,21 @@ async function continueBoot(args) {
3537
3585
  let configReadWarningSurfaced = false;
3538
3586
  const buildSessionsAccessCheck = () => {
3539
3587
  return ({ action, targetSessionKey }) => {
3588
+ // SAME-AGENT operator pass. The WS requester is the LOCAL OPERATOR
3589
+ // (localhost-bind + admin scope), anchored to the boot agent. The
3590
+ // operator owns EVERY session of their own agent, so any target under
3591
+ // that same agent passes — this guard's job is solely to refuse
3592
+ // CROSS-AGENT reach (gated below by visibility="all" + A2A policy).
3593
+ // Without this, the operator prompting a fresh same-agent thread
3594
+ // (`/new` → `agent:main:t-…`) or switching to any non-boot session of
3595
+ // their own agent was wrongly refused by the `visibility:"self"` rule
3596
+ // in `checkSessionToolAccess`, even though they plainly own it. The
3597
+ // agent's own `sessions_send` tool is unaffected — it calls
3598
+ // `checkSessionToolAccess` directly with the AGENT's session as the
3599
+ // requester, so its self/tree visibility still applies.
3600
+ if ((parseAgentSessionKey(targetSessionKey)?.agentId ?? agentId) === agentId) {
3601
+ return { allowed: true };
3602
+ }
3540
3603
  // Read the live config snapshot so `system.reload` that
3541
3604
  // tightens visibility/A2A takes effect on the very next RPC.
3542
3605
  // Sync `loadConfig()` would be ideal but the project's