experimental-a2 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +1 -1
- package/dist/{actor-C7b-1arn.d.ts → actor-CqibLgjQ.d.ts} +15 -7
- package/dist/actor-CqibLgjQ.d.ts.map +1 -0
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-client.js +2 -2
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor-react.js +5 -5
- package/dist/{actor-shared-t7w8xIKP.js → actor-shared-DFET5q16.js} +3 -3
- package/dist/actor-shared-DFET5q16.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +54 -43
- package/dist/actor.js.map +1 -1
- package/dist/{ai-Cz3hqVhk.d.ts → ai-DAu77CDr.d.ts} +8 -8
- package/dist/{ai-Cz3hqVhk.d.ts.map → ai-DAu77CDr.d.ts.map} +1 -1
- package/dist/{ai-mMiWul1T.js → ai-EEfKyc-z.js} +9 -9
- package/dist/ai-EEfKyc-z.js.map +1 -0
- package/dist/ai-client.d.ts +2 -2
- package/dist/ai-client.js +2 -2
- package/dist/ai-fx.d.ts +4 -2
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +214 -67
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-DTjkAr2T.d.ts} +2 -2
- package/dist/{ai-harness-CV6rXbB3.d.ts.map → ai-harness-DTjkAr2T.d.ts.map} +1 -1
- package/dist/{ai-harness-Clbg5ei7.js → ai-harness-dQeJaR1t.js} +7 -3
- package/dist/ai-harness-dQeJaR1t.js.map +1 -0
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.js +3 -3
- package/dist/ai-pi.js.map +1 -1
- package/dist/ai-react.d.ts +5 -5
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +14 -17
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +86 -73
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BhHLoNKF.js → client-BQS4i_V9.js} +29 -6
- package/dist/client-BQS4i_V9.js.map +1 -0
- package/dist/{client-h_DKFAjj.d.ts → client-CUMbS_38.d.ts} +3 -3
- package/dist/{client-h_DKFAjj.d.ts.map → client-CUMbS_38.d.ts.map} +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/{contract-BYKASlCV.js → contract-CWukt01d.js} +2 -2
- package/dist/contract-CWukt01d.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/{initial-snapshot-Bx-c0pDP.d.ts → initial-snapshot-D3Lve43o.d.ts} +4 -4
- package/dist/initial-snapshot-D3Lve43o.d.ts.map +1 -0
- package/dist/{internal-CscSY7K_.js → internal-CvsrprnU.js} +40 -16
- package/dist/internal-CvsrprnU.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/otel.js +1 -1
- package/dist/otel.js.map +1 -1
- package/dist/{react-BBXgHWFc.d.ts → react-BoHvC3v9.d.ts} +4 -4
- package/dist/{react-BBXgHWFc.d.ts.map → react-BoHvC3v9.d.ts.map} +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{reducer-CqA59Q6U.d.ts → reducer-CYPHsSmd.d.ts} +2 -2
- package/dist/{reducer-CqA59Q6U.d.ts.map → reducer-CYPHsSmd.d.ts.map} +1 -1
- package/dist/{reducer-BAXc8JyF.js → reducer-CjmvZSAb.js} +2 -2
- package/dist/{reducer-BAXc8JyF.js.map → reducer-CjmvZSAb.js.map} +1 -1
- package/dist/runtime-consumer-CIeL33wj.js +12 -0
- package/dist/runtime-consumer-CIeL33wj.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +3 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +3 -3
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-qyb5aJJG.js → server-DtFrWxi3.js} +412 -116
- package/dist/server-DtFrWxi3.js.map +1 -0
- package/dist/{server-DYAATFuT.d.ts → server-qgez0Xny.d.ts} +108 -41
- package/dist/server-qgez0Xny.d.ts.map +1 -0
- package/dist/server.browser.js +1 -1
- package/dist/server.browser.js.map +1 -1
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{session-clear-C-QwTXV1.d.ts → session-clear-D1PFe7xl.d.ts} +2 -2
- package/dist/session-clear-D1PFe7xl.d.ts.map +1 -0
- package/dist/{session-clear-DGI4jTQ5.js → session-clear-wHd5GCpZ.js} +5 -4
- package/dist/session-clear-wHd5GCpZ.js.map +1 -0
- package/dist/store-D0MhBvg6.d.ts +532 -0
- package/dist/store-D0MhBvg6.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-codec-DwrWlgQx.js +65 -0
- package/dist/store-codec-DwrWlgQx.js.map +1 -0
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +406 -225
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-7WJV0YWP.js → store-polling-BZt2Rf3D.js} +2 -2
- package/dist/{store-polling-7WJV0YWP.js.map → store-polling-BZt2Rf3D.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +1598 -587
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-presence-polling-JuAy2Ck2.js +111 -0
- package/dist/store-presence-polling-JuAy2Ck2.js.map +1 -0
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +4 -4
- package/dist/store-redis-notify-CsJw_hfD.js +3060 -0
- package/dist/store-redis-notify-CsJw_hfD.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +4 -5
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +527 -299
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-DrTyiIuo.js +57 -0
- package/dist/stream-buffer-DrTyiIuo.js.map +1 -0
- package/dist/{telemetry-Cyfqo6Qt.d.ts → telemetry-CWwsh-Gx.d.ts} +3 -3
- package/dist/telemetry-CWwsh-Gx.d.ts.map +1 -0
- package/dist/{wire-BKxH1vbP.js → wire-CV-Z7wz-.js} +6 -5
- package/dist/wire-CV-Z7wz-.js.map +1 -0
- package/docs/01-quickstart.mdx +21 -15
- package/docs/02-examples.mdx +1 -1
- package/docs/actors/01-introduction.mdx +13 -2
- package/docs/actors/02-concurrency.mdx +1 -1
- package/docs/agents/01-introduction.mdx +1 -1
- package/docs/agents/02-tools.mdx +12 -11
- package/docs/agents/03-conversations.mdx +36 -25
- package/docs/agents/04-harnesses.mdx +14 -7
- package/docs/client/03-presence.mdx +18 -14
- package/docs/contract/01-events.mdx +2 -2
- package/docs/contract/02-state.mdx +18 -14
- package/docs/contract/03-sessions.mdx +32 -25
- package/docs/index.mdx +29 -24
- package/docs/reference/01-core.mdx +7 -6
- package/docs/reference/02-server.mdx +250 -191
- package/docs/reference/04-ai.mdx +33 -23
- package/docs/reference/05-telemetry.mdx +27 -13
- package/docs/reference/06-errors.mdx +25 -19
- package/docs/reference/meta.ts +1 -1
- package/docs/schedulers/01-overview.mdx +8 -8
- package/docs/schedulers/02-vercel-queues.mdx +2 -2
- package/docs/schedulers/03-qstash.mdx +4 -4
- package/docs/server/01-consumers.mdx +418 -0
- package/docs/server/02-timers.mdx +31 -21
- package/docs/server/03-cancellation.mdx +52 -44
- package/docs/server/04-routes.mdx +4 -1
- package/docs/server/05-sql-projections.mdx +391 -0
- package/docs/server/06-guarantees.mdx +61 -51
- package/docs/stores/01-overview.mdx +3 -3
- package/docs/stores/02-redis.mdx +24 -10
- package/docs/stores/03-redis-http.mdx +8 -6
- package/docs/stores/04-postgres.mdx +65 -14
- package/docs/stores/05-sqlite.mdx +5 -3
- package/docs/upgrades/01-overview.mdx +31 -0
- package/docs/upgrades/78-0-22.mdx +115 -0
- package/docs/upgrades/79-0-21.mdx +165 -0
- package/docs/upgrades/80-0-20.mdx +43 -0
- package/docs/upgrades/81-0-19.mdx +33 -0
- package/docs/upgrades/82-0-18.mdx +33 -0
- package/docs/upgrades/83-0-17.mdx +69 -0
- package/docs/upgrades/meta.ts +3 -0
- package/examples/playground/DEPLOY.md +39 -7
- package/examples/playground/app/a0/chat.test.tsx +2 -2
- package/examples/playground/app/a0/sandbox-log.ts +17 -15
- package/examples/playground/app/a0/sandbox.ts +58 -13
- package/examples/playground/app/a0/server.ts +4 -4
- package/examples/playground/app/a2slack/a2slack.css +1067 -0
- package/examples/playground/app/a2slack/c/[channel]/page.tsx +20 -0
- package/examples/playground/app/a2slack/consumers/agent.ts +63 -0
- package/examples/playground/app/a2slack/consumers/feed.ts +164 -0
- package/examples/playground/app/a2slack/consumers/notify.ts +116 -0
- package/examples/playground/app/a2slack/consumers/search.ts +37 -0
- package/examples/playground/app/a2slack/consumers/triage.ts +31 -0
- package/examples/playground/app/a2slack/consumers/welcome.ts +25 -0
- package/examples/playground/app/a2slack/db/a2.ts +209 -0
- package/examples/playground/app/a2slack/db/index.ts +73 -0
- package/examples/playground/app/a2slack/db/migrations/0000_extensions.sql +4 -0
- package/examples/playground/app/a2slack/db/migrations/0001_workspace.sql +74 -0
- package/examples/playground/app/a2slack/db/migrations/0002_a2.sql +215 -0
- package/examples/playground/app/a2slack/db/migrations/0003_a2_batch.sql +67 -0
- package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0000_snapshot.json +18 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0001_snapshot.json +500 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0002_snapshot.json +1159 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0003_snapshot.json +1159 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
- package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +41 -0
- package/examples/playground/app/a2slack/db/queries.ts +263 -0
- package/examples/playground/app/a2slack/db/schema.ts +145 -0
- package/examples/playground/app/a2slack/drills/a2slack.drill.ts +708 -0
- package/examples/playground/app/a2slack/drills/harness.ts +244 -0
- package/examples/playground/app/a2slack/events/route.ts +36 -0
- package/examples/playground/app/a2slack/identity.ts +44 -0
- package/examples/playground/app/a2slack/inbox/route.ts +8 -0
- package/examples/playground/app/a2slack/lag/data/route.ts +8 -0
- package/examples/playground/app/a2slack/lag/page.tsx +101 -0
- package/examples/playground/app/a2slack/lag.ts +73 -0
- package/examples/playground/app/a2slack/layout.tsx +43 -0
- package/examples/playground/app/a2slack/messages/route.ts +17 -0
- package/examples/playground/app/a2slack/model.test.ts +208 -0
- package/examples/playground/app/a2slack/model.ts +369 -0
- package/examples/playground/app/a2slack/models.test.ts +72 -0
- package/examples/playground/app/a2slack/page.tsx +10 -0
- package/examples/playground/app/a2slack/scheduler/route.ts +5 -0
- package/examples/playground/app/a2slack/search/route.ts +40 -0
- package/examples/playground/app/a2slack/search-index.ts +108 -0
- package/examples/playground/app/a2slack/server.test.ts +315 -0
- package/examples/playground/app/a2slack/server.ts +55 -0
- package/examples/playground/app/a2slack/sessions.ts +50 -0
- package/examples/playground/app/a2slack/thread/route.ts +8 -0
- package/examples/playground/app/a2slack/ui/ChannelView.tsx +87 -0
- package/examples/playground/app/a2slack/ui/Composer.tsx +117 -0
- package/examples/playground/app/a2slack/ui/DotsShadow.tsx +42 -0
- package/examples/playground/app/a2slack/ui/Feed.tsx +157 -0
- package/examples/playground/app/a2slack/ui/InboxContext.tsx +63 -0
- package/examples/playground/app/a2slack/ui/InboxView.tsx +82 -0
- package/examples/playground/app/a2slack/ui/Menu.tsx +128 -0
- package/examples/playground/app/a2slack/ui/MessageRow.tsx +314 -0
- package/examples/playground/app/a2slack/ui/NameGate.tsx +35 -0
- package/examples/playground/app/a2slack/ui/SearchContext.tsx +146 -0
- package/examples/playground/app/a2slack/ui/SearchResults.tsx +97 -0
- package/examples/playground/app/a2slack/ui/Shell.tsx +34 -0
- package/examples/playground/app/a2slack/ui/Sidebar.tsx +199 -0
- package/examples/playground/app/a2slack/ui/ThreadPanel.tsx +134 -0
- package/examples/playground/app/a2slack/ui/useInbox.ts +71 -0
- package/examples/playground/app/agent/server.ts +3 -3
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
- package/examples/playground/app/chat/server.ts +45 -35
- package/examples/playground/app/components/demo-icons.tsx +7 -0
- package/examples/playground/app/components/demos.tsx +7 -0
- package/examples/playground/app/orders/[orderId]/order-client.tsx +2 -2
- package/examples/playground/app/orders/page.tsx +1 -1
- package/examples/playground/app/orders/server.ts +49 -30
- package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +14 -15
- package/examples/playground/app/recovery/page.tsx +4 -4
- package/examples/playground/app/recovery/server.ts +23 -32
- package/examples/playground/drizzle.config.ts +14 -0
- package/examples/playground/lib/store.ts +1 -1
- package/examples/playground/next.config.ts +6 -1
- package/examples/playground/package.json +12 -1
- package/examples/playground/scripts/migrate.mjs +31 -0
- package/examples/playground/vercel.json +9 -0
- package/examples/playground/vitest.drills.config.ts +32 -0
- package/package.json +5 -2
- package/sql/postgres/drizzle-schema.ts +209 -0
- package/sql/postgres/schema.sql +227 -67
- package/src/actor-shared.ts +10 -2
- package/src/actor.ts +101 -71
- package/src/ai-agent.ts +162 -111
- package/src/ai-control-server.ts +8 -8
- package/src/ai-fx.ts +324 -82
- package/src/ai-harness.ts +17 -2
- package/src/ai-pi.ts +2 -2
- package/src/ai-server.ts +2 -2
- package/src/ai.ts +7 -7
- package/src/client.ts +19 -2
- package/src/contract.ts +1 -1
- package/src/deterministic-id.ts +27 -4
- package/src/errors.ts +8 -5
- package/src/idempotent-replay.ts +0 -1
- package/src/index.ts +1 -1
- package/src/internal.ts +57 -16
- package/src/otel.ts +1 -1
- package/src/postgres-schema.ts +98 -57
- package/src/presence-recovery.ts +18 -11
- package/src/presence-tombstones.ts +52 -0
- package/src/push-envelope.ts +20 -0
- package/src/runtime-consumer.ts +10 -0
- package/src/scheduler-task.ts +2 -2
- package/src/scheduler-vercel.ts +1 -1
- package/src/server.browser.ts +1 -1
- package/src/server.ts +819 -218
- package/src/session-socket.ts +1 -1
- package/src/store-codec.ts +66 -0
- package/src/store-memory.ts +604 -286
- package/src/store-postgres.ts +2006 -857
- package/src/store-presence-polling.ts +59 -34
- package/src/store-redis-core.ts +1961 -1333
- package/src/store-redis-notify.ts +76 -26
- package/src/store-redis.ts +2 -3
- package/src/store-sqlite.ts +915 -516
- package/src/store.ts +241 -102
- package/src/telemetry.ts +2 -1
- package/src/wire.ts +1 -0
- package/dist/actor-C7b-1arn.d.ts.map +0 -1
- package/dist/actor-shared-t7w8xIKP.js.map +0 -1
- package/dist/ai-harness-Clbg5ei7.js.map +0 -1
- package/dist/ai-mMiWul1T.js.map +0 -1
- package/dist/client-BhHLoNKF.js.map +0 -1
- package/dist/contract-BYKASlCV.js.map +0 -1
- package/dist/idempotent-replay-JdgMQRRm.js +0 -21
- package/dist/idempotent-replay-JdgMQRRm.js.map +0 -1
- package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +0 -1
- package/dist/internal-CscSY7K_.js.map +0 -1
- package/dist/server-DYAATFuT.d.ts.map +0 -1
- package/dist/server-qyb5aJJG.js.map +0 -1
- package/dist/session-clear-C-QwTXV1.d.ts.map +0 -1
- package/dist/session-clear-DGI4jTQ5.js.map +0 -1
- package/dist/store-CyLUy86v.d.ts +0 -404
- package/dist/store-CyLUy86v.d.ts.map +0 -1
- package/dist/store-codec-DTG0Ftek.js +0 -10
- package/dist/store-codec-DTG0Ftek.js.map +0 -1
- package/dist/store-presence-polling-B1ixeqIS.js +0 -94
- package/dist/store-presence-polling-B1ixeqIS.js.map +0 -1
- package/dist/store-redis-notify-CM0AfLY0.js +0 -2544
- package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
- package/dist/stream-buffer-Cx7AAPGq.js +0 -23
- package/dist/stream-buffer-Cx7AAPGq.js.map +0 -1
- package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
- package/dist/wire-BKxH1vbP.js.map +0 -1
- package/docs/server/01-handlers.mdx +0 -227
|
@@ -24,13 +24,31 @@ Redis, poll cadence elsewhere. Upstash is durable by default; use its TCP
|
|
|
24
24
|
`rediss://` URL, not the REST one. Each feature creates its own server with
|
|
25
25
|
the shared store; storage remains namespaced per contract. Without either variable, a
|
|
26
26
|
production boot fails fast with `STORE_NOT_CONFIGURED` (by design).
|
|
27
|
-
Redis needs no setup.
|
|
28
|
-
|
|
27
|
+
Redis needs no setup. Postgres is migrated by every Vercel build: the
|
|
28
|
+
`vercel-build` script runs `scripts/migrate.mjs` (Drizzle's migrator, which,
|
|
29
|
+
unlike `drizzle-kit migrate`, prints the error when one fails) before the
|
|
30
|
+
usual `turbo run build`, through `DATABASE_URL_UNPOOLED` when it's set (the Neon
|
|
31
|
+
integration's direct connection). Previews and production share one database here, so a branch's
|
|
32
|
+
migration reaches it when its preview builds: keep migrations additive.
|
|
33
|
+
Elsewhere, migrate by hand (A2 never creates its tables at runtime):
|
|
29
34
|
|
|
30
35
|
```sh
|
|
31
|
-
|
|
36
|
+
DATABASE_URL=postgres://… pnpm --filter playground db:migrate
|
|
32
37
|
```
|
|
33
38
|
|
|
39
|
+
It applies every migration in `app/a2slack/db/migrations`. A2's tables are in
|
|
40
|
+
the Drizzle schema with a2slack's: `app/a2slack/db/a2.ts` is a copy of
|
|
41
|
+
`experimental-a2`'s `sql/postgres/drizzle-schema.ts`. drizzle-kit generated
|
|
42
|
+
their migration (`0002_a2.sql`) from it, and its SQL is `schema.sql`'s
|
|
43
|
+
instead of the generated `CREATE TABLE`s, because the shared database already
|
|
44
|
+
had A2's tables: it creates what's missing and upgrades the rest. Drizzle
|
|
45
|
+
can't declare A2's `a2_batch` function, so `0003_a2_batch.sql` is a custom
|
|
46
|
+
migration (`drizzle-kit generate --custom`) holding `schema.sql`'s block for
|
|
47
|
+
it. When a
|
|
48
|
+
release of `experimental-a2` changes its tables (its changelog says so),
|
|
49
|
+
update `a2.ts` from the new file, run `pnpm db:generate`, and read the SQL it
|
|
50
|
+
writes.
|
|
51
|
+
|
|
34
52
|
To verify a provider before leaning on it, run the full conformance
|
|
35
53
|
kit against it (throwaway database; the suite isolates by key prefix
|
|
36
54
|
and sweeps after itself):
|
|
@@ -42,6 +60,19 @@ TEST_REDIS_URL=rediss://… pnpm --filter a2 exec vitest run test/store-redis.co
|
|
|
42
60
|
`pg` and `ioredis` are optional peer dependencies of `experimental-a2` and direct
|
|
43
61
|
dependencies of this app.
|
|
44
62
|
|
|
63
|
+
a2slack keeps its tables in the Postgres at `DATABASE_URL`, defined with
|
|
64
|
+
Drizzle in `app/a2slack/db/schema.ts`. With `REDIS_URL` set too, A2 runs on
|
|
65
|
+
Redis and only a2slack's tables are used in Postgres (A2's are created there
|
|
66
|
+
too, and stay empty). Generate a migration with `pnpm db:generate` after
|
|
67
|
+
changing the schema; SQL drizzle-kit can't express, like an extension, goes
|
|
68
|
+
in a custom one (`pnpm db:generate --custom --name …`).
|
|
69
|
+
|
|
70
|
+
Its search index lives in Upstash Redis Search, through the Redis's REST
|
|
71
|
+
credentials (`KV_REST_API_URL` and `KV_REST_API_TOKEN`, or this project's
|
|
72
|
+
`REDIS_KV_REST_API_*`). The index is created the first time it's written.
|
|
73
|
+
Without them, search reads the Postgres table instead. In development the
|
|
74
|
+
tables live in PGlite at `.a2/a2slack`, migrated when it opens.
|
|
75
|
+
|
|
45
76
|
## 2. Vercel Queues scheduler
|
|
46
77
|
|
|
47
78
|
Queues need no configuration beyond the triggers in `vercel.json`. A0, Orders,
|
|
@@ -153,10 +184,11 @@ vercel deploy
|
|
|
153
184
|
```
|
|
154
185
|
|
|
155
186
|
The Recovery lab tests a real function timeout. Its dedicated trigger and
|
|
156
|
-
queue consumer both export `maxDuration = 20`. The pipeline
|
|
157
|
-
|
|
158
|
-
does not. A2 keeps dispatching events.
|
|
159
|
-
the third
|
|
187
|
+
queue consumer both export `maxDuration = 20`. The pipeline is four 8-second
|
|
188
|
+
steps, run in order by one A2 consumer, so every step fits in a fresh
|
|
189
|
+
invocation but the 32-second chain does not. A2 keeps dispatching events.
|
|
190
|
+
Vercel stops the first invocation while the third step is running, leaving
|
|
191
|
+
the consumer's cursor on that event.
|
|
160
192
|
The deadline-capped claim expires, its watchdog retries the same event in a
|
|
161
193
|
fresh function, and already completed events do not run again. The dedicated
|
|
162
194
|
`a2-recovery` topic keeps this artificial budget away from the
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
within,
|
|
11
11
|
} from '@testing-library/react'
|
|
12
12
|
import { tool } from 'ai'
|
|
13
|
-
import {
|
|
13
|
+
import { consumerContext, inputs } from 'experimental-a2/ai'
|
|
14
14
|
import { agent } from 'experimental-a2/ai/server'
|
|
15
15
|
import type { A2Operation } from 'experimental-a2/server'
|
|
16
16
|
import { memory } from 'experimental-a2/store-memory'
|
|
@@ -57,7 +57,7 @@ const tools = {
|
|
|
57
57
|
bash: tool({
|
|
58
58
|
inputSchema: z.object({ command: z.string() }),
|
|
59
59
|
execute: async () => {
|
|
60
|
-
const { signal } =
|
|
60
|
+
const { signal } = consumerContext()
|
|
61
61
|
await new Promise<void>((resolve, reject) => {
|
|
62
62
|
void bashDone.promise.then(resolve)
|
|
63
63
|
signal.addEventListener('abort', () => reject(signal.reason), {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
import { contract } from 'experimental-a2'
|
|
3
3
|
import { vercelQueues } from 'experimental-a2/scheduler-vercel'
|
|
4
|
-
import { createServer } from 'experimental-a2/server'
|
|
4
|
+
import { consumer, createServer } from 'experimental-a2/server'
|
|
5
5
|
import { z } from 'zod'
|
|
6
6
|
import { store } from '@/lib/store'
|
|
7
7
|
import { isAppId } from './apps'
|
|
@@ -31,24 +31,26 @@ export const sandboxScheduler =
|
|
|
31
31
|
? vercelQueues({ topic: 'a2-a0-sandboxes' })
|
|
32
32
|
: undefined
|
|
33
33
|
|
|
34
|
+
const deleteIdleSandbox = consumer({
|
|
35
|
+
contract: sandboxLog,
|
|
36
|
+
name: 'delete-idle-sandbox',
|
|
37
|
+
types: ['sandbox.idle.checked'],
|
|
38
|
+
}).run(async ({ event }) => {
|
|
39
|
+
const { appId } = event.payload
|
|
40
|
+
// A prompt since the sandbox opened makes it a real app's, whichever
|
|
41
|
+
// harness's agent got it.
|
|
42
|
+
const reads = await Promise.all(
|
|
43
|
+
Object.values(agentServers).map((server) => server.session(appId).state()),
|
|
44
|
+
)
|
|
45
|
+
if (reads.every(({ state }) => state.messages.length === 0))
|
|
46
|
+
await deleteAppSandbox({ appId })
|
|
47
|
+
})
|
|
48
|
+
|
|
34
49
|
export const sandboxServer = createServer({
|
|
35
50
|
...(store ? { store } : {}),
|
|
36
51
|
...(sandboxScheduler ? { scheduler: sandboxScheduler } : {}),
|
|
37
52
|
contract: sandboxLog,
|
|
38
|
-
|
|
39
|
-
'sandbox.idle.checked': async ({ event }) => {
|
|
40
|
-
const { appId } = event.payload
|
|
41
|
-
// A prompt since the sandbox opened makes it a real app's, whichever
|
|
42
|
-
// harness's agent got it.
|
|
43
|
-
const reads = await Promise.all(
|
|
44
|
-
Object.values(agentServers).map((server) =>
|
|
45
|
-
server.session(appId).state(),
|
|
46
|
-
),
|
|
47
|
-
)
|
|
48
|
-
if (reads.every(({ state }) => state.messages.length === 0))
|
|
49
|
-
await deleteAppSandbox({ appId })
|
|
50
|
-
},
|
|
51
|
-
},
|
|
53
|
+
consumers: [deleteIdleSandbox],
|
|
52
54
|
})
|
|
53
55
|
|
|
54
56
|
/**
|
|
@@ -68,6 +68,7 @@ async function openApp(appId: string): Promise<Sandbox> {
|
|
|
68
68
|
persistent: true,
|
|
69
69
|
snapshotExpiration: SNAPSHOT_EXPIRATION_MS,
|
|
70
70
|
keepLastSnapshots: { count: 1 },
|
|
71
|
+
onResume: startDev,
|
|
71
72
|
})
|
|
72
73
|
} catch (error) {
|
|
73
74
|
// Another instance created it first. The API holds a racing create until
|
|
@@ -81,8 +82,54 @@ async function openApp(appId: string): Promise<Sandbox> {
|
|
|
81
82
|
return sandbox
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
//
|
|
85
|
-
|
|
85
|
+
// Each instance keeps one handle per app, so parallel tool calls share an
|
|
86
|
+
// open and later ones skip the lookup. A handle resumes a stopped session on
|
|
87
|
+
// its next call; a deleted sandbox fails with a 404 until it's forgotten.
|
|
88
|
+
// An empty app's idle check deletes its sandbox from whichever instance runs
|
|
89
|
+
// it, 10 minutes after the sandbox opened. By then the session's 15 minutes
|
|
90
|
+
// leave less than KEEP_ALIVE_MS, so every call extends it, and the extension's
|
|
91
|
+
// 404 is how a kept handle finds out.
|
|
92
|
+
const handles = new Map<string, Promise<Sandbox>>()
|
|
93
|
+
const MAX_HANDLES = 100
|
|
94
|
+
|
|
95
|
+
const forget = (appId: string): void => {
|
|
96
|
+
handles.delete(appId)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const open = (appId: string): Promise<Sandbox> => {
|
|
100
|
+
const opened = openApp(appId)
|
|
101
|
+
opened.catch(() => {
|
|
102
|
+
if (handles.get(appId) === opened) forget(appId)
|
|
103
|
+
})
|
|
104
|
+
return opened
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const kept = (appId: string): Promise<Sandbox> => {
|
|
108
|
+
const pending = handles.get(appId) ?? open(appId)
|
|
109
|
+
// Most recently used last, so the cap drops the stalest.
|
|
110
|
+
handles.delete(appId)
|
|
111
|
+
handles.set(appId, pending)
|
|
112
|
+
const stalest = handles.keys().next().value
|
|
113
|
+
if (handles.size > MAX_HANDLES && stalest !== undefined) forget(stalest)
|
|
114
|
+
return pending
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether the sandbox is still there. Extends it only by the shortfall, so
|
|
119
|
+
* repeated calls never stack up time.
|
|
120
|
+
*/
|
|
121
|
+
const keepAlive = async (sandbox: Sandbox): Promise<boolean> => {
|
|
122
|
+
const shortfall =
|
|
123
|
+
Date.now() + KEEP_ALIVE_MS - (sandbox.expiresAt?.getTime() ?? 0)
|
|
124
|
+
if (shortfall <= 0) return true
|
|
125
|
+
try {
|
|
126
|
+
await sandbox.extendTimeout(shortfall)
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// Throws past the plan's session limit; the sandbox still works until then.
|
|
129
|
+
return !notFound(error)
|
|
130
|
+
}
|
|
131
|
+
return true
|
|
132
|
+
}
|
|
86
133
|
|
|
87
134
|
/** The app's sandbox, created or resumed, and kept up while it's used. */
|
|
88
135
|
export async function appSandbox({
|
|
@@ -90,16 +137,13 @@ export async function appSandbox({
|
|
|
90
137
|
}: {
|
|
91
138
|
appId: string
|
|
92
139
|
}): Promise<Sandbox> {
|
|
93
|
-
const pending =
|
|
94
|
-
opening.get(appId) ?? openApp(appId).finally(() => opening.delete(appId))
|
|
95
|
-
opening.set(appId, pending)
|
|
140
|
+
const pending = kept(appId)
|
|
96
141
|
const sandbox = await pending
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return sandbox
|
|
142
|
+
if (await keepAlive(sandbox)) return sandbox
|
|
143
|
+
if (handles.get(appId) === pending) forget(appId)
|
|
144
|
+
const reopened = await kept(appId)
|
|
145
|
+
await keepAlive(reopened)
|
|
146
|
+
return reopened
|
|
103
147
|
}
|
|
104
148
|
|
|
105
149
|
// A server that's booting refuses connections for a moment, and a page
|
|
@@ -118,12 +162,12 @@ export async function ensureApp({
|
|
|
118
162
|
appId: string
|
|
119
163
|
}): Promise<{ up: boolean; createdAt: Date }> {
|
|
120
164
|
const sandbox = await appSandbox({ appId })
|
|
121
|
-
const
|
|
165
|
+
const answer = await sandbox.runCommand({
|
|
122
166
|
cmd: 'bash',
|
|
123
167
|
args: ['-c', `${START_DEV}; ${PROBE}`],
|
|
124
168
|
})
|
|
125
169
|
return {
|
|
126
|
-
up: Number(await
|
|
170
|
+
up: Number(await answer.stdout()) > 0,
|
|
127
171
|
createdAt: sandbox.createdAt,
|
|
128
172
|
}
|
|
129
173
|
}
|
|
@@ -145,6 +189,7 @@ export async function deleteAppSandbox({
|
|
|
145
189
|
}: {
|
|
146
190
|
appId: string
|
|
147
191
|
}): Promise<void> {
|
|
192
|
+
forget(appId)
|
|
148
193
|
try {
|
|
149
194
|
const sandbox = await Sandbox.get({
|
|
150
195
|
name: sandboxName(appId),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
import { waitUntil } from '@vercel/functions'
|
|
3
3
|
import { tool } from 'ai'
|
|
4
|
-
import {
|
|
4
|
+
import { consumerContext } from 'experimental-a2/ai'
|
|
5
5
|
import { fx } from 'experimental-a2/ai/fx'
|
|
6
6
|
import { pi } from 'experimental-a2/ai/pi'
|
|
7
7
|
import { agent } from 'experimental-a2/ai/server'
|
|
@@ -55,7 +55,7 @@ const tools = {
|
|
|
55
55
|
execute: ({ path }) =>
|
|
56
56
|
timed(async () =>
|
|
57
57
|
previewUrl({
|
|
58
|
-
sandbox: await appSandbox({ appId:
|
|
58
|
+
sandbox: await appSandbox({ appId: consumerContext().session.id }),
|
|
59
59
|
path,
|
|
60
60
|
}),
|
|
61
61
|
),
|
|
@@ -68,7 +68,7 @@ const tools = {
|
|
|
68
68
|
timeoutSeconds: z.number().int().min(1).max(240).default(120),
|
|
69
69
|
}),
|
|
70
70
|
execute: ({ command, timeoutSeconds }) => {
|
|
71
|
-
const context =
|
|
71
|
+
const context = consumerContext()
|
|
72
72
|
return timed(async () =>
|
|
73
73
|
bash({
|
|
74
74
|
sandbox: await appSandbox({ appId: context.session.id }),
|
|
@@ -190,7 +190,7 @@ export const listServer = createServer({
|
|
|
190
190
|
contract: appList,
|
|
191
191
|
})
|
|
192
192
|
|
|
193
|
-
/** The dev tools' crash logs, one per app. They have no
|
|
193
|
+
/** The dev tools' crash logs, one per app. They have no consumers. */
|
|
194
194
|
export const crashServer = createServer({
|
|
195
195
|
...(store ? { store } : {}),
|
|
196
196
|
contract: crashLog,
|