experimental-a2 0.21.1 → 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 +21 -0
- package/dist/{actor-HnGmrOZZ.d.ts → actor-CqibLgjQ.d.ts} +13 -5
- package/dist/actor-CqibLgjQ.d.ts.map +1 -0
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-react.d.ts +1 -1
- package/dist/actor-react.js +1 -1
- package/dist/actor-shared-DFET5q16.js.map +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +3 -3
- package/dist/{ai-CvEnqVfo.d.ts → ai-DAu77CDr.d.ts} +2 -2
- package/dist/{ai-CvEnqVfo.d.ts.map → ai-DAu77CDr.d.ts.map} +1 -1
- package/dist/{ai-CzE672Q3.js → ai-EEfKyc-z.js} +2 -2
- package/dist/{ai-CzE672Q3.js.map → ai-EEfKyc-z.js.map} +1 -1
- package/dist/ai-client.d.ts +1 -1
- 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 +211 -61
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-Bfk-Lycn.d.ts → ai-harness-DTjkAr2T.d.ts} +2 -2
- package/dist/{ai-harness-Bfk-Lycn.d.ts.map → ai-harness-DTjkAr2T.d.ts.map} +1 -1
- package/dist/ai-harness-dQeJaR1t.js.map +1 -1
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.js +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +3 -3
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +10 -4
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +1 -1
- package/dist/ai.js +1 -1
- package/dist/{client-9IeTsiAs.js → client-BQS4i_V9.js} +27 -4
- package/dist/client-BQS4i_V9.js.map +1 -0
- package/dist/client-CUMbS_38.d.ts.map +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/{internal-Bc4PvfHk.js → internal-CvsrprnU.js} +32 -15
- package/dist/{internal-Bc4PvfHk.js.map → internal-CvsrprnU.js.map} +1 -1
- package/dist/react.js +1 -1
- package/dist/{runtime-consumer-BWmht60z.js → runtime-consumer-CIeL33wj.js} +2 -2
- package/dist/{runtime-consumer-BWmht60z.js.map → runtime-consumer-CIeL33wj.js.map} +1 -1
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +2 -2
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +2 -2
- package/dist/{server-DvIB8kj5.js → server-DtFrWxi3.js} +30 -8
- package/dist/server-DtFrWxi3.js.map +1 -0
- package/dist/{server-BshFZ0eL.d.ts → server-qgez0Xny.d.ts} +3 -3
- package/dist/{server-BshFZ0eL.d.ts.map → server-qgez0Xny.d.ts.map} +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1 -1
- package/dist/{session-clear-Bk_29eEw.d.ts → session-clear-D1PFe7xl.d.ts} +2 -2
- package/dist/session-clear-D1PFe7xl.d.ts.map +1 -0
- package/dist/{store-Z2qjw50n.d.ts → store-D0MhBvg6.d.ts} +15 -6
- package/dist/store-D0MhBvg6.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +15 -17
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-DYNyxCev.js → store-polling-BZt2Rf3D.js} +2 -2
- package/dist/{store-polling-DYNyxCev.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 +31 -40
- 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 +3 -3
- package/dist/{store-redis-notify-IiVdlYHc.js → store-redis-notify-CsJw_hfD.js} +538 -397
- 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 +3 -4
- 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 +16 -18
- 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/{wire-CZGqNZRx.js → wire-CV-Z7wz-.js} +3 -3
- package/dist/{wire-CZGqNZRx.js.map → wire-CV-Z7wz-.js.map} +1 -1
- package/docs/actors/01-introduction.mdx +5 -2
- package/docs/agents/03-conversations.mdx +9 -8
- package/docs/agents/04-harnesses.mdx +7 -4
- package/docs/reference/01-core.mdx +3 -2
- package/docs/reference/02-server.mdx +5 -1
- package/docs/reference/04-ai.mdx +13 -4
- package/docs/reference/06-errors.mdx +1 -1
- package/docs/server/01-consumers.mdx +4 -1
- package/docs/server/06-guarantees.mdx +7 -4
- package/docs/stores/02-redis.mdx +16 -10
- package/docs/stores/03-redis-http.mdx +6 -4
- package/docs/stores/04-postgres.mdx +2 -2
- package/docs/upgrades/01-overview.mdx +1 -0
- package/docs/upgrades/78-0-22.mdx +115 -0
- package/examples/playground/app/a0/sandbox.ts +58 -13
- package/examples/playground/app/a2slack/db/a2.ts +7 -4
- package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
- package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +8 -1
- package/examples/playground/app/chat/server.ts +2 -2
- package/examples/playground/lib/store.ts +1 -1
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/sql/postgres/drizzle-schema.ts +7 -4
- package/sql/postgres/schema.sql +24 -3
- package/src/actor-shared.ts +10 -2
- package/src/ai-agent.ts +13 -0
- package/src/ai-fx.ts +321 -77
- package/src/ai-harness.ts +7 -0
- package/src/client.ts +19 -2
- package/src/deterministic-id.ts +1 -1
- package/src/internal.ts +42 -11
- package/src/presence-recovery.ts +18 -11
- package/src/presence-tombstones.ts +52 -0
- package/src/push-envelope.ts +2 -2
- package/src/server.ts +49 -15
- package/src/store-memory.ts +24 -32
- package/src/store-postgres.ts +43 -53
- package/src/store-presence-polling.ts +59 -34
- package/src/store-redis-core.ts +595 -439
- package/src/store-redis-notify.ts +74 -26
- package/src/store-redis.ts +2 -3
- package/src/store-sqlite.ts +34 -40
- package/src/store.ts +14 -5
- package/dist/actor-HnGmrOZZ.d.ts.map +0 -1
- package/dist/client-9IeTsiAs.js.map +0 -1
- package/dist/server-DvIB8kj5.js.map +0 -1
- package/dist/session-clear-Bk_29eEw.d.ts.map +0 -1
- package/dist/store-Z2qjw50n.d.ts.map +0 -1
- package/dist/store-presence-polling-CzQ0wdtM.js +0 -94
- package/dist/store-presence-polling-CzQ0wdtM.js.map +0 -1
- package/dist/store-redis-notify-IiVdlYHc.js.map +0 -1
- package/dist/stream-buffer-Cx7AAPGq.js +0 -23
- package/dist/stream-buffer-Cx7AAPGq.js.map +0 -1
|
@@ -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),
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* your schema.
|
|
8
8
|
*
|
|
9
9
|
* Whatever creates them, A2 relies on the table and column names, the column
|
|
10
|
-
* types, the primary keys, and the unique index on a2_events
|
|
11
|
-
* other indexes keep its queries fast. Every name is spelled
|
|
12
|
-
* `casing` setting changes none of them.
|
|
10
|
+
* types, the primary keys, and the unique index on a2_events (session_id,
|
|
11
|
+
* event_id). The other indexes keep its queries fast. Every name is spelled
|
|
12
|
+
* out, so Drizzle's `casing` setting changes none of them.
|
|
13
13
|
*
|
|
14
14
|
* A2's tables change only when a release's changelog says so; compare your
|
|
15
15
|
* copy with that release's file. Drizzle can't declare functions or
|
|
@@ -48,7 +48,10 @@ export const a2Events = pgTable(
|
|
|
48
48
|
name: 'a2_events_pkey',
|
|
49
49
|
columns: [table.sessionId, table.idx],
|
|
50
50
|
}),
|
|
51
|
-
uniqueIndex('
|
|
51
|
+
uniqueIndex('a2_events_session_event_id').on(
|
|
52
|
+
table.sessionId,
|
|
53
|
+
table.eventId,
|
|
54
|
+
),
|
|
52
55
|
index('a2_events_types').on(table.sessionId, table.eventType, table.idx),
|
|
53
56
|
],
|
|
54
57
|
)
|