eve 0.27.0 → 0.27.2
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 +31 -0
- package/dist/src/channel/resolve-active-session.d.ts +4 -0
- package/dist/src/channel/resolve-active-session.js +1 -0
- package/dist/src/channel/routes.d.ts +11 -0
- package/dist/src/chunks/{use-eve-agent-PMY2WkAt.js → use-eve-agent-CbF0l_Fp.js} +66 -27
- package/dist/src/chunks/{use-eve-agent-AeQLLhu9.js → use-eve-agent-CgxB9WQv.js} +66 -27
- package/dist/src/cli/dev/tui/vercel-trusted-sources-policy.d.ts +1 -1
- package/dist/src/cli/dev/tui/vercel-trusted-sources.js +1 -1
- package/dist/src/client/index.d.ts +1 -1
- package/dist/src/client/open-stream.d.ts +15 -2
- package/dist/src/client/open-stream.js +1 -1
- package/dist/src/client/session.d.ts +3 -2
- package/dist/src/client/session.js +1 -1
- package/dist/src/client/types.d.ts +32 -0
- package/dist/src/compiled/.vendor-stamp.json +16 -16
- package/dist/src/compiled/@ai-sdk/anthropic/index.js +2 -2
- package/dist/src/compiled/@ai-sdk/google/index.d.ts +9 -2
- package/dist/src/compiled/@ai-sdk/google/index.js +6 -6
- package/dist/src/compiled/@ai-sdk/mcp/index.d.ts +18 -3
- package/dist/src/compiled/@ai-sdk/mcp/index.js +1 -1
- package/dist/src/compiled/@ai-sdk/openai/index.d.ts +285 -24
- package/dist/src/compiled/@ai-sdk/openai/index.js +2 -2
- package/dist/src/compiled/@ai-sdk/otel/index.js +1 -1
- package/dist/src/compiled/@ai-sdk/provider-utils/index.d.ts +7 -3
- package/dist/src/compiled/@vercel/oidc/index.js +28 -2
- package/dist/src/compiled/@vercel/sandbox/api-client/api-client.d.ts +11 -0
- package/dist/src/compiled/@vercel/sandbox/api-client/validators.d.ts +7 -0
- package/dist/src/compiled/@vercel/sandbox/command.d.ts +8 -0
- package/dist/src/compiled/@vercel/sandbox/execution-context.d.ts +116 -0
- package/dist/src/compiled/@vercel/sandbox/index.d.ts +3 -1
- package/dist/src/compiled/@vercel/sandbox/index.js +20 -19
- package/dist/src/compiled/@vercel/sandbox/network-policy.d.ts +13 -5
- package/dist/src/compiled/@vercel/sandbox/sandbox-user.d.ts +212 -0
- package/dist/src/compiled/@vercel/sandbox/sandbox.d.ts +133 -2
- package/dist/src/compiled/@vercel/sandbox/session.d.ts +2 -1
- package/dist/src/compiled/@vercel/sandbox/snapshot.d.ts +1 -1
- package/dist/src/compiled/@workflow/core/create-hook.d.ts +41 -22
- package/dist/src/compiled/@workflow/core/flushable-stream.d.ts +29 -0
- package/dist/src/compiled/@workflow/core/global.d.ts +2 -0
- package/dist/src/compiled/@workflow/core/index.js +2 -2
- package/dist/src/compiled/@workflow/core/private.d.ts +6 -17
- package/dist/src/compiled/@workflow/core/private.js +1 -1
- package/dist/src/compiled/@workflow/core/replay-payload-cache.d.ts +56 -0
- package/dist/src/compiled/@workflow/core/runtime/constants.d.ts +11 -0
- package/dist/src/compiled/@workflow/core/runtime/helpers.d.ts +6 -4
- package/dist/src/compiled/@workflow/core/runtime/resume-hook.d.ts +4 -1
- package/dist/src/compiled/@workflow/core/runtime/step-executor.d.ts +39 -0
- package/dist/src/compiled/@workflow/core/runtime.js +3 -3
- package/dist/src/compiled/@workflow/core/serialization.d.ts +44 -4
- package/dist/src/compiled/@workflow/core/symbols.d.ts +13 -0
- package/dist/src/compiled/@workflow/core/version.d.ts +1 -1
- package/dist/src/compiled/@workflow/core/workflow.js +1 -1
- package/dist/src/compiled/@workflow/errors/error-codes.d.ts +2 -0
- package/dist/src/compiled/@workflow/errors/index.d.ts +14 -0
- package/dist/src/compiled/@workflow/errors/index.js +1 -1
- package/dist/src/compiled/@workflow/world/events.d.ts +15 -0
- package/dist/src/compiled/@workflow/world/hooks.d.ts +2 -1
- package/dist/src/compiled/@workflow/world/interfaces.d.ts +77 -4
- package/dist/src/compiled/@workflow/world-local/index.js +4 -4
- package/dist/src/compiled/@workflow/world-vercel/index.js +16 -16
- package/dist/src/compiled/_chunks/node/{auth-BuQkq4Gr.js → auth-l7F2PF2b.js} +2 -2
- package/dist/src/compiled/_chunks/node/{version-DD-FX9rK.js → version-CjHTLx2a.js} +1 -1
- package/dist/src/compiled/_chunks/node/virtual-loader-Cwgujv7l.js +1 -0
- package/dist/src/compiled/_chunks/workflow/attribute-changes-Dk1fP6vt.js +20 -0
- package/dist/src/compiled/_chunks/workflow/{chunk-BHKSVoKr.js → chunk-BTyA9uPd.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/dist-B3qkUnLJ.js +3 -0
- package/dist/src/compiled/_chunks/workflow/{dist-CSfKNxQW.js → dist-BX517Nmz.js} +2 -2
- package/dist/src/compiled/_chunks/workflow/{dist-zLevmMyU.js → dist-C_VDTghO.js} +10 -10
- package/dist/src/compiled/_chunks/workflow/{dist-CttsoN4D.js → dist-CrxV2rsR.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/{functions-PT9ri9Hu.js → functions-Dv8dc_-I.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/resume-hook-czpG5xnP.js +1 -0
- package/dist/src/compiled/_chunks/workflow/run-6zaMjFY6.js +1 -0
- package/dist/src/compiled/_chunks/workflow/sleep-B2u06FWc.js +1 -0
- package/dist/src/compiled/_chunks/workflow/{src-CQuMexnO.js → src-D1EltLDA.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/{token-CaY0nA5J.js → token-Cz223z3C.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/token-DWqA5-DA.js +1 -0
- package/dist/src/compiled/_chunks/workflow/token-util-B6qBs3-0.js +27 -0
- package/dist/src/compiled/_chunks/workflow/{undici-fb54XL8i.js → undici-C2ZhYnV9.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/wrapper-Cc2otuUR.js +6 -0
- package/dist/src/compiled/env-runner/index.js +1 -1
- package/dist/src/compiled/env-runner/node-worker.js +1 -1
- package/dist/src/compiled/eventsource-parser/stream/index.d.ts +16 -3
- package/dist/src/compiled/jose/index.js +1 -1
- package/dist/src/compiler/compile-from-memory.d.ts +5 -0
- package/dist/src/compiler/compile-from-memory.js +1 -1
- package/dist/src/compiler/extension-compatibility.d.ts +10 -10
- package/dist/src/compiler/extension-compatibility.js +1 -1
- package/dist/src/execution/deliver-payloads.d.ts +1 -1
- package/dist/src/execution/deliver-payloads.js +1 -1
- package/dist/src/execution/workflow-entry.js +1 -1
- package/dist/src/execution/workflow-steps.js +1 -1
- package/dist/src/harness/session-limit-continuation.d.ts +8 -0
- package/dist/src/harness/session-limit-continuation.js +1 -1
- package/dist/src/harness/session-limit-enforcement.d.ts +9 -6
- package/dist/src/harness/session-limit-enforcement.js +1 -1
- package/dist/src/harness/stale-input-responses.d.ts +21 -3
- package/dist/src/harness/stale-input-responses.js +2 -2
- package/dist/src/harness/tool-loop.js +2 -2
- package/dist/src/harness/turn-cancellation.d.ts +13 -0
- package/dist/src/harness/turn-cancellation.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-module-evaluation-error.d.ts +2 -0
- package/dist/src/internal/authored-module-evaluation-error.js +4 -0
- package/dist/src/internal/authored-module-loader.js +2 -2
- package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
- package/dist/src/internal/workflow-bundle/vercel-workflow-output.js +1 -1
- package/dist/src/node_modules/.pnpm/@vercel_sdk@1.28.8/node_modules/@vercel/sdk/esm/models/updateprojectblock.js +1 -0
- package/dist/src/node_modules/.pnpm/@vercel_sdk@1.28.8/node_modules/@vercel/sdk/esm/models/updateprojectprojectsbranchmatcher.js +1 -0
- package/dist/src/packages/eve-catalog/src/index.js +1 -1
- package/dist/src/public/channels/discord/defaults.js +1 -1
- package/dist/src/public/channels/eve.js +2 -1
- package/dist/src/public/channels/linear/api.d.ts +2 -0
- package/dist/src/public/channels/linear/api.js +3 -1
- package/dist/src/public/channels/linear/defaults.js +2 -2
- package/dist/src/public/channels/linear/hitl.d.ts +8 -13
- package/dist/src/public/channels/linear/hitl.js +3 -3
- package/dist/src/public/channels/linear/index.d.ts +1 -1
- package/dist/src/public/channels/linear/index.js +1 -1
- package/dist/src/public/channels/linear/linearChannel.js +1 -1
- package/dist/src/public/channels/slack/api.d.ts +46 -2
- package/dist/src/public/channels/slack/api.js +1 -1
- package/dist/src/public/channels/slack/attachments.d.ts +6 -5
- package/dist/src/public/channels/slack/defaults.js +1 -1
- package/dist/src/public/channels/slack/inbound.d.ts +31 -0
- package/dist/src/public/channels/slack/inbound.js +1 -1
- package/dist/src/public/channels/slack/index.d.ts +2 -2
- package/dist/src/public/channels/slack/interactions.d.ts +2 -1
- package/dist/src/public/channels/slack/interactions.js +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +118 -13
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/slack/thread.d.ts +4 -3
- package/dist/src/public/channels/slack/thread.js +1 -1
- package/dist/src/public/channels/teams/defaults.js +2 -2
- package/dist/src/public/channels/teams/index.d.ts +1 -1
- package/dist/src/public/channels/teams/teamsChannel.d.ts +7 -2
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/telegram/defaults.js +1 -1
- package/dist/src/public/definitions/channel.d.ts +1 -1
- package/dist/src/runtime/framework-tools/connection-search-dynamic.js +2 -1
- package/dist/src/setup/scaffold/create/add-to-project.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +2 -2
- package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
- package/dist/src/setup/scaffold/create/web-template.js +90 -5
- package/dist/src/setup/scaffold/update/channels.js +2 -2
- package/dist/src/setup/scaffold/update/connections.js +1 -1
- package/dist/src/svelte/index.js +1 -1
- package/dist/src/svelte/use-eve-agent.js +1 -1
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/agent-config.md +10 -5
- package/docs/channels/slack.mdx +115 -12
- package/docs/channels/teams.mdx +10 -3
- package/docs/guides/client/streaming.mdx +15 -0
- package/docs/guides/deployment/vercel.mdx +1 -1
- package/docs/guides/frontend/overview.mdx +2 -0
- package/package.json +22 -22
- package/dist/src/compiled/@workflow/core/step-hydration-cache.d.ts +0 -148
- package/dist/src/compiled/_chunks/node/virtual-loader-B8K8w9cr.js +0 -1
- package/dist/src/compiled/_chunks/workflow/attribute-changes-CXiYB8h9.js +0 -20
- package/dist/src/compiled/_chunks/workflow/dist-BYo8Gno2.js +0 -3
- package/dist/src/compiled/_chunks/workflow/resume-hook-CHXbXuRZ.js +0 -1
- package/dist/src/compiled/_chunks/workflow/run-BjtAGa3_.js +0 -1
- package/dist/src/compiled/_chunks/workflow/sleep-BJAv0MFP.js +0 -1
- package/dist/src/compiled/_chunks/workflow/token-D4VIwGlD.js +0 -1
- package/dist/src/compiled/_chunks/workflow/wrapper-ulTvoTdW.js +0 -6
- package/dist/src/node_modules/.pnpm/@vercel_sdk@1.28.1/node_modules/@vercel/sdk/esm/models/updateprojectprojectsaction.js +0 -1
- package/dist/src/node_modules/.pnpm/@vercel_sdk@1.28.1/node_modules/@vercel/sdk/esm/models/updateprojectprojectsoptionsallowlist.js +0 -1
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/lib/primitives.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/lib/schemas.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/models/aclaction.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/models/sdkvalidationerror.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/defaultToZeroValue.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/fp.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/primitives.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/rfcdate.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/smartUnion.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/unrecognized.js +0 -0
|
@@ -16,8 +16,10 @@ export default eveChannel({
|
|
|
16
16
|
`,"app/_components/agent-chat.tsx":`"use client";
|
|
17
17
|
|
|
18
18
|
import type { UserContent } from "ai";
|
|
19
|
+
import { Client, type HandleMessageStreamEvent } from "eve/client";
|
|
19
20
|
import { useEveAgent } from "eve/react";
|
|
20
21
|
import { AlertCircleIcon } from "lucide-react";
|
|
22
|
+
import { useCallback, useRef, useState } from "react";
|
|
21
23
|
import {
|
|
22
24
|
Conversation,
|
|
23
25
|
ConversationContent,
|
|
@@ -35,16 +37,92 @@ import { AgentMessage } from "./agent-message";
|
|
|
35
37
|
const AGENT_NAME = "__EVE_INIT_APP_NAME__";
|
|
36
38
|
|
|
37
39
|
type AgentStatus = ReturnType<typeof useEveAgent>["status"];
|
|
40
|
+
type CancellationState = "idle" | "requested" | "cancelling";
|
|
41
|
+
|
|
42
|
+
type Cancellation = {
|
|
43
|
+
requested: boolean;
|
|
44
|
+
sentTurnId?: string;
|
|
45
|
+
turnId?: string;
|
|
46
|
+
};
|
|
38
47
|
|
|
39
48
|
export function AgentChat() {
|
|
40
|
-
const
|
|
49
|
+
const [session] = useState(() =>
|
|
50
|
+
new Client({ host: "", preserveCompletedSessions: true }).session(),
|
|
51
|
+
);
|
|
52
|
+
const cancellationRef = useRef<Cancellation>({ requested: false });
|
|
53
|
+
const [cancellationError, setCancellationError] = useState<string>();
|
|
54
|
+
const [cancellationState, setCancellationState] = useState<CancellationState>("idle");
|
|
55
|
+
|
|
56
|
+
const cancelTurn = useCallback(
|
|
57
|
+
(turnId: string) => {
|
|
58
|
+
const cancellation = cancellationRef.current;
|
|
59
|
+
if (!cancellation.requested || cancellation.sentTurnId === turnId) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
cancellation.sentTurnId = turnId;
|
|
64
|
+
setCancellationState("cancelling");
|
|
65
|
+
|
|
66
|
+
void session.cancel({ turnId }).catch((error: unknown) => {
|
|
67
|
+
if (cancellationRef.current !== cancellation) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
cancellation.requested = false;
|
|
72
|
+
cancellation.sentTurnId = undefined;
|
|
73
|
+
setCancellationError(toErrorMessage(error));
|
|
74
|
+
setCancellationState("idle");
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
[session],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const handleEvent = useCallback(
|
|
81
|
+
(event: HandleMessageStreamEvent) => {
|
|
82
|
+
if (event.type !== "turn.started") {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const cancellation = cancellationRef.current;
|
|
87
|
+
cancellation.turnId = event.data.turnId;
|
|
88
|
+
cancelTurn(event.data.turnId);
|
|
89
|
+
},
|
|
90
|
+
[cancelTurn],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const agent = useEveAgent({ onEvent: handleEvent, session });
|
|
41
94
|
const isBusy = agent.status === "submitted" || agent.status === "streaming";
|
|
42
95
|
const isEmpty = agent.data.messages.length === 0;
|
|
96
|
+
const errorMessage = cancellationError ?? agent.error?.message;
|
|
97
|
+
const submitStatus = isBusy && cancellationState !== "idle" ? "submitted" : agent.status;
|
|
98
|
+
|
|
99
|
+
const prepareTurn = () => {
|
|
100
|
+
cancellationRef.current = { requested: false };
|
|
101
|
+
setCancellationError(undefined);
|
|
102
|
+
setCancellationState("idle");
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const requestCancellation = () => {
|
|
106
|
+
if (!isBusy || cancellationState !== "idle") {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const cancellation = cancellationRef.current;
|
|
111
|
+
cancellation.requested = true;
|
|
112
|
+
setCancellationError(undefined);
|
|
113
|
+
setCancellationState("requested");
|
|
114
|
+
|
|
115
|
+
if (cancellation.turnId !== undefined) {
|
|
116
|
+
cancelTurn(cancellation.turnId);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
43
119
|
|
|
44
120
|
const handleSubmit = async (message: PromptInputMessage) => {
|
|
45
121
|
const text = message.text.trim();
|
|
46
122
|
if ((text.length === 0 && message.files.length === 0) || isBusy) return;
|
|
47
123
|
|
|
124
|
+
prepareTurn();
|
|
125
|
+
|
|
48
126
|
if (message.files.length === 0) {
|
|
49
127
|
await agent.send({ message: text });
|
|
50
128
|
return;
|
|
@@ -69,7 +147,7 @@ export function AgentChat() {
|
|
|
69
147
|
const composer = (
|
|
70
148
|
<PromptInput onSubmit={handleSubmit}>
|
|
71
149
|
<PromptInputTextarea placeholder="Send a message…" />
|
|
72
|
-
<PromptInputSubmit onStop={
|
|
150
|
+
<PromptInputSubmit onStop={requestCancellation} status={submitStatus} />
|
|
73
151
|
</PromptInput>
|
|
74
152
|
);
|
|
75
153
|
|
|
@@ -84,13 +162,13 @@ export function AgentChat() {
|
|
|
84
162
|
</header>
|
|
85
163
|
)}
|
|
86
164
|
|
|
87
|
-
{
|
|
165
|
+
{errorMessage ? (
|
|
88
166
|
<div className="mx-auto w-full max-w-3xl shrink-0 px-4 pt-2 sm:px-6">
|
|
89
167
|
<div className="flex items-start gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2.5 text-sm">
|
|
90
168
|
<AlertCircleIcon className="mt-0.5 size-4 shrink-0 text-destructive" />
|
|
91
169
|
<div>
|
|
92
170
|
<p className="font-medium">Request failed</p>
|
|
93
|
-
<p className="mt-0.5 text-muted-foreground">{
|
|
171
|
+
<p className="mt-0.5 text-muted-foreground">{errorMessage}</p>
|
|
94
172
|
</div>
|
|
95
173
|
</div>
|
|
96
174
|
</div>
|
|
@@ -107,7 +185,10 @@ export function AgentChat() {
|
|
|
107
185
|
}
|
|
108
186
|
key={message.id}
|
|
109
187
|
message={message}
|
|
110
|
-
onInputResponses={(inputResponses) =>
|
|
188
|
+
onInputResponses={(inputResponses) => {
|
|
189
|
+
prepareTurn();
|
|
190
|
+
return agent.send({ inputResponses });
|
|
191
|
+
}}
|
|
111
192
|
/>
|
|
112
193
|
))}
|
|
113
194
|
</ConversationContent>
|
|
@@ -134,6 +215,10 @@ export function AgentChat() {
|
|
|
134
215
|
);
|
|
135
216
|
}
|
|
136
217
|
|
|
218
|
+
function toErrorMessage(error: unknown): string {
|
|
219
|
+
return error instanceof Error ? error.message : "Unable to cancel the response.";
|
|
220
|
+
}
|
|
221
|
+
|
|
137
222
|
function StatusDot({ status }: { readonly status: AgentStatus }) {
|
|
138
223
|
const isLive = status === "submitted" || status === "streaming";
|
|
139
224
|
const tone =
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{pinnedNodeEngineMajor}from"../../node-engine.js";import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{patchPackageJson}from"./package-json.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"../create/web-template.js";import{DEFAULT_EVE_PACKAGE_CONTRACT,resolveEvePackageContract}from"../create/project.js";import{basename,join,resolve}from"node:path";import{readFile,readdir}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/eve/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,NEXT_PACKAGE_NAME=`next`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`],WEB_NEXT_CONFIG_PATH=`next.config.ts`,WEB_COMPETING_NEXT_CONFIG_PATHS=[`next.config.js`,`next.config.mjs`,WEB_NEXT_CONFIG_PATH,`next.config.mts`].filter(e=>e!==WEB_NEXT_CONFIG_PATH);function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readPackageJsonObject(e){if(!await pathExists(e))return;let t=JSON.parse(await readFile(e,`utf8`));return isJsonObject(t)?t:void 0}async function readDependencyVersion(e,t){let n=await readPackageJsonObject(e);if(n===void 0||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){let n=await readPackageJsonObject(e);return n!==void 0&&packageJsonHasDependency(n,t)}async function isNextJsProject(e){return hasPackageDependency(join(e,`package.json`),NEXT_PACKAGE_NAME)}const VERCEL_HOST_FRAMEWORK_PRESETS={"@sveltejs/kit":`sveltekit`,[NEXT_PACKAGE_NAME]:`nextjs`,nuxt:`nuxtjs`,nuxt3:`nuxtjs`,"nuxt-edge":`nuxtjs`,"nuxt-nightly":`nuxtjs`};async function resolveVercelHostFrameworkPreset(e){let t=await readPackageJsonObject(join(e,`package.json`));if(t!==void 0){for(let[e,n]of Object.entries(VERCEL_HOST_FRAMEWORK_PRESETS))if(packageJsonHasDependency(t,e))return n}}async function hasVercelHostFramework(e){return await resolveVercelHostFrameworkPreset(e)!==void 0}async function ensurePackageDependency(e,t,n){return!await pathExists(e)||await readDependencyVersion(e,t)===n?[]:(await patchPackageJson(e,{dependencies:{[t]:n}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{evePackage:e?.evePackage??DEFAULT_EVE_PACKAGE_CONTRACT,aiPackageVersion:e?.aiPackageVersion??`^7.0.
|
|
1
|
+
import{pinnedNodeEngineMajor}from"../../node-engine.js";import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{patchPackageJson}from"./package-json.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"../create/web-template.js";import{DEFAULT_EVE_PACKAGE_CONTRACT,resolveEvePackageContract}from"../create/project.js";import{basename,join,resolve}from"node:path";import{readFile,readdir}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/eve/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,NEXT_PACKAGE_NAME=`next`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`],WEB_NEXT_CONFIG_PATH=`next.config.ts`,WEB_COMPETING_NEXT_CONFIG_PATHS=[`next.config.js`,`next.config.mjs`,WEB_NEXT_CONFIG_PATH,`next.config.mts`].filter(e=>e!==WEB_NEXT_CONFIG_PATH);function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readPackageJsonObject(e){if(!await pathExists(e))return;let t=JSON.parse(await readFile(e,`utf8`));return isJsonObject(t)?t:void 0}async function readDependencyVersion(e,t){let n=await readPackageJsonObject(e);if(n===void 0||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){let n=await readPackageJsonObject(e);return n!==void 0&&packageJsonHasDependency(n,t)}async function isNextJsProject(e){return hasPackageDependency(join(e,`package.json`),NEXT_PACKAGE_NAME)}const VERCEL_HOST_FRAMEWORK_PRESETS={"@sveltejs/kit":`sveltekit`,[NEXT_PACKAGE_NAME]:`nextjs`,nuxt:`nuxtjs`,nuxt3:`nuxtjs`,"nuxt-edge":`nuxtjs`,"nuxt-nightly":`nuxtjs`};async function resolveVercelHostFrameworkPreset(e){let t=await readPackageJsonObject(join(e,`package.json`));if(t!==void 0){for(let[e,n]of Object.entries(VERCEL_HOST_FRAMEWORK_PRESETS))if(packageJsonHasDependency(t,e))return n}}async function hasVercelHostFramework(e){return await resolveVercelHostFrameworkPreset(e)!==void 0}async function ensurePackageDependency(e,t,n){return!await pathExists(e)||await readDependencyVersion(e,t)===n?[]:(await patchPackageJson(e,{dependencies:{[t]:n}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{evePackage:e?.evePackage??DEFAULT_EVE_PACKAGE_CONTRACT,aiPackageVersion:e?.aiPackageVersion??`^7.0.34`,nextPackageVersion:e?.nextPackageVersion??`16.3.0-preview.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatEveDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(t,n,i,s,u){let f=join(t,`package.json`);if(!await pathExists(f))return{mutations:[]};let m=resolveEvePackageContract(s.evePackage),h=pinnedNodeEngineMajor(m.nodeEngine),g={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:resolveVersionToken(`aiPackageVersion`,s.aiPackageVersion),eve:formatEveDependencySpecifier(m.version),next:resolveVersionToken(`nextPackageVersion`,s.nextPackageVersion),react:resolveVersionToken(`reactPackageVersion`,s.reactPackageVersion),"react-dom":resolveVersionToken(`reactDomPackageVersion`,s.reactDomPackageVersion),streamdown:resolveVersionToken(`streamdownPackageVersion`,s.streamdownPackageVersion),zod:resolveVersionToken(`zodPackageVersion`,s.zodPackageVersion)},_={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":h,"@types/react":resolveVersionToken(`typesReactPackageVersion`,s.typesReactPackageVersion),"@types/react-dom":resolveVersionToken(`typesReactDomPackageVersion`,s.typesReactDomPackageVersion),typescript:`6.0.3`},v=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts,y=isPackageManagerWorkspaceMember(n,i),b={dependencies:g,devDependencies:_,scripts:v};y||(b.nodeEngineRequirement=m.nodeEngine);let x=await patchPackageJson(f,b),S=(await patchWorkspaceRootPackageJson(n,i,{aiPackageVersion:g.ai,nodeEngineRequirement:m.nodeEngine,onWorkspaceRootMutation:u})).nodeEngineOverride??x.nodeEngineOverride;return{mutations:[{path:f,dependencies:Object.keys(g),devDependencies:Object.keys(_),scripts:Object.keys(v)}],nodeEngineOverride:S}}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){if(!e.startsWith(`slack/`)||e.length===6)throw Error(`Invalid Slack connector UID "${e}".`);return`import { connectSlackCredentials } from "@vercel/connect/eve";
|
|
2
2
|
import { slackChannel } from "eve/channels/slack";
|
|
3
3
|
|
|
4
4
|
export default slackChannel({
|
|
5
5
|
credentials: connectSlackCredentials(${JSON.stringify(e)}),
|
|
6
6
|
});
|
|
7
|
-
`}function renderWebAppTemplate(e,t){return e.replaceAll(`__EVE_INIT_APP_NAME__`,t).replaceAll(`__EVE_INIT_WITH_EVE_OPTIONS__`,``)}async function ensureWebVercelJson(e){return await pathExists(e)?`skipped`:(await writeTextFile(e,`${JSON.stringify({$schema:`https://openapi.vercel.sh/vercel.json`},null,2)}\n`,{force:!0}),`written`)}async function findCompetingNextConfigFiles(e){let t=[];for(let n of WEB_COMPETING_NEXT_CONFIG_PATHS){let i=join(e,n);await pathExists(i)&&t.push(i)}return t}async function ensureChannel(e){switch(e.kind){case`slack`:return ensureSlackChannel({...e,kind:`slack`});case`web`:return ensureWebChannel({...e,kind:`web`})}}async function ensureWebChannel(e){let t=join(e.projectRoot,`package.json`),n=await pathExists(join(e.projectRoot,`app/page.tsx`));if(!e.force&&await isNextJsProject(e.projectRoot))return{kind:`web`,action:`skipped`,skipReason:`nextjs-project`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let a=resolveWebPackageVersions(e.webPackageVersions),o=e.packageManager??`pnpm`,c=resolve(e.workspaceProbeDirectory??e.projectRoot),l=await patchWebPackageJson(e.projectRoot,o,c,a,e.onWorkspaceRootMutation),d=[],f=[],p=[],g=[],_=basename(resolve(e.projectRoot));if(e.configureVercelServices??!0){let t=join(e.projectRoot,`vercel.json`);await ensureWebVercelJson(t)===`written`?d.push(t):g.push(t)}let v=await applyPackageManagerWorkspaceConfiguration({packageManager:o,projectRoot:e.projectRoot,workspaceProbeRoot:c,onWorkspaceRootMutation:e.onWorkspaceRootMutation});d.push(...v.filesWritten),g.push(...v.filesSkipped);for(let[t,n]of Object.entries(WEB_APP_TEMPLATE_FILES)){let a=join(e.projectRoot,t);if(t===`agent/channels/eve.ts`&&!e.force&&await pathExists(a)){g.push(a);continue}let o=await pathExists(a);await writeTextFile(a,renderWebAppTemplate(n,_),{force:!0}),d.push(a),o&&f.push(a)}p.push(...await findCompetingNextConfigFiles(e.projectRoot));let y=[...new Set(d)],b=[...new Set(g)].filter(e=>!y.includes(e)),x={kind:`web`,action:n?`overwritten`:`created`,filesWritten:y,filesSkipped:b,packageJsonUpdated:l.mutations};return f.length>0&&(x.filesOverwritten=f),p.length>0&&(x.competingNextConfigFiles=p),l.nodeEngineOverride!==void 0&&(x.nodeEngineOverride=l.nodeEngineOverride),x}async function ensureSlackChannel(e){let t=join(e.projectRoot,`agent/channels/slack.ts`),n=await pathExists(t);if(!e.force&&n)return{kind:`slack`,action:`skipped`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let o=resolveVersionToken(`connectPackageVersion`,e.connectPackageVersion??`0.4.
|
|
7
|
+
`}function renderWebAppTemplate(e,t){return e.replaceAll(`__EVE_INIT_APP_NAME__`,t).replaceAll(`__EVE_INIT_WITH_EVE_OPTIONS__`,``)}async function ensureWebVercelJson(e){return await pathExists(e)?`skipped`:(await writeTextFile(e,`${JSON.stringify({$schema:`https://openapi.vercel.sh/vercel.json`},null,2)}\n`,{force:!0}),`written`)}async function findCompetingNextConfigFiles(e){let t=[];for(let n of WEB_COMPETING_NEXT_CONFIG_PATHS){let i=join(e,n);await pathExists(i)&&t.push(i)}return t}async function ensureChannel(e){switch(e.kind){case`slack`:return ensureSlackChannel({...e,kind:`slack`});case`web`:return ensureWebChannel({...e,kind:`web`})}}async function ensureWebChannel(e){let t=join(e.projectRoot,`package.json`),n=await pathExists(join(e.projectRoot,`app/page.tsx`));if(!e.force&&await isNextJsProject(e.projectRoot))return{kind:`web`,action:`skipped`,skipReason:`nextjs-project`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let a=resolveWebPackageVersions(e.webPackageVersions),o=e.packageManager??`pnpm`,c=resolve(e.workspaceProbeDirectory??e.projectRoot),l=await patchWebPackageJson(e.projectRoot,o,c,a,e.onWorkspaceRootMutation),d=[],f=[],p=[],g=[],_=basename(resolve(e.projectRoot));if(e.configureVercelServices??!0){let t=join(e.projectRoot,`vercel.json`);await ensureWebVercelJson(t)===`written`?d.push(t):g.push(t)}let v=await applyPackageManagerWorkspaceConfiguration({packageManager:o,projectRoot:e.projectRoot,workspaceProbeRoot:c,onWorkspaceRootMutation:e.onWorkspaceRootMutation});d.push(...v.filesWritten),g.push(...v.filesSkipped);for(let[t,n]of Object.entries(WEB_APP_TEMPLATE_FILES)){let a=join(e.projectRoot,t);if(t===`agent/channels/eve.ts`&&!e.force&&await pathExists(a)){g.push(a);continue}let o=await pathExists(a);await writeTextFile(a,renderWebAppTemplate(n,_),{force:!0}),d.push(a),o&&f.push(a)}p.push(...await findCompetingNextConfigFiles(e.projectRoot));let y=[...new Set(d)],b=[...new Set(g)].filter(e=>!y.includes(e)),x={kind:`web`,action:n?`overwritten`:`created`,filesWritten:y,filesSkipped:b,packageJsonUpdated:l.mutations};return f.length>0&&(x.filesOverwritten=f),p.length>0&&(x.competingNextConfigFiles=p),l.nodeEngineOverride!==void 0&&(x.nodeEngineOverride=l.nodeEngineOverride),x}async function ensureSlackChannel(e){let t=join(e.projectRoot,`agent/channels/slack.ts`),n=await pathExists(t);if(!e.force&&n)return{kind:`slack`,action:`skipped`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let o=resolveVersionToken(`connectPackageVersion`,e.connectPackageVersion??`0.4.2`),s=await ensurePackageDependency(join(e.projectRoot,`package.json`),`@vercel/connect`,o),c=e.slackConnectorSlug??await deriveSlackConnectorSlug(e.projectRoot);await writeTextFile(t,buildSlackTemplate(e.slackConnectorUid??`slack/${c}`),{force:e.force});let l={kind:`slack`,action:n?`overwritten`:`created`,filesWritten:[t],filesSkipped:[],packageJsonUpdated:s,slackConnectorSlug:c};return n&&(l.filesOverwritten=[t]),l}async function listAuthoredChannels(e){let r=join(e,`agent/channels`),i;try{i=await readdir(r,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}let a=[];for(let e of i){if(e.isFile()){let n=getSupportedModuleBaseName(e.name);n!==null&&a.push(n);continue}if(e.isDirectory())try{(await readdir(join(r,e.name))).some(e=>matchesSupportedModuleBaseName(e,`connection`))&&a.push(e.name)}catch{}}return a.sort()}export{DEFAULT_SLACK_CONNECTOR_SLUG,SLACK_CHANNEL_DEFAULT_ROUTE,deriveSlackConnectorSlug,ensureChannel,hasVercelHostFramework,isNextJsProject,listAuthoredChannels,normalizeSlackConnectorSlug,resolveVercelHostFrameworkPreset};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{endpointForProtocol}from"../connections/catalog.js";import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{patchPackageJson}from"./package-json.js";import{join}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const DEFAULT_CONNECT_PACKAGE_VERSION=`0.4.
|
|
1
|
+
import{endpointForProtocol}from"../connections/catalog.js";import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{patchPackageJson}from"./package-json.js";import{join}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const DEFAULT_CONNECT_PACKAGE_VERSION=`0.4.2`,CONNECT_PACKAGE_NAME=`@vercel/connect`,USER_AUTHORED_CONNECTION_DIR=`agent/connections`;function resolveAuth(e){return e.auth??{kind:`none`}}function envKeysForAuth(e){switch(e.kind){case`bearer-env`:return[e.envVar];case`header`:return e.headers.map(e=>e.envVar);case`connect`:case`none`:return[]}}function authBlock(e){switch(e.kind){case`connect`:return` auth: connect("${e.connector}"),\n`;case`bearer-env`:return` auth: { getToken: async () => ({ token: process.env.${e.envVar}! }) },\n`;case`header`:return` headers: () => ({\n${e.headers.map(e=>` "${e.header}": process.env.${e.envVar}!,`).join(`
|
|
2
2
|
`)}\n }),\n`;case`none`:return``}}function renderMcpTemplate(e,t,n){return`${n.kind===`connect`?`import { connect } from "@vercel/connect/eve";
|
|
3
3
|
import { defineMcpClientConnection } from "eve/connections";
|
|
4
4
|
`:`import { defineMcpClientConnection } from "eve/connections";
|
package/dist/src/svelte/index.js
CHANGED
package/dist/src/vue/index.js
CHANGED
package/docs/agent-config.md
CHANGED
|
@@ -139,11 +139,16 @@ Input and output budgets are checked independently. The model call that crosses
|
|
|
139
139
|
either limit is allowed to finish because providers only report exact token
|
|
140
140
|
usage after a call completes. Before the next model call, eve pauses the
|
|
141
141
|
session and sends a deterministic continuation prompt with two options:
|
|
142
|
-
**
|
|
143
|
-
and output windows reset together), and **Stop**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
**Approve** grants a fresh budget window of the configured size (both input
|
|
143
|
+
and output windows reset together), and **Stop** cancels the in-flight turn
|
|
144
|
+
through the standard cancellation path (`turn.cancelled` → `session.waiting`)
|
|
145
|
+
— a user decision, not an error. The session stays resumable; because it is
|
|
146
|
+
still over budget, the next message re-raises the prompt. Declining a
|
|
147
|
+
delegated child's prompt cancels the root turn, which cascades to the whole
|
|
148
|
+
delegation tree — the delegating parent never receives an error result it
|
|
149
|
+
could retry against a fresh quota share. A reply that answers neither option
|
|
150
|
+
re-raises the prompt; the reply stays in history and is processed once the
|
|
151
|
+
budget is granted.
|
|
147
152
|
|
|
148
153
|
Sessions that cannot reach a human — task-mode runs such as schedules and
|
|
149
154
|
subagents without input proxying — skip the prompt and fail the next model
|
package/docs/channels/slack.mdx
CHANGED
|
@@ -36,7 +36,11 @@ export default slackChannel({
|
|
|
36
36
|
});
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
`connectSlackCredentials` returns `{ botToken, webhookVerifier }`, keeping token rotation, multi-workspace tenancy, and request verification inside Connect rather than your code.
|
|
39
|
+
`connectSlackCredentials` returns `{ botToken, webhookVerifier }`, keeping token rotation, multi-workspace tenancy, and request verification inside Connect rather than your code.
|
|
40
|
+
|
|
41
|
+
### Deploy
|
|
42
|
+
|
|
43
|
+
Deploy once the trigger destination and channel file are ready:
|
|
40
44
|
|
|
41
45
|
```bash
|
|
42
46
|
VERCEL_USE_EXPERIMENTAL_FRAMEWORKS=1 vercel deploy --prod
|
|
@@ -44,17 +48,116 @@ VERCEL_USE_EXPERIMENTAL_FRAMEWORKS=1 vercel deploy --prod
|
|
|
44
48
|
|
|
45
49
|
`VERCEL_USE_EXPERIMENTAL_FRAMEWORKS=1` lets the Vercel CLI recognize eve as a framework during the build. eve's own setup commands set the same flag.
|
|
46
50
|
|
|
47
|
-
## How the channel handles
|
|
51
|
+
## How the channel handles inbound events
|
|
52
|
+
|
|
53
|
+
### Message hooks
|
|
54
|
+
|
|
55
|
+
Message hooks return `{ auth }` to dispatch, `null` to drop, or `{ auth, context }` to inject background into history.
|
|
56
|
+
|
|
57
|
+
- `onMessage(ctx, message)` handles Slack `message` events. eve drops messages authored by the installed app before this hook runs, preventing self-reply loops. Messages from other bots remain visible; check `message.author?.isBot` when those should also be ignored. `ctx.isBotMentioned()` and `ctx.isSubscribed()` support mention and active-thread policies.
|
|
58
|
+
- `onAppMention(ctx, message)` handles only `app_mention` and takes precedence over `onMessage`. Its default derives workspace-scoped auth and posts `Thinking…`.
|
|
59
|
+
- `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`.
|
|
60
|
+
|
|
61
|
+
| Incoming event | Handler order |
|
|
62
|
+
| ---------------------- | ------------------------------------------------------------------- |
|
|
63
|
+
| App mention | `onAppMention` → `onMessage` → `onEvent` → built-in mention default |
|
|
64
|
+
| Direct message | `onDirectMessage` → `onMessage` → `onEvent` → built-in DM default |
|
|
65
|
+
| Other Slack message | `onMessage` → `onEvent` → ignore |
|
|
66
|
+
| Other Events API event | `onEvent` → ignore |
|
|
67
|
+
|
|
68
|
+
Only the first available handler runs. A message hook returning `null` drops the message; it does not continue down the table.
|
|
69
|
+
|
|
70
|
+
`onInteraction(action, ctx)` separately handles `block_actions` callbacks not consumed by HITL. eve attaches the triggering Slack user id to the same model message as its text, preserving speaker attribution without profile lookups.
|
|
71
|
+
|
|
72
|
+
#### Continue conversations without repeated mentions
|
|
73
|
+
|
|
74
|
+
Use `onMessage` to handle explicit mentions and continue replies in threads with an active eve session:
|
|
75
|
+
|
|
76
|
+
```ts title="agent/channels/slack.ts"
|
|
77
|
+
export default slackChannel({
|
|
78
|
+
credentials: connectSlackCredentials("slack/my-agent"),
|
|
79
|
+
async onMessage(ctx, message) {
|
|
80
|
+
if (message.author?.isBot) return null;
|
|
81
|
+
const isDirectMessage = message.raw.channel_type === "im";
|
|
82
|
+
return isDirectMessage || ctx.isBotMentioned() || (await ctx.isSubscribed())
|
|
83
|
+
? { auth: null }
|
|
84
|
+
: null;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`isBotMentioned()` identifies an explicit mention. `isSubscribed()` checks whether the message belongs to a thread with an active eve session. For Vercel Connect, open **Advanced** when creating the connector and add `message.channels` under **Trigger Event Types** and `channels:history` under **Bot Scopes**. Private channels additionally need `message.groups` and `groups:history`.
|
|
90
|
+
|
|
91
|
+
Use `ctx.thread.listParticipants()` when routing depends on who has joined the thread. It fetches the current thread and returns unique human Slack user ids in first-appearance order, so the first id is the starting author for a human-started thread. Bot and system messages are excluded:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
async onMessage(ctx, message) {
|
|
95
|
+
if (!message.author || message.author.isBot) return null;
|
|
96
|
+
|
|
97
|
+
const participants = await ctx.thread.listParticipants();
|
|
98
|
+
const isGroupFollowUpFromStarter =
|
|
99
|
+
participants.length > 1 && participants[0] === message.author.userId;
|
|
100
|
+
|
|
101
|
+
return isGroupFollowUpFromStarter ? { auth: null } : null;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Like `threadContext`, this helper calls `conversations.replies` and requires the matching Slack history scope. It observes at most the first 50 messages of the thread, and a failed fetch is logged and swallowed, so the returned list may be empty or stale — treat an unexpected empty list as "don't route" rather than "no participants".
|
|
106
|
+
|
|
107
|
+
#### Cancel and replace in-flight work
|
|
108
|
+
|
|
109
|
+
Message hooks (`onMessage`, `onAppMention`, and `onDirectMessage`) receive a thread-bound `ctx.cancel({ turnId? })` helper. Call it before returning `{ auth }` to stop the current turn and queue the new Slack message as replacement input, producing a debounce-like experience:
|
|
110
|
+
|
|
111
|
+
```ts title="agent/channels/slack.ts"
|
|
112
|
+
export default slackChannel({
|
|
113
|
+
credentials: connectSlackCredentials("slack/my-agent"),
|
|
114
|
+
async onMessage(ctx, message) {
|
|
115
|
+
const shouldHandle = ctx.isBotMentioned() || (await ctx.isSubscribed());
|
|
116
|
+
if (!shouldHandle || message.author?.isBot) return null;
|
|
117
|
+
|
|
118
|
+
await ctx.cancel();
|
|
119
|
+
return { auth: null };
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`"accepted"` means the cancellation request was consumed; `"no_active_turn"` means the thread had no cancellable turn. 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 bound helper, which is useful for a Stop button that should cancel without replacement input.
|
|
125
|
+
|
|
126
|
+
### Other Events API callbacks
|
|
127
|
+
|
|
128
|
+
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. `ctx.receive(options)` is the current Slack channel's pre-bound form of the schedule API's `receive(slack, options)`: call it zero, one, or many times to start turns. Each call accepts `message`, `target`, and `auth`, and returns the resulting session.
|
|
129
|
+
|
|
130
|
+
```ts title="agent/channels/slack.ts"
|
|
131
|
+
import { connectSlackCredentials } from "@vercel/connect/eve";
|
|
132
|
+
import { slackChannel } from "eve/channels/slack";
|
|
133
|
+
|
|
134
|
+
const onboardingChannels = ["C0123ABC", "C0456DEF"];
|
|
135
|
+
|
|
136
|
+
export default slackChannel({
|
|
137
|
+
credentials: connectSlackCredentials("slack/my-agent"),
|
|
138
|
+
async onEvent({ receive }, event) {
|
|
139
|
+
if (event.type !== "team_join") return;
|
|
140
|
+
|
|
141
|
+
await Promise.all(
|
|
142
|
+
onboardingChannels.map((channelId) =>
|
|
143
|
+
receive({
|
|
144
|
+
message: `A user joined the Slack workspace. Onboard them from this event:\n${JSON.stringify(event)}`,
|
|
145
|
+
target: { channelId },
|
|
146
|
+
auth: null,
|
|
147
|
+
}),
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
```
|
|
48
153
|
|
|
49
|
-
|
|
154
|
+
The webhook invocation already keeps an awaited `onEvent` handler alive. Use `ctx.waitUntil(promise)` for deliberately detached work, matching a handler-form schedule. `ctx.slack.request(operation, body)` provides workspace-scoped Slack Web API access, while `ctx.envelope` carries delivery metadata such as `team_id`, `event_id`, and `event_time`. Calls to `ctx.receive` automatically seed the callback's team id into Slack session state.
|
|
50
155
|
|
|
51
|
-
|
|
156
|
+
Because a generic event is not necessarily tied to one thread, its cancellation helper takes the target explicitly: `ctx.cancel({ channelId, threadTs, turnId? })`. Pair it with `ctx.receive(...)` when an event should replace in-flight work, or call it alone to stop the turn.
|
|
52
157
|
|
|
53
|
-
|
|
54
|
-
- `onDirectMessage(ctx, message)` handles `message.im` events (needs `im:history` scope). Bot-authored messages and edits are filtered out first.
|
|
55
|
-
- `onInteraction(action, ctx)` handles `block_actions` callbacks not consumed by HITL.
|
|
158
|
+
`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.
|
|
56
159
|
|
|
57
|
-
|
|
160
|
+
`onEvent` covers JSON `event_callback` deliveries only. URL verification, slash commands, and interactive payloads do not reach it. Add every desired event and required OAuth scope to the Slack app's Event Subscriptions configuration; eve can only handle events Slack sends.
|
|
58
161
|
|
|
59
162
|
You get the triggering mention by default, but not the earlier replies in the thread. Enable `threadContext` to fetch and inject them with every message attributed by stable Slack user id. Use `since: "last-agent-reply"` so repeated mentions inject only what is new:
|
|
60
163
|
|
|
@@ -71,14 +174,14 @@ export default slackChannel({
|
|
|
71
174
|
`since` sets the boundary for what each mention injects and accepts three values:
|
|
72
175
|
|
|
73
176
|
- `"thread-root"` (the default): every prior message in the thread, on every mention. `threadContext: {}` behaves the same.
|
|
74
|
-
- `"last-agent-reply"`: only messages after
|
|
177
|
+
- `"last-agent-reply"`: only messages after this installed agent's last reply, keeping repeated mentions incremental. Replies from other Slack bots do not move the boundary.
|
|
75
178
|
- A predicate `(message: SlackThreadMessage) => boolean`: only messages after the last one it matches, such as "since the last message that mentioned a particular user".
|
|
76
179
|
|
|
77
|
-
`threadContext`
|
|
180
|
+
`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.
|
|
78
181
|
|
|
79
182
|
### Slack API calls outside a handler
|
|
80
183
|
|
|
81
|
-
Inside webhook-side handlers (`onAppMention`, `onInteraction`, `events`),
|
|
184
|
+
Inside webhook-side handlers (`onAppMention`, `onEvent`, `onInteraction`, `events`),
|
|
82
185
|
`ctx.slack.request(operation, body)` is the raw-API escape hatch. Outside
|
|
83
186
|
those contexts there is no handle — a schedule resolving reactions on old
|
|
84
187
|
messages, for example, has no inbound Slack request. For that, call the
|
|
@@ -105,7 +208,7 @@ when you need the bearer token itself.
|
|
|
105
208
|
|
|
106
209
|
### Delivery
|
|
107
210
|
|
|
108
|
-
The default handlers reply in-thread and show progress. Typing indicators post automatically: `Thinking…` on inbound, `Working…` on `turn.started`, a truncated reasoning snippet on `reasoning.appended`, and an action label on `actions.requested` — the tool name plus its most telling argument (`grep useEveAgent`, `read_file agent/agent.ts`), the subagent or remote-agent name for dispatched calls, and `+N more` when the model requests several actions at once. The model's own pre-tool narration, when present, takes precedence over the derived label. Reasoning snippets build progressively: extensions of at least four characters appear immediately, while smaller streamed deltas use the five-second refresh interval to avoid one Slack request per token. Override `events["reasoning.appended"]` if you prefer generic wording. Override
|
|
211
|
+
The default handlers reply in-thread and show progress. Typing indicators post automatically: `Thinking…` on inbound, `Working…` on `turn.started`, a truncated reasoning snippet on `reasoning.appended`, and an action label on `actions.requested` — the tool name plus its most telling argument (`grep useEveAgent`, `read_file agent/agent.ts`), the subagent or remote-agent name for dispatched calls, and `+N more` when the model requests several actions at once. The model's own pre-tool narration, when present, takes precedence over the derived label. Reasoning snippets build progressively: extensions of at least four characters appear immediately, while smaller streamed deltas use the five-second refresh interval to avoid one Slack request per token. Override `events["reasoning.appended"]` if you prefer generic wording. Override an inbound handler or the `events` handlers to customize.
|
|
109
212
|
|
|
110
213
|
Outbound text preserves bare `@` tokens as literal text. To mention a user, embed Slack's `<@USER_ID>` syntax directly or use `channel.thread.mentionUser(userId)`.
|
|
111
214
|
|
package/docs/channels/teams.mdx
CHANGED
|
@@ -28,17 +28,24 @@ By default the channel mounts at `POST /eve/v1/teams`. Point your Azure Bot or T
|
|
|
28
28
|
|
|
29
29
|
The default `onMessage` handles two cases: personal-chat messages, and channel or group-chat messages that mention the bot directly. Ambient resource-specific-consent messages are dropped unless you override it. Before dispatch, eve strips the mention, adds a `<teams_context>` block, and scopes channel and group threads by root activity id (`replyToId ?? id`).
|
|
30
30
|
|
|
31
|
+
For example, use the contextual `isSubscribed()` helper to continue active threads without requiring another mention:
|
|
32
|
+
|
|
31
33
|
```ts
|
|
32
34
|
import { defaultTeamsAuth, teamsChannel } from "eve/channels/teams";
|
|
33
35
|
|
|
34
36
|
export default teamsChannel({
|
|
35
|
-
onMessage(ctx, message) {
|
|
36
|
-
if (message.
|
|
37
|
-
|
|
37
|
+
async onMessage(ctx, message) {
|
|
38
|
+
if (message.from.role === "bot" || message.from.id === message.recipient.id) return null;
|
|
39
|
+
const isDirectMessage = message.scope === "personal";
|
|
40
|
+
return isDirectMessage || message.isBotMentioned || (await ctx.isSubscribed())
|
|
41
|
+
? { auth: defaultTeamsAuth(message) }
|
|
42
|
+
: null;
|
|
38
43
|
},
|
|
39
44
|
});
|
|
40
45
|
```
|
|
41
46
|
|
|
47
|
+
`isSubscribed()` checks whether the conversation has previously involved the agent.
|
|
48
|
+
|
|
42
49
|
### Delivery
|
|
43
50
|
|
|
44
51
|
Replies post as Markdown (`textFormat: "markdown"`), with oversized text split across messages and a typing indicator sent on turn start and action requests.
|
|
@@ -105,6 +105,21 @@ If you support refresh while an authorization prompt is pending, keep the sessio
|
|
|
105
105
|
|
|
106
106
|
HTTP connections can end before a run does. The client reconnects from the number of events already consumed, so long turns continue without replaying events. It stops at a turn boundary, when aborted, or when the stream can no longer make progress.
|
|
107
107
|
|
|
108
|
+
Set `streamReconnectPolicy: { reconnect: false }` when a relay or proxy owns the cursor and reconnection policy. This makes a single stream GET attempt and returns when that connection ends; it does not stop the server-side turn:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const response = await session.send({
|
|
112
|
+
message: "Run the long operation.",
|
|
113
|
+
streamReconnectPolicy: { reconnect: false },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
for await (const event of response) {
|
|
117
|
+
console.log(event.type);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The same option is available on manual attachments as `session.stream({ streamReconnectPolicy: { reconnect: false } })`.
|
|
122
|
+
|
|
108
123
|
## Open a stream manually
|
|
109
124
|
|
|
110
125
|
Use `session.stream()` when you already have a session cursor and only need to attach to the existing stream:
|
|
@@ -60,7 +60,7 @@ eve deploy
|
|
|
60
60
|
Vercel uses the generated output to configure these services:
|
|
61
61
|
|
|
62
62
|
- **Web runtime**: serves health, session, stream, channel, callback, and schedule routes
|
|
63
|
-
- **Vercel Workflow**: persists and resumes durable runs
|
|
63
|
+
- **Vercel Workflow**: persists and resumes durable runs, with optimistic replay preconditions enabled so stale event-log snapshots reload before they can commit
|
|
64
64
|
- **Vercel Cron**: invokes authored schedules
|
|
65
65
|
- **Vercel Sandbox**: runs sandbox sessions selected by `defaultBackend()`
|
|
66
66
|
|
|
@@ -100,6 +100,8 @@ Assistant text, reasoning, tool calls, and tool results stream into `data` as th
|
|
|
100
100
|
|
|
101
101
|
`stop()` is local: turns are resumable across disconnects, so detaching the stream never cancels the work — the turn keeps running and billing on the server. To stop the turn itself, POST the session's [cancel route](../../channels/eve#routes); the turn then settles on the stream as `turn.cancelled` followed by `session.waiting`.
|
|
102
102
|
|
|
103
|
+
A chat UI with a Stop button should own the `ClientSession` passed to `useEveAgent`, create it with `preserveCompletedSessions: true`, and call `session.cancel({ turnId })` after observing that turn's `turn.started` event. The `turnId` guard makes a late click a harmless no-op instead of cancelling a newer turn. Keep the stream open until the cancellation boundary arrives; do not use `stop()` for this interaction.
|
|
104
|
+
|
|
103
105
|
After eve confirms an attachment turn with `message.received`, the default reducer projects each
|
|
104
106
|
received attachment as a `file` part on the user message. The part includes `mediaType`, optional
|
|
105
107
|
`filename` and `size`, and a `url` only when the original attachment was browser-resolvable.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eve",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Filesystem-first framework for durable backend AI agents that run anywhere.",
|
|
6
6
|
"keywords": [
|
|
@@ -282,13 +282,13 @@
|
|
|
282
282
|
"nitro": "3.0.260610-beta"
|
|
283
283
|
},
|
|
284
284
|
"devDependencies": {
|
|
285
|
-
"@ai-sdk/anthropic": "^4.0.
|
|
286
|
-
"@ai-sdk/google": "^4.0.
|
|
287
|
-
"@ai-sdk/mcp": "^2.0.
|
|
288
|
-
"@ai-sdk/openai": "^4.0.
|
|
289
|
-
"@ai-sdk/otel": "^1.0.
|
|
285
|
+
"@ai-sdk/anthropic": "^4.0.18",
|
|
286
|
+
"@ai-sdk/google": "^4.0.21",
|
|
287
|
+
"@ai-sdk/mcp": "^2.0.16",
|
|
288
|
+
"@ai-sdk/openai": "^4.0.17",
|
|
289
|
+
"@ai-sdk/otel": "^1.0.34",
|
|
290
290
|
"@ai-sdk/provider": "^4.0.3",
|
|
291
|
-
"@ai-sdk/provider-utils": "^5.0.
|
|
291
|
+
"@ai-sdk/provider-utils": "^5.0.12",
|
|
292
292
|
"@chat-adapter/slack": "4.34.0",
|
|
293
293
|
"@chat-adapter/state-memory": "4.34.0",
|
|
294
294
|
"@chat-adapter/twilio": "4.34.0",
|
|
@@ -300,29 +300,29 @@
|
|
|
300
300
|
"@types/react": "19.2.15",
|
|
301
301
|
"@types/react-test-renderer": "19.1.0",
|
|
302
302
|
"@vercel/detect-agent": "1.2.3",
|
|
303
|
-
"@vercel/oidc": "3.
|
|
304
|
-
"@vercel/sandbox": "2.
|
|
305
|
-
"@vercel/sdk": "1.28.
|
|
306
|
-
"@workflow/core": "5.0.0-beta.
|
|
307
|
-
"@workflow/errors": "5.0.0-beta.
|
|
303
|
+
"@vercel/oidc": "3.8.0",
|
|
304
|
+
"@vercel/sandbox": "2.8.0",
|
|
305
|
+
"@vercel/sdk": "1.28.8",
|
|
306
|
+
"@workflow/core": "5.0.0-beta.36",
|
|
307
|
+
"@workflow/errors": "5.0.0-beta.12",
|
|
308
308
|
"@workflow/serde": "5.0.0-beta.2",
|
|
309
309
|
"@workflow/utils": "5.0.0-beta.6",
|
|
310
|
-
"@workflow/world": "5.0.0-beta.
|
|
311
|
-
"@workflow/world-local": "5.0.0-beta.
|
|
312
|
-
"@workflow/world-vercel": "5.0.0-beta.
|
|
313
|
-
"ai": "^7.0.
|
|
310
|
+
"@workflow/world": "5.0.0-beta.22",
|
|
311
|
+
"@workflow/world-local": "5.0.0-beta.30",
|
|
312
|
+
"@workflow/world-vercel": "5.0.0-beta.32",
|
|
313
|
+
"ai": "^7.0.34",
|
|
314
314
|
"autoevals": "0.0.132",
|
|
315
315
|
"chat": "4.34.0",
|
|
316
316
|
"chokidar": "5.0.0",
|
|
317
317
|
"commander": "14.0.3",
|
|
318
318
|
"emulate": "0.6.0",
|
|
319
|
-
"env-runner": "0.1.
|
|
320
|
-
"eventsource-parser": "3.0
|
|
319
|
+
"env-runner": "0.1.16",
|
|
320
|
+
"eventsource-parser": "3.1.0",
|
|
321
321
|
"experimental-ai-sdk-code-mode": "1.0.16",
|
|
322
322
|
"gray-matter": "4.0.3",
|
|
323
323
|
"jose": "6.2.3",
|
|
324
324
|
"jsonc-parser": "3.3.1",
|
|
325
|
-
"just-bash": "3.0
|
|
325
|
+
"just-bash": "3.1.0",
|
|
326
326
|
"marked": "17.0.6",
|
|
327
327
|
"microsandbox": "0.5.5",
|
|
328
328
|
"next": "16.3.0-preview.6",
|
|
@@ -332,8 +332,8 @@
|
|
|
332
332
|
"semver": "7.8.4",
|
|
333
333
|
"svelte": "^5.0.0",
|
|
334
334
|
"turndown": "7.2.4",
|
|
335
|
-
"vite": "^8.
|
|
336
|
-
"vitest": "4.1.
|
|
335
|
+
"vite": "^8.1.5",
|
|
336
|
+
"vitest": "4.1.10",
|
|
337
337
|
"vue": "^3.5.0",
|
|
338
338
|
"zod": "4.4.3",
|
|
339
339
|
"zod-validation-error": "5.0.0",
|
|
@@ -341,7 +341,7 @@
|
|
|
341
341
|
},
|
|
342
342
|
"peerDependencies": {
|
|
343
343
|
"@opentelemetry/api": "^1.0.0",
|
|
344
|
-
"ai": "^7.0.
|
|
344
|
+
"ai": "^7.0.34",
|
|
345
345
|
"braintrust": "^3.0.0",
|
|
346
346
|
"just-bash": "^3.0.0",
|
|
347
347
|
"microsandbox": "^0.5.0"
|