sentinelayer-cli 0.44.0 → 0.46.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.
Files changed (51) hide show
  1. package/README.md +10 -5
  2. package/package.json +1 -1
  3. package/src/agents/jules/tools/aidenid-email.js +7 -9
  4. package/src/agents/jules/tools/frontend-analyze.js +15 -3
  5. package/src/agents/jules/tools/runtime-audit.js +51 -21
  6. package/src/agents/shared-tools/shell.js +9 -0
  7. package/src/ai/aidenid.js +5 -4
  8. package/src/ai/proxy.js +15 -14
  9. package/src/audit/orchestrator.js +8 -5
  10. package/src/auth/admission-scope.js +5 -3
  11. package/src/auth/credential-destinations.js +221 -0
  12. package/src/auth/gate.js +4 -4
  13. package/src/auth/http.js +22 -5
  14. package/src/auth/service.js +52 -56
  15. package/src/cli.js +24 -6
  16. package/src/commands/ai/provision-governance.js +7 -6
  17. package/src/commands/init.js +25 -16
  18. package/src/commands/scan.js +6 -4
  19. package/src/commands/session.js +33 -45
  20. package/src/commands/spec.js +2 -2
  21. package/src/commands/watch.js +4 -3
  22. package/src/config/service.js +37 -0
  23. package/src/daemon/ast-parser-layer.js +8 -3
  24. package/src/daemon/callgraph-overlay.js +7 -2
  25. package/src/daemon/pulse.js +2 -1
  26. package/src/daemon/watchdog.js +117 -68
  27. package/src/legacy-cli.js +119 -39
  28. package/src/mcp/bridge-inputs.js +223 -0
  29. package/src/mcp/cli-command-tools.js +157 -7
  30. package/src/mcp/registry.js +2 -1
  31. package/src/mcp/session-stdio-server.js +114 -35
  32. package/src/mcp/smoke.js +27 -11
  33. package/src/mcp/token-service.js +7 -9
  34. package/src/memory/retrieval.js +14 -5
  35. package/src/pocket/ring-owner.js +13 -8
  36. package/src/program-lookup.js +54 -0
  37. package/src/review/ai-review.js +2 -2
  38. package/src/review/dd-report-email-client.js +4 -5
  39. package/src/review/local-review.js +11 -5
  40. package/src/session/admission-access.js +13 -10
  41. package/src/session/admission-auth.js +95 -16
  42. package/src/session/admission.js +44 -9
  43. package/src/session/checkpoints.js +11 -10
  44. package/src/session/file-locks.js +19 -18
  45. package/src/session/invitations.js +26 -19
  46. package/src/session/live-source.js +13 -13
  47. package/src/session/route-classes.js +107 -0
  48. package/src/session/sync.js +70 -56
  49. package/src/session/tickets.js +21 -19
  50. package/src/session/title-sync.js +4 -3
  51. package/src/telemetry/sync.js +8 -9
@@ -0,0 +1,107 @@
1
+ // route-classes.js -- the ONE classification of every local MCP session tool ("mcp:<tool>")
2
+ // and every `sl session` command the MCP CLI bridge can invoke ("cli:session <path>").
3
+ //
4
+ // In an agent context it decides what a route may do when the session it acts in stores NO
5
+ // admission on this machine (when the session stores one, every route binds to it or is
6
+ // refused; see runInSessionToolContext in src/mcp/session-stdio-server.js and
7
+ // assertDispatchMatchesScope in src/session/admission-auth.js):
8
+ //
9
+ // legacy-data-plane a compatibility list for rooms that don't require admission:
10
+ // posting, replying, reading/polling, message actions and reactions,
11
+ // attention requests. Runs as before; the server's admission mode
12
+ // decides whether a room accepts it.
13
+ // control leases, locks, guards, tickets, checkpoints, joining and leaving,
14
+ // listeners, room setup and everything else that is not plain
15
+ // data-plane traffic. Refused unless the session's admission binds it.
16
+ // owner room owner actions. Refused in an agent context.
17
+ // exempt-local sends no request (a local store or built-in constants only).
18
+ //
19
+ // A route missing from this table is treated as unclassified and refused like control. A
20
+ // unit test derives every MCP tool and every bridge-invokable command from the real handlers
21
+ // and command tree and fails when one is missing here.
22
+
23
+ export const SESSION_ROUTE_CLASSES = Object.freeze({
24
+ // ---- local MCP session tools
25
+ "mcp:poll_inbox": "legacy-data-plane",
26
+ "mcp:read_history": "legacy-data-plane",
27
+ "mcp:send_message": "legacy-data-plane",
28
+ "mcp:attention_request": "legacy-data-plane",
29
+ "mcp:session_action": "legacy-data-plane",
30
+ "mcp:session_react": "legacy-data-plane",
31
+ "mcp:session_reply": "legacy-data-plane",
32
+ "mcp:session_lock": "control",
33
+ "mcp:session_unlock": "control",
34
+ "mcp:session_locks": "control",
35
+ "mcp:memory.write": "exempt-local",
36
+ "mcp:memory.recall": "exempt-local",
37
+ "mcp:memory.summarize": "exempt-local",
38
+
39
+ // ---- `sl session` commands invokable through the MCP CLI bridge
40
+ "cli:session say": "legacy-data-plane",
41
+ "cli:session post-agent": "legacy-data-plane",
42
+ "cli:session observe": "legacy-data-plane",
43
+ "cli:session reply": "legacy-data-plane",
44
+ "cli:session comment": "legacy-data-plane",
45
+ "cli:session action": "legacy-data-plane",
46
+ "cli:session react": "legacy-data-plane",
47
+ "cli:session read": "legacy-data-plane",
48
+ "cli:session history": "legacy-data-plane",
49
+ "cli:session search": "legacy-data-plane",
50
+ "cli:session pins": "legacy-data-plane",
51
+ "cli:session lock": "control",
52
+ "cli:session unlock": "control",
53
+ "cli:session locks": "control",
54
+ "cli:session renew": "control",
55
+ "cli:session guard": "control",
56
+ "cli:session guard-hook": "control",
57
+ "cli:session guard-install": "control",
58
+ "cli:session guard-uninstall": "control",
59
+ "cli:session checkpoint create": "control",
60
+ "cli:session checkpoint generate": "control",
61
+ "cli:session checkpoint list": "control",
62
+ "cli:session checkpoint show": "control",
63
+ "cli:session ticket claim": "control",
64
+ "cli:session ticket events": "control",
65
+ "cli:session ticket list": "control",
66
+ "cli:session ticket release": "control",
67
+ "cli:session ticket renew": "control",
68
+ "cli:session ticket submit": "control",
69
+ "cli:session join": "control",
70
+ "cli:session leave": "control",
71
+ "cli:session stop-listener": "control",
72
+ "cli:session listeners": "control",
73
+ "cli:session set-title": "control",
74
+ "cli:session start": "control",
75
+ "cli:session ensure": "control",
76
+ "cli:session continue": "control",
77
+ "cli:session inject-guide": "control",
78
+ "cli:session setup-guides": "control",
79
+ "cli:session sync": "control",
80
+ "cli:session status": "control",
81
+ "cli:session list": "control",
82
+ "cli:session usage": "control",
83
+ "cli:session recall": "control",
84
+ "cli:session recap now": "control",
85
+ "cli:session edit": "control",
86
+ "cli:session wake codex": "control",
87
+ "cli:session wake codex-notify": "control",
88
+ "cli:session wake daemon": "control",
89
+ "cli:session access approve": "owner",
90
+ "cli:session access deny": "owner",
91
+ "cli:session access revoke": "owner",
92
+ "cli:session access mode": "owner",
93
+ "cli:session access request": "owner",
94
+ "cli:session access list": "owner",
95
+ "cli:session access status": "owner",
96
+ "cli:session actions": "exempt-local",
97
+ "cli:session templates": "exempt-local",
98
+ });
99
+
100
+ /** The compatibility list for rooms that don't require admission, as one named view of the table. */
101
+ export const LEGACY_DATA_PLANE_TOOLS = Object.freeze(
102
+ Object.keys(SESSION_ROUTE_CLASSES).filter((route) => SESSION_ROUTE_CLASSES[route] === "legacy-data-plane"),
103
+ );
104
+
105
+ export function sessionRouteClass(route) {
106
+ return SESSION_ROUTE_CLASSES[route] || "unclassified";
107
+ }
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import process from "node:process";
5
5
  import { randomUUID } from "node:crypto";
6
6
 
7
+ import { credentialedRequest, credentialFor, isAuthenticated } from "../auth/credential-destinations.js";
7
8
  import { resolveActiveAuthSession } from "../auth/service.js";
8
9
  import { createAgentEvent } from "../events/schema.js";
9
10
  import { isSessionControlEvent } from "./control-events.js";
@@ -378,7 +379,8 @@ function recordCircuitFailure(circuit, nowMs) {
378
379
 
379
380
  // 401 and 403 refuse one credential (an expired, revoked or narrower agent admission, or
380
381
  // an identity the account has not granted). They say nothing about API health, so they
381
- // never count toward the machine-wide outbound breaker; every other failure does.
382
+ // never count toward either machine-wide breaker (inbound or outbound): one agent's
383
+ // refusals must not pause another agent's traffic. Network errors, timeouts and 5xx do.
382
384
  function isCredentialRefusal(response) {
383
385
  return Boolean(response) && (response.status === 401 || response.status === 403);
384
386
  }
@@ -388,6 +390,11 @@ function recordOutboundFailure(response, nowMs) {
388
390
  recordCircuitFailure(outboundCircuit, nowMs);
389
391
  }
390
392
 
393
+ function recordInboundFailure(response, nowMs) {
394
+ if (isCredentialRefusal(response)) return;
395
+ recordCircuitFailure(inboundCircuit, nowMs);
396
+ }
397
+
391
398
  function recordCircuitSuccess(circuit) {
392
399
  if (!circuit) {
393
400
  return;
@@ -671,13 +678,21 @@ export async function fetchJsonWithFullTimeout(
671
678
  const controller = new AbortController();
672
679
  const startedAtMs = Date.now();
673
680
  let response = null;
681
+ // A credential (src/auth/credential-destinations.js) is sent only to its own origin, by
682
+ // credentialedRequest; fetchImpl is the transport that it calls.
683
+ const { credential = null, ...init } = options || {};
674
684
  const requestOptions = {
675
- ...options,
685
+ ...init,
676
686
  signal: combineAbortSignals([options?.signal, controller.signal]),
677
687
  };
688
+ const sent = credential
689
+ ? credentialedRequest(credential, url, requestOptions, {
690
+ fetchImpl: (target, request) => fetchImpl(target, request, resolvedTimeoutMs),
691
+ })
692
+ : fetchImpl(url, requestOptions, resolvedTimeoutMs);
678
693
 
679
694
  response = await withTimeout(
680
- fetchImpl(url, requestOptions, resolvedTimeoutMs),
695
+ sent,
681
696
  resolvedTimeoutMs,
682
697
  {
683
698
  reason: "request_timeout",
@@ -1003,7 +1018,7 @@ export async function syncSessionEventToApi(
1003
1018
  return { synced: false, reason: "no_session" };
1004
1019
  }
1005
1020
 
1006
- if (!session || !session.token) {
1021
+ if (!isAuthenticated(session)) {
1007
1022
  return { synced: false, reason: "not_authenticated" };
1008
1023
  }
1009
1024
 
@@ -1017,8 +1032,8 @@ export async function syncSessionEventToApi(
1017
1032
  method: "POST",
1018
1033
  headers: {
1019
1034
  "Content-Type": "application/json",
1020
- Authorization: `Bearer ${session.token}`,
1021
1035
  },
1036
+ credential: await credentialFor(session),
1022
1037
  body: requestBody,
1023
1038
  };
1024
1039
  const resolvedTimeoutMs = normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS);
@@ -1078,8 +1093,8 @@ export async function syncSessionEventToApi(
1078
1093
  method: "POST",
1079
1094
  headers: {
1080
1095
  "Content-Type": "application/json",
1081
- Authorization: `Bearer ${session.token}`,
1082
1096
  },
1097
+ credential: await credentialFor(session),
1083
1098
  body: JSON.stringify({
1084
1099
  agent_id: agentId,
1085
1100
  role: grantRole,
@@ -1201,7 +1216,7 @@ async function syncSessionAuxPayload(
1201
1216
  } catch {
1202
1217
  return { synced: false, reason: "no_session" };
1203
1218
  }
1204
- if (!session || !session.token) {
1219
+ if (!isAuthenticated(session)) {
1205
1220
  return { synced: false, reason: "not_authenticated" };
1206
1221
  }
1207
1222
 
@@ -1214,8 +1229,8 @@ async function syncSessionAuxPayload(
1214
1229
  method: "POST",
1215
1230
  headers: {
1216
1231
  "Content-Type": "application/json",
1217
- Authorization: `Bearer ${session.token}`,
1218
1232
  },
1233
+ credential: await credentialFor(session),
1219
1234
  body: JSON.stringify({
1220
1235
  ...payload,
1221
1236
  source: "cli",
@@ -1320,7 +1335,7 @@ export async function pollHumanMessages(
1320
1335
  cursor: normalizeString(since) || null,
1321
1336
  };
1322
1337
  }
1323
- if (!session || !session.token) {
1338
+ if (!isAuthenticated(session)) {
1324
1339
  return {
1325
1340
  ok: false,
1326
1341
  reason: "not_authenticated",
@@ -1344,14 +1359,14 @@ export async function pollHumanMessages(
1344
1359
  {
1345
1360
  method: "GET",
1346
1361
  headers: {
1347
- Authorization: `Bearer ${session.token}`,
1348
1362
  },
1363
+ credential: await credentialFor(session),
1349
1364
  },
1350
1365
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
1351
1366
  fetchImpl,
1352
1367
  );
1353
1368
  if (!response || !response.ok) {
1354
- recordCircuitFailure(inboundCircuit, normalizedNowMs);
1369
+ recordInboundFailure(response, normalizedNowMs);
1355
1370
  return {
1356
1371
  ok: false,
1357
1372
  reason: `api_${response ? response.status : "no_response"}`,
@@ -1487,7 +1502,7 @@ export async function pollSessionEvents(
1487
1502
  cursor: normalizeString(since) || null,
1488
1503
  };
1489
1504
  }
1490
- if (!session || !session.token) {
1505
+ if (!isAuthenticated(session)) {
1491
1506
  return {
1492
1507
  ok: false,
1493
1508
  reason: "not_authenticated",
@@ -1518,7 +1533,7 @@ export async function pollSessionEvents(
1518
1533
  endpoint,
1519
1534
  {
1520
1535
  method: "GET",
1521
- headers: { Authorization: `Bearer ${session.token}` },
1536
+ credential: await credentialFor(session),
1522
1537
  },
1523
1538
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
1524
1539
  fetchImpl,
@@ -1536,7 +1551,7 @@ export async function pollSessionEvents(
1536
1551
  cursor: normalizedSince || null,
1537
1552
  };
1538
1553
  }
1539
- recordCircuitFailure(inboundCircuit, normalizedNowMs);
1554
+ recordInboundFailure(response, normalizedNowMs);
1540
1555
  return {
1541
1556
  ok: false,
1542
1557
  reason: `api_${response ? response.status : "no_response"}`,
@@ -1655,7 +1670,7 @@ export async function fetchSessionPresence(
1655
1670
  present: [],
1656
1671
  };
1657
1672
  }
1658
- if (!session?.token) {
1673
+ if (!isAuthenticated(session)) {
1659
1674
  return {
1660
1675
  ok: false,
1661
1676
  reason: "not_authenticated",
@@ -1672,7 +1687,7 @@ export async function fetchSessionPresence(
1672
1687
  endpoint,
1673
1688
  {
1674
1689
  method: "GET",
1675
- headers: { Authorization: `Bearer ${session.token}` },
1690
+ credential: await credentialFor(session),
1676
1691
  },
1677
1692
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
1678
1693
  fetchImpl,
@@ -1698,7 +1713,7 @@ export async function fetchSessionPresence(
1698
1713
  present: [],
1699
1714
  };
1700
1715
  }
1701
- recordCircuitFailure(inboundCircuit, normalizedNowMs);
1716
+ recordInboundFailure(response, normalizedNowMs);
1702
1717
  return {
1703
1718
  ok: false,
1704
1719
  reason: `api_${response ? response.status : "no_response"}`,
@@ -1807,7 +1822,7 @@ export async function renewSessionPresence(
1807
1822
  recorded: false,
1808
1823
  };
1809
1824
  }
1810
- if (!session?.token) {
1825
+ if (!isAuthenticated(session)) {
1811
1826
  return {
1812
1827
  ok: false,
1813
1828
  reason: "not_authenticated",
@@ -1836,8 +1851,8 @@ export async function renewSessionPresence(
1836
1851
  method: "PUT",
1837
1852
  headers: {
1838
1853
  "Content-Type": "application/json",
1839
- Authorization: `Bearer ${session.token}`,
1840
1854
  },
1855
+ credential: await credentialFor(session),
1841
1856
  body: JSON.stringify(body),
1842
1857
  signal,
1843
1858
  },
@@ -1932,7 +1947,7 @@ export async function requestSessionListenerStop(
1932
1947
  } catch {
1933
1948
  return { ok: false, reason: "no_session", recorded: false };
1934
1949
  }
1935
- if (!session?.token) {
1950
+ if (!isAuthenticated(session)) {
1936
1951
  return { ok: false, reason: "not_authenticated", recorded: false };
1937
1952
  }
1938
1953
 
@@ -1952,9 +1967,9 @@ export async function requestSessionListenerStop(
1952
1967
  method: "POST",
1953
1968
  headers: {
1954
1969
  "Content-Type": "application/json",
1955
- Authorization: `Bearer ${session.token}`,
1956
1970
  "Idempotency-Key": normalizedIdempotencyKey,
1957
1971
  },
1972
+ credential: await credentialFor(session),
1958
1973
  body: JSON.stringify(body),
1959
1974
  },
1960
1975
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
@@ -2060,7 +2075,7 @@ export async function updateSessionReadCursor(
2060
2075
  } catch {
2061
2076
  return { ok: false, reason: "no_session", updated: false };
2062
2077
  }
2063
- if (!session?.token) {
2078
+ if (!isAuthenticated(session)) {
2064
2079
  return { ok: false, reason: "not_authenticated", updated: false };
2065
2080
  }
2066
2081
 
@@ -2081,8 +2096,8 @@ export async function updateSessionReadCursor(
2081
2096
  method: "PUT",
2082
2097
  headers: {
2083
2098
  "Content-Type": "application/json",
2084
- Authorization: `Bearer ${session.token}`,
2085
2099
  },
2100
+ credential: await credentialFor(session),
2086
2101
  body: JSON.stringify(body),
2087
2102
  signal,
2088
2103
  },
@@ -2194,7 +2209,7 @@ export async function streamSessionEvents(
2194
2209
  errorCount: 0,
2195
2210
  };
2196
2211
  }
2197
- if (!session || !session.token) {
2212
+ if (!isAuthenticated(session)) {
2198
2213
  return {
2199
2214
  ok: false,
2200
2215
  reason: "not_authenticated",
@@ -2244,18 +2259,18 @@ export async function streamSessionEvents(
2244
2259
 
2245
2260
  let response;
2246
2261
  try {
2247
- response = await fetchImpl(
2262
+ response = await credentialedRequest(
2263
+ await credentialFor(session),
2248
2264
  endpoint,
2249
2265
  {
2250
2266
  method: "GET",
2251
2267
  headers: {
2252
2268
  Accept: "text/event-stream",
2253
- Authorization: `Bearer ${session.token}`,
2254
2269
  ...(normalizedSince ? { "Last-Event-ID": normalizedSince } : {}),
2255
2270
  },
2256
2271
  signal: controller.signal,
2257
2272
  },
2258
- normalizedTimeoutMs
2273
+ { fetchImpl: (target, init) => fetchImpl(target, init, normalizedTimeoutMs) },
2259
2274
  );
2260
2275
  } catch (error) {
2261
2276
  clearTimeout(timeoutHandle);
@@ -2415,7 +2430,7 @@ export async function pollSessionEventsBefore(
2415
2430
  beforeSequence: null,
2416
2431
  };
2417
2432
  }
2418
- if (!session || !session.token) {
2433
+ if (!isAuthenticated(session)) {
2419
2434
  return {
2420
2435
  ok: false,
2421
2436
  reason: "not_authenticated",
@@ -2442,7 +2457,7 @@ export async function pollSessionEventsBefore(
2442
2457
  endpoint,
2443
2458
  {
2444
2459
  method: "GET",
2445
- headers: { Authorization: `Bearer ${session.token}` },
2460
+ credential: await credentialFor(session),
2446
2461
  },
2447
2462
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
2448
2463
  fetchImpl,
@@ -2460,7 +2475,7 @@ export async function pollSessionEventsBefore(
2460
2475
  beforeSequence: Number.isFinite(normalizedBeforeSequence) ? normalizedBeforeSequence : null,
2461
2476
  };
2462
2477
  }
2463
- recordCircuitFailure(inboundCircuit, normalizedNowMs);
2478
+ recordInboundFailure(response, normalizedNowMs);
2464
2479
  return {
2465
2480
  ok: false,
2466
2481
  reason: `api_${response ? response.status : "no_response"}`,
@@ -2538,7 +2553,7 @@ export async function listSessionMessageActions(
2538
2553
  } catch {
2539
2554
  return { ok: false, reason: "no_session", actions: [], count: 0, projection: null };
2540
2555
  }
2541
- if (!session || !session.token) {
2556
+ if (!isAuthenticated(session)) {
2542
2557
  return { ok: false, reason: "not_authenticated", actions: [], count: 0, projection: null };
2543
2558
  }
2544
2559
 
@@ -2563,13 +2578,13 @@ export async function listSessionMessageActions(
2563
2578
  endpoint,
2564
2579
  {
2565
2580
  method: "GET",
2566
- headers: { Authorization: `Bearer ${session.token}` },
2581
+ credential: await credentialFor(session),
2567
2582
  },
2568
2583
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
2569
2584
  fetchImpl,
2570
2585
  );
2571
2586
  if (!response || !response.ok) {
2572
- recordCircuitFailure(inboundCircuit, normalizedNowMs);
2587
+ recordInboundFailure(response, normalizedNowMs);
2573
2588
  return {
2574
2589
  ok: false,
2575
2590
  reason: `api_${response ? response.status : "no_response"}`,
@@ -2643,7 +2658,7 @@ export async function fetchSessionUsageLedger(
2643
2658
  } catch {
2644
2659
  return { ok: false, reason: "no_session", status: 0, payload: null };
2645
2660
  }
2646
- if (!session || !session.token) {
2661
+ if (!isAuthenticated(session)) {
2647
2662
  return { ok: false, reason: "not_authenticated", status: 0, payload: null };
2648
2663
  }
2649
2664
 
@@ -2660,13 +2675,13 @@ export async function fetchSessionUsageLedger(
2660
2675
  endpoint,
2661
2676
  {
2662
2677
  method: "GET",
2663
- headers: { Authorization: `Bearer ${session.token}` },
2678
+ credential: await credentialFor(session),
2664
2679
  },
2665
2680
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
2666
2681
  fetchImpl,
2667
2682
  );
2668
2683
  if (!response || !response.ok) {
2669
- recordCircuitFailure(inboundCircuit, normalizedNowMs);
2684
+ recordInboundFailure(response, normalizedNowMs);
2670
2685
  return {
2671
2686
  ok: false,
2672
2687
  reason: `api_${response ? response.status : "no_response"}`,
@@ -2842,9 +2857,9 @@ export async function editSessionMessage(sessionId, {
2842
2857
  let auth;
2843
2858
  try { auth = await resolveAuthSession({ cwd: targetPath, env: process.env, autoRotate: false }); }
2844
2859
  catch { return { ok: false, reason: "no_session" }; }
2845
- if (!auth?.token) return { ok: false, reason: "not_authenticated" };
2860
+ if (!isAuthenticated(auth)) return { ok: false, reason: "not_authenticated" };
2846
2861
  const base = `${resolveApiBaseUrl(auth)}/api/v1/sessions/${encodeURIComponent(sid)}`;
2847
- const headers = { Authorization: `Bearer ${auth.token}` };
2862
+ const credential = await credentialFor(auth);
2848
2863
  const failure = (response, payload) => {
2849
2864
  const code = normalizeString(payload?.detail?.code || payload?.error?.code || payload?.code);
2850
2865
  // Client/schema/ownership conflicts are terminal and must not poison unrelated writes.
@@ -2857,7 +2872,7 @@ export async function editSessionMessage(sessionId, {
2857
2872
  const resource = replyId ? `replies/${encodeURIComponent(replyId)}` : messageId
2858
2873
  ? `messages/${encodeURIComponent(messageId)}` : `messages/by-sequence/${sequence}`;
2859
2874
  const authorQuery = normalizeString(agentId) ? `?agentId=${encodeURIComponent(normalizeString(agentId))}` : "";
2860
- const { response, payload } = await fetchJsonWithFullTimeout(`${base}/${resource}${authorQuery}`, { method: "GET", headers, signal }, timeoutMs, fetchImpl, { readErrorBody: true });
2875
+ const { response, payload } = await fetchJsonWithFullTimeout(`${base}/${resource}${authorQuery}`, { method: "GET", credential, signal }, timeoutMs, fetchImpl, { readErrorBody: true });
2861
2876
  if (!response?.ok || payload?.ok === false) return failure(response, payload);
2862
2877
  const current = replyId ? payload?.reply : payload?.event;
2863
2878
  const fetchedRevision = Number(current?.messageRevision ?? 1);
@@ -2871,11 +2886,12 @@ export async function editSessionMessage(sessionId, {
2871
2886
  const { response, payload } = await fetchJsonWithFullTimeout(`${base}/${resource}`, {
2872
2887
  method: "PATCH",
2873
2888
  headers: {
2874
- ...createSessionMutationHeaders({ bearerToken: auth.token, sessionId: sid, routeId: replyId
2889
+ ...createSessionMutationHeaders({ credential, sessionId: sid, routeId: replyId
2875
2890
  ? "PATCH /api/v1/sessions/{session_id}/replies/{action_id}"
2876
2891
  : "PATCH /api/v1/sessions/{session_id}/messages/{message_id}", idempotencyKey: mutationKey }),
2877
2892
  "Content-Type": "application/json", "Idempotency-Key": mutationKey,
2878
2893
  },
2894
+ credential,
2879
2895
  body: JSON.stringify(body),
2880
2896
  signal,
2881
2897
  }, timeoutMs, fetchImpl, { readErrorBody: true });
@@ -2964,7 +2980,7 @@ export async function createSessionMessageAction(
2964
2980
  } catch {
2965
2981
  return { ok: false, reason: "no_session", action: null };
2966
2982
  }
2967
- if (!session || !session.token) {
2983
+ if (!isAuthenticated(session)) {
2968
2984
  return { ok: false, reason: "not_authenticated", action: null };
2969
2985
  }
2970
2986
 
@@ -3002,8 +3018,8 @@ export async function createSessionMessageAction(
3002
3018
  method: "POST",
3003
3019
  headers: {
3004
3020
  "Content-Type": "application/json",
3005
- Authorization: `Bearer ${session.token}`,
3006
3021
  },
3022
+ credential: await credentialFor(session),
3007
3023
  body: JSON.stringify(body),
3008
3024
  signal,
3009
3025
  },
@@ -3104,7 +3120,7 @@ export async function searchSessionEvents(
3104
3120
  nextBeforeSequence: null,
3105
3121
  };
3106
3122
  }
3107
- if (!session || !session.token) {
3123
+ if (!isAuthenticated(session)) {
3108
3124
  return {
3109
3125
  ok: false,
3110
3126
  reason: "not_authenticated",
@@ -3134,13 +3150,13 @@ export async function searchSessionEvents(
3134
3150
  endpoint,
3135
3151
  {
3136
3152
  method: "GET",
3137
- headers: { Authorization: `Bearer ${session.token}` },
3153
+ credential: await credentialFor(session),
3138
3154
  },
3139
3155
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
3140
3156
  fetchImpl,
3141
3157
  );
3142
3158
  if (!response || !response.ok) {
3143
- recordCircuitFailure(inboundCircuit, normalizedNowMs);
3159
+ recordInboundFailure(response, normalizedNowMs);
3144
3160
  return {
3145
3161
  ok: false,
3146
3162
  reason: `api_${response ? response.status : "no_response"}`,
@@ -3226,7 +3242,7 @@ export async function listSessionsFromApi({
3226
3242
  warnings: [],
3227
3243
  };
3228
3244
  }
3229
- if (!session || !session.token) {
3245
+ if (!isAuthenticated(session)) {
3230
3246
  return {
3231
3247
  ok: false,
3232
3248
  reason: "not_authenticated",
@@ -3262,7 +3278,7 @@ export async function listSessionsFromApi({
3262
3278
  endpoint,
3263
3279
  {
3264
3280
  method: "GET",
3265
- headers: { Authorization: `Bearer ${session.token}` },
3281
+ credential: await credentialFor(session),
3266
3282
  },
3267
3283
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
3268
3284
  fetchImpl,
@@ -3368,7 +3384,7 @@ export async function fetchSessionFromApi(
3368
3384
  } catch {
3369
3385
  return { ok: false, reason: "no_session", session: null };
3370
3386
  }
3371
- if (!session || !session.token) {
3387
+ if (!isAuthenticated(session)) {
3372
3388
  return { ok: false, reason: "not_authenticated", session: null, status: 401 };
3373
3389
  }
3374
3390
 
@@ -3382,7 +3398,7 @@ export async function fetchSessionFromApi(
3382
3398
  endpoint,
3383
3399
  {
3384
3400
  method: "GET",
3385
- headers: { Authorization: `Bearer ${session.token}` },
3401
+ credential: await credentialFor(session),
3386
3402
  },
3387
3403
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
3388
3404
  fetchImpl,
@@ -3447,7 +3463,7 @@ export async function probeSessionAccess(
3447
3463
  } catch {
3448
3464
  return { accessible: false, reason: "no_session" };
3449
3465
  }
3450
- if (!session || !session.token) {
3466
+ if (!isAuthenticated(session)) {
3451
3467
  return { accessible: false, reason: "not_authenticated" };
3452
3468
  }
3453
3469
 
@@ -3458,13 +3474,11 @@ export async function probeSessionAccess(
3458
3474
 
3459
3475
  let response;
3460
3476
  try {
3461
- response = await fetchImpl(
3477
+ response = await credentialedRequest(
3478
+ await credentialFor(session),
3462
3479
  endpoint,
3463
- {
3464
- method: "GET",
3465
- headers: { Authorization: `Bearer ${session.token}` },
3466
- },
3467
- normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
3480
+ { method: "GET" },
3481
+ { fetchImpl: (target, init) => fetchImpl(target, init, normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS)) },
3468
3482
  );
3469
3483
  } catch (err) {
3470
3484
  return {