sentinelayer-cli 0.36.0 → 0.36.1

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.36.0",
3
+ "version": "0.36.1",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -64,7 +64,7 @@ import {
64
64
  refreshSessionCacheForRemoteActivity,
65
65
  updateSessionTitle,
66
66
  } from "../session/store.js";
67
- import { fetchSessionListeners, formatListenerLine } from "../session/listeners.js";
67
+ import { fetchSessionListeners, formatListenerLine, summarizeListeners } from "../session/listeners.js";
68
68
  import {
69
69
  getListenerProcessStatus,
70
70
  removeListenerPidRecord,
@@ -1082,6 +1082,25 @@ function compareIsoDesc(left = "", right = "") {
1082
1082
  return normalizeString(right).localeCompare(normalizeString(left));
1083
1083
  }
1084
1084
 
1085
+ function latestIsoTimestamp(...values) {
1086
+ let selected = "";
1087
+ let selectedEpoch = Number.NEGATIVE_INFINITY;
1088
+ for (const value of values) {
1089
+ const normalized = normalizeString(value);
1090
+ if (!normalized) continue;
1091
+ const epoch = Date.parse(normalized);
1092
+ if (!Number.isFinite(epoch)) {
1093
+ if (!selected) selected = normalized;
1094
+ continue;
1095
+ }
1096
+ if (epoch >= selectedEpoch) {
1097
+ selected = new Date(epoch).toISOString();
1098
+ selectedEpoch = epoch;
1099
+ }
1100
+ }
1101
+ return selected;
1102
+ }
1103
+
1085
1104
  function buildSessionParticipants({ statsAgents = [], registeredAgents = [] } = {}) {
1086
1105
  const byAgentId = new Map();
1087
1106
  for (const agent of Array.isArray(statsAgents) ? statsAgents : []) {
@@ -1100,6 +1119,12 @@ function buildSessionParticipants({ statsAgents = [], registeredAgents = [] } =
1100
1119
  if (!agentId) continue;
1101
1120
  const existing = byAgentId.get(agentId);
1102
1121
  if (existing) {
1122
+ const lastActivityAt = latestIsoTimestamp(
1123
+ existing.lastActivityAt,
1124
+ existing.lastSeen,
1125
+ agent.lastActivityAt,
1126
+ agent.joinedAt,
1127
+ );
1103
1128
  byAgentId.set(agentId, {
1104
1129
  ...existing,
1105
1130
  model: existing.model || normalizeString(agent.model),
@@ -1108,7 +1133,8 @@ function buildSessionParticipants({ statsAgents = [], registeredAgents = [] } =
1108
1133
  registered: true,
1109
1134
  source: "events+registry",
1110
1135
  joinedAt: normalizeString(agent.joinedAt) || existing.joinedAt,
1111
- lastActivityAt: normalizeString(agent.lastActivityAt) || existing.lastActivityAt,
1136
+ lastSeen: latestIsoTimestamp(existing.lastSeen, lastActivityAt) || existing.lastSeen,
1137
+ lastActivityAt: lastActivityAt || null,
1112
1138
  active: agent.active,
1113
1139
  });
1114
1140
  continue;
@@ -6285,13 +6311,51 @@ export function registerSessionCommand(program) {
6285
6311
  ]);
6286
6312
 
6287
6313
  const staleAgents = detectStaleAgents(agents, {});
6314
+ const staleAgentIds = new Set(staleAgents.map((agent) => normalizeString(agent.agentId)));
6315
+ const activeAgents = agents.filter((agent) => !staleAgentIds.has(normalizeString(agent.agentId)));
6316
+ let listenerRows = summarizeListeners(recentEvents);
6317
+ let listenerPresence = {
6318
+ source: "local_recent_events",
6319
+ ok: true,
6320
+ count: listenerRows.length,
6321
+ liveCount: listenerRows.filter((row) => row.status === "active" || row.status === "idle").length,
6322
+ listeners: listenerRows,
6323
+ };
6324
+ try {
6325
+ const remoteListeners = await fetchSessionListeners(normalizedSessionId, {
6326
+ targetPath,
6327
+ limit: 200,
6328
+ maxPages: 5,
6329
+ });
6330
+ if (remoteListeners?.ok && Array.isArray(remoteListeners.listeners) && remoteListeners.listeners.length > 0) {
6331
+ listenerRows = remoteListeners.listeners;
6332
+ listenerPresence = {
6333
+ source: "remote_events",
6334
+ ok: true,
6335
+ count: listenerRows.length,
6336
+ liveCount: listenerRows.filter((row) => row.status === "active" || row.status === "idle").length,
6337
+ pageCount: Number(remoteListeners.pageCount || 0),
6338
+ scannedEventCount: Number(remoteListeners.scannedEventCount || 0),
6339
+ listenerEventCount: Number(remoteListeners.listenerEventCount || 0),
6340
+ partial: Boolean(remoteListeners.partial),
6341
+ listeners: listenerRows,
6342
+ };
6343
+ } else if (remoteListeners && remoteListeners.ok === false) {
6344
+ listenerPresence.remoteReason = normalizeString(remoteListeners.reason) || "remote_unavailable";
6345
+ }
6346
+ } catch (error) {
6347
+ listenerPresence.remoteReason = normalizeString(error?.message) || "remote_unavailable";
6348
+ }
6349
+ const liveListenerCount = listenerPresence.liveCount;
6288
6350
  const payload = {
6289
6351
  command: "session status",
6290
6352
  targetPath,
6291
6353
  sessionId: normalizedSessionId,
6292
6354
  session: sessionPayload,
6293
- activeAgents: agents,
6355
+ registeredAgents: agents,
6356
+ activeAgents,
6294
6357
  staleAgents,
6358
+ listenerPresence,
6295
6359
  runtimeRuns,
6296
6360
  activeLeases: leases.assignments,
6297
6361
  activeFileLocks: fileLocks,
@@ -6306,7 +6370,7 @@ export function registerSessionCommand(program) {
6306
6370
  console.log(pc.bold(`Session ${normalizedSessionId}`));
6307
6371
  console.log(
6308
6372
  pc.gray(
6309
- `status=${sessionPayload.status} agents=${agents.length} stale=${staleAgents.length} runs=${runtimeRuns.length} leases=${leases.assignments.length} locks=${fileLocks.length} tasks=${activeTasks.tasks.length}`
6373
+ `status=${sessionPayload.status} active=${activeAgents.length} registered=${agents.length} stale=${staleAgents.length} listeners=${liveListenerCount}/${listenerRows.length} runs=${runtimeRuns.length} leases=${leases.assignments.length} locks=${fileLocks.length} tasks=${activeTasks.tasks.length}`
6310
6374
  )
6311
6375
  );
6312
6376
  for (const event of recentEvents) {
package/src/legacy-cli.js CHANGED
@@ -2517,20 +2517,20 @@ jobs:
2517
2517
  fi
2518
2518
  - name: Run Omar Gate
2519
2519
  id: omar
2520
- uses: mrrCarter/sentinelayer-v1-action@03d7369cba7de2e9f15b959275c982111f0ee493
2520
+ uses: mrrCarter/sentinelayer-v1-action@5c4d8c175eb117ea5256452e50e01249ab126998
2521
2521
  with:
2522
2522
  github_token: \${{ github.token }}
2523
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' }}
2524
+ google_api_key: \${{ secrets.GOOGLE_GEMINI_API_KEY != '' && secrets.GOOGLE_GEMINI_API_KEY || secrets.GOOGLE_API_KEY }}
2525
+ llm_provider: \${{ secrets.OPENAI_API_KEY != '' && 'openai' || ((secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'google' || 'openai') }}
2526
2526
  sentinelayer_token: \${{ secrets.${normalizedSecret} }}${specIdBindingLine}
2527
- sentinelayer_managed_llm: \${{ secrets.GOOGLE_API_KEY == '' && secrets.OPENAI_API_KEY == '' && secrets.${normalizedSecret} != '' }}
2527
+ sentinelayer_managed_llm: \${{ secrets.${normalizedSecret} != '' }}
2528
2528
  scan_mode: \${{ github.event_name == 'workflow_dispatch' && inputs.scan_mode || 'deep' }}
2529
2529
  severity_gate: \${{ github.event_name == 'workflow_dispatch' && inputs.severity_gate || 'P1' }}
2530
- model: \${{ secrets.GOOGLE_API_KEY != '' && 'gemini-2.5-pro' || 'gpt-5.3-codex' }}
2530
+ model: \${{ secrets.OPENAI_API_KEY != '' && 'gpt-5.3-codex' || ((secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'gemini-3.1-flash-lite' || 'gpt-5.3-codex') }}
2531
2531
  codex_model: gpt-5.3-codex
2532
- model_fallback: \${{ secrets.GOOGLE_API_KEY != '' && 'gemini-2.5-flash' || 'gpt-4.1-mini' }}
2533
- use_codex: \${{ secrets.GOOGLE_API_KEY == '' }}
2532
+ model_fallback: \${{ (secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'gemini-3.1-flash-lite' || 'gpt-4.1-mini' }}
2533
+ use_codex: \${{ secrets.OPENAI_API_KEY != '' || (secrets.GOOGLE_GEMINI_API_KEY == '' && secrets.GOOGLE_API_KEY == '') }}
2534
2534
  codex_only: "false"
2535
2535
  llm_failure_policy: block
2536
2536
  max_daily_scans: \${{ vars.OMAR_MAX_DAILY_SCANS || '200' }}
@@ -2582,11 +2582,11 @@ jobs:
2582
2582
  OMAR_SEVERITY_GATE: \${{ github.event_name == 'workflow_dispatch' && inputs.severity_gate || 'P1' }}
2583
2583
  OMAR_P2_MAX_ALLOWED: \${{ github.event_name == 'workflow_dispatch' && inputs.p2_max_allowed || '5' }}
2584
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' }}
2585
+ OMAR_GOOGLE_KEY_PRESENT: \${{ secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '' }}
2586
+ OMAR_MANAGED_LLM: \${{ secrets.${normalizedSecret} != '' }}
2587
+ OMAR_LLM_PROVIDER: \${{ secrets.OPENAI_API_KEY != '' && 'openai' || ((secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'google' || 'openai') }}
2588
+ OMAR_MODEL: \${{ secrets.OPENAI_API_KEY != '' && 'gpt-5.3-codex' || ((secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'gemini-3.1-flash-lite' || 'gpt-5.3-codex') }}
2589
+ OMAR_MODEL_FALLBACK: \${{ (secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'gemini-3.1-flash-lite' || 'gpt-4.1-mini' }}
2590
2590
  run: |
2591
2591
  set -euo pipefail
2592
2592
  mkdir -p omar-artifacts
@@ -2626,11 +2626,11 @@ jobs:
2626
2626
  },
2627
2627
  "scan": {
2628
2628
  "mode": env("OMAR_SCAN_MODE", "deep"),
2629
- "action_ref": "mrrCarter/sentinelayer-v1-action@03d7369cba7de2e9f15b959275c982111f0ee493",
2629
+ "action_ref": "mrrCarter/sentinelayer-v1-action@5c4d8c175eb117ea5256452e50e01249ab126998",
2630
2630
  "llm_provider": env("OMAR_LLM_PROVIDER", "openai"),
2631
2631
  "model": env("OMAR_MODEL", "gpt-5.3-codex"),
2632
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"),
2633
+ "llm_route": "openai_api_key" if bool_env("OMAR_OPENAI_KEY_PRESENT") else ("google_api_key" if bool_env("OMAR_GOOGLE_KEY_PRESENT") else "sentinelayer_managed"),
2634
2634
  "google_key_present": bool_env("OMAR_GOOGLE_KEY_PRESENT"),
2635
2635
  "openai_key_present": bool_env("OMAR_OPENAI_KEY_PRESENT"),
2636
2636
  "managed_llm": bool_env("OMAR_MANAGED_LLM"),
@@ -2,7 +2,7 @@ import YAML from "yaml";
2
2
 
3
3
  export const DEFAULT_SCAN_WORKFLOW_PATH = ".github/workflows/omar-gate.yml";
4
4
  export const DEFAULT_SCAN_SECRET_NAME = "SENTINELAYER_TOKEN";
5
- export const SENTINELAYER_ACTION_REF = "mrrCarter/sentinelayer-v1-action@03d7369cba7de2e9f15b959275c982111f0ee493";
5
+ export const SENTINELAYER_ACTION_REF = "mrrCarter/sentinelayer-v1-action@5c4d8c175eb117ea5256452e50e01249ab126998";
6
6
  export const SUPPORTED_E2E_HINTS = Object.freeze(["auto", "yes", "no"]);
7
7
  export const SUPPORTED_PLAYWRIGHT_MODES = Object.freeze(["auto", "off", "baseline", "audit"]);
8
8
 
@@ -230,17 +230,18 @@ export function buildSecurityReviewWorkflow({ secretName = DEFAULT_SCAN_SECRET_N
230
230
  google_api_key:
231
231
  "${{ secrets.GOOGLE_GEMINI_API_KEY != '' && secrets.GOOGLE_GEMINI_API_KEY || secrets.GOOGLE_API_KEY }}",
232
232
  llm_provider:
233
- "${{ (secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'google' || 'openai' }}",
233
+ "${{ secrets.OPENAI_API_KEY != '' && 'openai' || ((secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'google' || 'openai') }}",
234
234
  sentinelayer_token: `\${{ secrets.${normalizedSecret} }}`,
235
- sentinelayer_managed_llm: `\${{ secrets.GOOGLE_GEMINI_API_KEY == '' && secrets.GOOGLE_API_KEY == '' && secrets.OPENAI_API_KEY == '' && secrets.${normalizedSecret} != '' }}`,
235
+ sentinelayer_managed_llm: `\${{ secrets.${normalizedSecret} != '' }}`,
236
236
  scan_mode: profile.scanMode || "deep",
237
237
  severity_gate: profile.severityGate || "P1",
238
238
  model:
239
- "${{ (secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'gemini-3.1-flash-lite' || 'gpt-5.3-codex' }}",
239
+ "${{ secrets.OPENAI_API_KEY != '' && 'gpt-5.3-codex' || ((secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'gemini-3.1-flash-lite' || 'gpt-5.3-codex') }}",
240
240
  codex_model: "gpt-5.3-codex",
241
241
  model_fallback:
242
242
  "${{ (secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'gemini-3.1-flash-lite' || 'gpt-4.1-mini' }}",
243
- use_codex: "${{ secrets.GOOGLE_GEMINI_API_KEY == '' && secrets.GOOGLE_API_KEY == '' }}",
243
+ use_codex:
244
+ "${{ secrets.OPENAI_API_KEY != '' || (secrets.GOOGLE_GEMINI_API_KEY == '' && secrets.GOOGLE_API_KEY == '') }}",
244
245
  codex_only: "false",
245
246
  llm_failure_policy: "block",
246
247
  max_daily_scans: "${{ vars.OMAR_MAX_DAILY_SCANS || '200' }}",
@@ -301,11 +302,11 @@ export function buildSecurityReviewWorkflow({ secretName = DEFAULT_SCAN_SECRET_N
301
302
  OMAR_OPENAI_KEY_PRESENT: "${{ secrets.OPENAI_API_KEY != '' }}",
302
303
  OMAR_GOOGLE_KEY_PRESENT:
303
304
  "${{ secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '' }}",
304
- OMAR_MANAGED_LLM: `\${{ secrets.GOOGLE_GEMINI_API_KEY == '' && secrets.GOOGLE_API_KEY == '' && secrets.OPENAI_API_KEY == '' && secrets.${normalizedSecret} != '' }}`,
305
+ OMAR_MANAGED_LLM: `\${{ secrets.${normalizedSecret} != '' }}`,
305
306
  OMAR_LLM_PROVIDER:
306
- "${{ (secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'google' || 'openai' }}",
307
+ "${{ secrets.OPENAI_API_KEY != '' && 'openai' || ((secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'google' || 'openai') }}",
307
308
  OMAR_MODEL:
308
- "${{ (secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'gemini-3.1-flash-lite' || 'gpt-5.3-codex' }}",
309
+ "${{ secrets.OPENAI_API_KEY != '' && 'gpt-5.3-codex' || ((secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'gemini-3.1-flash-lite' || 'gpt-5.3-codex') }}",
309
310
  OMAR_MODEL_FALLBACK:
310
311
  "${{ (secrets.GOOGLE_GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '') && 'gemini-3.1-flash-lite' || 'gpt-4.1-mini' }}",
311
312
  },
@@ -352,7 +353,7 @@ export function buildSecurityReviewWorkflow({ secretName = DEFAULT_SCAN_SECRET_N
352
353
  " 'llm_provider': env('OMAR_LLM_PROVIDER', 'openai'),",
353
354
  " 'model': env('OMAR_MODEL', 'gpt-5.3-codex'),",
354
355
  " 'model_fallback': env('OMAR_MODEL_FALLBACK', 'gpt-4.1-mini'),",
355
- " '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'),",
356
+ " 'llm_route': 'openai_api_key' if bool_env('OMAR_OPENAI_KEY_PRESENT') else ('google_api_key' if bool_env('OMAR_GOOGLE_KEY_PRESENT') else 'sentinelayer_managed'),",
356
357
  " 'google_key_present': bool_env('OMAR_GOOGLE_KEY_PRESENT'),",
357
358
  " 'openai_key_present': bool_env('OMAR_OPENAI_KEY_PRESENT'),",
358
359
  " 'managed_llm': bool_env('OMAR_MANAGED_LLM'),",
@@ -507,7 +507,7 @@ export async function listenSessionEvents({
507
507
  if (signal?.aborted || streamHeartbeatInFlight) return;
508
508
  streamHeartbeatInFlight = true;
509
509
  try {
510
- await notifyHeartbeat({ nextPollMs: null });
510
+ await notifyHeartbeat({ nextPollMs: streamHeartbeatMs });
511
511
  } finally {
512
512
  streamHeartbeatInFlight = false;
513
513
  }
@@ -543,7 +543,7 @@ export async function listenSessionEvents({
543
543
  });
544
544
  },
545
545
  onHeartbeat: async () => {
546
- await notifyHeartbeat({ nextPollMs: null });
546
+ await notifyHeartbeat({ nextPollMs: streamHeartbeatMs });
547
547
  },
548
548
  }).finally(() => {
549
549
  stopStreamHeartbeatTimer();
@@ -20,6 +20,7 @@ const DEFAULT_RECAP_INACTIVITY_MS = 600_000;
20
20
  const DEFAULT_RECAP_ACTIVITY_THRESHOLD = 5;
21
21
  const DEFAULT_TASK_SUMMARY_LIMIT = 3;
22
22
  const DEFAULT_WORK_PLAN_SUMMARY_LIMIT = 5;
23
+ const DEFAULT_ACTIVITY_SNIPPET_MAX_CHARS = 120;
23
24
  const MAX_WORK_PLAN_BYTES = 128_000;
24
25
  const WORK_PLAN_RELATIVE_PATH = "tasks/todo.md";
25
26
  const HISTORICAL_WORK_PLAN_MIN_COMPLETED = 25;
@@ -130,6 +131,11 @@ function eventSequenceId(event = {}) {
130
131
  return Math.floor(parsed);
131
132
  }
132
133
 
134
+ function formatSequenceMarker(event = {}) {
135
+ const sequence = eventSequenceId(event);
136
+ return sequence === null ? "" : ` #${sequence}`;
137
+ }
138
+
133
139
  function eventCursor(event = {}) {
134
140
  return normalizeString(event.cursor || event.eventId || event.idempotencyToken || event.ts);
135
141
  }
@@ -373,7 +379,9 @@ function summarizeRecentActivity(events = [], { forAgentId = "", limit = 2 } = {
373
379
  if (!message) {
374
380
  continue;
375
381
  }
376
- snippets.push(`${agentId}: ${message.replace(/\s+/g, " ").slice(0, 120)}`);
382
+ snippets.push(
383
+ `${agentId}${formatSequenceMarker(event)}: ${shortActivitySnippetText(message)}`,
384
+ );
377
385
  if (snippets.length >= Math.max(1, limit)) {
378
386
  break;
379
387
  }
@@ -381,6 +389,22 @@ function summarizeRecentActivity(events = [], { forAgentId = "", limit = 2 } = {
381
389
  return snippets.reverse();
382
390
  }
383
391
 
392
+ function shortActivitySnippetText(value, maxLength = DEFAULT_ACTIVITY_SNIPPET_MAX_CHARS) {
393
+ const text = normalizeString(value).replace(/\s+/g, " ");
394
+ const normalizedMaxLength = normalizePositiveInteger(maxLength, DEFAULT_ACTIVITY_SNIPPET_MAX_CHARS);
395
+ if (text.length <= normalizedMaxLength) {
396
+ return text;
397
+ }
398
+ const suffix = "...";
399
+ const cutLimit = Math.max(1, normalizedMaxLength - suffix.length);
400
+ const preferredBoundary = text.lastIndexOf(" ", cutLimit);
401
+ const minimumBoundary = Math.floor(normalizedMaxLength * 0.6);
402
+ const cutIndex =
403
+ preferredBoundary >= minimumBoundary ? preferredBoundary : cutLimit;
404
+ const prefix = text.slice(0, cutIndex).replace(/[\s,;:.-]+$/g, "");
405
+ return `${prefix || text.slice(0, cutLimit)}${suffix}`;
406
+ }
407
+
384
408
  async function readPendingTasks(sessionId, { forAgentId = "", targetPath = process.cwd() } = {}) {
385
409
  const normalizedAgentId = normalizeString(forAgentId).toLowerCase();
386
410
  if (!normalizedAgentId) {