@vendoai/vendo 0.4.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 (212) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +28 -0
  3. package/bin/vendo.mjs +4 -0
  4. package/dist/ai-sdk.d.ts +35 -0
  5. package/dist/ai-sdk.js +62 -0
  6. package/dist/auth-presets/auth-js.d.ts +14 -0
  7. package/dist/auth-presets/auth-js.js +91 -0
  8. package/dist/auth-presets/auth0.d.ts +23 -0
  9. package/dist/auth-presets/auth0.js +98 -0
  10. package/dist/auth-presets/clerk.d.ts +23 -0
  11. package/dist/auth-presets/clerk.js +65 -0
  12. package/dist/auth-presets/conformance.d.ts +42 -0
  13. package/dist/auth-presets/conformance.js +139 -0
  14. package/dist/auth-presets/identity.d.ts +56 -0
  15. package/dist/auth-presets/identity.js +152 -0
  16. package/dist/auth-presets/index.d.ts +14 -0
  17. package/dist/auth-presets/index.js +12 -0
  18. package/dist/auth-presets/jwt.d.ts +16 -0
  19. package/dist/auth-presets/jwt.js +54 -0
  20. package/dist/auth-presets/shared.d.ts +35 -0
  21. package/dist/auth-presets/shared.js +1 -0
  22. package/dist/auth-presets/supabase.d.ts +42 -0
  23. package/dist/auth-presets/supabase.js +229 -0
  24. package/dist/byo-approvals.d.ts +37 -0
  25. package/dist/byo-approvals.js +189 -0
  26. package/dist/capability-misses.d.ts +38 -0
  27. package/dist/capability-misses.js +172 -0
  28. package/dist/catalog.d.ts +19 -0
  29. package/dist/catalog.js +152 -0
  30. package/dist/cli/cloud/args.d.ts +3 -0
  31. package/dist/cli/cloud/args.js +32 -0
  32. package/dist/cli/cloud/auth.d.ts +17 -0
  33. package/dist/cli/cloud/auth.js +123 -0
  34. package/dist/cli/cloud/client.d.ts +28 -0
  35. package/dist/cli/cloud/client.js +133 -0
  36. package/dist/cli/cloud/command.d.ts +22 -0
  37. package/dist/cli/cloud/command.js +73 -0
  38. package/dist/cli/cloud/deploy.d.ts +26 -0
  39. package/dist/cli/cloud/deploy.js +187 -0
  40. package/dist/cli/cloud/device-login.d.ts +32 -0
  41. package/dist/cli/cloud/device-login.js +259 -0
  42. package/dist/cli/cloud/index.d.ts +5 -0
  43. package/dist/cli/cloud/index.js +59 -0
  44. package/dist/cli/cloud/keys.d.ts +2 -0
  45. package/dist/cli/cloud/keys.js +31 -0
  46. package/dist/cli/cloud/members.d.ts +3 -0
  47. package/dist/cli/cloud/members.js +25 -0
  48. package/dist/cli/cloud/output.d.ts +4 -0
  49. package/dist/cli/cloud/output.js +12 -0
  50. package/dist/cli/cloud/pending-claim.d.ts +29 -0
  51. package/dist/cli/cloud/pending-claim.js +38 -0
  52. package/dist/cli/cloud/read.d.ts +3 -0
  53. package/dist/cli/cloud/read.js +12 -0
  54. package/dist/cli/cloud/services.d.ts +2 -0
  55. package/dist/cli/cloud/services.js +9 -0
  56. package/dist/cli/cloud/session.d.ts +13 -0
  57. package/dist/cli/cloud/session.js +41 -0
  58. package/dist/cli/cloud-init.d.ts +60 -0
  59. package/dist/cli/cloud-init.js +149 -0
  60. package/dist/cli/dep-versions.d.ts +26 -0
  61. package/dist/cli/dep-versions.js +77 -0
  62. package/dist/cli/doctor-codes.d.ts +58 -0
  63. package/dist/cli/doctor-codes.js +60 -0
  64. package/dist/cli/doctor-live.d.ts +67 -0
  65. package/dist/cli/doctor-live.js +169 -0
  66. package/dist/cli/doctor.d.ts +52 -0
  67. package/dist/cli/doctor.js +467 -0
  68. package/dist/cli/eject.d.ts +29 -0
  69. package/dist/cli/eject.js +141 -0
  70. package/dist/cli/extract/apply.d.ts +26 -0
  71. package/dist/cli/extract/apply.js +123 -0
  72. package/dist/cli/extract/claude-cli-harness.d.ts +18 -0
  73. package/dist/cli/extract/claude-cli-harness.js +132 -0
  74. package/dist/cli/extract/claude-harness.d.ts +14 -0
  75. package/dist/cli/extract/claude-harness.js +127 -0
  76. package/dist/cli/extract/codex-cli-harness.d.ts +31 -0
  77. package/dist/cli/extract/codex-cli-harness.js +154 -0
  78. package/dist/cli/extract/delegate.d.ts +21 -0
  79. package/dist/cli/extract/delegate.js +77 -0
  80. package/dist/cli/extract/extraction.d.ts +65 -0
  81. package/dist/cli/extract/extraction.js +300 -0
  82. package/dist/cli/extract/gateway-fuel.d.ts +69 -0
  83. package/dist/cli/extract/gateway-fuel.js +69 -0
  84. package/dist/cli/extract/harness.d.ts +114 -0
  85. package/dist/cli/extract/harness.js +84 -0
  86. package/dist/cli/extract/index.d.ts +9 -0
  87. package/dist/cli/extract/index.js +9 -0
  88. package/dist/cli/extract/npx-engine-harness.d.ts +73 -0
  89. package/dist/cli/extract/npx-engine-harness.js +225 -0
  90. package/dist/cli/extract/stages.d.ts +149 -0
  91. package/dist/cli/extract/stages.js +398 -0
  92. package/dist/cli/framework.d.ts +8 -0
  93. package/dist/cli/framework.js +36 -0
  94. package/dist/cli/init-auth.d.ts +67 -0
  95. package/dist/cli/init-auth.js +142 -0
  96. package/dist/cli/init-scaffolds.d.ts +28 -0
  97. package/dist/cli/init-scaffolds.js +235 -0
  98. package/dist/cli/init.d.ts +115 -0
  99. package/dist/cli/init.js +1008 -0
  100. package/dist/cli/mcp/index.d.ts +7 -0
  101. package/dist/cli/mcp/index.js +59 -0
  102. package/dist/cli/mcp/registry.d.ts +9 -0
  103. package/dist/cli/mcp/registry.js +117 -0
  104. package/dist/cli/mcp/server-json.d.ts +12 -0
  105. package/dist/cli/mcp/server-json.js +65 -0
  106. package/dist/cli/mcp/server.schema.json +574 -0
  107. package/dist/cli/mcp/verify-domain.d.ts +11 -0
  108. package/dist/cli/mcp/verify-domain.js +48 -0
  109. package/dist/cli/playground/app/embed-entry.d.ts +20 -0
  110. package/dist/cli/playground/app/embed-entry.js +46 -0
  111. package/dist/cli/playground/app/fake-client.d.ts +3 -0
  112. package/dist/cli/playground/app/fake-client.js +215 -0
  113. package/dist/cli/playground/app/fixtures.d.ts +47 -0
  114. package/dist/cli/playground/app/fixtures.js +472 -0
  115. package/dist/cli/playground/app/main.d.ts +1 -0
  116. package/dist/cli/playground/app/main.js +108 -0
  117. package/dist/cli/playground/app/scenario-mount.d.ts +13 -0
  118. package/dist/cli/playground/app/scenario-mount.js +48 -0
  119. package/dist/cli/playground/app/scenarios.d.ts +25 -0
  120. package/dist/cli/playground/app/scenarios.js +152 -0
  121. package/dist/cli/playground/app/theme-editor.d.ts +13 -0
  122. package/dist/cli/playground/app/theme-editor.js +149 -0
  123. package/dist/cli/playground/app/theme-state.d.ts +29 -0
  124. package/dist/cli/playground/app/theme-state.js +151 -0
  125. package/dist/cli/playground/bundle.gen.d.ts +2 -0
  126. package/dist/cli/playground/bundle.gen.js +2 -0
  127. package/dist/cli/playground/embed-bundle.gen.d.ts +2 -0
  128. package/dist/cli/playground/embed-bundle.gen.js +2 -0
  129. package/dist/cli/playground.d.ts +33 -0
  130. package/dist/cli/playground.js +120 -0
  131. package/dist/cli/pretty.d.ts +48 -0
  132. package/dist/cli/pretty.js +330 -0
  133. package/dist/cli/refine.d.ts +43 -0
  134. package/dist/cli/refine.js +181 -0
  135. package/dist/cli/semantics.d.ts +31 -0
  136. package/dist/cli/semantics.js +122 -0
  137. package/dist/cli/shared.d.ts +68 -0
  138. package/dist/cli/shared.js +184 -0
  139. package/dist/cli/sync.d.ts +36 -0
  140. package/dist/cli/sync.js +202 -0
  141. package/dist/cli/theme/color.d.ts +15 -0
  142. package/dist/cli/theme/color.js +188 -0
  143. package/dist/cli/theme/css-vars.d.ts +13 -0
  144. package/dist/cli/theme/css-vars.js +40 -0
  145. package/dist/cli/theme/entry-candidates.d.ts +5 -0
  146. package/dist/cli/theme/entry-candidates.js +22 -0
  147. package/dist/cli/theme/extract-theme.d.ts +184 -0
  148. package/dist/cli/theme/extract-theme.js +358 -0
  149. package/dist/cli/theme/walk.d.ts +2 -0
  150. package/dist/cli/theme/walk.js +24 -0
  151. package/dist/cli.d.ts +2 -0
  152. package/dist/cli.js +333 -0
  153. package/dist/cloud-apps.d.ts +17 -0
  154. package/dist/cloud-apps.js +46 -0
  155. package/dist/cloud-console.d.ts +32 -0
  156. package/dist/cloud-console.js +72 -0
  157. package/dist/cloud-tools.d.ts +23 -0
  158. package/dist/cloud-tools.js +196 -0
  159. package/dist/connections.d.ts +71 -0
  160. package/dist/connections.js +192 -0
  161. package/dist/deployment-identity.d.ts +12 -0
  162. package/dist/deployment-identity.js +69 -0
  163. package/dist/dev-creds/model.d.ts +65 -0
  164. package/dist/dev-creds/model.js +159 -0
  165. package/dist/dev-creds/resolve.d.ts +36 -0
  166. package/dist/dev-creds/resolve.js +57 -0
  167. package/dist/hosted-store.d.ts +52 -0
  168. package/dist/hosted-store.js +322 -0
  169. package/dist/index.d.ts +13 -0
  170. package/dist/index.js +1 -0
  171. package/dist/mastra.d.ts +46 -0
  172. package/dist/mastra.js +88 -0
  173. package/dist/react.d.ts +10 -0
  174. package/dist/react.js +32 -0
  175. package/dist/refine.d.ts +244 -0
  176. package/dist/refine.js +599 -0
  177. package/dist/remixable.d.ts +18 -0
  178. package/dist/remixable.js +42 -0
  179. package/dist/runtime-capture.d.ts +18 -0
  180. package/dist/runtime-capture.js +111 -0
  181. package/dist/sandbox-wire.d.ts +101 -0
  182. package/dist/sandbox-wire.js +101 -0
  183. package/dist/sandbox.d.ts +38 -0
  184. package/dist/sandbox.js +389 -0
  185. package/dist/server.d.ts +216 -0
  186. package/dist/server.js +1320 -0
  187. package/dist/sync-impact.d.ts +14 -0
  188. package/dist/sync-impact.js +75 -0
  189. package/dist/turn-liveness.d.ts +33 -0
  190. package/dist/turn-liveness.js +105 -0
  191. package/dist/wire/approvals.d.ts +10 -0
  192. package/dist/wire/approvals.js +59 -0
  193. package/dist/wire/apps.d.ts +5 -0
  194. package/dist/wire/apps.js +155 -0
  195. package/dist/wire/automations.d.ts +5 -0
  196. package/dist/wire/automations.js +57 -0
  197. package/dist/wire/box.d.ts +3 -0
  198. package/dist/wire/box.js +268 -0
  199. package/dist/wire/connections.d.ts +5 -0
  200. package/dist/wire/connections.js +47 -0
  201. package/dist/wire/context.d.ts +22 -0
  202. package/dist/wire/context.js +190 -0
  203. package/dist/wire/doctor.d.ts +19 -0
  204. package/dist/wire/doctor.js +116 -0
  205. package/dist/wire/misc.d.ts +18 -0
  206. package/dist/wire/misc.js +227 -0
  207. package/dist/wire/shared.d.ts +158 -0
  208. package/dist/wire/shared.js +134 -0
  209. package/dist/wire/threads.d.ts +3 -0
  210. package/dist/wire/threads.js +68 -0
  211. package/package.json +151 -0
  212. package/skills/vendo-setup/SKILL.md +128 -0
@@ -0,0 +1,389 @@
1
+ import { VendoError } from "@vendoai/core";
2
+ import { deploymentIdentityHeaders } from "./deployment-identity.js";
3
+ import { CLOUD_BOX_PORT, CLOUD_SANDBOX_PATH, CLOUD_SNAPSHOT_REF_PREFIX, CLOUD_SNAPSHOTS_SUBPATH, CONSOLE_SNAPSHOT_REF_PREFIX, } from "./sandbox-wire.js";
4
+ /** Console error codes forwarded as-is when they are wire-legal VendoError
5
+ * codes (same posture as the apps block's cloud share/publish client). The
6
+ * console's "unavailable"/"quota-exhausted" have no VendoError twin; they fall
7
+ * to sandbox-unavailable and the 402 → cloud-required mapping respectively. */
8
+ const CLOUD_ERROR_CODES = new Set([
9
+ "validation",
10
+ "blocked",
11
+ "not-implemented",
12
+ "cloud-required",
13
+ "not-found",
14
+ "conflict",
15
+ ]);
16
+ /** Same default as the e2b adapter and the retired ENG-295 broker client:
17
+ * generous enough for a slow machine boot, small enough that a hung console
18
+ * request can't wedge a generation forever. */
19
+ const DEFAULT_TIMEOUT_MS = 300_000;
20
+ const encoder = new TextEncoder();
21
+ const decoder = new TextDecoder();
22
+ const toBytes = (data) => typeof data === "string" ? encoder.encode(data) : data;
23
+ const toArrayBuffer = (value) => value.slice().buffer;
24
+ /** btoa/atob-based codecs (the console speaks base64 JSON envelopes); chunked
25
+ * like the console's own encoder, and Buffer-free so the umbrella's server
26
+ * surface keeps loading on edge/Worker targets. */
27
+ function encodeBase64(bytes) {
28
+ let binary = "";
29
+ for (let offset = 0; offset < bytes.length; offset += 32_768) {
30
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + 32_768));
31
+ }
32
+ return btoa(binary);
33
+ }
34
+ function decodeBase64(value) {
35
+ try {
36
+ return Uint8Array.from(atob(value), (character) => character.charCodeAt(0));
37
+ }
38
+ catch {
39
+ // Console garbage is the SERVICE misbehaving, never the caller's fault —
40
+ // same posture as every malformed-success branch below.
41
+ throw new VendoError("sandbox-unavailable", "Vendo Cloud sandbox returned invalid base64 content");
42
+ }
43
+ }
44
+ async function raiseCloudError(response) {
45
+ let payload;
46
+ try {
47
+ payload = JSON.parse(await response.text());
48
+ }
49
+ catch {
50
+ payload = undefined;
51
+ }
52
+ const error = typeof payload === "object" && payload !== null && "error" in payload
53
+ ? payload.error
54
+ : undefined;
55
+ const message = typeof error?.message === "string"
56
+ ? error.message
57
+ : `Vendo Cloud sandbox request failed with ${response.status}`;
58
+ // The console's meter gate (quota-exhausted) rides HTTP 402 — the one
59
+ // "pay/upgrade to proceed" signal, same mapping as cloudConnections. 401
60
+ // (bad/revoked key) is the same "fix your Cloud standing" story for the
61
+ // host operator, so it keeps the ENG-295 client's cloud-required mapping —
62
+ // with the server's own message preserved.
63
+ if (response.status === 402 || response.status === 401) {
64
+ throw new VendoError("cloud-required", message);
65
+ }
66
+ const code = typeof error?.code === "string" && CLOUD_ERROR_CODES.has(error.code)
67
+ ? error.code
68
+ : "sandbox-unavailable";
69
+ throw new VendoError(code, message);
70
+ }
71
+ const parsePort = (env) => {
72
+ const port = Number(env.PORT ?? CLOUD_BOX_PORT);
73
+ return Number.isInteger(port) && port > 0 && port <= 65_535 ? port : CLOUD_BOX_PORT;
74
+ };
75
+ const toBase64Url = (value) => encodeBase64(encoder.encode(value)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
76
+ const encodeSnapshotRef = (state) => `${CLOUD_SNAPSHOT_REF_PREFIX}${toBase64Url(JSON.stringify(state))}`;
77
+ const decodeSnapshotRef = (snapshotRef) => {
78
+ try {
79
+ if (!snapshotRef.startsWith(CLOUD_SNAPSHOT_REF_PREFIX)
80
+ || snapshotRef.length <= CLOUD_SNAPSHOT_REF_PREFIX.length) {
81
+ throw new Error("unknown prefix");
82
+ }
83
+ const payload = snapshotRef.slice(CLOUD_SNAPSHOT_REF_PREFIX.length)
84
+ .replaceAll("-", "+").replaceAll("_", "/");
85
+ const state = JSON.parse(decoder.decode(Uint8Array.from(atob(payload), (character) => character.charCodeAt(0))));
86
+ if (state.version !== 2
87
+ || typeof state.machineId !== "string" || state.machineId.length === 0
88
+ || typeof state.ref !== "string" || !state.ref.startsWith(CONSOLE_SNAPSHOT_REF_PREFIX)) {
89
+ throw new Error("invalid payload");
90
+ }
91
+ if (state.allowedDomains !== undefined && !(Array.isArray(state.allowedDomains)
92
+ && state.allowedDomains.every((host) => typeof host === "string"))) {
93
+ throw new Error("invalid allowedDomains policy");
94
+ }
95
+ if (state.port !== undefined && !(Number.isInteger(state.port)
96
+ && state.port > 0 && state.port <= 65_535)) {
97
+ throw new Error("invalid port");
98
+ }
99
+ return {
100
+ version: 2,
101
+ machineId: state.machineId,
102
+ ref: state.ref,
103
+ ...(state.allowedDomains === undefined ? {} : { allowedDomains: [...state.allowedDomains] }),
104
+ ...(state.port === undefined ? {} : { port: state.port }),
105
+ };
106
+ }
107
+ catch {
108
+ throw new VendoError("validation", `Vendo Cloud snapshot references must start with "${CLOUD_SNAPSHOT_REF_PREFIX}" and carry a valid payload`);
109
+ }
110
+ };
111
+ /** True exactly for the "that state is already gone" answer that the seam's
112
+ * idempotent transitions (destroy twice, stop of a dead machine) absorb. */
113
+ const isGone = (error) => error instanceof VendoError && error.code === "not-found";
114
+ /** The Cloud sandbox adapter — the OSS side of the managed-sandbox seam: the
115
+ * execution-v2 SandboxAdapter speaking HTTP to the console's /api/v1/sandboxes
116
+ * routes (Vendo's pooled provider capacity, metered as sandbox_minutes). The
117
+ * wire contract — the ARTIFACT model, verified live — lives in
118
+ * sandbox-wire.ts. Cloned from cloudConnections' shape: behavior comes ONLY
119
+ * from constructor arguments (adapter rule — see selectSandbox in server.ts);
120
+ * the adapter never reads the environment.
121
+ *
122
+ * Provider particulars, versus the e2b reference port:
123
+ * - Snapshots are persistent artifacts that survive the machine; resume
124
+ * boots a NEW machine from one (fork when the source lives, wake when it
125
+ * is gone) and inherits NO network config, so every resume sends the
126
+ * applicable allowlist explicitly — the ref-recorded one bare, the
127
+ * caller's SandboxResumePolicy when a wake re-polices (Lane E replace
128
+ * semantics, native on the wire).
129
+ * - stop() destroys the machine: Cloud has no pause, and with artifacts
130
+ * surviving it, snapshot-then-destroy IS the sleep semantics; previously
131
+ * minted refs stay valid through it (the seam law).
132
+ * - Composite refs: the seam sees `vendo:v2:<base64url state>` carrying the
133
+ * console artifact ref, the source machine id (destroy-by-ref reaps a
134
+ * still-running source best-effort before the artifact GC), and the
135
+ * snapshot-time allowlist a bare resume re-applies.
136
+ * - `spec.template` is dropped from the wire: the create route takes none —
137
+ * the pooled base image (Node + the in-box agent) is Cloud's own.
138
+ *
139
+ * The machine object also carries adapter-private exec/files/url used for
140
+ * live-lane bootstrap and diagnostics — NOT part of the public seam (the
141
+ * in-box agent owns the inside of the box). */
142
+ export function cloudSandbox(options) {
143
+ const base = (options.baseUrl ?? "https://console.vendo.run").replace(/\/$/, "");
144
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
145
+ const fetchImpl = options.fetch ?? globalThis.fetch;
146
+ const send = async (path, init = {}) => {
147
+ const response = await fetchImpl(`${base}${CLOUD_SANDBOX_PATH}${path}`, {
148
+ ...init,
149
+ headers: {
150
+ authorization: `Bearer ${options.apiKey}`,
151
+ accept: "application/json",
152
+ // Interaction model: key-authed Cloud requests carry the deployment
153
+ // identity; the console meters usage from real traffic.
154
+ ...(await deploymentIdentityHeaders()),
155
+ ...init.headers,
156
+ },
157
+ signal: AbortSignal.timeout(timeoutMs),
158
+ });
159
+ if (!response.ok)
160
+ await raiseCloudError(response);
161
+ return response;
162
+ };
163
+ const sendJson = async (path, method, body) => {
164
+ const response = await send(path, {
165
+ method,
166
+ ...(body === undefined ? {} : {
167
+ headers: { "content-type": "application/json" },
168
+ body: JSON.stringify(body),
169
+ }),
170
+ });
171
+ try {
172
+ return await response.json();
173
+ }
174
+ catch {
175
+ return {};
176
+ }
177
+ };
178
+ const parseHandle = (payload) => {
179
+ const handle = payload;
180
+ if (typeof handle.id !== "string" || typeof handle.url !== "string") {
181
+ throw new VendoError("sandbox-unavailable", "Vendo Cloud sandbox returned no machine handle");
182
+ }
183
+ return { id: handle.id, url: handle.url };
184
+ };
185
+ const wrap = (handle, state) => {
186
+ const prefix = `/${encodeURIComponent(handle.id)}`;
187
+ /** POST /{id}/snapshot — mint a persistent artifact; the source keeps running. */
188
+ const mintArtifact = async () => {
189
+ const payload = await sendJson(`${prefix}/snapshot`, "POST");
190
+ if (typeof payload.ref !== "string" || payload.ref.length === 0) {
191
+ throw new VendoError("sandbox-unavailable", "Vendo Cloud sandbox returned no snapshot reference");
192
+ }
193
+ // A ref this adapter would itself refuse to carry must never reach a
194
+ // document — reject it as console garbage here instead.
195
+ if (!payload.ref.startsWith(CONSOLE_SNAPSHOT_REF_PREFIX)
196
+ || payload.ref.length <= CONSOLE_SNAPSHOT_REF_PREFIX.length) {
197
+ throw new VendoError("sandbox-unavailable", `Vendo Cloud sandbox returned a foreign snapshot reference (expected the "${CONSOLE_SNAPSHOT_REF_PREFIX}" prefix)`);
198
+ }
199
+ return payload.ref;
200
+ };
201
+ const remove = async () => {
202
+ try {
203
+ await sendJson(prefix, "DELETE");
204
+ }
205
+ catch (error) {
206
+ if (!isGone(error))
207
+ throw error;
208
+ }
209
+ };
210
+ // Seam rule: sleeping or destroying twice is not an error, and destroy is
211
+ // final. The one-shot promises make that hold under concurrency too —
212
+ // each transition is assigned synchronously, so racing callers share one
213
+ // console call chain, and a destroy during an in-flight stop serializes
214
+ // after it (e2b-adapter pattern).
215
+ let sleeping;
216
+ let destroying;
217
+ return {
218
+ id: handle.id,
219
+ async request(req) {
220
+ let payload;
221
+ try {
222
+ payload = await sendJson(`${prefix}/request`, "POST", {
223
+ method: req.method,
224
+ path: req.path.startsWith("/") ? req.path : `/${req.path}`,
225
+ // Absent port targets the canonical box port server-side; explicit
226
+ // ports (e.g. the in-box agent control port) route as-is.
227
+ ...(req.port === undefined ? {} : { port: req.port }),
228
+ ...(req.headers === undefined ? {} : { headers: req.headers }),
229
+ ...(req.body === undefined ? {} : { body_b64: encodeBase64(toBytes(req.body)) }),
230
+ });
231
+ }
232
+ catch (error) {
233
+ // Wave 7 — the seam's dead-machine signal. Cloud stop is final (no
234
+ // pause), so a conflict ("Sandbox is stopped") on the DATA path
235
+ // means the sweep destroyed the machine out from under this handle,
236
+ // exactly like a purged id's not-found: both become the thrown
237
+ // not-found the lifecycle's eviction/re-wake recovery keys on.
238
+ if (error instanceof VendoError && error.code === "conflict") {
239
+ throw new VendoError("not-found", `Vendo Cloud sandbox ${handle.id} is gone (destroyed by the provider): ${error.message}`);
240
+ }
241
+ throw error;
242
+ }
243
+ const proxied = payload;
244
+ if (typeof proxied.status !== "number" || typeof proxied.body_b64 !== "string") {
245
+ throw new VendoError("sandbox-unavailable", "Vendo Cloud sandbox returned an invalid proxy response");
246
+ }
247
+ const headers = typeof proxied.headers === "object" && proxied.headers !== null
248
+ ? Object.fromEntries(Object.entries(proxied.headers)
249
+ .filter((entry) => typeof entry[1] === "string"))
250
+ : {};
251
+ return { status: proxied.status, headers, body: decodeBase64(proxied.body_b64) };
252
+ },
253
+ async snapshot() {
254
+ // A checkpoint through a machine object that already slept or was
255
+ // destroyed is a caller bug — say so crisply instead of relaying
256
+ // whatever the console answers for the dead machine.
257
+ if (sleeping !== undefined || destroying !== undefined) {
258
+ throw new VendoError("conflict", "the machine is asleep or destroyed; resume its snapshot ref instead of checkpointing it");
259
+ }
260
+ return encodeSnapshotRef({
261
+ version: 2,
262
+ machineId: handle.id,
263
+ ref: await mintArtifact(),
264
+ ...(state.allowedDomains === undefined ? {} : { allowedDomains: [...state.allowedDomains] }),
265
+ port: state.port,
266
+ });
267
+ },
268
+ async stop() {
269
+ if (destroying !== undefined) {
270
+ await destroying;
271
+ return;
272
+ }
273
+ // Cloud sleep IS destruction: there is no pause, and snapshot
274
+ // artifacts survive the machine — the sleep flows mint their ref
275
+ // BEFORE stopping (machine-lifecycle.ts) and wake by resuming it.
276
+ sleeping ??= remove();
277
+ await sleeping;
278
+ },
279
+ async destroy() {
280
+ destroying ??= (sleeping ?? Promise.resolve())
281
+ .catch(() => undefined)
282
+ .then(remove);
283
+ await destroying;
284
+ },
285
+ // ——— adapter-private below this line (live-lane bootstrap + diagnostics) ———
286
+ async exec(cmd, execOptions) {
287
+ const payload = await sendJson(`${prefix}/exec`, "POST", {
288
+ cmd,
289
+ ...(execOptions?.cwd === undefined ? {} : { cwd: execOptions.cwd }),
290
+ ...(execOptions?.timeoutMs === undefined ? {} : { timeout_ms: execOptions.timeoutMs }),
291
+ });
292
+ if (typeof payload.code !== "number") {
293
+ throw new VendoError("sandbox-unavailable", "Vendo Cloud sandbox returned an invalid exec response");
294
+ }
295
+ return {
296
+ code: payload.code,
297
+ stdout: typeof payload.stdout === "string" ? payload.stdout : "",
298
+ stderr: typeof payload.stderr === "string" ? payload.stderr : "",
299
+ };
300
+ },
301
+ files: {
302
+ async read(path) {
303
+ const response = await send(`${prefix}/files?path=${encodeURIComponent(path)}`);
304
+ return new Uint8Array(await response.arrayBuffer());
305
+ },
306
+ async write(path, bytes) {
307
+ await send(`${prefix}/files?path=${encodeURIComponent(path)}`, {
308
+ method: "PUT",
309
+ headers: { "content-type": "application/octet-stream" },
310
+ body: toArrayBuffer(toBytes(bytes)),
311
+ });
312
+ },
313
+ async list(dir) {
314
+ const payload = await sendJson(`${prefix}/files/list?dir=${encodeURIComponent(dir)}`, "GET");
315
+ return Array.isArray(payload.entries)
316
+ ? payload.entries.filter((entry) => typeof entry === "string")
317
+ : [];
318
+ },
319
+ },
320
+ async url(port) {
321
+ // Wave 4 (layer 3) — the browser→box serving path. The handle URL
322
+ // from create/resume IS the canonical-port ingress: single-label
323
+ // `<id-suffix>-m.vendo.run` as shipped by the console (vendo-web
324
+ // #85; -m is a SUFFIX because Cloudflare routes only allow leading
325
+ // wildcards, `*-m.vendo.run/*`). Other ports insert before the
326
+ // suffix — `<id-suffix>-<port>-m.vendo.run` — matching the
327
+ // machine-proxy parse (sandbox-wire.ts ingress entry). Hosts
328
+ // without a -m label (custom consoles) keep the e2b-style prefix.
329
+ const target = port ?? state.port;
330
+ if (target === CLOUD_BOX_PORT)
331
+ return handle.url;
332
+ const ingress = new URL(handle.url);
333
+ const suffixed = /^(.+)-m(\..+)$/.exec(ingress.host);
334
+ ingress.host = suffixed === null
335
+ ? `${target}-${ingress.host}`
336
+ : `${suffixed[1]}-${target}-m${suffixed[2]}`;
337
+ return ingress.origin;
338
+ },
339
+ };
340
+ };
341
+ return {
342
+ async create(spec) {
343
+ // spec.template is dropped: the create route takes none — the pooled
344
+ // base image (Node + the in-box agent harness) is Cloud's own
345
+ // (sandbox-wire.ts).
346
+ return wrap(parseHandle(await sendJson("", "POST", {
347
+ env: spec.env,
348
+ // Seam semantics carried verbatim: absent = unrestricted, [] = deny-all
349
+ // (deny-by-default lives ABOVE the seam — Lane E's grant flow).
350
+ ...(spec.allowedDomains === undefined ? {} : { egress: [...spec.allowedDomains] }),
351
+ // Defensive copy: later refs must record the policy the machine was
352
+ // CREATED with, immune to caller-side mutation of the array.
353
+ })), {
354
+ allowedDomains: spec.allowedDomains === undefined ? undefined : [...spec.allowedDomains],
355
+ port: parsePort(spec.env),
356
+ });
357
+ },
358
+ async resume(snapshotRef, policy) {
359
+ const state = decodeSnapshotRef(snapshotRef);
360
+ // The new machine inherits NO network config from the artifact
361
+ // (sandbox-wire.ts), so every resume states the applicable allowlist:
362
+ // Lane E's replace semantics when the caller re-polices the wake, the
363
+ // ref-recorded snapshot-time policy otherwise. undefined stays the
364
+ // seam's "unrestricted" (absent field on the wire).
365
+ const allowedDomains = policy === undefined ? state.allowedDomains : policy.allowedDomains;
366
+ return wrap(parseHandle(await sendJson("/resume", "POST", {
367
+ ref: state.ref,
368
+ ...(allowedDomains === undefined ? {} : { egress: [...allowedDomains] }),
369
+ })), {
370
+ allowedDomains: allowedDomains === undefined ? undefined : [...allowedDomains],
371
+ port: state.port ?? CLOUD_BOX_PORT,
372
+ });
373
+ },
374
+ async destroy(snapshotRef) {
375
+ const state = decodeSnapshotRef(snapshotRef);
376
+ // Best-effort reap of the recorded source machine (it is usually
377
+ // already gone — the sleep flow destroyed it), then the artifact GC.
378
+ // A 404 from either is the seam's idempotent no-op.
379
+ await sendJson(`/${encodeURIComponent(state.machineId)}`, "DELETE").catch(() => undefined);
380
+ try {
381
+ await sendJson(`${CLOUD_SNAPSHOTS_SUBPATH}/${encodeURIComponent(state.ref)}`, "DELETE");
382
+ }
383
+ catch (error) {
384
+ if (!isGone(error))
385
+ throw error;
386
+ }
387
+ },
388
+ };
389
+ }
@@ -0,0 +1,216 @@
1
+ import { type ActionsRegistry, type Connector, type ServerActionHandler } from "@vendoai/actions";
2
+ import { type VendoAgent } from "@vendoai/agent";
3
+ import { type AppsConfig, type AppsRuntime, type SandboxAdapter } from "@vendoai/apps";
4
+ import { type AutomationsEngine } from "@vendoai/automations";
5
+ import { type ActAs, type ComponentCatalog, type ComponentRegistry, type Json, type Principal, type RunId, type SecretsProvider, type ToolRegistry } from "@vendoai/core";
6
+ import { type Judge, type PolicyConfig, type VendoGuard } from "@vendoai/guard";
7
+ import { type HostOAuthAdapter } from "@vendoai/mcp";
8
+ import { type VendoStore } from "@vendoai/store";
9
+ export { eraseStore, type EraseReport, type EraseTable } from "@vendoai/store";
10
+ export { createStore, envSecrets, secretStore, storeSecrets } from "@vendoai/store";
11
+ export { hostAuthPresetConformance, type HostAuthPreset, type HostAuthPresetConformanceOptions, type HostAuthPresetOptions, type HostAuthPresetUser, type HostAuthPresetUserResolver, type SupabaseHostAuthPresetOptions, } from "./auth-presets/index.js";
12
+ import type { HostAuthPreset } from "./auth-presets/index.js";
13
+ import type { LanguageModel } from "ai";
14
+ export { devModel, type DevModelOptions } from "./dev-creds/model.js";
15
+ import { type ConnectionsService } from "./connections.js";
16
+ export { byoConnections, cloudConnections, unconfiguredConnections, type CloudConnectionsOptions, type ConnectionsService, } from "./connections.js";
17
+ export { cloudSandbox, type CloudSandboxOptions } from "./sandbox.js";
18
+ export { cloudTools, type CloudToolsOptions } from "./cloud-tools.js";
19
+ export { hostedStore, type HostedStore, type HostedStoreOptions } from "./hosted-store.js";
20
+ export interface Vendo {
21
+ handler: (req: Request) => Promise<Response>;
22
+ emit(event: string, payload: Json, principal: Principal): Promise<RunId[]>;
23
+ agent: VendoAgent;
24
+ guard: VendoGuard;
25
+ /** Existing-agents — the guard-bound registry with BYO approval parking:
26
+ the registry the `vendo_*` tool pack executes through. Same binding
27
+ chat, apps, and automations ride (no unguarded route); the one addition
28
+ is that a `pending-approval` outcome parks the exact call so the wire
29
+ resumes it on approve, discards it on deny, and expires it on the
30
+ parked-call TTL sweep. */
31
+ guardedTools: ToolRegistry;
32
+ apps: AppsRuntime;
33
+ automations: AutomationsEngine;
34
+ actions: ActionsRegistry;
35
+ connections: ConnectionsService;
36
+ store: VendoStore;
37
+ }
38
+ export interface CreateVendoConfig {
39
+ /** The agent's LLM — the inference adapter seam (03-agent §1): any ai-SDK
40
+ LanguageModel. Optional since install-dx v1: an explicitly passed model
41
+ always wins (BYO-LLM); when absent the seam resolves a real key from the
42
+ environment — provider keys via devModel's ladder, then VENDO_API_KEY →
43
+ Vendo Cloud managed inference — and fails honestly with instructions
44
+ when none exists (precedence: selectModel). */
45
+ model?: LanguageModel;
46
+ /** v2 spec §4 — tier-0 paint lane knob for app generation. `model` is the
47
+ no-think switch (a thinking-disabled model instance for the instant
48
+ paint); `disabled` forces single-lane generation. */
49
+ paint?: AppsConfig["paint"];
50
+ /** 09-vendo §2.1 — ONE host-identity preset filling the principal, actAs, and
51
+ oauth seams from one config key. Mutually exclusive with all three:
52
+ mixing throws VendoError("validation") at compose time. */
53
+ auth?: HostAuthPreset;
54
+ /** Per-seam escape hatch: host session → principal; null → the per-client
55
+ ephemeral anonymous principal. With neither `auth` nor `principal`, every
56
+ session is anonymous (the null path is the default resolver — 09 §2). */
57
+ principal?: (req: Request) => Promise<Principal | null>;
58
+ /** Host components available to generated apps: the name-keyed registry
59
+ object (01 §14 — the same object serves <VendoRoot>; the server ignores
60
+ each entry's `component` reference) or the array form. Entry names must
61
+ mirror the client-side components map 1:1. */
62
+ catalog?: ComponentCatalog | ComponentRegistry;
63
+ store?: VendoStore;
64
+ sandbox?: SandboxAdapter;
65
+ connectors?: Connector[];
66
+ /** 04-actions §3 — an explicit connections adapter; always wins over the
67
+ defaults (precedence: selectConnections). */
68
+ connections?: ConnectionsService;
69
+ actAs?: ActAs;
70
+ /** 04-actions §1 (ENG-248): the server-action registration map emitted by the
71
+ generated wiring file, keyed `"<module>#<exportName>"`. Server-action tools
72
+ dispatch in-process through it; a missing key fails closed at execution. */
73
+ serverActions?: Record<string, ServerActionHandler>;
74
+ policy?: PolicyConfig;
75
+ judge?: Judge;
76
+ secrets?: SecretsProvider;
77
+ telemetry?: boolean;
78
+ /** Development-only source capture. NODE_ENV=development enables this with
79
+ cwd/.vendo defaults; an explicit object supplies a host root for adapters
80
+ whose process cwd differs. `false` disables the environment default. */
81
+ development?: boolean | {
82
+ root?: string;
83
+ out?: string;
84
+ };
85
+ /** 10-mcp §1 — the one flag: open the MCP door so outside agents (Claude,
86
+ ChatGPT, Cursor) reach the host's tools through the SAME guard-bound path.
87
+ Opening it is a host decision (10-mcp §2), so it is off by default.
88
+ The additive object form opens the door with options: `baseUrl` is the
89
+ canonical PUBLIC base URL the door's discovery metadata, issuer, resource
90
+ identifiers, and RFC 8707 audience binding derive from — set it (or
91
+ `VENDO_BASE_URL`, the default) behind a reverse proxy, where the request
92
+ URL carries the proxy-internal origin. Forwarded headers are never
93
+ trusted. `remoteAs` (10-mcp §3.1) trusts an external authorization server
94
+ — e.g. the hosted broker at `{tenant}.mcp.vendo.run` — instead of serving
95
+ the door's local OAuth surface, and `federation` (10-mcp §3.2) answers
96
+ that server's signed login handshake at `{mount}/federate`. */
97
+ mcp?: boolean | {
98
+ baseUrl?: string;
99
+ remoteAs?: {
100
+ issuer: string;
101
+ jwksUri?: string;
102
+ audience: string;
103
+ };
104
+ federation?: {
105
+ secret: string;
106
+ };
107
+ };
108
+ /** 10-mcp §3 plus its additive prebuilt flow — the host's session + identity seam. Threaded top-level like
109
+ `actAs`/`principal` (the door is agnostic; the umbrella owns the shape).
110
+ REQUIRED when `mcp` is true: the door cannot mint principals without it. */
111
+ oauth?: HostOAuthAdapter;
112
+ /** 03-agent — chat context controls. All optional. `toolOutputCap` defaults to
113
+ DEFAULT_TOOL_OUTPUT_CAP so one huge host-tool response can't blow the context;
114
+ pass 0 to disable. `historyWindow` bounds messages re-sent per turn (default: full). */
115
+ agent?: {
116
+ toolOutputCap?: number;
117
+ maxOutputTokens?: number;
118
+ historyWindow?: number;
119
+ /** ENG-252 — cap on the uncurated initial tool loadout; the rest stay
120
+ discoverable via `vendo_tools_search`. Defaults to the agent block's
121
+ DEFAULT_MAX_INITIAL_TOOLS. */
122
+ maxInitialTools?: number;
123
+ /** ENG-252 — explicit curated initial loadout by tool name. When set,
124
+ exactly these host tools (that exist and are enabled) start active —
125
+ the cap is not applied; the rest stay discoverable via
126
+ `vendo_tools_search`. Vendo's own `vendo_*` tools are always active. */
127
+ loadout?: string[];
128
+ /** AGENT-7: agent-loop step cap per turn (default 20). Exhaustion streams a
129
+ renderable `data-vendo-step-limit` part instead of ending silently. */
130
+ maxSteps?: number;
131
+ };
132
+ /** 02-store §4 (kill-list B3) — ephemeral (anonymous) session lifecycle.
133
+ Anonymous visitors get a TTL-based session on disk: every request touches
134
+ it; an idle session is swept — its rows erased from the store and its
135
+ in-memory threads cascaded away. All optional.
136
+ - `ttlMs` idle timeout before a session is swept (default 30 min). `0`
137
+ disables TTL eviction.
138
+ - `sweepIntervalMs` how often the amortized on-request sweep and the
139
+ unref'd background timer run (default 60 s).
140
+ - `now` internal clock seam (tests only). */
141
+ sessions?: {
142
+ ttlMs?: number;
143
+ sweepIntervalMs?: number;
144
+ now?: () => number;
145
+ };
146
+ /** Existing-agents — approval lifecycle knobs.
147
+ - `parkedCallTtlMs` idle timeout for a guarded call parked from a BYO
148
+ agent loop (a `vendo/approval-ref@1` envelope with no Vendo thread to
149
+ resume through). Past it, the sweep denies the approval through the
150
+ existing abandonment semantics and `<VendoApprovalEmbed>` reads
151
+ "expired". Default 60 min; `0` disables expiry. Vendo-thread approvals
152
+ are untouched — their abandonment stays turn-driven (AGENT-6). */
153
+ approvals?: {
154
+ parkedCallTtlMs?: number;
155
+ };
156
+ /** execution-v2 Waves 4+9 — apps-block options. `experimentalMachines` is
157
+ the per-project layer-2 opt-in: NEW box graduation (the escalation
158
+ ladder's last rung) and machine provisioning refuse with a typed
159
+ VendoError naming this flag until the host enables it; steps/agentic
160
+ automations (the ladder's first two rungs) never need it, and apps that
161
+ already carry a machine keep every runtime path. `experimentalServedApps`
162
+ is the layer-3 opt-in on top: a machine may serve the app surface itself
163
+ (the host embeds its URL in a sandboxed iframe) — it REQUIRES
164
+ `experimentalMachines` (layer 3 is served by a layer-2 machine). OFF by
165
+ default — layer-3 generation, the 2→3 surface flip, and open() on a
166
+ served app all refuse with a typed VendoError naming the flag. */
167
+ apps?: {
168
+ experimentalServedApps?: boolean;
169
+ experimentalMachines?: boolean;
170
+ /** Host design rules for app generation (spec 2026-07-20): the same prose
171
+ `.vendo/design-rules.md` carries, for hosts that prefer programmatic
172
+ config. A non-blank string wins over the file and is fixed for the
173
+ instance lifetime; unset/blank falls through to a PER-GENERATION read
174
+ of the file, so editing it applies to the next create/edit without a
175
+ restart. */
176
+ designRules?: string;
177
+ };
178
+ }
179
+ /** 09-vendo §2 — compose every live block around the guard choke point. */
180
+ export declare function createVendo(config: CreateVendoConfig): Vendo;
181
+ /** 09-vendo §2 — adapt the fetch handler to a Next.js catch-all route module.
182
+ PATCH stays exported even with no PATCH-only wire route left: Next.js
183
+ 405s any method the module does not export before the request ever
184
+ reaches `vendo.handler`, so dropping it would turn e.g. `PATCH
185
+ /api/vendo/orgs/:id/members/:subject` into a framework 405 instead of
186
+ the wire's own `cloud-required` seam (the org routes matched ANY
187
+ method — orgsRoutes in wire/misc.ts). PUT carries the box callback
188
+ surface's durable-row writes (execution-v2 Lane C:
189
+ PUT /api/vendo/box/rows/:collection/:id). */
190
+ export declare function nextVendoHandler(vendo: Vendo): {
191
+ GET(request: Request): Promise<Response>;
192
+ POST(request: Request): Promise<Response>;
193
+ PUT(request: Request): Promise<Response>;
194
+ PATCH(request: Request): Promise<Response>;
195
+ DELETE(request: Request): Promise<Response>;
196
+ };
197
+ /** 10-mcp §5 — adapt the fetch handler to a Next.js `app/.well-known/[...vendo]/
198
+ route.ts` module. The four discovery documents the door serves (RFC 9728/
199
+ 8414 metadata for its fixed mount, plus the SEP-2127 server card) live at
200
+ ORIGIN-ROOT paths, outside BASE_PATH — a host's `/api/vendo` catch-all route
201
+ never sees them, because Next.js dispatches by directory structure, not by
202
+ the wire's own routing. This file exists so that directory gets a handler
203
+ too, one that shares DOOR_WELL_KNOWN_PATHS with the wire itself (the SAME
204
+ set `isDoorPath` matches) instead of a hand-copied allowlist that can drift
205
+ from it. A request whose pathname is exactly one of those four paths
206
+ forwards to `vendo.handler` (which independently confirms it's a door path
207
+ and, if `mcp` is configured, serves it — the check here is only about
208
+ which requests reach the wire at all); anything else answers 404 with an
209
+ empty body, mirroring the hand-written route this replaces. With `mcp` left
210
+ unconfigured, `vendo.handler` still recognizes these four paths but has no
211
+ door to serve them, so the request falls through to the wire's ordinary
212
+ not-found response — never a 500. */
213
+ export declare function wellKnownVendoHandler(vendo: Vendo): {
214
+ GET(request: Request): Promise<Response>;
215
+ POST(request: Request): Promise<Response>;
216
+ };