@introspection-ai/recipes 0.20.0 → 0.22.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 (169) hide show
  1. package/README.md +10 -4
  2. package/dist/channels/index.d.ts +7 -0
  3. package/dist/channels/index.d.ts.map +1 -0
  4. package/dist/channels/index.js +4 -0
  5. package/dist/channels/index.js.map +1 -0
  6. package/dist/channels/module.d.ts +43 -0
  7. package/dist/channels/module.d.ts.map +1 -0
  8. package/dist/channels/module.js +61 -0
  9. package/dist/channels/module.js.map +1 -0
  10. package/dist/channels/refs.d.ts +28 -0
  11. package/dist/channels/refs.d.ts.map +1 -0
  12. package/dist/channels/refs.js +91 -0
  13. package/dist/channels/refs.js.map +1 -0
  14. package/dist/channels/tools.d.ts +56 -0
  15. package/dist/channels/tools.d.ts.map +1 -0
  16. package/dist/channels/tools.js +324 -0
  17. package/dist/channels/tools.js.map +1 -0
  18. package/dist/channels/types.d.ts +209 -0
  19. package/dist/channels/types.d.ts.map +1 -0
  20. package/dist/channels/types.js +23 -0
  21. package/dist/channels/types.js.map +1 -0
  22. package/dist/child-agent.d.ts +2 -0
  23. package/dist/child-agent.d.ts.map +1 -1
  24. package/dist/child-agent.js +33 -1
  25. package/dist/child-agent.js.map +1 -1
  26. package/dist/connector-tools.d.ts +37 -0
  27. package/dist/connector-tools.d.ts.map +1 -0
  28. package/dist/connector-tools.js +88 -0
  29. package/dist/connector-tools.js.map +1 -0
  30. package/dist/mcp-chunks/{auth-command-IQ4J332A.js → auth-command-4OEOMU2V.js} +22 -22
  31. package/dist/mcp-chunks/call-arguments-RPXZJOXL.js +15 -0
  32. package/dist/mcp-chunks/{call-command-WPQ3MDOE.js → call-command-AUEKSBMD.js} +31 -31
  33. package/dist/mcp-chunks/{chunk-3FB4ZJ4Z.js → chunk-3VLPWGJL.js} +4 -4
  34. package/dist/mcp-chunks/{chunk-WKUCIR2F.js → chunk-47N2EELW.js} +32 -29
  35. package/dist/mcp-chunks/{chunk-NACLB5YH.js → chunk-4NDR45CX.js} +5 -5
  36. package/dist/mcp-chunks/{chunk-RXSZ2W6Y.js → chunk-5WGJPYOR.js} +1 -1
  37. package/dist/mcp-chunks/{chunk-JJG2OEHI.js → chunk-6CYJ6RN3.js} +1 -1
  38. package/dist/mcp-chunks/{chunk-IUFDFNNP.js → chunk-6DL2C6V5.js} +1 -1
  39. package/dist/mcp-chunks/{chunk-NPU7EMGE.js → chunk-6QH47GNE.js} +5 -5
  40. package/dist/mcp-chunks/{chunk-UDG3RHKX.js → chunk-756YH7S4.js} +2 -2
  41. package/dist/mcp-chunks/{chunk-B4XFULJ6.js → chunk-7CS27CWE.js} +6 -6
  42. package/dist/mcp-chunks/{chunk-SCD2NAX5.js → chunk-7KEDX7SE.js} +2 -2
  43. package/dist/mcp-chunks/{chunk-24FKKIXI.js → chunk-AB7MHBEG.js} +11 -11
  44. package/dist/mcp-chunks/{chunk-ITJWLDF5.js → chunk-ACTRQW5L.js} +2 -2
  45. package/dist/mcp-chunks/{chunk-ETYYCEQF.js → chunk-ADINDBOF.js} +2 -2
  46. package/dist/mcp-chunks/{chunk-QONTHFPO.js → chunk-AF74TOWM.js} +4 -4
  47. package/dist/mcp-chunks/{chunk-GQHTGV5E.js → chunk-AFKE4BTR.js} +15 -15
  48. package/dist/mcp-chunks/{chunk-RDOD354S.js → chunk-DEYKPG4K.js} +4 -4
  49. package/dist/mcp-chunks/{chunk-UAIJZBGL.js → chunk-E4YYMOT2.js} +10 -10
  50. package/dist/mcp-chunks/{chunk-DIIFWDZ7.js → chunk-HBDCHBJP.js} +1 -1
  51. package/dist/mcp-chunks/{chunk-NMF5S5DC.js → chunk-HDUKEFBI.js} +2 -2
  52. package/dist/mcp-chunks/{chunk-5YBCGQA7.js → chunk-HHMWZ2FZ.js} +1 -1
  53. package/dist/mcp-chunks/{chunk-2CTVPSHF.js → chunk-IPH6U7SE.js} +3 -3
  54. package/dist/mcp-chunks/{chunk-NSZY5WLR.js → chunk-J3J3NQ4X.js} +1 -1
  55. package/dist/mcp-chunks/{chunk-U23EY5DN.js → chunk-J6VOMOKI.js} +1 -1
  56. package/dist/mcp-chunks/{chunk-KJ3CMHWS.js → chunk-JILBFEBD.js} +1 -1
  57. package/dist/mcp-chunks/{chunk-YDJ7E2PH.js → chunk-JZDXKYQM.js} +4 -4
  58. package/dist/mcp-chunks/{chunk-FZ7K5EGL.js → chunk-KMH3PHLA.js} +5 -5
  59. package/dist/mcp-chunks/{chunk-FW5QVG6V.js → chunk-KPXFBRTG.js} +1 -1
  60. package/dist/mcp-chunks/{chunk-ZI44YPFF.js → chunk-L4CUJJI5.js} +4 -4
  61. package/dist/mcp-chunks/{chunk-GPCLDF6U.js → chunk-LC2CCTQN.js} +1 -1
  62. package/dist/mcp-chunks/{chunk-3MIFROM7.js → chunk-LJCZWKGJ.js} +1 -1
  63. package/dist/mcp-chunks/{chunk-SHZVDY6Z.js → chunk-MFFE5QSH.js} +1 -1
  64. package/dist/mcp-chunks/{chunk-HULRE4D2.js → chunk-MOML5C2E.js} +1 -1
  65. package/dist/mcp-chunks/{chunk-TAVMVGD5.js → chunk-MRXSXBZT.js} +2 -2
  66. package/dist/mcp-chunks/{chunk-LI4L4FOV.js → chunk-NMFI6AUD.js} +21 -21
  67. package/dist/mcp-chunks/{chunk-EEYUPDRX.js → chunk-NWLOPQT7.js} +1 -1
  68. package/dist/mcp-chunks/{chunk-ZSKQI4TS.js → chunk-OGFFBNIC.js} +4 -4
  69. package/dist/mcp-chunks/{chunk-QK6LUI5Q.js → chunk-PPIXUCYQ.js} +2 -2
  70. package/dist/mcp-chunks/{chunk-ILULMM2E.js → chunk-PPOHGQKM.js} +2 -2
  71. package/dist/mcp-chunks/{chunk-FNQV2ONG.js → chunk-PRZCYXVO.js} +2 -2
  72. package/dist/mcp-chunks/{chunk-LYZRCACV.js → chunk-Q7ESBNJG.js} +2 -2
  73. package/dist/mcp-chunks/{chunk-VC6SKXTI.js → chunk-RE4EVQNC.js} +1 -1
  74. package/dist/mcp-chunks/{chunk-AJPFOQLF.js → chunk-RO2Y34XX.js} +1 -1
  75. package/dist/mcp-chunks/{chunk-G5C4WVDQ.js → chunk-SAK5KCLR.js} +1 -1
  76. package/dist/mcp-chunks/{chunk-ILSK6AWI.js → chunk-SCGMRUKI.js} +1 -1
  77. package/dist/mcp-chunks/{chunk-MNAVFYYV.js → chunk-SQTXZR56.js} +10 -10
  78. package/dist/mcp-chunks/{chunk-VREKED5H.js → chunk-TGJOZDKG.js} +2 -2
  79. package/dist/mcp-chunks/{chunk-ZNKNF2BV.js → chunk-US57PK4B.js} +1 -1
  80. package/dist/mcp-chunks/{chunk-RYAUNXFA.js → chunk-V4RYLZM5.js} +1 -1
  81. package/dist/mcp-chunks/{chunk-54UGGTOX.js → chunk-X3FOKZ5C.js} +1 -1
  82. package/dist/mcp-chunks/{chunk-XRRT3SZM.js → chunk-XVJAASVZ.js} +10 -10
  83. package/dist/mcp-chunks/{chunk-MDVNQ4HM.js → chunk-YX2NKW6O.js} +1 -1
  84. package/dist/mcp-chunks/{chunk-5HQYKAE7.js → chunk-Z7O6NF7X.js} +16 -16
  85. package/dist/mcp-chunks/{chunk-MWD5PZ4G.js → chunk-ZKFG37TB.js} +2 -2
  86. package/dist/mcp-chunks/{chunk-VJ2TSA5R.js → chunk-ZRZ7C6L3.js} +13 -8
  87. package/dist/mcp-chunks/{chunk-N2HVJU7X.js → chunk-ZZSD2JT7.js} +1 -1
  88. package/dist/mcp-chunks/{cli-Z7KV4PCE.js → cli-B7WQQUME.js} +67 -67
  89. package/dist/mcp-chunks/client-4ZHIBB67.js +21 -0
  90. package/dist/mcp-chunks/{config-command-XXCDOB3M.js → config-command-FF3JPMOP.js} +26 -26
  91. package/dist/mcp-chunks/daemon-command-FKW6YELL.js +35 -0
  92. package/dist/mcp-chunks/{emit-ts-command-FEHTMXXB.js → emit-ts-command-4X56IZ32.js} +16 -16
  93. package/dist/mcp-chunks/{generate-cli-runner-OA6XL77R.js → generate-cli-runner-MIPF73SJ.js} +42 -42
  94. package/dist/mcp-chunks/{inspect-cli-command-UQKKE5VO.js → inspect-cli-command-GX54ZXKR.js} +8 -8
  95. package/dist/mcp-chunks/{launch-KMPDJOUH.js → launch-E2LWGPZS.js} +1 -1
  96. package/dist/mcp-chunks/{lifecycle-R3IP22MJ.js → lifecycle-TONHXXXJ.js} +1 -1
  97. package/dist/mcp-chunks/{list-command-7ENYKBF6.js → list-command-YUGRZO4G.js} +28 -28
  98. package/dist/mcp-chunks/{output-utils-N75TOWBX.js → output-utils-AZY6N2VQ.js} +3 -3
  99. package/dist/mcp-chunks/{record-command-XHPCL7IG.js → record-command-25DLELA2.js} +5 -5
  100. package/dist/mcp-chunks/{replay-command-CB56L3ZA.js → replay-command-6SLDG6PC.js} +6 -6
  101. package/dist/mcp-chunks/{resource-command-STEBHVT7.js → resource-command-YVOB4JCU.js} +8 -8
  102. package/dist/mcp-chunks/{result-utils-UONY2KNY.js → result-utils-ZXB5DM36.js} +2 -2
  103. package/dist/mcp-chunks/runtime-NOWGTHZ5.js +39 -0
  104. package/dist/mcp-chunks/{runtime-wrapper-SYYAQVO6.js → runtime-wrapper-ZZPAFQE4.js} +5 -5
  105. package/dist/mcp-chunks/{serve-command-VYSFRZFE.js → serve-command-QYGM2AMB.js} +26 -26
  106. package/dist/mcp-chunks/{timeouts-T3JP3MUF.js → timeouts-P3TBB3AX.js} +3 -3
  107. package/dist/mcp-chunks/{vault-command-AJ3X7GDI.js → vault-command-VDMPATIX.js} +6 -6
  108. package/dist/mcp-daemon.js +29 -29
  109. package/dist/mcp-run-worker.js +29 -29
  110. package/dist/mcp-tools.d.ts +1 -6
  111. package/dist/mcp-tools.d.ts.map +1 -1
  112. package/dist/mcp-tools.js +5 -73
  113. package/dist/mcp-tools.js.map +1 -1
  114. package/dist/mcp.d.ts.map +1 -1
  115. package/dist/mcp.js +24 -0
  116. package/dist/mcp.js.map +1 -1
  117. package/dist/pi-extension.d.ts.map +1 -1
  118. package/dist/pi-extension.js +66 -35
  119. package/dist/pi-extension.js.map +1 -1
  120. package/dist/recipe-agent.d.ts.map +1 -1
  121. package/dist/recipe-agent.js +3 -2
  122. package/dist/recipe-agent.js.map +1 -1
  123. package/dist/recipe-extensions.d.ts +1 -0
  124. package/dist/recipe-extensions.d.ts.map +1 -1
  125. package/dist/recipe-extensions.js +32 -11
  126. package/dist/recipe-extensions.js.map +1 -1
  127. package/dist/recipe-package.d.ts +5 -0
  128. package/dist/recipe-package.d.ts.map +1 -1
  129. package/dist/recipe-package.js +68 -3
  130. package/dist/recipe-package.js.map +1 -1
  131. package/dist/run-controller.d.ts.map +1 -1
  132. package/dist/run-controller.js +7 -3
  133. package/dist/run-controller.js.map +1 -1
  134. package/dist/session.d.ts.map +1 -1
  135. package/dist/session.js +71 -16
  136. package/dist/session.js.map +1 -1
  137. package/dist/test-utils.d.ts +1 -0
  138. package/dist/test-utils.d.ts.map +1 -1
  139. package/dist/test-utils.js +1 -0
  140. package/dist/test-utils.js.map +1 -1
  141. package/dist/tool-search.d.ts +21 -0
  142. package/dist/tool-search.d.ts.map +1 -0
  143. package/dist/tool-search.js +176 -0
  144. package/dist/tool-search.js.map +1 -0
  145. package/docs/channels.md +166 -0
  146. package/docs/index.md +2 -0
  147. package/docs/mcp-configuration.md +6 -4
  148. package/docs/pi-extension.md +12 -8
  149. package/docs/recipe-format.md +37 -0
  150. package/docs/slack.md +135 -0
  151. package/package.json +23 -14
  152. package/dist/mcp-chunks/call-arguments-MCCBPJMY.js +0 -15
  153. package/dist/mcp-chunks/client-B3FMQNGF.js +0 -21
  154. package/dist/mcp-chunks/daemon-command-BAREANTC.js +0 -35
  155. package/dist/mcp-chunks/runtime-Z45IVD24.js +0 -39
  156. package/dist/recipe-check.d.ts +0 -18
  157. package/dist/recipe-check.d.ts.map +0 -1
  158. package/dist/recipe-check.js +0 -263
  159. package/dist/recipe-check.js.map +0 -1
  160. package/vendor/introspection-recipe-check/darwin-arm64/introspection-recipe-check +0 -0
  161. package/vendor/introspection-recipe-check/darwin-x64/introspection-recipe-check +0 -0
  162. package/vendor/introspection-recipe-check/linux-arm64/introspection-recipe-check +0 -0
  163. package/vendor/introspection-recipe-check/linux-x64/introspection-recipe-check +0 -0
  164. package/vendor/introspection-recipe-check/linux-x64-musl/introspection-recipe-check +0 -0
  165. package/vendor/introspection-recipe-check/win32-x64/introspection-recipe-check.exe +0 -0
  166. package/vendor/mcp-client/darwin-arm64/mcp-client +0 -0
  167. package/vendor/mcp-client/darwin-x64/mcp-client +0 -0
  168. package/vendor/mcp-client/linux-arm64/mcp-client +0 -0
  169. package/vendor/mcp-client/linux-x64/mcp-client +0 -0
@@ -0,0 +1,166 @@
1
+ # Channel tools
2
+
3
+ A Recipe that answers a chat message declares a **channel connector**. The host
4
+ then registers a fixed set of `channel_*` tools, named and shaped identically
5
+ for every provider, and bound to the one conversation the task came from.
6
+
7
+ Two properties follow from that, and both are structural rather than
8
+ conventional:
9
+
10
+ - **The agent cannot address anything else.** No `channel_*` tool takes a
11
+ channel, thread, workspace, or user argument. The conversation is closed over
12
+ by the host from the task origin, so a compromised prompt has no vocabulary
13
+ for "post this somewhere else". The invariant is asserted by a test that
14
+ walks every registered tool's input schema.
15
+ - **A tool that the provider cannot support is absent, not failing.** Each
16
+ adapter declares a capability descriptor, and registration filters on it.
17
+ For example, an adapter with no history API has no `channel_read` tool.
18
+
19
+ Before the first model call, the channel extension adds origin metadata to the
20
+ system prompt. The metadata contains the provider, the conversation name and
21
+ permalink when the adapter can resolve them, whether the origin is a thread,
22
+ and the available channel tools. It contains no provider conversation IDs and
23
+ no messages.
24
+ `channel_read` remains the only way to fetch earlier messages when the
25
+ provider supports it.
26
+
27
+ ## The tools
28
+
29
+ | Tool | Model arguments | Requires |
30
+ | --- | --- | --- |
31
+ | `channel_reply` | `text` (Markdown) | always |
32
+ | `channel_read` | `limit?`, `cursor?` | `read` |
33
+ | `channel_react` | `message`, `emoji`, `action?` (`add` or `remove`) | `react` |
34
+ | `channel_edit` | `message`, `text` | `edit` |
35
+ | `channel_retract` | `message` | `retract` |
36
+ | `channel_attach` | `path`, `title?`, `comment?` | `attach` |
37
+ | `channel_fetch_file` | `file` (a `file_…` handle), `variant?` | `fetch_file` |
38
+ | `channel_post_document` | `title`, `markdown` | `documents` |
39
+
40
+ `channel_reply`, `channel_read`, and `channel_react` are active by default when
41
+ the provider supports them. The other selected tools start inactive, and the
42
+ model can find them through `tool_search`.
43
+
44
+ Reply content is **Markdown**. Each adapter renders it in the provider's own
45
+ format. There is no raw provider payload because the same tool contract must
46
+ work with every adapter.
47
+
48
+ ### Message and file references
49
+
50
+ `channel_react`, `channel_edit`, and `channel_retract` take a `message`, which is
51
+ an opaque handle minted by the host for the current session. It is not a Slack
52
+ timestamp or another provider message ID. Handles come back from
53
+ `channel_reply` and `channel_read`, so the model can only act on a message that
54
+ a channel tool returned.
55
+
56
+ `channel_react` adds a reaction when `action` is omitted. Set `action` to
57
+ `remove` to remove the agent's reaction with the same emoji.
58
+
59
+ Edit and retract have an extra check. They accept only a handle for a message
60
+ that this agent posted. Reading the same message again keeps its original
61
+ handle and authorship record.
62
+
63
+ `channel_fetch_file` works the same way. Attachments returned by `channel_read`
64
+ carry a `file_…` handle, and that is the only value the tool accepts. A bot can
65
+ usually read files from every conversation it belongs to, so accepting a raw
66
+ provider file ID would let the model reach files outside the bound conversation.
67
+
68
+ ### Enrichment, not lookup tools
69
+
70
+ Author names and permalinks are resolved by the adapter in trusted code and
71
+ attached to what the agent is already reading: `channel_read` rows carry
72
+ `author.display_name`, and `channel_reply` returns a `permalink` where the
73
+ provider has one. There is no `resolve_user` or `get_permalink` tool, because
74
+ each would require another model turn. Each lookup would also take an
75
+ addressing argument.
76
+
77
+ ### Unsupported operations
78
+
79
+ Workspace search, channel listing and joining, directory lookup, and posting to
80
+ another conversation are unsupported. The proposal does not choose an API or
81
+ access model for those operations. A separate proposal can define them when a
82
+ concrete use case requires them.
83
+
84
+ Typing indicators and presence are runtime effects rather than model decisions.
85
+ Streaming controls how a reply is delivered. The runtime derives idempotency
86
+ keys, and the webhook already removes duplicate inbound events.
87
+
88
+ ## Declare a channel connector
89
+
90
+ ```json
91
+ {
92
+ "dependencies": {
93
+ "@introspection-ai/recipe-channel-slack": "^0.1.0"
94
+ },
95
+ "pi": {
96
+ "connectors": [
97
+ {
98
+ "provider": "slack"
99
+ }
100
+ ]
101
+ }
102
+ }
103
+ ```
104
+
105
+ The connector declaration enables the provider package and its supported tool
106
+ catalog. The agent YAML file is the only place that narrows the catalog:
107
+
108
+ ```yaml
109
+ tools: [channel_reply, channel_read, channel_react, channel_edit, channel_retract, channel_fetch_file]
110
+ ```
111
+
112
+ The host fails when an agent selects a tool that the provider does not support.
113
+
114
+ Because the vocabulary is neutral, the same declaration and the same agent
115
+ prompt work against another provider by changing the dependency and `provider`.
116
+ Each provider package declares the capabilities that it can support.
117
+
118
+ ## Write an adapter
119
+
120
+ An adapter supplies transport and a capability descriptor. It writes no tool
121
+ schemas. The shared schema keeps providers from defining different forms of
122
+ the same operation, and it prevents a provider from adding an addressing
123
+ argument.
124
+
125
+ ```ts
126
+ import {
127
+ createChannelConnectorModule,
128
+ type ChannelAdapter,
129
+ } from "@introspection-ai/recipes/channels";
130
+
131
+ const capabilities = {
132
+ react: false, edit: true, retract: true, read: false,
133
+ attach: false, fetchFile: false,
134
+ documents: false, resolveAuthors: true, permalinks: false,
135
+ };
136
+
137
+ class MyAdapter implements ChannelAdapter {
138
+ readonly provider = "my-channel";
139
+ readonly capabilities = capabilities;
140
+ async reply(ctx, { text }) { /* post into ctx.target */ }
141
+ async edit(ctx, { ref, text }) { /* edit an agent-authored message */ }
142
+ async retract(ctx, { ref }) { /* retract an agent-authored message */ }
143
+ }
144
+
145
+ export default createChannelConnectorModule({
146
+ provider: "my-channel",
147
+ capabilities,
148
+ createSession: ({ env }) => ({
149
+ adapter: new MyAdapter(/* client from env */),
150
+ // A function, so a task with no channel origin still starts: the tools
151
+ // fail when called, the session does not fail to open.
152
+ target: () => resolveTargetFrom(env),
153
+ }),
154
+ });
155
+ ```
156
+
157
+ Registration checks that the adapter implements every method its capabilities
158
+ claim, so a descriptor cannot promise a tool the adapter does not have.
159
+
160
+ The result is an ordinary `RecipeConnectorModule`. Manifest validation, agent
161
+ tool selection, and `tool_search` work without provider-specific code in the
162
+ Recipe.
163
+
164
+ ## Provider pages
165
+
166
+ - [Slack](slack.md)
package/docs/index.md CHANGED
@@ -17,6 +17,8 @@ task lifecycle, protocols, and deployment.
17
17
  | Compose agents and subagents | [Agent composition](agent-composition.md) |
18
18
  | Ask for user input across hosts | [Interactions](interactions.md) |
19
19
  | Declare capability policy and bindings | [MCP configuration](mcp-configuration.md) |
20
+ | Answer a chat message from a Recipe | [Channel tools](channels.md) |
21
+ | Add Slack tools to a Recipe | [Slack channel connector](slack.md) |
20
22
  | Define portable evaluation judges | [Recipe judges](recipe-judges.md) |
21
23
 
22
24
  ## Boundary
@@ -91,10 +91,12 @@ to hide all authorized tools for a server, then optionally list exact tools in
91
91
  or a sole `"*"` selector. `eager` wins when a tool matches both fields, but
92
92
  neither field can authorize a tool excluded by `include`/`exclude`.
93
93
 
94
- Deferred tools remain authorized and discoverable. When at least one exists,
95
- Recipes registers `mcp_search`; calling it searches only the authorized
96
- deferred catalog and adds matches to Pi's current active tool set for the next
97
- model request. It never grants access beyond `servers`.
94
+ Deferred tools remain authorized and discoverable. When at least one connector
95
+ or MCP tool is deferred, Recipes registers `tool_search`. Calling it searches
96
+ the inactive tools already allowed for the agent and adds the best matches to
97
+ Pi's active tool set for the next model request. It never grants access beyond
98
+ the Recipe manifest and agent policy. Recipes also registers `mcp_search` as a
99
+ compatibility alias when MCP tools are deferred.
98
100
 
99
101
  `defer` and `eager` are invalid in CLI mode. An omitted agent `mcp` block
100
102
  inherits its base policy. Once a child declares `mcp`, the complete block
@@ -53,9 +53,10 @@ For the selected agent, the extension:
53
53
  1. reads the root `package.json#pi` resource declarations;
54
54
  2. resolves the agent YAML, including `from:` inheritance;
55
55
  3. selects the model, thinking level, and tool allowlist;
56
- 4. loads selected skills, package prompts, and the complete Recipe extension closure;
57
- 5. materializes declared MCP bindings from host or local configuration;
58
- 6. exposes only the declared subagents through the shared `agent` tool.
56
+ 4. loads providers from `pi.connectors` and registers the tools selected by the agent;
57
+ 5. loads selected skills, package prompts, and the complete Recipe extension closure;
58
+ 6. materializes declared MCP bindings from host or local configuration;
59
+ 7. exposes only the declared subagents through the shared `agent` tool.
59
60
 
60
61
  See [Recipe Format](recipe-format.md) for the authored contract and
61
62
  [Agent composition](agent-composition.md) for inheritance and selection.
@@ -115,9 +116,8 @@ skills, prompt templates, and context files by default.
115
116
 
116
117
  ## Validation
117
118
 
118
- Every `pi --recipe` launch automatically runs the shared Recipe Format
119
- validator. Invalid Recipes are rendered in Pi and stop the session before any
120
- model call.
119
+ A `pi --recipe` launch does **not** validate the Recipe. Validation runs where
120
+ it can be acted on: while authoring, and in whatever host starts the session.
121
121
 
122
122
  For an explicit manual or CI check, run:
123
123
 
@@ -125,8 +125,12 @@ For an explicit manual or CI check, run:
125
125
  introspection check
126
126
  ```
127
127
 
128
- Both paths use the same Rust validation core. The binary embedded in the npm
129
- package is an internal bridge for Pi startup, not a second user-facing CLI.
128
+ That command uses the Rust validation core (`introspection-recipe-check`),
129
+ which is also published as a crate and a Python binding so a host can embed it
130
+ - the Introspection platform validates there, and blocks task creation on a
131
+ failure. This npm package no longer ships a validator binary: a launch-time
132
+ check was a third run of the same engine in the one place whose failure mode is
133
+ a dead session, including when the binary simply could not be spawned.
130
134
 
131
135
  ## Host parity
132
136
 
@@ -81,6 +81,43 @@ MUST commit one supported dependency lockfile: `package-lock.json`,
81
81
  `npm-shrinkwrap.json`, `pnpm-lock.yaml`, or `yarn.lock`. npm lockfiles MUST
82
82
  carry the same package name and version as `package.json`.
83
83
 
84
+ ## Connector tools
85
+
86
+ `pi.connectors` declares official provider tools that the host may register for
87
+ the Recipe. The declaration does not contain a connector ID, workspace ID, or
88
+ credential. A host binds those values when it starts a task.
89
+
90
+ ```json
91
+ {
92
+ "dependencies": {
93
+ "@introspection-ai/recipe-channel-slack": "^0.1.0"
94
+ },
95
+ "pi": {
96
+ "connectors": [
97
+ {
98
+ "provider": "slack"
99
+ }
100
+ ]
101
+ }
102
+ }
103
+ ```
104
+
105
+ Each provider may appear once. The host derives the package name from the
106
+ provider. For example, `slack` resolves to
107
+ `@introspection-ai/recipe-channel-slack`. The package owns its tool catalog
108
+ and marks which tools are active by default.
109
+
110
+ The provider package must be in the Recipe's production dependencies and
111
+ lockfile. The host imports the package only when the Recipe declares the
112
+ provider. The agent YAML file is the only place that narrows the package tool
113
+ catalog. An agent lists each allowed tool by its full name, such as
114
+ `channel_reply`, in its `tools` list. The host fails when the agent names a
115
+ tool that the connector does not register.
116
+
117
+ Chat providers share one connector shape. A channel connector registers the
118
+ provider-neutral `channel_*` tools that its capabilities support. See
119
+ [Channel tools](channels.md).
120
+
84
121
  ## Agents
85
122
 
86
123
  `pi.agents` may declare YAML agent definitions explicitly. When omitted,
package/docs/slack.md ADDED
@@ -0,0 +1,135 @@
1
+ # Slack channel connector
2
+
3
+ `@introspection-ai/recipe-channel-slack` is the Slack adapter for the
4
+ [channel tools](channels.md). It supplies Slack Web API transport and a
5
+ capability descriptor; the tool names and schemas are the neutral `channel_*`
6
+ set, so a Recipe written against it is not written against Slack.
7
+
8
+ Slack sends inbound events to the existing Events API webhook. The tools make
9
+ ordinary HTTP requests to the Slack Web API with the bot that received the
10
+ task. The package does not use Socket Mode, WebSockets, or a streamed tool
11
+ protocol.
12
+
13
+ ## Declare it
14
+
15
+ ```json
16
+ {
17
+ "dependencies": {
18
+ "@introspection-ai/recipe-channel-slack": "^0.1.0"
19
+ },
20
+ "pi": {
21
+ "connectors": [
22
+ {
23
+ "provider": "slack"
24
+ }
25
+ ]
26
+ }
27
+ }
28
+ ```
29
+
30
+ Commit the package manager lockfile. The host loads the package only for a
31
+ Recipe that declares the connector.
32
+
33
+ The connector package provides the complete Slack tool catalog. Each agent
34
+ lists the exact `channel_*` tools it may call in its YAML file. `channel_reply`,
35
+ `channel_read`, and `channel_react` are active from the start. Other selected
36
+ tools are available through `tool_search`.
37
+
38
+ ## What Slack registers
39
+
40
+ | Tool | Slack operation |
41
+ | --- | --- |
42
+ | `channel_reply` | `chat.postMessage` into the origin channel and thread |
43
+ | `channel_read` | `conversations.replies` in a thread, else `conversations.history` |
44
+ | `channel_react` | `reactions.add` or `reactions.remove` |
45
+ | `channel_edit` | `chat.update` for a message the agent posted |
46
+ | `channel_retract` | `chat.delete` for a message the agent posted |
47
+ | `channel_fetch_file` | `files.info` plus a private file download |
48
+
49
+ Slack history returns at most 15 messages to the agent per call. For a thread,
50
+ the first call reads the thread and returns the newest messages. The adapter
51
+ keeps older messages in the current session, and the returned opaque cursor
52
+ pages backward through that cache without another `conversations.replies`
53
+ request.
54
+
55
+ The connector uses a customer owned internal Slack app. Slack gives internal
56
+ apps the larger `conversations.replies` page and rate limits needed to read a
57
+ thread before selecting its newest messages. The connector does not support a
58
+ commercially distributed Slack app outside the Slack Marketplace, because
59
+ Slack restricts those installations to 15 replies and one request per minute.
60
+
61
+ `channel_attach` and `channel_post_document` are not registered: `files.uploadV2`
62
+ and canvases are not implemented in this package yet, and the capability
63
+ descriptor says so rather than registering tools that fail.
64
+
65
+ None of these take a channel or thread argument. Every tool acts on the
66
+ conversation the task came from. Author display names (`users.info`) and
67
+ permalinks (`chat.getPermalink`) are resolved inside the adapter and attached to
68
+ message rows and reply results, so there is no user lookup or permalink tool.
69
+ Edit and retract also require an opaque reference for a message posted by this
70
+ agent. They cannot act on another author's message.
71
+
72
+ Workspace search, channel listing and joining, directory lookup, and
73
+ cross-channel posting are unsupported. Their contract and access model are
74
+ deferred to a separate proposal.
75
+
76
+ ## Cloud access
77
+
78
+ The Recipe never receives the Slack bot token. The adapter sends the task
79
+ locator to `INTROSPECTION_EGRESS_URL`, the provider proxy inside the
80
+ Introspection environment, with the Slack host as the proxy route. The proxy
81
+ verifies and removes the locator, checks the connector's granted scope and
82
+ allowed path, and adds the bot token before the request leaves for Slack.
83
+
84
+ The adapter refuses to send a task locator when the provider proxy URL is
85
+ missing. It never falls back to sending the locator to Slack.
86
+
87
+ After `channel_reply` succeeds in cloud, the adapter posts the `connector_posted`
88
+ task event to the Data Plane, which checks the agent session, current run,
89
+ provider, and origin channel before recording the new thread root. A later Slack
90
+ reply then resumes the same task.
91
+
92
+ Slack writes are attempted once. The adapter does not retry `chat.postMessage`,
93
+ because Slack accepts no idempotency key for it. If Slack accepts the post but
94
+ event recording fails, the tool returns the message reference and a
95
+ `bridge_error`. It does not post again.
96
+
97
+ ## Test with introspection dev
98
+
99
+ Run `introspection dev` from the Recipe repository. A Slack event sent to the
100
+ development runtime starts a cloud sandbox with the local Recipe overlay, so the
101
+ adapter uses the cloud task origin and provider proxy and needs no local Slack
102
+ credential. Use `introspection dev --logs` for sandbox logs.
103
+
104
+ ## Test with introspection local
105
+
106
+ An `introspection local` run has no inbound Slack event, cloud task origin, or
107
+ credential proxy. Install dependencies, then set a bot token and a conversation:
108
+
109
+ ```bash
110
+ pnpm install --frozen-lockfile
111
+ export SLACK_BOT_TOKEN='xoxb-...'
112
+ export SLACK_CHANNEL_ID='C0123456789'
113
+ export SLACK_THREAD_TS='1234567890.123456' # optional
114
+ introspection local -p 'Summarise this thread and reply.'
115
+ ```
116
+
117
+ Local tools call Slack directly with `SLACK_BOT_TOKEN`. Local posts create no
118
+ inbound task or reply bridge, because no Data Plane task exists.
119
+
120
+ ## File downloads
121
+
122
+ `channel_fetch_file` writes a file under the task files directory and returns its
123
+ path, media type, size, and SHA-256 digest. The bytes land in the workspace and
124
+ not in model context. It accepts only a `file_…` handle from a `channel_read`
125
+ attachment, so the bot's cross-channel file read is not reachable from model
126
+ input. On the wire it accepts only `files.slack.com` download URLs, rejects
127
+ redirects, caps the body at 100 MiB, checks the declared size, and removes
128
+ partial files after a failure. The `video_low` variant uses Slack's smaller MP4
129
+ rendition when one exists.
130
+
131
+ ## Direct host use
132
+
133
+ The package exports `SlackChannelAdapter`, `createSlackChannelSession` and
134
+ `slackChannelTarget` for custom hosts and tests, alongside the default
135
+ `slackRecipeConnectorModule`. A normal Recipe uses `pi.connectors` instead.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@introspection-ai/recipes",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
4
4
  "description": "The open format for vertical agents, built on Pi.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -9,8 +9,6 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
- "vendor/mcp-client",
13
- "vendor/introspection-recipe-check",
14
12
  "docs",
15
13
  "README.md",
16
14
  "package.json"
@@ -60,6 +58,10 @@
60
58
  "types": "./dist/api/session.d.ts",
61
59
  "import": "./dist/api/session.js"
62
60
  },
61
+ "./channels": {
62
+ "types": "./dist/channels/index.d.ts",
63
+ "import": "./dist/channels/index.js"
64
+ },
63
65
  "./test-utils": {
64
66
  "types": "./dist/test-utils.d.ts",
65
67
  "import": "./dist/test-utils.js"
@@ -78,7 +80,7 @@
78
80
  "@introspection-sdk/introspection-pi": "^0.22.0",
79
81
  "@opentelemetry/api": "^1.9.1",
80
82
  "ajv": "^8.20.0",
81
- "mcporter": "0.13.6",
83
+ "mcporter": "0.13.7",
82
84
  "yaml": "^2.9.0"
83
85
  },
84
86
  "peerDependencies": {
@@ -89,24 +91,31 @@
89
91
  "typebox": "*"
90
92
  },
91
93
  "devDependencies": {
92
- "@earendil-works/pi-agent-core": "0.84.2",
93
- "@earendil-works/pi-ai": "0.84.2",
94
- "@earendil-works/pi-coding-agent": "0.84.2",
95
- "@earendil-works/pi-tui": "0.84.2",
94
+ "@earendil-works/pi-agent-core": "0.84.3",
95
+ "@earendil-works/pi-ai": "0.84.3",
96
+ "@earendil-works/pi-coding-agent": "0.84.3",
97
+ "@earendil-works/pi-tui": "0.84.3",
96
98
  "@types/node": "^26.1.2",
97
99
  "esbuild": "^0.28.1",
98
100
  "typebox": "^1.0.56",
99
101
  "typescript": "^7.0.2",
100
102
  "vitest": "^4.0.18"
101
103
  },
104
+ "optionalDependencies": {
105
+ "@introspection-ai/mcp-client-linux-x64": "0.22.0",
106
+ "@introspection-ai/mcp-client-linux-arm64": "0.22.0",
107
+ "@introspection-ai/mcp-client-darwin-arm64": "0.22.0",
108
+ "@introspection-ai/mcp-client-darwin-x64": "0.22.0"
109
+ },
102
110
  "scripts": {
103
111
  "build": "pnpm build:ts && pnpm build:native",
104
- "build:ts": "rm -rf dist && tsc && node scripts/build-mcp-daemon.mjs",
105
- "build:native": "cargo build --release -p pi-mcp-client && cargo build --release -p introspection-recipe-check --bin introspection-recipe-check && node scripts/package-mcp-client.mjs && node scripts/package-introspection-recipe-check.mjs",
112
+ "build:workspace-packages": "pnpm --filter './packages/**' build",
113
+ "build:ts": "rm -rf dist && tsc && node scripts/build-mcp-daemon.mjs && pnpm build:workspace-packages",
114
+ "build:native": "cargo build --release -p pi-mcp-client && node scripts/package-mcp-client.mjs",
106
115
  "bench:mcp-client": "pnpm build:ts && cargo build --release -p pi-mcp-client && node scripts/benchmark-mcp-client.mjs",
107
- "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
108
- "test": "pnpm build:ts && cargo test -p pi-mcp-client -p introspection-recipe-check && cargo build -p pi-mcp-client && cargo build -p introspection-recipe-check --bin introspection-recipe-check && MCP_CLIENT_BIN=target/debug/mcp-client node scripts/package-mcp-client.mjs && vitest run",
109
- "pack:check": "pnpm pack --dry-run --json | node scripts/check-npm-pack.mjs",
110
- "clean": "rm -rf dist .turbo node_modules target vendor/mcp-client vendor/introspection-recipe-check"
116
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json && pnpm --filter './packages/**' typecheck",
117
+ "test": "pnpm build:ts && cargo test -p pi-mcp-client -p introspection-recipe-check && cargo build -p pi-mcp-client && MCP_CLIENT_BIN=target/debug/mcp-client node scripts/package-mcp-client.mjs && vitest run",
118
+ "pack:check": "pnpm pack --dry-run --json | node scripts/check-npm-pack.mjs && pnpm --filter './packages/**' pack:check",
119
+ "clean": "rm -rf dist .turbo node_modules target vendor/mcp-client"
111
120
  }
112
121
  }
@@ -1,15 +0,0 @@
1
- import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
- import {
3
- parseCallArguments
4
- } from "./chunk-24FKKIXI.js";
5
- import "./chunk-FW5QVG6V.js";
6
- import "./chunk-NMF5S5DC.js";
7
- import "./chunk-EEYUPDRX.js";
8
- import "./chunk-ILSK6AWI.js";
9
- import "./chunk-SHZVDY6Z.js";
10
- import "./chunk-LYZRCACV.js";
11
- import "./chunk-DIIFWDZ7.js";
12
- import "./chunk-LRKZP7DJ.js";
13
- export {
14
- parseCallArguments
15
- };
@@ -1,21 +0,0 @@
1
- import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
- import {
3
- DaemonClient,
4
- resolveDaemonPaths
5
- } from "./chunk-GQHTGV5E.js";
6
- import "./chunk-MDVNQ4HM.js";
7
- import "./chunk-XRRT3SZM.js";
8
- import "./chunk-JJG2OEHI.js";
9
- import "./chunk-LYZRCACV.js";
10
- import "./chunk-5HQYKAE7.js";
11
- import "./chunk-ETYYCEQF.js";
12
- import "./chunk-2CTVPSHF.js";
13
- import "./chunk-VREKED5H.js";
14
- import "./chunk-DIIFWDZ7.js";
15
- import "./chunk-AJPFOQLF.js";
16
- import "./chunk-LWVFSQYX.js";
17
- import "./chunk-LRKZP7DJ.js";
18
- export {
19
- DaemonClient,
20
- resolveDaemonPaths
21
- };
@@ -1,35 +0,0 @@
1
- import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
- import {
3
- handleDaemonCli
4
- } from "./chunk-LI4L4FOV.js";
5
- import "./chunk-HULRE4D2.js";
6
- import "./chunk-RYAUNXFA.js";
7
- import "./chunk-WKUCIR2F.js";
8
- import "./chunk-VC6SKXTI.js";
9
- import "./chunk-FNQV2ONG.js";
10
- import "./chunk-VJ2TSA5R.js";
11
- import "./chunk-MNAVFYYV.js";
12
- import "./chunk-B4XFULJ6.js";
13
- import "./chunk-MWD5PZ4G.js";
14
- import "./chunk-RXSZ2W6Y.js";
15
- import "./chunk-NSZY5WLR.js";
16
- import "./chunk-54UGGTOX.js";
17
- import "./chunk-ZNKNF2BV.js";
18
- import "./chunk-K56RW4HB.js";
19
- import "./chunk-N2HVJU7X.js";
20
- import "./chunk-GQHTGV5E.js";
21
- import "./chunk-MDVNQ4HM.js";
22
- import "./chunk-XRRT3SZM.js";
23
- import "./chunk-JJG2OEHI.js";
24
- import "./chunk-LYZRCACV.js";
25
- import "./chunk-5HQYKAE7.js";
26
- import "./chunk-ETYYCEQF.js";
27
- import "./chunk-2CTVPSHF.js";
28
- import "./chunk-VREKED5H.js";
29
- import "./chunk-DIIFWDZ7.js";
30
- import "./chunk-AJPFOQLF.js";
31
- import "./chunk-LWVFSQYX.js";
32
- import "./chunk-LRKZP7DJ.js";
33
- export {
34
- handleDaemonCli
35
- };
@@ -1,39 +0,0 @@
1
- import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
- import {
3
- callOnce,
4
- createRuntime
5
- } from "./chunk-WKUCIR2F.js";
6
- import "./chunk-VC6SKXTI.js";
7
- import "./chunk-FNQV2ONG.js";
8
- import "./chunk-VJ2TSA5R.js";
9
- import "./chunk-MNAVFYYV.js";
10
- import "./chunk-B4XFULJ6.js";
11
- import "./chunk-MWD5PZ4G.js";
12
- import "./chunk-RXSZ2W6Y.js";
13
- import {
14
- MCPORTER_VERSION
15
- } from "./chunk-NSZY5WLR.js";
16
- import "./chunk-54UGGTOX.js";
17
- import "./chunk-ZNKNF2BV.js";
18
- import "./chunk-K56RW4HB.js";
19
- import "./chunk-XRRT3SZM.js";
20
- import "./chunk-JJG2OEHI.js";
21
- import "./chunk-LYZRCACV.js";
22
- import "./chunk-5HQYKAE7.js";
23
- import {
24
- readJsonFile,
25
- writeJsonFile
26
- } from "./chunk-ETYYCEQF.js";
27
- import "./chunk-2CTVPSHF.js";
28
- import "./chunk-VREKED5H.js";
29
- import "./chunk-DIIFWDZ7.js";
30
- import "./chunk-AJPFOQLF.js";
31
- import "./chunk-LWVFSQYX.js";
32
- import "./chunk-LRKZP7DJ.js";
33
- export {
34
- MCPORTER_VERSION,
35
- callOnce,
36
- createRuntime,
37
- readJsonFile,
38
- writeJsonFile
39
- };
@@ -1,18 +0,0 @@
1
- export interface RecipeCheckDiagnostic {
2
- code: string;
3
- path: string;
4
- span?: {
5
- line: number;
6
- column: number;
7
- };
8
- message: string;
9
- help?: string;
10
- }
11
- export interface RecipeCheckReport {
12
- valid: boolean;
13
- diagnostics: RecipeCheckDiagnostic[];
14
- resources?: Record<string, number>;
15
- }
16
- export declare function checkRecipeAtLoad(recipeDir: string, env: NodeJS.ProcessEnv): Promise<RecipeCheckReport>;
17
- export declare function formatRecipeDiagnostics(diagnostics: readonly RecipeCheckDiagnostic[]): string;
18
- //# sourceMappingURL=recipe-check.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"recipe-check.d.ts","sourceRoot":"","sources":["../src/recipe-check.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,qBAAqB,EAAE,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAwPD,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAyC5B;AAED,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,SAAS,qBAAqB,EAAE,GAC5C,MAAM,CAUR"}