sentinelayer-cli 0.46.5 → 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 (133) hide show
  1. package/README.md +47 -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 +32 -1
  26. package/src/auth/session-store.js +37 -0
  27. package/src/board/evidence-resolvers.js +3 -4
  28. package/src/cli.js +23 -3
  29. package/src/commands/ai/identity-lifecycle.js +30 -26
  30. package/src/commands/ai/provision-governance.js +32 -24
  31. package/src/commands/ai/shared.js +6 -4
  32. package/src/commands/audit.js +14 -12
  33. package/src/commands/board.js +2 -2
  34. package/src/commands/chat.js +8 -6
  35. package/src/commands/judge.js +125 -0
  36. package/src/commands/legacy-args.js +1 -0
  37. package/src/commands/mcp.js +45 -52
  38. package/src/commands/omargate.js +2 -1
  39. package/src/commands/plugin.js +11 -3
  40. package/src/commands/review.js +9 -4
  41. package/src/commands/scan.js +7 -7
  42. package/src/commands/session.js +516 -30
  43. package/src/commands/skills.js +45 -0
  44. package/src/commands/watch.js +9 -7
  45. package/src/config/output-filesystem.js +156 -0
  46. package/src/config/schema.js +4 -2
  47. package/src/config/service.js +40 -4
  48. package/src/cost/history.js +15 -9
  49. package/src/daemon/artifact-lineage.js +15 -12
  50. package/src/daemon/assignment-ledger.js +31 -25
  51. package/src/daemon/ast-drift.js +7 -3
  52. package/src/daemon/budget-governor.js +26 -20
  53. package/src/daemon/error-worker.js +28 -22
  54. package/src/daemon/fix-cycle.js +5 -4
  55. package/src/daemon/hybrid-mapper.js +19 -16
  56. package/src/daemon/ingest-refresh.js +4 -0
  57. package/src/daemon/jira-lifecycle.js +21 -17
  58. package/src/daemon/operator-control.js +18 -15
  59. package/src/daemon/reliability-lane.js +18 -15
  60. package/src/daemon/scope-engine.js +12 -8
  61. package/src/daemon/watchdog.js +15 -12
  62. package/src/engram/build-state.js +24 -3
  63. package/src/engram/capability-index.js +367 -0
  64. package/src/engram/compile-graph.js +1063 -0
  65. package/src/engram/contracts/action-receipt.js +82 -0
  66. package/src/engram/contracts/capability-index.js +259 -0
  67. package/src/engram/contracts/capability.js +377 -0
  68. package/src/engram/contracts/content-hash.js +8 -0
  69. package/src/engram/contracts/evidence.js +258 -0
  70. package/src/engram/contracts/graph.js +594 -0
  71. package/src/engram/contracts/identity.js +8 -0
  72. package/src/engram/contracts/index.js +149 -0
  73. package/src/engram/contracts/media-asset.js +85 -0
  74. package/src/engram/contracts/money.js +249 -0
  75. package/src/engram/contracts/source-snapshot.js +57 -0
  76. package/src/engram/contracts/task-context-pack.js +114 -0
  77. package/src/engram/contracts/temporal-segment.js +72 -0
  78. package/src/engram/contracts/versions.js +96 -0
  79. package/src/engram/extract-html.js +692 -0
  80. package/src/engram/graph-store.js +877 -0
  81. package/src/engram/index.js +98 -14
  82. package/src/engram/ingest-site.js +132 -0
  83. package/src/engram/live-state.js +1180 -0
  84. package/src/engram/namespace.js +34 -1
  85. package/src/engram/retrieve-site.js +753 -0
  86. package/src/engram/safe-reader.js +597 -0
  87. package/src/engram/source-intake.js +251 -0
  88. package/src/engram/store.js +227 -9
  89. package/src/ingest/engine.js +21 -28
  90. package/src/judge/verdict.js +305 -0
  91. package/src/legacy-cli.js +96 -19
  92. package/src/mcp/bridge-inputs.js +25 -4
  93. package/src/mcp/cli-command-tools.js +70 -0
  94. package/src/mcp/registry.js +17 -13
  95. package/src/mcp/session-stdio-server.js +297 -19
  96. package/src/memory/blackboard.js +5 -4
  97. package/src/net/gh-api-path.js +35 -9
  98. package/src/net/test-egress-guard.js +16 -4
  99. package/src/plugin/manifest.js +12 -8
  100. package/src/review/ai-review.js +110 -10
  101. package/src/review/investor-dd-devtestbot.js +17 -6
  102. package/src/review/investor-dd-orchestrator.js +21 -13
  103. package/src/review/local-review.js +26 -16
  104. package/src/review/omargate-cache.js +5 -5
  105. package/src/review/omargate-orchestrator.js +89 -17
  106. package/src/review/replay.js +9 -4
  107. package/src/review/report.js +22 -13
  108. package/src/review/typesafe-severity.js +201 -0
  109. package/src/session/admission-auth.js +34 -7
  110. package/src/session/admission.js +110 -15
  111. package/src/session/attachment-summary.js +77 -0
  112. package/src/session/edit-summary.js +19 -0
  113. package/src/session/invitations.js +65 -1
  114. package/src/session/mentions.js +17 -9
  115. package/src/session/message-edits.js +5 -4
  116. package/src/session/preview.js +10 -2
  117. package/src/session/recap.js +3 -2
  118. package/src/session/room-files.js +519 -0
  119. package/src/session/route-classes.js +22 -8
  120. package/src/session/setup-guides.js +4 -0
  121. package/src/session/standup.js +192 -0
  122. package/src/session/sync.js +229 -10
  123. package/src/session/tickets.js +41 -0
  124. package/src/session/transcript.js +2 -1
  125. package/src/session/wake/resolve-target.js +7 -3
  126. package/src/skills/listener-runtime.cjs +92 -0
  127. package/src/skills/service.js +207 -0
  128. package/src/swarm/factory.js +5 -5
  129. package/src/swarm/pentest.js +9 -9
  130. package/src/swarm/report.js +4 -4
  131. package/src/swarm/runtime.js +15 -12
  132. package/src/swarm/scenario-dsl.js +7 -5
  133. package/src/telemetry/ledger.js +16 -7
@@ -120,6 +120,7 @@ export const BRIDGE_ALLOWED_INPUTS = Object.freeze({
120
120
  "session.comment": "sessionId targetSequenceId message agent idempotencyKey path json",
121
121
  "session.continue": "path title json",
122
122
  "session.edit": "sessionId target message agent expectedRevision idempotencyKey path json",
123
+ "session.events": "sessionId remote beforeSequence limit path json autoGrant",
123
124
  "session.ensure": "path session title ttlSeconds forceNew resume reuseWindowSeconds json",
124
125
  "session.guard": "sessionId files agent path json",
125
126
  "session.guard-hook": "sessionId agent path",
@@ -127,31 +128,35 @@ export const BRIDGE_ALLOWED_INPUTS = Object.freeze({
127
128
  "session.guard-uninstall": "sessionId agent path json",
128
129
  "session.history": "limit preview path json",
129
130
  "session.inject-guide": "sessionId path json",
130
- "session.join": "sessionId name agent inviteToken seatKey idempotencyKey role model path goal deliverable stopWhen scope ttl provider wait waitSeconds cancelAdmission json",
131
+ "session.invitations.accept": "invitationId path json",
132
+ "session.invitations.list": "path json",
133
+ "session.join": "sessionId name agent inviteToken seatKey idempotencyKey role model path goal deliverable stopWhen scope ttl provider wait waitSeconds cancelAdmission autoGrant json",
131
134
  "session.leave": "sessionId agent reason path json",
132
135
  "session.list": "remote includeArchived limit path json",
133
136
  "session.listeners": "sessionId path limit maxPages json",
134
137
  "session.lock": "sessionId files intent ttl agent path json",
135
138
  "session.locks": "sessionId path json",
136
- "session.observe": "sessionId summary agent model displayName role kind severity owner batch targetSequence targetCursor proposal messageFile stdin path json",
139
+ "session.observe": "sessionId summary agent model displayName role kind severity owner batch targetSequence targetCursor proposal messageFile stdin autoGrant path json",
137
140
  "session.pins": "sessionId path json",
138
- "session.post-agent": "sessionId message agent model displayName role to path json",
141
+ "session.post-agent": "sessionId message agent model displayName role to autoGrant path json",
139
142
  "session.react": "sessionId reaction targetSequence targetCursor targetActionId agent idempotencyKey path json",
140
143
  "session.read": "sessionId tail follow live remote beforeSequence actions agent view includeControlEvents path json",
141
144
  "session.recall": "need session role k path remote includeControlEvents json",
142
145
  "session.recap.now": "sessionId session remote agent maxEvents path json",
143
146
  "session.renew": "sessionId files ttl agent path json",
144
147
  "session.reply": "sessionId targetSequenceId message agent idempotencyKey path json",
145
- "session.say": "sessionId message agent model displayName role to replyTo replyCursor messageFile stdin forceCliUser localOnly path json",
148
+ "session.say": "sessionId message agent model displayName role to replyTo replyCursor messageFile stdin forceCliUser localOnly autoGrant path json",
146
149
  "session.search": "sessionId query beforeSequence limit path json",
147
150
  "session.set-title": "sessionId title path json",
148
151
  "session.setup-guides": "sessionId path json",
152
+ "session.standup": "sessionId since agent path json",
149
153
  "session.start": "path title template ttlSeconds forceNew force resume reuseWindowSeconds daemon json",
150
154
  "session.status": "sessionId path json",
151
155
  "session.stop-listener": "sessionId agent path json",
152
156
  "session.sync": "sessionId since path json",
153
157
  "session.templates": "json",
154
158
  "session.ticket.claim": "sessionId ticketId expectedVersion agent path json",
159
+ "session.ticket.create": "sessionId title body agent assignee sourceSequence idempotencyKey path json",
155
160
  "session.ticket.events": "sessionId after agent path json",
156
161
  "session.ticket.list": "sessionId available agent path json",
157
162
  "session.ticket.release": "sessionId ticketId reason agent path json",
@@ -181,6 +186,11 @@ export const BRIDGE_ALLOWED_INPUTS = Object.freeze({
181
186
  "watch.run-events": "runId path outputDir pollSeconds maxIdleSeconds saveArtifacts json",
182
187
  });
183
188
 
189
+ // Room file downloads write to disk: agents use the native session_file_get tool, which keeps
190
+ // the file inside the workspace and returns its path, so the bridge does not run this command.
191
+ const ROOM_FILE_DOWNLOAD_VIA_NATIVE_TOOL =
192
+ "room file downloads go through the native session_file_get MCP tool, which keeps the file inside the workspace";
193
+
184
194
  /** Command path -> { input: why it is not exposed }. */
185
195
  export const BRIDGE_DENIED_INPUTS = Object.freeze({
186
196
  "ai.identity.lineage": {
@@ -199,9 +209,20 @@ export const BRIDGE_DENIED_INPUTS = Object.freeze({
199
209
  "mcp.smoke": {
200
210
  apiUrl: "the API that receives the user's token",
201
211
  },
212
+ "omargate.deep": {
213
+ severityClassifier: "external-provider evidence evaluation is operator CLI opt-in only; never enabled by an MCP caller",
214
+ },
202
215
  "ring-owner": {
203
216
  gatewayUrl: "the gateway that receives the user's token",
204
217
  },
218
+ "session.files.get": {
219
+ sessionId: ROOM_FILE_DOWNLOAD_VIA_NATIVE_TOOL,
220
+ attachmentId: ROOM_FILE_DOWNLOAD_VIA_NATIVE_TOOL,
221
+ out: ROOM_FILE_DOWNLOAD_VIA_NATIVE_TOOL,
222
+ force: ROOM_FILE_DOWNLOAD_VIA_NATIVE_TOOL,
223
+ path: ROOM_FILE_DOWNLOAD_VIA_NATIVE_TOOL,
224
+ json: ROOM_FILE_DOWNLOAD_VIA_NATIVE_TOOL,
225
+ },
205
226
  "session.join": {
206
227
  recoverAdmission: "restores an agent credential using the delegator's retained private key; CLI control only",
207
228
  },
@@ -6,6 +6,7 @@ import process from "node:process";
6
6
 
7
7
  import { bridgeAllowedInputs } from "./bridge-inputs.js";
8
8
  import { buildSentinelayerCliRegistryTemplate } from "./cli-registry.js";
9
+ import { assertCanonicalAgentId, canonicalAgentId } from "../session/admission-auth.js";
9
10
 
10
11
  const DEFAULT_TIMEOUT_MS = 30_000;
11
12
  const MAX_TIMEOUT_MS = 300_000;
@@ -15,7 +16,30 @@ const BRIDGE_DISABLED_ENV = "SENTINELAYER_MCP_CLI_BRIDGE_DISABLED";
15
16
  const CLI_PATH_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9-]*$/;
16
17
  const CLI_OPTION_FLAG_RE = /^--?[a-zA-Z0-9][a-zA-Z0-9-]*$/;
17
18
  const BLOCKED_COMMAND_PATHS = new Set(["mcp.server.run"]);
19
+ const SELF_JOIN_INPUTS = new Set([
20
+ "sessionId", "agent", "name", "role", "model", "path", "goal", "deliverable", "stopWhen",
21
+ "scope", "ttl", "provider", "wait", "waitSeconds", "cancelAdmission", "autoGrant",
22
+ "json", "timeoutMs", "timeout_ms",
23
+ ]);
24
+ const SELF_LEAVE_INPUTS = new Set(["sessionId", "agent", "reason", "path", "json", "timeoutMs", "timeout_ms"]);
25
+ const SELF_JOIN_ROLES = new Set(["coder", "reviewer", "tester", "observer"]);
26
+
27
+ export function selfProvisioningMcpInputSchema(tool) {
28
+ const allowed = tool.name === "sl.session.join" ? SELF_JOIN_INPUTS : SELF_LEAVE_INPUTS;
29
+ const schema = tool.inputSchema || {};
30
+ return {
31
+ ...schema,
32
+ properties: Object.fromEntries(Object.entries(schema.properties || {}).filter(([name]) => allowed.has(name))),
33
+ };
34
+ }
18
35
  const SENSITIVE_COMMAND_PATHS = new Set([
36
+ // Skill installation and inspection use user-selected local paths; keep them on the direct CLI.
37
+ "skills.install",
38
+ "skills.list",
39
+ // Judges reach the room through the native judge_verdict tool; both commands read a
40
+ // caller-chosen local file.
41
+ "judge.check",
42
+ "judge.verdict",
19
43
  "auth.login",
20
44
  "auth.logout",
21
45
  "auth.revoke",
@@ -316,6 +340,52 @@ function validateBridgeToolInput(tool = {}, input = {}) {
316
340
  return "";
317
341
  }
318
342
 
343
+ // The generated CLI registry requires human approval by default. These two commands have a
344
+ // narrower, server-validated policy: an agent can request its own owner-approved admission or
345
+ // unregister itself. The host supplies the caller id, never the MCP arguments. Validate again
346
+ // in the handler after the dispatcher checks it, so neither gate alone can authorize a spawn.
347
+ export function approvedSelfProvisioningMcpCall(tool, input, { env = process.env, forListing = false } = {}) {
348
+ const name = tool?.name;
349
+ if (name !== "sl.session.join" && name !== "sl.session.leave") return false;
350
+ if (validateBridgeToolDefinition(tool) || isBridgeDisabled(env)) return false;
351
+ if (tool.metadata?.blocked || tool.metadata?.blockedReason) return false;
352
+ if (tool.metadata?.approvalRequired !== true ||
353
+ tool.security?.requires_human_approval !== true || tool.security?.runtime_blocked !== true ||
354
+ tool.security?.runtime_block_reason !== "approval_required") return false;
355
+ if (tool.metadata.cliPath.join(".") !== name.slice(3)) return false;
356
+ if (tool.metadata.positional?.length !== 1 || tool.metadata.positional[0].name !== "sessionId" ||
357
+ !tool.metadata.options?.some((option) => option.name === "agent" && option.flag === "--agent")) return false;
358
+ const caller = env?.SENTINELAYER_AGENT_ID;
359
+ if (typeof caller !== "string" || !caller.trim()) return false;
360
+ try {
361
+ assertCanonicalAgentId(caller, "SENTINELAYER_AGENT_ID");
362
+ } catch {
363
+ return false;
364
+ }
365
+ if (forListing) return true;
366
+ if (!isPlainObject(input) || validateBridgeToolInput(tool, input)) return false;
367
+ const allowedInputs = name === "sl.session.join" ? SELF_JOIN_INPUTS : SELF_LEAVE_INPUTS;
368
+ if (Object.keys(input).some((key) => !allowedInputs.has(key))) return false;
369
+ if (typeof input.sessionId !== "string" || !input.sessionId.trim()) return false;
370
+ if (input.cancelAdmission !== undefined && typeof input.cancelAdmission !== "boolean") return false;
371
+ // --name is the legacy agent alias only without --agent. Beside --agent it becomes an
372
+ // untrusted display label, so the bridge refuses both together.
373
+ if (name === "sl.session.join" && input.agent !== undefined && input.name !== undefined) return false;
374
+ if (name === "sl.session.join" && input.role !== undefined && !SELF_JOIN_ROLES.has(input.role)) return false;
375
+ // Require one explicit identity and refuse ordinary joins without a goal.
376
+ const target = input.agent === undefined && name === "sl.session.join" ? input.name : input.agent;
377
+ if (typeof target !== "string" || !target.trim()) return false;
378
+ try {
379
+ assertCanonicalAgentId(target, "self-provisioning target");
380
+ } catch {
381
+ return false;
382
+ }
383
+ if (canonicalAgentId(target) !== canonicalAgentId(caller)) return false;
384
+ if (name === "sl.session.join" && input.cancelAdmission !== true &&
385
+ (typeof input.goal !== "string" || !input.goal.trim())) return false;
386
+ return true;
387
+ }
388
+
319
389
  // Positional and option values are copied into argv verbatim, and a value that starts with
320
390
  // "-" would be parsed as command-line syntax rather than as a value. The bridge does not
321
391
  // accept such values; the direct CLI remains available for them.
@@ -4,7 +4,8 @@ import process from "node:process";
4
4
 
5
5
  import { z } from "zod";
6
6
 
7
- import { resolveOutputRoot } from "../config/service.js";
7
+ import { resolveOutputContext } from "../config/service.js";
8
+ import { requireOutputFileSystem } from "../config/output-filesystem.js";
8
9
 
9
10
  /** MCP tool-registry schema version used by Sentinelayer CLI generated artifacts. */
10
11
  export const MCP_TOOL_REGISTRY_SCHEMA_VERSION = "1.0.0";
@@ -1390,14 +1391,16 @@ export function stringifyJson(value) {
1390
1391
  *
1391
1392
  * @param {string} filePath
1392
1393
  * @param {unknown} value
1393
- * @param {{ force?: boolean }} [options]
1394
+ * @param {{ force?: boolean, outputFs: ReturnType<import('../config/output-filesystem.js').createOutputFileSystem> }} options
1394
1395
  * @returns {Promise<string>}
1395
1396
  */
1396
- export async function writeJsonFile(filePath, value, { force = false } = {}) {
1397
+ export async function writeJsonFile(filePath, value, { force = false, outputFs } = {}) {
1398
+ requireOutputFileSystem(outputFs);
1397
1399
  const resolvedPath = path.resolve(filePath);
1400
+ await outputFs.assertPath(resolvedPath);
1398
1401
  if (!force) {
1399
1402
  try {
1400
- await fsp.access(resolvedPath);
1403
+ await outputFs.access(resolvedPath);
1401
1404
  throw new Error(`File already exists: ${resolvedPath}. Use --force to overwrite.`);
1402
1405
  } catch (error) {
1403
1406
  if (error && typeof error === "object" && error.code === "ENOENT") {
@@ -1409,8 +1412,8 @@ export async function writeJsonFile(filePath, value, { force = false } = {}) {
1409
1412
  }
1410
1413
  }
1411
1414
  }
1412
- await fsp.mkdir(path.dirname(resolvedPath), { recursive: true });
1413
- await fsp.writeFile(resolvedPath, stringifyJson(value), "utf-8");
1415
+ await outputFs.mkdir(path.dirname(resolvedPath), { recursive: true });
1416
+ await outputFs.writeFile(resolvedPath, stringifyJson(value), "utf-8");
1414
1417
  return resolvedPath;
1415
1418
  }
1416
1419
 
@@ -1435,8 +1438,8 @@ export async function readJsonFile(filePath) {
1435
1438
  * @param {{ cwd?: string, outputDir?: string, env?: NodeJS.ProcessEnv }} [options]
1436
1439
  * @returns {Promise<string>}
1437
1440
  */
1438
- export async function resolveDefaultMcpOutputPath({ cwd, outputDir, env } = {}) {
1439
- const outputRoot = await resolveOutputRoot({
1441
+ export async function resolveDefaultMcpOutputPath({ cwd, outputDir, env, outputContext } = {}) {
1442
+ const { outputRoot } = outputContext || await resolveOutputContext({
1440
1443
  cwd,
1441
1444
  outputDirOverride: outputDir,
1442
1445
  env,
@@ -1450,8 +1453,8 @@ export async function resolveDefaultMcpOutputPath({ cwd, outputDir, env } = {})
1450
1453
  * @param {{ cwd?: string, outputDir?: string, env?: NodeJS.ProcessEnv }} [options]
1451
1454
  * @returns {Promise<string>}
1452
1455
  */
1453
- export async function resolveDefaultAidenIdAdapterContractPath({ cwd, outputDir, env } = {}) {
1454
- const outputRoot = await resolveOutputRoot({
1456
+ export async function resolveDefaultAidenIdAdapterContractPath({ cwd, outputDir, env, outputContext } = {}) {
1457
+ const { outputRoot } = outputContext || await resolveOutputContext({
1455
1458
  cwd,
1456
1459
  outputDirOverride: outputDir,
1457
1460
  env,
@@ -1465,8 +1468,8 @@ export async function resolveDefaultAidenIdAdapterContractPath({ cwd, outputDir,
1465
1468
  * @param {{ cwd?: string, outputDir?: string, env?: NodeJS.ProcessEnv }} [options]
1466
1469
  * @returns {Promise<string>}
1467
1470
  */
1468
- export async function resolveDefaultHostedSentiSessionConnectorContractPath({ cwd, outputDir, env } = {}) {
1469
- const outputRoot = await resolveOutputRoot({
1471
+ export async function resolveDefaultHostedSentiSessionConnectorContractPath({ cwd, outputDir, env, outputContext } = {}) {
1472
+ const { outputRoot } = outputContext || await resolveOutputContext({
1470
1473
  cwd,
1471
1474
  outputDirOverride: outputDir,
1472
1475
  env,
@@ -1485,8 +1488,9 @@ export async function resolveDefaultMcpServerConfigPath({
1485
1488
  outputDir,
1486
1489
  env,
1487
1490
  serverId,
1491
+ outputContext,
1488
1492
  } = {}) {
1489
- const outputRoot = await resolveOutputRoot({
1493
+ const { outputRoot } = outputContext || await resolveOutputContext({
1490
1494
  cwd,
1491
1495
  outputDirOverride: outputDir,
1492
1496
  env,