@introspection-ai/recipes 0.22.1 → 0.24.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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/api/memory.d.ts +3 -0
  3. package/dist/api/memory.d.ts.map +1 -0
  4. package/dist/api/memory.js +2 -0
  5. package/dist/api/memory.js.map +1 -0
  6. package/dist/api/session.d.ts +1 -0
  7. package/dist/api/session.d.ts.map +1 -1
  8. package/dist/channels/config.d.ts +10 -0
  9. package/dist/channels/config.d.ts.map +1 -0
  10. package/dist/channels/config.js +13 -0
  11. package/dist/channels/config.js.map +1 -0
  12. package/dist/channels/index.d.ts +4 -2
  13. package/dist/channels/index.d.ts.map +1 -1
  14. package/dist/channels/index.js +2 -1
  15. package/dist/channels/index.js.map +1 -1
  16. package/dist/channels/module.d.ts +11 -6
  17. package/dist/channels/module.d.ts.map +1 -1
  18. package/dist/channels/module.js +22 -10
  19. package/dist/channels/module.js.map +1 -1
  20. package/dist/channels/refs.d.ts +3 -3
  21. package/dist/channels/refs.d.ts.map +1 -1
  22. package/dist/channels/refs.js +11 -8
  23. package/dist/channels/refs.js.map +1 -1
  24. package/dist/channels/tools.d.ts +15 -22
  25. package/dist/channels/tools.d.ts.map +1 -1
  26. package/dist/channels/tools.js +279 -114
  27. package/dist/channels/tools.js.map +1 -1
  28. package/dist/channels/types.d.ts +34 -29
  29. package/dist/channels/types.d.ts.map +1 -1
  30. package/dist/channels/types.js +4 -17
  31. package/dist/channels/types.js.map +1 -1
  32. package/dist/child-session.d.ts +3 -0
  33. package/dist/child-session.d.ts.map +1 -1
  34. package/dist/child-session.js +4 -0
  35. package/dist/child-session.js.map +1 -1
  36. package/dist/connector-tools.d.ts +2 -0
  37. package/dist/connector-tools.d.ts.map +1 -1
  38. package/dist/connector-tools.js +17 -15
  39. package/dist/connector-tools.js.map +1 -1
  40. package/dist/mcp-chunks/{chunk-JZDXKYQM.js → chunk-PLSS6TQZ.js} +6 -1
  41. package/dist/mcp-daemon.js +1 -1
  42. package/dist/mcp-run-worker.js +1 -1
  43. package/dist/memory.d.ts +40 -0
  44. package/dist/memory.d.ts.map +1 -0
  45. package/dist/memory.js +146 -0
  46. package/dist/memory.js.map +1 -0
  47. package/dist/recipe-package.d.ts +4 -2
  48. package/dist/recipe-package.d.ts.map +1 -1
  49. package/dist/recipe-package.js +31 -18
  50. package/dist/recipe-package.js.map +1 -1
  51. package/dist/run-controller.d.ts +3 -0
  52. package/dist/run-controller.d.ts.map +1 -1
  53. package/dist/run-controller.js +4 -0
  54. package/dist/run-controller.js.map +1 -1
  55. package/dist/session.d.ts +5 -0
  56. package/dist/session.d.ts.map +1 -1
  57. package/dist/session.js +26 -4
  58. package/dist/session.js.map +1 -1
  59. package/docs/channels.md +171 -58
  60. package/docs/host-api.md +21 -0
  61. package/docs/pi-extension.md +1 -1
  62. package/docs/recipe-format.md +9 -7
  63. package/docs/slack.md +46 -53
  64. package/package.json +9 -5
package/docs/channels.md CHANGED
@@ -1,90 +1,186 @@
1
1
  # Channel tools
2
2
 
3
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.
4
+ then registers one `channels` tool with a `command` discriminator, shaped identically
5
+ for every provider. Reply defaults to the origin; adapters can opt into explicit
6
+ read/send destinations within the same credential session.
6
7
 
7
8
  Two properties follow from that, and both are structural rather than
8
9
  conventional:
9
10
 
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
11
+ - **Explicit targeting is a capability.** `targeting: true` adds `channels send`
12
+ and channel/thread arguments to `channels read`. Adapters without it retain
13
+ bound schemas. Reply, attach, and document tools still use the origin.
14
+ - **A command that the provider cannot support is absent, not failing.** Each
16
15
  adapter declares a capability descriptor, and registration filters on it.
17
- For example, an adapter with no history API has no `channel_read` tool.
16
+ For example, an adapter with no history API has no `read` command.
18
17
 
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. The injected guidance tells the agent to deliver its user-facing
24
- response with `channel_reply`, because a normal final assistant response is not
25
- delivered to the originating channel. `channel_read` remains the only way to
26
- fetch earlier messages when the provider supports it.
18
+ Agents select `tools: [channels]`. By default this exposes all operations supported
19
+ by the adapter. To restrict commands for the recipe, set an allowlist on the
20
+ connector in `package.json`:
27
21
 
28
- ## The tools
22
+ ```json
23
+ {"pi":{"channels":[{"provider":"slack","commands":["list","read","reply"]}]}}
24
+ ```
29
25
 
30
- | Tool | Model arguments | Requires |
26
+ The allowlist applies to all agents using this connector; an empty list exposes
27
+ no channel tool. Unknown or unsupported commands fail at registration. Commands
28
+ outside the allowlist and invalid command arguments are rejected before provider
29
+ calls. The host API uses `registerChannelTools(..., { commands: [...] })` for the
30
+ same restriction.
31
+
32
+ ### Required replies
33
+
34
+ `requireReply` defaults to `true` when channel tools are enabled: inbound turns
35
+ must deliver a successful final `reply`. Set `requireReply: false` explicitly to
36
+ opt out, for example for a read-only command allowlist. UI and automation turns
37
+ without an origin are unaffected. Disabled channel tools remain disabled;
38
+ an enabled connector with required replies must expose `reply`.
39
+
40
+ `reply` accepts `final` (default `true`). Use `final: false` for progress updates.
41
+ Only a successful final reply satisfies the guard, not reactions or explicit
42
+ sends. The extension adds delivery instructions and queues one corrective
43
+ follow-up if the agent finishes without replying. If that also finishes without
44
+ a reply, a visible `channel-delivery-failed` message records the failure locally.
45
+ It never automatically publishes private assistant text. Aborted and provider-error
46
+ runs are not retried by this guard. Hosts must wait for `agent_settled`, not an
47
+ intermediate `agent_end`, before declaring a run complete.
48
+
49
+ Migration: rename `pi.connectors` to `pi.channels`, replace agent `channel_*` tool names with `channels`, and move any
50
+ operation restrictions to `commands` (without the `channel_` prefix). This is a
51
+ breaking interface change; old individual tool names are not registered.
52
+
53
+ Before each agent run, the extension adds origin metadata to the system prompt:
54
+ provider, channel/thread IDs, conversation scope, and optional name/permalink.
55
+ Labels are untrusted metadata; normal assistant output is not delivered to the
56
+ channel. Recipe instructions decide when to reply, stay silent, read history,
57
+ or send elsewhere; tool descriptions explain how.
58
+
59
+ User messages are not rewritten. Cloud ingress retains per-message `from`,
60
+ `message_id`, and `sent_at` attribution. Legacy custom origin entries are
61
+ filtered from model context when resuming older sessions.
62
+ For required replies, the single corrective reminder repeats the origin metadata
63
+ so a failed or omitted delivery can be retried against the correct destination.
64
+ An uncertain delivery must be checked before retrying to avoid duplicates.
65
+ Origin fields are JSON inside a single `<channel_context>` wrapper. JSON Unicode
66
+ escapes for `<`, `>`, and `&` keep provider labels from breaking the wrapper while
67
+ preserving valid JSON and the original values when parsed.
68
+
69
+ The context message is not an authorization boundary. Tools continue to resolve
70
+ the origin from host state, never from message text, and validate explicit targets
71
+ through the host's policy. Non-channel triggers receive no channel context.
72
+
73
+ ## Test channel recipes
74
+
75
+ Use `introspection dev` to test local recipe files through the platform's
76
+ provider proxy. Standalone `SLACK_BOT_TOKEN` credentials and local destination
77
+ aliases are no longer supported. Originless web and automation runs can still
78
+ use explicit channel targets with the connected provider credentials.
79
+
80
+ ## Commands
81
+
82
+ | Command (`channels` + `command`) | Additional arguments | Requires |
31
83
  | --- | --- | --- |
32
- | `channel_reply` | `text` (Markdown) | always |
33
- | `channel_read` | `limit?`, `cursor?` | `read` |
34
- | `channel_react` | `message`, `emoji`, `action?` (`add` or `remove`) | `react` |
35
- | `channel_edit` | `message`, `text` | `edit` |
36
- | `channel_retract` | `message` | `retract` |
37
- | `channel_attach` | `path`, `title?`, `comment?` | `attach` |
38
- | `channel_fetch_file` | `file` (a `file_…` handle), `variant?` | `fetch_file` |
39
- | `channel_post_document` | `title`, `markdown` | `documents` |
40
-
41
- `channel_reply`, `channel_read`, and `channel_react` are active by default when
42
- the provider supports them. The other selected tools start inactive, and the
43
- model can find them through `tool_search`.
84
+ | `channels reply` | `text` (Markdown), `final?` (default `true`) | always |
85
+ | `channels send` | `channel_id`, `thread_id?`, `text` | `targeting` |
86
+ | `channels list` | none | `list` |
87
+ | `channels read` | `channel_id?`, `thread_id?`, `limit?`, `cursor?` | `read`; addressing requires `targeting` |
88
+ | `channels react` | `message`, `emoji`, `action?` (`add` or `remove`) | `react` |
89
+ | `channels edit` | `message`, `text` | `edit` |
90
+ | `channels retract` | `message` | `retract` |
91
+ | `channels attach` | `path`, `title?`, `comment?` | `attach` |
92
+ | `channels fetch_file` | `file` (a `file_…` handle), `variant?` | `fetch_file` |
93
+ | `channels post_document` | `title`, `markdown` | `documents` |
94
+
95
+ `channels` is active immediately. Its command schema includes only supported,
96
+ allowed operations; no tool search is needed.
44
97
 
45
98
  Reply content is **Markdown**. Each adapter renders it in the provider's own
46
99
  format. There is no raw provider payload because the same tool contract must
47
100
  work with every adapter.
48
101
 
102
+ ### Targets and pagination
103
+
104
+ - `channels({command: "list"})` returns the channels available to the provider credential session.
105
+ - `channels({command: "read"})` reads the origin conversation.
106
+ - `channels({command: "read", channel_id: "C2"})` reads that channel's timeline.
107
+ - `channels({command: "read", channel_id: "C2", thread_id: "123.4"})` reads that thread.
108
+ - `channels({command: "read", thread_id: null})` reads the origin channel timeline.
109
+ - `channels({command: "send", channel_id: "C2", text: "Update"})` posts at channel level;
110
+ supplying `thread_id` posts inside that thread.
111
+
112
+ An explicit channel does not require an origin, but still requires a working
113
+ provider credential. Sending/reading never changes `channels reply`'s origin.
114
+ Thread IDs are provider-native roots/topics, not opaque message handles or
115
+ generic quoted-reply IDs. Quoted replies are not introduced in this first pass.
116
+
117
+ Read results include `target`, optional message `thread_id`/`reply_count`, and
118
+ `next_direction` when the adapter supplies it. Pages are chronological. Repeat
119
+ the same target with a cursor; cross-target cursors fail. Page size may change.
120
+
121
+ ### Access boundary
122
+
123
+ Each adapter and reference store belongs to **one credential session**. Never
124
+ reuse either across installations. The connector loader creates fresh instances.
125
+ No model argument switches credentials or customer connections.
126
+
127
+ Hosts can supply `validateTarget(target, operation)` on a connector session or
128
+ to `registerChannelTools`. It runs for every operation, including mutations and
129
+ file downloads using existing references. Without it, explicit targets rely on
130
+ the existing credential's provider permissions.
131
+
132
+ This is a **tool-layer policy, not a sandbox-wide security boundary**. Direct
133
+ shell/API calls can bypass it if the sandbox has broader credentials. Binding
134
+ enforcement, credential selection, durable receipts, and cross-channel reply
135
+ routing are separate platform work. Sending elsewhere does not imply that
136
+ subsequent replies resume the originating task.
137
+
49
138
  ### Message and file references
50
139
 
51
- `channel_react`, `channel_edit`, and `channel_retract` take a `message`, which is
140
+ `channels react`, `channels edit`, and `channels retract` take a `message`, which is
52
141
  an opaque handle minted by the host for the current session. It is not a Slack
53
142
  timestamp or another provider message ID. Handles come back from
54
- `channel_reply` and `channel_read`, so the model can only act on a message that
143
+ `channels reply`, `channels send`, and `channels read`, so the model can only act on a message that
55
144
  a channel tool returned.
56
145
 
57
- `channel_react` adds a reaction when `action` is omitted. Set `action` to
146
+ `channels react` adds a reaction when `action` is omitted. Set `action` to
58
147
  `remove` to remove the agent's reaction with the same emoji.
59
148
 
60
149
  Edit and retract have an extra check. They accept only a handle for a message
61
150
  that this agent posted. Reading the same message again keeps its original
62
151
  handle and authorship record.
152
+ Each reference resolves its own destination. Handles are session-local; editing
153
+ a previous session's message is not supported.
63
154
 
64
- `channel_fetch_file` works the same way. Attachments returned by `channel_read`
155
+ `channels fetch_file` works the same way. Attachments returned by `channels read`
65
156
  carry a `file_…` handle, and that is the only value the tool accepts. A bot can
66
157
  usually read files from every conversation it belongs to, so accepting a raw
67
- provider file ID would let the model reach files outside the bound conversation.
68
-
69
- ### Enrichment, not lookup tools
70
-
71
- Author names and permalinks are resolved by the adapter in trusted code and
72
- attached to what the agent is already reading: `channel_read` rows carry
73
- `author.display_name`, and `channel_reply` returns a `permalink` where the
158
+ provider file ID would bypass the requirement to observe the file first.
159
+ File handles also retain the channel/thread read scope where they were observed.
160
+ Fetching revalidates that scope; observing the same file through another scope
161
+ creates a separate handle rather than changing the authority of an earlier one.
162
+
163
+ ### Listing and enrichment
164
+
165
+ `channels list` returns provider channel IDs and names for explicitly targeted
166
+ reads and sends. When the host supplies `validateTarget`, every listed channel
167
+ is checked with the `list` operation and denied entries are omitted before any
168
+ IDs or names become model-visible. Author names and permalinks are resolved by the adapter in trusted code and
169
+ attached to what the agent is already reading: `channels read` rows carry
170
+ `author.display_name`, and `channels reply` returns a `permalink` where the
74
171
  provider has one. There is no `resolve_user` or `get_permalink` tool, because
75
172
  each would require another model turn. Each lookup would also take an
76
173
  addressing argument.
77
174
 
78
175
  ### Unsupported operations
79
176
 
80
- Workspace search, channel listing and joining, directory lookup, and posting to
81
- another conversation are unsupported. The proposal does not choose an API or
82
- access model for those operations. A separate proposal can define them when a
83
- concrete use case requires them.
177
+ Search, individual channel-info lookup, thread listing, channel joining, and directory
178
+ lookup are deferred. Explicit IDs can come from task context; thread IDs can
179
+ also come from channel reads.
84
180
 
85
181
  Typing indicators and presence are runtime effects rather than model decisions.
86
- Streaming controls how a reply is delivered. The runtime derives idempotency
87
- keys, and the webhook already removes duplicate inbound events.
182
+ Streaming controls how a reply is delivered. These tools do not add durable
183
+ send idempotency or claim exactly-once delivery.
88
184
 
89
185
  ## Declare a channel connector
90
186
 
@@ -94,7 +190,7 @@ keys, and the webhook already removes duplicate inbound events.
94
190
  "@introspection-ai/recipe-channel-slack": "^0.1.0"
95
191
  },
96
192
  "pi": {
97
- "connectors": [
193
+ "channels": [
98
194
  {
99
195
  "provider": "slack"
100
196
  }
@@ -103,11 +199,11 @@ keys, and the webhook already removes duplicate inbound events.
103
199
  }
104
200
  ```
105
201
 
106
- The connector declaration enables the provider package and its supported tool
202
+ The channel declaration enables the provider package and its supported tool
107
203
  catalog. The agent YAML file is the only place that narrows the catalog:
108
204
 
109
205
  ```yaml
110
- tools: [channel_reply, channel_read, channel_react, channel_edit, channel_retract, channel_fetch_file]
206
+ tools: [channels]
111
207
  ```
112
208
 
113
209
  The host fails when an agent selects a tool that the provider does not support.
@@ -120,13 +216,15 @@ Each provider package declares the capabilities that it can support.
120
216
 
121
217
  An adapter supplies transport and a capability descriptor. It writes no tool
122
218
  schemas. The shared schema keeps providers from defining different forms of
123
- the same operation, and it prevents a provider from adding an addressing
124
- argument.
219
+ the same operation. Set `targeting: true` and implement `send(ctx, {text})` to
220
+ opt into the shared targeting schema; set `list: true` and implement `list()` to
221
+ expose `channels list`. Omit these capabilities for origin-bound tools.
125
222
 
126
223
  ```ts
127
224
  import {
128
225
  createChannelConnectorModule,
129
226
  type ChannelAdapter,
227
+ type ChannelConfig,
130
228
  } from "@introspection-ai/recipes/channels";
131
229
 
132
230
  const capabilities = {
@@ -143,24 +241,39 @@ class MyAdapter implements ChannelAdapter {
143
241
  async retract(ctx, { ref }) { /* retract an agent-authored message */ }
144
242
  }
145
243
 
244
+ function targetFrom(config: ChannelConfig | null) {
245
+ if (!config || config.provider !== "my-channel") {
246
+ throw new Error("No my-channel destination is configured");
247
+ }
248
+ return {
249
+ provider: "my-channel",
250
+ conversation: config.channel_ref,
251
+ thread: config.thread_ref,
252
+ };
253
+ }
254
+
146
255
  export default createChannelConnectorModule({
147
256
  provider: "my-channel",
148
257
  capabilities,
149
- createSession: ({ env }) => ({
258
+ createSession: ({ config, env }) => ({
150
259
  adapter: new MyAdapter(/* client from env */),
151
260
  // A function, so a task with no channel origin still starts: the tools
152
261
  // fail when called, the session does not fail to open.
153
- target: () => resolveTargetFrom(env),
262
+ target: () => targetFrom(config),
154
263
  }),
155
264
  });
156
265
  ```
157
266
 
267
+ The host resolves `ChannelConfig` once from the `INTROSPECTION_TASK_CHANNEL_*`
268
+ environment contract. Provider packages map `channel_ref` and `thread_ref` to
269
+ their own API fields and do not define provider-specific origin types.
270
+
158
271
  Registration checks that the adapter implements every method its capabilities
159
272
  claim, so a descriptor cannot promise a tool the adapter does not have.
160
273
 
161
- The result is an ordinary `RecipeConnectorModule`. Manifest validation, agent
162
- tool selection, and `tool_search` work without provider-specific code in the
163
- Recipe.
274
+ The result is an ordinary `RecipeConnectorModule`. Manifest validation and agent
275
+ tool selection work without provider-specific code in the Recipe. The unified
276
+ tool is active immediately and does not require `tool_search`.
164
277
 
165
278
  ## Provider pages
166
279
 
package/docs/host-api.md CHANGED
@@ -70,6 +70,10 @@ const handle = await createAgentSession({
70
70
  sessionManager,
71
71
  additionalSkillPaths,
72
72
  materializedSkillPaths,
73
+ memory: {
74
+ indexPath: "/workspace/memories/MEMORY.md",
75
+ },
76
+ memoryOverride,
73
77
  transformSystemPrompt,
74
78
  onDiagnostics,
75
79
  onEvent,
@@ -109,6 +113,23 @@ gateway-decorated model without reimplementing Recipe semantics. The Recipe
109
113
  continues to own model configuration and tool selection; host seams replace
110
114
  transport and materialized resources, not the portable definition.
111
115
 
116
+ ### Persistent memory context
117
+
118
+ Hosts can provide one durable memory index through `memory`. Recipes loads the
119
+ index once during session construction and bounds the injected index content to
120
+ 200 lines and 25,000 UTF-8 bytes after XML escaping by default. Missing and
121
+ empty indexes render nothing. The host owns the directory, persistence, and
122
+ identity scope; Recipes assumes no filesystem layout.
123
+
124
+ The index should stay concise and can link to focused files in its directory.
125
+ Recipes injects it without adding usage or precedence instructions.
126
+
127
+ `memoryOverride` follows Pi's resource override pattern. It can replace, filter,
128
+ or disable the loaded index before rendering. The exported `loadMemoryIndex`
129
+ and `formatMemoryForPrompt` helpers let custom hosts use the same primitives.
130
+ Recipe-specific memory behavior belongs in `SYSTEM.md` or agent
131
+ `system_instructions`, while the host binds the actual memory resource.
132
+
112
133
  The default in-process subagent controller uses that same Recipe snapshot for
113
134
  every child, so no session reparses the package or observes a different source
114
135
  snapshot. Injected controllers own child selection and execution after the root
@@ -53,7 +53,7 @@ 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 providers from `pi.connectors` and registers the tools selected by the agent;
56
+ 4. loads providers from `pi.channels` and registers the tools selected by the agent;
57
57
  5. loads selected skills, package prompts, and the complete Recipe extension closure;
58
58
  6. materializes declared MCP bindings from host or local configuration;
59
59
  7. exposes only the declared subagents through the shared `agent` tool.
@@ -81,9 +81,9 @@ 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
84
+ ## Channel tools
85
85
 
86
- `pi.connectors` declares official provider tools that the host may register for
86
+ `pi.channels` declares official provider tools that the host may register for
87
87
  the Recipe. The declaration does not contain a connector ID, workspace ID, or
88
88
  credential. A host binds those values when it starts a task.
89
89
 
@@ -93,7 +93,7 @@ credential. A host binds those values when it starts a task.
93
93
  "@introspection-ai/recipe-channel-slack": "^0.1.0"
94
94
  },
95
95
  "pi": {
96
- "connectors": [
96
+ "channels": [
97
97
  {
98
98
  "provider": "slack"
99
99
  }
@@ -109,13 +109,15 @@ and marks which tools are active by default.
109
109
 
110
110
  The provider package must be in the Recipe's production dependencies and
111
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
112
+ provider. An agent lists each allowed tool by its full name, such as
113
+ `channels`, in its `tools` list. The host fails when the agent names a
115
114
  tool that the connector does not register.
116
115
 
117
116
  Chat providers share one connector shape. A channel connector registers the
118
- provider-neutral `channel_*` tools that its capabilities support. See
117
+ provider-neutral `channels` tool with commands for supported operations. An
118
+ optional `commands` array on the connector restricts these operations for all
119
+ agents using it, for example `{"provider":"slack","commands":["read","reply"]}`.
120
+ Omitting it allows supported commands; an empty list exposes no channel tool. See
119
121
  [Channel tools](channels.md).
120
122
 
121
123
  ## Agents
package/docs/slack.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  `@introspection-ai/recipe-channel-slack` is the Slack adapter for the
4
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.
5
+ capability descriptor. The `channels` tool and its command schemas are
6
+ provider-neutral, so a Recipe written against it is not written against Slack.
7
7
 
8
8
  Slack sends inbound events to the existing Events API webhook. The tools make
9
9
  ordinary HTTP requests to the Slack Web API with the bot that received the
@@ -18,7 +18,7 @@ protocol.
18
18
  "@introspection-ai/recipe-channel-slack": "^0.1.0"
19
19
  },
20
20
  "pi": {
21
- "connectors": [
21
+ "channels": [
22
22
  {
23
23
  "provider": "slack"
24
24
  }
@@ -30,48 +30,48 @@ protocol.
30
30
  Commit the package manager lockfile. The host loads the package only for a
31
31
  Recipe that declares the connector.
32
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`.
33
+ The connector registers one `channels` tool. Agents select `tools: [channels]`;
34
+ all supported commands are immediately visible. An optional connector `commands`
35
+ allowlist restricts operations for every agent using that connector.
37
36
 
38
37
  ## What Slack registers
39
38
 
40
39
  | Tool | Slack operation |
41
40
  | --- | --- |
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`
41
+ | `channels reply` | `chat.postMessage` into the origin channel and thread |
42
+ | `channels send` | `chat.postMessage` into an explicit channel and optional thread |
43
+ | `channels list` | paged `conversations.list` returning accessible channels |
44
+ | `channels read` | `conversations.replies` in a thread, else `conversations.history` |
45
+ | `channels react` | `reactions.add` or `reactions.remove` |
46
+ | `channels edit` | `chat.update` for a message the agent posted |
47
+ | `channels retract` | `chat.delete` for a message the agent posted |
48
+ | `channels fetch_file` | `files.info` plus a private file download |
49
+
50
+ Slack history requests at most 15 messages and makes one history request per
51
+ tool call. Threads start at the beginning and page forward; channel timelines
52
+ start with recent messages and page backward. Each page is chronological, and
53
+ `next_direction` describes pagination. Repeat the target with the cursor.
54
+ This replaces the old unbounded full-thread fetch/backward session cache.
55
+ Provider rate limits still apply; failures are not automatically retried.
56
+
57
+ `channels attach` and `channels post_document` are not registered: `files.uploadV2`
62
58
  and canvases are not implemented in this package yet, and the capability
63
59
  descriptor says so rather than registering tools that fail.
64
60
 
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
61
+ `channels list` returns all non-archived public and private channels where the
62
+ bot is a member. `channels send` requires an explicit `channel_id` (listing first is not required); `thread_id` is
63
+ optional. `channels read` accepts optional targets, defaulting to the origin. Explicit channel without
64
+ thread means timeline/top-level, not the origin's thread. Reply stays bound.
65
+ Author display names (`users.info`) and
67
66
  permalinks (`chat.getPermalink`) are resolved inside the adapter and attached to
68
67
  message rows and reply results, so there is no user lookup or permalink tool.
69
68
  Edit and retract also require an opaque reference for a message posted by this
70
69
  agent. They cannot act on another author's message.
71
70
 
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.
71
+ Search, individual channel-info lookup, joining, and directory lookup remain deferred.
72
+ Tools use one existing credential session; they do not select installations or
73
+ enforce project/customer bindings. The optional host target-policy callback
74
+ constrains these tools, not direct shell/API access.
75
75
 
76
76
  ## Cloud access
77
77
 
@@ -84,11 +84,16 @@ allowed path, and adds the bot token before the request leaves for Slack.
84
84
  The adapter refuses to send a task locator when the provider proxy URL is
85
85
  missing. It never falls back to sending the locator to Slack.
86
86
 
87
- After `channel_reply` succeeds in cloud, the adapter posts the `connector_posted`
87
+ After `channels reply` succeeds in cloud, the adapter posts the `connector_posted`
88
88
  task event to the Data Plane, which checks the agent session, current run,
89
89
  provider, and origin channel before recording the new thread root. A later Slack
90
90
  reply then resumes the same task.
91
91
 
92
+ `channels send` deliberately does not emit this origin-bound bridge event, even
93
+ when its explicit destination matches the origin. Its result includes the
94
+ actual target and `bridge_recorded: false`. Cross-channel continuation is not
95
+ implemented by this tools-only change.
96
+
92
97
  Slack writes are attempted once. The adapter does not retry `chat.postMessage`,
93
98
  because Slack accepts no idempotency key for it. If Slack accepts the post but
94
99
  event recording fails, the tool returns the message reference and a
@@ -101,29 +106,17 @@ development runtime starts a cloud sandbox with the local Recipe overlay, so the
101
106
  adapter uses the cloud task origin and provider proxy and needs no local Slack
102
107
  credential. Use `introspection dev --logs` for sandbox logs.
103
108
 
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.
109
+ Standalone channel access through `introspection local` is not supported. It
110
+ has no webhook receiver, cloud task origin, or provider proxy. Use the same
111
+ `introspection dev` workflow for inbound events and outbound channel tools.
119
112
 
120
113
  ## File downloads
121
114
 
122
- `channel_fetch_file` writes a file under the task files directory and returns its
115
+ `channels fetch_file` writes a file under the task files directory and returns its
123
116
  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
117
+ not in model context. It accepts only a `file_…` handle from a `channels read`
118
+ attachment, and resolves that reference's channel before the host policy check.
119
+ On the wire it accepts only `files.slack.com` download URLs, rejects
127
120
  redirects, caps the body at 100 MiB, checks the declared size, and removes
128
121
  partial files after a failure. The `video_low` variant uses Slack's smaller MP4
129
122
  rendition when one exists.
@@ -132,4 +125,4 @@ rendition when one exists.
132
125
 
133
126
  The package exports `SlackChannelAdapter`, `createSlackChannelSession` and
134
127
  `slackChannelTarget` for custom hosts and tests, alongside the default
135
- `slackRecipeConnectorModule`. A normal Recipe uses `pi.connectors` instead.
128
+ `slackRecipeConnectorModule`. A normal Recipe uses `pi.channels` instead.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@introspection-ai/recipes",
3
- "version": "0.22.1",
3
+ "version": "0.24.0",
4
4
  "description": "The open format for vertical agents, built on Pi.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -58,6 +58,10 @@
58
58
  "types": "./dist/api/session.d.ts",
59
59
  "import": "./dist/api/session.js"
60
60
  },
61
+ "./memory": {
62
+ "types": "./dist/api/memory.d.ts",
63
+ "import": "./dist/api/memory.js"
64
+ },
61
65
  "./channels": {
62
66
  "types": "./dist/channels/index.d.ts",
63
67
  "import": "./dist/channels/index.js"
@@ -102,10 +106,10 @@
102
106
  "vitest": "^4.0.18"
103
107
  },
104
108
  "optionalDependencies": {
105
- "@introspection-ai/mcp-client-linux-x64": "0.22.1",
106
- "@introspection-ai/mcp-client-linux-arm64": "0.22.1",
107
- "@introspection-ai/mcp-client-darwin-arm64": "0.22.1",
108
- "@introspection-ai/mcp-client-darwin-x64": "0.22.1"
109
+ "@introspection-ai/mcp-client-linux-x64": "0.24.0",
110
+ "@introspection-ai/mcp-client-linux-arm64": "0.24.0",
111
+ "@introspection-ai/mcp-client-darwin-arm64": "0.24.0",
112
+ "@introspection-ai/mcp-client-darwin-x64": "0.24.0"
109
113
  },
110
114
  "scripts": {
111
115
  "build": "pnpm build:ts && pnpm build:native",