@tanstack/ai-sandbox 0.2.4 → 0.3.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 (158) hide show
  1. package/dist/esm/agents-file.js +53 -34
  2. package/dist/esm/agents-file.js.map +1 -1
  3. package/dist/esm/align.d.ts +121 -0
  4. package/dist/esm/align.js +197 -0
  5. package/dist/esm/align.js.map +1 -0
  6. package/dist/esm/approvals.js +63 -29
  7. package/dist/esm/approvals.js.map +1 -1
  8. package/dist/esm/attach-preflight.d.ts +85 -0
  9. package/dist/esm/attach-preflight.js +189 -0
  10. package/dist/esm/attach-preflight.js.map +1 -0
  11. package/dist/esm/bootstrap.js +103 -117
  12. package/dist/esm/bootstrap.js.map +1 -1
  13. package/dist/esm/bridge-events.js +96 -71
  14. package/dist/esm/bridge-events.js.map +1 -1
  15. package/dist/esm/capabilities.d.ts +0 -5
  16. package/dist/esm/capabilities.js +32 -28
  17. package/dist/esm/capabilities.js.map +1 -1
  18. package/dist/esm/chunk-identity.d.ts +52 -0
  19. package/dist/esm/chunk-identity.js +102 -0
  20. package/dist/esm/chunk-identity.js.map +1 -0
  21. package/dist/esm/claim.d.ts +187 -0
  22. package/dist/esm/claim.js +349 -0
  23. package/dist/esm/claim.js.map +1 -0
  24. package/dist/esm/contracts.d.ts +13 -0
  25. package/dist/esm/driver.d.ts +83 -0
  26. package/dist/esm/driver.js +138 -0
  27. package/dist/esm/driver.js.map +1 -0
  28. package/dist/esm/durability.d.ts +263 -0
  29. package/dist/esm/durability.js +230 -0
  30. package/dist/esm/durability.js.map +1 -0
  31. package/dist/esm/errors.js +28 -24
  32. package/dist/esm/errors.js.map +1 -1
  33. package/dist/esm/file-diff.js +151 -135
  34. package/dist/esm/file-diff.js.map +1 -1
  35. package/dist/esm/git-exec.js +51 -62
  36. package/dist/esm/git-exec.js.map +1 -1
  37. package/dist/esm/harness-cwd.js +24 -19
  38. package/dist/esm/harness-cwd.js.map +1 -1
  39. package/dist/esm/index.d.ts +30 -8
  40. package/dist/esm/index.js +23 -91
  41. package/dist/esm/instance-store.d.ts +88 -0
  42. package/dist/esm/instance-store.js +67 -0
  43. package/dist/esm/instance-store.js.map +1 -0
  44. package/dist/esm/journal-bytes.d.ts +67 -0
  45. package/dist/esm/journal-bytes.js +110 -0
  46. package/dist/esm/journal-bytes.js.map +1 -0
  47. package/dist/esm/journal-reader.d.ts +66 -0
  48. package/dist/esm/journal-reader.js +228 -0
  49. package/dist/esm/journal-reader.js.map +1 -0
  50. package/dist/esm/journal-sweep.d.ts +113 -0
  51. package/dist/esm/journal-sweep.js +309 -0
  52. package/dist/esm/journal-sweep.js.map +1 -0
  53. package/dist/esm/journal.d.ts +542 -0
  54. package/dist/esm/journal.js +679 -0
  55. package/dist/esm/journal.js.map +1 -0
  56. package/dist/esm/key.js +36 -33
  57. package/dist/esm/key.js.map +1 -1
  58. package/dist/esm/middleware.d.ts +50 -2
  59. package/dist/esm/middleware.js +335 -208
  60. package/dist/esm/middleware.js.map +1 -1
  61. package/dist/esm/ngrok.js +75 -49
  62. package/dist/esm/ngrok.js.map +1 -1
  63. package/dist/esm/policy.js +43 -34
  64. package/dist/esm/policy.js.map +1 -1
  65. package/dist/esm/projection.js +16 -8
  66. package/dist/esm/projection.js.map +1 -1
  67. package/dist/esm/reap.d.ts +238 -0
  68. package/dist/esm/reap.js +355 -0
  69. package/dist/esm/reap.js.map +1 -0
  70. package/dist/esm/reclaim.d.ts +84 -0
  71. package/dist/esm/reclaim.js +106 -0
  72. package/dist/esm/reclaim.js.map +1 -0
  73. package/dist/esm/remote-tools.js +73 -62
  74. package/dist/esm/remote-tools.js.map +1 -1
  75. package/dist/esm/run.d.ts +93 -25
  76. package/dist/esm/run.js +274 -79
  77. package/dist/esm/run.js.map +1 -1
  78. package/dist/esm/runner.d.ts +119 -2
  79. package/dist/esm/runner.js +270 -51
  80. package/dist/esm/runner.js.map +1 -1
  81. package/dist/esm/sandbox.d.ts +3 -2
  82. package/dist/esm/sandbox.js +139 -123
  83. package/dist/esm/sandbox.js.map +1 -1
  84. package/dist/esm/secrets.js +39 -47
  85. package/dist/esm/secrets.js.map +1 -1
  86. package/dist/esm/setup-plan.js +22 -14
  87. package/dist/esm/setup-plan.js.map +1 -1
  88. package/dist/esm/shell.d.ts +8 -0
  89. package/dist/esm/shell.js +197 -158
  90. package/dist/esm/shell.js.map +1 -1
  91. package/dist/esm/testkit/conformance.d.ts +16 -0
  92. package/dist/esm/testkit/conformance.js +97 -0
  93. package/dist/esm/testkit/conformance.js.map +1 -0
  94. package/dist/esm/testkit/durable-run-fields-conformance.d.ts +4 -0
  95. package/dist/esm/testkit/durable-run-fields-conformance.js +95 -0
  96. package/dist/esm/testkit/durable-run-fields-conformance.js.map +1 -0
  97. package/dist/esm/testkit/journal-conformance.d.ts +51 -0
  98. package/dist/esm/testkit/journal-conformance.js +378 -0
  99. package/dist/esm/testkit/journal-conformance.js.map +1 -0
  100. package/dist/esm/testkit/reaper-conformance.d.ts +37 -0
  101. package/dist/esm/testkit/reaper-conformance.js +847 -0
  102. package/dist/esm/testkit/reaper-conformance.js.map +1 -0
  103. package/dist/esm/testkit/shell-spawn.d.ts +2 -0
  104. package/dist/esm/testkit/shell-spawn.js +60 -0
  105. package/dist/esm/testkit/shell-spawn.js.map +1 -0
  106. package/dist/esm/testkit/takeover-conformance.d.ts +24 -0
  107. package/dist/esm/testkit/takeover-conformance.js +685 -0
  108. package/dist/esm/testkit/takeover-conformance.js.map +1 -0
  109. package/dist/esm/tool-bridge.js +227 -180
  110. package/dist/esm/tool-bridge.js.map +1 -1
  111. package/dist/esm/tool-history.d.ts +62 -0
  112. package/dist/esm/tool-history.js +171 -0
  113. package/dist/esm/tool-history.js.map +1 -0
  114. package/dist/esm/watch.js +310 -236
  115. package/dist/esm/watch.js.map +1 -1
  116. package/dist/esm/workspace.d.ts +1 -1
  117. package/dist/esm/workspace.js +49 -28
  118. package/dist/esm/workspace.js.map +1 -1
  119. package/package.json +16 -6
  120. package/skills/ai-sandbox/SKILL.md +658 -20
  121. package/src/align.ts +297 -0
  122. package/src/attach-preflight.ts +292 -0
  123. package/src/capabilities.ts +4 -13
  124. package/src/chunk-identity.ts +154 -0
  125. package/src/claim.ts +479 -0
  126. package/src/contracts.ts +13 -0
  127. package/src/driver.ts +205 -0
  128. package/src/durability.ts +380 -0
  129. package/src/index.ts +212 -27
  130. package/src/instance-store.ts +122 -0
  131. package/src/journal-bytes.ts +136 -0
  132. package/src/journal-reader.ts +359 -0
  133. package/src/journal-sweep.ts +406 -0
  134. package/src/journal.ts +875 -0
  135. package/src/middleware.ts +470 -30
  136. package/src/reap.ts +723 -0
  137. package/src/reclaim.ts +191 -0
  138. package/src/run.ts +365 -75
  139. package/src/runner.ts +347 -3
  140. package/src/sandbox.ts +38 -8
  141. package/src/shell.ts +106 -38
  142. package/src/testkit/conformance.ts +117 -0
  143. package/src/testkit/durable-run-fields-conformance.ts +147 -0
  144. package/src/testkit/journal-conformance.ts +676 -0
  145. package/src/testkit/reaper-conformance.ts +1201 -0
  146. package/src/testkit/shell-spawn.ts +67 -0
  147. package/src/testkit/takeover-conformance.ts +1040 -0
  148. package/src/tool-history.ts +245 -0
  149. package/src/workspace.ts +1 -1
  150. package/dist/esm/index.js.map +0 -1
  151. package/dist/esm/run-log.d.ts +0 -81
  152. package/dist/esm/run-log.js +0 -107
  153. package/dist/esm/run-log.js.map +0 -1
  154. package/dist/esm/store.d.ts +0 -53
  155. package/dist/esm/store.js +0 -34
  156. package/dist/esm/store.js.map +0 -1
  157. package/src/run-log.ts +0 -224
  158. package/src/store.ts +0 -83
package/src/run-log.ts DELETED
@@ -1,224 +0,0 @@
1
- /**
2
- * Resumable run event-log — the primitive that lets a trigger (e.g. a
3
- * Cloudflare Worker) start an agent run and return immediately while a durable
4
- * orchestrator (e.g. a Durable Object) drives the run and persists every
5
- * emitted {@link StreamChunk} under a monotonic `seq`.
6
- *
7
- * Clients tail the log from a cursor (`fromSeq`), so a dropped connection, a new
8
- * browser tab, or an orchestrator that hibernated between chunks all reconnect
9
- * cleanly: replay everything after the client's last-seen `seq`, then live-tail
10
- * until the run reaches a terminal status. The *run* never depends on any single
11
- * connection staying open — that is what makes the serverless/edge model work.
12
- *
13
- * This module is transport- and storage-agnostic. {@link InMemoryRunEventLog} is
14
- * the default (single-process / tests); a durable backend (DO storage, KV, SQL)
15
- * implements the same {@link RunEventLog} interface — see the Cloudflare example.
16
- */
17
- import type { StreamChunk } from '@tanstack/ai'
18
-
19
- /** A terminal run status: no further events will be appended. */
20
- export type TerminalRunStatus = 'done' | 'error' | 'aborted'
21
-
22
- /** Lifecycle status of a run. `done`/`error`/`aborted` are terminal. */
23
- export type RunStatus = 'running' | TerminalRunStatus
24
-
25
- const TERMINAL: ReadonlySet<RunStatus> = new Set<RunStatus>([
26
- 'done',
27
- 'error',
28
- 'aborted',
29
- ])
30
-
31
- /** Whether a run status is terminal (no further events will be appended). */
32
- export function isTerminalRunStatus(status: RunStatus): boolean {
33
- return TERMINAL.has(status)
34
- }
35
-
36
- export interface RunError {
37
- message: string
38
- code?: string
39
- }
40
-
41
- /** Durable bookkeeping for a single run. */
42
- export interface RunRecord {
43
- runId: string
44
- threadId?: string
45
- status: RunStatus
46
- /** Seq of the last appended event, or `-1` when no events yet. */
47
- lastSeq: number
48
- error?: RunError
49
- createdAt: number
50
- updatedAt: number
51
- }
52
-
53
- /** One persisted event: a chunk plus its monotonic, gap-free sequence number. */
54
- export interface RunEvent {
55
- seq: number
56
- chunk: StreamChunk
57
- }
58
-
59
- export interface RunEventLogReadOptions {
60
- /**
61
- * Exclusive cursor: only events with `seq > fromSeq` are yielded. Pass the
62
- * client's last-seen `seq` to resume; omit (or `-1`) to replay from the start.
63
- */
64
- fromSeq?: number
65
- /** Stop tailing when this fires (e.g. the client disconnected). */
66
- signal?: AbortSignal
67
- }
68
-
69
- /**
70
- * Append-only, `seq`-indexed log of a run's stream, with resumable reads.
71
- *
72
- * Contract:
73
- * - `append` assigns the next `seq` (0, 1, 2, …) and returns it.
74
- * - `read` yields the backlog after `fromSeq` in order, then live-tails new
75
- * events, and RETURNS once the run is terminal and the cursor has caught up.
76
- * - All methods reject for an unknown `runId` except `get`, which resolves null.
77
- */
78
- export interface RunEventLog {
79
- /** Idempotently create (or return) the run record. */
80
- open: (input: { runId: string; threadId?: string }) => Promise<RunRecord>
81
- /** Append one chunk; resolves with its assigned `seq`. */
82
- append: (runId: string, chunk: StreamChunk) => Promise<number>
83
- /** Move the run to a terminal status. Idempotent for the same status. */
84
- finish: (
85
- runId: string,
86
- status: TerminalRunStatus,
87
- error?: RunError,
88
- ) => Promise<void>
89
- /** Current record, or null if the run is unknown. */
90
- get: (runId: string) => Promise<RunRecord | null>
91
- /** Replay-then-tail events with `seq > fromSeq` until the run is terminal. */
92
- read: (
93
- runId: string,
94
- options?: RunEventLogReadOptions,
95
- ) => AsyncIterable<RunEvent>
96
- }
97
-
98
- /** Per-run state for the in-memory log. */
99
- interface RunState {
100
- record: RunRecord
101
- chunks: Array<StreamChunk>
102
- /** Resolved (and cleared) whenever an event is appended or status changes. */
103
- waiters: Set<() => void>
104
- }
105
-
106
- /**
107
- * Single-process {@link RunEventLog}. Backs `read`'s live-tail with an internal
108
- * waiter set: `append`/`finish` wake every blocked reader. Suitable for a
109
- * long-running Node host, tests, and as the reference implementation a durable
110
- * backend mirrors.
111
- */
112
- export class InMemoryRunEventLog implements RunEventLog {
113
- private readonly runs = new Map<string, RunState>()
114
-
115
- private now(): number {
116
- return Date.now()
117
- }
118
-
119
- private require(runId: string): RunState {
120
- const state = this.runs.get(runId)
121
- if (!state) throw new Error(`run-log: unknown runId "${runId}"`)
122
- return state
123
- }
124
-
125
- private wake(state: RunState): void {
126
- const waiters = [...state.waiters]
127
- state.waiters.clear()
128
- for (const resolve of waiters) resolve()
129
- }
130
-
131
- // Mutators return a Promise without `async` so contract violations REJECT
132
- // (rather than throwing synchronously from a Promise-typed method — a
133
- // `.catch()` footgun) without an `await`-less async body.
134
- open(input: { runId: string; threadId?: string }): Promise<RunRecord> {
135
- const existing = this.runs.get(input.runId)
136
- if (existing) return Promise.resolve({ ...existing.record })
137
- const now = this.now()
138
- const record: RunRecord = {
139
- runId: input.runId,
140
- ...(input.threadId !== undefined ? { threadId: input.threadId } : {}),
141
- status: 'running',
142
- lastSeq: -1,
143
- createdAt: now,
144
- updatedAt: now,
145
- }
146
- this.runs.set(input.runId, { record, chunks: [], waiters: new Set() })
147
- return Promise.resolve({ ...record })
148
- }
149
-
150
- append(runId: string, chunk: StreamChunk): Promise<number> {
151
- const state = this.runs.get(runId)
152
- if (!state) {
153
- return Promise.reject(new Error(`run-log: unknown runId "${runId}"`))
154
- }
155
- if (isTerminalRunStatus(state.record.status)) {
156
- return Promise.reject(
157
- new Error(
158
- `run-log: cannot append to terminal run "${runId}" (status=${state.record.status})`,
159
- ),
160
- )
161
- }
162
- // Derive seq from the record's cursor (not `chunks.length`) so the gap-free
163
- // invariant holds the same way the durable backend computes it, even if the
164
- // backlog is ever trimmed/compacted.
165
- const seq = state.record.lastSeq + 1
166
- state.chunks.push(chunk)
167
- state.record.lastSeq = seq
168
- state.record.updatedAt = this.now()
169
- this.wake(state)
170
- return Promise.resolve(seq)
171
- }
172
-
173
- finish(
174
- runId: string,
175
- status: TerminalRunStatus,
176
- error?: RunError,
177
- ): Promise<void> {
178
- const state = this.runs.get(runId)
179
- if (!state) {
180
- return Promise.reject(new Error(`run-log: unknown runId "${runId}"`))
181
- }
182
- if (isTerminalRunStatus(state.record.status)) return Promise.resolve()
183
- state.record.status = status
184
- if (error !== undefined) state.record.error = error
185
- state.record.updatedAt = this.now()
186
- this.wake(state)
187
- return Promise.resolve()
188
- }
189
-
190
- get(runId: string): Promise<RunRecord | null> {
191
- const state = this.runs.get(runId)
192
- return Promise.resolve(state ? { ...state.record } : null)
193
- }
194
-
195
- async *read(
196
- runId: string,
197
- options?: RunEventLogReadOptions,
198
- ): AsyncIterable<RunEvent> {
199
- const state = this.require(runId)
200
- const signal = options?.signal
201
- let cursor = options?.fromSeq ?? -1
202
- while (!signal?.aborted) {
203
- while (cursor < state.record.lastSeq) {
204
- cursor += 1
205
- const chunk = state.chunks[cursor]
206
- if (chunk !== undefined) yield { seq: cursor, chunk }
207
- }
208
- if (isTerminalRunStatus(state.record.status)) return
209
- await this.waitForChange(state, signal)
210
- }
211
- }
212
-
213
- private waitForChange(state: RunState, signal?: AbortSignal): Promise<void> {
214
- return new Promise<void>((resolve) => {
215
- const wake = (): void => {
216
- state.waiters.delete(wake)
217
- if (signal) signal.removeEventListener('abort', wake)
218
- resolve()
219
- }
220
- state.waiters.add(wake)
221
- if (signal) signal.addEventListener('abort', wake, { once: true })
222
- })
223
- }
224
- }
package/src/store.ts DELETED
@@ -1,83 +0,0 @@
1
- /**
2
- * Persistence seams for the sandbox layer.
3
- *
4
- * v1 ships ONLY in-memory implementations (single-process resume). These are
5
- * deliberately pluggable OPTIONAL capabilities so the future persistence
6
- * package can `provide` durable implementations (D1/Postgres/Durable Objects)
7
- * without the sandbox layer changing. Do NOT hardcode storage here.
8
- */
9
-
10
- /** One persisted sandbox instance, keyed by the compound sandbox instance key. */
11
- export interface SandboxRecord {
12
- /** Compound key (see computeSandboxKey). */
13
- key: string
14
- /** Provider name that owns `providerSandboxId`. */
15
- provider: string
16
- /** Provider-assigned sandbox id used to resume. */
17
- providerSandboxId: string
18
- /** Most recent snapshot id, when the provider supports snapshots. */
19
- latestSnapshotId?: string
20
- threadId: string
21
- latestRunId?: string
22
- /** Epoch ms of last write (for keepAlive / GC by the persistence layer). */
23
- updatedAt: number
24
- }
25
-
26
- /** Maps a compound key to the provider sandbox that should be resumed. */
27
- export interface SandboxStore {
28
- get: (key: string) => Promise<SandboxRecord | null>
29
- upsert: (record: SandboxRecord) => Promise<void>
30
- delete: (key: string) => Promise<void>
31
- }
32
-
33
- /**
34
- * Mutual exclusion around sandbox ensure so two concurrent runs for the same
35
- * thread don't both create a sandbox. The in-memory default is single-process;
36
- * the persistence layer provides a distributed lock (e.g. a Durable Object).
37
- */
38
- export interface LockStore {
39
- withLock: <T>(key: string, fn: () => Promise<T>) => Promise<T>
40
- }
41
-
42
- /** In-memory {@link SandboxStore}. Resume works only within one process. */
43
- export class InMemorySandboxStore implements SandboxStore {
44
- private readonly map = new Map<string, SandboxRecord>()
45
-
46
- get(key: string): Promise<SandboxRecord | null> {
47
- return Promise.resolve(this.map.get(key) ?? null)
48
- }
49
-
50
- upsert(record: SandboxRecord): Promise<void> {
51
- this.map.set(record.key, record)
52
- return Promise.resolve()
53
- }
54
-
55
- delete(key: string): Promise<void> {
56
- this.map.delete(key)
57
- return Promise.resolve()
58
- }
59
- }
60
-
61
- /**
62
- * In-memory {@link LockStore} — a per-key promise chain. Correct within a
63
- * single process; multi-instance correctness needs a distributed lock from the
64
- * persistence layer.
65
- */
66
- export class InMemoryLockStore implements LockStore {
67
- private readonly chains = new Map<string, Promise<unknown>>()
68
-
69
- withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {
70
- const prior = this.chains.get(key) ?? Promise.resolve()
71
- // Chain after the prior holder regardless of how it settled.
72
- const run = prior.then(fn, fn)
73
- // Keep the chain alive but swallow rejections so one failure doesn't poison the lock.
74
- this.chains.set(
75
- key,
76
- run.then(
77
- () => undefined,
78
- () => undefined,
79
- ),
80
- )
81
- return run
82
- }
83
- }