@cohortapp/agent-sdk 2.5.1 → 2.6.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.
Files changed (107) hide show
  1. package/bin/maestro.mjs +305 -89
  2. package/bin/maestro.test.mjs +357 -48
  3. package/docs/runbooks/backup-restore.md +65 -33
  4. package/framework-features.json +4 -4
  5. package/lib/backup/policy.mjs +710 -0
  6. package/lib/backup/policy.test.mjs +305 -0
  7. package/lib/budget-escalate.mjs +133 -0
  8. package/lib/budget-escalate.test.mjs +232 -0
  9. package/lib/budget-guard.envelope.test.mjs +476 -0
  10. package/lib/budget-guard.mjs +853 -75
  11. package/lib/budget-guard.test.mjs +91 -42
  12. package/lib/cadences.mjs +33 -0
  13. package/lib/channels/orgmail/adapter.mjs +88 -3
  14. package/lib/channels/orgmail/adapter.test.mjs +137 -0
  15. package/lib/channels/repeat-suppressor.mjs +198 -0
  16. package/lib/channels/repeat-suppressor.test.mjs +134 -0
  17. package/lib/comms/receipts.mjs +297 -0
  18. package/lib/cost/ledger-row.mjs +333 -0
  19. package/lib/cost/ledger-row.test.mjs +183 -0
  20. package/lib/execution/drive.mjs +28 -1
  21. package/lib/execution/effects.mjs +191 -12
  22. package/lib/execution/effects.test.mjs +50 -11
  23. package/lib/goals/admission.mjs +13 -1
  24. package/lib/goals/admission.test.mjs +26 -1
  25. package/lib/goals/loop.mjs +13 -0
  26. package/lib/kpi-sensors.test.mjs +3 -0
  27. package/lib/mandate/cache.mjs +13 -5
  28. package/lib/mandate/derive.mjs +146 -21
  29. package/lib/mandate/derive.test.mjs +50 -6
  30. package/lib/mandate/model.mjs +32 -4
  31. package/lib/mandate/refresh.test.mjs +16 -2
  32. package/lib/mcp/server.test.mjs +12 -3
  33. package/lib/model-router/economics.mjs +107 -76
  34. package/lib/model-router/economics.test.mjs +64 -46
  35. package/lib/model-router/integration-coverage.test.mjs +39 -37
  36. package/lib/model-router/ledger.mjs +75 -22
  37. package/lib/model-router/ledger.test.mjs +35 -2
  38. package/lib/org/client.mjs +14 -0
  39. package/lib/org/cost-sync.mjs +16 -2
  40. package/lib/org/doctor.mjs +62 -1
  41. package/lib/org/doctor.test.mjs +36 -3
  42. package/lib/org/email-remedy.mjs +49 -0
  43. package/lib/org/engagement-ledger.mjs +376 -0
  44. package/lib/org/engagement-ledger.test.mjs +112 -0
  45. package/lib/org/engagement.mjs +1056 -0
  46. package/lib/org/engagement.test.mjs +739 -0
  47. package/lib/org/messaging.mjs +230 -3
  48. package/lib/org/messaging.test.mjs +110 -1
  49. package/lib/org/param-contract.mjs +56 -2
  50. package/lib/org/param-contract.test.mjs +26 -0
  51. package/lib/org/protocol.checksum +1 -1
  52. package/lib/org/protocol.mjs +5 -0
  53. package/lib/org/protocol.test.mjs +7 -1
  54. package/lib/org/tool-surface.mjs +506 -10
  55. package/lib/org/tool-surface.test.mjs +191 -7
  56. package/lib/org/ui-parity.mjs +333 -6
  57. package/lib/org/ui-parity.test.mjs +96 -3
  58. package/lib/org/work-ledger.mjs +241 -0
  59. package/lib/org/work-ledger.test.mjs +237 -0
  60. package/lib/plan/adoption-e2e.test.mjs +366 -0
  61. package/lib/plan/budget-enforcement.test.mjs +400 -0
  62. package/lib/plan/budget-runtime.mjs +215 -0
  63. package/lib/plan/compile.mjs +201 -5
  64. package/lib/plan/compile.test.mjs +19 -5
  65. package/lib/plan/emit.mjs +8 -0
  66. package/lib/plan/emit.test.mjs +18 -0
  67. package/lib/resource-governor.mjs +58 -12
  68. package/lib/resource-governor.test.mjs +41 -1
  69. package/lib/security/audit-engine.mjs +45 -8
  70. package/lib/security/audit-engine.test.mjs +35 -0
  71. package/lib/setup/enroll-from-cohort.mjs +14 -1
  72. package/lib/setup/sections/mandate.mjs +48 -7
  73. package/lib/setup/sections/mandate.test.mjs +17 -2
  74. package/lib/setup/sections/orgmail.mjs +10 -2
  75. package/lib/setup/state.mjs +83 -2
  76. package/lib/telemetry/collect.mjs +360 -20
  77. package/lib/telemetry/collect.test.mjs +266 -0
  78. package/package.json +1 -1
  79. package/scripts/cost/track-claude-usage.mjs +207 -48
  80. package/scripts/cost/track-claude-usage.test.mjs +148 -0
  81. package/scripts/daemon/agent-daemon.mjs +315 -17
  82. package/scripts/daemon/assurance-e2e.test.mjs +421 -0
  83. package/scripts/daemon/assurance.mjs +944 -0
  84. package/scripts/daemon/assurance.test.mjs +668 -0
  85. package/scripts/daemon/cadence-consumer-governance.test.mjs +56 -0
  86. package/scripts/daemon/cadence-consumer.mjs +147 -9
  87. package/scripts/daemon/cadence-consumer.test.mjs +6 -0
  88. package/scripts/daemon/cadence-handlers.mjs +158 -0
  89. package/scripts/daemon/cadence-handlers.test.mjs +64 -0
  90. package/scripts/daemon/classifier.test.mjs +18 -9
  91. package/scripts/daemon/deliver.mjs +314 -0
  92. package/scripts/daemon/dispatcher-governance.test.mjs +10 -0
  93. package/scripts/daemon/dispatcher.mjs +64 -6
  94. package/scripts/daemon/responder-cost.test.mjs +68 -0
  95. package/scripts/daemon/responder.mjs +351 -298
  96. package/scripts/local-triggers/generate-plists.test.mjs +7 -4
  97. package/scripts/maintenance/backup-run.mjs +415 -0
  98. package/scripts/maintenance/backup-to-cloud.sh +16 -116
  99. package/scripts/org/send-orgmail.mjs +16 -0
  100. package/scripts/record-receipt.sh +63 -0
  101. package/scripts/restore-from-backup.sh +14 -3
  102. package/scripts/restore-from-backup.test.mjs +8 -5
  103. package/scripts/send-email-threaded.py +47 -0
  104. package/scripts/send-sms.sh +4 -0
  105. package/scripts/send-whatsapp.sh +4 -0
  106. package/scripts/setup/init-backup.mjs +93 -38
  107. package/scripts/slack-send.sh +12 -0
@@ -24,7 +24,7 @@ import {
24
24
  executeOrgTool,
25
25
  DEFAULT_COHORT_BASE,
26
26
  } from "./tool-surface.mjs";
27
- import { methodDef, PROTOCOL_VERSION } from "./protocol.mjs";
27
+ import { methodDef, PROTOCOL_VERSION, READS } from "./protocol.mjs";
28
28
 
29
29
  // ── env hygiene ────────────────────────────────────────────────────────────
30
30
  const ENV_KEYS = [
@@ -59,13 +59,32 @@ function fakeFetch(body = { ok: true, result: { fine: true } }, { status = 200,
59
59
 
60
60
  // ── table shape ────────────────────────────────────────────────────────────
61
61
 
62
- test("table: curated 29 + 5 email + 5 artifact + 69 desk tools, snake_case names, valid access + schemas", () => {
62
+ test("table: curated 43 + 5 email + 5 artifact + 69 desk tools, snake_case names, valid access + schemas", () => {
63
63
  // 2026-08 mobile-parity delta: +5 mail (report_spam/react/move_mailbox/
64
64
  // summarise/ask) and +3 asks (files/calendar/crm) → desk 56 → 64; the
65
65
  // call-working-sessions delta adds the 5 calls-desk tools → 69.
66
66
  // The wire-contract fix adds task_assign: hq's editTaskSchema (board.updateTask)
67
67
  // has NO assignee field, so reassignment needs board.assignTask — curated 28 → 29.
68
- assert.equal(ORG_TOOLS.length, 108, "29 curated + 5 email + 5 artifact + 69 desk");
68
+ // The conversation-parity delta adds the per-message actions the human
69
+ // long-press sheet ships (react/bookmark/delete/mark_unread_from) and the two
70
+ // huddle verbs the share tools presuppose (start/join) — curated 29 → 35.
71
+ // They are ALWAYS-ON, not desk-gated: the base messaging/channel/calling
72
+ // families have been vendored since SP3.
73
+ // The macro-parity delta adds org_pulse (GET ops?pulse=1 — the founder's
74
+ // attention surface, previously human-only), org_huddle_end (which closes the
75
+ // loop org_huddle_start opened) and memory_recall — hq built memory.recall
76
+ // explicitly because "an SDK-driven seat has been reasoning off a strictly
77
+ // smaller memory than the chat lane", then shipped no tool for it, so the
78
+ // reverse-parity fix never reached the plane it was built for.
79
+ // Curated 35 → 38.
80
+ // The ENGAGEMENT delta adds the four verbs an SDK seat needs to bring somebody
81
+ // in and had no binding for, despite all four sitting in the frozen protocol
82
+ // table since it was written: messaging_open_group (channel.createConversation),
83
+ // messaging_create_channel (channel.create), messaging_add_to_channel
84
+ // (channel.addMember), and engage_colleagues — the judged path that decides
85
+ // WHETHER anyone needs involving before it opens anything at all.
86
+ // Curated 38 → 42, +1 for work_track → 43.
87
+ assert.equal(ORG_TOOLS.length, 122, "43 curated + 5 email + 5 artifact + 69 desk");
69
88
  assert.equal(ORG_TOOLS.filter((t) => t.desk).length, 69, "exactly sixty-nine desk tools");
70
89
  assert.equal(ORG_TOOLS.filter((t) => t.email).length, 5, "exactly five email tools");
71
90
  assert.equal(ORG_TOOLS.filter((t) => t.artifact).length, 5, "exactly five artifact tools");
@@ -78,6 +97,21 @@ test("table: curated 29 + 5 email + 5 artifact + 69 desk tools, snake_case names
78
97
  if (t.binding.kind === "rpc") {
79
98
  assert.ok(methodDef(t.binding.method), `${t.name} binds a real protocol method (${t.binding.method})`);
80
99
  }
100
+ // The rpc side was already pinned to the frozen table; the READ side was
101
+ // not, so a curated tool could name a read path the server does not serve
102
+ // and only fail at runtime, as a bare 404 the model cannot interpret.
103
+ if (t.binding.kind === "read") {
104
+ assert.ok(
105
+ Object.prototype.hasOwnProperty.call(READS, t.binding.path),
106
+ `${t.name} binds a real protocol read (${t.binding.path})`,
107
+ );
108
+ if (t.binding.query !== undefined) {
109
+ assert.equal(typeof t.binding.query, "object", `${t.name} binding.query is an object`);
110
+ for (const [k, v] of Object.entries(t.binding.query)) {
111
+ assert.equal(typeof v, "string", `${t.name} binding.query.${k} is a string`);
112
+ }
113
+ }
114
+ }
81
115
  }
82
116
  });
83
117
 
@@ -90,19 +124,19 @@ test("OUTBOUND_METHODS is DERIVED from outbound:true tools (messaging/email/mail
90
124
  test("email gating: family present in the vendored protocol → tools active; override excludes", () => {
91
125
  // The email family landed in the vendored protocol (sync-protocol phase 1).
92
126
  assert.equal(emailFamilyAvailable(), true, "vendored protocol carries email.send");
93
- assert.equal(getOrgTools().length, 108);
127
+ assert.equal(getOrgTools().length, 122);
94
128
  const without = getOrgTools({ emailAvailable: false });
95
- assert.equal(without.length, 103);
129
+ assert.equal(without.length, 117);
96
130
  assert.ok(!without.some((t) => t.email), "email tools excluded when family absent");
97
131
  });
98
132
 
99
133
  test("artifact gating: family present → tools active; override excludes (email precedent)", () => {
100
134
  assert.equal(artifactFamilyAvailable(), true, "vendored protocol carries artifact.act");
101
135
  const without = getOrgTools({ artifactAvailable: false });
102
- assert.equal(without.length, 103);
136
+ assert.equal(without.length, 117);
103
137
  assert.ok(!without.some((t) => t.artifact), "artifact tools excluded when family absent");
104
138
  const neither = getOrgTools({ emailAvailable: false, artifactAvailable: false, desksAvailable: false });
105
- assert.equal(neither.length, 29, "all additive families off → the 29 always-on tools");
139
+ assert.equal(neither.length, 43, "all additive families off → the 43 always-on tools");
106
140
  });
107
141
 
108
142
  test("isOrgTool / orgToolDef cover the full table; unknown names rejected", () => {
@@ -188,6 +222,110 @@ test("read binding: board_ready GETs /api/v1/board.ready and normalises the bare
188
222
  assert.match(fetchImpl.calls[0].url, /\/api\/v1\/board\.ready$/);
189
223
  });
190
224
 
225
+ // ── macro pulse (the founder's attention surface, previously human-only) ────
226
+
227
+ test("org_pulse: the binding PINS ?pulse=1 — the agent never has to know the param", async () => {
228
+ const fetchImpl = fakeFetch({
229
+ chainHead: { seq: 42, rowHash: "abc" },
230
+ members: 9,
231
+ openAlerts: 2,
232
+ readyTasks: 5,
233
+ pulse: {
234
+ stats: { agentsRunning: 3, blocked: 1, decisionsToday: 2, needs: 2 },
235
+ needs: [{ id: "esc_1", severity: "high", what: "Blocked on pricing" }],
236
+ activity: [{ id: "ev_0", sentence: "Assigned a card in Growth" }],
237
+ },
238
+ });
239
+ const frame = await executeOrgTool("org_pulse", {}, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
240
+ assert.equal(frame.ok, true);
241
+ assert.equal(fetchImpl.calls[0].init.method, "GET");
242
+ // Without a pinned query this GETs the bare probe and the pulse is null — the
243
+ // whole capability turns on this one param riding the binding.
244
+ assert.match(fetchImpl.calls[0].url, /\/api\/v1\/ops\?pulse=1$/);
245
+ assert.equal(frame.result.pulse.stats.blocked, 1);
246
+ assert.equal(frame.result.pulse.needs[0].severity, "high");
247
+ });
248
+
249
+ test("org_pulse is reachable at the READ tier — not another admin-only escape hatch", () => {
250
+ const pulse = orgToolDef("org_pulse");
251
+ assert.equal(pulse.access, "read", "an ordinary agent must be able to ask what needs attention");
252
+ assert.equal(pulse.desk, undefined, "always-on: `ops` has been in READS since the first protocol");
253
+ // The failure mode the description must pre-empt: reporting "nothing needs
254
+ // attention" when the derivation actually failed.
255
+ assert.match(pulse.description, /pulse: null/);
256
+ assert.match(pulse.description, /do not report zero/);
257
+ });
258
+
259
+ test("huddle lifecycle is CLOSED: a room an agent can open is a room it can enter and shut", async () => {
260
+ const names = ORG_TOOLS.filter((t) => t.name.startsWith("org_huddle_")).map((t) => t.name).sort();
261
+ assert.deepEqual(names, ["org_huddle_end", "org_huddle_join", "org_huddle_start"]);
262
+ for (const n of names) {
263
+ const def = orgToolDef(n);
264
+ assert.equal(def.desk, undefined, `${n} is always-on (the base calling family predates the desks)`);
265
+ assert.equal(def.access, "write");
266
+ assert.ok(def.binding.method.startsWith("calling."), `${n} rides the calling family`);
267
+ }
268
+ const fetchImpl = fakeFetch({ ok: true, result: { callId: "call_1", endedAt: "2026-08-12T10:00:00.000Z" } });
269
+ const frame = await executeOrgTool("org_huddle_end", { callId: "call_1" }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
270
+ assert.equal(frame.ok, true);
271
+ assert.equal(fetchImpl.calls[0].url, "https://org.example/api/v1/calling.end");
272
+ assert.deepEqual(JSON.parse(fetchImpl.calls[0].init.body), { callId: "call_1" });
273
+ // CONFLICT means "already closed", not "you failed" — the description must
274
+ // say so, or a model retries a call it successfully ended.
275
+ assert.match(orgToolDef("org_huddle_end").description, /CONFLICT/);
276
+ });
277
+
278
+ test("org_directory advertises the derived presence lane (a field nobody is told about is unreachable)", () => {
279
+ const dir = orgToolDef("org_directory");
280
+ for (const needle of ["presence", "lane", "detail", "indisposed"]) {
281
+ assert.match(dir.description, new RegExp(needle), `org_directory names ${needle}`);
282
+ }
283
+ // The two honest-reading rules: a human seat's null is not "offline", and the
284
+ // raw client-stamped `status.ts` is not the dot.
285
+ assert.match(dir.description, /presence: null/);
286
+ assert.match(dir.description, /client-stamped/);
287
+ });
288
+
289
+ test("memory_recall: the SDK seat gets the same memory the chat lane reasons off", async () => {
290
+ const recall = orgToolDef("memory_recall");
291
+ assert.equal(recall.access, "read", "recall is read-only — it must not need a write tier");
292
+ assert.equal(recall.binding.method, "memory.recall");
293
+ // knowledge.search is a NARROWED PROJECTION of the same index; if the model is
294
+ // not told that, it keeps reaching for the smaller one out of habit.
295
+ assert.match(orgToolDef("knowledge_search").description, /memory_recall/);
296
+ // The two contracts a model gets wrong without being told: the entity pin is
297
+ // a PAIR, and an empty index is a fact, not a failure to retry.
298
+ assert.match(recall.description, /TOGETHER/);
299
+ assert.match(recall.description, /found:false/);
300
+
301
+ const fetchImpl = fakeFetch({ ok: true, result: { found: true, items: [{ id: "m1" }] } });
302
+ const frame = await executeOrgTool(
303
+ "memory_recall",
304
+ { query: "what did we decide about pricing", scope: "self", depth: "specific", k: 5 },
305
+ { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl },
306
+ );
307
+ assert.equal(frame.ok, true);
308
+ assert.equal(fetchImpl.calls[0].url, "https://org.example/api/v1/memory.recall");
309
+ assert.deepEqual(JSON.parse(fetchImpl.calls[0].init.body), {
310
+ query: "what did we decide about pricing", scope: "self", depth: "specific", k: 5,
311
+ });
312
+ // A read carries no dispatcher idempotency key.
313
+ assert.equal(fetchImpl.calls[0].init.headers["x-idempotency-key"], undefined);
314
+ });
315
+
316
+ test("email_triage exposes every facet the server accepts (mark-unread-from, muted, done)", () => {
317
+ const triage = orgToolDef("email_triage");
318
+ const props = Object.keys(triage.input_schema.properties).sort();
319
+ assert.deepEqual(props, [
320
+ "assigneeMemberId", "category", "done", "muted", "priority",
321
+ "read", "starred", "tags", "threadId", "unreadFromMessageId",
322
+ ], "the schema matches email.triage's real param set — an undocumented param is an unreachable one");
323
+ // The exclusivity rules are the server's; a model that does not know them
324
+ // burns a turn on a BAD_REQUEST it cannot diagnose.
325
+ assert.match(triage.description, /mutually exclusive/);
326
+ assert.match(triage.description, /one-verb-per-call/);
327
+ });
328
+
191
329
  test("org_events_tail: cursor + clamped limit ride the query string", async () => {
192
330
  const fetchImpl = fakeFetch({ events: [] });
193
331
  await executeOrgTool("org_events_tail", { cursor: 7, limit: 9999 }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
@@ -592,3 +730,49 @@ test("org_whoami: COHORT_AGENT_ID hint routes to the direct member GET", async (
592
730
  assert.equal(frame.ok, true);
593
731
  assert.match(fetchImpl.calls[0].url, /\/api\/v1\/org\/members\/astra\?orgId=acme/);
594
732
  });
733
+
734
+ // ── engage_colleagues: the judged path through the tool plane ──────────────
735
+
736
+ test("engage_colleagues returns an OK frame when it decides NOBODY needs involving", async () => {
737
+ // "Nobody needed involving" is an answer. Returning it as an error would teach
738
+ // the model to retry until it manages to interrupt somebody.
739
+ const f = fakeFetch({ ok: true, result: { members: [] } });
740
+ const res = await executeOrgTool(
741
+ "engage_colleagues",
742
+ { id: "W-1", title: "Refresh the digest", state: "in_progress", interestedParties: ["M-2"] },
743
+ { orgConfig: CFG, agentRoot: "/tmp/engage-tool-test", fetchImpl: f, env: {} },
744
+ );
745
+ assert.equal(res.ok, true);
746
+ assert.equal(res.result.engaged, false);
747
+ assert.equal(res.result.reasonCode, "self_contained");
748
+ assert.deepEqual(res.result.consideredNotEngaged, ["M-2"]);
749
+ assert.ok(res.result.why.length, "the reasoning comes back to the model, not just the verdict");
750
+ assert.ok(
751
+ !f.calls.some((c) => /messaging\.send/.test(c.url)),
752
+ "a self-contained decision must not put a message on the wire",
753
+ );
754
+ });
755
+
756
+ test("engage_colleagues refuses an unidentified piece of work rather than guessing", async () => {
757
+ const res = await executeOrgTool(
758
+ "engage_colleagues",
759
+ { title: "no id" },
760
+ { orgConfig: CFG, agentRoot: "/tmp/engage-tool-test", fetchImpl: fakeFetch(), env: {} },
761
+ );
762
+ assert.equal(res.ok, false);
763
+ assert.equal(res.error.code, "BAD_REQUEST");
764
+ });
765
+
766
+ test("messaging_send passes mentions straight through to the wire", async () => {
767
+ const f = fakeFetch({ ok: true, result: { id: "m1" } });
768
+ await executeOrgTool(
769
+ "messaging_send",
770
+ { channelId: "C-1", body: "Need a decision here.", mentions: ["M-2"] },
771
+ { orgConfig: CFG, agentRoot: "/tmp", fetchImpl: f, env: {}, screenImpl: async () => ({ allow: true }) },
772
+ );
773
+ const body = JSON.parse(f.calls[0].init.body);
774
+ // The model writes the obvious thing — an array of id strings — and the param
775
+ // contract coerces it at the one chokepoint. Sending the string form verbatim
776
+ // fails hq's zod and 400s the WHOLE message, tags and body alike.
777
+ assert.deepEqual(body.mentions, [{ memberId: "M-2" }]);
778
+ });
@@ -1,12 +1,40 @@
1
1
  /**
2
2
  * lib/org/ui-parity.mjs — Agent UI-parity helpers (full human-action mirror).
3
3
  *
4
- * A 1:1 mirror of the human Cohort UI: 328 named convenience wrappers over the
5
- * /v1 RPC binding, one per agent-facing org method the human app exposes (charter,
6
- * member, team, channel, persona, profile, sop, escalation, annotation, contact,
7
- * file, memory, compact, notification, decision, messaging, board, invitation,
8
- * user, settings, billing, org, integration, email + the Inbox app, artifact, and
9
- * the agent desks: books, calendar, crm, directory, files, meetings.recapFile).
4
+ * A 1:1 mirror of the human Cohort UI: 359 named convenience wrappers over the
5
+ * /v1 RPC binding, one per method behind a HUMAN SURFACE — the things a person
6
+ * can do by clicking (charter, member, team, channel, persona, profile, sop,
7
+ * escalation, annotation, contact, file, memory, compact, notification,
8
+ * decision, messaging, calling, board, invitation, user, settings, billing, org,
9
+ * integration, email + the Inbox app, artifact, and the agent desks: books,
10
+ * calendar, crm, directory, files, meetings.recapFile).
11
+ *
12
+ * "ONE PER HUMAN SURFACE", NOT "one per method in the family" — and the
13
+ * difference is load-bearing, because the flat family list above reads like the
14
+ * stronger claim. Six families are deliberately PARTIAL, because part of the
15
+ * family is not a human surface at all:
16
+ *
17
+ * board — the human kanban is mirrored whole (workstreams, tasks,
18
+ * comments, attachments). The AGENT WORK KERNEL that has no
19
+ * human control (create/claim/heartbeat/complete/block/comment/
20
+ * decompose/assign/link/requestReview/resolveReview) is the
21
+ * daemon's own loop and lives in the curated tool table.
22
+ * decision — the human ledger acts (comment/sign/reverse/requestAdjustment/
23
+ * listComments). propose/adopt/supersede are the §0.4
24
+ * control-plane acts; adopt + supersede are governance-gated.
25
+ * escalation — create/resolve/list (the escalation strip). ask/answer author
26
+ * and resolve an `OpenQuestion`, a different artifact.
27
+ * memory — the Cortex authoring lifecycle. `memory.recall` is semantic
28
+ * retrieval, exposed as the `memory_recall` curated tool.
29
+ * integration — connect/disconnect/list (the settings pane). toolsetVersion/
30
+ * listAgentTools/invokeTool are the runtime toolset plane a
31
+ * daemon polls, not a pane anyone clicks.
32
+ * meetings — recapFile (the desk verb). `meetings.record` is the knowledge
33
+ * plane's writer.
34
+ *
35
+ * `ui-parity.test.mjs` pins that list as a SELF-CLEANING ledger: an unlisted
36
+ * unwrapped method fails the build, and a listed one that later gets a wrapper
37
+ * forces its line out. So the docblock cannot quietly become aspirational again.
10
38
  * The `branding.*` (product section: Design) wrappers live in ./client.mjs beside
11
39
  * the rest of that family, not here. Every wrapper is a thin one-liner over
12
40
  * `call()` from ./client.mjs, so they inherit the SAME contract:
@@ -3234,3 +3262,302 @@ export function directoryRemoveFromList(params, o = {}) {
3234
3262
  export function directoryShareList(params, o = {}) {
3235
3263
  return call("directory.shareList", params || {}, o);
3236
3264
  }
3265
+
3266
+ // ---------------------------------------------------------------------------
3267
+ // messaging (the rest) + calling — the conversation surfaces the human app
3268
+ // ships and this module previously did not mirror.
3269
+ //
3270
+ // This file's contract is "one wrapper per agent-facing org method the human
3271
+ // app exposes". Two families were exempt from it by accident rather than by
3272
+ // policy: `messaging` carried 6 of its 15 methods (the pin/bookmark/schedule/
3273
+ // delete half) and `calling` carried NONE of its 22, so the headphones huddle
3274
+ // control, the calendar Join, host mute/lock/remove, hand-raising and call
3275
+ // reactions had no named counterpart here at all. Both are ordinary
3276
+ // messaging.write / calling.write scope — nothing about them was privileged.
3277
+ // ---------------------------------------------------------------------------
3278
+
3279
+ /**
3280
+ * Post a message into a channel or DM (messaging.send). Idempotent — pass a stable `o.idempotencyKey`.
3281
+ * @param {object} params - { channelId, body, parentId?, clientMsgId? }
3282
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
3283
+ */
3284
+ export function messagingSend(params, o = {}) {
3285
+ return call("messaging.send", params || {}, o);
3286
+ }
3287
+
3288
+ /**
3289
+ * A channel's message history, newest-first (messaging.history). READ-ONLY.
3290
+ * @param {object} params - { channelId, limit?, before? }
3291
+ * @param {object} o - { base, token, fetchImpl? }
3292
+ */
3293
+ export function messagingHistory(params, o = {}) {
3294
+ return call("messaging.history", params || {}, o);
3295
+ }
3296
+
3297
+ /**
3298
+ * Full-text search across the channels this seat can see (messaging.search). READ-ONLY.
3299
+ * @param {object} params - { q, channelId?, limit? }
3300
+ * @param {object} o - { base, token, fetchImpl? }
3301
+ */
3302
+ export function messagingSearch(params, o = {}) {
3303
+ return call("messaging.search", params || {}, o);
3304
+ }
3305
+
3306
+ /**
3307
+ * The channels this seat can see (messaging.channels). READ-ONLY.
3308
+ * @param {object} params - { kind?, limit? }
3309
+ * @param {object} o - { base, token, fetchImpl? }
3310
+ */
3311
+ export function messagingChannels(params, o = {}) {
3312
+ return call("messaging.channels", params || {}, o);
3313
+ }
3314
+
3315
+ /**
3316
+ * Raise/clear the "is composing" indicator (messaging.typing). Writes NOTHING — no
3317
+ * row and no chain event; it is the one `/v1` method with no durable effect.
3318
+ * @param {object} params - { channelId, state? }
3319
+ * @param {object} o - { base, token, fetchImpl? }
3320
+ */
3321
+ export function messagingTyping(params, o = {}) {
3322
+ return call("messaging.typing", params || {}, o);
3323
+ }
3324
+
3325
+ /**
3326
+ * Add or remove YOUR reaction on a message (messaging.react). `op:add` upserts,
3327
+ * `op:remove` deletes; both are no-ops when already in that state.
3328
+ * @param {object} params - { messageId, emoji, op? }
3329
+ * @param {object} o - { base, token, fetchImpl? }
3330
+ */
3331
+ export function messagingReact(params, o = {}) {
3332
+ return call("messaging.react", params || {}, o);
3333
+ }
3334
+
3335
+ /**
3336
+ * Edit a message you authored (messaging.edit; REDACTED audit — never the body).
3337
+ * @param {object} params - { messageId, body }
3338
+ * @param {object} o - { base, token, fetchImpl? }
3339
+ */
3340
+ export function messagingEdit(params, o = {}) {
3341
+ return call("messaging.edit", params || {}, o);
3342
+ }
3343
+
3344
+ /**
3345
+ * Post a poll into a channel (messaging.createPoll). Idempotent — pass a stable `o.idempotencyKey`.
3346
+ * @param {object} params - { channelId, question, options[] }
3347
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
3348
+ */
3349
+ export function messagingCreatePoll(params, o = {}) {
3350
+ return call("messaging.createPoll", params || {}, o);
3351
+ }
3352
+
3353
+ /**
3354
+ * Cast/change your vote on a poll (messaging.votePoll). Idempotent — pass a stable `o.idempotencyKey`.
3355
+ * @param {object} params - { pollId, optionIds[] }
3356
+ * @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
3357
+ */
3358
+ export function messagingVotePoll(params, o = {}) {
3359
+ return call("messaging.votePoll", params || {}, o);
3360
+ }
3361
+
3362
+ /**
3363
+ * Open a huddle/call (calling.start) — anchored to a channel (the starter must
3364
+ * belong to it) or direct to `participantIds`. The server mints the LiveKit room
3365
+ * and returns a room+identity-scoped join token; the caller NEVER supplies one.
3366
+ * @param {object} params - { kind?, channelId?, participantIds? }
3367
+ * @param {object} o - { base, token, fetchImpl? }
3368
+ */
3369
+ export function callingStart(params, o = {}) {
3370
+ return call("calling.start", params || {}, o);
3371
+ }
3372
+
3373
+ /**
3374
+ * Join a live call (calling.join). Refused when the host has LOCKED the floor,
3375
+ * when you were removed, or when the call has ended.
3376
+ * @param {object} params - { callId, audioOnly? }
3377
+ * @param {object} o - { base, token, fetchImpl? }
3378
+ */
3379
+ export function callingJoin(params, o = {}) {
3380
+ return call("calling.join", params || {}, o);
3381
+ }
3382
+
3383
+ /**
3384
+ * End a call for everyone (calling.end).
3385
+ * @param {object} params - { callId }
3386
+ * @param {object} o - { base, token, fetchImpl? }
3387
+ */
3388
+ export function callingEnd(params, o = {}) {
3389
+ return call("calling.end", params || {}, o);
3390
+ }
3391
+
3392
+ /**
3393
+ * Invite members into a live call (calling.invite). Clears a prior removal.
3394
+ * @param {object} params - { callId, memberIds, message? }
3395
+ * @param {object} o - { base, token, fetchImpl? }
3396
+ */
3397
+ export function callingInvite(params, o = {}) {
3398
+ return call("calling.invite", params || {}, o);
3399
+ }
3400
+
3401
+ /**
3402
+ * Send an ephemeral in-call reaction (calling.react).
3403
+ * @param {object} params - { callId, emoji }
3404
+ * @param {object} o - { base, token, fetchImpl? }
3405
+ */
3406
+ export function callingReact(params, o = {}) {
3407
+ return call("calling.react", params || {}, o);
3408
+ }
3409
+
3410
+ /**
3411
+ * Raise your hand in a call (calling.raiseHand).
3412
+ * @param {object} params - { callId }
3413
+ * @param {object} o - { base, token, fetchImpl? }
3414
+ */
3415
+ export function callingRaiseHand(params, o = {}) {
3416
+ return call("calling.raiseHand", params || {}, o);
3417
+ }
3418
+
3419
+ /**
3420
+ * Lower your (or, as host, another participant's) hand (calling.lowerHand).
3421
+ * @param {object} params - { callId, memberId? }
3422
+ * @param {object} o - { base, token, fetchImpl? }
3423
+ */
3424
+ export function callingLowerHand(params, o = {}) {
3425
+ return call("calling.lowerHand", params || {}, o);
3426
+ }
3427
+
3428
+ /**
3429
+ * Mute/unmute yourself, or (as host) another participant (calling.setMute).
3430
+ * @param {object} params - { callId, memberId?, muted }
3431
+ * @param {object} o - { base, token, fetchImpl? }
3432
+ */
3433
+ export function callingSetMute(params, o = {}) {
3434
+ return call("calling.setMute", params || {}, o);
3435
+ }
3436
+
3437
+ /**
3438
+ * Lock/unlock the call floor (calling.setLock). A LOCKED floor refuses NEW joins;
3439
+ * existing participants and reconnects are unaffected.
3440
+ * @param {object} params - { callId, locked }
3441
+ * @param {object} o - { base, token, fetchImpl? }
3442
+ */
3443
+ export function callingSetLock(params, o = {}) {
3444
+ return call("calling.setLock", params || {}, o);
3445
+ }
3446
+
3447
+ /**
3448
+ * Remove a participant from a call, host-only (calling.removeParticipant).
3449
+ * @param {object} params - { callId, memberId }
3450
+ * @param {object} o - { base, token, fetchImpl? }
3451
+ */
3452
+ export function callingRemoveParticipant(params, o = {}) {
3453
+ return call("calling.removeParticipant", params || {}, o);
3454
+ }
3455
+
3456
+ /**
3457
+ * Start recording a call (calling.recordingStart).
3458
+ * @param {object} params - { callId }
3459
+ * @param {object} o - { base, token, fetchImpl? }
3460
+ */
3461
+ export function callingRecordingStart(params, o = {}) {
3462
+ return call("calling.recordingStart", params || {}, o);
3463
+ }
3464
+
3465
+ /**
3466
+ * Stop recording a call (calling.recordingStop).
3467
+ * @param {object} params - { callId }
3468
+ * @param {object} o - { base, token, fetchImpl? }
3469
+ */
3470
+ export function callingRecordingStop(params, o = {}) {
3471
+ return call("calling.recordingStop", params || {}, o);
3472
+ }
3473
+
3474
+ /**
3475
+ * Append one transcript line to a live call (calling.addTranscriptLine).
3476
+ * @param {object} params - { callId, at, text, speakerMemberId?, name? }
3477
+ * @param {object} o - { base, token, fetchImpl? }
3478
+ */
3479
+ export function callingAddTranscriptLine(params, o = {}) {
3480
+ return call("calling.addTranscriptLine", params || {}, o);
3481
+ }
3482
+
3483
+ /**
3484
+ * Post a message into the in-call chat (calling.chat).
3485
+ * @param {object} params - { callId, body }
3486
+ * @param {object} o - { base, token, fetchImpl? }
3487
+ */
3488
+ export function callingChat(params, o = {}) {
3489
+ return call("calling.chat", params || {}, o);
3490
+ }
3491
+
3492
+ /**
3493
+ * A call's details + participant roster (calling.getDetails). READ-ONLY.
3494
+ * @param {object} params - { callId }
3495
+ * @param {object} o - { base, token, fetchImpl? }
3496
+ */
3497
+ export function callingGetDetails(params, o = {}) {
3498
+ return call("calling.getDetails", params || {}, o);
3499
+ }
3500
+
3501
+ /**
3502
+ * A call's transcript (calling.getTranscript). READ-ONLY.
3503
+ * @param {object} params - { callId }
3504
+ * @param {object} o - { base, token, fetchImpl? }
3505
+ */
3506
+ export function callingGetTranscript(params, o = {}) {
3507
+ return call("calling.getTranscript", params || {}, o);
3508
+ }
3509
+
3510
+ /**
3511
+ * Start presenting a Cohort surface on a call's stage (calling.appShareStart).
3512
+ * @param {object} params - { callId, surface{ kind, ref, title? } }
3513
+ * @param {object} o - { base, token, fetchImpl? }
3514
+ */
3515
+ export function callingAppShareStart(params, o = {}) {
3516
+ return call("calling.appShareStart", params || {}, o);
3517
+ }
3518
+
3519
+ /**
3520
+ * Drive an active share with a step batch (calling.appShareAct). The notes and
3521
+ * typed text render on every participant's tile — screen them like any outbound.
3522
+ * @param {object} params - { callId, sessionId, steps[] }
3523
+ * @param {object} o - { base, token, fetchImpl? }
3524
+ */
3525
+ export function callingAppShareAct(params, o = {}) {
3526
+ return call("calling.appShareAct", params || {}, o);
3527
+ }
3528
+
3529
+ /**
3530
+ * End an active share (calling.appShareEnd).
3531
+ * @param {object} params - { callId, sessionId }
3532
+ * @param {object} o - { base, token, fetchImpl? }
3533
+ */
3534
+ export function callingAppShareEnd(params, o = {}) {
3535
+ return call("calling.appShareEnd", params || {}, o);
3536
+ }
3537
+
3538
+ /**
3539
+ * The call's current share session, if any (calling.getAppShare). READ-ONLY.
3540
+ * @param {object} params - { callId }
3541
+ * @param {object} o - { base, token, fetchImpl? }
3542
+ */
3543
+ export function callingGetAppShare(params, o = {}) {
3544
+ return call("calling.getAppShare", params || {}, o);
3545
+ }
3546
+
3547
+ /**
3548
+ * What is currently on the call's stage, for a joining agent (calling.getVisualContext). READ-ONLY.
3549
+ * @param {object} params - { callId }
3550
+ * @param {object} o - { base, token, fetchImpl? }
3551
+ */
3552
+ export function callingGetVisualContext(params, o = {}) {
3553
+ return call("calling.getVisualContext", params || {}, o);
3554
+ }
3555
+
3556
+ /**
3557
+ * The call's event stream — joins, shares, hands, reactions (calling.getCallEvents). READ-ONLY.
3558
+ * @param {object} params - { callId, since? }
3559
+ * @param {object} o - { base, token, fetchImpl? }
3560
+ */
3561
+ export function callingGetCallEvents(params, o = {}) {
3562
+ return call("calling.getCallEvents", params || {}, o);
3563
+ }