eve 0.84.0 → 0.85.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.
- package/CHANGELOG.md +46 -0
- package/dist/src/channel/channel-operations.d.ts +2 -18
- package/dist/src/channel/compiled-channel.d.ts +3 -7
- package/dist/src/channel/cross-channel-receive.d.ts +13 -11
- package/dist/src/channel/cross-channel-receive.js +1 -1
- package/dist/src/channel/delivery.js +1 -1
- package/dist/src/channel/schedule.js +1 -1
- package/dist/src/channel/session.d.ts +26 -4
- package/dist/src/channel/session.js +1 -1
- package/dist/src/channel/types.d.ts +2 -0
- package/dist/src/chunks/{use-eve-agent-BbbguIdB.js → use-eve-agent-B2jCfcnG.js} +29 -12
- package/dist/src/chunks/{use-eve-agent-DWjyzplb.js → use-eve-agent-DCLFi26P.js} +29 -12
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/client/client.js +1 -1
- package/dist/src/client/delivery.d.ts +2 -0
- package/dist/src/client/delivery.js +1 -1
- package/dist/src/client/open-stream.js +1 -1
- package/dist/src/client/optimistic-message-submissions.js +1 -1
- package/dist/src/client/session-store.js +1 -1
- package/dist/src/client/session.js +1 -1
- package/dist/src/client/sessions.d.ts +1 -0
- package/dist/src/client/sessions.js +1 -1
- package/dist/src/client/types.d.ts +7 -4
- package/dist/src/compiled/.vendor-stamp.json +2 -2
- package/dist/src/compiled/@chat-adapter/slack/index.js +13 -13
- package/dist/src/compiled/@vercel/sandbox/api-client/api-client.d.ts +75 -0
- package/dist/src/compiled/@vercel/sandbox/api-client/validators.d.ts +4 -0
- package/dist/src/compiled/@vercel/sandbox/command.d.ts +78 -0
- package/dist/src/compiled/@vercel/sandbox/drive.d.ts +41 -1
- package/dist/src/compiled/@vercel/sandbox/index.js +13 -13
- package/dist/src/compiled/@vercel/sandbox/network-policy.d.ts +35 -4
- package/dist/src/compiled/@vercel/sandbox/sandbox.d.ts +18 -4
- package/dist/src/compiled/@vercel/sandbox/session.d.ts +29 -3
- package/dist/src/compiled/@vercel/sandbox/utils/pipe-stdin.d.ts +19 -0
- package/dist/src/compiled/_chunks/client/{core-CspsxXR-.js → core-BsvaaMUJ.js} +1 -1
- package/dist/src/compiled/_chunks/node/{auth-Dztlx9pE.js → auth-v5pS3mLo.js} +1 -1
- package/dist/src/compiled/_chunks/node/is-stream-DsuQX_bd.js +1 -0
- package/dist/src/compiled/_chunks/node/token-DHGH-jVc.js +1 -0
- package/dist/src/compiled/_chunks/node/token-util-BH2pw9Qm.js +25 -0
- package/dist/src/compiled/_chunks/node/token-util-CcvxNv76.js +1 -0
- package/dist/src/compiled/_chunks/node/version-C2PfNyEv.js +1 -0
- package/dist/src/compiled/zod/index.js +1 -1
- package/dist/src/compiled/zod/v4/core.js +1 -1
- package/dist/src/compiled/zod-validation-error/index.js +1 -1
- package/dist/src/compiler/artifacts.d.ts +1 -0
- package/dist/src/compiler/artifacts.js +1 -1
- package/dist/src/compiler/manifest.d.ts +2 -2
- package/dist/src/compiler/manifest.js +1 -1
- package/dist/src/compiler/model-catalog.d.ts +8 -18
- package/dist/src/compiler/model-catalog.js +1 -1
- package/dist/src/compiler/normalize-agent-config.js +1 -1
- package/dist/src/compiler/normalize-manifest-types.d.ts +3 -0
- package/dist/src/compiler/normalize-manifest.js +1 -1
- package/dist/src/context/keys.d.ts +7 -8
- package/dist/src/context/keys.js +1 -1
- package/dist/src/eve-channel/index.js +1 -1
- package/dist/src/eve-channel/request.d.ts +1 -0
- package/dist/src/eve-channel/request.js +1 -1
- package/dist/src/eve-channel/support.js +1 -1
- package/dist/src/execution/agent-sessions/context.d.ts +5 -1
- package/dist/src/execution/agent-sessions/context.js +1 -1
- package/dist/src/execution/agent-sessions/steps.js +1 -1
- package/dist/src/execution/dynamic-workflow/tool.js +1 -1
- package/dist/src/execution/legacy-session/snapshot.d.ts +1 -2
- package/dist/src/execution/legacy-session/snapshot.js +1 -1
- package/dist/src/execution/proxied-deliver-step.js +1 -1
- package/dist/src/execution/public-destination.d.ts +3 -0
- package/dist/src/execution/public-destination.js +1 -1
- package/dist/src/execution/publish-session-events.js +1 -1
- package/dist/src/execution/sandbox/bindings/docker-cli.d.ts +2 -0
- package/dist/src/execution/sandbox/bindings/docker-cli.js +1 -1
- package/dist/src/execution/sandbox/bindings/docker-connection.js +1 -1
- package/dist/src/execution/sandbox/bindings/just-bash-runtime.js +1 -1
- package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
- package/dist/src/execution/sandbox/multiplexed-command.d.ts +11 -1
- package/dist/src/execution/sandbox/multiplexed-command.js +1 -1
- package/dist/src/execution/sandbox/session.js +1 -1
- package/dist/src/execution/session/checkpoint-migrations.d.ts +16 -5
- package/dist/src/execution/session/checkpoint-migrations.js +1 -1
- package/dist/src/execution/session/checkpoint-refusal.d.ts +4 -0
- package/dist/src/execution/session/checkpoint-refusal.js +1 -0
- package/dist/src/execution/session/entry.js +1 -1
- package/dist/src/execution/session/handoff-steps.d.ts +7 -1
- package/dist/src/execution/session/handoff-steps.js +1 -1
- package/dist/src/execution/session/imported-conversation.d.ts +2 -0
- package/dist/src/execution/session/imported-conversation.js +1 -0
- package/dist/src/execution/session/interrupted-step.js +1 -1
- package/dist/src/execution/session/pre-v27-checkpoint.d.ts +36 -0
- package/dist/src/execution/session/pre-v27-checkpoint.js +1 -0
- package/dist/src/execution/session/program.d.ts +2 -0
- package/dist/src/execution/session/program.js +1 -1
- package/dist/src/execution/session/turn-step.js +1 -1
- package/dist/src/execution/session-callback-request.js +1 -1
- package/dist/src/execution/session-inbox/address.d.ts +1 -0
- package/dist/src/execution/session-inbox/handle.d.ts +7 -0
- package/dist/src/execution/session-inbox/handle.js +1 -0
- package/dist/src/execution/session-inbox/owner.d.ts +4 -4
- package/dist/src/execution/session-inbox/owner.js +1 -1
- package/dist/src/execution/session-inbox/resume.d.ts +1 -0
- package/dist/src/execution/session-inbox/resume.js +1 -1
- package/dist/src/execution/tool-auth.js +1 -1
- package/dist/src/execution/tools/agent-router-workflow.js +1 -1
- package/dist/src/execution/tools/channel-to.d.ts +12 -0
- package/dist/src/execution/tools/channel-to.js +1 -0
- package/dist/src/execution/tools/workflow/body.d.ts +3 -0
- package/dist/src/execution/tools/workflow/body.js +1 -1
- package/dist/src/execution/tools/workflow/step-context.d.ts +5 -0
- package/dist/src/execution/tools/workflow/step-execution.js +1 -1
- package/dist/src/execution/tools/workflow/step.js +1 -1
- package/dist/src/execution/workflow-callback-url.d.ts +2 -3
- package/dist/src/execution/workflow-callback-url.js +1 -1
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/harness/compaction/step.js +1 -1
- package/dist/src/harness/model-call/call.d.ts +3 -1
- package/dist/src/harness/model-call/call.js +1 -1
- package/dist/src/harness/model-call/run.js +1 -1
- package/dist/src/harness/semantic-errors/rules/gateway.js +1 -1
- package/dist/src/harness/session-machine/transitions.js +1 -1
- package/dist/src/internal/application/compiled-artifacts.d.ts +2 -0
- package/dist/src/internal/application/compiled-artifacts.js +2 -2
- package/dist/src/internal/application/optional-package-install.js +1 -1
- package/dist/src/internal/application/stamped-package-version.js +1 -1
- package/dist/src/internal/attachments/limited-read.d.ts +2 -0
- package/dist/src/internal/attachments/limited-read.js +1 -1
- package/dist/src/internal/attachments/url-refs.js +1 -1
- package/dist/src/internal/capability-error.d.ts +3 -0
- package/dist/src/internal/capability-error.js +1 -0
- package/dist/src/internal/model-catalog.d.ts +39 -16
- package/dist/src/internal/model-catalog.js +1 -1
- package/dist/src/internal/nitro/host/dev-build-host.js +1 -1
- package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
- package/dist/src/protocol/delivery.d.ts +1 -0
- package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +1 -1
- package/dist/src/public/channels/discord/discordChannel.js +1 -1
- package/dist/src/public/channels/github/githubChannel.js +1 -1
- package/dist/src/public/channels/linear/linearChannel.js +1 -1
- package/dist/src/public/channels/linq/inboundContent.js +1 -1
- package/dist/src/public/channels/slack/answers.d.ts +2 -3
- package/dist/src/public/channels/slack/answers.js +1 -1
- package/dist/src/public/channels/slack/api.d.ts +23 -3
- package/dist/src/public/channels/slack/api.js +1 -1
- package/dist/src/public/channels/slack/app-manifest.d.ts +2 -0
- package/dist/src/public/channels/slack/app-manifest.js +1 -1
- package/dist/src/public/channels/slack/auth.d.ts +2 -1
- package/dist/src/public/channels/slack/auth.js +1 -1
- package/dist/src/public/channels/slack/code-channel.d.ts +42 -0
- package/dist/src/public/channels/slack/code-channel.js +1 -0
- package/dist/src/public/channels/slack/defaults.d.ts +7 -3
- package/dist/src/public/channels/slack/defaults.js +3 -3
- package/dist/src/public/channels/slack/hitl.d.ts +7 -17
- package/dist/src/public/channels/slack/hitl.js +1 -1
- package/dist/src/public/channels/slack/index.d.ts +4 -3
- package/dist/src/public/channels/slack/index.js +1 -1
- package/dist/src/public/channels/slack/interaction-cards.d.ts +0 -2
- package/dist/src/public/channels/slack/interaction-cards.js +1 -1
- package/dist/src/public/channels/slack/interactions.js +1 -1
- package/dist/src/public/channels/slack/open-code-channel.d.ts +30 -0
- package/dist/src/public/channels/slack/open-code-channel.js +1 -0
- package/dist/src/public/channels/slack/private-approval-delivery.d.ts +16 -4
- package/dist/src/public/channels/slack/private-approval-delivery.js +1 -1
- package/dist/src/public/channels/slack/renderers.d.ts +58 -6
- package/dist/src/public/channels/slack/renderers.js +1 -1
- package/dist/src/public/channels/slack/reply-delivery.d.ts +4 -0
- package/dist/src/public/channels/slack/reply-delivery.js +1 -1
- package/dist/src/public/channels/slack/request-card-render.d.ts +45 -0
- package/dist/src/public/channels/slack/request-card-render.js +1 -0
- package/dist/src/public/channels/slack/request-cards.d.ts +35 -0
- package/dist/src/public/channels/slack/request-cards.js +3 -0
- package/dist/src/public/channels/slack/sign-in-cancel.d.ts +3 -5
- package/dist/src/public/channels/slack/sign-in-cancel.js +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +109 -22
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/slack/task-card.d.ts +3 -3
- package/dist/src/public/channels/slack/task-card.js +1 -1
- package/dist/src/public/channels/slack/thread-status.js +1 -1
- package/dist/src/public/channels/teams/api.js +1 -1
- package/dist/src/public/channels/teams/defaults.js +1 -1
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/teams/verify.d.ts +9 -5
- package/dist/src/public/channels/teams/verify.js +1 -1
- package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
- package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
- package/dist/src/public/nuxt/module.js +1 -1
- package/dist/src/public/schedules/subscription.d.ts +10 -1
- package/dist/src/reactions/kinds/model.js +1 -1
- package/dist/src/reactions/kinds/subagent.js +1 -1
- package/dist/src/runtime/agent/resolve-model.d.ts +3 -4
- package/dist/src/runtime/agent/resolve-model.js +1 -1
- package/dist/src/runtime/loaders/artifact-paths.d.ts +1 -0
- package/dist/src/runtime/loaders/artifact-paths.js +1 -1
- package/dist/src/runtime/loaders/bundled-artifacts.d.ts +2 -0
- package/dist/src/runtime/loaders/bundled-artifacts.js +1 -1
- package/dist/src/runtime/loaders/model-metadata.d.ts +4 -0
- package/dist/src/runtime/loaders/model-metadata.js +1 -0
- package/dist/src/runtime/resolve-agent.js +1 -1
- package/dist/src/runtime/sessions/compiled-agent-cache.d.ts +3 -0
- package/dist/src/runtime/sessions/compiled-agent-cache.js +1 -1
- package/dist/src/runtime/subagents/dynamic-agent-config.d.ts +2 -2
- package/dist/src/runtime/subagents/dynamic-agent-config.js +1 -1
- package/dist/src/runtime/subagents/workflow.js +1 -1
- package/dist/src/sandbox/provider.d.ts +2 -0
- package/dist/src/setup/flows/model-source-change.js +1 -1
- package/dist/src/setup/index.d.ts +1 -1
- package/dist/src/setup/primitives/index.d.ts +1 -1
- package/dist/src/setup/primitives/pm/run.d.ts +0 -2
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/shared/agent-definition.d.ts +0 -1
- package/dist/src/shared/channel-definition.d.ts +4 -4
- package/dist/src/shared/sandbox-session.d.ts +4 -0
- package/dist/src/subagents/adapter.js +1 -1
- package/dist/src/subagents/parent-notification.js +1 -1
- package/dist/src/svelte/index.js +1 -1
- package/dist/src/svelte/use-eve-agent.js +1 -1
- package/dist/src/tools/definition.d.ts +21 -0
- package/dist/src/tools/provided/ask-question-workflow.js +1 -1
- package/dist/src/tools/provided/sleep-workflow.js +1 -1
- package/dist/src/tools/workflow-definition.d.ts +4 -4
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/agent-config.md +5 -4
- package/docs/channels/custom.mdx +16 -3
- package/docs/channels/eve.mdx +2 -0
- package/docs/channels/linq.mdx +1 -1
- package/docs/channels/slack.mdx +185 -18
- package/docs/channels/teams.mdx +6 -2
- package/docs/concepts/default-harness.md +6 -0
- package/docs/concepts/upgrade-to-v27-events.md +2 -2
- package/docs/evals/judge.mdx +13 -0
- package/docs/experimental/dynamic-schedules.mdx +2 -2
- package/docs/guides/client/overview.mdx +24 -2
- package/docs/guides/remote-agents.md +8 -0
- package/docs/patterns/fork-conversations-into-new-threads.md +159 -0
- package/docs/patterns/meta.json +1 -0
- package/docs/reference/cli.md +1 -1
- package/docs/sandbox/custom-providers.mdx +1 -1
- package/docs/sandbox/index.mdx +1 -1
- package/docs/schedules.mdx +1 -1
- package/docs/tools/overview.mdx +34 -0
- package/docs/tools/workflows.mdx +51 -5
- package/package.json +2 -2
- package/dist/src/compiled/_chunks/node/token-Bedbhv5z.js +0 -1
- package/dist/src/compiled/_chunks/node/token-util-DpJNusVS.js +0 -1
- package/dist/src/compiled/_chunks/node/token-util-DwHQ2a3c.js +0 -1
- package/dist/src/compiled/_chunks/node/version-C__MqbLM.js +0 -1
- package/dist/src/public/channels/slack/approval-cards.d.ts +0 -25
- package/dist/src/public/channels/slack/approval-cards.js +0 -2
- package/dist/src/runtime/agent/model-catalog.d.ts +0 -10
- package/dist/src/runtime/agent/model-catalog.js +0 -1
|
@@ -35,7 +35,7 @@ export interface AgentSessionOptions {
|
|
|
35
35
|
* it, and `stop()` and `delete()` throw. The session deletes it when the
|
|
36
36
|
* session ends. Steps can retry, so make sandbox writes safe to repeat.
|
|
37
37
|
*/
|
|
38
|
-
export type WorkflowStepToolContext = Pick<ToolContext, "abortSignal" | "callId" | "session" | "toolName" | "getSandbox" | "getToken" | "requireAuth">;
|
|
38
|
+
export type WorkflowStepToolContext = Pick<ToolContext, "abortSignal" | "callId" | "channel" | "session" | "toolName" | "getSandbox" | "getToken" | "requireAuth" | "to">;
|
|
39
39
|
/** Options for one message to a `ctx.agent` session. */
|
|
40
40
|
export interface AgentSendOptions<TOutput = unknown> {
|
|
41
41
|
/** Structured output for the turn, as on the client's `send`. */
|
|
@@ -76,10 +76,10 @@ export interface AgentSession {
|
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
78
|
* Members the context of every workflow tool entry point shares.
|
|
79
|
-
* `getSandbox`, `getToken`, and `
|
|
80
|
-
* to a `"use step"` helper; the workflow body throws when it calls them.
|
|
79
|
+
* `getSandbox`, `getToken`, `requireAuth`, and `to` work only after `ctx` is
|
|
80
|
+
* passed to a `"use step"` helper; the workflow body throws when it calls them.
|
|
81
81
|
*/
|
|
82
|
-
export type WorkflowSharedContext = Pick<ToolContext, "session" | "toolName" | "getSandbox" | "getToken" | "requireAuth"> & {
|
|
82
|
+
export type WorkflowSharedContext = Pick<ToolContext, "channel" | "session" | "toolName" | "getSandbox" | "getToken" | "requireAuth" | "to"> & {
|
|
83
83
|
/**
|
|
84
84
|
* Returns a new session with the agent of this invocation name. Nothing
|
|
85
85
|
* starts until the first `send`.
|
package/dist/src/vue/index.js
CHANGED
package/docs/agent-config.md
CHANGED
|
@@ -177,10 +177,11 @@ The function returns a Gateway model ID, an AI SDK `LanguageModel`, or a selecti
|
|
|
177
177
|
- **Serialization.** Only memo results must be JSON. The function can return a live provider
|
|
178
178
|
instance built from those results. `ctx.model` is unavailable during model selection.
|
|
179
179
|
- **Selection object.** `{ model, reasoning?, modelContextWindowTokens?, modelOptions? }`.
|
|
180
|
-
When `modelContextWindowTokens` is omitted, eve
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
When `modelContextWindowTokens` is omitted, eve reads it from the AI Gateway model catalog that
|
|
181
|
+
`eve build` or `eve dev` recorded, so selecting a model makes no network request. A model that
|
|
182
|
+
catalog doesn't list fails the turn: set `modelContextWindowTokens` for an unlisted, custom, or
|
|
183
|
+
newly published model, or rebuild. Dynamic agents cannot set sibling `modelContextWindowTokens` or
|
|
184
|
+
`modelOptions` fields; return per-model values from the function.
|
|
184
185
|
|
|
185
186
|
The `session.started` runtime identity does not include a model ID for a dynamic agent. Each public
|
|
186
187
|
`model.started` event reports the concrete model selected for that model call.
|
package/docs/channels/custom.mdx
CHANGED
|
@@ -258,7 +258,7 @@ as does the Chat SDK bridge. Keys must contain 1–256 characters and identify o
|
|
|
258
258
|
delivery. Prefix IDs with the provider or event type if they can overlap.
|
|
259
259
|
Reusing a retained key ignores the new input, even if the message changed.
|
|
260
260
|
|
|
261
|
-
Each session retains its last 1,024 distinct keys; reuse after eviction or in another session can run again. Replicas must share the same durable Workflow World. A known duplicate returns a new receipt settled `ignored`, without repeating the original reply. Use `result()` to read a send's settlement.
|
|
261
|
+
Each session retains its last 1,024 distinct keys; reuse after eviction, after the session ends, or in another session can run again. Replicas must share the same durable Workflow World. A known duplicate returns a new receipt settled `ignored`, without repeating the original reply. Use `result()` to read a send's settlement.
|
|
262
262
|
|
|
263
263
|
Await `send()` before acknowledging a webhook when the provider allows it.
|
|
264
264
|
Acknowledging sooner can lose work. This does not make tool calls, outbound
|
|
@@ -421,6 +421,7 @@ export default defineChannel({
|
|
|
421
421
|
principalId: event.source,
|
|
422
422
|
attributes: {},
|
|
423
423
|
},
|
|
424
|
+
idempotencyKey: `intake:${event.id}`,
|
|
424
425
|
}),
|
|
425
426
|
);
|
|
426
427
|
return new Response("ok");
|
|
@@ -433,13 +434,25 @@ Before sending:
|
|
|
433
434
|
|
|
434
435
|
- Verify the provider's signature and validate the payload, including size limits. Derive `event.source` from the verified sender, not an untrusted payload field.
|
|
435
436
|
- Filter out events that should not reach the agent.
|
|
436
|
-
-
|
|
437
|
+
- Pass the provider's stable event ID as `idempotencyKey`, so a redelivered webhook doesn't deliver the message twice. See [Retried deliveries](#retried-deliveries) for the limits.
|
|
437
438
|
|
|
438
439
|
The supplied `auth` identifies who started the session, available to tools and event handlers as `session.auth.initiator`.
|
|
439
440
|
|
|
440
441
|
The destination's `receive` hook defines the target fields and how they map to a session. See [Slack proactive sessions](./slack#proactive-sessions) for its target options. To let people continue the conversation, configure the destination's inbound handlers to accept follow-ups.
|
|
441
442
|
|
|
442
|
-
`
|
|
443
|
+
`send` takes the same options as `from(address).send()` and `Session.send()`: `auth`, `context`, `history`, `idempotencyKey`, `outputSchema`, `title`, and `turnPolicy`. The destination's `receive` hook gets them with the message and target, and every built-in channel passes them to the session. Keys are deduplicated per destination session, which every sender to that destination shares, and a Slack target with only a `channelId` uses the key to choose the session, so a retry reaches the same session. Make the key unique across all senders by prefixing it with the source, for example `pagerduty:${event.id}`. Two senders that pick the same key reach the same session, and the second delivery is `ignored`. The key still only dedupes within that session's retained keys: a retry after the key is evicted runs another turn, and a retry after the session ends starts a new one, which on Slack is a new thread. Anything a `receive` hook does before sending also repeats on retry, such as posting an `initialMessage` (Slack, Teams, Discord, GitHub, and Telegram) or creating a Linear Agent Session for an `issueId` or `commentId` target.
|
|
444
|
+
|
|
445
|
+
### Write a receive hook
|
|
446
|
+
|
|
447
|
+
A custom channel's `receive` hook maps the target to an address. Pass the remaining input to `from(address).send()`, so the session gets every option the sender set:
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
async receive({ message, target, ...send }, { from }) {
|
|
451
|
+
return await from(`inbox:${target.inboxId}`).send(message, { ...send, state: { inboxId: target.inboxId } });
|
|
452
|
+
},
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Route handlers, [handler-form schedules](../schedules#handler-form-run), and [tools](../tools#start-channel-sessions-ctxto) provide `to(...)`; other platform callbacks don't.
|
|
443
456
|
|
|
444
457
|
For notifications that don't need an agent turn, use the platform's API instead. See [Durable cross-channel notifications](../patterns/durable-cross-channel-notifications) when those posts also need retries.
|
|
445
458
|
|
package/docs/channels/eve.mdx
CHANGED
|
@@ -212,6 +212,8 @@ curl -X POST https://<deployment>/eve/v1/session/wrun_A \
|
|
|
212
212
|
-d '{"message":[{"type":"text","text":"Summarize this report."},{"type":"file","mediaType":"application/pdf","filename":"report.pdf","data":"data:application/pdf;base64,JVBERi0xLjQK..."}]}'
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
+
Framework-generated attachment markers (`eve-url:`, `eve-sandbox:`, and `chat-sdk-file:`) are not accepted in HTTP request bodies; they return `400 Bad Request`.
|
|
216
|
+
|
|
215
217
|
`uploadPolicy` sets the size cap and allowed media types, 25 MB and every type by default. A request whose inline file breaks the policy returns `413` for size or `415` for type. Inline bytes also count toward the request body limit of the platform that serves the channel. On Vercel Functions that limit is 4.5 MB, so a base64 file stops at about 3.3 MB.
|
|
216
218
|
|
|
217
219
|
For larger files, upload them from the browser to your own storage and send the URL in the file part. Add `fetchFile` to load the URLs you own inside the workflow step, so the bytes never pass through the request or the workflow start payload:
|
package/docs/channels/linq.mdx
CHANGED
|
@@ -20,7 +20,7 @@ The default webhook route is `/eve/v1/linq`. The channel verifies forwarded webh
|
|
|
20
20
|
|
|
21
21
|
Set `turnPolicy: "queue"` when every response should finish before Linq starts the next message.
|
|
22
22
|
|
|
23
|
-
Images and other files people send reach the agent as file parts. eve downloads each one from Linq's CDN and passes the bytes
|
|
23
|
+
Images and other files people send reach the agent as file parts. eve downloads each one from Linq's CDN over HTTPS and passes the bytes. Downloads and redirects must resolve to public network addresses and have a 30-second timeout. A failed download, or a file over 25 MB, reaches the agent as a short note.
|
|
24
24
|
|
|
25
25
|
Customize inbound dispatch with `onMessage`. Return `null` to ignore a message, or return `title` alongside `auth` to set the title when the dispatch starts a run:
|
|
26
26
|
|
package/docs/channels/slack.mdx
CHANGED
|
@@ -106,12 +106,13 @@ Message hooks return `{ auth }` to dispatch, `null` to drop, `{ auth, context }`
|
|
|
106
106
|
- `onAppMention(ctx, message)` handles only `app_mention` and takes precedence over `onMessage`. Its default derives workspace-scoped auth.
|
|
107
107
|
- `onDirectMessage(ctx, message)` handles only DMs and takes precedence over `onMessage`. Bot-authored messages and edits are filtered first; Slack requires `message.im` and `im:history`.
|
|
108
108
|
|
|
109
|
-
| Incoming event
|
|
110
|
-
|
|
|
111
|
-
| App mention
|
|
112
|
-
| Direct message
|
|
113
|
-
|
|
|
114
|
-
| Other
|
|
109
|
+
| Incoming event | Handler order |
|
|
110
|
+
| --------------------------------- | ------------------------------------------------------------------- |
|
|
111
|
+
| App mention | `onAppMention` → `onMessage` → `onEvent` → built-in mention default |
|
|
112
|
+
| Direct message | `onDirectMessage` → `onMessage` → `onEvent` → built-in DM default |
|
|
113
|
+
| Un-mentioned code channel message | `onMessage` → `onAppMention` → `onEvent` → built-in default |
|
|
114
|
+
| Other Slack message | `onMessage` → `onEvent` → ignore |
|
|
115
|
+
| Other Events API event | `onEvent` → ignore |
|
|
115
116
|
|
|
116
117
|
Only the first available handler runs. A message hook returning `null` drops the message; it does not continue down the table.
|
|
117
118
|
|
|
@@ -304,9 +305,127 @@ export default slackChannel({
|
|
|
304
305
|
|
|
305
306
|
Reset returns a `Delivery` for the retiring session; `await delivery.result()` waits for retirement and address release. An already unowned thread throws `SessionNotActiveError`. Returning `null` after reset consumes the triggering message as a command; return `{ auth }` instead to deliver that message as the first turn of the fresh session. Custom `onInteraction` handlers receive the same flat operations, which is useful for a New conversation button.
|
|
306
307
|
|
|
308
|
+
#### Serve Slack code channels
|
|
309
|
+
|
|
310
|
+
A [Slack code channel](https://docs.slack.dev/ai/slack-code) is a channel dedicated to one agent task. eve serves the whole code channel as one session: every message in it, top-level or threaded, reaches the same session. Replies post top-level, or in the thread when someone writes in one. The session status stays on the channel either way. While a turn runs, eve sets the Slack session status to `processing` in place of the thread status line, `suspended` while the turn waits on a person, and `active` when the turn ends or is cancelled. When someone stops the session in Slack, eve cancels the active turn, or sets the status back to `active` when no turn is running.
|
|
311
|
+
|
|
312
|
+
Set `codeChannels` to answer messages in a code channel without a mention:
|
|
313
|
+
|
|
314
|
+
```ts title="agent/channels/slack.ts"
|
|
315
|
+
import { connectSlackCredentials } from "@vercel/connect/eve";
|
|
316
|
+
import { slackChannel } from "eve/channels/slack";
|
|
317
|
+
|
|
318
|
+
export default slackChannel({
|
|
319
|
+
credentials: connectSlackCredentials("slack/my-agent"),
|
|
320
|
+
codeChannels: true,
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Every member of a code channel can start turns, with auth derived from each message's author, the same as in an ordinary thread. Messages that don't mention the bot go to `onMessage` when you define it, else to `onAppMention`, else the default handler, so a mention handler that filters who can reach the agent also filters code channel messages. `ctx.isBotMentioned()` still returns `true` only for messages that mention the bot; use `ctx.slack.codeChannel` to tell code channel messages apart. Without `codeChannels`, eve never looks a channel up and treats a code channel like any other channel.
|
|
325
|
+
|
|
326
|
+
A code channel session has no single thread, so `ctx.slack.threadTs` is empty, `SlackChannelState.threadTs` is `null`, and its Slack auth has no `thread_ts`. `ctx.slack.codeChannel`, `codeChannel: true` in state, and `codeChannel: true` in the channel metadata that tools and dynamic resolvers read as `ctx.channel.metadata` say it's a code channel. To address its session yourself, target `{ channelId, codeChannel: true }` in `onEvent` operations, or build its continuation token with `slackCodeChannelContinuationToken(channelId)`.
|
|
327
|
+
|
|
328
|
+
A code channel someone opens from Slack's UI starts without the origin conversation. When Slack opens a code channel from a message, it posts a quote of that message into the channel. eve skips the quote, so it never starts a turn, and doesn't add it to the session either. To start a code channel with the conversation so far, open it from eve with [`openCodeChannel()`](#open-a-code-channel-from-a-thread), which copies the conversation, or with [`ctx.openCodeChannel()`](#open-a-code-channel-from-a-message-hook) and pass `history`.
|
|
329
|
+
|
|
330
|
+
eve detects a code channel with Slack's `conversations.info` API, so the app needs `channels:read` (and `groups:read` for private code channels). If the lookup fails, eve logs an error, serves the channel per thread, and retries the lookup a minute later. With `codeChannels` set, the generated app manifest enables the code channels feature, adds the `code_channels:manage`, `channels:history`, `channels:read`, `groups:history`, and `groups:read` bot scopes (plus `im:read` and `mpim:read`, so eve can read a DM or group DM origin), and subscribes to the `message.session` and `agent_session_stopped` events. For Vercel Connect, add the same scopes and events under **Advanced** when you create the connector. To tell whether you're in a code channel, use `ctx.slack.codeChannel` in message hooks and `channel.state.codeChannel` in [renderers](#customize-rendering).
|
|
331
|
+
|
|
332
|
+
#### Open a code channel from a thread
|
|
333
|
+
|
|
334
|
+
To let the agent move a request that needs more than one message into a new code channel, give it a tool that calls `openCodeChannel()` with your Slack channel:
|
|
335
|
+
|
|
336
|
+
```ts title="agent/tools/open_code_channel.ts"
|
|
337
|
+
import { openCodeChannel } from "eve/channels/slack";
|
|
338
|
+
import { defineTool } from "eve/tools";
|
|
339
|
+
import { z } from "zod";
|
|
340
|
+
import slack from "../channels/slack";
|
|
341
|
+
|
|
342
|
+
export default defineTool({
|
|
343
|
+
description: [
|
|
344
|
+
"Move this Slack conversation into a new code channel for multi-step work: code changes, builds, tests, pull requests, investigations, or long output.",
|
|
345
|
+
"Answer in place when one message answers the request. Call it once per request; Slack links the new channel here, so afterward reply in one short sentence at most.",
|
|
346
|
+
"If it returns unavailable or Slack refuses, continue here and say why; don't retry.",
|
|
347
|
+
].join(" "),
|
|
348
|
+
inputSchema: z.object({
|
|
349
|
+
message: z
|
|
350
|
+
.string()
|
|
351
|
+
.describe("The task and where to start. The channel sees the conversation so far."),
|
|
352
|
+
name: z.string().max(80).describe("A short title, such as `Fix the flaky login test`."),
|
|
353
|
+
}),
|
|
354
|
+
async execute({ message, name }, ctx) {
|
|
355
|
+
return await openCodeChannel(ctx, slack, message, { name });
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`openCodeChannel(ctx, slack, message, { name })` opens the code channel from the message that triggered the turn, and starts the code channel's session with the conversation the model saw, then `message`. Without `name`, Slack names the channel from the message. Slack links the new channel in the origin thread, invites the person who wrote the origin message, and gives the channel the origin's privacy. eve reads the origin before creating anything and asks Slack for a private channel when the origin is private. If Slack still returns a public channel, eve archives it and doesn't copy the conversation. The origin thread keeps its own session for quick follow-ups.
|
|
361
|
+
|
|
362
|
+
It needs `codeChannels: true` on the Slack channel you pass. Outside Slack or inside a code channel, it returns `{ status: "unavailable", reason }` and the model continues in the thread. When Slack refuses, for example for a Slack Connect conversation, when code channels are disabled, or when a scope is missing, it throws Slack's error, which the model relays. eve retries transient Slack errors such as rate limits first. Like every [`ctx.to()`](/docs/tools#start-channel-sessions-ctxto), it works only in a tool on the root agent.
|
|
363
|
+
|
|
364
|
+
One person gets one code channel per message. A retried tool call reaches the same channel and session once. A second call for the same message reaches the same channel and delivers the conversation again, so a session someone started by writing in the new channel first still gets the thread's history.
|
|
365
|
+
|
|
366
|
+
#### Build your own code channel tool
|
|
367
|
+
|
|
368
|
+
To change more than the description and inputs, send to the Slack channel with a `codeChannel` target yourself. `ctx.channel.metadata` names the Slack conversation the tool was called from: `channelId` and `threadTs`. In a code channel, `threadTs` is `null`. The metadata doesn't depend on the session's auth, so the tool keeps working when a message hook returns its own `auth`:
|
|
369
|
+
|
|
370
|
+
```ts title="agent/tools/move_to_code_channel.ts"
|
|
371
|
+
import { defineTool } from "eve/tools";
|
|
372
|
+
import { z } from "zod";
|
|
373
|
+
import slack from "../channels/slack";
|
|
374
|
+
|
|
375
|
+
export default defineTool({
|
|
376
|
+
description: "Move this request into a new Slack code channel.",
|
|
377
|
+
inputSchema: z.object({ task: z.string() }),
|
|
378
|
+
async execute({ task }, ctx) {
|
|
379
|
+
const channelId = ctx.channel.metadata?.channelId;
|
|
380
|
+
const threadTs = ctx.channel.metadata?.threadTs;
|
|
381
|
+
if (typeof channelId !== "string" || typeof threadTs !== "string") {
|
|
382
|
+
return { status: "unavailable" };
|
|
383
|
+
}
|
|
384
|
+
const session = await ctx
|
|
385
|
+
.to(slack, {
|
|
386
|
+
channelId,
|
|
387
|
+
// Opens the channel from the thread's root message.
|
|
388
|
+
codeChannel: { name: task.slice(0, 80), originThreadTs: threadTs, originTs: threadTs },
|
|
389
|
+
})
|
|
390
|
+
.send(task, {
|
|
391
|
+
auth: ctx.session.auth.current,
|
|
392
|
+
idempotencyKey: `${ctx.session.id}:${ctx.callId}`,
|
|
393
|
+
});
|
|
394
|
+
return { sessionId: session.id, status: "opened" };
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
`codeChannel` takes the origin message's `originTs`, its thread's `originThreadTs`, and optionally the channel's `name`; without one, Slack names the channel from the message. The new channel gets the origin's privacy, and its session's audience follows it. A tool, route, schedule, or `onEvent` handler can open one from any message the app can see, so pass the origin of the conversation the tool was called from, as `openCodeChannel()` does. Pass `history` to start the code channel's session with earlier messages; `openCodeChannel()` passes the text of `ctx.messages`.
|
|
400
|
+
|
|
401
|
+
#### Open a code channel from a message hook
|
|
402
|
+
|
|
403
|
+
To open a code channel without the model deciding, call `ctx.openCodeChannel({ name })` in `onAppMention`, `onDirectMessage`, or `onMessage`. It opens the channel from the inbound message and returns the same session operations as `ctx`, bound to the code channel's session. Send the first turn there and return `null`, so the thread gets no turn. For example, an agent that works only in code channels:
|
|
404
|
+
|
|
405
|
+
```ts title="agent/channels/slack.ts"
|
|
406
|
+
import { connectSlackCredentials } from "@vercel/connect/eve";
|
|
407
|
+
import { defaultSlackAuth, slackChannel } from "eve/channels/slack";
|
|
408
|
+
|
|
409
|
+
export default slackChannel({
|
|
410
|
+
credentials: connectSlackCredentials("slack/my-agent"),
|
|
411
|
+
codeChannels: true,
|
|
412
|
+
async onAppMention(ctx, message) {
|
|
413
|
+
// Messages in the code channel reach its session as usual.
|
|
414
|
+
if (ctx.slack.codeChannel) return { auth: defaultSlackAuth(message, ctx) };
|
|
415
|
+
const codeChannel = await ctx.openCodeChannel({ name: message.text.slice(0, 80) });
|
|
416
|
+
await codeChannel.send(message.text);
|
|
417
|
+
return null;
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
The returned `send` defaults to the message author's auth and moves Slack auth you pass to the code channel. Omit `name` to let Slack name the channel from the message.
|
|
423
|
+
|
|
424
|
+
Unlike the tool, `ctx.openCodeChannel()` copies no conversation: the code channel's session starts with only what you send. Pass `history` to `send` if it should see earlier messages, such as the thread's replies. `ctx.openCodeChannel()` opens the channel the same way as a `codeChannel` target: one channel per author and message, the origin's privacy, and the session's audience from it. It throws without `codeChannels: true`, for a message already in a code channel, and when Slack refuses to create the channel, like the other `ctx` operations.
|
|
425
|
+
|
|
307
426
|
### Other Events API callbacks
|
|
308
427
|
|
|
309
|
-
Use `onEvent` for subscribed events such as `reaction_added`, `team_join`, or `channel_created`. It receives the raw, open-ended Slack event and owns control flow. Call `ctx.send(message, { target, auth,
|
|
428
|
+
Use `onEvent` for subscribed events such as `reaction_added`, `team_join`, or `channel_created`. It receives the raw, open-ended Slack event and owns control flow. Call `ctx.send(message, { target, auth, ...options })` zero, one, or many times to start turns. It takes the same options as [`to().send()`](./custom#cross-channel-hand-off), such as `title`, which sets the run title without changing the message sent to the model, `history`, which adds prior conversation before the message (see [Start from prior history](./eve#start-from-prior-history)), and `idempotencyKey`. Each call returns a delivery receipt; use its `sessionId` to attach a session handle.
|
|
310
429
|
|
|
311
430
|
```ts title="agent/channels/slack.ts"
|
|
312
431
|
import { connectSlackCredentials } from "@vercel/connect/eve";
|
|
@@ -350,6 +469,8 @@ await ctx.reset({ reason: "Start over", target });
|
|
|
350
469
|
const session = await ctx.resolveSession({ target });
|
|
351
470
|
```
|
|
352
471
|
|
|
472
|
+
For a [code channel](#serve-slack-code-channels)'s session, which spans all of its threads, target `{ channelId, codeChannel: true }` in every operation except `send`.
|
|
473
|
+
|
|
353
474
|
`onEvent` is the raw fallback after the message hooks. If an event is not claimed by `onAppMention`, `onDirectMessage`, or `onMessage`, an authored `onEvent` receives it; otherwise eve applies the built-in mention/DM default or ignores it.
|
|
354
475
|
|
|
355
476
|
`onEvent` covers JSON `event_callback` deliveries only. URL verification, slash commands, and interactive payloads do not reach it. Slash commands reach `onSlashCommand`; add every desired event and required OAuth scope to the Slack app's Event Subscriptions configuration so other callbacks reach their handlers.
|
|
@@ -462,11 +583,12 @@ eve writes the card from the session's own events as they happen, and only when
|
|
|
462
583
|
|
|
463
584
|
### Customize rendering
|
|
464
585
|
|
|
465
|
-
Without `renderers`, eve renders everything above with its default renderer. Pass `renderers` to change what the agent posts without replacing that default. Each renderer wraps the ones after it, ending with eve's default renderer, and has
|
|
586
|
+
Without `renderers`, eve renders everything above with its default renderer. Pass `renderers` to change what the agent posts without replacing that default. Each renderer wraps the ones after it, ending with eve's default renderer, and has four optional parts:
|
|
466
587
|
|
|
467
588
|
- `received(message, channel, next)` acknowledges a mention or DM the moment it arrives, and another channel message once `onMessage` dispatches it. eve's default sets `Thinking...`.
|
|
468
589
|
- `events` handles session facts such as `content.completed` or `interaction.opened`. Each handler receives `(event, ctx, next)`: the whole fact with its `type`, `data`, and `scope`, and a context whose `ctx.channel` holds Slack's platform handles, `ctx.channel.thread` and `ctx.channel.slack`, along with `ctx.position` and `ctx.view`.
|
|
469
590
|
- `taskCard(view, next)` returns the task card's blocks and notification text, or `null` for no card.
|
|
591
|
+
- `interactionCard(card, next)` returns the messages for one approval, question, or budget prompt.
|
|
470
592
|
|
|
471
593
|
Call `next()` to run the rest of the chain, ending with eve's default. Call it after your own work, before it, or with a changed fact, as in `next({ ...event, data: { ...event.data, value } })`; the rest of the chain runs at most once. Skip `next` to replace the default for that event. This renderer adds feedback buttons below each final reply and a context row to the finished task card:
|
|
472
594
|
|
|
@@ -562,6 +684,48 @@ A turn keeps one card unless it starts a task after every task on its card ended
|
|
|
562
684
|
|
|
563
685
|
`taskCard` is synchronous and pure: eve posts the card and updates it as the tables change. eve's default returns `null` for a card without tasks. Its block keeps the same `block_id` for the whole turn; keep that `block_id` on a block you build in its place. To change a row's title in every channel, give the tool a [label](/docs/tools#label-tool-activity) instead.
|
|
564
686
|
|
|
687
|
+
`interactionCard` restyles the card for one approval, question, or budget prompt. It receives:
|
|
688
|
+
|
|
689
|
+
- `interaction`: the request as the session holds it, with its `status`, `outcome`, `call` (including the tool `input`), and `answer`. Once the request settles, `answer` is the answer that decided it. While it's open, `answer` is the latest admitted answer, if any; an answer still being checked, for example by an approval response policy, or one that was refused doesn't count.
|
|
690
|
+
- `destination`: `"thread"` or `"direct-message"`, as [`approvalChannel`](#human-in-the-loop-hitl) decided. In a code channel, `"thread"` means the code channel itself.
|
|
691
|
+
- `audience`: who reads the message. A direct message is always `private`; a thread request has the thread's audience, as on the task card.
|
|
692
|
+
- `answeredBy`: the Slack user ID whose answer stands or decided the request, when known.
|
|
693
|
+
- `view`: the session snapshot, for the selectors from `eve/events`.
|
|
694
|
+
|
|
695
|
+
It returns `controls`, the message holding eve's buttons or select, and an optional `details` message, which eve posts before the controls. Each is a `SlackCardMessage` with `blocks` and `text`, the same shape `taskCard` returns. Requests asked together share one details message, which eve deletes once its last open request settles. Slack sends the whole controls message back with every click, so put long content such as tool input in `details`. eve's default posts details only for an approval with tool input, but a renderer can give any request details, including a question. This renderer shows a `plan-create` tool's steps as the approval's details:
|
|
696
|
+
|
|
697
|
+
```ts title="agent/channels/slack.ts"
|
|
698
|
+
import { defineSlackRenderer, slackChannel } from "eve/channels/slack";
|
|
699
|
+
|
|
700
|
+
const plan = defineSlackRenderer({
|
|
701
|
+
interactionCard(card, next) {
|
|
702
|
+
const parts = next(card);
|
|
703
|
+
const { call, status } = card.interaction;
|
|
704
|
+
if (call?.name !== "plan-create" || status !== "open") return parts;
|
|
705
|
+
const { steps = [] } = (call.input ?? {}) as { steps?: string[] };
|
|
706
|
+
if (steps.length === 0) return parts;
|
|
707
|
+
const text = steps.map((step, index) => `${index + 1}. ${step}`).join("\n");
|
|
708
|
+
return {
|
|
709
|
+
...parts,
|
|
710
|
+
details: { blocks: [{ type: "section", text: { type: "mrkdwn", text } }], text },
|
|
711
|
+
};
|
|
712
|
+
},
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
export default slackChannel({ renderers: [plan] });
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
eve calls `interactionCard` when a request opens, when an answer is admitted or refused, and when the request settles, and updates the Slack message only when its blocks changed. Requests in one message that settle together update it once. Pass `next` a changed card, such as one with a shorter `interaction.request.prompt`, to have eve's default render it. eve's default renders an open request as buttons or a select, and an answered or settled one as a single block that names the answer and, when known, who gave it. `interactionCard` doesn't run for sign-ins.
|
|
719
|
+
|
|
720
|
+
eve still routes, batches, and posts the messages, and owns the controls' action IDs, so clicks keep reaching the session. You can relabel eve's controls, reorder them, or nest them in your own blocks, within these rules:
|
|
721
|
+
|
|
722
|
+
- While the request is open and unanswered, `controls` holds exactly the controls `next` renders, each once, with the same `value` or option values. Every eve control's `action_id` starts with `eve_input:` or `eve_input_freeform:`.
|
|
723
|
+
- Once the request is answered or settled, `controls` holds no eve controls.
|
|
724
|
+
- `details` never holds eve controls, and each message holds at most 50 blocks.
|
|
725
|
+
- A message holds at most 50 blocks, including the requests eve grouped with yours. An update that would pass that uses eve's default for the changed request instead, and if that doesn't fit either, only the block holding eve's controls, so an open request always shows them.
|
|
726
|
+
|
|
727
|
+
If `interactionCard` throws or breaks these rules, eve logs it and renders that request with its default, since a request nobody can answer would hold the turn. If Slack refuses a rendered message's content, such as with `invalid_blocks` or `msg_too_long`, eve sends that message once more with its defaults. eve doesn't resend after any other failure, such as a timeout or `rate_limited`, because the first message may have landed.
|
|
728
|
+
|
|
565
729
|
Renderers run in the order you list them, and the first one is outermost. `onInteraction` receives clicks on buttons a renderer posts, such as the feedback buttons above.
|
|
566
730
|
|
|
567
731
|
### Show your own tool on the task card
|
|
@@ -652,9 +816,9 @@ export default slackChannel({
|
|
|
652
816
|
|
|
653
817
|
The callback receives the `Interaction` and current `SessionContext`, so it can switch on `request.kind`, `call`, or prompt presentation properties. Omit it to keep every request in the thread, or return `"direct-message"` unconditionally to make every request private. If eve cannot resolve the triggering Slack user for a direct message, it logs the undelivered request and posts no fallback, so private delivery fails closed. The existing `onAnswers` or tool approval response policy still decides whether the person who clicks may respond.
|
|
654
818
|
|
|
655
|
-
|
|
819
|
+
Each card updates from the session as its request changes. Once an answer is admitted, the card shows the answer and who gave it in place of the buttons, even while other approvals from the same step still wait for theirs. Clicks can't change an admitted answer, since the buttons are gone; if a policy or check refuses the answer, the buttons come back. Once the request settles, the separate tool-input post is deleted after the last open request it covers settles, and a direct-message request's `Waiting on approval` note in the thread changes to the outcome the card shows. This applies to approvals, questions, and budget prompts, however they end: a click, a typed reply in the thread, or a withdrawal, in which case the card reads `No longer needed`. When several requests share one message, answering one updates only its part of the message. To change how these cards look, use [`interactionCard`](#customize-rendering).
|
|
656
820
|
|
|
657
|
-
A click on any request whose session is no longer active, for example because its run was cancelled outside eve, can never be answered. eve removes the card's buttons, marks it `No longer active`, and tells the person who clicked, in an ephemeral message, to send a new message.
|
|
821
|
+
A click on any request whose session is no longer active, for example because its run was cancelled outside eve, can never be answered. eve removes the card's buttons, marks it `No longer active`, and tells the person who clicked, in an ephemeral message, to send a new message. eve has no session to render from at that point, so it edits the message Slack sent with the click. A freeform answer submitted from the **Type your answer** modal at that point replaces the whole message, including any requests grouped with it.
|
|
658
822
|
|
|
659
823
|
A renderer's `interaction.opened` handler can extend delivery and then hand the request on to eve with `next()`. For example, an application can add non-sensitive context to the Slack thread before eve renders the actionable controls. The configured `approvalChannel` still sends sensitive reviews to a DM and keeps ordinary requests in the thread:
|
|
660
824
|
|
|
@@ -710,7 +874,7 @@ answerFromSlackAction({ actionId: button.action_id, value: button.value });
|
|
|
710
874
|
// { response: { optionId: "approve", interactionId: request.interactionId } }
|
|
711
875
|
```
|
|
712
876
|
|
|
713
|
-
Use them to test a custom renderer against eve's wire format, or to post eve's controls from your own renderer. A click on these controls in the session's Slack thread resumes the session through `onAnswers`, the same as eve's own controls. eve does not track controls you post yourself, so update or remove those messages from your renderer's `interaction.settled` handler. To pass `response` to `respond()` yourself, validate it first with `parseAnswers` from `eve/client`.
|
|
877
|
+
Use them to test a custom renderer against eve's wire format, or to post eve's controls from your own renderer. To restyle the cards eve posts, use `interactionCard` instead, which keeps eve's batching and card updates. A click on these controls in the session's Slack thread resumes the session through `onAnswers`, the same as eve's own controls. eve does not track controls you post yourself, so update or remove those messages from your renderer's `interaction.settled` handler. To pass `response` to `respond()` yourself, validate it first with `parseAnswers` from `eve/client`.
|
|
714
878
|
|
|
715
879
|
Authorization prompts split public status from private credentials. A sign-in challenge (OAuth URL, device code) is a credential. Anyone who completes it binds their identity to the session's connection. A sign-in is an `interaction.opened` with `request.kind: "sign-in"`. The default handler posts a public, link-free status in the thread, delivers the actual challenge ephemerally to the Slack user behind the interaction's `audience.principalIds`, device code included, and then updates that public status when its `interaction.settled` fires. For a sign-in, a renderer's `interaction.opened` handler receives a private-delivery context as `ctx.channel`, with `postEphemeral`, `postDirectMessage` (needs the `im:write` scope), and `state`. There is, intentionally, no public `post` and no raw API access. Its `next()` reaches eve's default, which posts the public status; skip `next` to deliver the challenge yourself, as this handler does by direct message. `state.slackUsersByPrincipal` maps each principal eve has seen in the thread, through a message or an input response, to its Slack user ID. If the principal has no entry, the challenge is not sent privately.
|
|
716
880
|
|
|
@@ -738,16 +902,19 @@ The private authorization button uses the label **Sign in**, so long connection
|
|
|
738
902
|
|
|
739
903
|
### Proactive sessions
|
|
740
904
|
|
|
741
|
-
Start a Slack conversation from a [webhook on another channel](./custom#cross-channel-hand-off)
|
|
905
|
+
Start a Slack conversation from a [webhook on another channel](./custom#cross-channel-hand-off), a [schedule](../schedules#handler-form-run), or a [tool](../tools#start-channel-sessions-ctxto), without waiting for someone to message the app. To copy an existing conversation into a new thread or someone's direct messages, see [Fork conversations into a new thread](../patterns/fork-conversations-into-new-threads).
|
|
742
906
|
|
|
743
907
|
Pass `{ channelId }` as the target to start a new thread. The agent's first post becomes the thread's root message. You can also set these target fields:
|
|
744
908
|
|
|
745
|
-
| Field | Use
|
|
746
|
-
| -------------------- |
|
|
747
|
-
| `threadTs` | Continue an existing thread instead of creating one.
|
|
748
|
-
| `initialMessage` | Post a card as the thread root before the agent runs. Cannot be combined with `threadTs`.
|
|
749
|
-
| `installationTeamId` | Select the app installation workspace when using a function-form bot token.
|
|
750
|
-
| `
|
|
909
|
+
| Field | Use |
|
|
910
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
911
|
+
| `threadTs` | Continue an existing thread instead of creating one. |
|
|
912
|
+
| `initialMessage` | Post a card as the thread root before the agent runs. Cannot be combined with `threadTs`. |
|
|
913
|
+
| `installationTeamId` | Select the app installation workspace when using a function-form bot token. |
|
|
914
|
+
| `codeChannel` | Open a [code channel](#build-your-own-code-channel-tool) from a message in `channelId` and start the session there. Requires `codeChannels: true`. |
|
|
915
|
+
| `audience` | Set the destination's visibility when you know it. Defaults to `unknown`. |
|
|
916
|
+
|
|
917
|
+
A send with an `idempotencyKey` and no `threadTs` or `initialMessage` derives the new thread's session from the key, so a retry with the same key reaches the same session instead of starting a second thread. Because the key chooses the session, two different sends that reuse a key land in the same thread and the second is ignored. Use a key that identifies the delivery across every sender to this channel, not only across retries of one sender.
|
|
751
918
|
|
|
752
919
|
The default mention handler lets teammates continue the session by mentioning the app in the thread. To accept replies without a mention, [configure an `onMessage` hook and the required Slack subscriptions and scopes](#continue-conversations-without-repeated-mentions).
|
|
753
920
|
|
package/docs/channels/teams.mdx
CHANGED
|
@@ -4,7 +4,7 @@ description: "Reach your agent from Microsoft Teams via the Bot Framework Activi
|
|
|
4
4
|
type: integration
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
The Teams channel runs your agent inside Microsoft Teams as a bot. It takes Bot Framework Activity POSTs, checks the Bot Connector bearer JWT on each one, and routes message activities to your agent. Human-in-the-loop (HITL) prompts come back as Adaptive Cards, and replies go out over the Bot Framework Connector REST API. See [Channels](./overview) for the contract this builds on.
|
|
7
|
+
The Teams channel runs your agent inside Microsoft Teams as a bot. It takes Bot Framework Activity POSTs, checks the Bot Connector bearer JWT and its `serviceurl` binding on each one, and routes message activities to your agent. Human-in-the-loop (HITL) prompts come back as Adaptive Cards, and replies go out over the Bot Framework Connector REST API. See [Channels](./overview) for the contract this builds on.
|
|
8
8
|
|
|
9
9
|
## Add the channel
|
|
10
10
|
|
|
@@ -77,7 +77,11 @@ export default teamsChannel({
|
|
|
77
77
|
|
|
78
78
|
### Delivery
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Native Bot Connector verification requires the activity's `serviceUrl` to match the verified token's `serviceurl` claim. A custom `webhookVerifier` replaces this verification and must authenticate the forwarded activity and its service URL. Proactive conversation references are trusted application input: load them from your own verified records, not a caller-supplied URL. Outbound Connector requests require HTTPS without URL credentials, query strings, or fragments, and do not follow redirects.
|
|
81
|
+
|
|
82
|
+
HTTP service URLs used by Bot Framework Emulator are not supported, even with a custom verifier. Use an HTTPS endpoint or proxy for local Connector testing.
|
|
83
|
+
|
|
84
|
+
Replies post as Markdown (`textFormat: "markdown"`), with oversized text split across messages and a typing indicator sent on turn start and action requests. `thread.mentionUser()` escapes the display name (or fallback user ID) as literal text inside the mention; the mention entity retains the original identity fields.
|
|
81
85
|
|
|
82
86
|
### Human-in-the-loop (HITL)
|
|
83
87
|
|
|
@@ -24,6 +24,12 @@ input token count plus an estimate of new messages. A smaller character estimate
|
|
|
24
24
|
alone cannot satisfy compaction triggered by a higher provider count. If trimming
|
|
25
25
|
cannot free enough space, eve summarizes the older history.
|
|
26
26
|
|
|
27
|
+
The summary call retries transient provider errors like any model call. If it
|
|
28
|
+
still fails for any reason other than staying over the threshold, eve reports it
|
|
29
|
+
as a failed model call: the turn fails with `MODEL_CALL_FAILED` and the history
|
|
30
|
+
stays uncompacted. A transient error such as a 429 leaves the session waiting
|
|
31
|
+
for the next message.
|
|
32
|
+
|
|
27
33
|
First-class [memory](../memory) stays outside history, so the summarizer never
|
|
28
34
|
sees it. eve asks providers to capture before compaction rewrites history, and
|
|
29
35
|
what earlier turns recalled follows the checkpoint.
|
|
@@ -8,12 +8,12 @@ This guide covers breaking changes to agent definitions, approvals, channels, cl
|
|
|
8
8
|
## Before you upgrade
|
|
9
9
|
|
|
10
10
|
- Rebuild and republish installed extensions. Earlier epoch-based distributions cannot be loaded by this release; see [Extensions](../extensions).
|
|
11
|
-
- Keep the deployments that own existing sessions available.
|
|
11
|
+
- Keep the deployments that own existing sessions available. A session whose deployment runs eve 0.69 through 0.83 moves to this release the next time it receives a message while idle, or earlier when a channel prepares it for the next message. A session that has a background task, including an idle resumable one, stays on the deployment that owns it until the task finishes, and a session whose deployment runs an earlier release stays there. When a session moves, eve compacts its earlier conversation, so the model continues from a summary while the stored transcript stays intact. The session also starts over on remembered tool approvals and usage limits. On worlds without deployment routing, follow the [stranded-session recovery guidance](./execution-model-and-durability#stranded-sessions).
|
|
12
12
|
- Update event consumers, frontend persistence, and custom channel handlers using the mappings below. See [Sessions, runs & streaming](./sessions-runs-and-streaming) for the current stream contract.
|
|
13
13
|
|
|
14
14
|
## Restore saved history
|
|
15
15
|
|
|
16
|
-
New sessions use v27. Sessions started on an earlier deployment keep
|
|
16
|
+
New sessions use v27. Sessions started on an earlier deployment keep the lines they already stored in the earlier format. When such a session moves to this release, it appends v27 facts to the same stream, and a current deployment serves the whole stream as v27 events.
|
|
17
17
|
|
|
18
18
|
Don't pass event arrays saved before the upgrade as `initialEvents`. When the session's stream is still available, read it again from position 0 and replace what you saved. Saved cursors still count the same stored lines.
|
|
19
19
|
|
package/docs/evals/judge.mdx
CHANGED
|
@@ -66,6 +66,19 @@ t.judge("The assistant remembers the user's favorite word across turns.", {
|
|
|
66
66
|
|
|
67
67
|
The transcript contains user and assistant messages in turn order, excluding reasoning, tool calls, and tool results. See [Multi-turn evals](./cases#multi-turn-evals).
|
|
68
68
|
|
|
69
|
+
`turn.reply` is the turn's final reply. A turn can reply more than once, for example to confirm that work is underway before it waits on tasks. `session.transcript` includes every reply alongside the user's messages. To grade only one turn's replies, filter its messages with `transcriptOf` from `eve/client`:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { transcriptOf } from "eve/client";
|
|
73
|
+
|
|
74
|
+
const turn = await t.send("Check the deploy and tell me when it's done.");
|
|
75
|
+
const replies = transcriptOf(turn.events)
|
|
76
|
+
.messages.filter((message) => message.role === "assistant")
|
|
77
|
+
.map((message) => message.text);
|
|
78
|
+
|
|
79
|
+
t.judge("The agent reported the deploy's result.", { on: replies.join("\n\n") }).atLeast(0.8);
|
|
80
|
+
```
|
|
81
|
+
|
|
69
82
|
## Decide several questions together
|
|
70
83
|
|
|
71
84
|
A batch makes one decision request against one shared state. Pass `state` to replace the default `{ input, output }` completely. Batch calls use `state` instead of `on`.
|
|
@@ -158,11 +158,11 @@ Updates, enable, disable, and delete affect future scheduling, not emitted callb
|
|
|
158
158
|
|
|
159
159
|
## Handle occurrences and retries
|
|
160
160
|
|
|
161
|
-
`run` receives `payload`, `occurrence`, `auth`, `to`, and `waitUntil`. It may start zero or several sessions or perform application work directly. `to(channel, target).send(message)` is bound to the resolved creator; no auth argument is needed or accepted. It starts fresh, unattended sessions through an authored channel, even when the target names an existing thread. It does not steer an existing conversation.
|
|
161
|
+
`run` receives `payload`, `occurrence`, `auth`, `to`, and `waitUntil`. It may start zero or several sessions or perform application work directly. `to(channel, target).send(message, options)` is bound to the resolved creator; no auth argument is needed or accepted. It starts fresh, unattended sessions through an authored channel, even when the target names an existing thread. It does not steer an existing conversation. `options` takes `context`, `history`, `idempotencyKey`, `outputSchema`, and `title`, as on a route handler's [`to().send()`](../channels/custom#cross-channel-hand-off). It takes no `turnPolicy`, because a send never reaches a session the schedule didn't start.
|
|
162
162
|
|
|
163
163
|
eve awaits the callback and its registered `waitUntil` promises before reporting `occurrence.dispatched`. That event includes `sessionIds`; it means dispatch succeeded, not that the agent completed its task or delivered a successful result. Channels own output delivery and session-level failure reporting. Unattended sessions cannot wait for approvals or questions; configure tools accordingly.
|
|
164
164
|
|
|
165
|
-
Provider retries may repeat the callback and start new sessions. This is an at-least-once callback contract, not exactly-once side effects. Use `occurrence.executionId` together with the schedule/resource identity to deduplicate application effects where the destination supports it. An observer failure can also cause callback retry. `occurrence.failed` reports permanent provider failures or exhaustion of the provider retry budget.
|
|
165
|
+
Provider retries may repeat the callback and start new sessions. This is an at-least-once callback contract, not exactly-once side effects. Use `occurrence.executionId` together with the schedule/resource identity to deduplicate application effects where the destination supports it. For sessions, pass it in the send's key, for example `` idempotencyKey: `${occurrence.executionId}:digest` ``. A retried callback's send with the same key and target then reaches the session the first attempt started instead of starting another. Keys are scoped to the schedule, and the [usual limits](../channels/custom#retried-deliveries) apply. An observer failure can also cause callback retry. `occurrence.failed` reports permanent provider failures or exhaustion of the provider retry budget.
|
|
166
166
|
|
|
167
167
|
## Choose a provider and test locally
|
|
168
168
|
|
|
@@ -99,7 +99,7 @@ const client = new Client({
|
|
|
99
99
|
});
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
Clients reject redirects by default (`redirect: "error"`) so fetch cannot forward custom credentials to a redirected destination. Use `redirect: "manual"` to stop at a redirect without following it. Node.js exposes the response status and headers; browsers return an opaque redirect response with no readable status, headers, or body. The policy applies to inspection requests, custom fetches, session creation, and event streams, and overrides per-request redirect settings. Only opt into `redirect: "follow"` when you trust every possible redirect destination to receive your custom headers and request data.
|
|
103
103
|
|
|
104
104
|
Per-request headers can be attached to an individual turn:
|
|
105
105
|
|
|
@@ -113,6 +113,28 @@ await response.result();
|
|
|
113
113
|
|
|
114
114
|
Per-request headers override client-level values with the same name. For example, a turn can set its application user's `Authorization` header while `vercelOidc` continues to send the deployment-protection credential in `x-vercel-trusted-oidc-idp-token`.
|
|
115
115
|
|
|
116
|
+
## Reusing a session inbox
|
|
117
|
+
|
|
118
|
+
Keep the complete `session.state` when saving and restoring a conversation. It can include an
|
|
119
|
+
opaque `inbox` handle returned by the server. The client sends this handle with later messages
|
|
120
|
+
to avoid another hook lookup. Treat it as transport state: do not inspect or edit it.
|
|
121
|
+
|
|
122
|
+
Set `EVE_INBOX_HANDLE_SECRET` to the same server-only random secret (at least 32 characters)
|
|
123
|
+
on the deployments that share sessions. Without this key, handles are neither issued nor used.
|
|
124
|
+
The signature authenticates all routing and encryption fields before use. Rotation falls back
|
|
125
|
+
to ordinary token resolution.
|
|
126
|
+
|
|
127
|
+
Reuse also requires a Workflow SDK with `parseHookResumeHandle`, an adapter that declares
|
|
128
|
+
`resolvedHookResume`, and a backend with the guarded resolved-hook endpoint.
|
|
129
|
+
Older SDKs do not issue or use handles, even when the secret
|
|
130
|
+
is configured. This change keeps the released SDK dependencies; adopting the SDK update
|
|
131
|
+
enables the optimization. Handles exclude user metadata, live capabilities, and trace carriers,
|
|
132
|
+
which may contain private server baggage. The signature authenticates a handle; it does not hide its contents.
|
|
133
|
+
|
|
134
|
+
The server checks that the handle addresses the requested inbox. Workflow still validates the
|
|
135
|
+
hook, persists the message, and wakes the workflow. A definite stale-owner rejection resolves
|
|
136
|
+
the current inbox again. An uncertain write is surfaced without submitting a second delivery.
|
|
137
|
+
|
|
116
138
|
## Sessions
|
|
117
139
|
|
|
118
140
|
Create and park an ID-addressed conversation before its first turn when you can start session work
|
|
@@ -163,7 +185,7 @@ for await (const event of session.stream({ follow: false })) {
|
|
|
163
185
|
await session.reset({ reason: "User requested a fresh session" });
|
|
164
186
|
```
|
|
165
187
|
|
|
166
|
-
`client.sessions` stores
|
|
188
|
+
`client.sessions` stores the session ID, stream cursor, and optional opaque inbox handle. Every method calls an ID-addressed `/eve/v1/session/:sessionId/...` route. Sending through a handle for an unknown or terminal ID fails instead of creating a replacement, and reset leaves the handle pinned to the retired ID.
|
|
167
189
|
|
|
168
190
|
A client can own many independent fixed sessions at once:
|
|
169
191
|
|
|
@@ -220,6 +220,14 @@ A remote subagent call runs a child session in the remote deployment as a task:
|
|
|
220
220
|
|
|
221
221
|
The callback URL must use `http` or `https` and reach the parent at a public address. The remote rejects a create-session request whose callback host is a private, link-local, or reserved IP address, and refuses to deliver a callback to a hostname that resolves to one. A refused delivery fails without retries. Loopback hosts such as `localhost` and `127.0.0.1` work only when the remote runs as a local development server (`eve dev` or `vercel dev`), so parents and remotes that reach each other over a private network can't exchange callbacks.
|
|
222
222
|
|
|
223
|
+
Callback URLs contain only the message's callback capability, never the parent's deployment-protection bypass secret. For a parent protected by Vercel Deployment Protection, configure Trusted Sources on the parent to admit the remote project and environment.
|
|
224
|
+
|
|
225
|
+
On the remote deployment, set `EVE_CALLBACK_TRUSTED_ORIGINS` to a comma-separated list of exact parent HTTPS origins, for example `https://parent.example.com`. The remote attaches its own short-lived Vercel OIDC token only to those origins and its own deployment origins; different ports and subdomains do not inherit trust. Keep the remote's Vercel OIDC token available at runtime. Callbacks do not follow redirects.
|
|
226
|
+
|
|
227
|
+
Adding an origin grants trust: that recipient receives a bearer credential for the remote project's identity, not a token scoped to the callback recipient. List only parents you trust to act as the remote project until the token expires. A recipient can replay the token against other services, deployment-protection rules, or federated cloud roles that accept that identity.
|
|
228
|
+
|
|
229
|
+
This configuration is required when upgrading a protected cross-project setup that previously relied on a bypass secret in callback URLs. If either side is not on Vercel, make the capability-protected callback route reachable through your hosting policy; the framework does not forward a deployment-wide credential to the remote service.
|
|
230
|
+
|
|
223
231
|
The parent stream carries the same `task.started`, `child.opened`, and `call.settled` facts as local delegation. A remote child's deployment binding is private: only the parent's stream proxy reads it.
|
|
224
232
|
|
|
225
233
|
Clients follow a remote child through the parent. [`session.agent(opened).stream()`](./client/streaming#follow-a-subagent) reads the `child.opened` event's `stream` path, a route on the parent deployment. The parent verifies that its session recorded the child for that tool call, resolves the remote agent's `auth` and `headers`, and relays the child's stream. A browser never calls the remote deployment or holds its credentials; it only needs access to the parent session.
|