@spinabot/brigade 1.15.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.
- package/dist/auth/profiles.d.ts +24 -0
- package/dist/auth/profiles.d.ts.map +1 -1
- package/dist/auth/profiles.js +44 -0
- package/dist/auth/profiles.js.map +1 -1
- package/dist/buildstamp.json +1 -1
- package/dist/cli/commands/connect.d.ts.map +1 -1
- package/dist/cli/commands/connect.js +101 -12
- package/dist/cli/commands/connect.js.map +1 -1
- package/dist/cli/commands/gateway.d.ts.map +1 -1
- package/dist/cli/commands/gateway.js +40 -2
- package/dist/cli/commands/gateway.js.map +1 -1
- package/dist/core/auth-bridge.d.ts +4 -0
- package/dist/core/auth-bridge.d.ts.map +1 -1
- package/dist/core/auth-bridge.js +105 -12
- package/dist/core/auth-bridge.js.map +1 -1
- package/dist/core/server.js +64 -1
- package/dist/core/server.js.map +1 -1
- package/dist/core/ws-subscription-filter.d.ts.map +1 -1
- package/dist/core/ws-subscription-filter.js +13 -2
- package/dist/core/ws-subscription-filter.js.map +1 -1
- package/package.json +1 -1
package/dist/core/auth-bridge.js
CHANGED
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-bridge.js","sourceRoot":"","sources":["../../src/core/auth-bridge.ts"],"names":[],"mappings":"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"}
|
package/dist/core/server.js
CHANGED
|
@@ -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({
|
|
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
|