eve 0.52.2 → 0.52.3
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 +20 -0
- package/dist/src/channel/types.d.ts +1 -0
- package/dist/src/chunks/{use-eve-agent-1NAM6ftx.js → use-eve-agent-BR8EpIOX.js} +27 -10
- package/dist/src/chunks/{use-eve-agent-BEjdhhhK.js → use-eve-agent-XnfjQt1Z.js} +27 -10
- package/dist/src/cli/commands/deploy.js +1 -1
- package/dist/src/cli/commands/extension-init.d.ts +2 -1
- package/dist/src/cli/commands/extension-init.js +1 -1
- package/dist/src/cli/commands/init.d.ts +2 -13
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/dev/run-interactive-ui.d.ts +3 -0
- package/dist/src/cli/dev/run-interactive-ui.js +1 -1
- package/dist/src/cli/dev/tui/prompt-command-handler.js +1 -1
- package/dist/src/cli/dev/tui/runner.d.ts +6 -0
- package/dist/src/cli/dev/tui/runner.js +1 -1
- package/dist/src/cli/dev/tui/setup-commands.d.ts +5 -3
- package/dist/src/cli/dev/tui/setup-commands.js +2 -2
- package/dist/src/cli/dev/tui/status-line.js +1 -1
- package/dist/src/cli/dev/tui/tui.d.ts +4 -0
- package/dist/src/cli/dev/tui/tui.js +1 -1
- package/dist/src/cli/run.d.ts +1 -1
- package/dist/src/cli/run.js +2 -2
- package/dist/src/cli/telemetry/index.d.ts +15 -0
- package/dist/src/cli/telemetry/index.js +2 -2
- package/dist/src/client/session.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +1 -1
- package/dist/src/compiler/compile-from-memory.d.ts +1 -0
- package/dist/src/compiler/compile-from-memory.js +1 -1
- package/dist/src/compiler/extension-compatibility.d.ts +18 -18
- package/dist/src/compiler/extension-compatibility.js +1 -1
- package/dist/src/context/build-dynamic-tools.d.ts +2 -1
- package/dist/src/context/build-dynamic-tools.js +1 -1
- package/dist/src/context/dynamic-tool-lifecycle.d.ts +3 -3
- package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
- package/dist/src/context/dynamic-tool-metadata.d.ts +1 -1
- package/dist/src/context/hook-lifecycle.js +1 -1
- package/dist/src/context/keys.d.ts +2 -0
- package/dist/src/context/keys.js +1 -1
- package/dist/src/eve-channel/index.js +1 -1
- package/dist/src/execution/activity-events.d.ts +1 -0
- package/dist/src/execution/activity-events.js +1 -1
- package/dist/src/execution/node-step.js +1 -1
- package/dist/src/execution/sandbox/prewarm.js +1 -1
- package/dist/src/execution/session-activity-projection.js +1 -1
- package/dist/src/execution/session-activity.js +1 -1
- package/dist/src/execution/settle-cancelled-turn-step.js +1 -1
- package/dist/src/execution/tasks/parent/dispatch.js +1 -1
- package/dist/src/execution/tasks/parent/task-cancel.d.ts +2 -2
- package/dist/src/execution/tasks/parent/task-cancel.js +1 -1
- package/dist/src/execution/tasks/parent/tool-execution.js +1 -1
- package/dist/src/execution/tools/sleep-workflow.js +1 -1
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/execution/workflow-steps.js +1 -1
- package/dist/src/harness/action-presentation.d.ts +13 -0
- package/dist/src/harness/action-presentation.js +1 -0
- package/dist/src/harness/compaction.js +1 -1
- package/dist/src/harness/coordination.js +1 -1
- package/dist/src/harness/emission.d.ts +1 -6
- package/dist/src/harness/emission.js +1 -1
- package/dist/src/harness/execute-tool.d.ts +2 -1
- package/dist/src/harness/finish-reason.d.ts +3 -0
- package/dist/src/harness/finish-reason.js +1 -0
- package/dist/src/harness/provider-tool-schemas.d.ts +1 -0
- package/dist/src/harness/provider-tool-schemas.js +1 -1
- package/dist/src/harness/step-hooks.js +1 -1
- package/dist/src/harness/stream-actions.d.ts +2 -2
- package/dist/src/harness/stream-actions.js +1 -1
- package/dist/src/harness/tool-call-input-errors.d.ts +4 -3
- package/dist/src/harness/tool-call-input-errors.js +1 -1
- package/dist/src/harness/tool-loop.js +2 -2
- package/dist/src/harness/tool-presentation.d.ts +5 -0
- package/dist/src/harness/tool-presentation.js +1 -0
- package/dist/src/harness/turn-client-context.d.ts +1 -0
- package/dist/src/harness/workflow-lifecycle.d.ts +2 -3
- package/dist/src/harness/workflow-lifecycle.js +1 -1
- package/dist/src/internal/application/compiled-artifacts.d.ts +3 -0
- package/dist/src/internal/application/compiled-artifacts.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-definition/schema-backed.js +1 -1
- package/dist/src/internal/authored-module-conditions.d.ts +2 -0
- package/dist/src/internal/authored-module-conditions.js +1 -0
- package/dist/src/internal/authored-module-loader.d.ts +3 -0
- package/dist/src/internal/authored-module-loader.js +3 -3
- package/dist/src/internal/authored-module-map-loader.d.ts +3 -1
- package/dist/src/internal/authored-module-map-loader.js +1 -1
- package/dist/src/internal/authored-runtime-modules.d.ts +2 -0
- package/dist/src/internal/authored-runtime-modules.js +1 -1
- package/dist/src/internal/bundler/nitro-rolldown.d.ts +1 -0
- package/dist/src/internal/bundler/nitro-rolldown.js +1 -1
- package/dist/src/internal/nitro/development-generation.d.ts +2 -0
- package/dist/src/internal/nitro/development-generation.js +1 -1
- package/dist/src/internal/nitro/host/configure-nitro-routes.js +1 -1
- package/dist/src/internal/nitro/host/create-application-nitro.js +1 -1
- package/dist/src/internal/nitro/host/prepare-application-host.js +1 -1
- package/dist/src/internal/nitro/routes/channel-request-instrumentation.js +1 -1
- package/dist/src/internal/workflow-bundle/builder-support.d.ts +5 -0
- package/dist/src/internal/workflow-bundle/builder.js +2 -2
- package/dist/src/internal/workflow-bundle/dynamic-tool-transform.d.ts +1 -1
- package/dist/src/internal/workflow-bundle/dynamic-tool-transform.js +1 -1
- package/dist/src/protocol/activity.d.ts +6 -0
- package/dist/src/protocol/activity.js +1 -1
- package/dist/src/protocol/message.d.ts +13 -1
- package/dist/src/protocol/message.js +1 -1
- package/dist/src/public/channels/slack/activity-plan.js +1 -1
- package/dist/src/public/channels/slack/activity.js +2 -2
- package/dist/src/public/models/openai/chatgpt/credential-store.d.ts +6 -0
- package/dist/src/public/models/openai/chatgpt/credential-store.js +1 -0
- package/dist/src/public/models/openai/chatgpt/model.d.ts +1 -1
- package/dist/src/public/models/openai/chatgpt/oauth.d.ts +20 -0
- package/dist/src/public/models/openai/chatgpt/oauth.js +1 -0
- package/dist/src/public/models/openai/chatgpt/token-broker.d.ts +4 -3
- package/dist/src/public/models/openai/chatgpt/token-broker.js +1 -1
- package/dist/src/public/models/openai/chatgpt/transport.d.ts +1 -2
- package/dist/src/public/models/openai/index.d.ts +3 -4
- package/dist/src/public/tools/index.d.ts +1 -1
- package/dist/src/runtime/prompt/compose.js +1 -1
- package/dist/src/runtime/resolve-tool.js +1 -1
- package/dist/src/runtime/types.d.ts +2 -0
- package/dist/src/self-modification/agent.js +1 -1
- package/dist/src/self-modification/extension/instructions.d.ts +1 -3
- package/dist/src/self-modification/extension/instructions.js +4 -2
- package/dist/src/self-modification/filesystem.js +1 -1
- package/dist/src/setup/flows/chatgpt-auth.d.ts +14 -2
- package/dist/src/setup/flows/chatgpt-auth.js +1 -1
- package/dist/src/setup/flows/model.d.ts +3 -2
- package/dist/src/setup/flows/model.js +1 -1
- package/dist/src/setup/flows/provider.js +1 -1
- package/dist/src/setup/flows/registry.d.ts +4 -0
- package/dist/src/setup/flows/registry.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/shared/boundary-hook-error.d.ts +4 -0
- package/dist/src/shared/boundary-hook-error.js +1 -0
- package/dist/src/{execution/activity-text.d.ts → shared/presentation-text.d.ts} +1 -1
- package/dist/src/shared/presentation-text.js +1 -0
- 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/definition.js +1 -1
- package/dist/src/tools/durable-callbacks.d.ts +37 -9
- package/dist/src/tools/durable-callbacks.js +1 -1
- package/dist/src/tools/dynamic.d.ts +2 -1
- package/dist/src/tools/framework/agent-contract.js +1 -1
- package/dist/src/tools/provided/bash.js +1 -1
- package/dist/src/tools/provided/glob.js +1 -1
- package/dist/src/tools/provided/grep.js +1 -1
- package/dist/src/tools/provided/load-skill.js +1 -1
- package/dist/src/tools/provided/read-file.js +1 -1
- package/dist/src/tools/provided/web-fetch.js +1 -1
- package/dist/src/tools/provided/write-file.js +1 -1
- package/dist/src/tools/tool-label.d.ts +1 -0
- package/dist/src/tools/tool-label.js +1 -0
- package/dist/src/tracing/agent-action-instrumentation.js +1 -1
- package/dist/src/tracing/agent-approval-instrumentation.js +1 -1
- package/dist/src/tracing/agent-channel-delivery-instrumentation.js +1 -1
- package/dist/src/tracing/agent-otel-provider.js +2 -2
- package/dist/src/tracing/agent-otel-session-context.js +1 -1
- package/dist/src/tracing/agent-otel-usage.d.ts +10 -0
- package/dist/src/tracing/agent-otel-usage.js +1 -1
- package/dist/src/tracing/agent-span-naming.d.ts +1 -0
- package/dist/src/tracing/agent-span-naming.js +1 -0
- package/dist/src/tracing/agent-tool-instrumentation.js +1 -1
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/concepts/default-harness.md +6 -0
- package/docs/concepts/sessions-runs-and-streaming.md +4 -0
- package/docs/connections/overview.mdx +1 -1
- package/docs/guides/client/continuations.mdx +11 -0
- package/docs/guides/dynamic-capabilities.md +4 -4
- package/docs/guides/hooks.md +1 -1
- package/docs/guides/instrumentation.md +40 -1
- package/docs/guides/remote-agents.md +2 -0
- package/docs/instructions.mdx +1 -1
- package/docs/memory/overview.mdx +45 -13
- package/docs/reference/cli.md +6 -0
- package/docs/reference/telemetry.md +2 -2
- package/docs/reference/typescript-api.md +13 -9
- package/docs/sandbox.mdx +14 -2
- package/docs/subagents/index.mdx +8 -4
- package/docs/tools/overview.mdx +44 -2
- package/docs/tools/workflows.mdx +6 -0
- package/docs/tutorial/connect-a-warehouse.mdx +49 -26
- package/docs/tutorial/first-agent.mdx +20 -19
- package/docs/tutorial/guard-the-spend.mdx +4 -4
- package/docs/tutorial/how-it-runs.mdx +2 -3
- package/docs/tutorial/meta.json +2 -2
- package/docs/tutorial/query-sample-data.mdx +30 -19
- package/docs/tutorial/remember-definitions.mdx +5 -5
- package/docs/tutorial/run-analysis.mdx +155 -18
- package/docs/tutorial/ship-it.mdx +71 -81
- package/docs/tutorial/team-playbooks.mdx +10 -10
- package/package.json +1 -1
- package/dist/src/execution/activity-text.js +0 -1
- package/dist/src/internal/workflow-bundle/authored-workflow-modules.d.ts +0 -9
- package/dist/src/internal/workflow-bundle/authored-workflow-modules.js +0 -1
- package/dist/src/public/models/openai/chatgpt/codex-app-server.d.ts +0 -40
- package/dist/src/public/models/openai/chatgpt/codex-app-server.js +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Ship It"
|
|
3
|
-
description: "Part
|
|
3
|
+
description: "Part 8 of the Build an Agent tutorial. Put a web dashboard on the agent with useEveAgent, replace placeholderAuth, and deploy to Vercel."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
The analytics assistant runs
|
|
6
|
+
The analytics assistant runs in the TUI. Now add a web dashboard and deploy a private, single-user version on Vercel. This example protects the sample app with a username and password. It does not require another authentication service.
|
|
7
7
|
|
|
8
8
|
## Add the Web Chat app
|
|
9
9
|
|
|
@@ -24,121 +24,110 @@ const nextConfig: NextConfig = {};
|
|
|
24
24
|
export default withEve(nextConfig);
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
The dashboard talks to the built-in eve HTTP channel (`agent/channels/eve.ts`). On the browser side, `useEveAgent` handles session creation, streaming, and HITL. The scaffold renders its chat from `app/_components/agent-chat.tsx`, mounted by `app/page.tsx`. That component is fuller than you need to start, so replace its contents with this minimal version:
|
|
30
|
-
|
|
31
|
-
```tsx title="app/_components/agent-chat.tsx"
|
|
32
|
-
"use client";
|
|
33
|
-
|
|
34
|
-
import { useEveAgent } from "eve/react";
|
|
35
|
-
|
|
36
|
-
export function AgentChat() {
|
|
37
|
-
const agent = useEveAgent();
|
|
38
|
-
const isBusy = agent.status === "submitted" || agent.status === "streaming";
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<form
|
|
42
|
-
onSubmit={(event) => {
|
|
43
|
-
event.preventDefault();
|
|
44
|
-
const data = new FormData(event.currentTarget);
|
|
45
|
-
const message = String(data.get("q") ?? "").trim();
|
|
46
|
-
if (message) void agent.send(message);
|
|
47
|
-
}}
|
|
48
|
-
>
|
|
49
|
-
{agent.data.messages.map((message) => (
|
|
50
|
-
<article key={message.id}>
|
|
51
|
-
<header>{message.role}</header>
|
|
52
|
-
{message.parts.map((part, index) =>
|
|
53
|
-
part.type === "text" ? <p key={index}>{part.text}</p> : null,
|
|
54
|
-
)}
|
|
55
|
-
</article>
|
|
56
|
-
))}
|
|
57
|
-
<input name="q" disabled={isBusy} placeholder="Ask about the data…" />
|
|
58
|
-
<button type="submit" disabled={isBusy}>
|
|
59
|
-
Ask
|
|
60
|
-
</button>
|
|
61
|
-
</form>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
The generated `app/page.tsx` already imports and renders this `AgentChat` export, so no other wiring is needed:
|
|
27
|
+
## Use the generated chat
|
|
67
28
|
|
|
68
|
-
|
|
69
|
-
import { AgentChat } from "@/app/_components/agent-chat";
|
|
29
|
+
Keep the generated `app/_components/agent-chat.tsx` and `agent-message.tsx` components. `app/page.tsx` already renders the chat, and `useEveAgent` handles session creation and streaming.
|
|
70
30
|
|
|
71
|
-
|
|
72
|
-
return <AgentChat />;
|
|
73
|
-
}
|
|
74
|
-
```
|
|
31
|
+
The generated UI displays tool results and errors, authorization links, approval buttons, and question forms. The spend approval from [Guard the spend](./guard-the-spend) needs those controls to resume a waiting turn. A component that renders only text messages leaves those interactions unavailable.
|
|
75
32
|
|
|
76
|
-
|
|
33
|
+
Run `npm run dev`, open the local web URL printed by the server, and ask for an unfiltered revenue query. Confirm that the approval appears and that approving it lets the query finish. You can customize the UI after this flow works; see [Frontend](../guides/frontend/overview).
|
|
77
34
|
|
|
78
35
|
## Replace `placeholderAuth`
|
|
79
36
|
|
|
80
|
-
The scaffold's channel ships with `placeholderAuth()`, which
|
|
37
|
+
The scaffold's channel ships with `placeholderAuth()`, which rejects unauthenticated production requests. Replace it with a verifier that checks credentials on every request. Never return a fixed user without checking the request.
|
|
81
38
|
|
|
82
|
-
|
|
39
|
+
Create `agent/lib/auth.ts`. This uses eve's HTTP Basic verifier, which compares passwords in constant time. Missing environment variables, missing credentials, and wrong credentials all fail closed:
|
|
83
40
|
|
|
84
41
|
```ts title="agent/lib/auth.ts"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
42
|
+
import { verifyHttpBasic, withAuthChallenges } from "eve/channels/auth";
|
|
43
|
+
|
|
44
|
+
export const appAuth = withAuthChallenges(
|
|
45
|
+
(request: Request) => {
|
|
46
|
+
const username = process.env.ANALYTICS_USERNAME;
|
|
47
|
+
const password = process.env.ANALYTICS_PASSWORD;
|
|
48
|
+
if (!username || !password) return null;
|
|
49
|
+
|
|
50
|
+
if (request.headers.has("origin") && request.headers.get("sec-fetch-site") !== "same-origin")
|
|
51
|
+
return null;
|
|
52
|
+
|
|
53
|
+
const result = verifyHttpBasic(request.headers.get("authorization"), { username, password });
|
|
54
|
+
if (!result.ok) return null;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
...result.sessionAuth,
|
|
58
|
+
attributes: { team: "growth" },
|
|
59
|
+
issuer: "analytics-tutorial",
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
[{ scheme: "Basic", parameters: { realm: "analytics", charset: "UTF-8" } }],
|
|
63
|
+
);
|
|
94
64
|
```
|
|
95
65
|
|
|
96
|
-
|
|
66
|
+
Replace `agent/channels/eve.ts`, removing the earlier `devTeam` entry. The eve channel checks `appAuth` for session creation, messages, controls, and streams. The remaining helpers preserve authenticated Vercel CLI access and local development:
|
|
97
67
|
|
|
98
68
|
```ts title="agent/channels/eve.ts"
|
|
99
69
|
import { eveChannel } from "eve/channels/eve";
|
|
100
|
-
import { localDev, vercelOidc
|
|
101
|
-
import {
|
|
102
|
-
|
|
103
|
-
const appAuth: AuthFn<Request> = async (request) => {
|
|
104
|
-
const user = await authenticate(request); // your cookie/session/provider
|
|
105
|
-
if (!user) return null;
|
|
106
|
-
return {
|
|
107
|
-
attributes: { team: user.team }, // the claim Step 7's playbook reads
|
|
108
|
-
principalType: "user",
|
|
109
|
-
principalId: user.id,
|
|
110
|
-
authenticator: "app",
|
|
111
|
-
issuer: "analytics-dashboard",
|
|
112
|
-
};
|
|
113
|
-
};
|
|
70
|
+
import { localDev, vercelOidc } from "eve/channels/auth";
|
|
71
|
+
import { appAuth } from "../lib/auth";
|
|
114
72
|
|
|
115
73
|
export default eveChannel({
|
|
116
74
|
auth: [appAuth, vercelOidc(), localDev()],
|
|
117
75
|
});
|
|
118
76
|
```
|
|
119
77
|
|
|
120
|
-
|
|
78
|
+
The verified username becomes the user principal. The `growth` team selects the sample playbook from [Team playbooks](./team-playbooks). Keep these credentials private to one person. A shared password does not give each person a separate identity or isolated sessions. For a multi-user app, use a real session provider and enforce [session ownership](../guides/auth-and-route-protection#what-reaches-ctxsessionauth).
|
|
79
|
+
|
|
80
|
+
Add `proxy.ts` at the project root so opening the dashboard triggers the browser's native username/password prompt. It protects the generated UI routes; eve API routes keep their channel auth, so an OIDC-authenticated CLI request does not encounter a browser-only gate:
|
|
81
|
+
|
|
82
|
+
```ts title="proxy.ts"
|
|
83
|
+
import { routeAuth } from "eve/channels/auth";
|
|
84
|
+
import { NextResponse } from "next/server";
|
|
85
|
+
import { appAuth } from "./agent/lib/auth";
|
|
86
|
+
|
|
87
|
+
export async function proxy(request: Request) {
|
|
88
|
+
if (process.env.NODE_ENV === "development") return NextResponse.next();
|
|
89
|
+
const result = await routeAuth(request, appAuth);
|
|
90
|
+
return result instanceof Response ? result : NextResponse.next();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const config = { matcher: ["/", "/s/:path*"] };
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
After login, the browser sends the credentials on same-origin requests from the generated chat. When a browser sends an `Origin` header, the Basic verifier also requires [`Sec-Fetch-Site: same-origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site). Keep the auth module server-side: never put the password in a React component, a `NEXT_PUBLIC_` variable, or a client-side `useEveAgent` option. Use HTTPS for deployed HTTP Basic authentication.
|
|
121
97
|
|
|
122
98
|
## Deploy to Vercel
|
|
123
99
|
|
|
100
|
+
From `analytics-assistant/`, link a Vercel project and add a username and a long, unique password. These commands prompt for values without putting the password in shell history:
|
|
101
|
+
|
|
124
102
|
```bash
|
|
125
|
-
vercel
|
|
103
|
+
npx vercel@latest link
|
|
104
|
+
npx vercel@latest env add ANALYTICS_USERNAME preview
|
|
105
|
+
npx vercel@latest env add ANALYTICS_PASSWORD preview
|
|
126
106
|
```
|
|
127
107
|
|
|
128
|
-
|
|
108
|
+
Before deploying, configure the model credential your agent uses in the project's Preview environment; local `.env` values are not uploaded by deployment. If you used a local ChatGPT subscription, switch `agent/agent.ts` to an AI Gateway model and configure `AI_GATEWAY_API_KEY` for that model. Subscription credentials stay on your laptop. See [Deployment](../guides/deployment/overview) for model and runtime configuration.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx vercel@latest deploy
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Open the HTTPS preview URL and enter the configured username and password. Create a session and ask a sample-data question. In a private browser window, cancel the login prompt and verify the dashboard is denied. A `POST /eve/v1/session` request without credentials must also return `401`.
|
|
115
|
+
|
|
116
|
+
The authenticated web app and eve runtime share the same origin, and the sandbox runs on Vercel Sandbox. You can also smoke-test the deployment through the authenticated CLI:
|
|
129
117
|
|
|
130
118
|
```bash
|
|
131
119
|
npx eve dev https://your-analytics-app.vercel.app
|
|
132
120
|
```
|
|
133
121
|
|
|
134
|
-
|
|
122
|
+
For a production deployment, add the username, password, and model credential to the Production environment too, then run `npx vercel@latest deploy --prod`. Missing Basic credentials keep browser access closed.
|
|
123
|
+
|
|
124
|
+
The private assistant queries the sample data, runs analysis in a sandbox, charts the results, remembers definitions, loads the Growth playbook, and asks before an expensive query.
|
|
135
125
|
|
|
136
126
|
## What you learned
|
|
137
127
|
|
|
138
|
-
Across the
|
|
128
|
+
Across the eight steps you built and shipped one agent, and along the way you used:
|
|
139
129
|
|
|
140
130
|
- **Tools** to give the model typed actions (`run_sql`, `chart_series`, `define_metric`).
|
|
141
|
-
- **Connections** to reach a warehouse over an OAuth MCP, with per-user tokens eve resolves for you.
|
|
142
131
|
- **The sandbox** to compute and chart beyond SQL in an isolated `/workspace`.
|
|
143
132
|
- **State** (`defineState`) to remember the team's glossary across turns.
|
|
144
133
|
- **Dynamic skills** (`defineDynamic`) to load the right team playbook per caller.
|
|
@@ -148,6 +137,7 @@ Across the nine steps you built and shipped one agent, and along the way you use
|
|
|
148
137
|
|
|
149
138
|
## Next steps
|
|
150
139
|
|
|
140
|
+
- [Connect a warehouse](./connect-a-warehouse) when you have a data service to use in place of the sample dataset.
|
|
151
141
|
- [MCP connections](../connections/mcp) for tool allowlists and per-connection approval.
|
|
152
142
|
- [Sandbox](../sandbox) for backends, lifecycle, and network policy.
|
|
153
143
|
- [Dynamic capabilities](../guides/dynamic-capabilities) for schema-derived dynamic tools, a read-only analyst subagent, and model-authored report workflows on this same example.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Team Playbooks"
|
|
3
|
-
description: "Part
|
|
3
|
+
description: "Part 6 of the Build an Agent tutorial. Load the caller's team playbook with a dynamic skill keyed on the principal."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
The glossary from [Step
|
|
6
|
+
The glossary from [Step 5](./remember-definitions) is per-session. But your teams have standing analysis conventions for the analytics assistant (Growth groups revenue by date, Finance reconciles customer totals), and those shouldn't bleed across tenants. Load the right team's playbook for whoever is asking.
|
|
7
7
|
|
|
8
8
|
A skill is an on-demand procedure. The model pulls it in with `load_skill` only when a turn needs it. Make it dynamic and the skill gets decided at runtime instead of baked in. A `defineDynamic` resolver reads the session and returns a `defineSkill` (or nothing). Here you key that decision on the caller's identity in `ctx.session.auth`.
|
|
9
9
|
|
|
@@ -18,14 +18,14 @@ const PLAYBOOKS: Record<string, { title: string; markdown: string }> = {
|
|
|
18
18
|
growth: {
|
|
19
19
|
title: "Growth analysis playbook",
|
|
20
20
|
markdown:
|
|
21
|
-
"When analyzing
|
|
22
|
-
"
|
|
21
|
+
"When analyzing sample orders, group revenue by order date, report dollars, " +
|
|
22
|
+
"and compare customers by plan.",
|
|
23
23
|
},
|
|
24
24
|
finance: {
|
|
25
25
|
title: "Finance analysis playbook",
|
|
26
26
|
markdown:
|
|
27
|
-
"
|
|
28
|
-
"
|
|
27
|
+
"Reconcile total order revenue against the daily and customer totals. " +
|
|
28
|
+
"Report dollars and label this as gross order revenue; the dataset has no refunds.",
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
31
|
|
|
@@ -52,14 +52,14 @@ export default defineDynamic({
|
|
|
52
52
|
|
|
53
53
|
## See it route
|
|
54
54
|
|
|
55
|
-
The team comes from authenticated claims, which the auth layer stamps on in [Step
|
|
55
|
+
The team comes from authenticated claims, which the auth layer stamps on in [Step 8](./ship-it). Until then `ctx.session.auth.current` has no `team`, so the resolver returns `null` and no playbook loads. To verify routing now, stamp a team in local dev. Add a dev-only entry to `agent/channels/eve.ts` ahead of `localDev()`, and remove it before Step 8 wires real auth:
|
|
56
56
|
|
|
57
57
|
```ts title="agent/channels/eve.ts"
|
|
58
58
|
import { eveChannel } from "eve/channels/eve";
|
|
59
59
|
import { localDev, placeholderAuth, vercelOidc, type AuthFn } from "eve/channels/auth";
|
|
60
60
|
|
|
61
|
-
// Dev-only: stamp a team so Step
|
|
62
|
-
// Remove before Step
|
|
61
|
+
// Dev-only: stamp a team so Step 6's playbook resolver has something to read.
|
|
62
|
+
// Remove before Step 8.
|
|
63
63
|
const devTeam: AuthFn<Request> = () =>
|
|
64
64
|
process.env.NODE_ENV === "production"
|
|
65
65
|
? null
|
|
@@ -75,7 +75,7 @@ export default eveChannel({
|
|
|
75
75
|
});
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Restart with `npm run dev` and ask "
|
|
78
|
+
Restart with `npm run dev` and ask "Summarize May 2026 revenue using our team's playbook." The model calls `load_skill`, queries the sample data, and applies Growth's daily and plan groupings. Switch `team` to `"finance"`, restart, and start a new session before asking again. The resolver runs at `session.started`, so changing the team does not replace the playbook in an existing session. The same question in the new session uses Finance's reconciliation rules.
|
|
79
79
|
|
|
80
80
|
Because the team comes from authenticated claims, not from the message, one tenant can't borrow another's playbook through the message content.
|
|
81
81
|
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const MAX_ACTIVITY_TEXT_LENGTH=500;function normalizeActivityText(e){return e.replace(/\p{Cc}/gu,` `).replace(/\s+/g,` `).trim().slice(0,500)}export{MAX_ACTIVITY_TEXT_LENGTH,normalizeActivityText};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export interface AuthoredWorkflowModules {
|
|
2
|
-
readonly directiveModules: readonly string[];
|
|
3
|
-
readonly workflowModules: readonly string[];
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* Scans the whole application root, as the SDK's bundler integrations do, so a
|
|
7
|
-
* step helper can live wherever the tool imports it from.
|
|
8
|
-
*/
|
|
9
|
-
export declare function discoverAuthoredWorkflowModules(appRoot: string): Promise<AuthoredWorkflowModules>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{prepareAuthoredWorkflowDirectives}from"./authored-workflow-directives.js";import{isWorkflowSourceFile}from"./builder-support.js";import{isAuthoredApplicationModule,isAuthoredApplicationRoot}from"./workflow-builders.js";import{readFile,readdir}from"node:fs/promises";import{join}from"node:path";import{isGeneratedWorkflowFile}from"#compiled/@workflow/builders/index.js";const IGNORED_DIRECTORIES=new Set([`.cache`,`.eve`,`.git`,`.next`,`.nitro`,`.nuxt`,`.output`,`.pnpm-store`,`.svelte-kit`,`.swc`,`.turbo`,`.vercel`,`.workflow-data`,`.workflow-vitest`,`.yarn`,`coverage`,`dist`,`node_modules`]);function isIgnoredDirectory(e){return IGNORED_DIRECTORIES.has(e)||e.startsWith(`.output.`)}async function discoverAuthoredWorkflowModules(e){let t=[],n=[];if(!isAuthoredApplicationRoot(e))return{directiveModules:t,workflowModules:n};let r=await collectSourceFiles(e);for(let i of r.sort()){if(!isAuthoredApplicationModule(i,e)||isGeneratedWorkflowFile(i))continue;let r=await readFile(i,`utf8`);if(!r.includes(`use workflow`)&&!r.includes(`use step`))continue;let a=await prepareAuthoredWorkflowDirectives({filePath:i,source:r});a.hasDirectives&&(t.push(i),a.hasWorkflowDirective&&n.push(i))}return{directiveModules:t,workflowModules:n}}async function collectSourceFiles(e){let t=[];async function visit(e){let n;try{n=await readdir(e,{withFileTypes:!0})}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return;throw e}for(let r of n){let n=join(e,r.name);if(r.isDirectory()){isIgnoredDirectory(r.name)||await visit(n);continue}r.isFile()&&isWorkflowSourceFile(r.name)&&t.push(n)}}return await visit(e),t}export{discoverAuthoredWorkflowModules};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
-
export interface CodexAuthStatus {
|
|
3
|
-
readonly authMethod?: string;
|
|
4
|
-
readonly authToken?: string;
|
|
5
|
-
readonly requiresOpenaiAuth?: boolean;
|
|
6
|
-
}
|
|
7
|
-
export interface CodexAppServer {
|
|
8
|
-
getAuthStatus(input: {
|
|
9
|
-
readonly refreshToken: boolean;
|
|
10
|
-
}): Promise<CodexAuthStatus>;
|
|
11
|
-
restart?(): void;
|
|
12
|
-
}
|
|
13
|
-
export interface CodexAppServerProcess {
|
|
14
|
-
readonly stderr: ChildProcessWithoutNullStreams["stderr"];
|
|
15
|
-
readonly stdin: ChildProcessWithoutNullStreams["stdin"];
|
|
16
|
-
readonly stdout: ChildProcessWithoutNullStreams["stdout"];
|
|
17
|
-
kill(): boolean;
|
|
18
|
-
once(event: "error", listener: (error: Error) => void): this;
|
|
19
|
-
once(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
|
|
20
|
-
unref(): void;
|
|
21
|
-
}
|
|
22
|
-
type SpawnCodexAppServer = (command: string, args: readonly string[], options: {
|
|
23
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
24
|
-
stdio: ["pipe", "pipe", "pipe"];
|
|
25
|
-
}) => CodexAppServerProcess;
|
|
26
|
-
export interface CodexAppServerOptions {
|
|
27
|
-
readonly command?: string;
|
|
28
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
29
|
-
readonly spawnProcess?: SpawnCodexAppServer;
|
|
30
|
-
}
|
|
31
|
-
/** Minimal authenticated subset of Codex's JSONL app-server protocol. */
|
|
32
|
-
export declare class CodexAppServerClient implements CodexAppServer {
|
|
33
|
-
#private;
|
|
34
|
-
constructor(options?: CodexAppServerOptions);
|
|
35
|
-
getAuthStatus(input: {
|
|
36
|
-
readonly refreshToken: boolean;
|
|
37
|
-
}): Promise<CodexAuthStatus>;
|
|
38
|
-
restart(): void;
|
|
39
|
-
}
|
|
40
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{spawn}from"node:child_process";import{isObject}from"#shared/guards.js";import{createInterface}from"node:readline";var CodexAppServerClient=class{#e;#t;#n;#r;constructor(e={}){this.#e=e.command??`codex`,this.#t=e.env,this.#n=e.spawnProcess??spawnCodexAppServer}async getAuthStatus(e){let t=await this.#i();try{return await t.getAuthStatus(e)}catch(e){throw t.exited&&(this.#r=void 0),e}}restart(){let e=this.#r;this.#r=void 0,e?.then(e=>e.close()).catch(()=>void 0)}#i(){return this.#r===void 0&&(this.#r=CodexAppServerConnection.start({command:this.#e,env:this.#t,spawnProcess:this.#n}).catch(e=>{throw this.#r=void 0,e})),this.#r}},CodexAppServerConnection=class CodexAppServerConnection{#e;#t;#n=new Map;#r=1;#i=!1;#a;constructor(e){this.#e=e,e.unref(),unrefStream(e.stdin),unrefStream(e.stdout),unrefStream(e.stderr),e.stderr.resume(),this.#t=createInterface({input:e.stdout}),this.#t.on(`line`,e=>this.#l(e)),e.once(`error`,e=>this.#u(e)),e.once(`exit`,(e,t)=>{let n=t===null?`code ${e??`unknown`}`:`signal ${t}`;this.#u(Error(`Codex app-server exited with ${n}.`))})}get exited(){return this.#i}close(){this.#e.kill(),this.#u(Error(`Codex app-server connection was restarted.`))}static async start(e){let t;try{t=e.spawnProcess(e.command,[`app-server`,`--stdio`],{env:e.env,stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){throw codexUnavailableError(e)}let n=new CodexAppServerConnection(t);try{return await n.#o(`initialize`,{capabilities:null,clientInfo:{name:`eve`,title:`eve`,version:`0.35.0`}}),n.#s(`initialized`,{}),n}catch(e){throw t.kill(),e}}async getAuthStatus(e){let t=await this.#o(`getAuthStatus`,{includeToken:!0,refreshToken:e.refreshToken});if(!isObject(t))throw Error(`Codex app-server returned an invalid authentication response.`);return{...typeof t.authMethod==`string`&&{authMethod:t.authMethod},...typeof t.authToken==`string`&&{authToken:t.authToken},...typeof t.requiresOpenaiAuth==`boolean`&&{requiresOpenaiAuth:t.requiresOpenaiAuth}}}#o(e,t){if(this.#i)return Promise.reject(this.#a??Error(`Codex app-server is not running.`));let n=this.#r++,r=new Promise((e,t)=>{this.#n.set(n,{reject:t,resolve:e})});return this.#c({id:n,method:e,params:t}),r}#s(e,t){this.#c({method:e,params:t})}#c(e){this.#e.stdin.write(`${JSON.stringify(e)}\n`)}#l(e){let t;try{t=JSON.parse(e)}catch{return}if(typeof t.id!=`number`)return;let n=this.#n.get(t.id);if(n!==void 0){if(this.#n.delete(t.id),t.error!==void 0){n.reject(Error(t.error.message??`Codex app-server request failed (${t.error.code}).`));return}n.resolve(t.result)}}#u(e){if(!this.#i){this.#i=!0,this.#a=codexUnavailableError(e),this.#t.close();for(let e of this.#n.values())e.reject(this.#a);this.#n.clear()}}};function spawnCodexAppServer(e,t,n){return spawn(e,[...t],n)}function unrefStream(e){`unref`in e&&typeof e.unref==`function`&&e.unref()}function codexUnavailableError(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return Error("ChatGPT subscription authentication requires the Codex CLI. Install or upgrade `codex`, then run `codex login`.");let t=e instanceof Error?e.message:String(e);return Error(`Codex app-server is unavailable: ${t}`)}export{CodexAppServerClient};
|