@oneie/sdk 0.9.0 → 0.11.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 (53) hide show
  1. package/README.md +3 -0
  2. package/dist/auth.d.ts +1 -1
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +2 -0
  5. package/dist/auth.js.map +1 -1
  6. package/dist/billing.d.ts +49 -0
  7. package/dist/billing.d.ts.map +1 -0
  8. package/dist/billing.js +49 -0
  9. package/dist/billing.js.map +1 -0
  10. package/dist/brain.d.ts +36 -0
  11. package/dist/brain.d.ts.map +1 -0
  12. package/dist/brain.js +99 -0
  13. package/dist/brain.js.map +1 -0
  14. package/dist/broadcast.d.ts +37 -0
  15. package/dist/broadcast.d.ts.map +1 -0
  16. package/dist/broadcast.js +7 -0
  17. package/dist/broadcast.js.map +1 -0
  18. package/dist/client.d.ts +28 -34
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +36 -26
  21. package/dist/client.js.map +1 -1
  22. package/dist/index.d.ts +10 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/market.d.ts +63 -0
  27. package/dist/market.d.ts.map +1 -0
  28. package/dist/market.js +92 -0
  29. package/dist/market.js.map +1 -0
  30. package/dist/meta.d.ts +45 -0
  31. package/dist/meta.d.ts.map +1 -0
  32. package/dist/meta.js +44 -0
  33. package/dist/meta.js.map +1 -0
  34. package/dist/openapi.d.ts +27 -0
  35. package/dist/openapi.d.ts.map +1 -0
  36. package/dist/openapi.js +62 -0
  37. package/dist/openapi.js.map +1 -0
  38. package/dist/receivers.d.ts +1497 -0
  39. package/dist/receivers.d.ts.map +1 -0
  40. package/dist/receivers.js +1441 -0
  41. package/dist/receivers.js.map +1 -0
  42. package/dist/schemas.d.ts +52 -0
  43. package/dist/schemas.d.ts.map +1 -1
  44. package/dist/schemas.js +40 -0
  45. package/dist/schemas.js.map +1 -1
  46. package/dist/soul.d.ts +10 -0
  47. package/dist/soul.d.ts.map +1 -0
  48. package/dist/soul.js +31 -0
  49. package/dist/soul.js.map +1 -0
  50. package/dist/testing/index.js.map +1 -1
  51. package/dist/types.d.ts +18 -7
  52. package/dist/types.d.ts.map +1 -1
  53. package/package.json +26 -1
@@ -0,0 +1,1441 @@
1
+ import { z } from "zod";
2
+ import { AgentActionResponseSchema, AgentStatusResponseSchema, CapabilityItemSchema, PayResponseSchema, RegisterResponseSchema, StatsSchema, StepKind, WorkflowDiffSchema, } from "./schemas.js";
3
+ /**
4
+ * Declare a receiver contract. Identity at runtime — its only job is to pin the
5
+ * `Req`/`Res` generics so the catalog infers payload + outcome types.
6
+ */
7
+ export function receiver(r) {
8
+ return r;
9
+ }
10
+ const ok = z.object({ ok: z.boolean() });
11
+ /**
12
+ * The receiver catalog — one source of truth for capability. Object-literal keys
13
+ * are always literal, so `keyof typeof RECEIVERS` yields the declared receiver
14
+ * names and `ask<R>` infers payload + outcome by inference (no codegen).
15
+ *
16
+ * Populated incrementally: C2 = spine + 21 `world:*` + `grant-capability`;
17
+ * C4 = remaining families with metadata; C5 = the `meta:` family.
18
+ */
19
+ export const RECEIVERS = {
20
+ // ── Spine (non-world) — contracts only this cycle; route-wiring lands in C4 ──
21
+ "agent:enroll": receiver({
22
+ receiver: "agent:enroll",
23
+ summary: "Self-enroll as an agent — mint identity + API key with no prior session",
24
+ request: z.object({ name: z.string(), capabilities: z.array(z.string()).optional() }),
25
+ response: z.object({ ok: z.literal(true), actorId: z.string(), slug: z.string(), apiKey: z.string(), credits: z.number() }),
26
+ effect: "ask", auth: "public", reversible: false, idempotent: false,
27
+ }),
28
+ "auth:agent": receiver({
29
+ receiver: "auth:agent",
30
+ summary: "Become an actor — mint a uid, wallet, and scoped API key",
31
+ request: z.object({ name: z.string().optional(), uid: z.string().optional(), kind: z.string().optional() }),
32
+ response: z.object({
33
+ uid: z.string(), name: z.string(), kind: z.string(),
34
+ wallet: z.string().nullable(), apiKey: z.string(), keyId: z.string(), returning: z.boolean(),
35
+ }),
36
+ effect: "ask",
37
+ reversible: false,
38
+ idempotent: true,
39
+ }),
40
+ "agents:sync": receiver({
41
+ receiver: "agents:sync",
42
+ summary: "Declare capability — sync agent definitions, skills, and memberships",
43
+ // Accepts a single markdown body or a structured multi-agent world payload.
44
+ request: z.union([
45
+ z.object({ markdown: z.string() }),
46
+ z.object({
47
+ agents: z.array(z.object({ name: z.string().optional(), content: z.string() })),
48
+ world: z.string().optional(),
49
+ description: z.unknown().optional(),
50
+ }),
51
+ ]),
52
+ response: z.object({
53
+ ok: z.boolean(), agent: z.string().optional(), uid: z.string().optional(),
54
+ wallet: z.string().nullable().optional(), suiObjectId: z.string().nullable().optional(),
55
+ skills: z.array(z.string()).optional(), world: z.string().optional(),
56
+ }).passthrough(),
57
+ effect: "ask",
58
+ }),
59
+ // ── world:* — workspace / actor / thing / key / path / learning mutations ──
60
+ "world:create-workspace": receiver({
61
+ receiver: "world:create-workspace",
62
+ summary: "Create a workspace; optionally provision a full billing-aware client (parent + credits + brand + starter agents) in one atomic call",
63
+ request: z.object({
64
+ name: z.string(),
65
+ slug: z.string(),
66
+ ownerEmail: z.string().email().optional(),
67
+ // Extended atomic client provision (optional — omit for a bare workspace).
68
+ parent: z.string().optional(),
69
+ plan: z.enum(["free", "pro", "studio", "agency", "enterprise"]).optional(),
70
+ credit: z.number().int().nonnegative().optional(),
71
+ markup: z.number().int().min(0).max(100).optional(),
72
+ cap: z.number().int().nonnegative().nullable().optional(),
73
+ brand: z.boolean().optional(),
74
+ agents: z.array(z.string()).optional(),
75
+ }),
76
+ response: z.object({ wsid: z.string(), slug: z.string() }),
77
+ effect: "ask", auth: "manage_workspace", reversible: false,
78
+ examples: [
79
+ { name: "Acme", slug: "acme", ownerEmail: "owner@acme.com" },
80
+ { name: "Acme", slug: "acme", parent: "donal", plan: "studio", credit: 50000, markup: 20, cap: 50000, brand: true, agents: ["sales", "support"] },
81
+ ],
82
+ }),
83
+ "world:invite-client": receiver({
84
+ receiver: "world:invite-client",
85
+ summary: "Agency mints a scoped invite; on accept the invitee owns a new workspace parented under the agency",
86
+ request: z.object({
87
+ slug: z.string(),
88
+ name: z.string().optional(),
89
+ email: z.string().email().optional(),
90
+ plan: z.enum(["free", "pro", "studio", "agency", "enterprise"]).optional(),
91
+ credits: z.number().int().nonnegative().optional(),
92
+ markup: z.number().int().min(0).max(100).optional(),
93
+ cap: z.number().int().nonnegative().nullable().optional(),
94
+ brand: z.boolean().optional(),
95
+ agents: z.array(z.string()).optional(),
96
+ }),
97
+ response: z.object({ token: z.string(), id: z.string(), child: z.string(), expiresAt: z.number() }),
98
+ effect: "ask", auth: "manage_clients", reversible: false,
99
+ examples: [{ slug: "beta", email: "owner@beta.com", plan: "studio", credits: 50000, markup: 20 }],
100
+ }),
101
+ "world:accept-client-invite": receiver({
102
+ receiver: "world:accept-client-invite",
103
+ summary: "Redeem a client invite — creates the workspace owned by the invitee (single-use, replay-protected)",
104
+ request: z.object({ token: z.string(), email: z.string().email().optional(), name: z.string().optional() }),
105
+ response: z.object({ wsid: z.string(), slug: z.string(), owner: z.string(), parent: z.string() }),
106
+ effect: "ask", reversible: false, idempotent: false,
107
+ }),
108
+ "world:invite-member": receiver({
109
+ receiver: "world:invite-member",
110
+ summary: "Invite a human member to a workspace",
111
+ request: z.object({ workspace: z.string(), email: z.string().email(), role: z.string().optional() }),
112
+ response: z.object({ ok: z.boolean(), uid: z.string() }),
113
+ effect: "ask", auth: "manage_members",
114
+ }),
115
+ "world:remove-member": receiver({
116
+ receiver: "world:remove-member",
117
+ summary: "Remove a member from a workspace",
118
+ request: z.object({ workspace: z.string(), aid: z.string() }),
119
+ response: ok, effect: "ask", auth: "manage_members", reversible: false,
120
+ }),
121
+ "world:suspend-workspace": receiver({
122
+ receiver: "world:suspend-workspace",
123
+ summary: "Suspend a workspace",
124
+ request: z.object({ workspace: z.string() }),
125
+ response: ok, effect: "ask", auth: "manage_workspace",
126
+ }),
127
+ "world:create-group": receiver({
128
+ receiver: "world:create-group",
129
+ summary: "Create a group in the caller's workspace",
130
+ request: z.object({ name: z.string(), type: z.string(), tags: z.array(z.string()).optional() }),
131
+ response: z.object({ gid: z.string() }), effect: "ask", auth: "manage_groups",
132
+ }),
133
+ "world:update-group": receiver({
134
+ receiver: "world:update-group",
135
+ summary: "Update a group's name, tags, or meta",
136
+ request: z.object({ gid: z.string(), name: z.string().optional(), tags: z.array(z.string()).optional(), meta: z.unknown().optional() }),
137
+ response: ok, effect: "ask", auth: "manage_groups",
138
+ }),
139
+ "world:remove-group": receiver({
140
+ receiver: "world:remove-group",
141
+ summary: "Delete a group",
142
+ request: z.object({ gid: z.string() }), response: ok, effect: "ask", auth: "manage_groups", reversible: false,
143
+ }),
144
+ "world:create-actor": receiver({
145
+ receiver: "world:create-actor",
146
+ summary: "Create an actor (agent or human) in the world",
147
+ request: z.object({
148
+ name: z.string(), type: z.string(), group: z.string().optional(),
149
+ tags: z.array(z.string()).optional(), model: z.string().optional(), prompt: z.string().optional(),
150
+ }),
151
+ response: z.object({ aid: z.string() }), effect: "ask", auth: "manage_actors",
152
+ examples: [{ name: "scout", type: "agent" }],
153
+ }),
154
+ "world:update-actor": receiver({
155
+ receiver: "world:update-actor",
156
+ summary: "Update an actor's name, tags, prompt, or model",
157
+ request: z.object({ aid: z.string(), name: z.string().optional(), tags: z.array(z.string()).optional(), prompt: z.string().optional(), model: z.string().optional() }),
158
+ response: ok, effect: "ask", auth: "manage_actors",
159
+ }),
160
+ "world:update-contact": receiver({
161
+ receiver: "world:update-contact",
162
+ summary: "Update an actor's contact/profile attributes — identity, contact details, professional, personal, CRM metrics",
163
+ request: z.object({
164
+ aid: z.string(),
165
+ // identity
166
+ name: z.string().optional(),
167
+ firstName: z.string().optional(),
168
+ lastName: z.string().optional(),
169
+ nickname: z.string().optional(),
170
+ title: z.string().optional(),
171
+ avatar: z.string().optional(),
172
+ bio: z.string().optional(),
173
+ // contact
174
+ email: z.string().optional(),
175
+ phone: z.string().optional(),
176
+ website: z.string().optional(),
177
+ // professional
178
+ jobTitle: z.string().optional(),
179
+ company: z.string().optional(),
180
+ department: z.string().optional(),
181
+ role: z.string().optional(),
182
+ // personal
183
+ birthday: z.string().optional(),
184
+ gender: z.string().optional(),
185
+ nationality: z.string().optional(),
186
+ timezone: z.string().optional(),
187
+ // address
188
+ street: z.string().optional(),
189
+ city: z.string().optional(),
190
+ region: z.string().optional(),
191
+ postcode: z.string().optional(),
192
+ country: z.string().optional(),
193
+ // crm
194
+ lifecycle: z.string().optional(),
195
+ source: z.string().optional(),
196
+ // numeric crm metrics
197
+ value: z.number().optional(),
198
+ fitScore: z.number().optional(),
199
+ nps: z.number().optional(),
200
+ csat: z.number().optional(),
201
+ }),
202
+ response: ok, effect: "ask", auth: "manage_actors",
203
+ }),
204
+ "world:remove-actor": receiver({
205
+ receiver: "world:remove-actor",
206
+ summary: "Delete an actor",
207
+ request: z.object({ aid: z.string() }), response: ok, effect: "ask", auth: "manage_actors", reversible: false,
208
+ }),
209
+ "world:create-key": receiver({
210
+ receiver: "world:create-key",
211
+ summary: "Mint a scoped API key (PAT) for an actor you own; scope is capped at the actor's role, optional expiresIn (seconds)",
212
+ request: z.object({
213
+ actor: z.string(),
214
+ scope: z.enum(["read", "write", "admin"]).optional(),
215
+ label: z.string().optional(),
216
+ expiresIn: z.number().int().positive().optional(),
217
+ }),
218
+ // Self-service: any authenticated member can mint a key for an actor they own
219
+ // (requireActorOwner is the gate). The scope ceiling prevents privilege escalation.
220
+ response: z.object({ key: z.string(), keyId: z.string(), scope: z.string() }),
221
+ effect: "ask", auth: "member", scope: "write", reversible: false,
222
+ examples: [{ actor: "01ABC", scope: "write", label: "cli" }],
223
+ }),
224
+ "world:list-keys": receiver({
225
+ receiver: "world:list-keys",
226
+ summary: "List active API keys (metadata only — never the secret) for an actor you own",
227
+ request: z.object({ actor: z.string() }),
228
+ response: z.object({
229
+ keys: z.array(z.object({
230
+ keyId: z.string(),
231
+ label: z.string().nullable(),
232
+ scope: z.string(),
233
+ createdAt: z.number(),
234
+ validTo: z.number().nullable(),
235
+ })),
236
+ }),
237
+ effect: "ask", auth: "member", scope: "read", cost: "free", idempotent: true,
238
+ }),
239
+ "world:revoke-key": receiver({
240
+ receiver: "world:revoke-key",
241
+ summary: "Revoke an API key",
242
+ request: z.object({ keyId: z.string() }), response: ok, effect: "ask", auth: "member", scope: "write", reversible: false,
243
+ }),
244
+ "world:create-thing": receiver({
245
+ receiver: "world:create-thing",
246
+ summary: "Create a thing (skill, token, product) in the world",
247
+ request: z.object({ name: z.string(), type: z.string(), group: z.string().optional(), tags: z.array(z.string()).optional(), price: z.number().optional() }),
248
+ response: z.object({ tid: z.string() }), effect: "ask", auth: "manage_things",
249
+ }),
250
+ "world:update-thing": receiver({
251
+ receiver: "world:update-thing",
252
+ summary: "Update a thing's name, tags, price, or meta",
253
+ request: z.object({ tid: z.string(), name: z.string().optional(), tags: z.array(z.string()).optional(), price: z.number().optional(), meta: z.unknown().optional() }),
254
+ response: ok, effect: "ask", auth: "manage_things",
255
+ }),
256
+ "world:remove-thing": receiver({
257
+ receiver: "world:remove-thing",
258
+ summary: "Delete a thing",
259
+ request: z.object({ tid: z.string() }), response: ok, effect: "ask", auth: "manage_things", reversible: false,
260
+ }),
261
+ "world:freeze-path": receiver({
262
+ receiver: "world:freeze-path",
263
+ summary: "Freeze a path so decay cannot weaken it",
264
+ request: z.object({ edge: z.string() }), response: ok, effect: "ask", auth: "manage_paths",
265
+ }),
266
+ "world:unfreeze-path": receiver({
267
+ receiver: "world:unfreeze-path",
268
+ summary: "Unfreeze a path",
269
+ request: z.object({ edge: z.string() }), response: ok, effect: "ask", auth: "manage_paths",
270
+ }),
271
+ "world:remove-path": receiver({
272
+ receiver: "world:remove-path",
273
+ summary: "Delete a path",
274
+ request: z.object({ edge: z.string() }), response: ok, effect: "ask", auth: "manage_paths", reversible: false,
275
+ }),
276
+ "world:create-hypothesis": receiver({
277
+ receiver: "world:create-hypothesis",
278
+ summary: "Record a hypothesis for later verification",
279
+ request: z.object({ claim: z.string(), tags: z.array(z.string()).optional(), confidence: z.number().optional() }),
280
+ response: z.object({ hid: z.string() }), effect: "ask", auth: "manage_learning",
281
+ }),
282
+ "world:promote-hypothesis": receiver({
283
+ receiver: "world:promote-hypothesis",
284
+ summary: "Promote a hypothesis to confirmed",
285
+ request: z.object({ hid: z.string() }), response: ok, effect: "ask", auth: "manage_learning",
286
+ }),
287
+ "world:reject-hypothesis": receiver({
288
+ receiver: "world:reject-hypothesis",
289
+ summary: "Reject a hypothesis with a reason",
290
+ request: z.object({ hid: z.string(), reason: z.string().optional() }), response: ok, effect: "ask", auth: "manage_learning",
291
+ }),
292
+ // ── grant-capability — capability grant (intercepted in signal/[receiver].ts) ──
293
+ "grant-capability": receiver({
294
+ receiver: "grant-capability",
295
+ summary: "Issue a scoped, time-boxed capability grant to a principal",
296
+ request: z.object({
297
+ grantee: z.string().min(1),
298
+ actions: z.array(z.string()).min(1),
299
+ scope: z.string().min(1),
300
+ valid_from: z.number(),
301
+ valid_to: z.number(),
302
+ }).refine((g) => g.valid_to > g.valid_from, { message: "valid_to must be after valid_from", path: ["valid_to"] }),
303
+ response: z.object({ outcome: z.literal("granted"), id: z.string(), grantee: z.string(), scope: z.string() }),
304
+ effect: "ask", auth: "mint_capability", reversible: false,
305
+ }),
306
+ // ════════════════════════════════════════════════════════════════════════
307
+ // C4 — remaining families (sequenced by recipe: identity → groups → BUILD
308
+ // → TRADE → TRANSACT → reads). Request schemas mirror the exact client.ts
309
+ // call sites so a wrong payload is a compile error (the C3 mechanism).
310
+ // ════════════════════════════════════════════════════════════════════════
311
+ // ── identity ──
312
+ "identity:address": receiver({
313
+ receiver: "identity:address",
314
+ summary: "Resolve an actor's wallet address",
315
+ request: z.object({ uid: z.string() }),
316
+ response: z.object({ uid: z.string(), address: z.string() }),
317
+ effect: "ask", cost: "free", idempotent: true,
318
+ }),
319
+ // ── auth (human session) ──
320
+ // auth:sign-in killed (C7): Better Auth handles all human auth; signal/ask pipeline returns dissolved
321
+ "auth:sign-out": receiver({
322
+ receiver: "auth:sign-out",
323
+ summary: "End the current session",
324
+ request: z.object({}),
325
+ response: ok,
326
+ effect: "signal", idempotent: true,
327
+ }),
328
+ // board:join killed (C7): no resolver, no route, no caller
329
+ // agency:push:complete killed (C7): no resolver, no route, no caller
330
+ // agency:friction killed (C7): no resolver, no route, no caller
331
+ // ── groups ──
332
+ "groups:join": receiver({
333
+ receiver: "groups:join",
334
+ summary: "Join a group",
335
+ request: z.object({ gid: z.string() }),
336
+ response: z.object({ ok: z.boolean(), gid: z.string(), role: z.string() }),
337
+ effect: "signal", idempotent: true,
338
+ }),
339
+ "groups:leave": receiver({
340
+ receiver: "groups:leave",
341
+ summary: "Leave a group",
342
+ request: z.object({ gid: z.string() }),
343
+ response: ok,
344
+ effect: "signal", idempotent: true,
345
+ }),
346
+ "groups:invite": receiver({
347
+ receiver: "groups:invite",
348
+ summary: "Invite an actor to a group",
349
+ request: z.object({ gid: z.string(), uid: z.string(), role: z.string().optional() }),
350
+ response: ok,
351
+ effect: "signal", auth: "manage_members",
352
+ }),
353
+ "groups:members": receiver({
354
+ receiver: "groups:members",
355
+ summary: "List a group's members",
356
+ request: z.object({ gid: z.string() }),
357
+ response: z.object({
358
+ gid: z.string(),
359
+ members: z.array(z.object({ uid: z.string(), name: z.string().optional(), role: z.string() })),
360
+ }),
361
+ effect: "ask", cost: "free", idempotent: true,
362
+ }),
363
+ "billing:clients": receiver({
364
+ receiver: "billing:clients",
365
+ summary: "Agency P&L — per-client funded · burned · margin · balance · cap (margin from the ledger)",
366
+ request: z.object({}).optional(),
367
+ response: z.object({
368
+ agency: z.object({ slug: z.string(), name: z.string().nullable(), markup_pct: z.number(), pool: z.number() }).nullable(),
369
+ clients: z.array(z.object({
370
+ slug: z.string(), name: z.string().nullable(), plan: z.string(),
371
+ funded: z.number(), burned: z.number(), margin: z.number(), balance: z.number(), cap: z.number().nullable(),
372
+ })),
373
+ }),
374
+ effect: "ask", auth: "manage_clients", cost: "free", idempotent: true,
375
+ }),
376
+ // paths:bridge killed (C7): no resolver, no route, no caller
377
+ // ── subscriptions ──
378
+ "subscriptions:register": receiver({
379
+ receiver: "subscriptions:register",
380
+ summary: "Subscribe to a receiver's events by tag",
381
+ request: z.object({
382
+ receiver: z.string(),
383
+ tags: z.array(z.string()),
384
+ scope: z.enum(["private", "public"]).optional(),
385
+ }),
386
+ response: ok,
387
+ effect: "signal", idempotent: true,
388
+ }),
389
+ // ── agents (TRADE / lifecycle) ──
390
+ "agents:register": receiver({
391
+ receiver: "agents:register",
392
+ summary: "Register an agent as a sellable actor with capabilities",
393
+ request: z.object({
394
+ uid: z.string(),
395
+ kind: z.string().optional(),
396
+ capabilities: z.array(z.object({ skill: z.string(), price: z.number().optional() })).optional(),
397
+ wallet: z.string().optional(),
398
+ chain: z.string().optional(),
399
+ }),
400
+ response: RegisterResponseSchema,
401
+ effect: "ask", idempotent: true,
402
+ }),
403
+ "agents:commend": receiver({
404
+ receiver: "agents:commend",
405
+ summary: "Commend an agent — strengthen its reputation path",
406
+ request: z.object({ uid: z.string() }),
407
+ response: AgentActionResponseSchema,
408
+ effect: "signal",
409
+ }),
410
+ "agents:flag": receiver({
411
+ receiver: "agents:flag",
412
+ summary: "Flag an agent — add resistance to its reputation path",
413
+ request: z.object({ uid: z.string() }),
414
+ response: AgentActionResponseSchema,
415
+ effect: "signal",
416
+ }),
417
+ "agents:status": receiver({
418
+ receiver: "agents:status",
419
+ summary: "Set an agent active or inactive",
420
+ request: z.object({ uid: z.string(), status: z.string() }),
421
+ response: AgentStatusResponseSchema,
422
+ effect: "signal", idempotent: true,
423
+ }),
424
+ "agents:capabilities": receiver({
425
+ receiver: "agents:capabilities",
426
+ summary: "List an agent's published capabilities",
427
+ request: z.object({ uid: z.string() }),
428
+ response: z.array(CapabilityItemSchema),
429
+ effect: "ask", cost: "free", idempotent: true,
430
+ }),
431
+ // agents:deploy-on-behalf killed (C7): no resolver, no route, no caller
432
+ // ── pay (TRANSACT — onchain settlement) ──
433
+ "pay:weight": receiver({
434
+ receiver: "pay:weight",
435
+ summary: "Pay for a task by weighting the path between two actors",
436
+ request: z.object({ from: z.string(), to: z.string(), task: z.string(), amount: z.number() }),
437
+ response: PayResponseSchema,
438
+ effect: "ask", cost: "variable", settles: "onchain", reversible: false, simulatable: true,
439
+ }),
440
+ // ── market (A2A negotiation, C2) ──
441
+ "market:offer": receiver({
442
+ receiver: "market:offer",
443
+ summary: "Buyer creates a Deal at OFFER stage — initiates A2A negotiation",
444
+ request: z.object({
445
+ id: z.string().uuid().optional(),
446
+ seller: z.string().min(1),
447
+ skill: z.string().min(1),
448
+ price: z.number().nonnegative(),
449
+ currency: z.string().default("credits"),
450
+ deadline: z.string().datetime(),
451
+ }),
452
+ response: z.union([
453
+ z.object({ ok: z.literal(true), deal: z.object({ id: z.string(), stage: z.string(), version: z.number() }) }),
454
+ z.object({ ok: z.literal(false), error: z.string() }),
455
+ ]),
456
+ effect: "ask", auth: "required", reversible: true, idempotent: false,
457
+ }),
458
+ "market:counter": receiver({
459
+ receiver: "market:counter",
460
+ summary: "Seller revises deal terms — stays at OFFER, bumps version",
461
+ request: z.object({
462
+ dealId: z.string().min(1),
463
+ price: z.number().nonnegative().optional(),
464
+ deadline: z.string().datetime().optional(),
465
+ skill: z.string().optional(),
466
+ }),
467
+ response: z.union([
468
+ z.object({ ok: z.literal(true), deal: z.object({ id: z.string(), stage: z.string(), version: z.number() }) }),
469
+ z.object({ ok: z.literal(false), error: z.string() }),
470
+ ]),
471
+ effect: "ask", auth: "required", reversible: true, idempotent: false,
472
+ }),
473
+ "market:accept": receiver({
474
+ receiver: "market:accept",
475
+ summary: "Buyer accepts deal — OFFER → ESCROW",
476
+ request: z.object({ dealId: z.string().min(1) }),
477
+ response: z.union([
478
+ z.object({ ok: z.literal(true), deal: z.object({ id: z.string(), stage: z.string(), version: z.number() }) }),
479
+ z.object({ ok: z.literal(false), error: z.string() }),
480
+ ]),
481
+ effect: "ask", auth: "required", reversible: false, idempotent: false,
482
+ }),
483
+ "market:reject": receiver({
484
+ receiver: "market:reject",
485
+ summary: "Buyer rejects deal — OFFER → FADE",
486
+ request: z.object({ dealId: z.string().min(1) }),
487
+ response: z.union([
488
+ z.object({ ok: z.literal(true), deal: z.object({ id: z.string(), stage: z.string(), version: z.number() }) }),
489
+ z.object({ ok: z.literal(false), error: z.string() }),
490
+ ]),
491
+ effect: "ask", auth: "required", reversible: false, idempotent: false,
492
+ }),
493
+ "market:deliver": receiver({
494
+ receiver: "market:deliver",
495
+ summary: "Seller delivers work — EXECUTE → VERIFY",
496
+ request: z.object({ dealId: z.string().min(1), result: z.record(z.string(), z.unknown()).optional() }),
497
+ response: z.union([
498
+ z.object({ ok: z.literal(true), deal: z.object({ id: z.string(), stage: z.string(), version: z.number() }) }),
499
+ z.object({ ok: z.literal(false), error: z.string() }),
500
+ ]),
501
+ effect: "ask", auth: "required", reversible: false, idempotent: false,
502
+ }),
503
+ "market:verify": receiver({
504
+ receiver: "market:verify",
505
+ summary: "Buyer scores delivered work — VERIFY → SETTLE (≥0.65) or DISPUTE (<0.65)",
506
+ request: z.object({
507
+ dealId: z.string().min(1),
508
+ fit: z.number().min(0).max(1),
509
+ form: z.number().min(0).max(1),
510
+ truth: z.number().min(0).max(1),
511
+ taste: z.number().min(0).max(1),
512
+ }),
513
+ response: z.union([
514
+ z.object({ ok: z.literal(true), deal: z.object({ id: z.string(), stage: z.string(), version: z.number() }), scores: z.object({ fit: z.number(), form: z.number(), truth: z.number(), taste: z.number() }) }),
515
+ z.object({ ok: z.literal(false), error: z.string() }),
516
+ ]),
517
+ effect: "ask", auth: "required", reversible: false, idempotent: false,
518
+ }),
519
+ "market:settle": receiver({
520
+ receiver: "market:settle",
521
+ summary: "Release escrow payment — SETTLE → RECEIPT",
522
+ request: z.object({ dealId: z.string().min(1) }),
523
+ response: z.union([
524
+ z.object({ ok: z.literal(true), deal: z.object({ id: z.string(), stage: z.string(), version: z.number() }) }),
525
+ z.object({ ok: z.literal(false), error: z.string() }),
526
+ ]),
527
+ effect: "ask", auth: "required", reversible: false, idempotent: false,
528
+ }),
529
+ "market:dispute": receiver({
530
+ receiver: "market:dispute",
531
+ summary: "Escalate an active deal to DISPUTE — either party may call",
532
+ request: z.object({ dealId: z.string().min(1) }),
533
+ response: z.union([
534
+ z.object({ ok: z.literal(true), deal: z.object({ id: z.string(), stage: z.string(), version: z.number() }) }),
535
+ z.object({ ok: z.literal(false), error: z.string() }),
536
+ ]),
537
+ effect: "ask", auth: "required", reversible: false, idempotent: false,
538
+ }),
539
+ "market:escrow": receiver({
540
+ receiver: "market:escrow",
541
+ summary: "Buyer locks payment via pay.one.ie — ESCROW → EXECUTE on confirmed tx",
542
+ request: z.object({ dealId: z.string().min(1) }),
543
+ response: z.union([
544
+ z.object({ ok: z.literal(true), deal: z.object({ id: z.string(), stage: z.string(), version: z.number() }), escrowRef: z.string() }),
545
+ z.object({ ok: z.literal(false), error: z.string(), deal: z.object({ id: z.string(), stage: z.string() }).optional() }),
546
+ ]),
547
+ effect: "ask", auth: "required", reversible: false, idempotent: false,
548
+ }),
549
+ // ── market (TRADE) ──
550
+ "market:hire": receiver({
551
+ receiver: "market:hire",
552
+ summary: "Hire an agent for a skill — opens a chat or returns an escrow template",
553
+ request: z.object({ providerUid: z.string(), skillId: z.string(), initialMessage: z.string().optional() }),
554
+ response: z.union([
555
+ z.object({ ok: z.literal(true), groupId: z.string(), chatUrl: z.string() }),
556
+ z.object({
557
+ status: z.literal(402), code: z.string(),
558
+ escrow_template: z.record(z.string(), z.unknown()), expires_at: z.string(),
559
+ }),
560
+ ]),
561
+ effect: "ask", cost: "variable", settles: "onchain", reversible: false, simulatable: true,
562
+ }),
563
+ "market:bounty": receiver({
564
+ receiver: "market:bounty",
565
+ summary: "Post a bounty for a skill, backed by an escrow",
566
+ request: z.object({
567
+ skillId: z.string(), sellerUid: z.string(), posterUid: z.string(), price: z.number(),
568
+ tags: z.array(z.string()).optional(),
569
+ content: z.record(z.string(), z.unknown()).optional(),
570
+ rubric: z.object({
571
+ fit: z.number().optional(), form: z.number().optional(),
572
+ truth: z.number().optional(), taste: z.number().optional(),
573
+ }).optional(),
574
+ deadline: z.string().optional(),
575
+ posterUnitObjectId: z.string().optional(),
576
+ workerUnitObjectId: z.string().optional(),
577
+ pathObjectId: z.string().optional(),
578
+ }),
579
+ response: z.object({ id: z.string(), escrowId: z.string().optional(), data: z.record(z.string(), z.unknown()) }),
580
+ effect: "ask", cost: "variable", settles: "onchain", reversible: false, simulatable: true,
581
+ }),
582
+ "market:bounties": receiver({
583
+ receiver: "market:bounties",
584
+ summary: "List bounties, optionally filtered by seller or poster",
585
+ request: z.object({ seller: z.string().optional(), poster: z.string().optional() }),
586
+ response: z.array(z.object({
587
+ id: z.string(), skillId: z.string(), sellerUid: z.string(), posterUid: z.string(), price: z.number(),
588
+ tags: z.array(z.string()).optional(), deadline: z.string().optional(), status: z.string(),
589
+ })),
590
+ effect: "ask", cost: "free", idempotent: true,
591
+ }),
592
+ "market:claim": receiver({
593
+ receiver: "market:claim",
594
+ summary: "Claim an open bounty — atomically marks it picked and binds the claimant",
595
+ request: z.object({ bountyId: z.string() }),
596
+ response: z.union([
597
+ z.object({ ok: z.literal(true), bountyId: z.string(), claimantUid: z.string() }),
598
+ z.object({ ok: z.literal(false), error: z.string() }),
599
+ ]),
600
+ effect: "ask", cost: "free", reversible: false, idempotent: false,
601
+ }),
602
+ "market:list": receiver({
603
+ receiver: "market:list",
604
+ summary: "List the capability market",
605
+ request: z.object({ tag: z.string().optional(), maxPrice: z.number().optional() }),
606
+ response: z.object({ capabilities: z.array(z.unknown()) }),
607
+ effect: "ask", cost: "free", idempotent: true,
608
+ }),
609
+ // ── capabilities ──
610
+ "capabilities:publish": receiver({
611
+ receiver: "capabilities:publish",
612
+ summary: "Publish a capability (skill listing) to the market",
613
+ request: z.object({
614
+ skillId: z.string(), name: z.string(), price: z.number(),
615
+ mode: z.string().optional(), visibility: z.string().optional(), scope: z.string().optional(),
616
+ tags: z.array(z.string()).optional(),
617
+ rubricThresholds: z.object({
618
+ fit: z.number().optional(), form: z.number().optional(),
619
+ truth: z.number().optional(), taste: z.number().optional(),
620
+ }).optional(),
621
+ }),
622
+ response: z.object({ ok: z.literal(true), sid: z.string(), scope: z.string() }),
623
+ effect: "ask",
624
+ }),
625
+ // loop:close killed (C7): no resolver, no route, no caller
626
+ // ── reads (free · idempotent) ──
627
+ "signals:list": receiver({
628
+ receiver: "signals:list",
629
+ summary: "List recent signals",
630
+ request: z.object({
631
+ limit: z.number().optional(), since: z.number().optional(),
632
+ from: z.number().optional(), to: z.number().optional(),
633
+ }),
634
+ response: z.array(z.object({
635
+ id: z.string(), from: z.string(), to: z.string(), skill: z.string().optional(),
636
+ outcome: z.enum(["success", "failure", "timeout"]), revenue: z.number(), ts: z.number(),
637
+ })),
638
+ effect: "ask", cost: "free", idempotent: true,
639
+ }),
640
+ "stats:current": receiver({
641
+ receiver: "stats:current",
642
+ summary: "Current world stats — units, skills, highways, revenue, signals",
643
+ request: z.object({}),
644
+ response: StatsSchema,
645
+ effect: "ask", cost: "free", idempotent: true,
646
+ }),
647
+ "dashboard:usage": receiver({
648
+ receiver: "dashboard:usage",
649
+ summary: "Caller's plan usage — calls, agents, limits",
650
+ request: z.object({}),
651
+ response: z.object({
652
+ tier: z.string(), calls_this_month: z.number(), agents_count: z.number(),
653
+ api_limit: z.number(), agent_limit: z.number(), loops: z.array(z.string()),
654
+ highways_count: z.number(), upgrade_url: z.string(),
655
+ }),
656
+ effect: "ask", cost: "free", idempotent: true,
657
+ }),
658
+ "world:state": receiver({
659
+ receiver: "world:state",
660
+ summary: "Full world snapshot — units, edges, highways, tags, stats",
661
+ request: z.object({}),
662
+ response: z.object({}).passthrough(),
663
+ effect: "ask", cost: "free", idempotent: true,
664
+ }),
665
+ // ════════════════════════════════════════════════════════════════════════
666
+ // C5 — meta: the self-describing surface. Pure reads an agent uses to learn
667
+ // the catalog, a receiver's schema, and its own memory before acting. The
668
+ // catalog/schema handlers are pure projections over RECEIVERS (see meta.ts).
669
+ // ════════════════════════════════════════════════════════════════════════
670
+ "meta:catalog": receiver({
671
+ receiver: "meta:catalog",
672
+ summary: "List every receiver the caller can use (with cost/reversibility/settlement), or a goal's recipe",
673
+ request: z.object({ goal: z.enum(["spine", "build", "trade", "transact"]).optional() }),
674
+ response: z.union([
675
+ z.array(z.object({
676
+ receiver: z.string(), summary: z.string(), effect: z.enum(["signal", "ask"]),
677
+ auth: z.string().optional(), reversible: z.boolean().optional(),
678
+ settles: z.enum(["none", "offchain", "onchain"]).optional(),
679
+ }).passthrough()),
680
+ z.object({ goal: z.string(), recipe: z.array(z.string()) }),
681
+ ]),
682
+ effect: "ask", cost: "free", idempotent: true,
683
+ }),
684
+ "meta:schema": receiver({
685
+ receiver: "meta:schema",
686
+ summary: "JSON Schema for one receiver's request + response — read it before you call",
687
+ request: z.object({ receiver: z.string() }),
688
+ response: z.object({
689
+ receiver: z.string(), summary: z.string(),
690
+ request: z.unknown(), response: z.unknown(),
691
+ }),
692
+ effect: "ask", cost: "free", idempotent: true,
693
+ }),
694
+ "meta:recall": receiver({
695
+ receiver: "meta:recall",
696
+ summary: "Recall the caller's hypotheses (memory), optionally filtered by a search term",
697
+ request: z.object({ match: z.string().optional(), limit: z.number().optional() }),
698
+ response: z.object({
699
+ hypotheses: z.array(z.object({
700
+ hid: z.string(), claim: z.string(), status: z.string(), confidence: z.number().nullable(),
701
+ })),
702
+ }),
703
+ effect: "ask", cost: "free", idempotent: true,
704
+ }),
705
+ // ── actor discovery + messaging ──────────────────────────────────────────
706
+ "actors:find": receiver({
707
+ receiver: "actors:find",
708
+ summary: "Find agents on the marketplace by name fragment or skill — marketplace discovery only",
709
+ request: z.object({
710
+ // human is excluded: personal names/emails are PII, not marketplace data.
711
+ type: z.enum(["agent", "world"]).optional(),
712
+ search: z.string().max(100).optional(),
713
+ skill: z.string().max(100).optional(),
714
+ limit: z.number().max(100).optional(),
715
+ }),
716
+ response: z.object({
717
+ actors: z.array(z.object({ uid: z.string(), name: z.string(), type: z.string() })),
718
+ total: z.number(),
719
+ }),
720
+ effect: "ask", cost: "free", idempotent: true,
721
+ }),
722
+ "peer:message": receiver({
723
+ receiver: "peer:message",
724
+ summary: "Send an async message to another agent's inbox — instant store, no LLM, no streaming",
725
+ request: z.object({ to: z.string(), content: z.string(), from: z.string().optional() }),
726
+ response: z.object({ ok: z.boolean(), id: z.string().optional(), ts: z.number().optional(), to: z.string().optional(), from: z.string().optional(), error: z.string().optional() }),
727
+ effect: "ask", idempotent: false,
728
+ }),
729
+ "chat:send": receiver({
730
+ receiver: "chat:send",
731
+ summary: "Send a message to a group the caller is a member of — sender is always the authenticated caller",
732
+ request: z.object({
733
+ group: z.string(),
734
+ text: z.string(),
735
+ // sender is NOT in the schema — derived server-side from the authenticated caller uid.
736
+ slug: z.string().optional(),
737
+ }),
738
+ response: z.object({ ok: z.boolean(), reply: z.string().optional(), error: z.string().optional() }),
739
+ effect: "ask", auth: "member",
740
+ }),
741
+ // ── booking — one calendar, two doors (text/booking-plan.md §5) ───────────────
742
+ // Both public-bookable: a customer with no account books a slot. The provider
743
+ // side (configure a service) is the separate, gated booking:configure.
744
+ "booking:list-slots": receiver({
745
+ receiver: "booking:list-slots",
746
+ summary: "List open slots for a workspace's service on a date (or next open days)",
747
+ request: z.object({
748
+ slug: z.string(), // provider workspace
749
+ service: z.string().optional(), // service_id; omitted → the workspace's first active
750
+ date: z.string().optional(), // 'YYYY-MM-DD'; omitted → next N open days
751
+ days: z.number().int().min(1).max(14).optional(), // window size when date omitted
752
+ }),
753
+ response: z.object({
754
+ service: z.object({
755
+ service_id: z.string(), name: z.string(), duration_min: z.number(),
756
+ price: z.number(), currency: z.string(),
757
+ }).nullable(),
758
+ slots: z.array(z.object({ date: z.string(), times: z.array(z.string()) })), // CalendarCard shape
759
+ error: z.string().optional(),
760
+ }),
761
+ effect: "ask", cost: "free", idempotent: true, simulatable: false,
762
+ examples: [{ slug: "clearwater", date: "2026-06-10" }],
763
+ }),
764
+ "booking:create": receiver({
765
+ receiver: "booking:create",
766
+ summary: "Hold a slot for a customer; returns a ref and a payment intent when price > 0",
767
+ request: z.object({
768
+ slug: z.string(),
769
+ service: z.string(),
770
+ slotDate: z.string(), // 'YYYY-MM-DD'
771
+ slotTime: z.string(), // 'HH:MM' 24h, slot start
772
+ customer: z.object({ name: z.string(), email: z.string().email().optional() }),
773
+ paymentRail: z.enum(["stripe", "sui"]).optional(), // resolver overrides by caller kind
774
+ }),
775
+ response: z.object({
776
+ ok: z.boolean(),
777
+ ref: z.string().optional(),
778
+ status: z.enum(["pending", "confirmed"]).optional(),
779
+ paymentRail: z.enum(["stripe", "sui"]).nullable().optional(),
780
+ paymentIntent: z.object({ clientSecret: z.string(), amount: z.number() }).optional(), // human, price>0
781
+ escrow: z.object({ escrowId: z.string(), bounty: z.number(), deadline: z.string() }).optional(), // agent
782
+ error: z.string().optional(), // 'slot_taken' | 'closed' | 'unknown_service'
783
+ }),
784
+ effect: "ask", cost: "variable", settles: "offchain", reversible: false,
785
+ simulatable: true, idempotent: true, // idempotencyKey dedupes a retried hold
786
+ examples: [{ slug: "clearwater", service: "svc_x", slotDate: "2026-06-10",
787
+ slotTime: "15:00", customer: { name: "Sarah", email: "s@ex.com" } }],
788
+ }),
789
+ // booking:configure — the ONE provider-gated booking receiver (§10). Unlike the
790
+ // two public-bookable receivers above, the resolver re-walks
791
+ // can(actor, group, "manage_clients") server-side and UPSERTs only the caller's
792
+ // own service rows. NOT public-bookable.
793
+ "booking:configure": receiver({
794
+ receiver: "booking:configure",
795
+ summary: "Provider-gated: create or update a bookable service (hours, slot length, price, currency, tz, active)",
796
+ request: z.object({
797
+ slug: z.string(), // the workspace; the write scope (re-walked)
798
+ actorId: z.string().optional(), // re-walk subject (gateway-forwarded)
799
+ service: z.string().optional(), // service_id to update; omitted → create
800
+ name: z.string(),
801
+ duration_min: z.number().int().positive(),
802
+ price: z.number().int().nonnegative().optional(),
803
+ currency: z.string().optional(),
804
+ hours_json: z.array(z.array(z.object({ start: z.string(), end: z.string() }))), // 7 entries, 0=Sun
805
+ tz: z.string().optional(),
806
+ active: z.boolean().optional(),
807
+ }),
808
+ response: z.object({ ok: z.boolean(), service_id: z.string().optional(), error: z.string().optional() }),
809
+ effect: "ask", cost: "free", idempotent: true, reversible: true, auth: "manage_clients",
810
+ examples: [{ slug: "clearwater", name: "Consultation", duration_min: 30, price: 4000, currency: "gbp",
811
+ hours_json: [[], [{ start: "09:00", end: "17:00" }], [], [], [], [], []] }],
812
+ }),
813
+ // booking:list-bookings — the provider's own appointment list for a month (§6
814
+ // web surface). Tenant-private read: the resolver scopes to the authenticated
815
+ // owner, never the body slug, so a caller only ever sees their own workspace.
816
+ "booking:list-bookings": receiver({
817
+ receiver: "booking:list-bookings",
818
+ summary: "Provider-gated: list a workspace's bookings for a month (customer, time, service, status)",
819
+ request: z.object({
820
+ slug: z.string(), // workspace (informational; gate re-scopes to caller)
821
+ month: z.string().optional(), // 'YYYY-MM'; omitted → current month
822
+ }),
823
+ response: z.object({
824
+ bookings: z.array(z.object({
825
+ id: z.string(), ref: z.string(), service_name: z.string(),
826
+ slot_date: z.string(), slot_time: z.string(),
827
+ customer_name: z.string(), status: z.string(),
828
+ })),
829
+ error: z.string().optional(),
830
+ }),
831
+ effect: "ask", cost: "free", idempotent: true, auth: "manage_clients",
832
+ examples: [{ slug: "clearwater", month: "2026-06" }],
833
+ }),
834
+ // booking:cancel — provider dissolves a booking by ref, releasing the slot
835
+ // immediately (the partial unique index drops dissolved rows from scope, §9).
836
+ // Tenant-private write: scoped to the authenticated owner. Idempotent.
837
+ "booking:cancel": receiver({
838
+ receiver: "booking:cancel",
839
+ summary: "Provider-gated: dissolve a booking by ref, releasing the slot",
840
+ request: z.object({
841
+ slug: z.string(),
842
+ ref: z.string(),
843
+ }),
844
+ response: z.object({ ok: z.boolean(), error: z.string().optional() }),
845
+ effect: "ask", cost: "free", idempotent: true, reversible: false, auth: "manage_clients",
846
+ examples: [{ slug: "clearwater", ref: "CLE-2026-4821" }],
847
+ }),
848
+ // ── workflows — define an SOP, run it, watch it learn (text/workflows-plan.md) ──
849
+ // Every workflow op is a typed receiver. No new REST routes. apply-diff is
850
+ // simulatable: simulate=true validates the WorkflowDiff (incl. DAG acyclicity)
851
+ // and commits nothing — the chat-authoring preview path.
852
+ "workflow:list": receiver({
853
+ receiver: "workflow:list",
854
+ summary: "List a workspace's workflows (D1 mirror); pass templates=true for the public SOP catalog",
855
+ request: z.object({
856
+ slug: z.string().optional(),
857
+ templates: z.boolean().optional(),
858
+ }),
859
+ response: z.object({
860
+ workflows: z.array(z.object({
861
+ id: z.string(), name: z.string(), description: z.string().optional(), step_count: z.number(),
862
+ status: z.string(), last_run_status: z.string().nullable(),
863
+ })),
864
+ error: z.string().optional(),
865
+ }),
866
+ effect: "ask", cost: "free", idempotent: true, simulatable: false, auth: "view_workflows",
867
+ examples: [{ slug: "acme" }, { templates: true }],
868
+ }),
869
+ "workflow:get": receiver({
870
+ receiver: "workflow:get",
871
+ summary: "Fetch one workflow's full graph — steps (kind, config, position) and edges (with conditions)",
872
+ request: z.object({ workflowId: z.string() }),
873
+ response: z.object({
874
+ id: z.string(), name: z.string(), status: z.string(),
875
+ steps: z.array(z.object({
876
+ id: z.string(), kind: StepKind, name: z.string(),
877
+ config: z.string(), position: z.string().optional(),
878
+ })),
879
+ edges: z.array(z.object({
880
+ source: z.string(), target: z.string(),
881
+ strength: z.number(), resistance: z.number(),
882
+ condition: z.string().optional(),
883
+ })),
884
+ error: z.string().optional(),
885
+ }),
886
+ effect: "ask", cost: "free", idempotent: true, simulatable: false, auth: "view_workflows",
887
+ examples: [{ workflowId: "wf_abc" }],
888
+ }),
889
+ "workflow:runs": receiver({
890
+ receiver: "workflow:runs",
891
+ summary: "List recent runs of a workflow (D1 workflow_run) for the monitor + history surfaces",
892
+ request: z.object({ workflowId: z.string(), runId: z.string().optional(), limit: z.number().int().min(1).max(200).optional() }),
893
+ response: z.object({
894
+ runs: z.array(z.object({
895
+ id: z.string(), status: z.string(),
896
+ started_at: z.number(), finished_at: z.number().nullable(),
897
+ })),
898
+ events: z.array(z.object({
899
+ step_id: z.string(), kind: z.string(), status: z.string().nullable(),
900
+ latency_ms: z.number().nullable(), at: z.number(),
901
+ })).optional(),
902
+ error: z.string().optional(),
903
+ }),
904
+ effect: "ask", cost: "free", idempotent: true, simulatable: false, auth: "view_workflows",
905
+ examples: [{ workflowId: "wf_abc", limit: 20 }, { workflowId: "wf_abc", runId: "run_1" }],
906
+ }),
907
+ "workflow:create": receiver({
908
+ receiver: "workflow:create",
909
+ summary: "Create a blank workflow (group group-type=workflow) — optionally cloned from a template",
910
+ request: z.object({
911
+ slug: z.string().optional(),
912
+ name: z.string(),
913
+ description: z.string().optional(), // defaults from the cloned template
914
+ fromTemplate: z.string().optional(),
915
+ simulate: z.boolean().optional(),
916
+ }),
917
+ response: z.object({ id: z.string(), workflowId: z.string() }),
918
+ effect: "ask", cost: "free", reversible: true, idempotent: false, simulatable: true,
919
+ auth: "manage_workflows",
920
+ examples: [{ name: "Client Onboarding" }, { name: "demo", simulate: true }],
921
+ }),
922
+ "workflow:apply-diff": receiver({
923
+ receiver: "workflow:apply-diff",
924
+ summary: "Apply a WorkflowDiff (add/remove/connect/disconnect/update). simulate=true validates the DAG without persisting",
925
+ request: z.object({
926
+ workflowId: z.string(),
927
+ diff: WorkflowDiffSchema,
928
+ simulate: z.boolean().optional(),
929
+ version: z.number().int().optional(), // optimistic lock — stale → conflict
930
+ }),
931
+ response: z.object({
932
+ ok: z.boolean(),
933
+ stepCount: z.number().optional(),
934
+ idMap: z.record(z.string(), z.string()).optional(), // tempId → persisted id
935
+ error: z.string().optional(), // 'cycle_detected' | 'conflict' | 'invalid_diff'
936
+ }),
937
+ effect: "ask", cost: "free", reversible: true, idempotent: false, simulatable: true,
938
+ auth: "manage_workflows",
939
+ examples: [{ workflowId: "wf_abc", diff: { add: [{ tempId: "s1", kind: "trigger", name: "On signup", config: "{}" }] }, simulate: true }],
940
+ }),
941
+ "workflow:run": receiver({
942
+ receiver: "workflow:run",
943
+ summary: "Start a run — spawns the WorkflowRun DO, executes each step, marks path strength on traversed edges",
944
+ request: z.object({
945
+ workflowId: z.string(),
946
+ triggerPayload: z.record(z.string(), z.unknown()).optional(),
947
+ idempotencyKey: z.string().optional(),
948
+ }),
949
+ response: z.object({ runId: z.string(), status: z.string(), error: z.string().optional() }),
950
+ effect: "ask", cost: "variable", reversible: false, idempotent: true, simulatable: false, settles: "none",
951
+ auth: "manage_workflows",
952
+ examples: [{ workflowId: "wf_abc" }],
953
+ }),
954
+ "human:resolve": receiver({
955
+ receiver: "human:resolve",
956
+ summary: "Resolve a suspended human step — carries { runId, stepId, decision | formPayload } to the run DO",
957
+ request: z.object({
958
+ runId: z.string(),
959
+ stepId: z.string(),
960
+ decision: z.enum(["approved", "rejected"]).optional(),
961
+ formPayload: z.record(z.string(), z.unknown()).optional(),
962
+ // System callers (a Telegram/Discord button via channels) attest the
963
+ // workspace; an authenticated session's slug always wins over this. The
964
+ // `slug` is trusted ONLY with a valid `attestation` — HMAC(IDENTITY_SECRET,
965
+ // "human:resolve:<runId>:<stepId>:<slug>") that only channels can mint after
966
+ // it re-walks the tapper's authority. Without it, slug is ignored.
967
+ slug: z.string().optional(),
968
+ attestation: z.string().optional(),
969
+ }),
970
+ response: z.object({ ok: z.boolean(), status: z.string().optional(), error: z.string().optional() }),
971
+ effect: "ask", cost: "free", idempotent: true, simulatable: false, auth: "manage_workflows",
972
+ examples: [{ runId: "run_1", stepId: "s2", decision: "approved" }],
973
+ }),
974
+ "checkout:create": receiver({
975
+ receiver: "checkout:create",
976
+ summary: "The `sell` step's binding — mint a Connect checkout session for the buyer, stamping { runId, stepId } into session metadata so checkout:resume can wake the run",
977
+ request: z.object({
978
+ workspace: z.string(),
979
+ ppid: z.string(),
980
+ // Set by the executor when a `sell` step dispatches this — the run's resume coordinates.
981
+ runId: z.string().optional(),
982
+ stepId: z.string().optional(),
983
+ }),
984
+ response: z.object({
985
+ clientSecret: z.string().nullable().optional(),
986
+ sessionId: z.string().optional(),
987
+ applicationFeeAmount: z.number().optional(),
988
+ stripeAccount: z.string().optional(),
989
+ error: z.string().optional(),
990
+ }),
991
+ effect: "ask", cost: "variable", reversible: false, idempotent: false, simulatable: false, settles: "offchain",
992
+ auth: "manage_workflows",
993
+ examples: [{ workspace: "acme", ppid: "pr_1" }],
994
+ }),
995
+ "checkout:resume": receiver({
996
+ receiver: "checkout:resume",
997
+ summary: "Wake a run suspended at a `sell` step — the storefront webhook fires this on checkout.session.completed with the (runId, stepId) it stamped at create. Sibling of human:resolve.",
998
+ request: z.object({
999
+ runId: z.string(),
1000
+ stepId: z.string(),
1001
+ // The run's own workspace (from the session metadata the webhook reads); the
1002
+ // runId is the resume capability, re-validated against the parked run server-side.
1003
+ slug: z.string().optional(),
1004
+ }),
1005
+ response: z.object({ ok: z.boolean(), status: z.string().optional(), error: z.string().optional() }),
1006
+ effect: "signal", cost: "free", idempotent: true, simulatable: false, auth: "manage_workflows",
1007
+ examples: [{ runId: "run_1", stepId: "s2", slug: "acme" }],
1008
+ }),
1009
+ "human:notify": receiver({
1010
+ receiver: "human:notify",
1011
+ summary: "Post a channel-shaped approval into a run's conversation when it suspends at a human step — web card · Telegram inline keys · Discord components. Sibling of chat:send.",
1012
+ request: z.object({
1013
+ runId: z.string(),
1014
+ stepId: z.string(),
1015
+ stepName: z.string().optional(),
1016
+ workflowName: z.string().optional(),
1017
+ group: z.string().optional(), // conversation group; falls back to the run's started_by
1018
+ }),
1019
+ response: z.object({ ok: z.boolean(), group: z.string().optional(), error: z.string().optional() }),
1020
+ effect: "signal", cost: "free", idempotent: true, simulatable: false, auth: "manage_workflows",
1021
+ examples: [{ runId: "run_1", stepId: "s2", stepName: "Approve contract" }],
1022
+ }),
1023
+ "workflow:stop": receiver({
1024
+ receiver: "workflow:stop",
1025
+ summary: "Park a live run at its current step (status → paused); preserves the resume cursor so human:resolve or a re-run continues it",
1026
+ request: z.object({ runId: z.string() }),
1027
+ response: z.object({ ok: z.boolean(), status: z.string().optional(), error: z.string().optional() }),
1028
+ effect: "ask", cost: "free", reversible: true, idempotent: true, simulatable: false, auth: "manage_workflows",
1029
+ examples: [{ runId: "run_1" }],
1030
+ }),
1031
+ "workflow:trigger": receiver({
1032
+ receiver: "workflow:trigger",
1033
+ summary: "Fire every workspace workflow whose trigger step matches a channel source (e.g. webhook:telegram)",
1034
+ request: z.object({
1035
+ source: z.string(),
1036
+ slug: z.string().optional(),
1037
+ triggerPayload: z.record(z.string(), z.unknown()).optional(),
1038
+ }),
1039
+ response: z.object({ ok: z.boolean(), started: z.number(), runIds: z.array(z.string()) }),
1040
+ effect: "signal", cost: "variable", reversible: false, idempotent: false, simulatable: false, auth: "manage_workflows",
1041
+ examples: [{ source: "webhook:telegram", slug: "acme" }],
1042
+ }),
1043
+ "workflow:delete": receiver({
1044
+ receiver: "workflow:delete",
1045
+ summary: "Delete a workflow and all its runs, steps, and edges",
1046
+ request: z.object({ workflowId: z.string() }),
1047
+ response: z.object({ ok: z.boolean(), error: z.string().optional() }),
1048
+ effect: "ask", cost: "free", reversible: false, idempotent: true, auth: "manage_workflows",
1049
+ examples: [{ workflowId: "wf_abc" }],
1050
+ }),
1051
+ "workflow:update": receiver({
1052
+ receiver: "workflow:update",
1053
+ summary: "Rename a workflow or change its status (draft|active|paused)",
1054
+ request: z.object({ workflowId: z.string(), name: z.string().optional(), status: z.enum(["draft", "active", "paused"]).optional() }),
1055
+ response: z.object({ ok: z.boolean(), error: z.string().optional() }),
1056
+ effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "manage_workflows",
1057
+ examples: [{ workflowId: "wf_abc", name: "Renamed Workflow" }, { workflowId: "wf_abc", status: "paused" }],
1058
+ }),
1059
+ // ── The autonomy ladder's leaf runners — a workflow skill/agent step compiles
1060
+ // to one of these (workflow-executor.ts stepBinding). Both proxy to the
1061
+ // channels runtime; web owns the catalog + tenant authority (workflows-lock C2/C3).
1062
+ "skills:list": receiver({
1063
+ receiver: "skills:list",
1064
+ summary: "List the caller's workspace skill catalog (R2) — powers the canvas SkillPicker",
1065
+ request: z.object({}),
1066
+ response: z.object({
1067
+ skills: z.array(z.object({ name: z.string(), size: z.number() })),
1068
+ }),
1069
+ effect: "ask", cost: "free", idempotent: true, simulatable: false, auth: "view_workflows",
1070
+ examples: [{}],
1071
+ }),
1072
+ "skill:run": receiver({
1073
+ receiver: "skill:run",
1074
+ summary: "Run a workspace skill — loads its body from R2, runs one bounded turn via channels, returns { text }",
1075
+ request: z.object({ skill: z.string() }).catchall(z.unknown()), // extra keys = the skill's input
1076
+ response: z.object({ ok: z.boolean(), text: z.string().optional(), skill: z.string().optional(), error: z.string().optional() }),
1077
+ effect: "ask", cost: "variable", reversible: false, idempotent: false, simulatable: false, settles: "none", auth: "manage_workflows",
1078
+ examples: [{ skill: "copywriting", topic: "launch" }],
1079
+ }),
1080
+ "agent:run": receiver({
1081
+ receiver: "agent:run",
1082
+ summary: "Invoke a bound actor (optionally skill-constrained) for one bounded turn via channels; returns { text }",
1083
+ request: z.object({
1084
+ actorId: z.string(),
1085
+ skill: z.string().optional(),
1086
+ instructions: z.string().optional(),
1087
+ }).catchall(z.unknown()), // extra keys = the agent's input
1088
+ response: z.object({ ok: z.boolean(), text: z.string().optional(), actorId: z.string().optional(), error: z.string().optional() }),
1089
+ effect: "ask", cost: "variable", reversible: false, idempotent: false, simulatable: false, settles: "none", auth: "manage_workflows",
1090
+ examples: [{ actorId: "one:support", skill: "support", instructions: "Reply kindly." }],
1091
+ }),
1092
+ // ── video — room provisioning, sessions, invitations, recording (text/video-live-improve-plan.md) ──
1093
+ // Seven receivers already ship in receiver-resolvers.ts. These declarations expose
1094
+ // them to typed ask<R>/signal<R> callers, the MCP surface, and edge validation.
1095
+ // Four new receivers (quick-room, join-event, start-recording, start-stream) are
1096
+ // declared here and implemented in the same W1 batch.
1097
+ "video:create-room": receiver({
1098
+ receiver: "video:create-room",
1099
+ summary: "Create a 100ms video room for a workspace; returns the room slug and URL",
1100
+ request: z.object({
1101
+ workspace: z.string(),
1102
+ roomSlug: z.string(),
1103
+ name: z.string(),
1104
+ description: z.string().optional(),
1105
+ type: z.enum(["meeting", "classroom", "webinar"]).optional(),
1106
+ region: z.string().optional(),
1107
+ }),
1108
+ response: z.object({
1109
+ ok: z.boolean(),
1110
+ room: z.object({
1111
+ slug: z.string(), name: z.string(), description: z.string(),
1112
+ type: z.string(), hmsRoomId: z.string(), url: z.string(),
1113
+ }).optional(),
1114
+ error: z.string().optional(),
1115
+ }),
1116
+ effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
1117
+ examples: [{ workspace: "acme", roomSlug: "algebra-101", name: "Algebra 101", type: "classroom" }],
1118
+ }),
1119
+ "video:delete-room": receiver({
1120
+ receiver: "video:delete-room",
1121
+ summary: "Disable a video room and its 100ms counterpart; room is no longer joinable",
1122
+ request: z.object({ workspace: z.string(), roomSlug: z.string() }),
1123
+ response: z.object({ ok: z.boolean(), error: z.string().optional() }),
1124
+ effect: "ask", cost: "free", idempotent: true, reversible: false, auth: "manage_clients",
1125
+ examples: [{ workspace: "acme", roomSlug: "algebra-101" }],
1126
+ }),
1127
+ "video:contact-call": receiver({
1128
+ receiver: "video:contact-call",
1129
+ summary: "Provision a one-to-one video room for a contact; links the room thread to the contact CRM record",
1130
+ request: z.object({
1131
+ workspace: z.string(),
1132
+ actorId: z.string(),
1133
+ name: z.string().optional(),
1134
+ }),
1135
+ response: z.object({
1136
+ ok: z.boolean(),
1137
+ roomSlug: z.string().optional(),
1138
+ threadId: z.string().nullable().optional(),
1139
+ guestJoinUrl: z.string().optional(),
1140
+ hostJoinUrl: z.string().optional(),
1141
+ error: z.string().optional(),
1142
+ }),
1143
+ effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
1144
+ examples: [{ workspace: "acme", actorId: "actor_123", name: "Call with Sarah" }],
1145
+ }),
1146
+ "video:invite": receiver({
1147
+ receiver: "video:invite",
1148
+ summary: "Generate a tracked /go/ join link for an actor into an existing room",
1149
+ request: z.object({
1150
+ workspace: z.string(),
1151
+ roomSlug: z.string(),
1152
+ actorId: z.string().optional(),
1153
+ }),
1154
+ response: z.object({
1155
+ ok: z.boolean(),
1156
+ goUrl: z.string().optional(),
1157
+ destination: z.string().optional(),
1158
+ error: z.string().optional(),
1159
+ }),
1160
+ effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
1161
+ examples: [{ workspace: "acme", roomSlug: "algebra-101", actorId: "actor_456" }],
1162
+ }),
1163
+ "video:schedule-webinar": receiver({
1164
+ receiver: "video:schedule-webinar",
1165
+ summary: "Create a webinar room and send tracked invite links to N contacts in one call",
1166
+ request: z.object({
1167
+ workspace: z.string(),
1168
+ name: z.string().optional(),
1169
+ actorIds: z.array(z.string()).min(1).max(500),
1170
+ }),
1171
+ response: z.object({
1172
+ ok: z.boolean(),
1173
+ roomUrl: z.string().optional(),
1174
+ invited: z.number().optional(),
1175
+ links: z.array(z.object({ actorId: z.string(), goUrl: z.string() })).optional(),
1176
+ error: z.string().optional(),
1177
+ }),
1178
+ effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
1179
+ examples: [{ workspace: "acme", name: "Product Launch", actorIds: ["actor_1", "actor_2"] }],
1180
+ }),
1181
+ "video:create-session": receiver({
1182
+ receiver: "video:create-session",
1183
+ summary: "Create a video session with host + guest tracked join links; optionally schedules the call",
1184
+ request: z.object({
1185
+ workspace: z.string(),
1186
+ roomSlug: z.string(),
1187
+ name: z.string(),
1188
+ type: z.enum(["meeting", "classroom", "webinar"]).optional(),
1189
+ region: z.string().optional(),
1190
+ guestActorId: z.string().optional(),
1191
+ scheduledAt: z.number().optional(),
1192
+ }),
1193
+ response: z.object({
1194
+ ok: z.boolean(),
1195
+ room: z.object({ slug: z.string(), hmsRoomId: z.string(), url: z.string() }).optional(),
1196
+ hostJoinUrl: z.string().optional(),
1197
+ guestJoinUrl: z.string().optional(),
1198
+ error: z.string().optional(),
1199
+ }),
1200
+ effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
1201
+ examples: [{ workspace: "acme", roomSlug: "consult-abc", name: "Consultation", guestActorId: "actor_789" }],
1202
+ }),
1203
+ "video:summary": receiver({
1204
+ receiver: "video:summary",
1205
+ summary: "Run an AI summary of a call transcript and append it as a system message on the room thread",
1206
+ request: z.object({
1207
+ threadId: z.string(),
1208
+ transcript: z.string(),
1209
+ }),
1210
+ response: z.object({
1211
+ ok: z.boolean(),
1212
+ threadId: z.string().optional(),
1213
+ skipped: z.string().optional(),
1214
+ error: z.string().optional(),
1215
+ }),
1216
+ effect: "ask", cost: "variable", idempotent: false, auth: "manage_clients",
1217
+ examples: [{ threadId: "thr_abc", transcript: "Host: Hello... Guest: Hi..." }],
1218
+ }),
1219
+ "video:quick-room": receiver({
1220
+ receiver: "video:quick-room",
1221
+ summary: "Ad-hoc video room from any chat thread — creates the room, sends the join link into the thread",
1222
+ request: z.object({
1223
+ workspace: z.string(),
1224
+ threadId: z.string().optional(),
1225
+ name: z.string().optional(),
1226
+ }),
1227
+ response: z.object({
1228
+ ok: z.boolean(),
1229
+ roomSlug: z.string().optional(),
1230
+ guestJoinUrl: z.string().optional(),
1231
+ hostJoinUrl: z.string().optional(),
1232
+ threadId: z.string().nullable().optional(),
1233
+ error: z.string().optional(),
1234
+ }),
1235
+ effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
1236
+ examples: [{ workspace: "acme", threadId: "thr_xyz", name: "Quick sync" }],
1237
+ }),
1238
+ "video:join-event": receiver({
1239
+ receiver: "video:join-event",
1240
+ summary: "Record a peer join event for tracking and lifecycle; public — called from the /go/ link handler",
1241
+ request: z.object({
1242
+ workspace: z.string(),
1243
+ roomSlug: z.string(),
1244
+ role: z.string().optional(),
1245
+ actorId: z.string().optional(),
1246
+ }),
1247
+ response: z.object({ ok: z.boolean(), error: z.string().optional() }),
1248
+ effect: "signal", cost: "free", idempotent: true,
1249
+ examples: [{ workspace: "acme", roomSlug: "consult-abc", role: "guest" }],
1250
+ }),
1251
+ "video:start-recording": receiver({
1252
+ receiver: "video:start-recording",
1253
+ summary: "Start cloud recording for an active room via 100ms REST; inserts a video_recordings row",
1254
+ request: z.object({ workspace: z.string(), roomSlug: z.string() }),
1255
+ response: z.object({
1256
+ ok: z.boolean(),
1257
+ recordingId: z.string().optional(),
1258
+ error: z.string().optional(),
1259
+ }),
1260
+ effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
1261
+ examples: [{ workspace: "acme", roomSlug: "algebra-101" }],
1262
+ }),
1263
+ "video:start-stream": receiver({
1264
+ receiver: "video:start-stream",
1265
+ summary: "Start an HLS live stream for a room; returns the playback URL when the stream is ready",
1266
+ request: z.object({
1267
+ workspace: z.string(),
1268
+ roomSlug: z.string(),
1269
+ hlsConfig: z.record(z.string(), z.unknown()).optional(),
1270
+ }),
1271
+ response: z.object({
1272
+ ok: z.boolean(),
1273
+ hlsUrl: z.string().optional(),
1274
+ error: z.string().optional(),
1275
+ }),
1276
+ effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
1277
+ examples: [{ workspace: "acme", roomSlug: "product-launch" }],
1278
+ }),
1279
+ // connect:channel — token-paste connect for a channel (Connect plan C7). The
1280
+ // typed contract over the shipped /api/connect/telegram route: the caller pastes
1281
+ // a channel credential, web verifies it (getMe), points the webhook, then hands
1282
+ // the raw secret to channels' /connect sink under the owner gate. SECURITY: the
1283
+ // token rides the REQUEST only; the response NEVER echoes it. effect:ask,
1284
+ // auth:manage_clients re-walked server-side off locals.slug (never body actorId).
1285
+ "connect:channel": receiver({
1286
+ receiver: "connect:channel",
1287
+ summary: "Connect a channel (e.g. telegram) by pasting its bot token; verifies, sets the webhook, seals the credential under the owner gate",
1288
+ request: z.object({
1289
+ channel: z.string(), // 'telegram' (token-paste path)
1290
+ token: z.string(), // the bot credential — request-only, never returned/logged
1291
+ slug: z.string().optional(), // workspace; gate re-scopes to the authenticated owner
1292
+ }),
1293
+ response: z.object({
1294
+ ok: z.boolean(),
1295
+ status: z.string().optional(), // 'active' | 'updated' from the channels sink
1296
+ account: z.string().optional(), // public handle (e.g. bot username) — never the token
1297
+ error: z.string().optional(), // 'invalid token' | 'unauthorized' | 'duplicate'
1298
+ }),
1299
+ effect: "ask", cost: "free", reversible: false, idempotent: true, auth: "manage_clients",
1300
+ examples: [{ channel: "telegram", token: "<bot-token>" }],
1301
+ }),
1302
+ // connect:status — list the workspace's connected channels (Connect plan C8).
1303
+ // Read-only contract over /api/connect/status: rows come from the `connection`
1304
+ // table scoped to the authenticated owner (locals.slug — never a body slug).
1305
+ // SECURITY: the response carries channel + public account id + status only;
1306
+ // cred_ref and webhook_secret never leave the server.
1307
+ "connect:status": receiver({
1308
+ receiver: "connect:status",
1309
+ summary: "List connected channels for the workspace — channel, account, status, runtime; never the credential",
1310
+ request: z.object({}),
1311
+ response: z.object({
1312
+ ok: z.boolean(),
1313
+ connections: z
1314
+ .array(z.object({
1315
+ channel: z.string(), // 'telegram' | 'slack' | 'discord' | 'email'
1316
+ account: z.string(), // external_account_id (bot id / team id / inbound address)
1317
+ status: z.string(), // 'active' | 'revoked'
1318
+ runtime: z.string(), // 'webhook' | 'socket'
1319
+ connected_at: z.number(), // unix epoch seconds
1320
+ }))
1321
+ .optional(),
1322
+ error: z.string().optional(),
1323
+ }),
1324
+ effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "manage_clients",
1325
+ examples: [{}],
1326
+ }),
1327
+ // ── Broadcast (newsletter) ────────────────────────────────────────────────
1328
+ "broadcast:create": receiver({
1329
+ receiver: "broadcast:create",
1330
+ summary: "Create a broadcast draft — subject, body_md, audience_tag, channel",
1331
+ request: z.object({ workspace: z.string(), subject: z.string().optional(), body_md: z.string().optional(), audience_tag: z.string().optional(), channel: z.enum(["email", "sms", "whatsapp"]).optional() }),
1332
+ response: z.object({ broadcastId: z.string() }),
1333
+ effect: "ask", cost: "free", reversible: true, idempotent: false, auth: "write",
1334
+ }),
1335
+ "broadcast:update": receiver({
1336
+ receiver: "broadcast:update",
1337
+ summary: "Update a broadcast draft — subject, body, audience, schedule",
1338
+ request: z.object({ workspace: z.string(), broadcastId: z.string(), subject: z.string().optional(), body_md: z.string().optional(), audience_tag: z.string().optional(), scheduled_at: z.number().optional() }),
1339
+ response: z.object({ ok: z.boolean() }),
1340
+ effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "write",
1341
+ }),
1342
+ "broadcast:list": receiver({
1343
+ receiver: "broadcast:list",
1344
+ summary: "List broadcasts for a workspace with status and sent_at",
1345
+ request: z.object({ workspace: z.string(), status: z.enum(["draft", "scheduled", "sending", "sent", "cancelled"]).optional(), limit: z.number().int().max(100).optional() }),
1346
+ response: z.object({ broadcasts: z.array(z.object({ id: z.string(), subject: z.string(), status: z.string(), sent_at: z.number().nullable(), audience_tag: z.string().nullable() })) }),
1347
+ effect: "ask", cost: "free", reversible: false, idempotent: true, auth: "read",
1348
+ }),
1349
+ "broadcast:get": receiver({
1350
+ receiver: "broadcast:get",
1351
+ summary: "Get a single broadcast with recipient counts",
1352
+ request: z.object({ workspace: z.string(), broadcastId: z.string() }),
1353
+ response: z.object({ broadcast: z.record(z.string(), z.unknown()) }),
1354
+ effect: "ask", cost: "free", reversible: false, idempotent: true, auth: "read",
1355
+ }),
1356
+ "broadcast:test": receiver({
1357
+ receiver: "broadcast:test",
1358
+ summary: "Send a test email for a broadcast to one address",
1359
+ request: z.object({ workspace: z.string(), broadcastId: z.string(), to: z.string().email() }),
1360
+ response: z.object({ ok: z.boolean() }),
1361
+ effect: "signal", cost: "variable", reversible: false, idempotent: false, auth: "write",
1362
+ }),
1363
+ "broadcast:send": receiver({
1364
+ receiver: "broadcast:send",
1365
+ summary: "Seed recipients from audience tag, apply suppression, enqueue the send batch",
1366
+ request: z.object({ workspace: z.string(), broadcastId: z.string() }),
1367
+ response: z.object({ enqueued: z.number() }),
1368
+ effect: "signal", cost: "variable", reversible: false, idempotent: false, auth: "write",
1369
+ }),
1370
+ "broadcast:schedule": receiver({
1371
+ receiver: "broadcast:schedule",
1372
+ summary: "Schedule a broadcast to send at a future unix timestamp",
1373
+ request: z.object({ workspace: z.string(), broadcastId: z.string(), scheduled_at: z.number().int() }),
1374
+ response: z.object({ ok: z.boolean() }),
1375
+ effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "write",
1376
+ }),
1377
+ "broadcast:cancel": receiver({
1378
+ receiver: "broadcast:cancel",
1379
+ summary: "Cancel a scheduled or in-progress broadcast",
1380
+ request: z.object({ workspace: z.string(), broadcastId: z.string() }),
1381
+ response: z.object({ ok: z.boolean() }),
1382
+ effect: "ask", cost: "free", reversible: false, idempotent: true, auth: "write",
1383
+ }),
1384
+ // ── Audience (public subscribe/unsubscribe) ────────────────────────────────
1385
+ "audience:subscribe": receiver({
1386
+ receiver: "audience:subscribe",
1387
+ summary: "Begin double opt-in for a contact — writes consent:email:pending tag, sends confirm email",
1388
+ request: z.object({ workspace: z.string(), address: z.string().email(), list: z.string().optional(), ref: z.string().optional() }),
1389
+ response: z.object({ ok: z.boolean() }),
1390
+ effect: "ask", cost: "free", reversible: false, idempotent: true, auth: "public",
1391
+ }),
1392
+ "audience:unsubscribe": receiver({
1393
+ receiver: "audience:unsubscribe",
1394
+ summary: "One-click unsubscribe — writes suppression row and flips consent tag; RFC 8058 safe",
1395
+ request: z.object({ workspace: z.string(), channel: z.enum(["email", "sms", "whatsapp"]).optional(), address: z.string(), token: z.string() }),
1396
+ response: z.object({ ok: z.boolean() }),
1397
+ effect: "ask", cost: "free", reversible: false, idempotent: true, auth: "public",
1398
+ }),
1399
+ "audience:confirm": receiver({
1400
+ receiver: "audience:confirm",
1401
+ summary: "Confirm email subscription via opt-in token — flips consent tag to subscribed",
1402
+ request: z.object({ token: z.string() }),
1403
+ response: z.object({ ok: z.boolean() }),
1404
+ effect: "ask", cost: "free", reversible: false, idempotent: true, auth: "public",
1405
+ }),
1406
+ // ── Winback batch ─────────────────────────────────────────────────────────
1407
+ "audience:winback": receiver({
1408
+ receiver: "audience:winback",
1409
+ summary: "Re-engage dormant subscribers (batch winback sequence tool) — queries contacts with no send in `days` days, sends one re-engage email each through the suppression-checked send path",
1410
+ request: z.object({ days: z.number().optional(), campaign: z.string().optional() }),
1411
+ response: z.object({ ok: z.boolean(), sent: z.number(), scanned: z.number() }),
1412
+ effect: "signal", cost: "variable", reversible: false, idempotent: false, auth: "write",
1413
+ }),
1414
+ // ── Workflow tool — channel-neutral send ──────────────────────────────────
1415
+ "message:send": receiver({
1416
+ receiver: "message:send",
1417
+ summary: "Send a message to one address via channel — suppression-checked; sms/whatsapp dissolve until adapters ship",
1418
+ request: z.object({ workspace: z.string(), channel: z.enum(["email", "sms", "whatsapp"]), to: z.string(), subject: z.string().optional(), body: z.string(), campaign: z.string().optional() }),
1419
+ response: z.object({ ok: z.boolean() }),
1420
+ effect: "signal", cost: "variable", reversible: false, idempotent: false, auth: "write",
1421
+ }),
1422
+ };
1423
+ /**
1424
+ * RECIPES — the four agent journeys as typed, ordered receiver sequences (C6).
1425
+ * Each entry is `readonly ReceiverName[]`, so a typo or a renamed receiver is a
1426
+ * compile error. `meta:catalog?goal=` returns one of these; the MCP server
1427
+ * groups its tools by them. See `text/agent-first-spec-plan.md`.
1428
+ */
1429
+ export const RECIPES = {
1430
+ /** Onboard: become an actor → mint a key → join a group → declare capability. */
1431
+ spine: ["auth:agent", "world:create-key", "groups:join", "agents:sync"],
1432
+ /** Build a world: workspace → group → actor → thing. */
1433
+ build: ["world:create-workspace", "world:create-group", "world:create-actor", "world:create-thing"],
1434
+ /** Trade: list a capability → browse the market → hire → pay. */
1435
+ trade: ["capabilities:publish", "market:list", "market:hire", "pay:weight"],
1436
+ /** Transact: post a bounty → settle it onchain. */
1437
+ transact: ["market:bounty", "pay:weight"],
1438
+ /** SOP: define a workflow → shape it with a diff → run it. */
1439
+ sop: ["workflow:create", "workflow:apply-diff", "workflow:run"],
1440
+ };
1441
+ //# sourceMappingURL=receivers.js.map