eve 0.32.0 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.md +8 -4
  3. package/dist/src/channel/channel-address.d.ts +4 -1
  4. package/dist/src/channel/channel-address.js +1 -1
  5. package/dist/src/channel/channel-operations.d.ts +3 -1
  6. package/dist/src/channel/compiled-channel.d.ts +2 -1
  7. package/dist/src/channel/cross-channel-receive.d.ts +5 -2
  8. package/dist/src/channel/cross-channel-receive.js +1 -1
  9. package/dist/src/channel/session.d.ts +6 -2
  10. package/dist/src/channel/session.js +1 -1
  11. package/dist/src/channel/types.d.ts +6 -0
  12. package/dist/src/channel/types.js +1 -1
  13. package/dist/src/chunks/{use-eve-agent-CX87G1vU.js → use-eve-agent-3oBnxzhV.js} +27 -17
  14. package/dist/src/chunks/{use-eve-agent-DwyRde1N.js → use-eve-agent-B-Hky2f4.js} +27 -17
  15. package/dist/src/cli/commands/agent-prompt/collect-intent.md +3 -0
  16. package/dist/src/cli/commands/agent-prompt/scaffold.md +4 -2
  17. package/dist/src/cli/commands/info.js +1 -1
  18. package/dist/src/cli/commands/init-repl.d.ts +1 -1
  19. package/dist/src/cli/commands/init-repl.js +1 -1
  20. package/dist/src/cli/commands/init.d.ts +7 -0
  21. package/dist/src/cli/commands/init.js +1 -1
  22. package/dist/src/cli/commands/integration-connect.d.ts +3 -3
  23. package/dist/src/cli/commands/integration-connect.js +1 -1
  24. package/dist/src/cli/commands/integration-setup.js +1 -1
  25. package/dist/src/cli/commands/set.d.ts +20 -0
  26. package/dist/src/cli/commands/set.js +1 -0
  27. package/dist/src/cli/dev/tui/runner.js +1 -1
  28. package/dist/src/cli/dev/tui/setup-issues.js +1 -1
  29. package/dist/src/cli/option-parsers.d.ts +3 -0
  30. package/dist/src/cli/option-parsers.js +1 -1
  31. package/dist/src/cli/run.js +1 -1
  32. package/dist/src/client/agent-info-schema.d.ts +28 -5
  33. package/dist/src/client/agent-info-schema.js +1 -1
  34. package/dist/src/client/session.js +1 -1
  35. package/dist/src/client/types.d.ts +3 -0
  36. package/dist/src/compiler/extension-compatibility.d.ts +19 -9
  37. package/dist/src/compiler/extension-compatibility.js +1 -1
  38. package/dist/src/compiler/manifest.d.ts +119 -19
  39. package/dist/src/compiler/manifest.js +1 -1
  40. package/dist/src/compiler/model-catalog.d.ts +2 -30
  41. package/dist/src/compiler/model-catalog.js +1 -1
  42. package/dist/src/compiler/module-map.d.ts +2 -2
  43. package/dist/src/compiler/module-map.js +2 -2
  44. package/dist/src/compiler/normalize-agent-config.js +1 -1
  45. package/dist/src/compiler/normalize-instructions.js +1 -1
  46. package/dist/src/compiler/normalize-manifest.js +2 -2
  47. package/dist/src/compiler/normalize-skill.js +1 -1
  48. package/dist/src/compiler/normalize-subagent.d.ts +5 -1
  49. package/dist/src/compiler/normalize-subagent.js +1 -1
  50. package/dist/src/compiler/remote-agent-node.d.ts +4 -1
  51. package/dist/src/compiler/workspace-resources.js +1 -1
  52. package/dist/src/context/dynamic-model-lifecycle.d.ts +7 -2
  53. package/dist/src/context/dynamic-model-lifecycle.js +1 -1
  54. package/dist/src/context/dynamic-subagent-lifecycle.js +1 -1
  55. package/dist/src/context/hook-lifecycle.js +1 -1
  56. package/dist/src/context/keys.d.ts +8 -0
  57. package/dist/src/context/keys.js +1 -1
  58. package/dist/src/evals/session.js +1 -1
  59. package/dist/src/execution/dispatch-runtime-actions-step.d.ts +3 -9
  60. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  61. package/dist/src/execution/durable-session-store.d.ts +5 -0
  62. package/dist/src/execution/durable-session-store.js +1 -1
  63. package/dist/src/execution/effective-agent-config.js +1 -1
  64. package/dist/src/execution/node-step.js +1 -1
  65. package/dist/src/execution/parked-delivery-wait.d.ts +8 -0
  66. package/dist/src/execution/parked-delivery-wait.js +1 -1
  67. package/dist/src/execution/route-child-delivery.js +1 -1
  68. package/dist/src/execution/session-command-wire.js +1 -1
  69. package/dist/src/execution/session.d.ts +2 -0
  70. package/dist/src/execution/session.js +2 -2
  71. package/dist/src/execution/settle-cancelled-turn-step.js +1 -1
  72. package/dist/src/execution/subagent-hitl-proxy.d.ts +1 -0
  73. package/dist/src/execution/subagent-hitl-proxy.js +1 -1
  74. package/dist/src/execution/turn-control-receiver.d.ts +2 -0
  75. package/dist/src/execution/turn-control-receiver.js +1 -1
  76. package/dist/src/execution/turn-workflow.js +1 -1
  77. package/dist/src/execution/workflow-entry.js +1 -1
  78. package/dist/src/execution/workflow-steps.d.ts +4 -1
  79. package/dist/src/execution/workflow-steps.js +1 -1
  80. package/dist/src/harness/emission.d.ts +1 -1
  81. package/dist/src/harness/emission.js +1 -1
  82. package/dist/src/harness/hitl/approval-input-requests.d.ts +13 -0
  83. package/dist/src/harness/hitl/approval-input-requests.js +1 -0
  84. package/dist/src/harness/hitl/pending-input-resolution.d.ts +51 -0
  85. package/dist/src/harness/hitl/pending-input-resolution.js +1 -0
  86. package/dist/src/harness/hitl/question-input-requests.d.ts +15 -0
  87. package/dist/src/harness/hitl/question-input-requests.js +1 -0
  88. package/dist/src/harness/hitl/session-limit-input-requests.d.ts +10 -0
  89. package/dist/src/harness/hitl/session-limit-input-requests.js +1 -0
  90. package/dist/src/harness/input-requests.d.ts +17 -105
  91. package/dist/src/harness/input-requests.js +1 -1
  92. package/dist/src/harness/pending-input-batches.d.ts +77 -0
  93. package/dist/src/harness/pending-input-batches.js +1 -0
  94. package/dist/src/harness/proxy-input-requests.d.ts +13 -3
  95. package/dist/src/harness/proxy-input-requests.js +1 -1
  96. package/dist/src/harness/session-limit-enforcement.js +1 -1
  97. package/dist/src/harness/step-hooks.d.ts +1 -1
  98. package/dist/src/harness/step-hooks.js +1 -1
  99. package/dist/src/harness/tool-loop.d.ts +1 -1
  100. package/dist/src/harness/tool-loop.js +2 -2
  101. package/dist/src/harness/types.d.ts +11 -5
  102. package/dist/src/harness/types.js +1 -1
  103. package/dist/src/internal/application/package.js +1 -1
  104. package/dist/src/internal/authored-definition/core.js +1 -1
  105. package/dist/src/internal/authored-definition/schema-backed.js +1 -1
  106. package/dist/src/internal/authored-module-loader.js +1 -1
  107. package/dist/src/internal/authored-module-map-loader.js +1 -1
  108. package/dist/src/internal/model-catalog.d.ts +46 -0
  109. package/dist/src/internal/model-catalog.js +1 -0
  110. package/dist/src/internal/nitro/host/build-vercel-agent-summary.js +1 -1
  111. package/dist/src/internal/nitro/host/create-application-nitro.js +1 -1
  112. package/dist/src/internal/nitro/host/dev-host-fingerprint.js +1 -1
  113. package/dist/src/internal/nitro/routes/agent-info/build-agent-info-response-from-manifest.js +1 -1
  114. package/dist/src/internal/nitro/routes/agent-info/build-agent-info-response.d.ts +20 -10
  115. package/dist/src/internal/nitro/routes/agent-info/build-agent-info-response.js +1 -1
  116. package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
  117. package/dist/src/internal/nitro/routes/info.js +1 -1
  118. package/dist/src/internal/vercel-agent-summary.d.ts +12 -3
  119. package/dist/src/internal/vercel-agent-summary.js +1 -1
  120. package/dist/src/protocol/message.d.ts +2 -2
  121. package/dist/src/protocol/message.js +1 -1
  122. package/dist/src/public/channels/chat-sdk/chatSdkChannel.d.ts +5 -5
  123. package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +2 -2
  124. package/dist/src/public/channels/discord/discordChannel.d.ts +3 -1
  125. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  126. package/dist/src/public/channels/eve.d.ts +3 -1
  127. package/dist/src/public/channels/eve.js +1 -1
  128. package/dist/src/public/channels/github/githubChannel.d.ts +3 -1
  129. package/dist/src/public/channels/github/githubChannel.js +1 -1
  130. package/dist/src/public/channels/index.d.ts +1 -1
  131. package/dist/src/public/channels/linear/linearChannel.d.ts +3 -1
  132. package/dist/src/public/channels/linear/linearChannel.js +1 -1
  133. package/dist/src/public/channels/photon/photonIMessageChannel.d.ts +3 -1
  134. package/dist/src/public/channels/photon/photonIMessageChannel.js +1 -1
  135. package/dist/src/public/channels/slack/slackChannel.d.ts +3 -1
  136. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  137. package/dist/src/public/channels/teams/teamsChannel.d.ts +3 -1
  138. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  139. package/dist/src/public/channels/telegram/telegramChannel.d.ts +3 -1
  140. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  141. package/dist/src/public/channels/twilio/twilioChannel.d.ts +3 -1
  142. package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
  143. package/dist/src/public/definitions/agent.d.ts +5 -7
  144. package/dist/src/public/definitions/agent.js +1 -1
  145. package/dist/src/public/definitions/channel.d.ts +3 -1
  146. package/dist/src/public/definitions/channel.js +1 -1
  147. package/dist/src/public/definitions/tool.d.ts +1 -5
  148. package/dist/src/public/definitions/tool.js +1 -1
  149. package/dist/src/public/index.d.ts +1 -1
  150. package/dist/src/runtime/agent/bootstrap.d.ts +19 -6
  151. package/dist/src/runtime/agent/bootstrap.js +1 -1
  152. package/dist/src/runtime/agent/model-catalog.d.ts +10 -0
  153. package/dist/src/runtime/agent/model-catalog.js +1 -0
  154. package/dist/src/runtime/agent/resolve-model.d.ts +8 -4
  155. package/dist/src/runtime/agent/resolve-model.js +1 -1
  156. package/dist/src/runtime/framework-channels/index.js +1 -1
  157. package/dist/src/runtime/resolve-agent-graph.js +1 -1
  158. package/dist/src/runtime/resolve-agent.d.ts +2 -2
  159. package/dist/src/runtime/resolve-agent.js +1 -1
  160. package/dist/src/runtime/resolve-channel.js +1 -1
  161. package/dist/src/runtime/resolve-dynamic-subagent.d.ts +2 -5
  162. package/dist/src/runtime/subagents/dynamic-agent-config.d.ts +8 -7
  163. package/dist/src/runtime/subagents/dynamic-agent-config.js +1 -1
  164. package/dist/src/runtime/types.d.ts +11 -4
  165. package/dist/src/setup/flows/deploy.d.ts +5 -3
  166. package/dist/src/setup/flows/deploy.js +1 -1
  167. package/dist/src/setup/flows/ensure-vercel-project.d.ts +5 -1
  168. package/dist/src/setup/flows/ensure-vercel-project.js +1 -1
  169. package/dist/src/setup/flows/model-source-change.d.ts +1 -0
  170. package/dist/src/setup/flows/model-source-change.js +1 -1
  171. package/dist/src/setup/integrations/discord/setup.js +1 -1
  172. package/dist/src/setup/integrations/github/setup.js +1 -1
  173. package/dist/src/setup/integrations/linear/setup.js +1 -1
  174. package/dist/src/setup/integrations/photon/setup-flow.js +1 -1
  175. package/dist/src/setup/integrations/slack/setup.js +1 -1
  176. package/dist/src/setup/scaffold/create/add-to-project.d.ts +2 -0
  177. package/dist/src/setup/scaffold/create/add-to-project.js +1 -1
  178. package/dist/src/setup/scaffold/create/project.d.ts +3 -1
  179. package/dist/src/setup/scaffold/create/project.js +4 -4
  180. package/dist/src/setup/scaffold/create/web-template.d.ts +9 -0
  181. package/dist/src/setup/scaffold/create/web-template.js +291 -1
  182. package/dist/src/setup/scaffold/index.d.ts +1 -1
  183. package/dist/src/setup/scaffold/update/channels.d.ts +4 -13
  184. package/dist/src/setup/scaffold/update/channels.js +2 -2
  185. package/dist/src/setup/scaffold/update/web-options.d.ts +15 -0
  186. package/dist/src/setup/scaffold/update/web-options.js +1 -0
  187. package/dist/src/setup/vercel-project.js +0 -0
  188. package/dist/src/shared/agent-definition.d.ts +23 -23
  189. package/dist/src/shared/agent-definition.js +1 -1
  190. package/dist/src/shared/channel-definition.d.ts +3 -1
  191. package/dist/src/shared/dynamic-tool-definition.d.ts +4 -10
  192. package/dist/src/shared/dynamic-tool-definition.js +1 -1
  193. package/dist/src/svelte/index.js +1 -1
  194. package/dist/src/svelte/use-eve-agent.js +1 -1
  195. package/dist/src/vue/index.js +1 -1
  196. package/dist/src/vue/use-eve-agent.js +1 -1
  197. package/docs/agent-config.md +30 -19
  198. package/docs/channels/chat-sdk.mdx +5 -5
  199. package/docs/channels/custom.mdx +20 -0
  200. package/docs/channels/discord.mdx +1 -0
  201. package/docs/channels/eve.mdx +2 -0
  202. package/docs/channels/github.mdx +1 -1
  203. package/docs/channels/linear.mdx +1 -1
  204. package/docs/channels/overview.mdx +19 -0
  205. package/docs/channels/photon.mdx +5 -2
  206. package/docs/channels/slack.mdx +5 -11
  207. package/docs/concepts/execution-model-and-durability.mdx +5 -8
  208. package/docs/concepts/sessions-runs-and-streaming.md +6 -6
  209. package/docs/guides/dynamic-capabilities.md +24 -20
  210. package/docs/guides/frontend/overview.mdx +31 -10
  211. package/docs/guides/frontend/use-eve-agent-svelte.mdx +37 -15
  212. package/docs/guides/frontend/use-eve-agent-vue.mdx +38 -15
  213. package/docs/installation.mdx +7 -1
  214. package/docs/reference/cli.md +33 -8
  215. package/docs/tools/human-in-the-loop.md +2 -2
  216. package/package.json +1 -1
@@ -17,6 +17,10 @@ export default defineAgent({
17
17
  });
18
18
  ```
19
19
 
20
+ For a static AI Gateway model ID, you can make the same source change from the
21
+ project root with `eve set --model anthropic/claude-opus-4.8` or from the local
22
+ dev TUI with `/model anthropic/claude-opus-4.8`.
23
+
20
24
  The root `agent.ts` can be omitted when no runtime config is needed. In that case, eve defaults
21
25
  to `anthropic/claude-sonnet-5`. When `agent.ts` is present, `model` is required.
22
26
 
@@ -45,21 +49,23 @@ Model use is subject to the terms, data-processing commitments, retention behavi
45
49
 
46
50
  ### Choose the model dynamically
47
51
 
48
- `model` also accepts `defineDynamic({ fallback, events })`. `fallback` is the
49
- compiled static model: it anchors build-time metadata (routing, credentials,
50
- context window) and serves whenever no dynamic selection is set.
52
+ `model` also accepts `defineDynamic({ events })`. Each matching handler must
53
+ return the concrete model for its scope; a dynamic model has no compiled
54
+ default.
51
55
 
52
56
  ```ts title="agent/agent.ts"
53
57
  import { defineAgent, defineDynamic } from "eve";
54
58
 
55
59
  export default defineAgent({
56
60
  model: defineDynamic({
57
- fallback: "anthropic/claude-sonnet-5",
58
61
  events: {
59
- "session.started": (_event, ctx) =>
60
- ctx.session.auth.initiator?.attributes.plan === "enterprise"
61
- ? "anthropic/claude-opus-4.8"
62
- : null,
62
+ "session.started": (_event, ctx) => {
63
+ if (ctx.session.auth.initiator?.attributes.plan === "enterprise") {
64
+ return "anthropic/claude-opus-4.8";
65
+ }
66
+
67
+ return "anthropic/claude-sonnet-5";
68
+ },
63
69
  },
64
70
  }),
65
71
  });
@@ -68,24 +74,28 @@ export default defineAgent({
68
74
  Handlers receive the shared [dynamic resolver
69
75
  context](./guides/dynamic-capabilities) (`ctx.session`, `ctx.channel`,
70
76
  `ctx.messages`) and return a gateway model id, an AI SDK `LanguageModel`, a
71
- selection object, or `null` to leave the scope unset.
77
+ selection object. Returning `null` or `undefined` fails the turn.
72
78
 
73
79
  - **Scopes.** `session.started` (once per session), `turn.started` (once per
74
80
  turn), `step.started` (every model step). Precedence: step > turn >
75
- session > `fallback`. Prefer `session.started`: prompt caches are per
76
- model, so every switch re-ingests the conversation at uncached prices.
77
- - **Failures degrade, never fail the turn.** A resolver that throws or
78
- returns an invalid selection logs an error and leaves its scope unset.
79
- Build-time validation covers only `fallback`; a selected model without
80
- credentials fails at request time.
81
+ session. Prefer `session.started`: prompt caches are per model, so every
82
+ switch re-ingests the conversation at uncached prices. If no active
83
+ selection exists before model-dependent work begins, the turn fails.
84
+ - **Failures stop the turn.** A resolver that throws, returns no model, or
85
+ returns an invalid selection fails before the provider call. A selected
86
+ model without valid credentials fails at request time.
81
87
  - **Serialization.** Session/turn selections must be model id strings; return
82
88
  live `LanguageModel` objects only from `step.started`.
83
89
  - **Selection object.** `{ model, modelContextWindowTokens?, modelOptions? }`.
84
- Set `modelContextWindowTokens` when the selected model's window differs
85
- from the fallback's — it is never inherited. Omitted `modelOptions` reuses
86
- the agent-level `modelOptions`.
90
+ When `modelContextWindowTokens` is omitted, eve resolves it from the AI
91
+ Gateway catalog and caches successful metadata in durable session state for
92
+ 24 hours. Set it explicitly for an unlisted or custom model. Dynamic agents
93
+ cannot set sibling `modelContextWindowTokens` or `modelOptions` fields;
94
+ return per-model values from the handler.
87
95
 
88
- Runtime identity reports a dynamic agent's model as `dynamic:<fallback id>`.
96
+ The `session.started` runtime identity does not include a model id for a
97
+ dynamic agent. Each public `step.started` event reports the concrete `modelId`
98
+ selected for that model call.
89
99
 
90
100
  ## Reasoning effort
91
101
 
@@ -103,6 +113,7 @@ Supported values are `"provider-default"`, `"none"`, `"minimal"`, `"low"`,
103
113
  `"medium"`, `"high"`, and `"xhigh"`. The selected model and provider determine
104
114
  which levels are available and how they map to provider-native settings. Use
105
115
  `modelOptions.providerOptions` when you need provider-specific reasoning controls.
116
+ Run `eve set --reasoning high` to update this field from the command line.
106
117
 
107
118
  ## Compaction
108
119
 
@@ -99,22 +99,22 @@ bot.onNewMention(async (thread, message) => {
99
99
 
100
100
  `options` accepts `{ thread, auth?, title?, mode?, callback?, adapterName?, turnPolicy? }`. `title` sets the eve session's display title without changing the model message; `auth` attaches an authenticated principal to the turn.
101
101
 
102
- ### Experimental steering
102
+ ### Steering
103
103
 
104
- By default, a message sent while an eve turn is active waits for the next turn. Pass `turnPolicy: "experimental-steer"` to cancel the active turn before sending the new message:
104
+ Messages default to `turnPolicy: "steer"`: a message sent while an eve turn is active is durably buffered, then cancels that turn and starts as its replacement. Set `turnPolicy: "queue"` when the active turn should finish first:
105
105
 
106
106
  ```ts
107
107
  bot.onSubscribedMessage(async (thread, message) => {
108
108
  await send(message.text, {
109
109
  thread,
110
- turnPolicy: "experimental-steer",
110
+ turnPolicy: "queue",
111
111
  });
112
112
  });
113
113
  ```
114
114
 
115
- This is cancellation-backed steering: the interrupted turn emits `turn.cancelled`, and the replacement message starts a new turn with a new turn ID. Partial output and completed side effects from the interrupted turn are not rolled back. If no turn is active, the message is sent normally. Use the default `turnPolicy: "queue"` when every turn should finish in order.
115
+ Cancellation-backed steering emits `turn.cancelled`, and the replacement message starts a new turn with a new turn ID. Partial output and completed side effects from the interrupted turn are not rolled back. If no turn is active, the message is sent normally. You can set the policy once on `chatSdkChannel({ turnPolicy })` or override it per `send(...)`.
116
116
 
117
- This policy controls overlapping eve turns. Chat SDK's separate `concurrency` option controls overlapping webhook handlers; use `concurrency: "concurrent"` when each inbound message should reach the experimental steering path immediately.
117
+ This policy controls overlapping eve turns. Chat SDK's separate `concurrency` option controls overlapping webhook handlers; use `concurrency: "concurrent"` when each inbound message should reach the steering path immediately.
118
118
 
119
119
  ### Delivery
120
120
 
@@ -109,6 +109,26 @@ await session.reset({ reason: "Retire this session" });
109
109
  await session.getEventStream({ startIndex: 12 });
110
110
  ```
111
111
 
112
+ Message sends use the channel's `turnPolicy`, which defaults to `"steer"`. An accepted message arriving during an active turn is durably buffered before eve cancels that turn and starts the message as a replacement turn. Configure `turnPolicy: "queue"` on `defineChannel(...)` when active turns should finish in order, or override one send:
113
+
114
+ ```ts
115
+ export default defineChannel({
116
+ turnPolicy: "queue",
117
+ routes: [
118
+ POST("/messages", async (request, { from }) => {
119
+ const body = await request.json();
120
+ await from(body.threadId).send(body.message, {
121
+ auth: null,
122
+ turnPolicy: "steer",
123
+ });
124
+ return new Response(null, { status: 202 });
125
+ }),
126
+ ],
127
+ });
128
+ ```
129
+
130
+ The same override is available on fixed `Session.send(...)` and cross-channel `to(...).send(...)`. `respond(...)` never steers: it delivers only addressed input responses. Use `cancel()` when you need to stop work without a replacement message.
131
+
112
132
  Attaching does no lookup. The first operation reports whether the ID is active.
113
133
  Call `resolveSession(address)` only when you explicitly need to snapshot an
114
134
  address's current owner as a fixed handle.
@@ -16,6 +16,7 @@ pnpm eve add channel/discord
16
16
 
17
17
  The setup guides you through creating a Discord application and bot, then:
18
18
 
19
+ - signs you in to Vercel and creates or links a project when needed;
19
20
  - validates the bot token;
20
21
  - creates a Vercel Connect client and attaches `/eve/v1/discord` as its trigger destination;
21
22
  - registers an application command with a required `message` option;
@@ -56,6 +56,8 @@ curl -X POST https://<deployment>/eve/v1/session/wrun_A \
56
56
  -d '{"inputResponses":[{"requestId":"req_A","optionId":"approve"}]}'
57
57
  ```
58
58
 
59
+ Follow-up messages use `turnPolicy: "steer"` by default. If a turn is active, eve buffers the message before cooperatively cancelling that turn, then starts the follow-up as a replacement turn with a new turn ID. Set `turnPolicy: "queue"` on `eveChannel(...)` when follow-ups should wait for active turns to finish. `inputResponses` never steer.
60
+
59
61
  Sending a message to an unknown or terminal session ID returns `409` with
60
62
  `{"code":"session_not_active","error":"The session is no longer active.","ok":false}`.
61
63
  TypeScript clients expose the stable code as `ClientError.code`. The route never
@@ -14,7 +14,7 @@ Run the registry setup from the agent directory:
14
14
  eve add channel/github
15
15
  ```
16
16
 
17
- The flow checks that the Vercel CLI is authenticated, creates or links a Vercel project when needed, provisions an app-scoped GitHub Connect client, and registers `/eve/v1/github` as a trigger destination. It then installs `@vercel/connect` and writes `agent/channels/github.ts` with the connector UID.
17
+ The flow signs you in to Vercel when needed, creates or links a Vercel project, provisions an app-scoped GitHub Connect client, and registers `/eve/v1/github` as a trigger destination. It then installs `@vercel/connect` and writes `agent/channels/github.ts` with the connector UID.
18
18
 
19
19
  Vercel Connect creates the GitHub App, receives and verifies its webhooks, and forwards them to the deployed agent. After deploying, open the GitHub App in the Connect dashboard and install it in the organization or account where you want to use it. Add the generated invocation token (for example, `@my-agent`) to a new issue, pull request, or review comment to start a conversation. GitHub may not autocomplete the token or render it as a linked mention.
20
20
 
@@ -16,7 +16,7 @@ eve add linear
16
16
 
17
17
  Select **Linear Channel** in the component checklist. **Linear MCP** is also selected by default if you want the agent to search and update Linear through MCP. Run `eve add channel/linear-agent` instead to install only the channel directly.
18
18
 
19
- The flow checks that the Vercel CLI is authenticated, creates or links a Vercel project when needed, reuses a compatible existing connector when available or provisions an app-scoped Linear Connect client, and registers `/eve/v1/linear` as a trigger destination. It then installs `@vercel/connect` and writes `agent/channels/linear.ts` with the connector UID.
19
+ The flow signs you in to Vercel when needed, creates or links a Vercel project, reuses a compatible existing connector when available or provisions an app-scoped Linear Connect client, and registers `/eve/v1/linear` as a trigger destination. It then installs `@vercel/connect` and writes `agent/channels/linear.ts` with the connector UID.
20
20
 
21
21
  Vercel Connect creates the Linear app with the `app:assignable` and `app:mentionable` scopes required for Agent Sessions, receives and verifies `AgentSessionEvent` webhooks, and forwards them to the deployed agent. After deploying, open the Linear app in the Connect dashboard and install it in the workspace where you want to delegate work. Then delegate an issue or mention the agent in a Linear Agent Session.
22
22
 
@@ -13,6 +13,25 @@ eve ships a base HTTP channel plus first-class platform channels, and you can au
13
13
 
14
14
  After a channel normalizes input, eve runs the same agent runtime regardless of where the message came from. Tools and instructions do not need channel-specific logic.
15
15
 
16
+ ## Overlapping messages
17
+
18
+ Channels default to `turnPolicy: "steer"`. When an accepted message arrives while a turn is active, eve first durably buffers the message, then cooperatively cancels the active turn and starts a replacement turn. The cancelled turn emits `turn.cancelled` followed by `session.waiting`; the replacement starts with a new turn ID. Output already streamed and completed side effects are not rolled back.
19
+
20
+ Set `turnPolicy: "queue"` on any built-in or custom channel when each turn must finish before the next message starts:
21
+
22
+ ```ts
23
+ export default defineChannel({
24
+ turnPolicy: "queue",
25
+ routes: [
26
+ // ...
27
+ ],
28
+ });
29
+ ```
30
+
31
+ `from(address).send(...)`, fixed `Session.send(...)`, cross-channel sends, and Chat SDK bridge sends also accept a per-send `turnPolicy` override. Pure `inputResponses` deliveries answer their pending request without steering. Explicit `cancel()` remains the stop-without-replacement operation.
32
+
33
+ Channel admission still runs first. Ignored mentions, rejected signatures, duplicates, and any other dropped platform events never affect the active turn.
34
+
16
35
  Each channel has its own provider terms, data flow, auth model, and user-consent expectations. Before sending non-public, sensitive, regulated, or production data through a channel, confirm that the channel provider and your configured scopes, signature checks, route auth, and delivery behavior are appropriate for your use case.
17
36
 
18
37
  ## Where channels live
@@ -7,8 +7,9 @@ Use `photonIMessageChannel` to receive and reply to iMessages through a Photon p
7
7
 
8
8
  Run `eve add channel/photon-imessage` to create or use a Photon project and register
9
9
  your phone number. It then asks whether to configure Vercel Connect or portable
10
- credentials. With Vercel Connect, eve creates the connector and configures
11
- Photon’s webhook through Connect. The channel resolves credentials lazily when
10
+ credentials. With Vercel Connect, eve signs you in and creates or links a Vercel
11
+ project when needed, then creates the connector and configures Photon’s webhook
12
+ through Connect. The channel resolves credentials lazily when
12
13
  the adapter first initializes:
13
14
 
14
15
  ```ts title="agent/channels/photon.ts"
@@ -26,6 +27,8 @@ read, and continues the same eve session for every message in an iMessage
26
27
  conversation. A new accepted message cooperatively cancels an active turn and
27
28
  steers its replacement turn.
28
29
 
30
+ Set `turnPolicy: "queue"` when every response should finish before Photon starts the next message.
31
+
29
32
  Customize inbound dispatch with `onMessage`. Return `null` to ignore a message:
30
33
 
31
34
  ```ts
@@ -18,7 +18,7 @@ The command recommends Vercel Connect, then scaffolds `agent/channels/slack.ts`
18
18
 
19
19
  ### Recommended: use Vercel Connect
20
20
 
21
- Vercel Connect setup requires an authenticated Vercel CLI and a linked Vercel project. The guided flow creates or reuses a Slack connector, waits for you to install it in a workspace, and registers `/eve/v1/slack` as a trigger destination before writing the channel file.
21
+ Vercel Connect setup requires the Vercel CLI. The guided flow signs you in when needed, creates or links a Vercel project, creates or reuses a Slack connector, waits for you to install it in a workspace, and registers `/eve/v1/slack` as a trigger destination before writing the channel file.
22
22
 
23
23
  The generated channel resolves credentials at runtime:
24
24
 
@@ -236,24 +236,18 @@ export default slackChannel({
236
236
 
237
237
  `threadContext` requires the matching Slack history scope. Thread helpers reuse messages already loaded within the same inbound handler, and overlapping refreshes share one `conversations.replies` request. Omit it when the agent should see only direct mentions. `loadThreadContextMessages` remains available when you need custom filtering or non-model processing of the raw thread messages.
238
238
 
239
- #### Cancel an active turn
239
+ #### Control overlapping turns
240
240
 
241
- Message hooks (`onMessage`, `onAppMention`, and `onDirectMessage`) receive thread-bound session operations directly on `ctx`. Call `ctx.cancel({ turnId? })` before returning `{ auth }` to stop the current turn and queue the new Slack message as replacement input, producing a debounce-like experience:
241
+ Accepted Slack messages use cancellation-backed steering by default: eve buffers the new message, cancels the active turn, and starts a replacement turn. Admission happens first, so ignored mentions and messages rejected by your hooks never cancel work. Set `turnPolicy: "queue"` when every active turn should finish:
242
242
 
243
243
  ```ts title="agent/channels/slack.ts"
244
244
  export default slackChannel({
245
245
  credentials: connectSlackCredentials("slack/my-agent"),
246
- async onMessage(ctx, message) {
247
- const shouldHandle = ctx.isBotMentioned() || (await ctx.isSubscribed());
248
- if (!shouldHandle || message.author?.isBot) return null;
249
-
250
- await ctx.cancel();
251
- return { auth: null };
252
- },
246
+ turnPolicy: "queue",
253
247
  });
254
248
  ```
255
249
 
256
- `"accepted"` means the live thread session durably queued the cancellation request. A parked session consumes it as a no-op; `"no_active_turn"` means the thread has no active session owner. Both are successful outcomes, so the hook can still return `{ auth }` to deliver the replacement message. Cancellation never sends input itself. A custom `onInteraction` handler receives the same flat operations, which is useful for a Stop button that should cancel without replacement input.
250
+ Message hooks (`onMessage`, `onAppMention`, and `onDirectMessage`) also receive thread-bound session operations directly on `ctx`. `ctx.cancel({ turnId? })` remains useful for a Stop button: it cancels without sending replacement input. `"accepted"` means the live thread session durably queued that request; a parked session consumes it as a no-op, and `"no_active_turn"` means the thread has no active session owner.
257
251
 
258
252
  Message and interaction hooks expose every current-owner operation directly on `ctx`:
259
253
 
@@ -66,7 +66,7 @@ You don't write workflow code directly. Workflow primitives (`start()`, `resumeH
66
66
 
67
67
  Some work has to wait, including a human approving a [tool](../tools), an interactive OAuth sign-in for a [connection](../connections), or a long-running [subagent](../subagents). At those points the turn parks durably. The workflow suspends and holds no compute until the input it's waiting on arrives (a click, a callback, a child completing), even if that's much later. When it does, the conversation picks up exactly where it left off.
68
68
 
69
- ## Message delivery and queueing
69
+ ## Message delivery and steering
70
70
 
71
71
  eve does not maintain a durable FIFO queue of user messages for a session. An
72
72
  ID-addressed HTTP delivery and a channel-address delivery both target the
@@ -79,14 +79,11 @@ channel alias. Rekeying replaces only that channel alias. An HTTP-created
79
79
  session has the stable ID alias and no channel continuation token. Competing
80
80
  channel input is not forwarded to the owner.
81
81
 
82
- When a session is waiting, a delivery through its ID or current channel address
83
- wakes the session and starts the next turn. When a turn is already active, the
84
- inbox may accept additional deliveries, but the runtime only drains them at
85
- specific workflow boundaries. If more than one delivery is ready when the driver
86
- checks, eve may fold them into the next turn; that drain is best-effort and
87
- depends on workflow and transport timing.
82
+ When a session is waiting, a delivery through its ID or current channel address wakes it and starts the next turn. Message sends default to `turnPolicy: "steer"`: while a turn is active, the session driver durably buffers the replacement before requesting cooperative cancellation. The interrupted turn emits `turn.cancelled` followed by `session.waiting`, then the replacement starts with a new turn ID. Partial output and completed side effects are not rolled back.
88
83
 
89
- So don't rely on concurrent sends to the same session behaving like a typical ordered chat queue. For deterministic behavior, send one user turn at a time and wait for `session.waiting` before sending the next message to the same session. If your channel can receive bursts while the agent is working, keep your own per-session queue in the channel or app layer, then deliver the next message after the session parks again. Separate sessions still run independently.
84
+ `turnPolicy: "queue"` preserves the message until the active turn settles. If several deliveries are ready when the driver checks, eve may fold adjacent messages into the next turn while preserving their arrival order. Pure `inputResponses` deliveries do not steer; they remain available to the pending request they address.
85
+
86
+ Every built-in and custom channel accepts a default `turnPolicy`, and imperative message sends can override it. The policy is part of the same durable delivery command as the message, so concurrent senders cannot separate replacement ownership from cancellation intent. Separate sessions still run independently.
90
87
 
91
88
  ## Subagents
92
89
 
@@ -148,17 +148,17 @@ curl -X POST http://127.0.0.1:2000/eve/v1/session/<sessionId> \
148
148
  -d '{"message":"Now send the short version."}'
149
149
  ```
150
150
 
151
- The follow-up reuses the same durable session: same history, same state.
151
+ The follow-up reuses the same durable session: same history, same state. Message sends default to cancellation-backed `"steer"`; if a turn is active, eve buffers the follow-up, cancels that turn, and starts the message under a new turn ID. Channels and TypeScript `Session.send(...)` calls can select `turnPolicy: "queue"` when active work should finish first. Structured `inputResponses` never steer.
152
152
 
153
- If the session is waiting on a human-in-the-loop approval, a matching text reply such as `approve` or `cancel` answers the approval. Other follow-up text is held until the approval is answered, so an unrelated message does not implicitly deny the pending tool call.
153
+ If one pending batch is waiting on a human-in-the-loop approval, a matching text reply such as `approve` or `cancel` answers it. Unrelated text starts an ordinary turn immediately without denying the tool call; the approval stays pending and answerable. A later structured `inputResponses` answer keyed by its `requestId` still resumes the original tool call, even after intervening turns.
154
154
 
155
- If the session is waiting on `ask_question`, a follow-up message clears that pending request before the model continues. An exact option match or permitted freeform response answers the question; any other message marks the question unanswered and starts the follow-up turn.
155
+ With one question-only batch, an exact option match or permitted freeform response answers `ask_question`. Any other follow-up marks the question unanswered and starts the new turn. With several approval or question batches pending, eve does not guess which batch plain text addresses: the message starts an ordinary turn and the batches stay open. Use structured responses to target requests unambiguously.
156
156
 
157
- A response is stale when its request is no longer pending: the question or approval was already answered, cleared by a follow-up message, or cancelled. eve delivers a stale response to the model as a new user message, and the model decides whether the old selection still matters. A stale approval never authorizes the earlier tool call; the model must request the action and approval again if they are still needed.
157
+ A structured response matches any currently pending request by ID, not only the newest batch. It becomes stale only after that request was answered, cleared, or cancelled. eve delivers a stale response to the model as a new user message, and the model decides whether the old selection still matters. A stale approval never authorizes the earlier tool call; the model must request the action and approval again if they are still needed.
158
158
 
159
- Responses match pending requests by request ID, so a response to an older request stays a plain user message even while a different question or approval is pending. Like any follow-up message, a stale response clears a pending question and is held while an approval is pending.
159
+ One delivery can answer requests from several batches. eve resumes approval-bearing batches in durable order and carries later answers forward until each batch can resume.
160
160
 
161
- For deterministic ordering, send one follow-up at a time and wait for the next `session.waiting` event before sending another message to the same session. See [message delivery and queueing](./execution-model-and-durability#message-delivery-and-queueing) for the current runtime contract.
161
+ Multiple replacement messages retain their durable arrival order and may be folded into the same replacement turn when they arrive before cancellation settles. See [message delivery and steering](./execution-model-and-durability#message-delivery-and-steering) for the current runtime contract.
162
162
 
163
163
  ## Cancel the in-flight turn
164
164
 
@@ -7,26 +7,26 @@ description: "Resolve models, subagents, tools, skills, and instructions at runt
7
7
 
8
8
  ## Dynamic models
9
9
 
10
- The `model` field in `agent.ts` accepts `defineDynamic({ fallback, events })`.
11
- Resolvers run at `session.started`, `turn.started`, or `step.started`
12
- (precedence: step > turn > session > `fallback`); `null` leaves a scope unset
13
- and failures degrade to the next scope. Prefer `session.started` — prompt
14
- caches are per model, so switching mid-session re-ingests the conversation at
15
- uncached prices. See
10
+ The `model` field in `agent.ts` accepts `defineDynamic({ events })`. Resolvers
11
+ run at `session.started`, `turn.started`, or `step.started` (precedence: step >
12
+ turn > session). Every matching handler must return a concrete model. A
13
+ missing, invalid, or throwing selection fails the turn before model-dependent
14
+ work begins. Prefer `session.started` — prompt caches are per model, so
15
+ switching mid-session re-ingests the conversation at uncached prices. See
16
16
  [agent configuration](../agent-config#choose-the-model-dynamically) for the
17
17
  full contract.
18
18
 
19
- The agent always needs exactly one model, so the compiled fallback anchors
20
- build-time metadata. Tools, skills, instructions, and subagents default by
21
- authoring a static entry (or returning `null`), so `fallback` on their
22
- `defineDynamic` export is a build error.
19
+ Dynamic models do not compile a default model or model metadata. When a
20
+ resolver first selects a model, eve normalizes the selection and resolves any
21
+ omitted context-window metadata from the AI Gateway catalog. Dynamic tools,
22
+ skills, instructions, and subagents may return `null` to omit a capability.
23
23
 
24
24
  ## Dynamic subagents
25
25
 
26
26
  Wrap a declared subagent's own `agent.ts` in `defineDynamic` when its
27
27
  availability depends on the caller, tenant, environment, or a feature flag.
28
- Return the child definition to configure and expose it. Return `null` or
29
- `undefined` to omit it from the parent's model-visible tools.
28
+ Return the child definition to configure and expose it. Return `null` to omit
29
+ it from the parent's model-visible tools.
30
30
 
31
31
  ```ts title="agent/subagents/finance/agent.ts"
32
32
  import { defineAgent, defineDynamic } from "eve";
@@ -44,15 +44,19 @@ export default defineDynamic({
44
44
  });
45
45
  ```
46
46
 
47
- eve always compiles the subagent's filesystem manifest, including its
48
- instructions, tools, skills, connections, sandbox, and nested subagents. When
49
- the resolver selects it, eve injects the returned agent configuration into that
50
- compiled manifest. Each resolution can return a different model or other
51
- runtime agent settings. Runtime-selected models must use string model IDs;
52
- build and Workflow-world configuration cannot be selected at runtime.
47
+ eve always compiles the subagent's filesystem resources, including its
48
+ instructions, tools, skills, connections, sandbox, and nested subagents. It
49
+ does not compile an agent config or placeholder model for a dynamic subagent.
50
+ When the resolver selects the subagent, eve combines the returned config with
51
+ those resources before starting the child session. Each resolution can return
52
+ a different model or other runtime agent settings. A returned local config
53
+ must use a static model; it cannot contain another `defineDynamic` model.
54
+ Runtime-selected models must use string model IDs. Put build configuration on
55
+ the outer `defineDynamic` definition; build and Workflow-world configuration
56
+ cannot be selected in a handler result.
53
57
 
54
58
  A single-file remote subagent uses the same lifecycle. Return
55
- `defineRemoteAgent(...)` to expose the selected deployment, or nil to omit it:
59
+ `defineRemoteAgent(...)` to expose the selected deployment, or `null` to omit it:
56
60
 
57
61
  ```ts title="agent/subagents/finance.ts"
58
62
  import { defineDynamic, defineRemoteAgent } from "eve";
@@ -77,7 +81,7 @@ outbound request without entering durable workflow state.
77
81
 
78
82
  Dynamic subagents support `session.started` and `turn.started`. A turn selection
79
83
  shadows the session selection for that turn, including when the turn handler
80
- returns nil. If a resolver throws or returns an invalid definition, eve logs the
84
+ returns `null`. If a resolver throws or returns an invalid definition, eve logs the
81
85
  failure and omits the subagent.
82
86
 
83
87
  The resolved set applies to local and remote direct delegation and the
@@ -113,20 +113,41 @@ received attachment as a `file` part on the user message. The part includes `med
113
113
 
114
114
  ## Human-in-the-loop prompts
115
115
 
116
- Tools opt into approval with `approval`, and the model can also ask a question with `ask_question` — see [Human-in-the-loop](/docs/human-in-the-loop) for the server-side model. Either way the stream emits an `input.requested` event, and the pending request rides on a `dynamic-tool` part of the latest message at `part.toolMetadata?.eve?.inputRequest`. Read it, then answer through the same session with `respond()`:
116
+ Tools opt into approval with `approval`, and the model can also ask a question with `ask_question` — see [Human-in-the-loop](/docs/human-in-the-loop) for the server-side model. Either way the stream emits an `input.requested` event, and the pending request rides on a `dynamic-tool` part at `part.toolMetadata?.eve?.inputRequest`. Scan every message because an unrelated turn can add newer messages while an approval stays open, then answer through the same session with `respond()`:
117
117
 
118
118
  ```tsx
119
- const request = agent.data.messages
120
- .at(-1)
121
- ?.parts.find((part) => part.type === "dynamic-tool" && part.toolMetadata?.eve?.inputRequest)
122
- ?.toolMetadata?.eve?.inputRequest;
123
-
124
- if (request) {
125
- await agent.respond([{ requestId: request.requestId, optionId: "approve" }]);
126
- }
119
+ const pendingRequests = agent.data.messages
120
+ .flatMap((message) => message.parts)
121
+ .flatMap((part) => {
122
+ if (part.type !== "dynamic-tool" || part.state !== "approval-requested") return [];
123
+ const request = part.toolMetadata?.eve?.inputRequest;
124
+ return request ? [request] : [];
125
+ });
126
+
127
+ return pendingRequests.map((request) => (
128
+ <fieldset key={request.requestId}>
129
+ <legend>
130
+ {request.kind === "tool-approval"
131
+ ? "Approval required"
132
+ : request.kind === "question"
133
+ ? "Question"
134
+ : "Session limit"}
135
+ </legend>
136
+ <p>{request.prompt}</p>
137
+ {request.options?.map((option) => (
138
+ <button
139
+ key={option.id}
140
+ onClick={() => void agent.respond([{ requestId: request.requestId, optionId: option.id }])}
141
+ type="button"
142
+ >
143
+ {option.label}
144
+ </button>
145
+ ))}
146
+ </fieldset>
147
+ ));
127
148
  ```
128
149
 
129
- `request.prompt` and `request.options` give you what you need to render the approve and cancel UI. The default reducer marks the part as responded immediately, then updates it again once eve streams the resumed result.
150
+ For a question with `allowFreeform`, render a text input and send `{ requestId, text }`. The default reducer marks each matching part as responded immediately. Approved tools update again when eve streams their result.
130
151
 
131
152
  ## Authorization prompts
132
153
 
@@ -77,26 +77,48 @@ await agent.send([
77
77
 
78
78
  ## Human-in-the-loop prompts
79
79
 
80
- A tool opts into approval with `approval` ([Tools](../../tools)). When one fires, the pending request rides along on a `dynamic-tool` part of the latest message at `part.toolMetadata?.eve?.inputRequest`. Read it, then answer through the same session with `agent.respond(inputResponses)`:
80
+ A tool opts into approval with `approval` ([Tools](../../tools)). When one fires, the pending request rides along on a `dynamic-tool` part at `part.toolMetadata?.eve?.inputRequest`. Scan every message because an unrelated turn can add newer messages while the approval stays open, then answer through the same session with `agent.respond(inputResponses)`:
81
81
 
82
- ```ts
83
- import type { EveDynamicToolPart, EveMessagePart } from "eve/svelte";
84
-
85
- const isDynamicToolPart = (part: EveMessagePart): part is EveDynamicToolPart =>
86
- part.type === "dynamic-tool";
82
+ ```svelte
83
+ <script lang="ts">
84
+ import { useEveAgent } from "eve/svelte";
87
85
 
88
- const request = agent.data.messages
89
- .at(-1)
90
- ?.parts.filter(isDynamicToolPart)
91
- .map((part) => part.toolMetadata?.eve?.inputRequest)
92
- .find((value) => value !== undefined);
86
+ const agent = useEveAgent();
87
+ const pendingRequests = $derived(
88
+ agent.data.messages.flatMap((message) =>
89
+ message.parts.flatMap((part) => {
90
+ if (part.type !== "dynamic-tool" || part.state !== "approval-requested") return [];
91
+ const request = part.toolMetadata?.eve?.inputRequest;
92
+ return request ? [request] : [];
93
+ }),
94
+ ),
95
+ );
96
+ </script>
93
97
 
94
- if (request) {
95
- await agent.respond([{ requestId: request.requestId, optionId: "approve" }]);
96
- }
98
+ {#each pendingRequests as request (request.requestId)}
99
+ <fieldset>
100
+ <legend>
101
+ {request.kind === "tool-approval"
102
+ ? "Approval required"
103
+ : request.kind === "question"
104
+ ? "Question"
105
+ : "Session limit"}
106
+ </legend>
107
+ <p>{request.prompt}</p>
108
+ {#each request.options ?? [] as option (option.id)}
109
+ <button
110
+ type="button"
111
+ onclick={() =>
112
+ void agent.respond([{ requestId: request.requestId, optionId: option.id }])}
113
+ >
114
+ {option.label}
115
+ </button>
116
+ {/each}
117
+ </fieldset>
118
+ {/each}
97
119
  ```
98
120
 
99
- The find-and-answer flow is identical across frameworks. The [React hook reference](./overview) covers the longer walkthrough.
121
+ For a question with `allowFreeform`, bind a text input and send `{ requestId, text }`. The [React hook reference](./overview) covers the shared behavior.
100
122
 
101
123
  ## Stop, reset, and resume
102
124
 
@@ -88,28 +88,51 @@ async function onFileChange(event: Event) {
88
88
 
89
89
  ## Human-in-the-loop prompts
90
90
 
91
- A tool opts into approval with `approval` ([Tools](../../tools)). When it triggers, the pending request shows up as a `dynamic-tool` part on the latest message at `part.toolMetadata?.eve?.inputRequest`. Read it, then answer through the same session with `respond(inputResponses)`:
91
+ A tool opts into approval with `approval` ([Tools](../../tools)). When it triggers, the pending request shows up on a `dynamic-tool` part at `part.toolMetadata?.eve?.inputRequest`. Scan every message because an unrelated turn can add newer messages while the approval stays open, then answer through the same session with `respond(inputResponses)`:
92
92
 
93
- ```ts
94
- import type { EveDynamicToolPart, EveMessagePart } from "eve/vue";
93
+ ```vue
94
+ <script setup lang="ts">
95
+ import { computed } from "vue";
96
+ import { useEveAgent } from "eve/vue";
95
97
 
96
98
  const { data, respond } = useEveAgent();
97
99
 
98
- const isDynamicToolPart = (part: EveMessagePart): part is EveDynamicToolPart =>
99
- part.type === "dynamic-tool";
100
-
101
- const request = data.value.messages
102
- .at(-1)
103
- ?.parts.filter(isDynamicToolPart)
104
- .map((part) => part.toolMetadata?.eve?.inputRequest)
105
- .find((value) => value !== undefined);
100
+ const pendingRequests = computed(() =>
101
+ data.value.messages.flatMap((message) =>
102
+ message.parts.flatMap((part) => {
103
+ if (part.type !== "dynamic-tool" || part.state !== "approval-requested") return [];
104
+ const request = part.toolMetadata?.eve?.inputRequest;
105
+ return request ? [request] : [];
106
+ }),
107
+ ),
108
+ );
109
+ </script>
106
110
 
107
- if (request) {
108
- await respond([{ requestId: request.requestId, optionId: "approve" }]);
109
- }
111
+ <template>
112
+ <fieldset v-for="request in pendingRequests" :key="request.requestId">
113
+ <legend>
114
+ {{
115
+ request.kind === "tool-approval"
116
+ ? "Approval required"
117
+ : request.kind === "question"
118
+ ? "Question"
119
+ : "Session limit"
120
+ }}
121
+ </legend>
122
+ <p>{{ request.prompt }}</p>
123
+ <button
124
+ v-for="option in request.options ?? []"
125
+ :key="option.id"
126
+ type="button"
127
+ @click="respond([{ requestId: request.requestId, optionId: option.id }])"
128
+ >
129
+ {{ option.label }}
130
+ </button>
131
+ </fieldset>
132
+ </template>
110
133
  ```
111
134
 
112
- The find-and-answer flow is the same across every framework. The [React hook reference](./overview) covers the longer walkthrough.
135
+ For a question with `allowFreeform`, bind a text input and send `{ requestId, text }`. The [React hook reference](./overview) covers the shared behavior.
113
136
 
114
137
  ## Stop, reset, and resume
115
138