sentinelayer-cli 0.4.5 → 0.8.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 +16 -18
- package/package.json +7 -6
- package/src/agents/jules/config/definition.js +13 -62
- package/src/agents/jules/config/system-prompt.js +8 -1
- package/src/agents/jules/fix-cycle.js +12 -372
- package/src/agents/jules/loop.js +116 -26
- package/src/agents/jules/pulse.js +10 -327
- package/src/agents/jules/stream.js +13 -12
- package/src/agents/jules/swarm/orchestrator.js +3 -3
- package/src/agents/jules/swarm/sub-agent.js +6 -3
- package/src/agents/jules/tools/aidenid-email.js +189 -0
- package/src/agents/jules/tools/auth-audit.js +1187 -45
- package/src/agents/jules/tools/dispatch.js +25 -12
- package/src/agents/jules/tools/file-edit.js +2 -180
- package/src/agents/jules/tools/file-read.js +2 -100
- package/src/agents/jules/tools/glob.js +2 -168
- package/src/agents/jules/tools/grep.js +2 -228
- package/src/agents/jules/tools/path-guards.js +2 -161
- package/src/agents/jules/tools/runtime-audit.js +6 -2
- package/src/agents/jules/tools/shell.js +2 -383
- package/src/agents/persona-visuals.js +64 -0
- package/src/agents/shared-tools/dispatch-core.js +320 -0
- package/src/agents/shared-tools/file-edit.js +180 -0
- package/src/agents/shared-tools/file-read.js +100 -0
- package/src/agents/shared-tools/glob.js +168 -0
- package/src/agents/shared-tools/grep.js +228 -0
- package/src/agents/shared-tools/index.js +46 -0
- package/src/agents/shared-tools/path-guards.js +161 -0
- package/src/agents/shared-tools/shell.js +383 -0
- package/src/ai/aidenid.js +56 -7
- package/src/ai/client.js +45 -0
- package/src/ai/proxy.js +137 -0
- package/src/auth/gate.js +290 -16
- package/src/auth/http.js +450 -39
- package/src/auth/service.js +262 -47
- package/src/auth/session-store.js +475 -21
- package/src/cli.js +5 -0
- package/src/commands/audit.js +13 -8
- package/src/commands/auth.js +53 -9
- package/src/commands/omargate.js +10 -2
- package/src/commands/scan.js +10 -4
- package/src/commands/session.js +590 -0
- package/src/commands/spec.js +62 -0
- package/src/commands/watch.js +3 -2
- package/src/daemon/assignment-ledger.js +196 -0
- package/src/daemon/error-worker.js +599 -16
- package/src/daemon/fix-cycle.js +384 -0
- package/src/daemon/ingest-refresh.js +10 -9
- package/src/daemon/jira-lifecycle.js +135 -0
- package/src/daemon/pulse.js +327 -0
- package/src/daemon/scope-engine.js +1068 -0
- package/src/events/schema.js +190 -0
- package/src/interactive/index.js +18 -16
- package/src/legacy-cli.js +606 -37
- package/src/prompt/generator.js +19 -1
- package/src/review/ai-review.js +11 -1
- package/src/review/local-review.js +75 -19
- package/src/review/omargate-interactive.js +68 -0
- package/src/review/omargate-orchestrator.js +404 -0
- package/src/review/persona-prompts.js +296 -0
- package/src/review/scan-modes.js +48 -0
- package/src/scan/generator.js +1 -1
- package/src/session/agent-registry.js +352 -0
- package/src/session/daemon.js +801 -0
- package/src/session/paths.js +33 -0
- package/src/session/runtime-bridge.js +739 -0
- package/src/session/store.js +388 -0
- package/src/session/stream.js +325 -0
- package/src/spec/generator.js +100 -0
- package/src/telemetry/session-tracker.js +148 -32
- package/src/telemetry/sync.js +6 -2
- package/src/ui/command-hints.js +13 -0
package/src/auth/service.js
CHANGED
|
@@ -5,13 +5,14 @@ import { setTimeout as sleep } from "node:timers/promises";
|
|
|
5
5
|
import open from "open";
|
|
6
6
|
|
|
7
7
|
import { loadConfig } from "../config/service.js";
|
|
8
|
-
import { SentinelayerApiError, requestJson } from "./http.js";
|
|
8
|
+
import { SentinelayerApiError, requestJson, requestJsonMutation } from "./http.js";
|
|
9
9
|
import {
|
|
10
10
|
clearStoredSession,
|
|
11
11
|
readStoredSession,
|
|
12
12
|
readStoredSessionMetadata,
|
|
13
13
|
writeStoredSession,
|
|
14
14
|
} from "./session-store.js";
|
|
15
|
+
import { authLoginHint } from "../ui/command-hints.js";
|
|
15
16
|
|
|
16
17
|
const DEFAULT_API_URL = "https://api.sentinelayer.com";
|
|
17
18
|
/** Default maximum wall-clock wait for browser-based CLI auth approval (ms). */
|
|
@@ -21,6 +22,9 @@ export const DEFAULT_API_TOKEN_TTL_DAYS = 365;
|
|
|
21
22
|
/** Default threshold at which stored tokens are rotated before expiry (days). */
|
|
22
23
|
export const DEFAULT_TOKEN_ROTATE_THRESHOLD_DAYS = 7;
|
|
23
24
|
const DEFAULT_IDE_NAME = "sl-cli";
|
|
25
|
+
const MAX_AUTH_POLL_REQUESTS = 600;
|
|
26
|
+
const MIN_TRANSIENT_POLL_DELAY_MS = 2_000;
|
|
27
|
+
const TRANSIENT_DELAY_THRESHOLD = 3;
|
|
24
28
|
|
|
25
29
|
function normalizeApiUrl(rawValue) {
|
|
26
30
|
const candidate = String(rawValue || "").trim() || DEFAULT_API_URL;
|
|
@@ -30,6 +34,11 @@ function normalizeApiUrl(rawValue) {
|
|
|
30
34
|
} catch {
|
|
31
35
|
throw new Error(`Invalid API URL '${candidate}'.`);
|
|
32
36
|
}
|
|
37
|
+
const hostname = String(parsed.hostname || "").toLowerCase();
|
|
38
|
+
const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1";
|
|
39
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && isLocalhost)) {
|
|
40
|
+
throw new Error(`API URL must use https (received '${candidate}').`);
|
|
41
|
+
}
|
|
33
42
|
parsed.pathname = "/";
|
|
34
43
|
parsed.search = "";
|
|
35
44
|
parsed.hash = "";
|
|
@@ -71,11 +80,71 @@ function generateChallenge() {
|
|
|
71
80
|
return crypto.randomBytes(48).toString("base64url");
|
|
72
81
|
}
|
|
73
82
|
|
|
83
|
+
function createFlowRequestId() {
|
|
84
|
+
try {
|
|
85
|
+
return crypto.randomUUID();
|
|
86
|
+
} catch {
|
|
87
|
+
return `flow-${Date.now().toString(36)}-${crypto.randomBytes(8).toString("hex")}`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function withFlowRequestHeaders(headers, flowRequestId) {
|
|
92
|
+
const merged = {
|
|
93
|
+
...(headers || {}),
|
|
94
|
+
"X-Request-Id": createFlowRequestId(),
|
|
95
|
+
};
|
|
96
|
+
if (flowRequestId) {
|
|
97
|
+
merged["X-Flow-Request-Id"] = flowRequestId;
|
|
98
|
+
}
|
|
99
|
+
return merged;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function requestAuthJson(flowRequestId, ...args) {
|
|
103
|
+
try {
|
|
104
|
+
return await requestJson(...args);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error instanceof SentinelayerApiError && !error.requestId && flowRequestId) {
|
|
107
|
+
error.requestId = flowRequestId;
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function requestAuthJsonMutation(flowRequestId, ...args) {
|
|
114
|
+
try {
|
|
115
|
+
return await requestJsonMutation(...args);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof SentinelayerApiError && !error.requestId && flowRequestId) {
|
|
118
|
+
error.requestId = flowRequestId;
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
74
124
|
function defaultTokenLabel() {
|
|
75
125
|
const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "").replace("T", "-");
|
|
76
126
|
return `sl-cli-session-${stamp}`;
|
|
77
127
|
}
|
|
78
128
|
|
|
129
|
+
function createIdempotencyKey(prefix) {
|
|
130
|
+
const normalizedPrefix = String(prefix || "sl-cli").trim() || "sl-cli";
|
|
131
|
+
let suffix;
|
|
132
|
+
try {
|
|
133
|
+
suffix = crypto.randomUUID();
|
|
134
|
+
} catch {
|
|
135
|
+
suffix = `${Date.now().toString(36)}-${crypto.randomBytes(8).toString("hex")}`;
|
|
136
|
+
}
|
|
137
|
+
return `${normalizedPrefix}-${suffix}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function computeDeterministicJitterFactor({ sessionId, attempt }) {
|
|
141
|
+
const seed = `${String(sessionId || "").trim()}|${Number(attempt) || 0}`;
|
|
142
|
+
const digest = crypto.createHash("sha256").update(seed).digest();
|
|
143
|
+
const raw = digest.readUInt32BE(0);
|
|
144
|
+
const ratio = raw / 0xffffffff;
|
|
145
|
+
return 0.85 + ratio * 0.3;
|
|
146
|
+
}
|
|
147
|
+
|
|
79
148
|
function isNearExpiry(tokenExpiresAt, thresholdDays) {
|
|
80
149
|
const normalized = String(tokenExpiresAt || "").trim();
|
|
81
150
|
if (!normalized) {
|
|
@@ -125,15 +194,21 @@ export async function resolveApiUrl({
|
|
|
125
194
|
return normalizeApiUrl(DEFAULT_API_URL);
|
|
126
195
|
}
|
|
127
196
|
|
|
128
|
-
async function startCliAuthSession({ apiUrl, challenge, ide, cliVersion }) {
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
197
|
+
async function startCliAuthSession({ apiUrl, challenge, ide, cliVersion, flowRequestId }) {
|
|
198
|
+
return requestAuthJsonMutation(
|
|
199
|
+
flowRequestId,
|
|
200
|
+
buildApiPath(apiUrl, "/api/v1/auth/cli/sessions/start"),
|
|
201
|
+
{
|
|
202
|
+
method: "POST",
|
|
203
|
+
operationName: "auth-start",
|
|
204
|
+
headers: withFlowRequestHeaders(null, flowRequestId),
|
|
205
|
+
body: {
|
|
206
|
+
challenge,
|
|
207
|
+
ide: String(ide || DEFAULT_IDE_NAME),
|
|
208
|
+
cli_version: String(cliVersion || "").trim() || null,
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
);
|
|
137
212
|
}
|
|
138
213
|
|
|
139
214
|
async function pollCliAuthSession({
|
|
@@ -142,30 +217,126 @@ async function pollCliAuthSession({
|
|
|
142
217
|
challenge,
|
|
143
218
|
timeoutMs,
|
|
144
219
|
pollIntervalSeconds,
|
|
220
|
+
flowRequestId,
|
|
145
221
|
}) {
|
|
146
222
|
const timeout = normalizePositiveNumber(timeoutMs, "timeoutMs", DEFAULT_AUTH_TIMEOUT_MS);
|
|
147
223
|
const pollIntervalMs = Math.max(250, Math.round(Number(pollIntervalSeconds || 2) * 1000));
|
|
224
|
+
const maxPollIntervalMs = Math.max(pollIntervalMs, Math.min(5_000, Math.floor(timeout / 4)));
|
|
225
|
+
const pollRequestTimeoutMs = Math.min(5_000, Math.max(1_000, pollIntervalMs));
|
|
226
|
+
const pollRequestMaxRetries = 1;
|
|
227
|
+
const pollRequestRetryDelayMs = 250;
|
|
228
|
+
let pollAttempt = 0;
|
|
229
|
+
const transientErrorBudget = Math.max(8, Math.ceil(timeout / maxPollIntervalMs) * 3);
|
|
230
|
+
const maxSleepBudgetMs = Math.max(2_000, Math.floor(timeout * 0.8));
|
|
231
|
+
const maxPollAttempts = Math.min(
|
|
232
|
+
MAX_AUTH_POLL_REQUESTS,
|
|
233
|
+
Math.max(1, Math.ceil(timeout / Math.max(250, pollIntervalMs)))
|
|
234
|
+
);
|
|
235
|
+
let rateLimitErrorCount = 0;
|
|
236
|
+
let serverErrorCount = 0;
|
|
237
|
+
let networkErrorCount = 0;
|
|
238
|
+
let transientBudgetUsed = 0;
|
|
239
|
+
let cumulativeSleepMs = 0;
|
|
240
|
+
let pollIterations = 0;
|
|
148
241
|
const deadline = Date.now() + timeout;
|
|
149
|
-
const
|
|
150
|
-
error instanceof SentinelayerApiError
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
242
|
+
const classifyPollError = (error) => {
|
|
243
|
+
if (!(error instanceof SentinelayerApiError)) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
if (error.status === 429) {
|
|
247
|
+
return "rate_limit";
|
|
248
|
+
}
|
|
249
|
+
if (error.status >= 500) {
|
|
250
|
+
return "server";
|
|
251
|
+
}
|
|
252
|
+
if (error.code === "NETWORK_ERROR" || error.code === "TIMEOUT") {
|
|
253
|
+
return "network";
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
};
|
|
257
|
+
const computePollDelayMs = (retryAfterMs = null) => {
|
|
258
|
+
const transientErrorCount = rateLimitErrorCount + serverErrorCount + networkErrorCount;
|
|
259
|
+
const minDelayMs =
|
|
260
|
+
transientErrorCount >= TRANSIENT_DELAY_THRESHOLD ? MIN_TRANSIENT_POLL_DELAY_MS : 250;
|
|
261
|
+
if (Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
|
|
262
|
+
return Math.max(minDelayMs, Math.min(maxPollIntervalMs, Math.round(retryAfterMs)));
|
|
263
|
+
}
|
|
264
|
+
const exponent = Math.min(6, pollAttempt);
|
|
265
|
+
const backoffMs = Math.min(maxPollIntervalMs, Math.round(pollIntervalMs * Math.pow(1.5, exponent)));
|
|
266
|
+
const jitterFactor = computeDeterministicJitterFactor({ sessionId, attempt: pollAttempt });
|
|
267
|
+
return Math.max(minDelayMs, Math.round(backoffMs * jitterFactor));
|
|
268
|
+
};
|
|
155
269
|
|
|
156
270
|
while (Date.now() < deadline) {
|
|
271
|
+
const remainingBeforeRequestMs = deadline - Date.now();
|
|
272
|
+
if (remainingBeforeRequestMs <= 250) {
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
pollIterations += 1;
|
|
276
|
+
if (pollIterations > maxPollAttempts) {
|
|
277
|
+
throw new SentinelayerApiError("CLI authentication polling exceeded attempt budget.", {
|
|
278
|
+
status: 408,
|
|
279
|
+
code: "CLI_AUTH_POLL_EXHAUSTED",
|
|
280
|
+
requestId: flowRequestId || null,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
157
283
|
let payload;
|
|
158
284
|
try {
|
|
159
|
-
payload = await
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
285
|
+
payload = await requestAuthJsonMutation(
|
|
286
|
+
flowRequestId,
|
|
287
|
+
buildApiPath(apiUrl, "/api/v1/auth/cli/sessions/poll"),
|
|
288
|
+
{
|
|
289
|
+
method: "POST",
|
|
290
|
+
operationName: "auth-poll",
|
|
291
|
+
headers: withFlowRequestHeaders(null, flowRequestId),
|
|
292
|
+
body: {
|
|
293
|
+
session_id: sessionId,
|
|
294
|
+
challenge,
|
|
295
|
+
},
|
|
296
|
+
timeoutMs: Math.max(250, Math.min(pollRequestTimeoutMs, remainingBeforeRequestMs)),
|
|
297
|
+
maxRetries: pollRequestMaxRetries,
|
|
298
|
+
retryDelayMs: pollRequestRetryDelayMs,
|
|
299
|
+
}
|
|
300
|
+
);
|
|
166
301
|
} catch (error) {
|
|
167
|
-
if (
|
|
168
|
-
|
|
302
|
+
if (error instanceof SentinelayerApiError && !error.requestId && flowRequestId) {
|
|
303
|
+
error.requestId = flowRequestId;
|
|
304
|
+
}
|
|
305
|
+
const classification = classifyPollError(error);
|
|
306
|
+
if (classification) {
|
|
307
|
+
const transientWeight = classification === "rate_limit" ? 3 : classification === "server" ? 2 : 1;
|
|
308
|
+
if (transientBudgetUsed + transientWeight > transientErrorBudget) {
|
|
309
|
+
throw new SentinelayerApiError("CLI authentication polling exhausted transient error budget.", {
|
|
310
|
+
status: 503,
|
|
311
|
+
code: "CLI_AUTH_TRANSIENT_BUDGET_EXHAUSTED",
|
|
312
|
+
requestId: error.requestId || flowRequestId || null,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
transientBudgetUsed += transientWeight;
|
|
316
|
+
if (classification === "rate_limit") {
|
|
317
|
+
rateLimitErrorCount += 1;
|
|
318
|
+
} else if (classification === "server") {
|
|
319
|
+
serverErrorCount += 1;
|
|
320
|
+
} else {
|
|
321
|
+
networkErrorCount += 1;
|
|
322
|
+
}
|
|
323
|
+
const retryAfterMs = Number.isFinite(error.retryAfterMs) ? error.retryAfterMs : null;
|
|
324
|
+
const delayMs = computePollDelayMs(retryAfterMs);
|
|
325
|
+
const remainingBeforeSleepMs = deadline - Date.now();
|
|
326
|
+
if (remainingBeforeSleepMs <= 250) {
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
const boundedDelayMs = Math.max(250, Math.min(delayMs, remainingBeforeSleepMs));
|
|
330
|
+
if (cumulativeSleepMs + boundedDelayMs > maxSleepBudgetMs) {
|
|
331
|
+
throw new SentinelayerApiError("CLI authentication polling exceeded retry sleep budget.", {
|
|
332
|
+
status: 503,
|
|
333
|
+
code: "CLI_AUTH_SLEEP_BUDGET_EXHAUSTED",
|
|
334
|
+
requestId: error.requestId || flowRequestId || null,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
cumulativeSleepMs += boundedDelayMs;
|
|
338
|
+
pollAttempt += 1;
|
|
339
|
+
await sleep(boundedDelayMs);
|
|
169
340
|
continue;
|
|
170
341
|
}
|
|
171
342
|
throw error;
|
|
@@ -179,29 +350,43 @@ async function pollCliAuthSession({
|
|
|
179
350
|
throw new SentinelayerApiError("CLI authentication was not approved.", {
|
|
180
351
|
status: 401,
|
|
181
352
|
code: "CLI_AUTH_REJECTED",
|
|
353
|
+
requestId: flowRequestId || null,
|
|
182
354
|
});
|
|
183
355
|
}
|
|
184
356
|
if (status === "expired") {
|
|
185
357
|
throw new SentinelayerApiError("CLI authentication session expired.", {
|
|
186
358
|
status: 401,
|
|
187
359
|
code: "CLI_AUTH_EXPIRED",
|
|
360
|
+
requestId: flowRequestId || null,
|
|
188
361
|
});
|
|
189
362
|
}
|
|
190
363
|
|
|
191
|
-
|
|
364
|
+
const delayMs = computePollDelayMs();
|
|
365
|
+
const remainingBeforeSleepMs = deadline - Date.now();
|
|
366
|
+
if (remainingBeforeSleepMs <= 250) {
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
const boundedDelayMs = Math.max(250, Math.min(delayMs, remainingBeforeSleepMs));
|
|
370
|
+
pollAttempt += 1;
|
|
371
|
+
await sleep(boundedDelayMs);
|
|
192
372
|
}
|
|
193
373
|
|
|
194
374
|
throw new SentinelayerApiError("CLI authentication timed out. Restart and try again.", {
|
|
195
375
|
status: 408,
|
|
196
376
|
code: "CLI_AUTH_TIMEOUT",
|
|
377
|
+
requestId: flowRequestId || null,
|
|
197
378
|
});
|
|
198
379
|
}
|
|
199
380
|
|
|
200
|
-
async function fetchCurrentUser({ apiUrl, token }) {
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
381
|
+
async function fetchCurrentUser({ apiUrl, token, flowRequestId }) {
|
|
382
|
+
return requestAuthJson(
|
|
383
|
+
flowRequestId,
|
|
384
|
+
buildApiPath(apiUrl, "/api/v1/auth/me"),
|
|
385
|
+
{
|
|
386
|
+
method: "GET",
|
|
387
|
+
headers: withFlowRequestHeaders(toAuthHeader(token), flowRequestId),
|
|
388
|
+
}
|
|
389
|
+
);
|
|
205
390
|
}
|
|
206
391
|
|
|
207
392
|
async function issueApiToken({
|
|
@@ -209,20 +394,31 @@ async function issueApiToken({
|
|
|
209
394
|
authToken,
|
|
210
395
|
tokenLabel,
|
|
211
396
|
tokenTtlDays,
|
|
397
|
+
flowRequestId,
|
|
212
398
|
}) {
|
|
213
399
|
const expiresInDays = Math.round(
|
|
214
400
|
normalizePositiveNumber(tokenTtlDays, "apiTokenTtlDays", DEFAULT_API_TOKEN_TTL_DAYS)
|
|
215
401
|
);
|
|
216
|
-
return
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
402
|
+
return requestAuthJsonMutation(
|
|
403
|
+
flowRequestId,
|
|
404
|
+
buildApiPath(apiUrl, "/api/v1/auth/api-tokens"),
|
|
405
|
+
{
|
|
406
|
+
method: "POST",
|
|
407
|
+
operationName: "issue-token",
|
|
408
|
+
headers: withFlowRequestHeaders(
|
|
409
|
+
{
|
|
410
|
+
...toAuthHeader(authToken),
|
|
411
|
+
},
|
|
412
|
+
flowRequestId
|
|
413
|
+
),
|
|
414
|
+
body: {
|
|
415
|
+
label: String(tokenLabel || "").trim() || defaultTokenLabel(),
|
|
416
|
+
scope: "cli",
|
|
417
|
+
llm_credential_mode: "managed",
|
|
418
|
+
expires_in_days: expiresInDays,
|
|
419
|
+
},
|
|
420
|
+
}
|
|
421
|
+
);
|
|
226
422
|
}
|
|
227
423
|
|
|
228
424
|
async function revokeApiToken({ apiUrl, authToken, tokenId }) {
|
|
@@ -230,9 +426,12 @@ async function revokeApiToken({ apiUrl, authToken, tokenId }) {
|
|
|
230
426
|
if (!normalizedTokenId) {
|
|
231
427
|
return false;
|
|
232
428
|
}
|
|
233
|
-
await
|
|
429
|
+
await requestJsonMutation(buildApiPath(apiUrl, `/api/v1/auth/api-tokens/${encodeURIComponent(normalizedTokenId)}`), {
|
|
234
430
|
method: "DELETE",
|
|
235
|
-
|
|
431
|
+
operationName: "revoke-token",
|
|
432
|
+
headers: {
|
|
433
|
+
...toAuthHeader(authToken),
|
|
434
|
+
},
|
|
236
435
|
});
|
|
237
436
|
return true;
|
|
238
437
|
}
|
|
@@ -270,6 +469,7 @@ async function rotateStoredApiTokenIfNeeded({
|
|
|
270
469
|
{ homeDir }
|
|
271
470
|
);
|
|
272
471
|
|
|
472
|
+
let revokeError = null;
|
|
273
473
|
if (session.tokenId) {
|
|
274
474
|
try {
|
|
275
475
|
await revokeApiToken({
|
|
@@ -277,14 +477,15 @@ async function rotateStoredApiTokenIfNeeded({
|
|
|
277
477
|
authToken: nextSession.token,
|
|
278
478
|
tokenId: session.tokenId,
|
|
279
479
|
});
|
|
280
|
-
} catch {
|
|
281
|
-
|
|
480
|
+
} catch (error) {
|
|
481
|
+
revokeError = error;
|
|
282
482
|
}
|
|
283
483
|
}
|
|
284
484
|
|
|
285
485
|
return {
|
|
286
486
|
session: nextSession,
|
|
287
487
|
rotated: true,
|
|
488
|
+
revokeError,
|
|
288
489
|
};
|
|
289
490
|
}
|
|
290
491
|
|
|
@@ -335,11 +536,13 @@ export async function loginAndPersistSession({
|
|
|
335
536
|
} = {}) {
|
|
336
537
|
const apiUrl = await resolveApiUrl({ cwd, env, explicitApiUrl, homeDir });
|
|
337
538
|
const challenge = generateChallenge();
|
|
539
|
+
const flowRequestId = createFlowRequestId();
|
|
338
540
|
const session = await startCliAuthSession({
|
|
339
541
|
apiUrl,
|
|
340
542
|
challenge,
|
|
341
543
|
ide,
|
|
342
544
|
cliVersion,
|
|
545
|
+
flowRequestId,
|
|
343
546
|
});
|
|
344
547
|
|
|
345
548
|
const authorizeUrl = String(session.authorize_url || "").trim();
|
|
@@ -359,6 +562,7 @@ export async function loginAndPersistSession({
|
|
|
359
562
|
challenge,
|
|
360
563
|
timeoutMs,
|
|
361
564
|
pollIntervalSeconds: Number(session.poll_interval_seconds || 2),
|
|
565
|
+
flowRequestId,
|
|
362
566
|
});
|
|
363
567
|
|
|
364
568
|
const approvalToken = String(approval.auth_token || "").trim();
|
|
@@ -366,15 +570,19 @@ export async function loginAndPersistSession({
|
|
|
366
570
|
throw new SentinelayerApiError("Authentication completed but no auth token was returned.", {
|
|
367
571
|
status: 503,
|
|
368
572
|
code: "CLI_AUTH_MISSING_TOKEN",
|
|
573
|
+
requestId: flowRequestId || null,
|
|
369
574
|
});
|
|
370
575
|
}
|
|
371
576
|
|
|
372
|
-
const user = normalizeUser(
|
|
577
|
+
const user = normalizeUser(
|
|
578
|
+
approval.user || (await fetchCurrentUser({ apiUrl, token: approvalToken, flowRequestId }))
|
|
579
|
+
);
|
|
373
580
|
const issuedApiToken = await issueApiToken({
|
|
374
581
|
apiUrl,
|
|
375
582
|
authToken: approvalToken,
|
|
376
583
|
tokenLabel,
|
|
377
584
|
tokenTtlDays,
|
|
585
|
+
flowRequestId,
|
|
378
586
|
});
|
|
379
587
|
|
|
380
588
|
// Extract AIdenID metadata from approval (no secret stored locally)
|
|
@@ -413,6 +621,7 @@ export async function loginAndPersistSession({
|
|
|
413
621
|
storage: stored.storage,
|
|
414
622
|
filePath: stored.filePath,
|
|
415
623
|
aidenid: stored.aidenid || null,
|
|
624
|
+
flowRequestId,
|
|
416
625
|
};
|
|
417
626
|
}
|
|
418
627
|
|
|
@@ -513,6 +722,12 @@ export async function resolveActiveAuthSession({
|
|
|
513
722
|
});
|
|
514
723
|
active = rotateResult.session;
|
|
515
724
|
rotated = rotateResult.rotated;
|
|
725
|
+
if (rotateResult.revokeError) {
|
|
726
|
+
console.warn(
|
|
727
|
+
"Sentinelayer token rotation succeeded but previous token revocation failed. " +
|
|
728
|
+
"Revoke the old token manually in the dashboard if needed."
|
|
729
|
+
);
|
|
730
|
+
}
|
|
516
731
|
} catch {
|
|
517
732
|
// Keep existing token if rotation fails.
|
|
518
733
|
active = stored;
|
|
@@ -718,7 +933,7 @@ export async function revokeAuthToken({
|
|
|
718
933
|
homeDir,
|
|
719
934
|
});
|
|
720
935
|
if (!active || !active.token) {
|
|
721
|
-
throw new SentinelayerApiError(
|
|
936
|
+
throw new SentinelayerApiError(`No active auth token found. Run \`${authLoginHint()}\` first.`, {
|
|
722
937
|
status: 401,
|
|
723
938
|
code: "AUTH_REQUIRED",
|
|
724
939
|
});
|