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.
Files changed (72) hide show
  1. package/README.md +16 -18
  2. package/package.json +7 -6
  3. package/src/agents/jules/config/definition.js +13 -62
  4. package/src/agents/jules/config/system-prompt.js +8 -1
  5. package/src/agents/jules/fix-cycle.js +12 -372
  6. package/src/agents/jules/loop.js +116 -26
  7. package/src/agents/jules/pulse.js +10 -327
  8. package/src/agents/jules/stream.js +13 -12
  9. package/src/agents/jules/swarm/orchestrator.js +3 -3
  10. package/src/agents/jules/swarm/sub-agent.js +6 -3
  11. package/src/agents/jules/tools/aidenid-email.js +189 -0
  12. package/src/agents/jules/tools/auth-audit.js +1187 -45
  13. package/src/agents/jules/tools/dispatch.js +25 -12
  14. package/src/agents/jules/tools/file-edit.js +2 -180
  15. package/src/agents/jules/tools/file-read.js +2 -100
  16. package/src/agents/jules/tools/glob.js +2 -168
  17. package/src/agents/jules/tools/grep.js +2 -228
  18. package/src/agents/jules/tools/path-guards.js +2 -161
  19. package/src/agents/jules/tools/runtime-audit.js +6 -2
  20. package/src/agents/jules/tools/shell.js +2 -383
  21. package/src/agents/persona-visuals.js +64 -0
  22. package/src/agents/shared-tools/dispatch-core.js +320 -0
  23. package/src/agents/shared-tools/file-edit.js +180 -0
  24. package/src/agents/shared-tools/file-read.js +100 -0
  25. package/src/agents/shared-tools/glob.js +168 -0
  26. package/src/agents/shared-tools/grep.js +228 -0
  27. package/src/agents/shared-tools/index.js +46 -0
  28. package/src/agents/shared-tools/path-guards.js +161 -0
  29. package/src/agents/shared-tools/shell.js +383 -0
  30. package/src/ai/aidenid.js +56 -7
  31. package/src/ai/client.js +45 -0
  32. package/src/ai/proxy.js +137 -0
  33. package/src/auth/gate.js +290 -16
  34. package/src/auth/http.js +450 -39
  35. package/src/auth/service.js +262 -47
  36. package/src/auth/session-store.js +475 -21
  37. package/src/cli.js +5 -0
  38. package/src/commands/audit.js +13 -8
  39. package/src/commands/auth.js +53 -9
  40. package/src/commands/omargate.js +10 -2
  41. package/src/commands/scan.js +10 -4
  42. package/src/commands/session.js +590 -0
  43. package/src/commands/spec.js +62 -0
  44. package/src/commands/watch.js +3 -2
  45. package/src/daemon/assignment-ledger.js +196 -0
  46. package/src/daemon/error-worker.js +599 -16
  47. package/src/daemon/fix-cycle.js +384 -0
  48. package/src/daemon/ingest-refresh.js +10 -9
  49. package/src/daemon/jira-lifecycle.js +135 -0
  50. package/src/daemon/pulse.js +327 -0
  51. package/src/daemon/scope-engine.js +1068 -0
  52. package/src/events/schema.js +190 -0
  53. package/src/interactive/index.js +18 -16
  54. package/src/legacy-cli.js +606 -37
  55. package/src/prompt/generator.js +19 -1
  56. package/src/review/ai-review.js +11 -1
  57. package/src/review/local-review.js +75 -19
  58. package/src/review/omargate-interactive.js +68 -0
  59. package/src/review/omargate-orchestrator.js +404 -0
  60. package/src/review/persona-prompts.js +296 -0
  61. package/src/review/scan-modes.js +48 -0
  62. package/src/scan/generator.js +1 -1
  63. package/src/session/agent-registry.js +352 -0
  64. package/src/session/daemon.js +801 -0
  65. package/src/session/paths.js +33 -0
  66. package/src/session/runtime-bridge.js +739 -0
  67. package/src/session/store.js +388 -0
  68. package/src/session/stream.js +325 -0
  69. package/src/spec/generator.js +100 -0
  70. package/src/telemetry/session-tracker.js +148 -32
  71. package/src/telemetry/sync.js +6 -2
  72. package/src/ui/command-hints.js +13 -0
@@ -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 requestJson(buildApiPath(apiUrl, "/api/v1/auth/cli/sessions/start"), {
130
- method: "POST",
131
- body: {
132
- challenge,
133
- ide: String(ide || DEFAULT_IDE_NAME),
134
- cli_version: String(cliVersion || "").trim() || null,
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 isTransientPollError = (error) =>
150
- error instanceof SentinelayerApiError &&
151
- (error.code === "NETWORK_ERROR" ||
152
- error.code === "TIMEOUT" ||
153
- error.status === 429 ||
154
- error.status >= 500);
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 requestJson(buildApiPath(apiUrl, "/api/v1/auth/cli/sessions/poll"), {
160
- method: "POST",
161
- body: {
162
- session_id: sessionId,
163
- challenge,
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 (isTransientPollError(error)) {
168
- await sleep(pollIntervalMs);
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
- await sleep(pollIntervalMs);
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 requestJson(buildApiPath(apiUrl, "/api/v1/auth/me"), {
202
- method: "GET",
203
- headers: toAuthHeader(token),
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 requestJson(buildApiPath(apiUrl, "/api/v1/auth/api-tokens"), {
217
- method: "POST",
218
- headers: toAuthHeader(authToken),
219
- body: {
220
- label: String(tokenLabel || "").trim() || defaultTokenLabel(),
221
- scope: "github_app_bridge",
222
- llm_credential_mode: "managed",
223
- expires_in_days: expiresInDays,
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 requestJson(buildApiPath(apiUrl, `/api/v1/auth/api-tokens/${encodeURIComponent(normalizedTokenId)}`), {
429
+ await requestJsonMutation(buildApiPath(apiUrl, `/api/v1/auth/api-tokens/${encodeURIComponent(normalizedTokenId)}`), {
234
430
  method: "DELETE",
235
- headers: toAuthHeader(authToken),
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
- // Ignore revoke failures; new token is already active.
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(approval.user || (await fetchCurrentUser({ apiUrl, token: approvalToken })));
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("No active auth token found. Run `sl auth login` first.", {
936
+ throw new SentinelayerApiError(`No active auth token found. Run \`${authLoginHint()}\` first.`, {
722
937
  status: 401,
723
938
  code: "AUTH_REQUIRED",
724
939
  });