@cohortapp/agent-sdk 2.3.2 → 2.4.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 (148) hide show
  1. package/framework-features.json +30 -0
  2. package/lib/backlog.mjs +136 -0
  3. package/lib/cadences.mjs +63 -2
  4. package/lib/cadences.test.mjs +105 -0
  5. package/lib/capability/inventory.mjs +542 -0
  6. package/lib/capability/inventory.test.mjs +232 -0
  7. package/lib/capability/probe.mjs +255 -0
  8. package/lib/channels/contract.mjs +37 -1
  9. package/lib/channels/contract.test.mjs +25 -1
  10. package/lib/claude-bin.mjs +37 -3
  11. package/lib/claude-bin.test.mjs +42 -8
  12. package/lib/execution/disposition.mjs +501 -0
  13. package/lib/execution/disposition.test.mjs +482 -0
  14. package/lib/execution/drive.mjs +352 -0
  15. package/lib/execution/drive.test.mjs +270 -0
  16. package/lib/execution/effects.mjs +340 -0
  17. package/lib/execution/effects.test.mjs +193 -0
  18. package/lib/execution/index.mjs +152 -0
  19. package/lib/execution/intake.mjs +581 -0
  20. package/lib/execution/intake.test.mjs +343 -0
  21. package/lib/execution/journal.mjs +374 -0
  22. package/lib/execution/journal.test.mjs +261 -0
  23. package/lib/execution/match.mjs +331 -0
  24. package/lib/execution/match.test.mjs +235 -0
  25. package/lib/execution/pipeline.mjs +341 -0
  26. package/lib/execution/pipeline.test.mjs +389 -0
  27. package/lib/execution/route.mjs +332 -0
  28. package/lib/execution/route.test.mjs +186 -0
  29. package/lib/execution/surface-policy.mjs +446 -0
  30. package/lib/execution/surface-policy.test.mjs +162 -0
  31. package/lib/goals/admission.mjs +209 -0
  32. package/lib/goals/admission.test.mjs +139 -0
  33. package/lib/goals/classify.mjs +206 -0
  34. package/lib/goals/classify.test.mjs +109 -0
  35. package/lib/goals/collaborate.mjs +415 -0
  36. package/lib/goals/collaborate.test.mjs +324 -0
  37. package/lib/goals/gaps.mjs +111 -0
  38. package/lib/goals/gaps.test.mjs +284 -0
  39. package/lib/goals/loop.mjs +537 -0
  40. package/lib/goals/loop.test.mjs +719 -0
  41. package/lib/identity/persona.mjs +247 -0
  42. package/lib/identity/persona.test.mjs +117 -0
  43. package/lib/kpi.mjs +469 -0
  44. package/lib/kpi.test.mjs +244 -0
  45. package/lib/mandate/audit.mjs +168 -0
  46. package/lib/mandate/audit.test.mjs +195 -0
  47. package/lib/mandate/cache.mjs +162 -0
  48. package/lib/mandate/derive.mjs +317 -0
  49. package/lib/mandate/derive.test.mjs +224 -0
  50. package/lib/mandate/model.mjs +352 -0
  51. package/lib/mandate/model.test.mjs +145 -0
  52. package/lib/mandate/refresh.mjs +187 -0
  53. package/lib/mandate/refresh.test.mjs +293 -0
  54. package/lib/mcp/server.test.mjs +4 -4
  55. package/lib/org/approvals.mjs +14 -2
  56. package/lib/org/client.mjs +58 -22
  57. package/lib/org/client.test.mjs +3 -1
  58. package/lib/org/inbound/directedness.mjs +720 -0
  59. package/lib/org/inbound/directedness.test.mjs +543 -0
  60. package/lib/org/inbound/facts.mjs +501 -0
  61. package/lib/org/inbound/facts.test.mjs +375 -0
  62. package/lib/org/inbound/hydrate.mjs +535 -0
  63. package/lib/org/inbound/hydrate.test.mjs +326 -0
  64. package/lib/org/inbound/index.mjs +233 -0
  65. package/lib/org/inbound/index.test.mjs +324 -0
  66. package/lib/org/inbound/io.mjs +141 -0
  67. package/lib/org/inbound/project.mjs +201 -0
  68. package/lib/org/inbound/project.test.mjs +287 -0
  69. package/lib/org/inbound/surfaces.mjs +257 -0
  70. package/lib/org/knowledge.mjs +10 -1
  71. package/lib/org/knowledge.test.mjs +8 -1
  72. package/lib/org/leases.mjs +5 -0
  73. package/lib/org/mesh.mjs +17 -2
  74. package/lib/org/messaging.mjs +40 -4
  75. package/lib/org/messaging.test.mjs +40 -0
  76. package/lib/org/param-contract.mjs +694 -0
  77. package/lib/org/param-contract.test.mjs +451 -0
  78. package/lib/org/protocol.checksum +1 -1
  79. package/lib/org/protocol.mjs +8 -0
  80. package/lib/org/protocol.test.mjs +5 -1
  81. package/lib/org/push.mjs +1025 -0
  82. package/lib/org/push.test.mjs +690 -0
  83. package/lib/org/tool-surface.mjs +138 -38
  84. package/lib/org/tool-surface.test.mjs +13 -8
  85. package/lib/org/typing.mjs +341 -0
  86. package/lib/org/typing.test.mjs +291 -0
  87. package/lib/plan/compile.mjs +510 -0
  88. package/lib/plan/compile.test.mjs +286 -0
  89. package/lib/plan/emit.mjs +256 -0
  90. package/lib/plan/emit.test.mjs +246 -0
  91. package/lib/plan/explain.mjs +226 -0
  92. package/lib/plan/explain.test.mjs +188 -0
  93. package/lib/plan/schema.mjs +140 -0
  94. package/lib/resource-governor.mjs +47 -1
  95. package/lib/resource-governor.test.mjs +21 -1
  96. package/lib/setup/enroll-from-cohort.mjs +84 -16
  97. package/lib/setup/enroll-from-cohort.test.mjs +43 -1
  98. package/lib/setup/sections/identity.mjs +15 -4
  99. package/lib/setup/sections/identity.test.mjs +94 -0
  100. package/lib/setup/sections/inventory.mjs +178 -0
  101. package/lib/setup/sections/inventory.test.mjs +198 -0
  102. package/lib/setup/sections/mandate.mjs +392 -0
  103. package/lib/setup/sections/mandate.test.mjs +373 -0
  104. package/lib/setup/sections/subagents.mjs +427 -0
  105. package/lib/setup/sections/subagents.test.mjs +429 -0
  106. package/lib/setup/sections/verify.mjs +121 -0
  107. package/lib/setup/sections/verify.test.mjs +175 -0
  108. package/lib/setup/sot.mjs +2 -0
  109. package/lib/subagents/cli.mjs +463 -0
  110. package/lib/subagents/cli.test.mjs +389 -0
  111. package/lib/subagents/client.mjs +373 -0
  112. package/lib/subagents/client.test.mjs +309 -0
  113. package/lib/subagents/gap.mjs +268 -0
  114. package/lib/subagents/gap.test.mjs +234 -0
  115. package/lib/subagents/lock.mjs +296 -0
  116. package/lib/subagents/lock.test.mjs +248 -0
  117. package/lib/subagents/manifest.mjs +224 -0
  118. package/lib/subagents/manifest.test.mjs +175 -0
  119. package/lib/subagents/refs.mjs +274 -0
  120. package/lib/subagents/refs.test.mjs +204 -0
  121. package/lib/subagents/resolve.mjs +455 -0
  122. package/lib/subagents/resolve.test.mjs +422 -0
  123. package/lib/subagents/schema.mjs +467 -0
  124. package/lib/subagents/schema.test.mjs +306 -0
  125. package/package.json +8 -3
  126. package/plugins/maestro-skills/.claude-plugin/marketplace.json +16 -0
  127. package/policies/ai-disclosure.yaml +42 -2
  128. package/scaffold/CLAUDE.md +16 -2
  129. package/schedules/triggers/goal-steward.md +79 -0
  130. package/scripts/ci/conformance-org-api.mjs +792 -0
  131. package/scripts/ci/conformance-org-api.test.mjs +417 -0
  132. package/scripts/daemon/agent-daemon.mjs +36 -4
  133. package/scripts/daemon/cadence-handlers.mjs +145 -1
  134. package/scripts/daemon/goal-steward-cadence.test.mjs +243 -0
  135. package/scripts/daemon/inbox-deferral.mjs +45 -2
  136. package/scripts/daemon/inbox-deferral.test.mjs +56 -0
  137. package/scripts/daemon/inbox-wake.mjs +282 -0
  138. package/scripts/daemon/inbox-wake.test.mjs +199 -0
  139. package/scripts/daemon/prompt-builder.mjs +41 -1
  140. package/scripts/daemon/typing-registry.mjs +55 -2
  141. package/scripts/daemon/typing-registry.test.mjs +25 -0
  142. package/scripts/local-triggers/generate-plists.test.mjs +5 -5
  143. package/scripts/setup/gen-subagent-manifest.mjs +95 -0
  144. package/scripts/setup/gen-subagent-manifest.test.mjs +124 -0
  145. package/scripts/setup/generate-plan.mjs +108 -0
  146. package/scripts/setup/init-capability-manifest.mjs +70 -0
  147. package/scripts/setup/init-skill-marketplace.mjs +155 -0
  148. package/scripts/setup/init-skill-marketplace.test.mjs +193 -0
@@ -431,8 +431,28 @@ export async function pullInbound(o = {}) {
431
431
  maxSeq = Math.max(Number(maxSeq) || 0, Number(ev.seq));
432
432
  }
433
433
  const directed = isDirectedAtAgent(ev, me, { myChannelIds });
434
- if (!directed) continue;
435
- events.push(toMessageEvent(ev, me));
434
+ if (directed) {
435
+ events.push(toMessageEvent(ev, me));
436
+ continue;
437
+ }
438
+ // MENTION CANDIDATE. The org-wide feed says `mentionCount` — a NUMBER —
439
+ // and deliberately does NOT name who was mentioned: it is readable by every
440
+ // agent holding `org.read`, so listing member ids there would leak the
441
+ // association graph of private rooms (the payload is a REDACTED audit row
442
+ // by design). That redaction is right, and it is also why this filter alone
443
+ // is deaf to an @mention in a space.
444
+ //
445
+ // So resolve it the ACL-safe way: mark it a candidate here and confirm it
446
+ // below against `messaging.history`, which hq gates PER MEMBER. If this
447
+ // agent cannot read that room, it gets nothing back and the candidate is
448
+ // dropped — fail-closed, with no widening of what the shared feed says.
449
+ const p = ev.payload || ev;
450
+ const count = Number(p.mentionCount || 0);
451
+ if (me && count > 0) {
452
+ const cand = toMessageEvent(ev, me);
453
+ cand._mentionCandidate = true;
454
+ events.push(cand);
455
+ }
436
456
  }
437
457
 
438
458
  // HYDRATE THE BODY. The org event feed is redacted by design — it carries
@@ -448,7 +468,7 @@ export async function pullInbound(o = {}) {
448
468
  //
449
469
  // Fail-open: a message we cannot hydrate is dropped rather than delivered
450
470
  // blank, because a blank inbound produces a nonsense reply in a real room.
451
- const needBody = events.filter((e) => !e.text && e.kind !== "call");
471
+ const needBody = events.filter((e) => (!e.text || e._mentionCandidate) && e.kind !== "call");
452
472
  if (needBody.length) {
453
473
  const byChannel = new Map();
454
474
  for (const e of needBody) {
@@ -471,6 +491,10 @@ export async function pullInbound(o = {}) {
471
491
  const hit = byId.get(String(e.message_id));
472
492
  if (!hit) continue;
473
493
  e.text = String(hit.body ?? hit.text ?? "");
494
+ // The ACL'd read is the ONLY place mention identities are trusted.
495
+ e._mentions = Array.isArray(hit.mentions)
496
+ ? hit.mentions.map((m) => String((m && (m.memberId || m.id)) ?? m))
497
+ : [];
474
498
  if (!e.from.name || e.from.name === e.from.id) {
475
499
  e.from.name = String(hit.authorName || hit.author?.displayName || e.from.name || "");
476
500
  }
@@ -481,7 +505,19 @@ export async function pullInbound(o = {}) {
481
505
  }
482
506
  }
483
507
 
484
- const delivered = events.filter((e) => e.kind === "call" || (e.text && e.text.trim()));
508
+ // Confirm the mention candidates against what history actually returned. A
509
+ // candidate survives ONLY if this agent's id is in that message's `mentions`
510
+ // — proven by an ACL'd read, never inferred from the shared feed.
511
+ const confirmed = events.filter((e) => {
512
+ if (!e._mentionCandidate) return true;
513
+ const ok = Array.isArray(e._mentions) && e._mentions.map(String).includes(String(me));
514
+ if (!ok) return false;
515
+ e.reason = "mention";
516
+ return true;
517
+ });
518
+ for (const e of confirmed) { delete e._mentionCandidate; delete e._mentions; }
519
+
520
+ const delivered = confirmed.filter((e) => e.kind === "call" || (e.text && e.text.trim()));
485
521
  return { events: delivered, nextCursor: raw.length ? maxSeq : cursor };
486
522
  }
487
523
 
@@ -313,3 +313,43 @@ test("isDirectedAtAgent: a call event in my room is an invite", () => {
313
313
  payload: { channelId: "room-1", topic: "standup" } };
314
314
  assert.equal(isDirectedAtAgent(ev, "agent-self", { myChannelIds: mine }), true);
315
315
  });
316
+
317
+ test("isDirectedAtAgent: the SHARED feed never names who was mentioned", () => {
318
+ // hq's chain payload is a REDACTED audit row read by every agent holding
319
+ // `org.read`, so it carries `mentionCount` and deliberately NOT the member
320
+ // ids — listing them would leak the association graph of private rooms. This
321
+ // filter therefore cannot resolve a space @mention on its own, BY DESIGN;
322
+ // pullInbound confirms candidates against the per-member `messaging.history`
323
+ // read instead. Asserted so nobody "fixes" the deafness by widening the feed.
324
+ const shared = {
325
+ family: "messaging", kind: "send", entityId: "msg_1", actor: "human-1",
326
+ payload: { channelId: "space-general", channelKind: "PUBLIC", mentionCount: 1 },
327
+ };
328
+ assert.equal(isDirectedAtAgent(shared, "agent-self"), false);
329
+ });
330
+
331
+ test("isDirectedAtAgent: an explicit mentions[] still wins when a producer supplies one", () => {
332
+ // hq's chain payload used to carry `mentionCount` — a NUMBER — so an agent
333
+ // tailing the feed could see that a mention happened but not that it was the
334
+ // one mentioned. Combined with hq deferring to live daemons, an @mention in a
335
+ // public space reached nobody: the server stayed quiet and the agent was deaf.
336
+ // hq now emits `mentions: [memberId…]`, which this filter already understood.
337
+ const ev = {
338
+ seq: 10500,
339
+ family: "messaging",
340
+ kind: "send",
341
+ entityId: "msg_1",
342
+ actor: "human-1",
343
+ payload: {
344
+ channelId: "space-general",
345
+ channelKind: "PUBLIC",
346
+ mentions: ["agent-self"],
347
+ mentionCount: 1,
348
+ recipientCount: 12,
349
+ },
350
+ };
351
+ // Membership is NOT required for a mention — being named is the invitation.
352
+ assert.equal(isDirectedAtAgent(ev, "agent-self"), true);
353
+ // …and someone else's mention in the same space is still not ours.
354
+ assert.equal(isDirectedAtAgent(ev, "agent-other"), false);
355
+ });