sentinelayer-cli 0.44.0 → 0.45.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 (46) hide show
  1. package/README.md +8 -4
  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/ai/aidenid.js +5 -4
  7. package/src/ai/proxy.js +15 -14
  8. package/src/audit/orchestrator.js +8 -5
  9. package/src/auth/admission-scope.js +5 -3
  10. package/src/auth/credential-destinations.js +221 -0
  11. package/src/auth/gate.js +4 -4
  12. package/src/auth/http.js +22 -5
  13. package/src/auth/service.js +52 -56
  14. package/src/cli.js +14 -6
  15. package/src/commands/ai/provision-governance.js +7 -6
  16. package/src/commands/init.js +25 -16
  17. package/src/commands/scan.js +6 -4
  18. package/src/commands/session.js +33 -45
  19. package/src/commands/spec.js +2 -2
  20. package/src/commands/watch.js +4 -3
  21. package/src/config/service.js +37 -0
  22. package/src/daemon/pulse.js +2 -1
  23. package/src/daemon/watchdog.js +117 -68
  24. package/src/legacy-cli.js +119 -39
  25. package/src/mcp/bridge-inputs.js +223 -0
  26. package/src/mcp/cli-command-tools.js +157 -7
  27. package/src/mcp/registry.js +2 -1
  28. package/src/mcp/session-stdio-server.js +114 -35
  29. package/src/mcp/smoke.js +27 -11
  30. package/src/mcp/token-service.js +7 -9
  31. package/src/memory/retrieval.js +14 -5
  32. package/src/pocket/ring-owner.js +13 -8
  33. package/src/review/ai-review.js +2 -2
  34. package/src/review/dd-report-email-client.js +4 -5
  35. package/src/session/admission-access.js +13 -10
  36. package/src/session/admission-auth.js +95 -16
  37. package/src/session/admission.js +44 -9
  38. package/src/session/checkpoints.js +11 -10
  39. package/src/session/file-locks.js +19 -18
  40. package/src/session/invitations.js +26 -19
  41. package/src/session/live-source.js +13 -13
  42. package/src/session/route-classes.js +107 -0
  43. package/src/session/sync.js +70 -56
  44. package/src/session/tickets.js +21 -19
  45. package/src/session/title-sync.js +4 -3
  46. package/src/telemetry/sync.js +8 -9
@@ -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 {
@@ -19,6 +19,7 @@ import os from "node:os";
19
19
  import path from "node:path";
20
20
  import process from "node:process";
21
21
 
22
+ import { checkedTransport, credentialFor, isAuthenticated } from "../auth/credential-destinations.js";
22
23
  import { requestJson, requestJsonMutation } from "../auth/http.js";
23
24
  import { resolveActiveAuthSession } from "../auth/service.js";
24
25
 
@@ -70,8 +71,8 @@ async function writeState(filePath, state) {
70
71
 
71
72
  async function auth(targetPath, resolveAuthSession) {
72
73
  const session = await resolveAuthSession({ cwd: targetPath, env: process.env, autoRotate: false });
73
- if (!session?.token || !session?.apiUrl) throw new Error("Not authenticated for this session.");
74
- return { token: session.token, apiUrl: normalizeString(session.apiUrl).replace(/\/+$/, "") };
74
+ if (!isAuthenticated(session) || !session?.apiUrl) throw new Error("Not authenticated for this session.");
75
+ return { credential: await credentialFor(session), apiUrl: normalizeString(session.apiUrl).replace(/\/+$/, "") };
75
76
  }
76
77
 
77
78
  function ticketsUrl(apiUrl, sessionId, suffix = "") {
@@ -155,7 +156,7 @@ export async function listTickets(
155
156
  maxPages = SNAPSHOT_MAX_PAGES,
156
157
  } = {}
157
158
  ) {
158
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
159
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
159
160
  const items = [];
160
161
  let cursor = null;
161
162
  let afterId = null;
@@ -164,9 +165,9 @@ export async function listTickets(
164
165
  const query = new URLSearchParams({ limit: String(pageSize) });
165
166
  if (afterId) query.set("afterId", afterId);
166
167
  const body = validatedSnapshotPage(
167
- await requestRead(`${ticketsUrl(apiUrl, sessionId, "/snapshot")}?${query}`, {
168
+ await checkedTransport(requestRead)(`${ticketsUrl(apiUrl, sessionId, "/snapshot")}?${query}`, {
168
169
  method: "GET",
169
- headers: { Authorization: `Bearer ${token}` },
170
+ credential,
170
171
  }),
171
172
  { first: page === 0, afterId },
172
173
  );
@@ -192,19 +193,20 @@ export async function ticketEvents(
192
193
  sessionId,
193
194
  { after = 0, targetPath = process.cwd(), resolveAuthSession = resolveActiveAuthSession, requestRead = requestJson } = {}
194
195
  ) {
195
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
196
- return requestRead(`${ticketsUrl(apiUrl, sessionId, "/events")}?after=${encodeURIComponent(String(after))}`, {
196
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
197
+ return checkedTransport(requestRead)(`${ticketsUrl(apiUrl, sessionId, "/events")}?after=${encodeURIComponent(String(after))}`, {
197
198
  method: "GET",
198
- headers: { Authorization: `Bearer ${token}` },
199
+ credential,
199
200
  });
200
201
  }
201
202
 
202
- async function mutate(url, { token, key, operation, body, requestMutation }) {
203
- return requestMutation(url, {
203
+ async function mutate(url, { credential, key, operation, body, requestMutation }) {
204
+ return checkedTransport(requestMutation)(url, {
204
205
  method: "POST",
205
206
  operationName: `session.ticket_${operation}`,
206
207
  idempotencyKey: key,
207
- headers: { Authorization: `Bearer ${token}`, "Idempotency-Key": key },
208
+ credential,
209
+ headers: { "Idempotency-Key": key },
208
210
  body,
209
211
  });
210
212
  }
@@ -222,10 +224,10 @@ export async function claimTicket(
222
224
  } = {}
223
225
  ) {
224
226
  const statePath = ticketLeasePath(sessionId, ticketId, identity, { homeDir });
225
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
227
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
226
228
  const { key } = await operationKey(statePath, "claim", String(expectedVersion ?? "any"));
227
229
  const response = await mutate(ticketsUrl(apiUrl, sessionId, `/${encodeURIComponent(ticketId)}/claim`), {
228
- token, key, operation: "claim", requestMutation,
230
+ credential, key, operation: "claim", requestMutation,
229
231
  body: expectedVersion == null ? {} : { expectedVersion: Number(expectedVersion) },
230
232
  });
231
233
  // The lease is recorded here only if the server says it is held by the identity that
@@ -258,10 +260,10 @@ export async function renewTicketLease(
258
260
  ) {
259
261
  const statePath = ticketLeasePath(sessionId, ticketId, identity, { homeDir });
260
262
  const held = await heldLease(statePath);
261
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
263
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
262
264
  const { key } = await operationKey(statePath, "renew", `${held.leaseId}:${held.fence}:${held.ticketVersion}`);
263
265
  const response = await mutate(ticketsUrl(apiUrl, sessionId, `/${encodeURIComponent(ticketId)}/lease/renew`), {
264
- token, key, operation: "renew", requestMutation, body: { leaseId: held.leaseId, fence: held.fence },
266
+ credential, key, operation: "renew", requestMutation, body: { leaseId: held.leaseId, fence: held.fence },
265
267
  });
266
268
  await writeState(statePath, leaseState(response));
267
269
  return response;
@@ -274,10 +276,10 @@ export async function releaseTicketLease(
274
276
  ) {
275
277
  const statePath = ticketLeasePath(sessionId, ticketId, identity, { homeDir });
276
278
  const held = await heldLease(statePath);
277
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
279
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
278
280
  const { key } = await operationKey(statePath, "release", `${held.leaseId}:${held.fence}:${held.ticketVersion}`);
279
281
  const response = await mutate(ticketsUrl(apiUrl, sessionId, `/${encodeURIComponent(ticketId)}/lease/release`), {
280
- token, key, operation: "release", requestMutation,
282
+ credential, key, operation: "release", requestMutation,
281
283
  body: { leaseId: held.leaseId, fence: held.fence, expectedVersion: held.ticketVersion, ...(reason ? { reason } : {}) },
282
284
  });
283
285
  await fsp.rm(statePath, { force: true });
@@ -299,10 +301,10 @@ export async function submitTicket(
299
301
  ) {
300
302
  const statePath = ticketLeasePath(sessionId, ticketId, identity, { homeDir });
301
303
  const held = await heldLease(statePath);
302
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
304
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
303
305
  const { key } = await operationKey(statePath, "submit", `${held.leaseId}:${held.fence}:${held.ticketVersion}`);
304
306
  const response = await mutate(ticketsUrl(apiUrl, sessionId, `/${encodeURIComponent(ticketId)}/submit`), {
305
- token, key, operation: "submit", requestMutation,
307
+ credential, key, operation: "submit", requestMutation,
306
308
  body: {
307
309
  leaseId: held.leaseId,
308
310
  fence: held.fence,
@@ -1,5 +1,6 @@
1
1
  import process from "node:process";
2
2
 
3
+ import { checkedTransport, credentialFor, isAuthenticated } from "../auth/credential-destinations.js";
3
4
  import { SentinelayerApiError, requestJsonMutation } from "../auth/http.js";
4
5
  import { resolveActiveAuthSession } from "../auth/service.js";
5
6
  import { recordSessionRemoteTitleSync } from "./store.js";
@@ -66,7 +67,7 @@ export async function pushSessionTitleToApi(
66
67
  env,
67
68
  autoRotate,
68
69
  });
69
- if (!session?.token || !session?.apiUrl) {
70
+ if (!isAuthenticated(session) || !session?.apiUrl) {
70
71
  await recordRemoteTitleSync(normalizedSessionId, {
71
72
  targetPath,
72
73
  title: normalizedTitle,
@@ -76,12 +77,12 @@ export async function pushSessionTitleToApi(
76
77
  return { synced: false, reason: "not_authenticated" };
77
78
  }
78
79
  const apiUrl = String(session.apiUrl).replace(/\/+$/, "");
79
- const result = await requestMutation(
80
+ const result = await checkedTransport(requestMutation)(
80
81
  `${apiUrl}/api/v1/sessions/${encodeURIComponent(normalizedSessionId)}/title`,
81
82
  {
82
83
  method: "POST",
83
84
  operationName: "session.set_title",
84
- headers: { Authorization: `Bearer ${session.token}` },
85
+ credential: await credentialFor(session, { env }),
85
86
  body: { title: normalizedTitle },
86
87
  timeoutMs,
87
88
  maxRetries,
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { dirname, resolve } from "node:path";
6
+ import { credentialedRequest, credentialFor, isAuthenticated } from "../auth/credential-destinations.js";
6
7
  import { resolveActiveAuthSession } from "../auth/service.js";
7
8
 
8
9
  // Read CLI version from package.json at module load
@@ -71,7 +72,7 @@ export async function syncRunToDashboard(runData) {
71
72
  return { synced: false, reason: "no_session" };
72
73
  }
73
74
 
74
- if (!session || !session.token) {
75
+ if (!isAuthenticated(session)) {
75
76
  return { synced: false, reason: "not_authenticated" };
76
77
  }
77
78
 
@@ -121,14 +122,12 @@ export async function syncRunToDashboard(runData) {
121
122
  },
122
123
  };
123
124
 
124
- const response = await fetchWithTimeout(apiUrl + "/api/v1/telemetry", {
125
- method: "POST",
126
- headers: {
127
- "Content-Type": "application/json",
128
- Authorization: "Bearer " + session.token,
129
- },
130
- body: JSON.stringify(payload),
131
- }, SYNC_TIMEOUT_MS);
125
+ const response = await credentialedRequest(
126
+ await credentialFor(session),
127
+ apiUrl + "/api/v1/telemetry",
128
+ { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) },
129
+ { fetchImpl: (target, init) => fetchWithTimeout(target, init, SYNC_TIMEOUT_MS) },
130
+ );
132
131
 
133
132
  if (!response.ok) {
134
133
  consecutiveFailures++;