sentinelayer-cli 0.46.4 → 0.48.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 (137) hide show
  1. package/README.md +63 -3
  2. package/package.json +12 -7
  3. package/skills/omar-gate-setup/SKILL.md +103 -0
  4. package/skills/senti-agent-identity/SKILL.md +80 -0
  5. package/skills/senti-judge/SKILL.md +139 -0
  6. package/skills/senti-room-notifications/SKILL.md +73 -0
  7. package/skills/senti-room-notifications/listen-wrapper.cjs +150 -0
  8. package/skills/senti-room-notifications/package.json +1 -0
  9. package/skills/senti-room-notifications/test-classifier.js +109 -0
  10. package/skills/senti-room-notifications/test-receipt.js +54 -0
  11. package/skills/sentinelayer-cli/SKILL.md +222 -0
  12. package/src/agents/devtestbot/runner.js +22 -11
  13. package/src/agents/devtestbot/tool.js +24 -11
  14. package/src/agents/jules/fix-cycle.js +7 -1
  15. package/src/agents/jules/tools/runtime-audit.js +17 -12
  16. package/src/agents/shared-tools/dispatch-core.js +10 -5
  17. package/src/ai/aidenid.js +4 -2
  18. package/src/ai/domain-target-store.js +11 -5
  19. package/src/ai/identity-store.js +11 -5
  20. package/src/ai/site-store.js +8 -4
  21. package/src/audit/orchestrator.js +21 -14
  22. package/src/audit/package.js +6 -4
  23. package/src/audit/persona-loop.js +7 -0
  24. package/src/audit/replay.js +4 -2
  25. package/src/auth/gate.js +46 -8
  26. package/src/auth/service.js +34 -15
  27. package/src/auth/session-store.js +105 -47
  28. package/src/board/evidence-resolvers.js +3 -4
  29. package/src/cli.js +23 -3
  30. package/src/commands/ai/identity-lifecycle.js +30 -26
  31. package/src/commands/ai/provision-governance.js +32 -24
  32. package/src/commands/ai/shared.js +6 -4
  33. package/src/commands/audit.js +14 -12
  34. package/src/commands/auth.js +24 -4
  35. package/src/commands/board.js +2 -2
  36. package/src/commands/chat.js +8 -6
  37. package/src/commands/judge.js +125 -0
  38. package/src/commands/legacy-args.js +1 -0
  39. package/src/commands/mcp.js +45 -52
  40. package/src/commands/omargate.js +2 -1
  41. package/src/commands/plugin.js +11 -3
  42. package/src/commands/review.js +9 -4
  43. package/src/commands/scan.js +7 -7
  44. package/src/commands/session.js +607 -57
  45. package/src/commands/skills.js +45 -0
  46. package/src/commands/watch.js +9 -7
  47. package/src/config/output-filesystem.js +156 -0
  48. package/src/config/schema.js +4 -2
  49. package/src/config/service.js +40 -4
  50. package/src/cost/history.js +15 -9
  51. package/src/daemon/artifact-lineage.js +15 -12
  52. package/src/daemon/assignment-ledger.js +31 -25
  53. package/src/daemon/ast-drift.js +7 -3
  54. package/src/daemon/budget-governor.js +26 -20
  55. package/src/daemon/error-worker.js +28 -22
  56. package/src/daemon/fix-cycle.js +5 -4
  57. package/src/daemon/hybrid-mapper.js +19 -16
  58. package/src/daemon/ingest-refresh.js +4 -0
  59. package/src/daemon/jira-lifecycle.js +21 -17
  60. package/src/daemon/operator-control.js +18 -15
  61. package/src/daemon/reliability-lane.js +18 -15
  62. package/src/daemon/scope-engine.js +12 -8
  63. package/src/daemon/watchdog.js +15 -12
  64. package/src/engram/build-state.js +24 -3
  65. package/src/engram/capability-index.js +367 -0
  66. package/src/engram/compile-graph.js +1063 -0
  67. package/src/engram/contracts/action-receipt.js +82 -0
  68. package/src/engram/contracts/capability-index.js +259 -0
  69. package/src/engram/contracts/capability.js +377 -0
  70. package/src/engram/contracts/content-hash.js +8 -0
  71. package/src/engram/contracts/evidence.js +258 -0
  72. package/src/engram/contracts/graph.js +594 -0
  73. package/src/engram/contracts/identity.js +8 -0
  74. package/src/engram/contracts/index.js +149 -0
  75. package/src/engram/contracts/media-asset.js +85 -0
  76. package/src/engram/contracts/money.js +249 -0
  77. package/src/engram/contracts/source-snapshot.js +57 -0
  78. package/src/engram/contracts/task-context-pack.js +114 -0
  79. package/src/engram/contracts/temporal-segment.js +72 -0
  80. package/src/engram/contracts/versions.js +96 -0
  81. package/src/engram/extract-html.js +692 -0
  82. package/src/engram/graph-store.js +877 -0
  83. package/src/engram/index.js +98 -14
  84. package/src/engram/ingest-site.js +132 -0
  85. package/src/engram/live-state.js +1180 -0
  86. package/src/engram/namespace.js +34 -1
  87. package/src/engram/retrieve-site.js +753 -0
  88. package/src/engram/safe-reader.js +597 -0
  89. package/src/engram/source-intake.js +251 -0
  90. package/src/engram/store.js +227 -9
  91. package/src/ingest/engine.js +21 -28
  92. package/src/judge/verdict.js +305 -0
  93. package/src/legacy-cli.js +96 -19
  94. package/src/mcp/bridge-inputs.js +28 -4
  95. package/src/mcp/cli-command-tools.js +70 -0
  96. package/src/mcp/registry.js +17 -13
  97. package/src/mcp/session-stdio-server.js +297 -19
  98. package/src/memory/blackboard.js +5 -4
  99. package/src/net/gh-api-path.js +35 -9
  100. package/src/net/test-egress-guard.js +16 -4
  101. package/src/plugin/manifest.js +12 -8
  102. package/src/review/ai-review.js +110 -10
  103. package/src/review/investor-dd-devtestbot.js +17 -6
  104. package/src/review/investor-dd-orchestrator.js +21 -13
  105. package/src/review/local-review.js +26 -16
  106. package/src/review/omargate-cache.js +5 -5
  107. package/src/review/omargate-orchestrator.js +89 -17
  108. package/src/review/replay.js +9 -4
  109. package/src/review/report.js +22 -13
  110. package/src/review/typesafe-severity.js +201 -0
  111. package/src/session/admission-auth.js +34 -7
  112. package/src/session/admission.js +322 -71
  113. package/src/session/agent-access.js +137 -0
  114. package/src/session/agent-registry.js +2 -1
  115. package/src/session/attachment-summary.js +77 -0
  116. package/src/session/edit-summary.js +19 -0
  117. package/src/session/invitations.js +65 -1
  118. package/src/session/mentions.js +17 -9
  119. package/src/session/message-edits.js +5 -4
  120. package/src/session/preview.js +10 -2
  121. package/src/session/recap.js +3 -2
  122. package/src/session/room-files.js +519 -0
  123. package/src/session/route-classes.js +22 -8
  124. package/src/session/setup-guides.js +4 -0
  125. package/src/session/standup.js +192 -0
  126. package/src/session/sync.js +229 -10
  127. package/src/session/tickets.js +41 -0
  128. package/src/session/transcript.js +2 -1
  129. package/src/session/wake/resolve-target.js +7 -3
  130. package/src/skills/listener-runtime.cjs +92 -0
  131. package/src/skills/service.js +207 -0
  132. package/src/swarm/factory.js +5 -5
  133. package/src/swarm/pentest.js +9 -9
  134. package/src/swarm/report.js +4 -4
  135. package/src/swarm/runtime.js +15 -12
  136. package/src/swarm/scenario-dsl.js +7 -5
  137. package/src/telemetry/ledger.js +16 -7
@@ -0,0 +1,77 @@
1
+ // Display metadata only. The files command resolves pointers when asked to fetch.
2
+ import { containsSecret } from "./redact.js";
3
+ import { sessionMessageRoutingEvent } from "./message-edits.js";
4
+
5
+ const MAX_VISIBLE_ATTACHMENTS = 5;
6
+
7
+ function displayText(value, fallback = "") {
8
+ if (typeof value !== "string") return fallback;
9
+ const text = value.replace(/[\p{Cc}\p{Cf}]/gu, " ").replace(/\s+/g, " ").trim();
10
+ if (containsSecret(text) || /\w+:\/\/|\bbearer\s+\S+/i.test(text)) return fallback;
11
+ return Array.from(text.replace(/[\[\]`<>]/g, "")).slice(0, 255).join("") || fallback;
12
+ }
13
+
14
+ function commandId(value) {
15
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/.test(value)
16
+ && !value.includes("..") && !containsSecret(value) ? value : "";
17
+ }
18
+
19
+ function pointerAttachmentId(pointer, sessionId) {
20
+ if (typeof pointer !== "string" || !sessionId) return "";
21
+ const match = /^\/api\/v1\/sessions\/([^/]+)\/attachments\/([^/?#]+)$/.exec(pointer);
22
+ return match && match[1] === sessionId ? commandId(match[2]) : "";
23
+ }
24
+
25
+ function fileSize(value) {
26
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return "";
27
+ if (value < 1024) return `${value} B`;
28
+ const units = ["KB", "MB", "GB", "TB", "PB"];
29
+ let size = value / 1024;
30
+ let unit = 0;
31
+ while (size >= 1024 && unit < units.length - 1) {
32
+ size /= 1024;
33
+ unit += 1;
34
+ }
35
+ return `${size.toFixed(size < 10 ? 1 : 0)} ${units[unit]}`;
36
+ }
37
+
38
+ export function formatAttachmentSummary(event = {}) {
39
+ const session = event?.sessionId || event?.session_id;
40
+ event = sessionMessageRoutingEvent(event);
41
+ const payload = event?.payload || {};
42
+ const attachments = Array.isArray(payload.attachments) ? payload.attachments : [];
43
+ const sourceSessionId = commandId(session || event?.sessionId || event?.session_id);
44
+ const sessionId = sourceSessionId || "<sessionId>";
45
+ const summaries = attachments.slice(0, MAX_VISIBLE_ATTACHMENTS).map((attachment) => {
46
+ const item = attachment && typeof attachment === "object" ? attachment : {};
47
+ const details = [`${displayText(item.kind, "file")} ${displayText(item.filename, "unnamed")}`];
48
+ const size = fileSize(item.sizeBytes ?? item.size_bytes);
49
+ if (size) details.push(size);
50
+ if (typeof item.sha256 === "string" && /^[a-f\d]{64}$/i.test(item.sha256)) {
51
+ details.push(`sha256 ${item.sha256.slice(0, 8).toLowerCase()}…`);
52
+ }
53
+ const status = item.removed === true ? "removed" : displayText(item.status);
54
+ if (status) details.push(`status ${status}`);
55
+ const marker = `[attachment: ${details.join(", ")}]`;
56
+ if (item.removed === true) return marker;
57
+ const attachmentId = commandId(item.attachmentId || item.attachment_id || item.id
58
+ || pointerAttachmentId(item.pointer, sourceSessionId));
59
+ const fetch = attachmentId.length >= 8
60
+ ? `sl session files get ${sessionId} ${attachmentId}`
61
+ : `sl session files ${sessionId}`;
62
+ return `${marker} (fetch: ${fetch})`;
63
+ });
64
+ if (attachments.length > MAX_VISIBLE_ATTACHMENTS) {
65
+ summaries.push(`+${attachments.length - MAX_VISIBLE_ATTACHMENTS} more`);
66
+ }
67
+ return summaries.join(" ");
68
+ }
69
+
70
+ export function appendAttachmentSummary(text, event) {
71
+ return [text, formatAttachmentSummary(event)].filter(Boolean).join(" ");
72
+ }
73
+
74
+ export function withAttachmentSummary(event) {
75
+ const attachmentSummary = formatAttachmentSummary(event);
76
+ return attachmentSummary ? { ...event, attachmentSummary } : event;
77
+ }
@@ -0,0 +1,19 @@
1
+ export function sessionMessageEditText(event = {}) {
2
+ const payload = event.payload || {};
3
+ const text = payload.message ?? payload.reply?.note ?? payload.event?.payload?.message;
4
+ return typeof text === "string" ? text : "";
5
+ }
6
+
7
+ export function formatEditSummary(event = {}) {
8
+ if ((event.event || event.type) !== "session_message_edited") return "";
9
+ const payload = event.payload || {};
10
+ const sequence = Number(payload.targetSequenceId ?? payload.reply?.targetSequenceId ?? payload.event?.sequenceId);
11
+ const target = Number.isSafeInteger(sequence) && sequence > 0 ? sequence : "?";
12
+ const kind = payload.targetActionId || payload.reply ? "edited reply under" : "edited";
13
+ return [`[${kind} #${target}]`, sessionMessageEditText(event).trim()].filter(Boolean).join(" ");
14
+ }
15
+
16
+ export function withEditSummary(event) {
17
+ const editSummary = formatEditSummary(event);
18
+ return editSummary ? { ...event, editSummary } : event;
19
+ }
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import process from "node:process";
5
5
 
6
6
  import { checkedTransport, credentialFor, credentialHmac, isAuthenticated, isCredential } from "../auth/credential-destinations.js";
7
- import { requestJsonMutation } from "../auth/http.js";
7
+ import { requestJson, requestJsonMutation } from "../auth/http.js";
8
8
  import { resolveActiveAuthSession } from "../auth/service.js";
9
9
  import { renderCoordinationBulletList } from "./coordination-guidance.js";
10
10
  import { resolveSessionPaths } from "./paths.js";
@@ -12,8 +12,20 @@ import { urlPathSegment } from "../net/url-path.js";
12
12
 
13
13
  export const SESSION_INVITATION_ACCEPT_ROUTE_ID =
14
14
  "POST /api/v1/sessions/{session_id}/invitations/accept";
15
+ export const PENDING_INVITATION_ACCEPT_ROUTE_ID =
16
+ "POST /api/v1/sessions/invitations/{invitation_id}/accept";
15
17
  export const SESSION_MUTATION_ORIGIN = "https://sentinelayer.com";
16
18
 
19
+ export function throwIfPendingSessionInvitation(payload, status) {
20
+ if ((status !== 403 && status !== 404) || payload?.error?.code !== "INVITATION_PENDING") return;
21
+ const error = new Error(
22
+ "An invitation to this room is waiting. Run `sl session invitations list --json`, then `sl session invitations accept <invitationId>` using its invitationId.",
23
+ );
24
+ error.code = "INVITATION_PENDING";
25
+ error.status = status;
26
+ throw error;
27
+ }
28
+
17
29
  function normalizeString(value) {
18
30
  return String(value || "").trim();
19
31
  }
@@ -85,6 +97,58 @@ export function createSessionMutationIdempotencyKey(operationName = "invitation-
85
97
  return `sl-cli-${operation}-${randomUUID()}`;
86
98
  }
87
99
 
100
+ export async function listPendingSessionInvitations({
101
+ targetPath = process.cwd(),
102
+ resolveAuthSession = resolveActiveAuthSession,
103
+ request = requestJson,
104
+ } = {}) {
105
+ const auth = await resolveAuthSession({ cwd: targetPath, env: process.env, autoRotate: false });
106
+ if (!isAuthenticated(auth) || !auth?.apiUrl) {
107
+ throw new Error("Not authenticated. Run `sl auth login` first.");
108
+ }
109
+ const apiUrl = normalizeString(auth.apiUrl).replace(/\/+$/, "");
110
+ return checkedTransport(request)(`${apiUrl}/api/v1/sessions/invitations/pending`, {
111
+ method: "GET",
112
+ credential: await credentialFor(auth),
113
+ });
114
+ }
115
+
116
+ export async function acceptPendingSessionInvitation(invitationId, {
117
+ targetPath = process.cwd(),
118
+ idempotencyKey = "",
119
+ origin = SESSION_MUTATION_ORIGIN,
120
+ resolveAuthSession = resolveActiveAuthSession,
121
+ requestMutation = requestJsonMutation,
122
+ } = {}) {
123
+ const normalizedInvitationId = typeof invitationId === "string" ? invitationId.trim() : "";
124
+ if (!normalizedInvitationId) throw new Error("invitation id is required.");
125
+ const invitationPath = `/api/v1/sessions/invitations/${urlPathSegment(normalizedInvitationId, { label: "invitationId" })}/accept`;
126
+ const auth = await resolveAuthSession({ cwd: targetPath, env: process.env, autoRotate: false });
127
+ if (!isAuthenticated(auth) || !auth?.apiUrl) {
128
+ throw new Error("Not authenticated. Run `sl auth login` first.");
129
+ }
130
+ const credential = await credentialFor(auth);
131
+ const resolvedIdempotencyKey = normalizeString(idempotencyKey)
132
+ || createSessionMutationIdempotencyKey("session-invite-accept");
133
+ const apiUrl = normalizeString(auth.apiUrl).replace(/\/+$/, "");
134
+ return checkedTransport(requestMutation)(
135
+ `${apiUrl}${invitationPath}`,
136
+ {
137
+ method: "POST",
138
+ operationName: "session.invitation_accept_by_id",
139
+ idempotencyKey: resolvedIdempotencyKey,
140
+ credential,
141
+ headers: createSessionMutationHeaders({
142
+ credential,
143
+ routeId: PENDING_INVITATION_ACCEPT_ROUTE_ID,
144
+ idempotencyKey: resolvedIdempotencyKey,
145
+ origin,
146
+ }),
147
+ body: {},
148
+ },
149
+ );
150
+ }
151
+
88
152
  export async function acceptSessionInvitation(
89
153
  sessionId,
90
154
  {
@@ -21,6 +21,8 @@
21
21
  * punctuation + cap length)
22
22
  */
23
23
 
24
+ import { sessionMessageEditText } from "./edit-summary.js";
25
+
24
26
  // `@handle` is one or more identifier characters. We allow letters,
25
27
  // digits, `._-` so `@codex-1`, `@human-mrrcarter`, `@claude.verifier`
26
28
  // all work. Word boundary on the front avoids matching `you@example.com`.
@@ -97,20 +99,26 @@ export function enrichEventWithMentions(event) {
97
99
  : null;
98
100
  if (!payload) return event;
99
101
 
102
+ // An edit's immutable snapshot retains the original explicit audience.
103
+ const routingPayload = event.event === "session_message_edited"
104
+ ? { ...payload.event?.payload, ...payload }
105
+ : payload;
100
106
  // Already routed by an explicit caller — don't second-guess.
101
107
  const hasExplicitTo =
102
- Array.isArray(payload.to) ||
103
- Array.isArray(payload.recipients) ||
104
- typeof payload.recipient === "string" ||
105
- typeof payload.targetAgent === "string" ||
106
- typeof payload.targetAgentId === "string";
108
+ Array.isArray(routingPayload.to) ||
109
+ Array.isArray(routingPayload.recipients) ||
110
+ typeof routingPayload.recipient === "string" ||
111
+ typeof routingPayload.targetAgent === "string" ||
112
+ typeof routingPayload.targetAgentId === "string";
107
113
 
108
114
  // We pull text from the standard fields the renderer already uses,
109
115
  // matching the priority in `Session.tsx:payloadText`.
110
- const text = normalizeString(payload.message)
111
- || normalizeString(payload.text)
112
- || normalizeString(payload.detail)
113
- || normalizeString(payload.title);
116
+ const text = event.event === "session_message_edited"
117
+ ? sessionMessageEditText(event)
118
+ : normalizeString(payload.message)
119
+ || normalizeString(payload.text)
120
+ || normalizeString(payload.detail)
121
+ || normalizeString(payload.title);
114
122
  if (!text) return event;
115
123
 
116
124
  const parsed = parseMentions(text);
@@ -1,6 +1,7 @@
1
1
  import { createAgentEvent } from "../events/schema.js";
2
2
  import { enrichEventWithMentions } from "./mentions.js";
3
3
  import { sessionEventIdentityKeys } from "./event-identity.js";
4
+ import { sessionMessageEditText } from "./edit-summary.js";
4
5
 
5
6
  export function messageRevision(value = {}) {
6
7
  const revision = Number(value.messageRevision ?? value.message_revision ?? 1);
@@ -79,11 +80,11 @@ export function sessionMessageRoutingEvent(event = {}) {
79
80
  if (!snapshot) return event;
80
81
  const parentPayload = event.payload?.event?.payload || {};
81
82
  const payload = { ...parentPayload, ...snapshot.payload };
82
- if (event.payload?.reply) payload.message = String(event.payload.reply.note ?? "");
83
+ payload.message = sessionMessageEditText(event);
83
84
  for (const key of ["to", "recipient", "recipients", "targetAgent", "targetAgentId", "broadcast", "mentions"]) {
84
85
  if (event.payload[key] !== undefined) payload[key] = event.payload[key];
85
86
  }
86
- const routed = enrichEventWithMentions({ ...snapshot, agent: event.agent || snapshot.agent, payload });
87
- if (routed.payload?.mentions?.broadcast?.length) routed.payload.broadcast = true;
88
- return routed;
87
+ // Use the same recipient precedence as a new message. Broadcast mentions
88
+ // are metadata; only an explicit routing flag can widen the audience.
89
+ return enrichEventWithMentions({ ...snapshot, agent: event.agent || snapshot.agent, payload });
89
90
  }
@@ -7,9 +7,12 @@
7
7
  */
8
8
 
9
9
  import { readStream } from "./stream.js";
10
+ import { appendAttachmentSummary, formatAttachmentSummary } from "./attachment-summary.js";
11
+ import { formatEditSummary } from "./edit-summary.js";
10
12
 
11
13
  const PREVIEW_EVENTS = new Set([
12
14
  "session_message",
15
+ "session_message_edited",
13
16
  "session_say",
14
17
  "agent_response",
15
18
  "session_observation",
@@ -45,14 +48,16 @@ export function pickLatestPreview(events = []) {
45
48
  const kind = String(event.event || event.type || "").trim();
46
49
  if (!kind || !PREVIEW_EVENTS.has(kind)) continue;
47
50
  const payload = event.payload && typeof event.payload === "object" ? event.payload : {};
51
+ const editSummary = formatEditSummary(event);
48
52
  const text =
53
+ editSummary ||
49
54
  payload.summary ||
50
55
  payload.message ||
51
56
  payload.response ||
52
57
  payload.alert ||
53
58
  payload.reason ||
54
59
  payload.text;
55
- if (!text) continue;
60
+ if (!text && !formatAttachmentSummary(event)) continue;
56
61
  return {
57
62
  ts: event.ts || event.timestamp || null,
58
63
  agentId:
@@ -61,7 +66,10 @@ export function pickLatestPreview(events = []) {
61
66
  payload.agentId ||
62
67
  null,
63
68
  kind,
64
- message: trim(text),
69
+ message: appendAttachmentSummary(trim(text), event),
70
+ ...(editSummary ? { editSummary, event } : {}),
71
+ ...(Array.isArray(payload.attachments) && payload.attachments.length
72
+ ? { attachments: payload.attachments } : {}),
65
73
  };
66
74
  }
67
75
  return { ts: null, agentId: null, kind: null, message: null };
@@ -7,6 +7,7 @@ import { createAgentEvent } from "../events/schema.js";
7
7
  import { dedupeSessionEvents } from "./event-identity.js";
8
8
  import { resolveSessionPaths } from "./paths.js";
9
9
  import { readStream } from "./stream.js";
10
+ import { appendAttachmentSummary, formatAttachmentSummary } from "./attachment-summary.js";
10
11
  import { getSession } from "./store.js";
11
12
  import { aggregateSessionUsage } from "./usage.js";
12
13
 
@@ -381,11 +382,11 @@ function summarizeRecentActivity(events = [], { forAgentId = "", limit = 2 } = {
381
382
  const message = normalizeString(
382
383
  payload.message || payload.response || payload.recap || payload.alert || payload.reason
383
384
  );
384
- if (!message) {
385
+ if (!message && !formatAttachmentSummary(event)) {
385
386
  continue;
386
387
  }
387
388
  snippets.push(
388
- `${agentId}${formatSequenceMarker(event)}: ${shortActivitySnippetText(message)}`,
389
+ `${agentId}${formatSequenceMarker(event)}: ${appendAttachmentSummary(shortActivitySnippetText(message), event)}`,
389
390
  );
390
391
  if (snippets.length >= Math.max(1, limit)) {
391
392
  break;