sentinelayer-cli 0.34.3 → 0.34.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentinelayer-cli",
3
- "version": "0.34.3",
3
+ "version": "0.34.5",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -783,9 +783,14 @@ function mergeSessionActionEvents(events = [], actionEvents = []) {
783
783
  return dedupeSessionEvents([...(Array.isArray(events) ? events : []), ...actionEvents])
784
784
  .map((event, index) => ({ event, index }))
785
785
  .sort((left, right) => {
786
+ const leftSequence = eventSequenceNumber(left.event);
787
+ const rightSequence = eventSequenceNumber(right.event);
788
+ if (leftSequence > 0 && rightSequence > 0 && leftSequence !== rightSequence) {
789
+ return leftSequence - rightSequence;
790
+ }
786
791
  const timeDiff = eventTimestampMs(left.event) - eventTimestampMs(right.event);
787
792
  if (timeDiff !== 0) return timeDiff;
788
- const sequenceDiff = eventSequenceNumber(left.event) - eventSequenceNumber(right.event);
793
+ const sequenceDiff = leftSequence - rightSequence;
789
794
  if (sequenceDiff !== 0) return sequenceDiff;
790
795
  return left.index - right.index;
791
796
  })
@@ -5574,6 +5579,9 @@ export function registerSessionCommand(program) {
5574
5579
  const rawActionEvents = remoteActions?.ok
5575
5580
  ? buildSessionActionEvents(normalizedSessionId, remoteActions.actions)
5576
5581
  : [];
5582
+ const visibleActionEvents = includeControlEvents
5583
+ ? rawActionEvents
5584
+ : rawActionEvents.filter((event) => !isSessionControlEvent(event));
5577
5585
  const unacknowledgedHumanMessages = remoteActions?.ok
5578
5586
  ? readProjectionUnacknowledgedHumanMessages(remoteActions)
5579
5587
  : [];
@@ -5593,8 +5601,8 @@ export function registerSessionCommand(program) {
5593
5601
  : sourceEvents.filter((event) => !isSessionControlEvent(event));
5594
5602
  const hiddenControlEventCount = sourceEvents.length - transcriptEvents.length;
5595
5603
  const actionEvents = remoteWindowRead
5596
- ? filterSessionActionEventsForTranscriptWindow(rawActionEvents, transcriptEvents)
5597
- : rawActionEvents;
5604
+ ? filterSessionActionEventsForTranscriptWindow(visibleActionEvents, transcriptEvents)
5605
+ : visibleActionEvents;
5598
5606
  const events = mergeSessionActionEvents(transcriptEvents, actionEvents).slice(-tail);
5599
5607
  const autoView = recordSessionReadViews(normalizedSessionId, events, {
5600
5608
  targetPath,
@@ -6245,15 +6253,19 @@ export function registerSessionCommand(program) {
6245
6253
  limit: 5_000,
6246
6254
  }),
6247
6255
  ]);
6248
- const actionEvents = remoteActions?.ok
6256
+ const includeControlEvents = Boolean(options.includeControlEvents);
6257
+ const rawActionEvents = remoteActions?.ok
6249
6258
  ? buildSessionActionEvents(normalizedSessionId, remoteActions.actions)
6250
6259
  : [];
6251
- const includeControlEvents = Boolean(options.includeControlEvents);
6260
+ const actionEvents = includeControlEvents
6261
+ ? rawActionEvents
6262
+ : rawActionEvents.filter((event) => !isSessionControlEvent(event));
6252
6263
  const dedupedEvents = dedupeSessionEvents(events);
6253
6264
  const transcriptBaseEvents = includeControlEvents
6254
6265
  ? dedupedEvents
6255
6266
  : filterSessionMaterialEvents(dedupedEvents);
6256
- const hiddenControlEventCount = dedupedEvents.length - transcriptBaseEvents.length;
6267
+ const hiddenControlEventCount =
6268
+ dedupedEvents.length - transcriptBaseEvents.length + rawActionEvents.length - actionEvents.length;
6257
6269
  const exportEvents = mergeSessionActionEvents(transcriptBaseEvents, actionEvents);
6258
6270
  const stats = computeTranscriptStats({
6259
6271
  sessionMeta: sessionPayload,
@@ -6400,15 +6412,19 @@ export function registerSessionCommand(program) {
6400
6412
  listAgents(normalizedSessionId, { targetPath, includeInactive: true }),
6401
6413
  readStream(normalizedSessionId, { targetPath, tail: 0 }),
6402
6414
  ]);
6403
- const actionEvents = remoteActions?.ok
6415
+ const includeControlEvents = Boolean(options.includeControlEvents);
6416
+ const rawActionEvents = remoteActions?.ok
6404
6417
  ? buildSessionActionEvents(normalizedSessionId, remoteActions.actions)
6405
6418
  : [];
6406
- const includeControlEvents = Boolean(options.includeControlEvents);
6419
+ const actionEvents = includeControlEvents
6420
+ ? rawActionEvents
6421
+ : rawActionEvents.filter((event) => !isSessionControlEvent(event));
6407
6422
  const dedupedEvents = dedupeSessionEvents(events);
6408
6423
  const transcriptBaseEvents = includeControlEvents
6409
6424
  ? dedupedEvents
6410
6425
  : filterSessionMaterialEvents(dedupedEvents);
6411
- const hiddenControlEventCount = dedupedEvents.length - transcriptBaseEvents.length;
6426
+ const hiddenControlEventCount =
6427
+ dedupedEvents.length - transcriptBaseEvents.length + rawActionEvents.length - actionEvents.length;
6412
6428
  const transcriptEvents = mergeSessionActionEvents(transcriptBaseEvents, actionEvents);
6413
6429
 
6414
6430
  // Pull GitHub/Google avatar + display name from the active auth
package/src/legacy-cli.js CHANGED
@@ -2520,16 +2520,22 @@ jobs:
2520
2520
  uses: mrrCarter/sentinelayer-v1-action@03d7369cba7de2e9f15b959275c982111f0ee493
2521
2521
  with:
2522
2522
  github_token: \${{ github.token }}
2523
+ openai_api_key: \${{ secrets.OPENAI_API_KEY }}
2524
+ google_api_key: \${{ secrets.GOOGLE_API_KEY }}
2525
+ llm_provider: \${{ secrets.GOOGLE_API_KEY != '' && 'google' || 'openai' }}
2523
2526
  sentinelayer_token: \${{ secrets.${normalizedSecret} }}${specIdBindingLine}
2524
- sentinelayer_managed_llm: "true"
2527
+ sentinelayer_managed_llm: \${{ secrets.GOOGLE_API_KEY == '' && secrets.OPENAI_API_KEY == '' && secrets.${normalizedSecret} != '' }}
2525
2528
  scan_mode: \${{ github.event_name == 'workflow_dispatch' && inputs.scan_mode || 'deep' }}
2526
2529
  severity_gate: \${{ github.event_name == 'workflow_dispatch' && inputs.severity_gate || 'P1' }}
2527
- model: gpt-5.3-codex
2530
+ model: \${{ secrets.GOOGLE_API_KEY != '' && 'gemini-2.5-pro' || 'gpt-5.3-codex' }}
2528
2531
  codex_model: gpt-5.3-codex
2529
- model_fallback: gpt-4.1-mini
2530
- use_codex: "true"
2532
+ model_fallback: \${{ secrets.GOOGLE_API_KEY != '' && 'gemini-2.5-flash' || 'gpt-4.1-mini' }}
2533
+ use_codex: \${{ secrets.GOOGLE_API_KEY == '' }}
2531
2534
  codex_only: "false"
2532
2535
  llm_failure_policy: block
2536
+ max_daily_scans: \${{ vars.OMAR_MAX_DAILY_SCANS || '200' }}
2537
+ min_scan_interval_minutes: \${{ vars.OMAR_MIN_SCAN_INTERVAL_MINUTES || '0' }}
2538
+ rate_limit_fail_mode: closed
2533
2539
  - name: Enforce Omar reviewer merge thresholds
2534
2540
  shell: bash
2535
2541
  env:
@@ -2575,6 +2581,12 @@ jobs:
2575
2581
  OMAR_SCAN_MODE: \${{ github.event_name == 'workflow_dispatch' && inputs.scan_mode || 'deep' }}
2576
2582
  OMAR_SEVERITY_GATE: \${{ github.event_name == 'workflow_dispatch' && inputs.severity_gate || 'P1' }}
2577
2583
  OMAR_P2_MAX_ALLOWED: \${{ github.event_name == 'workflow_dispatch' && inputs.p2_max_allowed || '5' }}
2584
+ OMAR_OPENAI_KEY_PRESENT: \${{ secrets.OPENAI_API_KEY != '' }}
2585
+ OMAR_GOOGLE_KEY_PRESENT: \${{ secrets.GOOGLE_API_KEY != '' }}
2586
+ OMAR_MANAGED_LLM: \${{ secrets.GOOGLE_API_KEY == '' && secrets.OPENAI_API_KEY == '' && secrets.${normalizedSecret} != '' }}
2587
+ OMAR_LLM_PROVIDER: \${{ secrets.GOOGLE_API_KEY != '' && 'google' || 'openai' }}
2588
+ OMAR_MODEL: \${{ secrets.GOOGLE_API_KEY != '' && 'gemini-2.5-pro' || 'gpt-5.3-codex' }}
2589
+ OMAR_MODEL_FALLBACK: \${{ secrets.GOOGLE_API_KEY != '' && 'gemini-2.5-flash' || 'gpt-4.1-mini' }}
2578
2590
  run: |
2579
2591
  set -euo pipefail
2580
2592
  mkdir -p omar-artifacts
@@ -2590,6 +2602,9 @@ jobs:
2590
2602
  value = env(name, "0").strip()
2591
2603
  return int(value) if value.isdigit() else 0
2592
2604
 
2605
+ def bool_env(name):
2606
+ return env(name).strip().lower() == "true"
2607
+
2593
2608
  github_run_id = env("GITHUB_RUN_ID")
2594
2609
  server_url = env("GITHUB_SERVER_URL", "https://github.com").rstrip("/")
2595
2610
  repository = env("GITHUB_REPOSITORY")
@@ -2612,7 +2627,13 @@ jobs:
2612
2627
  "scan": {
2613
2628
  "mode": env("OMAR_SCAN_MODE", "deep"),
2614
2629
  "action_ref": "mrrCarter/sentinelayer-v1-action@03d7369cba7de2e9f15b959275c982111f0ee493",
2615
- "managed_llm": True,
2630
+ "llm_provider": env("OMAR_LLM_PROVIDER", "openai"),
2631
+ "model": env("OMAR_MODEL", "gpt-5.3-codex"),
2632
+ "model_fallback": env("OMAR_MODEL_FALLBACK", "gpt-4.1-mini"),
2633
+ "llm_route": "google_api_key" if bool_env("OMAR_GOOGLE_KEY_PRESENT") else ("openai_api_key" if bool_env("OMAR_OPENAI_KEY_PRESENT") else "sentinelayer_managed"),
2634
+ "google_key_present": bool_env("OMAR_GOOGLE_KEY_PRESENT"),
2635
+ "openai_key_present": bool_env("OMAR_OPENAI_KEY_PRESENT"),
2636
+ "managed_llm": bool_env("OMAR_MANAGED_LLM"),
2616
2637
  "llm_failure_policy": "block",
2617
2638
  },
2618
2639
  "github": {
@@ -95,6 +95,7 @@ const REVIEW_RULES = Object.freeze([
95
95
  regex: /\b(sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,})\b/,
96
96
  },
97
97
  {
98
+ id: "SL-SEC-005",
98
99
  severity: "P2",
99
100
  message: "Possible hardcoded credential literal.",
100
101
  regex: /(api[_-]?key|secret|token)\s*[:=]\s*['\"][^'\"]{20,}['\"]/i,
@@ -336,6 +337,16 @@ function regexMatches(regex, input) {
336
337
  return new RegExp(regex.source, flags).test(input);
337
338
  }
338
339
 
340
+ function isGitHubActionsContextExpressionLiteral(line) {
341
+ return /(?:[:=]\s*)['"]?\s*\$\{\{\s*(?:github|secrets|vars|env|inputs|steps|needs|matrix|runner|strategy)\.[^}]+\}\}\s*['"]?/i.test(
342
+ String(line || "")
343
+ );
344
+ }
345
+
346
+ function shouldSuppressRuleLine(rule, line) {
347
+ return rule.id === "SL-SEC-005" && isGitHubActionsContextExpressionLiteral(line);
348
+ }
349
+
339
350
  function sanitizeLineForExcerpt(line) {
340
351
  return String(line || "")
341
352
  .trim()
@@ -590,6 +601,9 @@ async function scanRulesForFiles({ rootPath, filePaths, rules, maxFindings = MAX
590
601
  if (!regexMatches(rule.regex, line)) {
591
602
  continue;
592
603
  }
604
+ if (shouldSuppressRuleLine(rule, line)) {
605
+ continue;
606
+ }
593
607
  const pushed = tryPushFinding(
594
608
  findings,
595
609
  createFinding({
@@ -226,16 +226,22 @@ export function buildSecurityReviewWorkflow({ secretName = DEFAULT_SCAN_SECRET_N
226
226
  uses: SENTINELAYER_ACTION_REF,
227
227
  with: {
228
228
  github_token: "${{ github.token }}",
229
+ openai_api_key: "${{ secrets.OPENAI_API_KEY }}",
230
+ google_api_key: "${{ secrets.GOOGLE_API_KEY }}",
231
+ llm_provider: "${{ secrets.GOOGLE_API_KEY != '' && 'google' || 'openai' }}",
229
232
  sentinelayer_token: `\${{ secrets.${normalizedSecret} }}`,
230
- sentinelayer_managed_llm: "true",
233
+ sentinelayer_managed_llm: `\${{ secrets.GOOGLE_API_KEY == '' && secrets.OPENAI_API_KEY == '' && secrets.${normalizedSecret} != '' }}`,
231
234
  scan_mode: profile.scanMode || "deep",
232
235
  severity_gate: profile.severityGate || "P1",
233
- model: "gpt-5.3-codex",
236
+ model: "${{ secrets.GOOGLE_API_KEY != '' && 'gemini-2.5-pro' || 'gpt-5.3-codex' }}",
234
237
  codex_model: "gpt-5.3-codex",
235
- model_fallback: "gpt-4.1-mini",
236
- use_codex: "true",
238
+ model_fallback: "${{ secrets.GOOGLE_API_KEY != '' && 'gemini-2.5-flash' || 'gpt-4.1-mini' }}",
239
+ use_codex: "${{ secrets.GOOGLE_API_KEY == '' }}",
237
240
  codex_only: "false",
238
241
  llm_failure_policy: "block",
242
+ max_daily_scans: "${{ vars.OMAR_MAX_DAILY_SCANS || '200' }}",
243
+ min_scan_interval_minutes: "${{ vars.OMAR_MIN_SCAN_INTERVAL_MINUTES || '0' }}",
244
+ rate_limit_fail_mode: "closed",
239
245
  playwright_mode: profile.playwrightMode || "off",
240
246
  sbom_mode: profile.sbomMode || "off",
241
247
  wait_for_completion: "true",
@@ -288,6 +294,12 @@ export function buildSecurityReviewWorkflow({ secretName = DEFAULT_SCAN_SECRET_N
288
294
  OMAR_SCAN_MODE: "${{ github.event_name == 'workflow_dispatch' && inputs.scan_mode || 'deep' }}",
289
295
  OMAR_SEVERITY_GATE: "${{ github.event_name == 'workflow_dispatch' && inputs.severity_gate || 'P1' }}",
290
296
  OMAR_P2_MAX_ALLOWED: "${{ github.event_name == 'workflow_dispatch' && inputs.p2_max_allowed || '5' }}",
297
+ OMAR_OPENAI_KEY_PRESENT: "${{ secrets.OPENAI_API_KEY != '' }}",
298
+ OMAR_GOOGLE_KEY_PRESENT: "${{ secrets.GOOGLE_API_KEY != '' }}",
299
+ OMAR_MANAGED_LLM: `\${{ secrets.GOOGLE_API_KEY == '' && secrets.OPENAI_API_KEY == '' && secrets.${normalizedSecret} != '' }}`,
300
+ OMAR_LLM_PROVIDER: "${{ secrets.GOOGLE_API_KEY != '' && 'google' || 'openai' }}",
301
+ OMAR_MODEL: "${{ secrets.GOOGLE_API_KEY != '' && 'gemini-2.5-pro' || 'gpt-5.3-codex' }}",
302
+ OMAR_MODEL_FALLBACK: "${{ secrets.GOOGLE_API_KEY != '' && 'gemini-2.5-flash' || 'gpt-4.1-mini' }}",
291
303
  },
292
304
  run: [
293
305
  "set -euo pipefail",
@@ -304,6 +316,9 @@ export function buildSecurityReviewWorkflow({ secretName = DEFAULT_SCAN_SECRET_N
304
316
  " value = env(name, '0').strip()",
305
317
  " return int(value) if value.isdigit() else 0",
306
318
  "",
319
+ "def bool_env(name):",
320
+ " return env(name).strip().lower() == 'true'",
321
+ "",
307
322
  "github_run_id = env('GITHUB_RUN_ID')",
308
323
  "server_url = env('GITHUB_SERVER_URL', 'https://github.com').rstrip('/')",
309
324
  "repository = env('GITHUB_REPOSITORY')",
@@ -326,7 +341,13 @@ export function buildSecurityReviewWorkflow({ secretName = DEFAULT_SCAN_SECRET_N
326
341
  " 'scan': {",
327
342
  " 'mode': env('OMAR_SCAN_MODE', 'deep'),",
328
343
  ` 'action_ref': '${SENTINELAYER_ACTION_REF}',`,
329
- " 'managed_llm': True,",
344
+ " 'llm_provider': env('OMAR_LLM_PROVIDER', 'openai'),",
345
+ " 'model': env('OMAR_MODEL', 'gpt-5.3-codex'),",
346
+ " 'model_fallback': env('OMAR_MODEL_FALLBACK', 'gpt-4.1-mini'),",
347
+ " 'llm_route': 'google_api_key' if bool_env('OMAR_GOOGLE_KEY_PRESENT') else ('openai_api_key' if bool_env('OMAR_OPENAI_KEY_PRESENT') else 'sentinelayer_managed'),",
348
+ " 'google_key_present': bool_env('OMAR_GOOGLE_KEY_PRESENT'),",
349
+ " 'openai_key_present': bool_env('OMAR_OPENAI_KEY_PRESENT'),",
350
+ " 'managed_llm': bool_env('OMAR_MANAGED_LLM'),",
330
351
  " 'llm_failure_policy': 'block',",
331
352
  " },",
332
353
  " 'github': {",
@@ -1,8 +1,20 @@
1
1
  const SESSION_CONTROL_EVENT_TYPES = new Set([
2
+ "file_lock",
3
+ "file_unlock",
4
+ "file_lock_expired",
2
5
  "listener_stop",
3
6
  "session_coaching",
4
7
  "session_listen_catchup",
5
8
  "session_listen_error",
9
+ "session_reaction",
10
+ ]);
11
+
12
+ const QUIET_SESSION_ACTION_TYPES = new Set([
13
+ "ack",
14
+ "dislike",
15
+ "disregard",
16
+ "like",
17
+ "view",
6
18
  ]);
7
19
 
8
20
  function normalizeString(value) {
@@ -28,8 +40,13 @@ export function isSessionListenerLifecycleEvent(event = {}) {
28
40
 
29
41
  export function isSessionControlEvent(event = {}) {
30
42
  const type = eventType(event);
43
+ const payload = event?.payload && typeof event.payload === "object" && !Array.isArray(event.payload)
44
+ ? event.payload
45
+ : {};
46
+ const actionType = normalizeString(payload.actionType || payload.action_type || payload.reaction).toLowerCase();
31
47
  return (
32
48
  isSessionListenerLifecycleEvent(event) ||
49
+ (type === "session_action" && QUIET_SESSION_ACTION_TYPES.has(actionType)) ||
33
50
  type.startsWith("session_listen_") ||
34
51
  SESSION_CONTROL_EVENT_TYPES.has(type)
35
52
  );
@@ -253,9 +253,14 @@ function chronologicalSessionEvents(events = []) {
253
253
  return (Array.isArray(events) ? events : [])
254
254
  .map((event, index) => ({ event, index }))
255
255
  .sort((left, right) => {
256
+ const leftSequence = eventSequenceNumber(left.event);
257
+ const rightSequence = eventSequenceNumber(right.event);
258
+ if (leftSequence > 0 && rightSequence > 0 && leftSequence !== rightSequence) {
259
+ return leftSequence - rightSequence;
260
+ }
256
261
  const timeDiff = eventTimestampMs(left.event) - eventTimestampMs(right.event);
257
262
  if (timeDiff !== 0) return timeDiff;
258
- const sequenceDiff = eventSequenceNumber(left.event) - eventSequenceNumber(right.event);
263
+ const sequenceDiff = leftSequence - rightSequence;
259
264
  if (sequenceDiff !== 0) return sequenceDiff;
260
265
  return left.index - right.index;
261
266
  })