@oneie/sdk 0.14.4 → 0.14.6

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.
package/dist/receivers.js CHANGED
@@ -257,6 +257,30 @@ export const RECEIVERS = {
257
257
  idempotent: true,
258
258
  examples: [{ visitorHash: "9f2a1c7b4e0d8a3f5c6b" }],
259
259
  }),
260
+ "visitor:identify": receiver({
261
+ receiver: "visitor:identify",
262
+ summary: "Every surface's front door to identify a visitor — mines free text, enriches by email, climbs the ladder, merges tenant-walled candidates, upserts the vCard.",
263
+ request: z.object({
264
+ email: z.string().optional(),
265
+ phone: z.string().optional(),
266
+ name: z.string().optional(),
267
+ handle: z.string().optional(),
268
+ channel: z.string().optional(),
269
+ sender: z.string().optional(),
270
+ visitorHash: z.string().optional(),
271
+ freeText: z.string().optional(),
272
+ source: z.enum(["door", "chat", "asked", "event"]),
273
+ }),
274
+ response: z.object({
275
+ ok: z.boolean(),
276
+ aid: z.string().optional(),
277
+ merged: z.boolean().optional(),
278
+ reason: z.string().optional(),
279
+ }),
280
+ effect: "ask",
281
+ auth: "manage_actors",
282
+ examples: [{ email: "sam@acme.com", source: "chat" }],
283
+ }),
260
284
  "world:update-contact": receiver({
261
285
  receiver: "world:update-contact",
262
286
  summary: "Update an actor's contact/profile attributes — identity, contact details, professional, personal, CRM metrics",
@@ -399,6 +423,13 @@ export const RECEIVERS = {
399
423
  summary: "Delete a thing",
400
424
  request: z.object({ tid: z.string() }), response: ok, effect: "ask", auth: "manage_things", reversible: false,
401
425
  }),
426
+ "world:publish-agent": receiver({
427
+ receiver: "world:publish-agent",
428
+ summary: "Publish an agent.md to a workspace's R2 bucket so it can be loaded by the chat UI",
429
+ request: z.object({ slug: z.string(), name: z.string(), content: z.string() }),
430
+ response: z.object({ ok: z.boolean(), url: z.string(), name: z.string(), bytes: z.number() }),
431
+ effect: "ask", auth: "update_group",
432
+ }),
402
433
  "world:list-things": receiver({
403
434
  receiver: "world:list-things",
404
435
  summary: "List things of a given type — relation pickers (browse, first 100 by name) and name resolution (pass `ids` to resolve exactly those, uncapped)",
@@ -485,6 +516,54 @@ export const RECEIVERS = {
485
516
  response: z.object({ ok: z.boolean(), target: z.string().optional() }),
486
517
  effect: "ask", auth: "session", reversible: true,
487
518
  }),
519
+ "world:in": receiver({
520
+ receiver: "world:in",
521
+ summary: "The one front door: a message enters the world inbox, a guard quarantines toxic content, the router classifies the destination (private/fanout/public), and delivers into each destined workspace. Opt-in delivery — a workspace receives only tags it subscribed.",
522
+ request: z.object({
523
+ content: z.string().min(1),
524
+ tags: z.array(z.string()).optional(),
525
+ to: z.string().optional(),
526
+ from: z.string().optional(),
527
+ }),
528
+ response: z.object({
529
+ ok: z.boolean(),
530
+ decision: z.enum(["private", "fanout", "public", "quarantine"]).optional(),
531
+ targets: z.array(z.string()).optional(),
532
+ delivered: z.array(z.string()).optional(),
533
+ guard: z.object({ toxic: z.boolean(), score: z.number(), reason: z.string() }).optional(),
534
+ note: z.string().optional(),
535
+ error: z.string().optional(),
536
+ }),
537
+ effect: "ask", auth: "session", reversible: false,
538
+ }),
539
+ "world:subscribe": receiver({
540
+ receiver: "world:subscribe",
541
+ summary: "Opt into a tag so the world:in front door routes matching messages to you. An agent self-subscribes (omit workspace/actor); a workspace you own via `workspace`; a trusted relay may register an agent via `actor`. Writes claw_paths world:sub:<tag>.",
542
+ request: z.object({ tag: z.string().min(1), workspace: z.string().optional(), actor: z.string().optional() }),
543
+ response: z.object({ ok: z.boolean(), subscriber: z.string().optional(), tag: z.string().optional(), error: z.string().optional() }),
544
+ effect: "ask", auth: "session", reversible: true,
545
+ }),
546
+ "world:unsubscribe": receiver({
547
+ receiver: "world:unsubscribe",
548
+ summary: "Leave a tag — the symmetric op to world:subscribe. Removes the claw_paths world:sub:<tag> row so the front door stops routing it to you. Same authority as subscribe.",
549
+ request: z.object({ tag: z.string().min(1), workspace: z.string().optional(), actor: z.string().optional() }),
550
+ response: z.object({ ok: z.boolean(), subscriber: z.string().optional(), tag: z.string().optional(), removed: z.number().optional(), error: z.string().optional() }),
551
+ effect: "ask", auth: "session", reversible: false,
552
+ }),
553
+ "world:subscribers": receiver({
554
+ receiver: "world:subscribers",
555
+ summary: "Discovery — who follows this tag? Returns the subscribers (agents + workspaces) of one or more tags from the world space. The world's subscriber directory: agents find each other by shared interest, then peer:message or broadcast to them. Read-only; not gated by inbox tenancy.",
556
+ request: z.object({ tag: z.string().optional(), tags: z.array(z.string()).optional() }),
557
+ response: z.object({
558
+ ok: z.boolean(),
559
+ tags: z.array(z.string()).optional(),
560
+ count: z.number().optional(),
561
+ subscribers: z.array(z.string()).optional(),
562
+ byTag: z.record(z.string(), z.array(z.string())).optional(),
563
+ error: z.string().optional(),
564
+ }),
565
+ effect: "ask", auth: "session", reversible: true,
566
+ }),
488
567
  // ════════════════════════════════════════════════════════════════════════
489
568
  // C4 — remaining families (sequenced by recipe: identity → groups → BUILD
490
569
  // → TRADE → TRANSACT → reads). Request schemas mirror the exact client.ts
@@ -547,7 +626,7 @@ export const RECEIVERS = {
547
626
  summary: "Agency P&L — per-client funded · burned · margin · balance · cap (margin from the ledger)",
548
627
  request: z.object({ slug: z.string().optional() }).optional(),
549
628
  response: z.object({
550
- agency: z.object({ slug: z.string(), name: z.string().nullable(), markup_pct: z.number(), pool: z.number() }).nullable(),
629
+ agency: z.object({ slug: z.string(), name: z.string().nullable(), markup_pct: z.number(), plan: z.string(), pool: z.number() }).nullable(),
551
630
  clients: z.array(z.object({
552
631
  slug: z.string(), name: z.string().nullable(), plan: z.string(),
553
632
  funded: z.number(), burned: z.number(), margin: z.number(), balance: z.number(), cap: z.number().nullable(),
@@ -571,6 +650,18 @@ export const RECEIVERS = {
571
650
  receiver: z.string(),
572
651
  tags: z.array(z.string()),
573
652
  scope: z.enum(["private", "public"]).optional(),
653
+ // Shared cross-tenant board — the one workspace two different ctx.ownerSlug
654
+ // identities can both land in (subscriptions.ts fanOut never delivers to its
655
+ // own sender, so a self-follow needs a distinct board+actor pairing).
656
+ board: z.literal("marketplace").optional(),
657
+ delivery: z.enum(["inbox", "telegram", "discord", "webhook"]).optional(),
658
+ delivery_target: z.string().optional(),
659
+ match: z.enum(["any", "all"]).optional(),
660
+ // Service-caller nomination only (Bearer === GATEWAY_API_KEY / X-Gateway-Key ===
661
+ // GATEWAY_SERVICE_SECRET) — a session/browser caller's value here is ignored in
662
+ // favor of their attested locals.slug. See one.ie/web/src/pages/api/ask/[...receiver].ts.
663
+ slug: z.string().optional(),
664
+ workspace: z.string().optional(),
574
665
  }),
575
666
  response: ok,
576
667
  effect: "signal", idempotent: true,
@@ -581,16 +672,81 @@ export const RECEIVERS = {
581
672
  request: z.object({
582
673
  tags: z.array(z.string()),
583
674
  content: z.string().optional(),
675
+ board: z.literal("marketplace").optional(),
676
+ slug: z.string().optional(),
677
+ workspace: z.string().optional(),
584
678
  }),
585
679
  response: z.object({ ok: z.boolean(), matched: z.number().optional(), actors: z.array(z.string()).optional() }),
586
680
  effect: "ask",
587
681
  }),
682
+ "world": receiver({
683
+ receiver: "world",
684
+ summary: "THE door — say it once, the world decides. Routes a tagged signal via the ladder (learned path → staked subscribers → CEO); fan:'all' broadcasts to every matching subscriber instead. Address form world:<tag-expr> carries tags in the receiver",
685
+ request: z.object({
686
+ tags: z.array(z.string()).optional(),
687
+ fan: z.enum(["one", "all"]).optional(),
688
+ deliver: z.boolean().optional(),
689
+ board: z.literal("marketplace").optional(),
690
+ slug: z.string().optional(),
691
+ workspace: z.string().optional(),
692
+ }),
693
+ response: z.object({
694
+ ok: z.boolean(),
695
+ routed: z.enum(["learned", "staked", "explore", "ceo"]).optional(),
696
+ actor: z.string().optional(),
697
+ matched: z.number().optional(),
698
+ actors: z.array(z.string()).optional(),
699
+ tags: z.array(z.string()).optional(),
700
+ }),
701
+ effect: "ask",
702
+ }),
703
+ "world:outcome": receiver({
704
+ receiver: "world:outcome",
705
+ summary: "Close the routing loop with the RESULT: whoever consumed a route reports success/failure, moving the same tag→receiver paths the router reads — success marks, failure warns. Pass the actor id the route returned",
706
+ request: z.object({
707
+ tags: z.array(z.string()),
708
+ actor: z.string(),
709
+ success: z.boolean().optional(),
710
+ weight: z.number().optional(),
711
+ slug: z.string().optional(),
712
+ workspace: z.string().optional(),
713
+ }),
714
+ response: z.object({
715
+ ok: z.boolean(),
716
+ outcome: z.enum(["marked", "warned"]).optional(),
717
+ actor: z.string().optional(),
718
+ tags: z.array(z.string()).optional(),
719
+ weight: z.number().optional(),
720
+ }),
721
+ effect: "ask", idempotent: false,
722
+ }),
723
+ "world:route": receiver({
724
+ receiver: "world:route",
725
+ summary: "Route a tagged signal to THE handler via the ladder: learned tag-node path (follow_route) → staked subscribers (reputation-ranked) → CEO standing workflow; marks the delivery path so the next route is smarter",
726
+ request: z.object({
727
+ tags: z.array(z.string()),
728
+ deliver: z.boolean().optional(),
729
+ board: z.literal("marketplace").optional(),
730
+ slug: z.string().optional(),
731
+ workspace: z.string().optional(),
732
+ }),
733
+ response: z.object({
734
+ ok: z.boolean(),
735
+ routed: z.enum(["learned", "staked", "explore", "ceo"]).optional(),
736
+ actor: z.string().optional(),
737
+ tags: z.array(z.string()).optional(),
738
+ }),
739
+ effect: "ask",
740
+ }),
588
741
  "tasks:announce": receiver({
589
742
  receiver: "tasks:announce",
590
743
  summary: "Announce a task into the world by its tags — the first caller of world:announce",
591
744
  request: z.object({
592
745
  taskId: z.string(),
593
746
  tags: z.array(z.string()),
747
+ board: z.literal("marketplace").optional(),
748
+ slug: z.string().optional(),
749
+ workspace: z.string().optional(),
594
750
  }),
595
751
  response: z.object({ ok: z.boolean(), taskId: z.string().optional(), matched: z.number().optional(), actors: z.array(z.string()).optional() }),
596
752
  effect: "ask",
@@ -999,6 +1155,48 @@ export const RECEIVERS = {
999
1155
  effect: "ask", cost: "free", idempotent: true,
1000
1156
  examples: [{}],
1001
1157
  }),
1158
+ "plugins:buy": receiver({
1159
+ receiver: "plugins:buy",
1160
+ summary: "Buy a plugin for the caller's workspace. method:'stripe' (default) returns a hosted-checkout url; method:'credits' spends the workspace credit pool and grants the entitlement synchronously. Pay with ONE coin by topping up credits on the one-token rail first, then buy with credits. Price is looked up server-side from the plugin name.",
1161
+ request: z.object({
1162
+ name: z.string(),
1163
+ method: z.enum(["stripe", "credits"]).optional(),
1164
+ }),
1165
+ response: z.object({
1166
+ method: z.enum(["stripe", "credits"]),
1167
+ // stripe rail
1168
+ sessionId: z.string().optional(),
1169
+ clientSecret: z.string().nullable().optional(),
1170
+ url: z.string().nullable().optional(),
1171
+ price_usd: z.number().optional(),
1172
+ // credits rail
1173
+ entitled: z.boolean().optional(),
1174
+ already: z.boolean().optional(),
1175
+ credits_spent: z.number().optional(),
1176
+ plugin: z.string(),
1177
+ feature: z.string(),
1178
+ }),
1179
+ effect: "ask", auth: "owner", cost: "variable", idempotent: false,
1180
+ examples: [{ name: "shop" }, { name: "shop", method: "credits" }],
1181
+ }),
1182
+ "plugins:grant": receiver({
1183
+ receiver: "plugins:grant",
1184
+ summary: "Agency resale — grant a plugin to a client workspace the caller controls. Two gates, no money: the caller must itself be entitled to the plugin (you can only resell what you own) and must control the target client via the owners walk-up. The client's entitlement is granted idempotently; nobody is charged. Billing the client is a separate concern.",
1185
+ request: z.object({
1186
+ name: z.string(),
1187
+ for: z.string().describe("the client workspace slug to grant the plugin to"),
1188
+ }),
1189
+ response: z.object({
1190
+ granted: z.boolean().optional(),
1191
+ already: z.boolean().optional(),
1192
+ plugin: z.string(),
1193
+ feature: z.string(),
1194
+ client: z.string().optional(),
1195
+ reseller: z.string().optional(),
1196
+ }),
1197
+ effect: "ask", auth: "owner", cost: "free", idempotent: true,
1198
+ examples: [{ name: "shop", for: "acme-client" }],
1199
+ }),
1002
1200
  "plugins:source": receiver({
1003
1201
  receiver: "plugins:source",
1004
1202
  summary: "Get the install reference for a plugin — 402 when the workspace is not entitled. Use meta:entitlements to check first; this doubles as the delivery gate.",
@@ -1501,6 +1699,42 @@ export const RECEIVERS = {
1501
1699
  }),
1502
1700
  effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "member",
1503
1701
  }),
1702
+ // ── view:* — saved views (custom-views C1). A view is a workspace-scoped Thing (world_things type 'view'). ──
1703
+ "view:create": receiver({
1704
+ receiver: "view:create",
1705
+ summary: "Save a named view — dimension × tags × kind × config — as a workspace Thing",
1706
+ request: z.object({ slug: z.string(), name: z.string(), kind: z.string().optional(), query: z.object({ dimension: z.string().optional(), type: z.string().optional(), tags: z.array(z.string()).optional(), stage: z.string().optional() }), config: z.record(z.string(), z.unknown()).optional(), pinned: z.boolean().optional() }),
1707
+ response: z.object({ ok: z.boolean(), id: z.string().optional(), name: z.string().optional(), error: z.string().optional() }),
1708
+ effect: "ask", cost: "free", reversible: true, idempotent: false, auth: "member",
1709
+ }),
1710
+ "view:list": receiver({
1711
+ receiver: "view:list",
1712
+ summary: "List a workspace's saved views (cold flag derived at read time)",
1713
+ request: z.object({ slug: z.string() }),
1714
+ response: z.object({ views: z.array(z.object({ id: z.string(), name: z.string(), kind: z.string().optional(), pinned: z.boolean(), cold: z.boolean() })) }),
1715
+ effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "member",
1716
+ }),
1717
+ "view:get": receiver({
1718
+ receiver: "view:get",
1719
+ summary: "Read a saved view and resolve its live rows",
1720
+ request: z.object({ slug: z.string(), id: z.string() }),
1721
+ response: z.object({ ok: z.boolean(), rows: z.array(z.record(z.string(), z.unknown())).optional(), error: z.string().optional() }),
1722
+ effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "member",
1723
+ }),
1724
+ "view:update": receiver({
1725
+ receiver: "view:update",
1726
+ summary: "Update a saved view's name / query / kind / config / pinned",
1727
+ request: z.object({ slug: z.string(), id: z.string(), name: z.string().optional(), kind: z.string().optional(), query: z.object({ dimension: z.string().optional(), type: z.string().optional(), tags: z.array(z.string()).optional(), stage: z.string().optional() }).optional(), config: z.record(z.string(), z.unknown()).optional(), pinned: z.boolean().optional() }),
1728
+ response: z.object({ ok: z.boolean(), id: z.string().optional(), error: z.string().optional() }),
1729
+ effect: "ask", cost: "free", reversible: true, idempotent: false, auth: "member",
1730
+ }),
1731
+ "view:delete": receiver({
1732
+ receiver: "view:delete",
1733
+ summary: "Delete a saved view (the prune)",
1734
+ request: z.object({ slug: z.string(), id: z.string() }),
1735
+ response: z.object({ ok: z.boolean(), id: z.string().optional(), error: z.string().optional() }),
1736
+ effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "member",
1737
+ }),
1504
1738
  // ── pages + workspace settings — handlers in resolvers/pages.ts ────────────────
1505
1739
  "pages:create": receiver({
1506
1740
  receiver: "pages:create",
@@ -1597,21 +1831,21 @@ export const RECEIVERS = {
1597
1831
  "playbook:save": receiver({
1598
1832
  receiver: "playbook:save",
1599
1833
  summary: "Save a learner's playbook entry for a course lesson (upsert)",
1600
- request: z.object({ course: z.string(), lesson: z.string(), content: z.string().optional() }),
1834
+ request: z.object({ course: z.string(), lesson: z.string(), content: z.string().optional(), slug: z.string().optional(), workspace: z.string().optional() }),
1601
1835
  response: z.object({ ok: z.boolean() }),
1602
1836
  effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "public",
1603
1837
  }),
1604
1838
  "playbook:get": receiver({
1605
1839
  receiver: "playbook:get",
1606
1840
  summary: "Get the caller's playbook entries (optionally scoped to one course)",
1607
- request: z.object({ course: z.string().optional() }),
1841
+ request: z.object({ course: z.string().optional(), slug: z.string().optional(), workspace: z.string().optional() }),
1608
1842
  response: z.object({ entries: z.array(z.record(z.string(), z.unknown())).optional() }),
1609
1843
  effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "public",
1610
1844
  }),
1611
1845
  "playbook:promote": receiver({
1612
1846
  receiver: "playbook:promote",
1613
1847
  summary: "Promote a completed playbook into a reusable skill in the workspace's content repo",
1614
- request: z.object({ course: z.string() }),
1848
+ request: z.object({ course: z.string(), slug: z.string().optional(), workspace: z.string().optional() }),
1615
1849
  response: z.object({ ok: z.boolean(), skillRef: z.string().optional() }),
1616
1850
  effect: "ask", cost: "free", reversible: true, idempotent: false, auth: "member",
1617
1851
  }),
@@ -1871,6 +2105,23 @@ export const RECEIVERS = {
1871
2105
  auth: "manage_workflows",
1872
2106
  examples: [{ name: "Client Onboarding" }, { name: "demo", simulate: true }],
1873
2107
  }),
2108
+ "workflow:webhook-subscribe": receiver({
2109
+ receiver: "workflow:webhook-subscribe",
2110
+ summary: "Register a URL that receives every future run's events for one workflow as signed, retried POSTs. Mints a signing secret server-side, returned once.",
2111
+ request: z.object({
2112
+ workflowId: z.string(),
2113
+ url: z.string().url(),
2114
+ }),
2115
+ response: z.object({
2116
+ ok: z.boolean(),
2117
+ subId: z.string().optional(),
2118
+ secret: z.string().optional(),
2119
+ error: z.string().optional(),
2120
+ }),
2121
+ effect: "ask", cost: "free", reversible: true, idempotent: false, simulatable: false,
2122
+ auth: "manage_workflows",
2123
+ examples: [{ workflowId: "wf_abc", url: "https://example.com/hooks/one" }],
2124
+ }),
1874
2125
  "workflow:apply-diff": receiver({
1875
2126
  receiver: "workflow:apply-diff",
1876
2127
  summary: "Apply a WorkflowDiff (add/remove/connect/disconnect/update). simulate=true validates the DAG without persisting",
@@ -2029,6 +2280,14 @@ export const RECEIVERS = {
2029
2280
  effect: "ask", cost: "free", reversible: true, idempotent: true, auth: "manage_workflows",
2030
2281
  examples: [{ workflowId: "wf_abc", name: "Renamed Workflow" }, { workflowId: "wf_abc", status: "paused" }],
2031
2282
  }),
2283
+ "workflow:fade": receiver({
2284
+ receiver: "workflow:fade",
2285
+ summary: "Decay edge weights (strength & resistance) on one workflow — or all of the caller's workflows when workflowId is omitted — so stale bottlenecks stop glowing; multiplies both by (1-rate), default 0.05",
2286
+ request: z.object({ workflowId: z.string().optional(), rate: z.number().optional() }),
2287
+ response: z.object({ ok: z.boolean(), faded: z.number().optional(), error: z.string().optional() }),
2288
+ effect: "ask", cost: "free", reversible: false, idempotent: false, auth: "manage_workflows",
2289
+ examples: [{ workflowId: "wf_abc" }, { rate: 0.1 }, {}],
2290
+ }),
2032
2291
  // ── The autonomy ladder's leaf runners — a workflow skill/agent step compiles
2033
2292
  // to one of these (workflow-executor.ts stepBinding). Both proxy to the
2034
2293
  // channels runtime; web owns the catalog + tenant authority (workflows-lock C2/C3).
@@ -2088,6 +2347,9 @@ export const RECEIVERS = {
2088
2347
  }),
2089
2348
  effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
2090
2349
  examples: [{ workspace: "acme", roomSlug: "algebra-101", name: "Algebra 101", type: "classroom" }],
2350
+ // Fan-out pilot (actions-fan-out C2): offered to chat via chatToolsFor — no
2351
+ // curated workspace tool needed; the registry derivation is the whole tool.
2352
+ surfaces: { chat: true },
2091
2353
  }),
2092
2354
  "video:delete-room": receiver({
2093
2355
  receiver: "video:delete-room",
@@ -2150,6 +2412,7 @@ export const RECEIVERS = {
2150
2412
  }),
2151
2413
  effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
2152
2414
  examples: [{ workspace: "acme", name: "Product Launch", actorIds: ["actor_1", "actor_2"] }],
2415
+ surfaces: { chat: true }, // fan-out: registry-derived chat tool (actions-fan-out C5)
2153
2416
  }),
2154
2417
  "video:create-session": receiver({
2155
2418
  receiver: "video:create-session",
@@ -2188,6 +2451,7 @@ export const RECEIVERS = {
2188
2451
  }),
2189
2452
  effect: "ask", cost: "variable", idempotent: false, auth: "manage_clients",
2190
2453
  examples: [{ threadId: "thr_abc", transcript: "Host: Hello... Guest: Hi..." }],
2454
+ surfaces: { chat: true }, // fan-out: registry-derived chat tool (actions-fan-out C5)
2191
2455
  }),
2192
2456
  "video:quick-room": receiver({
2193
2457
  receiver: "video:quick-room",
@@ -2207,6 +2471,8 @@ export const RECEIVERS = {
2207
2471
  }),
2208
2472
  effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
2209
2473
  examples: [{ workspace: "acme", threadId: "thr_xyz", name: "Quick sync" }],
2474
+ // Fan-out pilot (actions-fan-out C2).
2475
+ surfaces: { chat: true },
2210
2476
  }),
2211
2477
  "video:join-event": receiver({
2212
2478
  receiver: "video:join-event",
@@ -2232,6 +2498,7 @@ export const RECEIVERS = {
2232
2498
  }),
2233
2499
  effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
2234
2500
  examples: [{ workspace: "acme", roomSlug: "algebra-101" }],
2501
+ surfaces: { chat: true }, // fan-out: registry-derived chat tool (actions-fan-out C5)
2235
2502
  }),
2236
2503
  "video:start-stream": receiver({
2237
2504
  receiver: "video:start-stream",
@@ -2248,6 +2515,7 @@ export const RECEIVERS = {
2248
2515
  }),
2249
2516
  effect: "ask", cost: "free", idempotent: false, auth: "manage_clients",
2250
2517
  examples: [{ workspace: "acme", roomSlug: "product-launch" }],
2518
+ surfaces: { chat: true }, // fan-out: registry-derived chat tool (actions-fan-out C5)
2251
2519
  }),
2252
2520
  // connect:channel — token-paste connect for a channel (Connect plan C7). The
2253
2521
  // typed contract over the shipped /api/connect/telegram route: the caller pastes
@@ -2542,6 +2810,94 @@ export const RECEIVERS = {
2542
2810
  }),
2543
2811
  effect: "signal", auth: "read_corpus", cost: "free", reversible: false, idempotent: false,
2544
2812
  }),
2813
+ // -- rag:connect + brain: family — the phase-2 ingestion edge (brain C5 catalog; C7 handlers) --
2814
+ "rag:connect": receiver({
2815
+ receiver: "rag:connect",
2816
+ summary: "Register a knowledge source (e.g. a Google Drive folder) to sync into a workspace brain. Records the source in workspace_settings.sources; ingest-tick pulls changes on a schedule. Provision the brain (brain:provision) first.",
2817
+ request: z.object({
2818
+ provider: z.enum(["google-drive"]).optional().default("google-drive"),
2819
+ folder_id: z.string().describe("Drive folder id to sync (read-only)."),
2820
+ corpus: z.string().optional().describe("Target brain @-pointer; defaults to your workspace brain @<slug>-brain. Authority-walked (write_corpus)."),
2821
+ }),
2822
+ response: z.object({
2823
+ ok: z.boolean(),
2824
+ corpus: z.string().optional(),
2825
+ provider: z.string().optional(),
2826
+ folder_id: z.string().optional(),
2827
+ sources: z.number().optional().describe("Count of registered sources after this connect."),
2828
+ reason: z.string().optional(),
2829
+ }),
2830
+ effect: "ask", auth: "write_corpus", cost: { fixed: 1 }, reversible: true, idempotent: true,
2831
+ }),
2832
+ "brain:ingest": receiver({
2833
+ receiver: "brain:ingest",
2834
+ summary: "Ingest one document (Drive file, URL, or inline text) into a corpus: fetch -> markdown -> chunk -> hash-gate -> embed -> upsert with document provenance. Idempotent on content_hash.",
2835
+ request: z.object({
2836
+ corpus: z.string().describe("Target brain @-pointer. Authority-walked (write_corpus)."),
2837
+ source_ref: z.string().optional().describe("Drive file id or URL — the stable key for tombstones + hash-gate."),
2838
+ document_url: z.string().optional().describe("Fetch URL for the document."),
2839
+ raw_text: z.string().optional().describe("Inline markdown/text/html."),
2840
+ mime_type: z.string().optional(),
2841
+ content_hash: z.string().optional(),
2842
+ title: z.string().optional(),
2843
+ tags: z.array(z.string()).optional(),
2844
+ }),
2845
+ response: z.object({
2846
+ ok: z.boolean(),
2847
+ corpus: z.string().optional(),
2848
+ source_ref: z.string().optional(),
2849
+ content_hash: z.string().optional(),
2850
+ chunks: z.number().optional(),
2851
+ skipped: z.boolean().optional().describe("True when content_hash matched (no re-embed)."),
2852
+ reason: z.string().optional(),
2853
+ }),
2854
+ effect: "ask", auth: "write_corpus", cost: { fixed: 1 }, reversible: false, idempotent: true,
2855
+ }),
2856
+ "brain:provision": receiver({
2857
+ receiver: "brain:provision",
2858
+ summary: "Idempotently create a workspace or personal brain: a corpus thing @<slug>-brain contained by the owning group. Safe to call repeatedly (put). Auth walks the workspace group (the corpus does not exist yet).",
2859
+ request: z.object({
2860
+ slug: z.string().describe("Target workspace or personal slug."),
2861
+ kind: z.enum(["workspace", "personal"]).optional().default("workspace"),
2862
+ actorId: z.string().optional(),
2863
+ }),
2864
+ response: z.object({
2865
+ ok: z.boolean(),
2866
+ corpus: z.string().optional().describe("The provisioned @-pointer, e.g. @acme-brain."),
2867
+ slug: z.string().optional(),
2868
+ kind: z.string().optional(),
2869
+ reason: z.string().optional(),
2870
+ }),
2871
+ effect: "ask", auth: "write_corpus", cost: "free", reversible: false, idempotent: true,
2872
+ }),
2873
+ "brain:tombstone": receiver({
2874
+ receiver: "brain:tombstone",
2875
+ summary: "Remove a deleted source document's chunks from a corpus, keyed by source_ref (Drive file id or URL). Fired by ingest-tick when a previously-known file no longer appears in a folder listing.",
2876
+ request: z.object({
2877
+ corpus: z.string().describe("Target brain @-pointer. Authority-walked (write_corpus)."),
2878
+ source_ref: z.string().describe("The stable key used at ingest — Drive file id or URL."),
2879
+ }),
2880
+ response: z.object({
2881
+ ok: z.boolean(),
2882
+ corpus: z.string().optional(),
2883
+ source_ref: z.string().optional(),
2884
+ removed: z.number().optional().describe("Chunk rows deleted."),
2885
+ reason: z.string().optional(),
2886
+ }),
2887
+ effect: "ask", auth: "write_corpus", cost: "free", reversible: false, idempotent: true,
2888
+ }),
2889
+ "rag:promote": receiver({
2890
+ receiver: "rag:promote",
2891
+ summary: "Distill repeat retrieval winners into typed fact. Sweeps retrieval_outcomes for chunks with >=5 mark and 0 warn in a 14-day window, extracts a verbatim statement (no synthesis), and writes it via memory:remember into the chunk's owning group. Fired by the promote-tick cron trigger; safe to call anonymously — the target group is derived from data, never a caller-supplied workspace.",
2892
+ request: z.object({}),
2893
+ response: z.object({
2894
+ ok: z.boolean(),
2895
+ promoted: z.number().optional(),
2896
+ skipped: z.number().optional(),
2897
+ reason: z.string().optional(),
2898
+ }),
2899
+ effect: "ask", auth: "public", cost: "free", reversible: false, idempotent: true,
2900
+ }),
2545
2901
  // ── seo — handlers in resolvers/seo.ts (C1 live; C2 async) ─────────────────
2546
2902
  "seo:backlinks-summary": receiver({
2547
2903
  receiver: "seo:backlinks-summary",