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.
- package/README.md +8 -4
- package/package.json +1 -1
- package/src/agents/jules/tools/aidenid-email.js +7 -9
- package/src/agents/jules/tools/frontend-analyze.js +15 -3
- package/src/agents/jules/tools/runtime-audit.js +51 -21
- package/src/ai/aidenid.js +5 -4
- package/src/ai/proxy.js +15 -14
- package/src/audit/orchestrator.js +8 -5
- package/src/auth/admission-scope.js +5 -3
- package/src/auth/credential-destinations.js +221 -0
- package/src/auth/gate.js +4 -4
- package/src/auth/http.js +22 -5
- package/src/auth/service.js +52 -56
- package/src/cli.js +14 -6
- package/src/commands/ai/provision-governance.js +7 -6
- package/src/commands/init.js +25 -16
- package/src/commands/scan.js +6 -4
- package/src/commands/session.js +33 -45
- package/src/commands/spec.js +2 -2
- package/src/commands/watch.js +4 -3
- package/src/config/service.js +37 -0
- package/src/daemon/pulse.js +2 -1
- package/src/daemon/watchdog.js +117 -68
- package/src/legacy-cli.js +119 -39
- package/src/mcp/bridge-inputs.js +223 -0
- package/src/mcp/cli-command-tools.js +157 -7
- package/src/mcp/registry.js +2 -1
- package/src/mcp/session-stdio-server.js +114 -35
- package/src/mcp/smoke.js +27 -11
- package/src/mcp/token-service.js +7 -9
- package/src/memory/retrieval.js +14 -5
- package/src/pocket/ring-owner.js +13 -8
- package/src/review/ai-review.js +2 -2
- package/src/review/dd-report-email-client.js +4 -5
- package/src/session/admission-access.js +13 -10
- package/src/session/admission-auth.js +95 -16
- package/src/session/admission.js +44 -9
- package/src/session/checkpoints.js +11 -10
- package/src/session/file-locks.js +19 -18
- package/src/session/invitations.js +26 -19
- package/src/session/live-source.js +13 -13
- package/src/session/route-classes.js +107 -0
- package/src/session/sync.js +70 -56
- package/src/session/tickets.js +21 -19
- package/src/session/title-sync.js +4 -3
- package/src/telemetry/sync.js +8 -9
package/src/session/sync.js
CHANGED
|
@@ -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
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2860
|
+
if (!isAuthenticated(auth)) return { ok: false, reason: "not_authenticated" };
|
|
2846
2861
|
const base = `${resolveApiBaseUrl(auth)}/api/v1/sessions/${encodeURIComponent(sid)}`;
|
|
2847
|
-
const
|
|
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",
|
|
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({
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
3477
|
+
response = await credentialedRequest(
|
|
3478
|
+
await credentialFor(session),
|
|
3462
3479
|
endpoint,
|
|
3463
|
-
{
|
|
3464
|
-
|
|
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 {
|
package/src/session/tickets.js
CHANGED
|
@@ -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
|
|
74
|
-
return {
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
199
|
+
credential,
|
|
199
200
|
});
|
|
200
201
|
}
|
|
201
202
|
|
|
202
|
-
async function mutate(url, {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
85
|
+
credential: await credentialFor(session, { env }),
|
|
85
86
|
body: { title: normalizedTitle },
|
|
86
87
|
timeoutMs,
|
|
87
88
|
maxRetries,
|
package/src/telemetry/sync.js
CHANGED
|
@@ -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
|
|
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
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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++;
|