experimental-ash 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/docs/public/advanced/execution-model-and-security.md +319 -0
  3. package/dist/docs/public/advanced/meta.json +1 -0
  4. package/dist/docs/public/tools.mdx +51 -0
  5. package/dist/skills/v0-ash-onboarding/SKILL.md +308 -0
  6. package/dist/src/cli/dev/tui/markdown.js +1 -1
  7. package/dist/src/compiled/.vendor-stamp.json +2 -2
  8. package/dist/src/compiled/experimental-ai-sdk-code-mode/approval-continuation.d.ts +71 -0
  9. package/dist/src/compiled/experimental-ai-sdk-code-mode/approval.d.ts +32 -0
  10. package/dist/src/compiled/experimental-ai-sdk-code-mode/code-mode-tool.d.ts +16 -0
  11. package/dist/src/compiled/experimental-ai-sdk-code-mode/continuation-capability.d.ts +33 -0
  12. package/dist/src/compiled/experimental-ai-sdk-code-mode/errors.d.ts +114 -0
  13. package/dist/src/compiled/experimental-ai-sdk-code-mode/fetch-policy.d.ts +21 -0
  14. package/dist/src/compiled/experimental-ai-sdk-code-mode/host-interrupt.d.ts +25 -0
  15. package/dist/src/compiled/experimental-ai-sdk-code-mode/index.d.ts +11 -144
  16. package/dist/src/compiled/experimental-ai-sdk-code-mode/index.js +10 -73
  17. package/dist/src/compiled/experimental-ai-sdk-code-mode/interrupt-continuation.d.ts +32 -0
  18. package/dist/src/compiled/experimental-ai-sdk-code-mode/options.d.ts +3 -0
  19. package/dist/src/compiled/experimental-ai-sdk-code-mode/run-code-mode.d.ts +12 -0
  20. package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/guest-sources.d.ts +3 -0
  21. package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/manager.d.ts +22 -0
  22. package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/max-workers.d.ts +20 -0
  23. package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/protocol.d.ts +49 -0
  24. package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/worker-source.d.ts +11 -0
  25. package/dist/src/compiled/experimental-ai-sdk-code-mode/serialization.d.ts +5 -0
  26. package/dist/src/compiled/experimental-ai-sdk-code-mode/source-cache.d.ts +11 -0
  27. package/dist/src/compiled/experimental-ai-sdk-code-mode/telemetry.d.ts +20 -0
  28. package/dist/src/compiled/experimental-ai-sdk-code-mode/tool-invocation.d.ts +24 -0
  29. package/dist/src/compiled/experimental-ai-sdk-code-mode/tool-prompt.d.ts +3 -0
  30. package/dist/src/compiled/experimental-ai-sdk-code-mode/types.d.ts +802 -0
  31. package/dist/src/execution/node-step.js +1 -1
  32. package/dist/src/execution/tool-auth.d.ts +42 -0
  33. package/dist/src/execution/tool-auth.js +1 -0
  34. package/dist/src/harness/action-result-helpers.d.ts +17 -0
  35. package/dist/src/harness/action-result-helpers.js +1 -1
  36. package/dist/src/harness/code-mode-interrupt-state.d.ts +26 -0
  37. package/dist/src/harness/code-mode-interrupt-state.js +1 -0
  38. package/dist/src/harness/code-mode-lifecycle.js +1 -1
  39. package/dist/src/harness/code-mode.js +1 -1
  40. package/dist/src/harness/emission.js +1 -1
  41. package/dist/src/harness/tool-loop.js +1 -1
  42. package/dist/src/internal/application/package.js +1 -1
  43. package/dist/src/internal/authored-definition/schema-backed.js +1 -1
  44. package/dist/src/internal/node-esm-compat-banner.d.ts +11 -1
  45. package/dist/src/internal/node-esm-compat-banner.js +3 -1
  46. package/dist/src/packages/ash-scaffold/src/channels.js +2 -2
  47. package/dist/src/packages/ash-scaffold/src/steps/run-add-to-agent.js +1 -1
  48. package/dist/src/packages/ash-scaffold/src/web-template.js +2 -14
  49. package/dist/src/public/channels/slack/inbound.d.ts +2 -0
  50. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  51. package/dist/src/public/channels/slack/utils.d.ts +1 -0
  52. package/dist/src/public/channels/slack/utils.js +1 -0
  53. package/dist/src/public/connections/index.js +1 -1
  54. package/dist/src/public/definitions/tool.d.ts +61 -1
  55. package/dist/src/public/definitions/tool.js +1 -1
  56. package/dist/src/public/next/server.js +1 -1
  57. package/dist/src/runtime/connections/mcp-client.js +1 -1
  58. package/dist/src/runtime/connections/scoped-authorization.d.ts +61 -0
  59. package/dist/src/runtime/connections/scoped-authorization.js +1 -0
  60. package/dist/src/runtime/framework-tools/connection-search-dynamic.js +1 -1
  61. package/dist/src/runtime/resolve-tool.js +1 -1
  62. package/dist/src/runtime/types.d.ts +10 -0
  63. package/package.json +2 -2
  64. package/dist/src/harness/code-mode-approval.d.ts +0 -22
  65. package/dist/src/harness/code-mode-approval.js +0 -1
  66. package/dist/src/harness/code-mode-connection-auth-state.d.ts +0 -15
  67. package/dist/src/harness/code-mode-connection-auth-state.js +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.59.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c0f795d: Unify code mode's host-interrupt handling and remove Ash's per-callback context shims. `experimental-ai-sdk-code-mode` now re-enters the originating invocation's async context at the worker bridge for every host callback, so the code-mode host-tool wrapper and lifecycle projection are context-transparent (no more pinning `execute`, `needsApproval`, or `emit` to a build-time context). Nested-tool approval and connection auth now ride one generic `CodeModeInterrupt` park/resume path instead of two bespoke ones, so any durable interrupt kind resumes through a single seam.
8
+ - 77aeeee: Add per-tool authorization. `defineTool` now accepts an `auth` field (the same shapes as connection auth: `connect("...")`, a custom interactive definition, or `{ getToken }`), and the tool's `execute` context gains `ctx.getToken()` and `ctx.requireAuth()`. A token cache miss or a thrown `ConnectionAuthorizationRequiredError` suspends the turn, drives the OAuth consent flow on a tool-scoped callback URL, and re-runs the tool after the user authorizes.
9
+
10
+ ### Patch Changes
11
+
12
+ - 651b295: Constrain the Next.js dev-server origin parser to loopback URLs so dependency metadata URLs cannot be written to the Ash dev-server registry.
13
+ - c0f795d: Project code-mode nested tool results through the same Ash-owned `action.result` helper as native tool calls. The code-mode lifecycle no longer uses a separate strict-JSON coercion with an error fallback; both paths now funnel through one `createRuntimeToolResultFromValue` helper, so the raw-output (never `toModelOutput`) decision is made once and code-mode nested results carry their raw structured output exactly like native results.
14
+ - b932acd: Fix the dev TUI mangling URLs that contain underscores. The terminal markdown renderer treated `_…_` as italics and stripped the underscores, corrupting Vercel Connect authorization URLs (`sca_…`) and hook callback paths shown in the authorization panel. URLs are now shielded from inline emphasis and render verbatim.
15
+
16
+ ## 0.58.1
17
+
18
+ ### Patch Changes
19
+
20
+ - 7362a65: Keep Anthropic provider-executed server-tool results out of the local approval-resume repair so `srvtoolu_*` results stay provider-owned instead of replaying as generic tool results.
21
+ - f9ba08b: Provide sourcemaps when Ash injects the Node ESM compatibility banner into authored module bundles, suppressing Rolldown sourcemap warnings during agent startup.
22
+ - cac284c: Dedup inbound Slack events by `event_id` within a process so retried webhook deliveries no longer trigger the agent more than once.
23
+
3
24
  ## 0.58.0
4
25
 
5
26
  ### Minor Changes
@@ -0,0 +1,319 @@
1
+ ---
2
+ title: "Execution Model & Security"
3
+ description: "Where your code runs, what has access to what, and how secrets flow through the system."
4
+ ---
5
+
6
+ Ash agents run across two execution contexts with a trust boundary between them.
7
+
8
+ ## Execution Contexts
9
+
10
+ Your agent has two execution contexts: the **app runtime** and the **sandbox**.
11
+
12
+ The **app runtime** is where your agent code runs — tool implementations, model calls, MCP
13
+ connections, state management, observability, and durable execution. It has `process.env`, your
14
+ secrets, and full Node.js. On Vercel, this runs inside
15
+ [Vercel Functions](https://vercel.com/docs/functions).
16
+
17
+ The **sandbox** is an isolated environment. The model runs shell commands here via the built-in
18
+ `bash`, `read_file`, and `write_file` tools. It has its own filesystem (`/workspace`) but no access
19
+ to `process.env`, secrets, or the app runtime. On Vercel, each sandbox runs in a
20
+ [Vercel Sandbox](https://vercel.com/docs/sandbox) microVM with hardware-level isolation. See
21
+ [Sandboxes](../sandbox.md) for the full API.
22
+
23
+ | | App Runtime | Sandbox |
24
+ | ----------------------- | ------------ | --------------------- |
25
+ | `process.env` / secrets | Yes | No |
26
+ | Node.js / your code | Yes | No |
27
+ | Network | Unrestricted | Controlled by policy |
28
+ | Filesystem | App's own | Isolated `/workspace` |
29
+
30
+ Everything except sandbox shell commands runs in the app runtime.
31
+
32
+ ## Durable Execution
33
+
34
+ Agent turns run inside [Vercel Workflow](https://vercel.com/docs/workflow):
35
+
36
+ - If the app runtime crashes mid-turn, Workflow resumes from the last step boundary.
37
+ - Durable state is serialized at step boundaries, so sessions survive across invocations.
38
+ - A session can span many requests over days or weeks without losing context.
39
+
40
+ Ash manages the Workflow lifecycle — you don't configure it directly. Sessions are durable by
41
+ default.
42
+
43
+ ## The Agent Loop
44
+
45
+ Each turn follows the same cycle: model call, tool calls, results, another model call, until the
46
+ model produces a final response.
47
+
48
+ ```mermaid
49
+ sequenceDiagram
50
+ participant M as Model
51
+ participant A as App
52
+ participant S as Sandbox
53
+ M->>A: your tool
54
+ A->>A: execute
55
+ A->>M: result
56
+ M->>A: bash
57
+ A->>S: proxy
58
+ S->>A: stdout
59
+ A->>M: result
60
+ ```
61
+
62
+ All tools — including the built-in `bash`, `read_file`, and `write_file` — run in the app runtime.
63
+ The built-in tools proxy requests to the sandbox, where commands execute and files are read and
64
+ written, then return results back to the model. The model sees tool definitions and results — never
65
+ your secrets.
66
+
67
+ ## Data Flow Examples
68
+
69
+ ### `write_file` — app runtime proxies into sandbox
70
+
71
+ The model writes a file. The tool runs in the app runtime, which authenticates to the sandbox
72
+ automatically via Vercel OIDC and proxies the write.
73
+
74
+ ```mermaid
75
+ sequenceDiagram
76
+ participant M as Model
77
+ participant A as App
78
+ participant S as Sandbox
79
+
80
+ M->>A: write_file
81
+ A->>A: validate path
82
+ A->>S: write via OIDC
83
+ S->>S: /workspace
84
+ S->>A: ok
85
+ A->>M: result
86
+ ```
87
+
88
+ ### Custom tool — runs in the app runtime
89
+
90
+ Custom tools run in the app runtime as regular JavaScript. They can call external APIs using secrets
91
+ from `process.env`, interact with the sandbox via `ctx.getSandbox()`, or both.
92
+
93
+ ```ts
94
+ // agent/tools/get_deployments.ts
95
+ import { defineTool } from "experimental-ash/tools";
96
+ import { z } from "zod";
97
+
98
+ export default defineTool({
99
+ description: "List recent deployments for a project.",
100
+ inputSchema: z.object({
101
+ projectId: z.string(),
102
+ }),
103
+ async execute(input) {
104
+ const res = await fetch(`https://api.vercel.com/v6/deployments?projectId=${input.projectId}`, {
105
+ headers: { authorization: `Bearer ${process.env.VERCEL_API_TOKEN}` },
106
+ });
107
+ const { deployments } = await res.json();
108
+ return deployments.map((d: any) => ({
109
+ id: d.uid,
110
+ url: d.url,
111
+ state: d.state,
112
+ }));
113
+ },
114
+ });
115
+ ```
116
+
117
+ ```mermaid
118
+ sequenceDiagram
119
+ participant M as Model
120
+ participant A as App
121
+ participant API as Vercel API
122
+
123
+ M->>A: get_deployments
124
+ A->>A: read env token
125
+ A->>API: GET with Bearer
126
+ API->>A: deployments
127
+ A->>M: results
128
+
129
+ note over M,A: Model never sees the token
130
+ ```
131
+
132
+ This is the typical pattern for most integrations. Your tool has full access to secrets and returns
133
+ only what the model needs.
134
+
135
+ ### MCP connection — framework manages auth
136
+
137
+ [MCP connections](../connections.mdx) expose third-party tools without writing executor code. The
138
+ framework handles discovery and injects authentication headers automatically.
139
+
140
+ ```ts
141
+ // agent/connections/linear.ts
142
+ import { defineMcpClientConnection } from "experimental-ash/connections";
143
+
144
+ export default defineMcpClientConnection({
145
+ url: "https://mcp.linear.app/sse",
146
+ description: "Linear workspace — issues, projects, cycles, and comments.",
147
+ auth: {
148
+ getToken: async () => ({ token: process.env.LINEAR_API_TOKEN! }),
149
+ },
150
+ });
151
+ ```
152
+
153
+ ```mermaid
154
+ sequenceDiagram
155
+ participant M as Model
156
+ participant A as App
157
+ participant MCP as MCP Server
158
+
159
+ M->>A: connection_search
160
+ A->>A: resolve token
161
+ A->>MCP: listTools + Bearer
162
+ MCP->>A: available tools
163
+ A->>M: tools
164
+
165
+ M->>A: list_issues
166
+ A->>MCP: tools/call + Bearer
167
+ MCP->>A: issues
168
+ A->>M: issues
169
+
170
+ note over A: Token cached per-step
171
+ note over M,A: Model never sees the token
172
+ ```
173
+
174
+ The model discovers tools via `connection_search` and calls them by name. The framework resolves the
175
+ token from `getToken()`, caches it per-step (never serialized to durable state), and injects it into
176
+ every MCP request.
177
+
178
+ ### OAuth via Vercel Connect — interactive auth
179
+
180
+ When a connection needs user authorization, Ash suspends the turn and orchestrates an OAuth flow via
181
+ [Vercel Connect](https://vercel.com/docs/connect).
182
+
183
+ ```ts
184
+ // agent/connections/linear.ts
185
+ import { connect } from "@vercel/connect/ash";
186
+ import { defineMcpClientConnection } from "experimental-ash/connections";
187
+
188
+ export default defineMcpClientConnection({
189
+ url: "https://mcp.linear.app/sse",
190
+ description: "Linear workspace — issues, projects, cycles, and comments.",
191
+ auth: connect("linear"),
192
+ });
193
+ ```
194
+
195
+ ```mermaid
196
+ sequenceDiagram
197
+ participant M as Model
198
+ participant A as App
199
+ participant U as User
200
+ participant O as OAuth
201
+ participant MCP as MCP Server
202
+
203
+ M->>A: list_issues
204
+ A->>MCP: tools/call
205
+ MCP->>A: 401
206
+
207
+ A->>A: start auth
208
+ A->>M: auth required
209
+ note over M,A: Turn suspends
210
+
211
+ U->>O: grant access
212
+ O->>A: callback
213
+ A->>A: complete auth
214
+
215
+ note over M,A: Turn resumes
216
+
217
+ A->>MCP: tools/call + Bearer
218
+ MCP->>A: issues
219
+ A->>M: issues
220
+ ```
221
+
222
+ The first call returns 401. Ash starts the OAuth flow and suspends the turn. After the user
223
+ authorizes, the OAuth provider redirects back, Ash caches the token, and the tool re-executes. The
224
+ token is never serialized to durable state and never visible to the model.
225
+
226
+ ## Sandbox Security
227
+
228
+ The sandbox is isolated by default:
229
+
230
+ | Property | Behavior |
231
+ | -------------- | ---------------------------------------------------------------------------------------- |
232
+ | **Filesystem** | Isolated `/workspace`. Seeded from `agent/sandbox/workspace/`, persists across requests. |
233
+ | **Secrets** | No `process.env`, no app runtime access. Secrets cannot leak to the sandbox. |
234
+ | **Network** | Controlled by policy: `"allow-all"` (default), `"deny-all"`, or per-domain allow-list. |
235
+
236
+ When the model needs authenticated network access from inside the sandbox — for example,
237
+ `git clone` on a private repo — and you can't route the request through a tool or connection, use
238
+ credential brokering.
239
+
240
+ ### Credential Brokering
241
+
242
+ When using [Vercel Sandbox](https://vercel.com/docs/sandbox) as your sandbox backend, credential
243
+ brokering injects auth headers at the sandbox's network firewall. The secret stays in the app
244
+ runtime — the sandbox process never sees it.
245
+
246
+ ```mermaid
247
+ sequenceDiagram
248
+ participant M as Model
249
+ participant A as App
250
+ participant S as Sandbox
251
+ participant GH as github.com
252
+
253
+ M->>A: bash git clone
254
+ A->>S: run command
255
+ S->>S: egress to github.com
256
+ note over S: Firewall injects auth header
257
+ S->>GH: request + auth
258
+ GH->>S: response
259
+ S->>A: stdout
260
+ A->>M: stdout
261
+ ```
262
+
263
+ Configure it in the sandbox `onSession` hook. The `transform` injects headers for matching domains
264
+ at the firewall — the sandbox process only sees the response, never the credential:
265
+
266
+ ```ts
267
+ // agent/sandbox/sandbox.ts
268
+ import { defineSandbox } from "experimental-ash/sandbox";
269
+
270
+ export default defineSandbox({
271
+ async onSession({ use }) {
272
+ const ghToken = process.env.GITHUB_TOKEN;
273
+ await use({
274
+ networkPolicy: {
275
+ allow: {
276
+ "github.com": [
277
+ {
278
+ transform: [
279
+ { headers: { authorization: `Basic ${btoa(`x-access-token:${ghToken}`)}` } },
280
+ ],
281
+ },
282
+ ],
283
+ "*": [],
284
+ },
285
+ },
286
+ });
287
+ },
288
+ });
289
+ ```
290
+
291
+ The `"*": []` entry keeps general egress open. The sandbox can `git clone` private repos and `curl`
292
+ the GitHub API without ever seeing the token.
293
+
294
+ When the credential is resolved mid-turn, use `setNetworkPolicy` on the live sandbox handle:
295
+
296
+ ```ts
297
+ const sandbox = await ctx.getSandbox();
298
+ await sandbox.setNetworkPolicy({
299
+ allow: {
300
+ "github.com": [
301
+ {
302
+ transform: [{ headers: { authorization: `Basic ${btoa(`x-access-token:${token}`)}` } }],
303
+ },
304
+ ],
305
+ "*": [],
306
+ },
307
+ });
308
+ ```
309
+
310
+ See [Sandboxes — Network Policies](../sandbox.md#network-policies) for the full Ash policy API and
311
+ [Vercel Sandbox — Credential Brokering](https://vercel.com/docs/sandbox/concepts/firewall#credentials-brokering)
312
+ for the underlying platform mechanism.
313
+
314
+ ## What To Read Next
315
+
316
+ - [Tools](../tools.mdx) — defining typed tools that run in the app runtime.
317
+ - [Connections](../connections.mdx) — MCP server connections with static tokens or OAuth.
318
+ - [Sandboxes](../sandbox.md) — lifecycle, backends, and network policies.
319
+ - [Sessions And Streaming](./runs-and-streaming.md) — the HTTP API and session lifecycle.
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "title": "Advanced",
3
3
  "pages": [
4
+ "execution-model-and-security",
4
5
  "project-layout",
5
6
  "context-control",
6
7
  "hooks",
@@ -101,6 +101,57 @@ The `ctx` parameter passed to `execute` is the primary way to access runtime sta
101
101
 
102
102
  These are available inside `execute` and other active authored runtime execution contexts.
103
103
 
104
+ ## Tool Authorization
105
+
106
+ A tool can declare its own authorization strategy with the `auth` field. Use it when the tool calls
107
+ a service behind OAuth (for example an Okta-protected API) and you want Ash to drive the sign-in
108
+ flow, cache the resulting token, and re-run the tool after the user authorizes — without standing up
109
+ a separate connection.
110
+
111
+ `auth` accepts the same shapes as a connection's `auth`: `connect("...")` from `@vercel/connect/ash`
112
+ for Vercel Connect-managed OAuth, a custom interactive definition, or a plain `{ getToken }` object
113
+ for static or pre-provisioned credentials.
114
+
115
+ `agent/tools/list_okta_groups.ts`
116
+
117
+ ```ts
118
+ import { defineTool } from "experimental-ash/tools";
119
+ import { connect } from "@vercel/connect/ash";
120
+ import { z } from "zod";
121
+
122
+ export default defineTool({
123
+ description: "List the caller's Okta groups.",
124
+ inputSchema: z.object({}),
125
+ auth: connect("okta"),
126
+ async execute(_input, ctx) {
127
+ // Resolves the per-user token. If the user has not signed in, this
128
+ // suspends the turn, the channel shows a "Sign in" affordance, and
129
+ // the tool re-runs after the OAuth callback completes.
130
+ const { token } = await ctx.getToken();
131
+ const res = await fetch("https://api.okta-proxy.internal/groups", {
132
+ headers: { authorization: `Bearer ${token}` },
133
+ });
134
+ return await res.json();
135
+ },
136
+ });
137
+ ```
138
+
139
+ When the tool declares `auth`, the `ctx` passed to `execute` gains two accessors:
140
+
141
+ - `ctx.getToken()` resolves the bearer for the declared strategy, consulting the per-step token cache
142
+ before invoking the authored `getToken`. For interactive strategies a cache miss suspends the turn
143
+ on a framework-owned callback URL and re-runs the tool after sign-in.
144
+ - `ctx.requireAuth()` explicitly throws `ConnectionAuthorizationRequiredError` to gate the tool on
145
+ authorization without resolving a token first. The runtime converts it into the same consent
146
+ prompt.
147
+
148
+ Throwing `ConnectionAuthorizationRequiredError` anywhere inside `execute` — directly, via
149
+ `ctx.requireAuth()`, or implicitly from `ctx.getToken()` — triggers the consent flow. The
150
+ authorization state (token cache and callback URL) is keyed by the tool's name, the same way
151
+ connection auth is keyed by the connection name.
152
+
153
+ Calling `ctx.getToken()` or `ctx.requireAuth()` on a tool that does **not** declare `auth` throws.
154
+
104
155
  ## When A Tool Runs
105
156
 
106
157
  Ash does not execute authored tools during discovery.