@finalai/final-agent-provider 0.7.0 → 0.9.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 (76) hide show
  1. package/HOSTING.md +3 -3
  2. package/README.md +26 -91
  3. package/dist/modules/channel-factories.d.mts +5 -6
  4. package/dist/modules/channel-factories.mjs +7 -11
  5. package/dist/modules/channel-factories.mjs.map +3 -3
  6. package/dist/modules/channels/inbound.mjs +2 -2
  7. package/dist/modules/channels/inbound.mjs.map +2 -2
  8. package/dist/modules/channels/index.d.mts +0 -7
  9. package/dist/modules/channels/index.mjs +0 -7
  10. package/dist/modules/channels/index.mjs.map +2 -2
  11. package/dist/modules/channels/prompt.d.mts +2 -2
  12. package/dist/modules/channels/prompt.mjs +3 -3
  13. package/dist/modules/channels/prompt.mjs.map +2 -2
  14. package/dist/modules/channels/routing.d.mts +1 -1
  15. package/dist/modules/channels/routing.mjs +1 -1
  16. package/dist/modules/channels/routing.mjs.map +1 -1
  17. package/dist/modules/sandboxes/bytes.d.mts +1 -0
  18. package/dist/modules/sandboxes/bytes.mjs +7 -1
  19. package/dist/modules/sandboxes/bytes.mjs.map +2 -2
  20. package/dist/modules/sandboxes/computer-processes.mjs +5 -3
  21. package/dist/modules/sandboxes/computer-processes.mjs.map +2 -2
  22. package/dist/modules/sandboxes/computer.mjs +5 -1
  23. package/dist/modules/sandboxes/computer.mjs.map +2 -2
  24. package/dist/modules/sandboxes/computers-api-transport.d.mts +1 -1
  25. package/dist/modules/sandboxes/computers-api-transport.mjs +8 -0
  26. package/dist/modules/sandboxes/computers-api-transport.mjs.map +2 -2
  27. package/dist/modules/sandboxes/device-api.mjs +8 -0
  28. package/dist/modules/sandboxes/device-api.mjs.map +2 -2
  29. package/dist/modules/sandboxes/device.mjs +6 -0
  30. package/dist/modules/sandboxes/device.mjs.map +2 -2
  31. package/dist/modules/sandboxes/session.d.mts +1 -0
  32. package/dist/modules/sandboxes/session.mjs +106 -36
  33. package/dist/modules/sandboxes/session.mjs.map +3 -3
  34. package/dist/modules/sandboxes/testing/conformance.mjs +3 -0
  35. package/dist/modules/sandboxes/testing/conformance.mjs.map +2 -2
  36. package/dist/modules/sandboxes/testing/fake-machine.mjs +11 -0
  37. package/dist/modules/sandboxes/testing/fake-machine.mjs.map +2 -2
  38. package/dist/modules/sandboxes/transport.d.mts +1 -0
  39. package/dist/modules/sandboxes/transport.mjs.map +2 -2
  40. package/package.json +4 -40
  41. package/dist/modules/channels/channel-utils.d.mts +0 -132
  42. package/dist/modules/channels/channel-utils.mjs +0 -445
  43. package/dist/modules/channels/channel-utils.mjs.map +0 -7
  44. package/dist/modules/channels/discord-dispatch.d.mts +0 -17
  45. package/dist/modules/channels/discord-dispatch.mjs +0 -139
  46. package/dist/modules/channels/discord-dispatch.mjs.map +0 -7
  47. package/dist/modules/channels/discord-gateway.d.mts +0 -19
  48. package/dist/modules/channels/discord-gateway.mjs +0 -233
  49. package/dist/modules/channels/discord-gateway.mjs.map +0 -7
  50. package/dist/modules/channels/discord.d.mts +0 -9
  51. package/dist/modules/channels/discord.mjs +0 -904
  52. package/dist/modules/channels/discord.mjs.map +0 -7
  53. package/dist/modules/channels/github.d.mts +0 -5
  54. package/dist/modules/channels/github.mjs +0 -720
  55. package/dist/modules/channels/github.mjs.map +0 -7
  56. package/dist/modules/channels/linear.d.mts +0 -4
  57. package/dist/modules/channels/linear.mjs +0 -621
  58. package/dist/modules/channels/linear.mjs.map +0 -7
  59. package/dist/modules/channels/phone.d.mts +0 -8
  60. package/dist/modules/channels/phone.mjs +0 -382
  61. package/dist/modules/channels/phone.mjs.map +0 -7
  62. package/dist/modules/channels/reply-receipts.d.mts +0 -31
  63. package/dist/modules/channels/reply-receipts.mjs +0 -63
  64. package/dist/modules/channels/reply-receipts.mjs.map +0 -7
  65. package/dist/modules/channels/slack.d.mts +0 -7
  66. package/dist/modules/channels/slack.mjs +0 -1134
  67. package/dist/modules/channels/slack.mjs.map +0 -7
  68. package/dist/modules/channels/voice-recording.d.mts +0 -7
  69. package/dist/modules/channels/voice-recording.mjs +0 -49
  70. package/dist/modules/channels/voice-recording.mjs.map +0 -7
  71. package/dist/modules/channels/voice.d.mts +0 -33
  72. package/dist/modules/channels/voice.mjs +0 -851
  73. package/dist/modules/channels/voice.mjs.map +0 -7
  74. package/dist/modules/channels/webhook.d.mts +0 -5
  75. package/dist/modules/channels/webhook.mjs +0 -76
  76. package/dist/modules/channels/webhook.mjs.map +0 -7
@@ -2,10 +2,25 @@ import { SandboxAccess } from "@finalai/agent-provider";
2
2
  import { createConfinedFileReader, SandboxEntryType } from "@finalai/agent-provider/sandbox";
3
3
  import { encodeSandboxText } from "@finalai/agent-provider/sandbox/bytes";
4
4
  import { readTextFileStream } from "@finalai/agent-provider/sandbox/convenience";
5
- import { collectStream, errorMessage, isNotFoundMessage, rechunk } from "./bytes.mjs";
5
+ import { collectStream, errorMessage, isNotFoundMessage, rechunk, splitChunks } from "./bytes.mjs";
6
6
  import { posixCommandLine } from "./commands.mjs";
7
7
  import { POSIX_PATHS } from "./paths.mjs";
8
- import { exitStatus } from "./transport.mjs";
8
+ import {
9
+ exitStatus
10
+ } from "./transport.mjs";
11
+ const PROCESS_REF_PREFIX = "final-process.v1";
12
+ const encodeProcessRef = (parts) => [PROCESS_REF_PREFIX, parts.session, parts.process.id, parts.process.bootId, parts.scope].map(encodeURIComponent).join(":");
13
+ const decodeProcessRef = (ref) => {
14
+ const parts = ref.split(":");
15
+ if (parts.length !== 5 || parts[0] !== PROCESS_REF_PREFIX) return null;
16
+ try {
17
+ const [session = "", id = "", bootId = "", scope = ""] = parts.slice(1).map(decodeURIComponent);
18
+ return { session, process: { id, bootId }, scope };
19
+ } catch {
20
+ return null;
21
+ }
22
+ };
23
+ const outputStart = (chunk, requested) => Math.max(requested, chunk.nextOffset - chunk.data.byteLength);
9
24
  const textDecoder = () => new TextDecoder("utf-8");
10
25
  const pushStream = () => {
11
26
  const holder = {};
@@ -28,20 +43,15 @@ const createTransportSession = (args) => {
28
43
  const paths = args.paths ?? POSIX_PATHS;
29
44
  const commandLine = args.commands ?? posixCommandLine;
30
45
  const resolvePath = (path) => paths.resolve(args.workspaceRoot, path);
31
- const spawnProcess = async (options, closeStdin) => {
32
- options.abortSignal?.throwIfAborted();
33
- const process = await transport.exec({
34
- id: args.randomUuid(),
35
- command: commandLine(options),
36
- cwd: resolvePath(options.cwd ?? args.workspaceRoot),
37
- env: { ...await args.baseEnv(), ...options.env },
38
- timeoutMs: options.timeoutMs ?? null,
39
- signal: options.abortSignal
40
- });
46
+ const kill = (process) => async (signal) => {
47
+ await transport.input(
48
+ signal == null || signal === "SIGKILL" ? { process, terminate: true } : { process, signal }
49
+ );
50
+ };
51
+ const followProcess = (args2) => {
52
+ const { process } = args2;
41
53
  const stdoutPush = pushStream();
42
54
  const stderrPush = pushStream();
43
- const { stream: stdout } = stdoutPush;
44
- const { stream: stderr } = stderrPush;
45
55
  let settleExit = () => void 0;
46
56
  let failExit = () => void 0;
47
57
  const exit = new Promise((resolve, reject) => {
@@ -49,28 +59,23 @@ const createTransportSession = (args) => {
49
59
  failExit = reject;
50
60
  });
51
61
  exit.catch(() => void 0);
52
- const kill = async (signal) => {
53
- await transport.input(
54
- signal == null || signal === "SIGKILL" ? { process, terminate: true } : { process, signal }
55
- );
56
- };
57
- const onAbort = () => {
58
- void kill().catch(() => void 0);
59
- };
60
- options.abortSignal?.addEventListener("abort", onAbort, { once: true });
61
- if (options.abortSignal?.aborted === true) onAbort();
62
62
  void (async () => {
63
- let stdoutOffset = 0;
64
- let stderrOffset = 0;
63
+ let stdoutOffset = args2.stdoutOffset;
64
+ let stderrOffset = args2.stderrOffset;
65
65
  let waitMs = transport.outputWaitMs;
66
+ let pending = args2.first;
66
67
  try {
67
68
  for (; ; ) {
68
- const output = await transport.output({
69
+ args2.detach?.throwIfAborted();
70
+ const output = pending ?? await transport.output({
69
71
  process,
70
72
  stdoutOffset,
71
73
  stderrOffset,
72
- waitMs
74
+ waitMs,
75
+ signal: args2.detach
73
76
  });
77
+ pending = void 0;
78
+ args2.detach?.throwIfAborted();
74
79
  if (output.stdout.data.byteLength > 0) stdoutPush.controller?.enqueue(output.stdout.data);
75
80
  if (output.stderr.data.byteLength > 0) stderrPush.controller?.enqueue(output.stderr.data);
76
81
  const advanced = output.stdout.nextOffset !== stdoutOffset || output.stderr.nextOffset !== stderrOffset;
@@ -89,11 +94,37 @@ const createTransportSession = (args) => {
89
94
  stdoutPush.controller?.error(error);
90
95
  stderrPush.controller?.error(error);
91
96
  failExit(error);
92
- } finally {
93
- options.abortSignal?.removeEventListener("abort", onAbort);
94
97
  }
95
98
  })();
99
+ return { stdout: stdoutPush.stream, stderr: stderrPush.stream, exit, kill: kill(process) };
100
+ };
101
+ const spawnProcess = async (options, closeStdin) => {
102
+ options.abortSignal?.throwIfAborted();
103
+ const durable = options.durable === true && transport.processGone != null;
104
+ const process = await transport.exec({
105
+ id: args.randomUuid(),
106
+ command: commandLine(options),
107
+ cwd: resolvePath(options.cwd ?? args.workspaceRoot),
108
+ env: { ...await args.baseEnv(), ...options.env },
109
+ timeoutMs: options.timeoutMs ?? null,
110
+ signal: options.abortSignal
111
+ });
112
+ const followed = followProcess({
113
+ process,
114
+ stdoutOffset: 0,
115
+ stderrOffset: 0,
116
+ detach: durable ? options.abortSignal : void 0
117
+ });
118
+ if (!durable) {
119
+ const onAbort = () => {
120
+ void followed.kill().catch(() => void 0);
121
+ };
122
+ options.abortSignal?.addEventListener("abort", onAbort, { once: true });
123
+ if (options.abortSignal?.aborted === true) onAbort();
124
+ void followed.exit.catch(() => void 0).finally(() => options.abortSignal?.removeEventListener("abort", onAbort));
125
+ }
96
126
  const write = async (data, writeOptions) => {
127
+ if (durable) options.abortSignal?.throwIfAborted();
97
128
  const bytes = data instanceof Uint8Array ? data : new TextEncoder().encode(data);
98
129
  if (bytes.byteLength > 0) await transport.input({ process, data: bytes });
99
130
  if (writeOptions?.end === true) await transport.input({ process, eof: true });
@@ -101,7 +132,7 @@ const createTransportSession = (args) => {
101
132
  const input = options.stdin;
102
133
  if (input instanceof ReadableStream) {
103
134
  void (async () => {
104
- for await (const chunk of rechunk(input, 64 * 1024)) await write(chunk);
135
+ for await (const chunk of splitChunks(input, 64 * 1024)) await write(chunk);
105
136
  await write(new Uint8Array(), { end: true });
106
137
  })().catch(() => void 0);
107
138
  } else if (input != null) {
@@ -111,11 +142,48 @@ const createTransportSession = (args) => {
111
142
  }
112
143
  return {
113
144
  pid: process.id,
114
- stdout,
115
- stderr,
145
+ ref: durable ? encodeProcessRef({ session: args.id, process, scope: args.processScope?.() ?? "" }) : null,
146
+ stdout: followed.stdout,
147
+ stderr: followed.stderr,
116
148
  write,
117
- wait: () => exit,
118
- kill
149
+ wait: () => followed.exit,
150
+ kill: followed.kill
151
+ };
152
+ };
153
+ const attachProcess = async (options) => {
154
+ options.abortSignal?.throwIfAborted();
155
+ const decoded = decodeProcessRef(options.ref);
156
+ if (transport.processGone == null || decoded == null || decoded.session !== args.id || decoded.scope !== (args.processScope?.() ?? ""))
157
+ return null;
158
+ const { process } = decoded;
159
+ let first;
160
+ try {
161
+ first = await transport.output({
162
+ process,
163
+ stdoutOffset: options.stdoutOffset,
164
+ stderrOffset: options.stderrOffset,
165
+ waitMs: 0,
166
+ signal: options.abortSignal
167
+ });
168
+ } catch (cause) {
169
+ if (transport.processGone(cause)) return null;
170
+ throw cause;
171
+ }
172
+ const followed = followProcess({
173
+ process,
174
+ stdoutOffset: options.stdoutOffset,
175
+ stderrOffset: options.stderrOffset,
176
+ first,
177
+ detach: options.abortSignal
178
+ });
179
+ return {
180
+ ref: options.ref,
181
+ stdoutOffset: outputStart(first.stdout, options.stdoutOffset),
182
+ stderrOffset: outputStart(first.stderr, options.stderrOffset),
183
+ stdout: followed.stdout,
184
+ stderr: followed.stderr,
185
+ wait: () => followed.exit,
186
+ kill: followed.kill
119
187
  };
120
188
  };
121
189
  const readStream = async (path, signal) => {
@@ -181,7 +249,7 @@ const createTransportSession = (args) => {
181
249
  writeTextFile: async ({ path, content, encoding, abortSignal }) => writeStream(path, bytesStream(encodeSandboxText(content, encoding)), abortSignal),
182
250
  spawn: (options) => spawnProcess(options, false),
183
251
  run: async (options) => {
184
- const process = await spawnProcess(options, true);
252
+ const process = await spawnProcess({ ...options, durable: false }, true);
185
253
  const [stdout, stderr, exit] = await Promise.all([
186
254
  collectStream(process.stdout),
187
255
  collectStream(process.stderr),
@@ -227,6 +295,7 @@ const createTransportSession = (args) => {
227
295
  stop: () => args.network.stop(),
228
296
  destroy: () => args.network.destroy()
229
297
  };
298
+ if (transport.processGone != null) session.attachProcess = attachProcess;
230
299
  return { ...session, confinedFileReader: createConfinedFileReader(session) };
231
300
  };
232
301
  const readOnlyRefusal = async () => {
@@ -246,6 +315,7 @@ const readOnlySession = (session) => {
246
315
  };
247
316
  if (session.setNetworkPolicy != null) restricted.setNetworkPolicy = readOnlyRefusal;
248
317
  delete restricted.egress;
318
+ delete restricted.attachProcess;
249
319
  const screen = session.screen;
250
320
  if (screen != null)
251
321
  restricted.screen = async (options) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/sandboxes/session.ts"],
4
- "sourcesContent": ["import { SandboxAccess } from '@finalai/agent-provider';\nimport type {\n DirEntryIface,\n NetworkSandboxSessionIface,\n PortProtocol,\n SandboxProcessIface,\n SandboxProcessOptionsIface,\n SandboxRunResultIface,\n StatIface,\n} from '@finalai/agent-provider/sandbox';\nimport { createConfinedFileReader, SandboxEntryType } from '@finalai/agent-provider/sandbox';\nimport { encodeSandboxText } from '@finalai/agent-provider/sandbox/bytes';\nimport { readTextFileStream } from '@finalai/agent-provider/sandbox/convenience';\nimport { collectStream, errorMessage, isNotFoundMessage, rechunk } from \"./bytes.mjs\";\nimport { posixCommandLine, type SandboxCommandEncoderIface } from \"./commands.mjs\";\nimport { POSIX_PATHS, type SandboxPathsIface } from \"./paths.mjs\";\nimport { exitStatus, type SandboxTransportIface, type TransportProcessIface } from \"./transport.mjs\";\n\n// NetworkSandboxSessionIface over any SandboxTransportIface. Provider specifics\n// (lifecycle, ports, extra capabilities) come in through `network`.\n\nexport interface TransportSessionNetworkIface {\n ports: readonly number[];\n getPortEndpoint(options: {\n port: number;\n protocol?: PortProtocol;\n }): Promise<{ url: string; headers?: Record<string, string> }>;\n stop(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport interface CreateTransportSessionArgsIface {\n id: string;\n description: string;\n workspaceRoot: string;\n transport: SandboxTransportIface;\n // Environment every process gets (injected variables), under per-call env;\n // read again for every process.\n baseEnv: () => Promise<Record<string, string>>;\n randomUuid: () => string;\n network: TransportSessionNetworkIface;\n // The sandbox's path flavour and command encoding; POSIX unless a Device says otherwise.\n paths?: SandboxPathsIface;\n commands?: SandboxCommandEncoderIface;\n}\n\nconst textDecoder = (): TextDecoder => new TextDecoder('utf-8');\n\ninterface PushStreamIface {\n stream: ReadableStream<Uint8Array>;\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\ninterface ControllerHolderIface {\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\n// A stream the output pump pushes into; unread output stays queued in memory.\nconst pushStream = (): PushStreamIface => {\n const holder: ControllerHolderIface = {};\n const stream = new ReadableStream<Uint8Array>({\n start: (controller) => {\n holder.controller = controller;\n },\n });\n return { stream, controller: holder.controller };\n};\n\nconst toStatEntry = (\n path: string,\n stat: { type: SandboxEntryType; size: number; modifiedAtMs: number | null; mode: number | null },\n): StatIface => ({\n path,\n type: stat.type,\n size: stat.size,\n modifiedAt: stat.modifiedAtMs == null ? null : new Date(stat.modifiedAtMs).toISOString(),\n mode: stat.mode,\n});\n\nexport const createTransportSession = (\n args: CreateTransportSessionArgsIface,\n): NetworkSandboxSessionIface => {\n const { transport } = args;\n const paths = args.paths ?? POSIX_PATHS;\n const commandLine = args.commands ?? posixCommandLine;\n const resolvePath = (path: string): string => paths.resolve(args.workspaceRoot, path);\n\n const spawnProcess = async (\n options: SandboxProcessOptionsIface,\n closeStdin: boolean,\n ): Promise<SandboxProcessIface> => {\n options.abortSignal?.throwIfAborted();\n const process: TransportProcessIface = await transport.exec({\n id: args.randomUuid(),\n command: commandLine(options),\n cwd: resolvePath(options.cwd ?? args.workspaceRoot),\n env: { ...(await args.baseEnv()), ...options.env },\n timeoutMs: options.timeoutMs ?? null,\n signal: options.abortSignal,\n });\n const stdoutPush = pushStream();\n const stderrPush = pushStream();\n const { stream: stdout } = stdoutPush;\n const { stream: stderr } = stderrPush;\n let settleExit: (value: { exitCode: number }) => void = () => undefined;\n let failExit: (error: Error) => void = () => undefined;\n const exit = new Promise<{ exitCode: number }>((resolve, reject) => {\n settleExit = resolve;\n failExit = reject;\n });\n // Avoid an unhandled rejection when the caller only reads the streams.\n exit.catch(() => undefined);\n\n const kill = async (signal?: string): Promise<void> => {\n await transport.input(\n signal == null || signal === 'SIGKILL' ? { process, terminate: true } : { process, signal },\n );\n };\n const onAbort = (): void => {\n void kill().catch(() => undefined);\n };\n options.abortSignal?.addEventListener('abort', onAbort, { once: true });\n if (options.abortSignal?.aborted === true) onAbort();\n\n void (async () => {\n let stdoutOffset = 0;\n let stderrOffset = 0;\n let waitMs = transport.outputWaitMs;\n try {\n for (;;) {\n const output = await transport.output({\n process,\n stdoutOffset,\n stderrOffset,\n waitMs,\n });\n if (output.stdout.data.byteLength > 0) stdoutPush.controller?.enqueue(output.stdout.data);\n if (output.stderr.data.byteLength > 0) stderrPush.controller?.enqueue(output.stderr.data);\n const advanced =\n output.stdout.nextOffset !== stdoutOffset || output.stderr.nextOffset !== stderrOffset;\n stdoutOffset = output.stdout.nextOffset;\n stderrOffset = output.stderr.nextOffset;\n if (output.exited && output.stdout.eof && output.stderr.eof) {\n stdoutPush.controller?.close();\n stderrPush.controller?.close();\n settleExit({ exitCode: exitStatus(output.exitCode, output.exitSignal) });\n return;\n }\n // Exited with unread output: drain it without waiting.\n waitMs = output.exited && advanced ? 0 : transport.outputWaitMs;\n }\n } catch (cause) {\n const error = cause instanceof Error ? cause : new Error(String(cause));\n stdoutPush.controller?.error(error);\n stderrPush.controller?.error(error);\n failExit(error);\n } finally {\n options.abortSignal?.removeEventListener('abort', onAbort);\n }\n })();\n\n const write = async (data: Uint8Array | string, writeOptions?: { end?: boolean }) => {\n const bytes = data instanceof Uint8Array ? data : new TextEncoder().encode(data);\n if (bytes.byteLength > 0) await transport.input({ process, data: bytes });\n if (writeOptions?.end === true) await transport.input({ process, eof: true });\n };\n\n const input = options.stdin;\n if (input instanceof ReadableStream) {\n void (async () => {\n for await (const chunk of rechunk(input, 64 * 1024)) await write(chunk);\n await write(new Uint8Array(), { end: true });\n })().catch(() => undefined);\n } else if (input != null) {\n await write(input, { end: true });\n } else if (closeStdin) {\n await transport.input({ process, eof: true });\n }\n\n return {\n pid: process.id,\n stdout,\n stderr,\n write,\n wait: () => exit,\n kill,\n };\n };\n\n const readStream = async (\n path: string,\n signal: AbortSignal | undefined,\n ): Promise<ReadableStream<Uint8Array> | null> => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal });\n if (stat == null) return null;\n if (stat.type === SandboxEntryType.Directory) throw new Error(`${resolved} is a directory`);\n // Read to end of file rather than to the stat size: a symlink's stat is the\n // link's own length, not its target's.\n let offset = 0;\n let done = false;\n return new ReadableStream<Uint8Array>({\n pull: async (controller) => {\n if (done) {\n controller.close();\n return;\n }\n const chunk = await transport.readChunk({\n path: resolved,\n offset,\n length: transport.fileChunkBytes,\n signal,\n });\n done = chunk.eof || chunk.bytes.byteLength === 0;\n offset += chunk.bytes.byteLength;\n if (chunk.bytes.byteLength > 0) controller.enqueue(chunk.bytes);\n if (done) controller.close();\n },\n });\n };\n\n const writeStream = async (\n path: string,\n content: ReadableStream<Uint8Array>,\n signal: AbortSignal | undefined,\n ): Promise<void> => {\n const resolved = resolvePath(path);\n await transport.createDirectory({ path: paths.dirname(resolved), signal });\n let offset = 0;\n for await (const chunk of rechunk(content, transport.fileChunkBytes)) {\n await transport.writeChunk({ path: resolved, offset, bytes: chunk, signal });\n offset += chunk.byteLength;\n }\n if (offset === 0)\n await transport.writeChunk({ path: resolved, offset: 0, bytes: new Uint8Array(), signal });\n };\n\n const bytesStream = (bytes: Uint8Array): ReadableStream<Uint8Array> =>\n new ReadableStream<Uint8Array>({\n start: (controller) => {\n if (bytes.byteLength > 0) controller.enqueue(bytes);\n controller.close();\n },\n });\n\n const session: NetworkSandboxSessionIface = {\n id: args.id,\n description: args.description,\n defaultWorkingDirectory: args.workspaceRoot,\n ports: args.network.ports,\n resolvePath,\n readFile: async ({ path, abortSignal }) => readStream(path, abortSignal),\n readBinaryFile: async ({ path, abortSignal }) => {\n const stream = await readStream(path, abortSignal);\n return stream == null ? null : collectStream(stream);\n },\n readTextFile: async (options) => {\n const stream = await readStream(options.path, options.abortSignal);\n return stream == null ? null : readTextFileStream(stream, options);\n },\n writeFile: async ({ path, content, abortSignal }) => writeStream(path, content, abortSignal),\n writeBinaryFile: async ({ path, content, abortSignal }) =>\n writeStream(path, bytesStream(content), abortSignal),\n writeTextFile: async ({ path, content, encoding, abortSignal }) =>\n writeStream(path, bytesStream(encodeSandboxText(content, encoding)), abortSignal),\n spawn: (options) => spawnProcess(options, false),\n run: async (options): Promise<SandboxRunResultIface> => {\n const process = await spawnProcess(options, true);\n const [stdout, stderr, exit] = await Promise.all([\n collectStream(process.stdout),\n collectStream(process.stderr),\n process.wait(),\n ]);\n return {\n exitCode: exit.exitCode,\n stdout: textDecoder().decode(stdout),\n stderr: textDecoder().decode(stderr),\n };\n },\n stat: async ({ path, abortSignal }) => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal: abortSignal });\n return stat == null ? null : toStatEntry(resolved, stat);\n },\n readDir: async ({ path, abortSignal }): Promise<DirEntryIface[]> => {\n const resolved = resolvePath(path);\n const entries = await transport.readDir({ path: resolved, signal: abortSignal });\n return entries.map((entry) => ({\n name: entry.name,\n path: paths.join(resolved, entry.name),\n type: entry.type,\n size: entry.size,\n }));\n },\n removePath: async ({ path, recursive, force, abortSignal }) => {\n const resolved = resolvePath(path);\n try {\n await transport.remove({\n path: resolved,\n recursive: recursive === true,\n force: force === true,\n signal: abortSignal,\n });\n } catch (error) {\n if (force === true && isNotFoundMessage(errorMessage(error))) return;\n throw error;\n }\n },\n getPortEndpoint: (options) => args.network.getPortEndpoint(options),\n getPortUrl: async (options) => (await args.network.getPortEndpoint(options)).url,\n stop: () => args.network.stop(),\n destroy: () => args.network.destroy(),\n };\n return { ...session, confinedFileReader: createConfinedFileReader(session) };\n};\n\nconst readOnlyRefusal = async (): Promise<never> => {\n throw new Error('this sandbox binding is read-only');\n};\n\n// A read binding's view of a handle: reads pass through; writes, processes and\n// lifecycle are refused here as well as by the lease the provider took.\nexport const readOnlySession = <S extends NetworkSandboxSessionIface>(session: S): S => {\n const restricted: S = {\n ...session,\n writeFile: readOnlyRefusal,\n writeBinaryFile: readOnlyRefusal,\n writeTextFile: readOnlyRefusal,\n spawn: readOnlyRefusal,\n run: readOnlyRefusal,\n removePath: readOnlyRefusal,\n stop: readOnlyRefusal,\n destroy: readOnlyRefusal,\n };\n if (session.setNetworkPolicy != null) restricted.setNetworkPolicy = readOnlyRefusal;\n delete restricted.egress;\n const screen = session.screen;\n if (screen != null)\n restricted.screen = async (options) => {\n if (options?.viewOnly === false) return readOnlyRefusal();\n return screen.call(session, options);\n };\n return restricted;\n};\n\nconst fileWriteRefusal = async (): Promise<never> => {\n throw new Error('this sandbox binding runs processes but does not write files');\n};\n\n// An execute binding's view of a handle: reads and processes pass through; file\n// writes and lifecycle are refused.\nexport const executeOnlySession = <S extends NetworkSandboxSessionIface>(session: S): S => {\n const restricted: S = {\n ...session,\n writeFile: fileWriteRefusal,\n writeBinaryFile: fileWriteRefusal,\n writeTextFile: fileWriteRefusal,\n removePath: fileWriteRefusal,\n stop: fileWriteRefusal,\n destroy: fileWriteRefusal,\n };\n delete restricted.egress;\n return restricted;\n};\n\n// The view of a handle that a binding's access allows.\nexport const sessionForAccess = <S extends NetworkSandboxSessionIface>(\n session: S,\n access: SandboxAccess,\n): S =>\n access === SandboxAccess.Read\n ? readOnlySession(session)\n : access === SandboxAccess.Execute\n ? executeOnlySession(session)\n : session;\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["import { SandboxAccess } from '@finalai/agent-provider';\nimport type {\n DirEntryIface,\n NetworkSandboxSessionIface,\n PortProtocol,\n SandboxAttachedProcessIface,\n SandboxAttachProcessOptionsIface,\n SandboxProcessIface,\n SandboxProcessOptionsIface,\n SandboxRunResultIface,\n StatIface,\n} from '@finalai/agent-provider/sandbox';\nimport { createConfinedFileReader, SandboxEntryType } from '@finalai/agent-provider/sandbox';\nimport { encodeSandboxText } from '@finalai/agent-provider/sandbox/bytes';\nimport { readTextFileStream } from '@finalai/agent-provider/sandbox/convenience';\nimport { collectStream, errorMessage, isNotFoundMessage, rechunk, splitChunks } from \"./bytes.mjs\";\nimport { posixCommandLine, type SandboxCommandEncoderIface } from \"./commands.mjs\";\nimport { POSIX_PATHS, type SandboxPathsIface } from \"./paths.mjs\";\nimport {\n exitStatus,\n type SandboxTransportIface,\n type TransportOutputIface,\n type TransportOutputStreamIface,\n type TransportProcessIface,\n} from \"./transport.mjs\";\n\n// NetworkSandboxSessionIface over any SandboxTransportIface. Provider specifics\n// (lifecycle, ports, extra capabilities) come in through `network`.\n\nexport interface TransportSessionNetworkIface {\n ports: readonly number[];\n getPortEndpoint(options: {\n port: number;\n protocol?: PortProtocol;\n }): Promise<{ url: string; headers?: Record<string, string> }>;\n stop(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport interface CreateTransportSessionArgsIface {\n id: string;\n description: string;\n workspaceRoot: string;\n transport: SandboxTransportIface;\n // Environment every process gets (injected variables), under per-call env;\n // read again for every process.\n baseEnv: () => Promise<Record<string, string>>;\n randomUuid: () => string;\n network: TransportSessionNetworkIface;\n // The sandbox's path flavour and command encoding; POSIX unless a Device says otherwise.\n paths?: SandboxPathsIface;\n commands?: SandboxCommandEncoderIface;\n // What else a process ref must match beyond this session and the process's\n // boot, read when spawning and attaching (a Device's lease generation).\n processScope?: () => string;\n}\n\n// A durable process's ref: this session, the process and its boot, and the scope.\nconst PROCESS_REF_PREFIX = 'final-process.v1';\n\nconst encodeProcessRef = (parts: {\n session: string;\n process: TransportProcessIface;\n scope: string;\n}): string =>\n [PROCESS_REF_PREFIX, parts.session, parts.process.id, parts.process.bootId, parts.scope]\n .map(encodeURIComponent)\n .join(':');\n\nconst decodeProcessRef = (\n ref: string,\n): { session: string; process: TransportProcessIface; scope: string } | null => {\n const parts = ref.split(':');\n if (parts.length !== 5 || parts[0] !== PROCESS_REF_PREFIX) return null;\n try {\n const [session = '', id = '', bootId = '', scope = ''] = parts.slice(1).map(decodeURIComponent);\n return { session, process: { id, bootId }, scope };\n } catch {\n return null;\n }\n};\n\n// Where output read from `requested` starts: there, or later when the\n// backend no longer retains the bytes from there.\nconst outputStart = (chunk: TransportOutputStreamIface, requested: number): number =>\n Math.max(requested, chunk.nextOffset - chunk.data.byteLength);\n\nconst textDecoder = (): TextDecoder => new TextDecoder('utf-8');\n\ninterface PushStreamIface {\n stream: ReadableStream<Uint8Array>;\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\ninterface ControllerHolderIface {\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\n// A stream the output pump pushes into; unread output stays queued in memory.\nconst pushStream = (): PushStreamIface => {\n const holder: ControllerHolderIface = {};\n const stream = new ReadableStream<Uint8Array>({\n start: (controller) => {\n holder.controller = controller;\n },\n });\n return { stream, controller: holder.controller };\n};\n\nconst toStatEntry = (\n path: string,\n stat: { type: SandboxEntryType; size: number; modifiedAtMs: number | null; mode: number | null },\n): StatIface => ({\n path,\n type: stat.type,\n size: stat.size,\n modifiedAt: stat.modifiedAtMs == null ? null : new Date(stat.modifiedAtMs).toISOString(),\n mode: stat.mode,\n});\n\nexport const createTransportSession = (\n args: CreateTransportSessionArgsIface,\n): NetworkSandboxSessionIface => {\n const { transport } = args;\n const paths = args.paths ?? POSIX_PATHS;\n const commandLine = args.commands ?? posixCommandLine;\n const resolvePath = (path: string): string => paths.resolve(args.workspaceRoot, path);\n\n const kill =\n (process: TransportProcessIface) =>\n async (signal?: string): Promise<void> => {\n await transport.input(\n signal == null || signal === 'SIGKILL' ? { process, terminate: true } : { process, signal },\n );\n };\n\n // Polls a process's output from the given offsets (after `first`, output\n // already read from there) until it exits. Aborting `detach` stops polling\n // and fails the streams and the exit; the process is untouched.\n const followProcess = (args: {\n process: TransportProcessIface;\n stdoutOffset: number;\n stderrOffset: number;\n first?: TransportOutputIface;\n detach?: AbortSignal;\n }) => {\n const { process } = args;\n const stdoutPush = pushStream();\n const stderrPush = pushStream();\n let settleExit: (value: { exitCode: number }) => void = () => undefined;\n let failExit: (error: Error) => void = () => undefined;\n const exit = new Promise<{ exitCode: number }>((resolve, reject) => {\n settleExit = resolve;\n failExit = reject;\n });\n // Avoid an unhandled rejection when the caller only reads the streams.\n exit.catch(() => undefined);\n void (async () => {\n let stdoutOffset = args.stdoutOffset;\n let stderrOffset = args.stderrOffset;\n let waitMs = transport.outputWaitMs;\n let pending = args.first;\n try {\n for (;;) {\n args.detach?.throwIfAborted();\n const output =\n pending ??\n (await transport.output({\n process,\n stdoutOffset,\n stderrOffset,\n waitMs,\n signal: args.detach,\n }));\n pending = undefined;\n args.detach?.throwIfAborted();\n if (output.stdout.data.byteLength > 0) stdoutPush.controller?.enqueue(output.stdout.data);\n if (output.stderr.data.byteLength > 0) stderrPush.controller?.enqueue(output.stderr.data);\n const advanced =\n output.stdout.nextOffset !== stdoutOffset || output.stderr.nextOffset !== stderrOffset;\n stdoutOffset = output.stdout.nextOffset;\n stderrOffset = output.stderr.nextOffset;\n if (output.exited && output.stdout.eof && output.stderr.eof) {\n stdoutPush.controller?.close();\n stderrPush.controller?.close();\n settleExit({ exitCode: exitStatus(output.exitCode, output.exitSignal) });\n return;\n }\n // Exited with unread output: drain it without waiting.\n waitMs = output.exited && advanced ? 0 : transport.outputWaitMs;\n }\n } catch (cause) {\n const error = cause instanceof Error ? cause : new Error(String(cause));\n stdoutPush.controller?.error(error);\n stderrPush.controller?.error(error);\n failExit(error);\n }\n })();\n return { stdout: stdoutPush.stream, stderr: stderrPush.stream, exit, kill: kill(process) };\n };\n\n const spawnProcess = async (\n options: SandboxProcessOptionsIface,\n closeStdin: boolean,\n ): Promise<SandboxProcessIface> => {\n options.abortSignal?.throwIfAborted();\n // Durable only where processes outlive this client (`processGone`).\n const durable = options.durable === true && transport.processGone != null;\n const process: TransportProcessIface = await transport.exec({\n id: args.randomUuid(),\n command: commandLine(options),\n cwd: resolvePath(options.cwd ?? args.workspaceRoot),\n env: { ...(await args.baseEnv()), ...options.env },\n timeoutMs: options.timeoutMs ?? null,\n signal: options.abortSignal,\n });\n const followed = followProcess({\n process,\n stdoutOffset: 0,\n stderrOffset: 0,\n detach: durable ? options.abortSignal : undefined,\n });\n if (!durable) {\n const onAbort = (): void => {\n void followed.kill().catch(() => undefined);\n };\n options.abortSignal?.addEventListener('abort', onAbort, { once: true });\n if (options.abortSignal?.aborted === true) onAbort();\n void followed.exit\n .catch(() => undefined)\n .finally(() => options.abortSignal?.removeEventListener('abort', onAbort));\n }\n\n // A detached durable handle neither writes to nor closes the process's stdin.\n const write = async (data: Uint8Array | string, writeOptions?: { end?: boolean }) => {\n if (durable) options.abortSignal?.throwIfAborted();\n const bytes = data instanceof Uint8Array ? data : new TextEncoder().encode(data);\n if (bytes.byteLength > 0) await transport.input({ process, data: bytes });\n if (writeOptions?.end === true) await transport.input({ process, eof: true });\n };\n\n const input = options.stdin;\n if (input instanceof ReadableStream) {\n void (async () => {\n // Forward input as it arrives: a stdin stream is often interactive.\n for await (const chunk of splitChunks(input, 64 * 1024)) await write(chunk);\n await write(new Uint8Array(), { end: true });\n })().catch(() => undefined);\n } else if (input != null) {\n await write(input, { end: true });\n } else if (closeStdin) {\n await transport.input({ process, eof: true });\n }\n\n return {\n pid: process.id,\n ref: durable\n ? encodeProcessRef({ session: args.id, process, scope: args.processScope?.() ?? '' })\n : null,\n stdout: followed.stdout,\n stderr: followed.stderr,\n write,\n wait: () => followed.exit,\n kill: followed.kill,\n };\n };\n\n // Finds a durable process again: null for a ref of another session or scope,\n // or a process the backend no longer knows.\n const attachProcess = async (\n options: SandboxAttachProcessOptionsIface,\n ): Promise<SandboxAttachedProcessIface | null> => {\n options.abortSignal?.throwIfAborted();\n const decoded = decodeProcessRef(options.ref);\n if (\n transport.processGone == null ||\n decoded == null ||\n decoded.session !== args.id ||\n decoded.scope !== (args.processScope?.() ?? '')\n )\n return null;\n const { process } = decoded;\n let first: TransportOutputIface;\n try {\n first = await transport.output({\n process,\n stdoutOffset: options.stdoutOffset,\n stderrOffset: options.stderrOffset,\n waitMs: 0,\n signal: options.abortSignal,\n });\n } catch (cause) {\n if (transport.processGone(cause)) return null;\n throw cause;\n }\n const followed = followProcess({\n process,\n stdoutOffset: options.stdoutOffset,\n stderrOffset: options.stderrOffset,\n first,\n detach: options.abortSignal,\n });\n return {\n ref: options.ref,\n stdoutOffset: outputStart(first.stdout, options.stdoutOffset),\n stderrOffset: outputStart(first.stderr, options.stderrOffset),\n stdout: followed.stdout,\n stderr: followed.stderr,\n wait: () => followed.exit,\n kill: followed.kill,\n };\n };\n\n const readStream = async (\n path: string,\n signal: AbortSignal | undefined,\n ): Promise<ReadableStream<Uint8Array> | null> => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal });\n if (stat == null) return null;\n if (stat.type === SandboxEntryType.Directory) throw new Error(`${resolved} is a directory`);\n // Read to end of file rather than to the stat size: a symlink's stat is the\n // link's own length, not its target's.\n let offset = 0;\n let done = false;\n return new ReadableStream<Uint8Array>({\n pull: async (controller) => {\n if (done) {\n controller.close();\n return;\n }\n const chunk = await transport.readChunk({\n path: resolved,\n offset,\n length: transport.fileChunkBytes,\n signal,\n });\n done = chunk.eof || chunk.bytes.byteLength === 0;\n offset += chunk.bytes.byteLength;\n if (chunk.bytes.byteLength > 0) controller.enqueue(chunk.bytes);\n if (done) controller.close();\n },\n });\n };\n\n const writeStream = async (\n path: string,\n content: ReadableStream<Uint8Array>,\n signal: AbortSignal | undefined,\n ): Promise<void> => {\n const resolved = resolvePath(path);\n await transport.createDirectory({ path: paths.dirname(resolved), signal });\n let offset = 0;\n for await (const chunk of rechunk(content, transport.fileChunkBytes)) {\n await transport.writeChunk({ path: resolved, offset, bytes: chunk, signal });\n offset += chunk.byteLength;\n }\n if (offset === 0)\n await transport.writeChunk({ path: resolved, offset: 0, bytes: new Uint8Array(), signal });\n };\n\n const bytesStream = (bytes: Uint8Array): ReadableStream<Uint8Array> =>\n new ReadableStream<Uint8Array>({\n start: (controller) => {\n if (bytes.byteLength > 0) controller.enqueue(bytes);\n controller.close();\n },\n });\n\n const session: NetworkSandboxSessionIface = {\n id: args.id,\n description: args.description,\n defaultWorkingDirectory: args.workspaceRoot,\n ports: args.network.ports,\n resolvePath,\n readFile: async ({ path, abortSignal }) => readStream(path, abortSignal),\n readBinaryFile: async ({ path, abortSignal }) => {\n const stream = await readStream(path, abortSignal);\n return stream == null ? null : collectStream(stream);\n },\n readTextFile: async (options) => {\n const stream = await readStream(options.path, options.abortSignal);\n return stream == null ? null : readTextFileStream(stream, options);\n },\n writeFile: async ({ path, content, abortSignal }) => writeStream(path, content, abortSignal),\n writeBinaryFile: async ({ path, content, abortSignal }) =>\n writeStream(path, bytesStream(content), abortSignal),\n writeTextFile: async ({ path, content, encoding, abortSignal }) =>\n writeStream(path, bytesStream(encodeSandboxText(content, encoding)), abortSignal),\n spawn: (options) => spawnProcess(options, false),\n run: async (options): Promise<SandboxRunResultIface> => {\n const process = await spawnProcess({ ...options, durable: false }, true);\n const [stdout, stderr, exit] = await Promise.all([\n collectStream(process.stdout),\n collectStream(process.stderr),\n process.wait(),\n ]);\n return {\n exitCode: exit.exitCode,\n stdout: textDecoder().decode(stdout),\n stderr: textDecoder().decode(stderr),\n };\n },\n stat: async ({ path, abortSignal }) => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal: abortSignal });\n return stat == null ? null : toStatEntry(resolved, stat);\n },\n readDir: async ({ path, abortSignal }): Promise<DirEntryIface[]> => {\n const resolved = resolvePath(path);\n const entries = await transport.readDir({ path: resolved, signal: abortSignal });\n return entries.map((entry) => ({\n name: entry.name,\n path: paths.join(resolved, entry.name),\n type: entry.type,\n size: entry.size,\n }));\n },\n removePath: async ({ path, recursive, force, abortSignal }) => {\n const resolved = resolvePath(path);\n try {\n await transport.remove({\n path: resolved,\n recursive: recursive === true,\n force: force === true,\n signal: abortSignal,\n });\n } catch (error) {\n if (force === true && isNotFoundMessage(errorMessage(error))) return;\n throw error;\n }\n },\n getPortEndpoint: (options) => args.network.getPortEndpoint(options),\n getPortUrl: async (options) => (await args.network.getPortEndpoint(options)).url,\n stop: () => args.network.stop(),\n destroy: () => args.network.destroy(),\n };\n if (transport.processGone != null) session.attachProcess = attachProcess;\n return { ...session, confinedFileReader: createConfinedFileReader(session) };\n};\n\nconst readOnlyRefusal = async (): Promise<never> => {\n throw new Error('this sandbox binding is read-only');\n};\n\n// A read binding's view of a handle: reads pass through; writes, processes and\n// lifecycle are refused here as well as by the lease the provider took.\nexport const readOnlySession = <S extends NetworkSandboxSessionIface>(session: S): S => {\n const restricted: S = {\n ...session,\n writeFile: readOnlyRefusal,\n writeBinaryFile: readOnlyRefusal,\n writeTextFile: readOnlyRefusal,\n spawn: readOnlyRefusal,\n run: readOnlyRefusal,\n removePath: readOnlyRefusal,\n stop: readOnlyRefusal,\n destroy: readOnlyRefusal,\n };\n if (session.setNetworkPolicy != null) restricted.setNetworkPolicy = readOnlyRefusal;\n delete restricted.egress;\n delete restricted.attachProcess;\n const screen = session.screen;\n if (screen != null)\n restricted.screen = async (options) => {\n if (options?.viewOnly === false) return readOnlyRefusal();\n return screen.call(session, options);\n };\n return restricted;\n};\n\nconst fileWriteRefusal = async (): Promise<never> => {\n throw new Error('this sandbox binding runs processes but does not write files');\n};\n\n// An execute binding's view of a handle: reads and processes pass through; file\n// writes and lifecycle are refused.\nexport const executeOnlySession = <S extends NetworkSandboxSessionIface>(session: S): S => {\n const restricted: S = {\n ...session,\n writeFile: fileWriteRefusal,\n writeBinaryFile: fileWriteRefusal,\n writeTextFile: fileWriteRefusal,\n removePath: fileWriteRefusal,\n stop: fileWriteRefusal,\n destroy: fileWriteRefusal,\n };\n delete restricted.egress;\n return restricted;\n};\n\n// The view of a handle that a binding's access allows.\nexport const sessionForAccess = <S extends NetworkSandboxSessionIface>(\n session: S,\n access: SandboxAccess,\n): S =>\n access === SandboxAccess.Read\n ? readOnlySession(session)\n : access === SandboxAccess.Execute\n ? executeOnlySession(session)\n : session;\n"],
5
+ "mappings": "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",
6
+ "names": ["args"]
7
7
  }
@@ -6,6 +6,9 @@ const expectConformance = async (sandbox) => {
6
6
  expect(failed).toEqual([]);
7
7
  expect(report.cases.length).toBeGreaterThan(10);
8
8
  expect(report.passed).toBe(true);
9
+ expect(report.cases.filter((entry) => entry.name.includes("durable processes: "))).toHaveLength(
10
+ sandbox.attachProcess == null ? 0 : 8
11
+ );
9
12
  };
10
13
  export {
11
14
  expectConformance
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/sandboxes/testing/conformance.ts"],
4
- "sourcesContent": ["import { runSandboxConformance } from '@finalai/agent-provider/conformance';\nimport type { NetworkSandboxSessionIface } from '@finalai/agent-provider/sandbox';\nimport { expect } from 'vitest';\n\n// The contract's conformance kit on a started handle, directly and over the handle RPC.\nexport const expectConformance = async (sandbox: NetworkSandboxSessionIface): Promise<void> => {\n const report = await runSandboxConformance({ handle: sandbox, largeFileBytes: 5 * 1024 * 1024 });\n const failed = report.cases\n .filter((entry) => entry.error != null)\n .map((entry) => `${entry.name}: ${entry.error}`);\n expect(failed).toEqual([]);\n expect(report.cases.length).toBeGreaterThan(10);\n expect(report.passed).toBe(true);\n};\n"],
5
- "mappings": "AAAA,SAAS,6BAA6B;AAEtC,SAAS,cAAc;AAGhB,MAAM,oBAAoB,OAAO,YAAuD;AAC7F,QAAM,SAAS,MAAM,sBAAsB,EAAE,QAAQ,SAAS,gBAAgB,IAAI,OAAO,KAAK,CAAC;AAC/F,QAAM,SAAS,OAAO,MACnB,OAAO,CAAC,UAAU,MAAM,SAAS,IAAI,EACrC,IAAI,CAAC,UAAU,GAAG,MAAM,IAAI,KAAK,MAAM,KAAK,EAAE;AACjD,SAAO,MAAM,EAAE,QAAQ,CAAC,CAAC;AACzB,SAAO,OAAO,MAAM,MAAM,EAAE,gBAAgB,EAAE;AAC9C,SAAO,OAAO,MAAM,EAAE,KAAK,IAAI;AACjC;",
4
+ "sourcesContent": ["import { runSandboxConformance } from '@finalai/agent-provider/conformance';\nimport type { NetworkSandboxSessionIface } from '@finalai/agent-provider/sandbox';\nimport { expect } from 'vitest';\n\n// The contract's conformance kit on a started handle, directly and over the handle RPC.\nexport const expectConformance = async (sandbox: NetworkSandboxSessionIface): Promise<void> => {\n const report = await runSandboxConformance({ handle: sandbox, largeFileBytes: 5 * 1024 * 1024 });\n const failed = report.cases\n .filter((entry) => entry.error != null)\n .map((entry) => `${entry.name}: ${entry.error}`);\n expect(failed).toEqual([]);\n expect(report.cases.length).toBeGreaterThan(10);\n expect(report.passed).toBe(true);\n // A sandbox with durable processes runs their cases directly and over the wire.\n expect(report.cases.filter((entry) => entry.name.includes('durable processes: '))).toHaveLength(\n sandbox.attachProcess == null ? 0 : 8,\n );\n};\n"],
5
+ "mappings": "AAAA,SAAS,6BAA6B;AAEtC,SAAS,cAAc;AAGhB,MAAM,oBAAoB,OAAO,YAAuD;AAC7F,QAAM,SAAS,MAAM,sBAAsB,EAAE,QAAQ,SAAS,gBAAgB,IAAI,OAAO,KAAK,CAAC;AAC/F,QAAM,SAAS,OAAO,MACnB,OAAO,CAAC,UAAU,MAAM,SAAS,IAAI,EACrC,IAAI,CAAC,UAAU,GAAG,MAAM,IAAI,KAAK,MAAM,KAAK,EAAE;AACjD,SAAO,MAAM,EAAE,QAAQ,CAAC,CAAC;AACzB,SAAO,OAAO,MAAM,MAAM,EAAE,gBAAgB,EAAE;AAC9C,SAAO,OAAO,MAAM,EAAE,KAAK,IAAI;AAE/B,SAAO,OAAO,MAAM,OAAO,CAAC,UAAU,MAAM,KAAK,SAAS,qBAAqB,CAAC,CAAC,EAAE;AAAA,IACjF,QAAQ,iBAAiB,OAAO,IAAI;AAAA,EACtC;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { DURABLE_CONFORMANCE_SCRIPT } from "@finalai/agent-provider/conformance";
1
2
  import { SANDBOX_SEED_MARKER, SANDBOX_SEED_PROTOCOL } from "@finalai/agent-provider/sandbox";
2
3
  import { posix } from "node:path";
3
4
  import * as z from "zod";
@@ -155,6 +156,16 @@ const createFakeMachine = () => {
155
156
  return true;
156
157
  }
157
158
  if (script.startsWith("sleep ")) return true;
159
+ if (script === DURABLE_CONFORMANCE_SCRIPT) {
160
+ control.stdout("one\n");
161
+ control.stderr("err-one\n");
162
+ setTimeout(() => {
163
+ control.stdout("two\n");
164
+ control.stderr("err-two\n");
165
+ control.exit(7);
166
+ }, 100);
167
+ return true;
168
+ }
158
169
  if (script === "echo out; echo err >&2; exit 3") {
159
170
  control.stdout("out\n");
160
171
  control.stderr("err\n");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/sandboxes/testing/fake-machine.ts"],
4
- "sourcesContent": ["import { SANDBOX_SEED_MARKER, SANDBOX_SEED_PROTOCOL } from '@finalai/agent-provider/sandbox';\nimport { posix } from 'node:path';\nimport * as z from 'zod';\nimport { concatBytes } from \"../bytes.mjs\";\nimport { GUIDE_COMMAND_PROTOCOL } from \"../workspace-setup.mjs\";\nimport { quotedShellArguments } from \"./quoted-shell-arguments.mjs\";\n\n// An in-memory machine behind the fake Computers API, data plane and Device: a file\n// tree plus managed processes whose behaviour tests script by command.\n\nexport interface FakeExecIface {\n command: string;\n // The command with the provider's `bash -lc '\u2026'` / `exec '\u2026'` wrapper removed.\n script: string;\n cwd: string;\n env: Record<string, string>;\n}\n\nexport interface FakeProcessControlIface {\n readonly exec: FakeExecIface;\n stdout(text: string | Uint8Array): void;\n stderr(text: string | Uint8Array): void;\n exit(code: number, signal?: string | null): void;\n onStdin(listener: (data: Uint8Array) => void): void;\n onEof(listener: () => void): void;\n}\n\nexport interface FakeBehaviorIface {\n (process: FakeProcessControlIface): void;\n}\n\nexport interface FakeProcessIface {\n id: string;\n bootId: string;\n exec: FakeExecIface;\n stdout: Uint8Array;\n stderr: Uint8Array;\n exited: boolean;\n exitCode: number | null;\n signal: string | null;\n write(data: Uint8Array): void;\n eof(): void;\n terminate(signal?: string): void;\n // Resolves on the next change (output or exit), or after `ms`.\n changed(ms: number): Promise<void>;\n}\n\nexport interface FakeMachineIface {\n files: Map<string, Uint8Array>;\n dirs: Set<string>;\n // Symlink path \u2192 target path.\n links: Map<string, string>;\n execs: FakeExecIface[];\n processes: Map<string, FakeProcessIface>;\n // Tests register behaviours; the first whose predicate matches runs.\n on(match: (exec: FakeExecIface) => boolean, behavior: FakeBehaviorIface): void;\n exec(args: {\n id: string;\n command: string;\n cwd: string;\n env: Record<string, string>;\n }): FakeProcessIface;\n mkdirp(path: string): void;\n readText(path: string): string | null;\n}\n\n// Removes the session handle's `bash -lc '\u2026'` or `exec '\u2026' '\u2026'` wrapper.\nexport const unwrapCommand = (command: string): string => {\n if (command.startsWith('bash -lc '))\n return quotedShellArguments(command.slice('bash -lc '.length)).join(' ');\n if (command.startsWith('exec bash -lc '))\n return quotedShellArguments(command.slice('exec bash -lc '.length)).join(' ');\n if (command.startsWith('exec '))\n return quotedShellArguments(command.slice('exec '.length)).join(' ');\n return command;\n};\n\n// The first single-quoted argument after `marker` in a script.\nexport const quotedAfter = (script: string, marker: string): string | null => {\n const index = script.indexOf(marker);\n if (index < 0) return null;\n return quotedShellArguments(script.slice(index + marker.length))[0] ?? null;\n};\n\nconst bytes = (data: string | Uint8Array): Uint8Array =>\n data instanceof Uint8Array ? data : new TextEncoder().encode(data);\n\nexport const createFakeMachine = (): FakeMachineIface => {\n const files = new Map<string, Uint8Array>();\n const dirs = new Set<string>(['/', '/workspaces', '/tmp']);\n const links = new Map<string, string>();\n const behaviors: { match: (exec: FakeExecIface) => boolean; behavior: FakeBehaviorIface }[] = [];\n const execs: FakeExecIface[] = [];\n const processes = new Map<string, FakeProcessIface>();\n\n const mkdirp = (path: string): void => {\n let current = posix.normalize(path);\n while (current !== '/' && !dirs.has(current)) {\n dirs.add(current);\n current = posix.dirname(current);\n }\n };\n\n // Built-in behaviour for the provider's own helper scripts.\n const builtin = (control: FakeProcessControlIface): boolean => {\n const script = control.exec.script;\n if (script.includes(GUIDE_COMMAND_PROTOCOL)) {\n const chunks: Uint8Array[] = [];\n control.onStdin((data) => chunks.push(data));\n control.onEof(() => {\n const payload = z\n .object({ root: z.string(), files: z.record(z.string(), z.string()) })\n .parse(JSON.parse(new TextDecoder().decode(concatBytes(chunks))));\n if (\n [...links.keys()].some(\n (path) => payload.root === path || payload.root.startsWith(`${path}/`),\n )\n ) {\n control.stderr('symlink command directory');\n control.exit(1);\n return;\n }\n for (const [name, content] of Object.entries(payload.files)) {\n const path = posix.join(payload.root, name);\n const existing = files.get(path);\n if (\n links.has(path) ||\n (existing != null && new TextDecoder().decode(existing) !== content)\n ) {\n control.stderr('guide command differs from its pinned source');\n control.exit(1);\n return;\n }\n mkdirp(payload.root);\n files.set(path, bytes(content));\n }\n control.exit(0);\n });\n return true;\n }\n if (script.includes(SANDBOX_SEED_PROTOCOL)) {\n const chunks: Uint8Array[] = [];\n control.onStdin((data) => chunks.push(data));\n control.onEof(() => {\n const payload = z\n .object({ root: z.string(), files: z.record(z.string(), z.string()) })\n .parse(JSON.parse(new TextDecoder().decode(concatBytes(chunks))));\n const marker = posix.join(payload.root, '.final', SANDBOX_SEED_MARKER);\n if (files.has(marker)) {\n control.exit(0);\n return;\n }\n for (const [path, content] of Object.entries(payload.files)) {\n const target = posix.join(payload.root, path);\n if (files.has(target)) continue;\n mkdirp(posix.dirname(target));\n files.set(target, bytes(content));\n }\n mkdirp(posix.dirname(marker));\n files.set(marker, bytes(SANDBOX_SEED_PROTOCOL));\n control.exit(0);\n });\n return true;\n }\n // The provider's helper scripts, matched by how they start.\n const leading = (marker: string): string | null =>\n script.startsWith(marker) ? quotedAfter(script, marker) : null;\n const statPath = leading('if [ ! -e ');\n if (statPath != null && script.includes('stat -c')) {\n // Like `stat` without -L: a symlink reports its own length.\n const link = links.get(statPath);\n if (link != null) control.stdout(`symbolic link|${link.length}|1700000000|777\\n`);\n else if (dirs.has(statPath)) control.stdout(`directory|4096|1700000000|755\\n`);\n else if (files.has(statPath))\n control.stdout(`regular file|${files.get(statPath)?.byteLength ?? 0}|1700000000|644\\n`);\n else control.stdout('missing\\n');\n control.exit(0);\n return true;\n }\n const listPath = leading('[ -d ');\n if (listPath != null && script.includes('find ')) {\n if (!dirs.has(listPath)) {\n control.stderr('Not a directory\\n');\n control.exit(2);\n return true;\n }\n const entries: string[] = [];\n for (const dir of dirs) {\n if (dir !== listPath && posix.dirname(dir) === listPath)\n entries.push(`d\\t4096\\t${posix.basename(dir)}\\0`);\n }\n for (const [path, content] of files) {\n if (posix.dirname(path) === listPath)\n entries.push(`f\\t${content.byteLength}\\t${posix.basename(path)}\\0`);\n }\n control.stdout(entries.join(''));\n control.exit(0);\n return true;\n }\n const mkdirPath = leading('mkdir -p -- ');\n if (mkdirPath != null) {\n mkdirp(mkdirPath);\n control.exit(0);\n return true;\n }\n const removePath = leading('if [ -d ');\n if (removePath != null && script.includes('rm')) {\n if (dirs.has(removePath)) {\n for (const dir of [...dirs])\n if (dir === removePath || dir.startsWith(`${removePath}/`)) dirs.delete(dir);\n for (const path of [...files.keys()])\n if (path.startsWith(`${removePath}/`)) files.delete(path);\n } else if (files.has(removePath)) {\n files.delete(removePath);\n } else if (!script.includes('-f')) {\n control.stderr(`rm: cannot remove: No such file or directory\\n`);\n control.exit(1);\n return true;\n }\n control.exit(0);\n return true;\n }\n if (script.includes('git clone')) {\n const destination = quotedAfter(script.slice(script.indexOf('git clone')), \"' \");\n if (destination != null) mkdirp(`${destination}/.git`);\n control.exit(0);\n return true;\n }\n // Enough of a shell for the conformance kit's process cases.\n if (script === 'cat') {\n control.onStdin((data) => control.stdout(data));\n control.onEof(() => control.exit(0));\n return true;\n }\n if (script.startsWith('sleep ')) return true;\n if (script === 'echo out; echo err >&2; exit 3') {\n control.stdout('out\\n');\n control.stderr('err\\n');\n control.exit(3);\n return true;\n }\n if (script.startsWith('printenv ')) {\n const name = script.slice('printenv '.length);\n const value = Object.hasOwn(control.exec.env, name) ? control.exec.env[name] : undefined;\n if (value != null) control.stdout(`${value}\\n`);\n control.exit(value == null ? 1 : 0);\n return true;\n }\n if (script === 'echo' || script.startsWith('echo ')) {\n control.stdout(`${script.slice('echo '.length)}\\n`);\n control.exit(0);\n return true;\n }\n return false;\n };\n\n const exec: FakeMachineIface['exec'] = (args) => {\n const record: FakeExecIface = {\n command: args.command,\n script: unwrapCommand(args.command),\n cwd: args.cwd,\n env: args.env,\n };\n execs.push(record);\n const stdinListeners: ((data: Uint8Array) => void)[] = [];\n const eofListeners: (() => void)[] = [];\n let waiters: (() => void)[] = [];\n const notify = (): void => {\n const pending = waiters;\n waiters = [];\n for (const wake of pending) wake();\n };\n const process: FakeProcessIface = {\n id: args.id,\n bootId: 'boot-1',\n exec: record,\n stdout: new Uint8Array(),\n stderr: new Uint8Array(),\n exited: false,\n exitCode: null,\n signal: null,\n write: (data) => {\n for (const listener of stdinListeners) listener(data);\n },\n eof: () => {\n for (const listener of eofListeners) listener();\n },\n terminate: (signal) => {\n if (process.exited) return;\n process.exited = true;\n process.signal = signal ?? 'SIGKILL';\n notify();\n },\n changed: (ms) =>\n new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n waiters.push(() => {\n clearTimeout(timer);\n resolve();\n });\n }),\n };\n const control: FakeProcessControlIface = {\n exec: record,\n stdout: (data) => {\n process.stdout = concatBytes([process.stdout, bytes(data)]);\n notify();\n },\n stderr: (data) => {\n process.stderr = concatBytes([process.stderr, bytes(data)]);\n notify();\n },\n exit: (code, signal = null) => {\n if (process.exited) return;\n process.exited = true;\n process.exitCode = signal == null ? code : null;\n process.signal = signal;\n notify();\n },\n onStdin: (listener) => stdinListeners.push(listener),\n onEof: (listener) => eofListeners.push(listener),\n };\n processes.set(args.id, process);\n const custom = behaviors.find((entry) => entry.match(record));\n if (custom != null) custom.behavior(control);\n else if (!builtin(control)) control.exit(0);\n return process;\n };\n\n return {\n files,\n dirs,\n links,\n execs,\n processes,\n on: (match, behavior) => behaviors.push({ match, behavior }),\n exec,\n mkdirp,\n readText: (path) => {\n const content = files.get(path);\n return content == null ? null : new TextDecoder().decode(content);\n },\n };\n};\n\n// Output from offsets, waiting up to `waitMs` (capped for fast tests) for news.\nexport const readFakeOutput = async (\n process: FakeProcessIface,\n offsets: { stdout: number; stderr: number; waitMs: number },\n): Promise<{ stdout: Uint8Array; stderr: Uint8Array; exited: boolean }> => {\n if (\n !process.exited &&\n process.stdout.byteLength <= offsets.stdout &&\n process.stderr.byteLength <= offsets.stderr &&\n offsets.waitMs > 0\n ) {\n await process.changed(Math.min(offsets.waitMs, 50));\n }\n return {\n stdout: process.stdout.subarray(offsets.stdout),\n stderr: process.stderr.subarray(offsets.stderr),\n exited: process.exited,\n };\n};\n"],
5
- "mappings": "AAAA,SAAS,qBAAqB,6BAA6B;AAC3D,SAAS,aAAa;AACtB,YAAY,OAAO;AACnB,SAAS,mBAAmB;AAC5B,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AA8D9B,MAAM,gBAAgB,CAAC,YAA4B;AACxD,MAAI,QAAQ,WAAW,WAAW;AAChC,WAAO,qBAAqB,QAAQ,MAAM,YAAY,MAAM,CAAC,EAAE,KAAK,GAAG;AACzE,MAAI,QAAQ,WAAW,gBAAgB;AACrC,WAAO,qBAAqB,QAAQ,MAAM,iBAAiB,MAAM,CAAC,EAAE,KAAK,GAAG;AAC9E,MAAI,QAAQ,WAAW,OAAO;AAC5B,WAAO,qBAAqB,QAAQ,MAAM,QAAQ,MAAM,CAAC,EAAE,KAAK,GAAG;AACrE,SAAO;AACT;AAGO,MAAM,cAAc,CAAC,QAAgB,WAAkC;AAC5E,QAAM,QAAQ,OAAO,QAAQ,MAAM;AACnC,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,qBAAqB,OAAO,MAAM,QAAQ,OAAO,MAAM,CAAC,EAAE,CAAC,KAAK;AACzE;AAEA,MAAM,QAAQ,CAAC,SACb,gBAAgB,aAAa,OAAO,IAAI,YAAY,EAAE,OAAO,IAAI;AAE5D,MAAM,oBAAoB,MAAwB;AACvD,QAAM,QAAQ,oBAAI,IAAwB;AAC1C,QAAM,OAAO,oBAAI,IAAY,CAAC,KAAK,eAAe,MAAM,CAAC;AACzD,QAAM,QAAQ,oBAAI,IAAoB;AACtC,QAAM,YAAwF,CAAC;AAC/F,QAAM,QAAyB,CAAC;AAChC,QAAM,YAAY,oBAAI,IAA8B;AAEpD,QAAM,SAAS,CAAC,SAAuB;AACrC,QAAI,UAAU,MAAM,UAAU,IAAI;AAClC,WAAO,YAAY,OAAO,CAAC,KAAK,IAAI,OAAO,GAAG;AAC5C,WAAK,IAAI,OAAO;AAChB,gBAAU,MAAM,QAAQ,OAAO;AAAA,IACjC;AAAA,EACF;AAGA,QAAM,UAAU,CAAC,YAA8C;AAC7D,UAAM,SAAS,QAAQ,KAAK;AAC5B,QAAI,OAAO,SAAS,sBAAsB,GAAG;AAC3C,YAAM,SAAuB,CAAC;AAC9B,cAAQ,QAAQ,CAAC,SAAS,OAAO,KAAK,IAAI,CAAC;AAC3C,cAAQ,MAAM,MAAM;AAClB,cAAM,UAAU,EACb,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,EACpE,MAAM,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC;AAClE,YACE,CAAC,GAAG,MAAM,KAAK,CAAC,EAAE;AAAA,UAChB,CAAC,SAAS,QAAQ,SAAS,QAAQ,QAAQ,KAAK,WAAW,GAAG,IAAI,GAAG;AAAA,QACvE,GACA;AACA,kBAAQ,OAAO,2BAA2B;AAC1C,kBAAQ,KAAK,CAAC;AACd;AAAA,QACF;AACA,mBAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,QAAQ,KAAK,GAAG;AAC3D,gBAAM,OAAO,MAAM,KAAK,QAAQ,MAAM,IAAI;AAC1C,gBAAM,WAAW,MAAM,IAAI,IAAI;AAC/B,cACE,MAAM,IAAI,IAAI,KACb,YAAY,QAAQ,IAAI,YAAY,EAAE,OAAO,QAAQ,MAAM,SAC5D;AACA,oBAAQ,OAAO,8CAA8C;AAC7D,oBAAQ,KAAK,CAAC;AACd;AAAA,UACF;AACA,iBAAO,QAAQ,IAAI;AACnB,gBAAM,IAAI,MAAM,MAAM,OAAO,CAAC;AAAA,QAChC;AACA,gBAAQ,KAAK,CAAC;AAAA,MAChB,CAAC;AACD,aAAO;AAAA,IACT;AACA,QAAI,OAAO,SAAS,qBAAqB,GAAG;AAC1C,YAAM,SAAuB,CAAC;AAC9B,cAAQ,QAAQ,CAAC,SAAS,OAAO,KAAK,IAAI,CAAC;AAC3C,cAAQ,MAAM,MAAM;AAClB,cAAM,UAAU,EACb,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,EACpE,MAAM,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC;AAClE,cAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,UAAU,mBAAmB;AACrE,YAAI,MAAM,IAAI,MAAM,GAAG;AACrB,kBAAQ,KAAK,CAAC;AACd;AAAA,QACF;AACA,mBAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,QAAQ,KAAK,GAAG;AAC3D,gBAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,IAAI;AAC5C,cAAI,MAAM,IAAI,MAAM,EAAG;AACvB,iBAAO,MAAM,QAAQ,MAAM,CAAC;AAC5B,gBAAM,IAAI,QAAQ,MAAM,OAAO,CAAC;AAAA,QAClC;AACA,eAAO,MAAM,QAAQ,MAAM,CAAC;AAC5B,cAAM,IAAI,QAAQ,MAAM,qBAAqB,CAAC;AAC9C,gBAAQ,KAAK,CAAC;AAAA,MAChB,CAAC;AACD,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,CAAC,WACf,OAAO,WAAW,MAAM,IAAI,YAAY,QAAQ,MAAM,IAAI;AAC5D,UAAM,WAAW,QAAQ,YAAY;AACrC,QAAI,YAAY,QAAQ,OAAO,SAAS,SAAS,GAAG;AAElD,YAAM,OAAO,MAAM,IAAI,QAAQ;AAC/B,UAAI,QAAQ,KAAM,SAAQ,OAAO,iBAAiB,KAAK,MAAM;AAAA,CAAmB;AAAA,eACvE,KAAK,IAAI,QAAQ,EAAG,SAAQ,OAAO;AAAA,CAAiC;AAAA,eACpE,MAAM,IAAI,QAAQ;AACzB,gBAAQ,OAAO,gBAAgB,MAAM,IAAI,QAAQ,GAAG,cAAc,CAAC;AAAA,CAAmB;AAAA,UACnF,SAAQ,OAAO,WAAW;AAC/B,cAAQ,KAAK,CAAC;AACd,aAAO;AAAA,IACT;AACA,UAAM,WAAW,QAAQ,OAAO;AAChC,QAAI,YAAY,QAAQ,OAAO,SAAS,OAAO,GAAG;AAChD,UAAI,CAAC,KAAK,IAAI,QAAQ,GAAG;AACvB,gBAAQ,OAAO,mBAAmB;AAClC,gBAAQ,KAAK,CAAC;AACd,eAAO;AAAA,MACT;AACA,YAAM,UAAoB,CAAC;AAC3B,iBAAW,OAAO,MAAM;AACtB,YAAI,QAAQ,YAAY,MAAM,QAAQ,GAAG,MAAM;AAC7C,kBAAQ,KAAK,UAAY,MAAM,SAAS,GAAG,CAAC,IAAI;AAAA,MACpD;AACA,iBAAW,CAAC,MAAM,OAAO,KAAK,OAAO;AACnC,YAAI,MAAM,QAAQ,IAAI,MAAM;AAC1B,kBAAQ,KAAK,KAAM,QAAQ,UAAU,IAAK,MAAM,SAAS,IAAI,CAAC,IAAI;AAAA,MACtE;AACA,cAAQ,OAAO,QAAQ,KAAK,EAAE,CAAC;AAC/B,cAAQ,KAAK,CAAC;AACd,aAAO;AAAA,IACT;AACA,UAAM,YAAY,QAAQ,cAAc;AACxC,QAAI,aAAa,MAAM;AACrB,aAAO,SAAS;AAChB,cAAQ,KAAK,CAAC;AACd,aAAO;AAAA,IACT;AACA,UAAM,aAAa,QAAQ,UAAU;AACrC,QAAI,cAAc,QAAQ,OAAO,SAAS,IAAI,GAAG;AAC/C,UAAI,KAAK,IAAI,UAAU,GAAG;AACxB,mBAAW,OAAO,CAAC,GAAG,IAAI;AACxB,cAAI,QAAQ,cAAc,IAAI,WAAW,GAAG,UAAU,GAAG,EAAG,MAAK,OAAO,GAAG;AAC7E,mBAAW,QAAQ,CAAC,GAAG,MAAM,KAAK,CAAC;AACjC,cAAI,KAAK,WAAW,GAAG,UAAU,GAAG,EAAG,OAAM,OAAO,IAAI;AAAA,MAC5D,WAAW,MAAM,IAAI,UAAU,GAAG;AAChC,cAAM,OAAO,UAAU;AAAA,MACzB,WAAW,CAAC,OAAO,SAAS,IAAI,GAAG;AACjC,gBAAQ,OAAO;AAAA,CAAgD;AAC/D,gBAAQ,KAAK,CAAC;AACd,eAAO;AAAA,MACT;AACA,cAAQ,KAAK,CAAC;AACd,aAAO;AAAA,IACT;AACA,QAAI,OAAO,SAAS,WAAW,GAAG;AAChC,YAAM,cAAc,YAAY,OAAO,MAAM,OAAO,QAAQ,WAAW,CAAC,GAAG,IAAI;AAC/E,UAAI,eAAe,KAAM,QAAO,GAAG,WAAW,OAAO;AACrD,cAAQ,KAAK,CAAC;AACd,aAAO;AAAA,IACT;AAEA,QAAI,WAAW,OAAO;AACpB,cAAQ,QAAQ,CAAC,SAAS,QAAQ,OAAO,IAAI,CAAC;AAC9C,cAAQ,MAAM,MAAM,QAAQ,KAAK,CAAC,CAAC;AACnC,aAAO;AAAA,IACT;AACA,QAAI,OAAO,WAAW,QAAQ,EAAG,QAAO;AACxC,QAAI,WAAW,kCAAkC;AAC/C,cAAQ,OAAO,OAAO;AACtB,cAAQ,OAAO,OAAO;AACtB,cAAQ,KAAK,CAAC;AACd,aAAO;AAAA,IACT;AACA,QAAI,OAAO,WAAW,WAAW,GAAG;AAClC,YAAM,OAAO,OAAO,MAAM,YAAY,MAAM;AAC5C,YAAM,QAAQ,OAAO,OAAO,QAAQ,KAAK,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI,IAAI,IAAI;AAC/E,UAAI,SAAS,KAAM,SAAQ,OAAO,GAAG,KAAK;AAAA,CAAI;AAC9C,cAAQ,KAAK,SAAS,OAAO,IAAI,CAAC;AAClC,aAAO;AAAA,IACT;AACA,QAAI,WAAW,UAAU,OAAO,WAAW,OAAO,GAAG;AACnD,cAAQ,OAAO,GAAG,OAAO,MAAM,QAAQ,MAAM,CAAC;AAAA,CAAI;AAClD,cAAQ,KAAK,CAAC;AACd,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,QAAM,OAAiC,CAAC,SAAS;AAC/C,UAAM,SAAwB;AAAA,MAC5B,SAAS,KAAK;AAAA,MACd,QAAQ,cAAc,KAAK,OAAO;AAAA,MAClC,KAAK,KAAK;AAAA,MACV,KAAK,KAAK;AAAA,IACZ;AACA,UAAM,KAAK,MAAM;AACjB,UAAM,iBAAiD,CAAC;AACxD,UAAM,eAA+B,CAAC;AACtC,QAAI,UAA0B,CAAC;AAC/B,UAAM,SAAS,MAAY;AACzB,YAAM,UAAU;AAChB,gBAAU,CAAC;AACX,iBAAW,QAAQ,QAAS,MAAK;AAAA,IACnC;AACA,UAAM,UAA4B;AAAA,MAChC,IAAI,KAAK;AAAA,MACT,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ,IAAI,WAAW;AAAA,MACvB,QAAQ,IAAI,WAAW;AAAA,MACvB,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,OAAO,CAAC,SAAS;AACf,mBAAW,YAAY,eAAgB,UAAS,IAAI;AAAA,MACtD;AAAA,MACA,KAAK,MAAM;AACT,mBAAW,YAAY,aAAc,UAAS;AAAA,MAChD;AAAA,MACA,WAAW,CAAC,WAAW;AACrB,YAAI,QAAQ,OAAQ;AACpB,gBAAQ,SAAS;AACjB,gBAAQ,SAAS,UAAU;AAC3B,eAAO;AAAA,MACT;AAAA,MACA,SAAS,CAAC,OACR,IAAI,QAAQ,CAAC,YAAY;AACvB,cAAM,QAAQ,WAAW,SAAS,EAAE;AACpC,gBAAQ,KAAK,MAAM;AACjB,uBAAa,KAAK;AAClB,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAAA,IACL;AACA,UAAM,UAAmC;AAAA,MACvC,MAAM;AAAA,MACN,QAAQ,CAAC,SAAS;AAChB,gBAAQ,SAAS,YAAY,CAAC,QAAQ,QAAQ,MAAM,IAAI,CAAC,CAAC;AAC1D,eAAO;AAAA,MACT;AAAA,MACA,QAAQ,CAAC,SAAS;AAChB,gBAAQ,SAAS,YAAY,CAAC,QAAQ,QAAQ,MAAM,IAAI,CAAC,CAAC;AAC1D,eAAO;AAAA,MACT;AAAA,MACA,MAAM,CAAC,MAAM,SAAS,SAAS;AAC7B,YAAI,QAAQ,OAAQ;AACpB,gBAAQ,SAAS;AACjB,gBAAQ,WAAW,UAAU,OAAO,OAAO;AAC3C,gBAAQ,SAAS;AACjB,eAAO;AAAA,MACT;AAAA,MACA,SAAS,CAAC,aAAa,eAAe,KAAK,QAAQ;AAAA,MACnD,OAAO,CAAC,aAAa,aAAa,KAAK,QAAQ;AAAA,IACjD;AACA,cAAU,IAAI,KAAK,IAAI,OAAO;AAC9B,UAAM,SAAS,UAAU,KAAK,CAAC,UAAU,MAAM,MAAM,MAAM,CAAC;AAC5D,QAAI,UAAU,KAAM,QAAO,SAAS,OAAO;AAAA,aAClC,CAAC,QAAQ,OAAO,EAAG,SAAQ,KAAK,CAAC;AAC1C,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,IAAI,CAAC,OAAO,aAAa,UAAU,KAAK,EAAE,OAAO,SAAS,CAAC;AAAA,IAC3D;AAAA,IACA;AAAA,IACA,UAAU,CAAC,SAAS;AAClB,YAAM,UAAU,MAAM,IAAI,IAAI;AAC9B,aAAO,WAAW,OAAO,OAAO,IAAI,YAAY,EAAE,OAAO,OAAO;AAAA,IAClE;AAAA,EACF;AACF;AAGO,MAAM,iBAAiB,OAC5B,SACA,YACyE;AACzE,MACE,CAAC,QAAQ,UACT,QAAQ,OAAO,cAAc,QAAQ,UACrC,QAAQ,OAAO,cAAc,QAAQ,UACrC,QAAQ,SAAS,GACjB;AACA,UAAM,QAAQ,QAAQ,KAAK,IAAI,QAAQ,QAAQ,EAAE,CAAC;AAAA,EACpD;AACA,SAAO;AAAA,IACL,QAAQ,QAAQ,OAAO,SAAS,QAAQ,MAAM;AAAA,IAC9C,QAAQ,QAAQ,OAAO,SAAS,QAAQ,MAAM;AAAA,IAC9C,QAAQ,QAAQ;AAAA,EAClB;AACF;",
4
+ "sourcesContent": ["import { DURABLE_CONFORMANCE_SCRIPT } from '@finalai/agent-provider/conformance';\nimport { SANDBOX_SEED_MARKER, SANDBOX_SEED_PROTOCOL } from '@finalai/agent-provider/sandbox';\nimport { posix } from 'node:path';\nimport * as z from 'zod';\nimport { concatBytes } from \"../bytes.mjs\";\nimport { GUIDE_COMMAND_PROTOCOL } from \"../workspace-setup.mjs\";\nimport { quotedShellArguments } from \"./quoted-shell-arguments.mjs\";\n\n// An in-memory machine behind the fake Computers API, data plane and Device: a file\n// tree plus managed processes whose behaviour tests script by command.\n\nexport interface FakeExecIface {\n command: string;\n // The command with the provider's `bash -lc '\u2026'` / `exec '\u2026'` wrapper removed.\n script: string;\n cwd: string;\n env: Record<string, string>;\n}\n\nexport interface FakeProcessControlIface {\n readonly exec: FakeExecIface;\n stdout(text: string | Uint8Array): void;\n stderr(text: string | Uint8Array): void;\n exit(code: number, signal?: string | null): void;\n onStdin(listener: (data: Uint8Array) => void): void;\n onEof(listener: () => void): void;\n}\n\nexport interface FakeBehaviorIface {\n (process: FakeProcessControlIface): void;\n}\n\nexport interface FakeProcessIface {\n id: string;\n bootId: string;\n exec: FakeExecIface;\n stdout: Uint8Array;\n stderr: Uint8Array;\n exited: boolean;\n exitCode: number | null;\n signal: string | null;\n write(data: Uint8Array): void;\n eof(): void;\n terminate(signal?: string): void;\n // Resolves on the next change (output or exit), or after `ms`.\n changed(ms: number): Promise<void>;\n}\n\nexport interface FakeMachineIface {\n files: Map<string, Uint8Array>;\n dirs: Set<string>;\n // Symlink path \u2192 target path.\n links: Map<string, string>;\n execs: FakeExecIface[];\n processes: Map<string, FakeProcessIface>;\n // Tests register behaviours; the first whose predicate matches runs.\n on(match: (exec: FakeExecIface) => boolean, behavior: FakeBehaviorIface): void;\n exec(args: {\n id: string;\n command: string;\n cwd: string;\n env: Record<string, string>;\n }): FakeProcessIface;\n mkdirp(path: string): void;\n readText(path: string): string | null;\n}\n\n// Removes the session handle's `bash -lc '\u2026'` or `exec '\u2026' '\u2026'` wrapper.\nexport const unwrapCommand = (command: string): string => {\n if (command.startsWith('bash -lc '))\n return quotedShellArguments(command.slice('bash -lc '.length)).join(' ');\n if (command.startsWith('exec bash -lc '))\n return quotedShellArguments(command.slice('exec bash -lc '.length)).join(' ');\n if (command.startsWith('exec '))\n return quotedShellArguments(command.slice('exec '.length)).join(' ');\n return command;\n};\n\n// The first single-quoted argument after `marker` in a script.\nexport const quotedAfter = (script: string, marker: string): string | null => {\n const index = script.indexOf(marker);\n if (index < 0) return null;\n return quotedShellArguments(script.slice(index + marker.length))[0] ?? null;\n};\n\nconst bytes = (data: string | Uint8Array): Uint8Array =>\n data instanceof Uint8Array ? data : new TextEncoder().encode(data);\n\nexport const createFakeMachine = (): FakeMachineIface => {\n const files = new Map<string, Uint8Array>();\n const dirs = new Set<string>(['/', '/workspaces', '/tmp']);\n const links = new Map<string, string>();\n const behaviors: { match: (exec: FakeExecIface) => boolean; behavior: FakeBehaviorIface }[] = [];\n const execs: FakeExecIface[] = [];\n const processes = new Map<string, FakeProcessIface>();\n\n const mkdirp = (path: string): void => {\n let current = posix.normalize(path);\n while (current !== '/' && !dirs.has(current)) {\n dirs.add(current);\n current = posix.dirname(current);\n }\n };\n\n // Built-in behaviour for the provider's own helper scripts.\n const builtin = (control: FakeProcessControlIface): boolean => {\n const script = control.exec.script;\n if (script.includes(GUIDE_COMMAND_PROTOCOL)) {\n const chunks: Uint8Array[] = [];\n control.onStdin((data) => chunks.push(data));\n control.onEof(() => {\n const payload = z\n .object({ root: z.string(), files: z.record(z.string(), z.string()) })\n .parse(JSON.parse(new TextDecoder().decode(concatBytes(chunks))));\n if (\n [...links.keys()].some(\n (path) => payload.root === path || payload.root.startsWith(`${path}/`),\n )\n ) {\n control.stderr('symlink command directory');\n control.exit(1);\n return;\n }\n for (const [name, content] of Object.entries(payload.files)) {\n const path = posix.join(payload.root, name);\n const existing = files.get(path);\n if (\n links.has(path) ||\n (existing != null && new TextDecoder().decode(existing) !== content)\n ) {\n control.stderr('guide command differs from its pinned source');\n control.exit(1);\n return;\n }\n mkdirp(payload.root);\n files.set(path, bytes(content));\n }\n control.exit(0);\n });\n return true;\n }\n if (script.includes(SANDBOX_SEED_PROTOCOL)) {\n const chunks: Uint8Array[] = [];\n control.onStdin((data) => chunks.push(data));\n control.onEof(() => {\n const payload = z\n .object({ root: z.string(), files: z.record(z.string(), z.string()) })\n .parse(JSON.parse(new TextDecoder().decode(concatBytes(chunks))));\n const marker = posix.join(payload.root, '.final', SANDBOX_SEED_MARKER);\n if (files.has(marker)) {\n control.exit(0);\n return;\n }\n for (const [path, content] of Object.entries(payload.files)) {\n const target = posix.join(payload.root, path);\n if (files.has(target)) continue;\n mkdirp(posix.dirname(target));\n files.set(target, bytes(content));\n }\n mkdirp(posix.dirname(marker));\n files.set(marker, bytes(SANDBOX_SEED_PROTOCOL));\n control.exit(0);\n });\n return true;\n }\n // The provider's helper scripts, matched by how they start.\n const leading = (marker: string): string | null =>\n script.startsWith(marker) ? quotedAfter(script, marker) : null;\n const statPath = leading('if [ ! -e ');\n if (statPath != null && script.includes('stat -c')) {\n // Like `stat` without -L: a symlink reports its own length.\n const link = links.get(statPath);\n if (link != null) control.stdout(`symbolic link|${link.length}|1700000000|777\\n`);\n else if (dirs.has(statPath)) control.stdout(`directory|4096|1700000000|755\\n`);\n else if (files.has(statPath))\n control.stdout(`regular file|${files.get(statPath)?.byteLength ?? 0}|1700000000|644\\n`);\n else control.stdout('missing\\n');\n control.exit(0);\n return true;\n }\n const listPath = leading('[ -d ');\n if (listPath != null && script.includes('find ')) {\n if (!dirs.has(listPath)) {\n control.stderr('Not a directory\\n');\n control.exit(2);\n return true;\n }\n const entries: string[] = [];\n for (const dir of dirs) {\n if (dir !== listPath && posix.dirname(dir) === listPath)\n entries.push(`d\\t4096\\t${posix.basename(dir)}\\0`);\n }\n for (const [path, content] of files) {\n if (posix.dirname(path) === listPath)\n entries.push(`f\\t${content.byteLength}\\t${posix.basename(path)}\\0`);\n }\n control.stdout(entries.join(''));\n control.exit(0);\n return true;\n }\n const mkdirPath = leading('mkdir -p -- ');\n if (mkdirPath != null) {\n mkdirp(mkdirPath);\n control.exit(0);\n return true;\n }\n const removePath = leading('if [ -d ');\n if (removePath != null && script.includes('rm')) {\n if (dirs.has(removePath)) {\n for (const dir of [...dirs])\n if (dir === removePath || dir.startsWith(`${removePath}/`)) dirs.delete(dir);\n for (const path of [...files.keys()])\n if (path.startsWith(`${removePath}/`)) files.delete(path);\n } else if (files.has(removePath)) {\n files.delete(removePath);\n } else if (!script.includes('-f')) {\n control.stderr(`rm: cannot remove: No such file or directory\\n`);\n control.exit(1);\n return true;\n }\n control.exit(0);\n return true;\n }\n if (script.includes('git clone')) {\n const destination = quotedAfter(script.slice(script.indexOf('git clone')), \"' \");\n if (destination != null) mkdirp(`${destination}/.git`);\n control.exit(0);\n return true;\n }\n // Enough of a shell for the conformance kit's process cases.\n if (script === 'cat') {\n control.onStdin((data) => control.stdout(data));\n control.onEof(() => control.exit(0));\n return true;\n }\n if (script.startsWith('sleep ')) return true;\n if (script === DURABLE_CONFORMANCE_SCRIPT) {\n control.stdout('one\\n');\n control.stderr('err-one\\n');\n setTimeout(() => {\n control.stdout('two\\n');\n control.stderr('err-two\\n');\n control.exit(7);\n }, 100);\n return true;\n }\n if (script === 'echo out; echo err >&2; exit 3') {\n control.stdout('out\\n');\n control.stderr('err\\n');\n control.exit(3);\n return true;\n }\n if (script.startsWith('printenv ')) {\n const name = script.slice('printenv '.length);\n const value = Object.hasOwn(control.exec.env, name) ? control.exec.env[name] : undefined;\n if (value != null) control.stdout(`${value}\\n`);\n control.exit(value == null ? 1 : 0);\n return true;\n }\n if (script === 'echo' || script.startsWith('echo ')) {\n control.stdout(`${script.slice('echo '.length)}\\n`);\n control.exit(0);\n return true;\n }\n return false;\n };\n\n const exec: FakeMachineIface['exec'] = (args) => {\n const record: FakeExecIface = {\n command: args.command,\n script: unwrapCommand(args.command),\n cwd: args.cwd,\n env: args.env,\n };\n execs.push(record);\n const stdinListeners: ((data: Uint8Array) => void)[] = [];\n const eofListeners: (() => void)[] = [];\n let waiters: (() => void)[] = [];\n const notify = (): void => {\n const pending = waiters;\n waiters = [];\n for (const wake of pending) wake();\n };\n const process: FakeProcessIface = {\n id: args.id,\n bootId: 'boot-1',\n exec: record,\n stdout: new Uint8Array(),\n stderr: new Uint8Array(),\n exited: false,\n exitCode: null,\n signal: null,\n write: (data) => {\n for (const listener of stdinListeners) listener(data);\n },\n eof: () => {\n for (const listener of eofListeners) listener();\n },\n terminate: (signal) => {\n if (process.exited) return;\n process.exited = true;\n process.signal = signal ?? 'SIGKILL';\n notify();\n },\n changed: (ms) =>\n new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n waiters.push(() => {\n clearTimeout(timer);\n resolve();\n });\n }),\n };\n const control: FakeProcessControlIface = {\n exec: record,\n stdout: (data) => {\n process.stdout = concatBytes([process.stdout, bytes(data)]);\n notify();\n },\n stderr: (data) => {\n process.stderr = concatBytes([process.stderr, bytes(data)]);\n notify();\n },\n exit: (code, signal = null) => {\n if (process.exited) return;\n process.exited = true;\n process.exitCode = signal == null ? code : null;\n process.signal = signal;\n notify();\n },\n onStdin: (listener) => stdinListeners.push(listener),\n onEof: (listener) => eofListeners.push(listener),\n };\n processes.set(args.id, process);\n const custom = behaviors.find((entry) => entry.match(record));\n if (custom != null) custom.behavior(control);\n else if (!builtin(control)) control.exit(0);\n return process;\n };\n\n return {\n files,\n dirs,\n links,\n execs,\n processes,\n on: (match, behavior) => behaviors.push({ match, behavior }),\n exec,\n mkdirp,\n readText: (path) => {\n const content = files.get(path);\n return content == null ? null : new TextDecoder().decode(content);\n },\n };\n};\n\n// Output from offsets, waiting up to `waitMs` (capped for fast tests) for news.\nexport const readFakeOutput = async (\n process: FakeProcessIface,\n offsets: { stdout: number; stderr: number; waitMs: number },\n): Promise<{ stdout: Uint8Array; stderr: Uint8Array; exited: boolean }> => {\n if (\n !process.exited &&\n process.stdout.byteLength <= offsets.stdout &&\n process.stderr.byteLength <= offsets.stderr &&\n offsets.waitMs > 0\n ) {\n await process.changed(Math.min(offsets.waitMs, 50));\n }\n return {\n stdout: process.stdout.subarray(offsets.stdout),\n stderr: process.stderr.subarray(offsets.stderr),\n exited: process.exited,\n };\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -89,6 +89,7 @@ export interface SandboxTransportIface {
89
89
  signal?: AbortSignal;
90
90
  }): Promise<TransportOutputIface>;
91
91
  input(args: TransportInputArgsIface): Promise<void>;
92
+ processGone?(cause: unknown): boolean;
92
93
  close(): void;
93
94
  }
94
95
  export declare const exitStatus: (exitCode: number | null, exitSignal: string | null) => number;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/sandboxes/transport.ts"],
4
- "sourcesContent": ["import { SandboxEntryType } from '@finalai/agent-provider/sandbox';\n\n// What the session handle needs from whatever reaches the sandbox: the Computers API\n// (REST), the computer's `final-node-v2` data plane, or a Device job over the gateway.\n// Every method maps to one request on the underlying wire.\n\nexport interface TransportStatIface {\n type: SandboxEntryType;\n size: number;\n modifiedAtMs: number | null;\n mode: number | null;\n}\n\nexport interface TransportDirEntryIface {\n name: string;\n type: SandboxEntryType;\n size: number | null;\n}\n\nexport interface TransportChunkIface {\n bytes: Uint8Array;\n eof: boolean;\n}\n\nexport interface TransportExecArgsIface {\n // Client-chosen process id (UUID); the data plane deduplicates on it.\n id: string;\n // Command text for the sandbox's selected shell.\n command: string;\n cwd: string;\n env: Record<string, string>;\n timeoutMs: number | null;\n signal?: AbortSignal;\n}\n\nexport interface TransportProcessIface {\n id: string;\n bootId: string;\n}\n\nexport interface TransportOutputStreamIface {\n data: Uint8Array;\n nextOffset: number;\n eof: boolean;\n}\n\nexport interface TransportOutputIface {\n stdout: TransportOutputStreamIface;\n stderr: TransportOutputStreamIface;\n exited: boolean;\n exitCode: number | null;\n exitSignal: string | null;\n}\n\nexport interface TransportInputArgsIface {\n process: TransportProcessIface;\n data?: Uint8Array;\n eof?: boolean;\n signal?: string;\n terminate?: boolean;\n abortSignal?: AbortSignal;\n}\n\nexport interface SandboxTransportIface {\n readonly kind: string;\n // Largest file chunk one request moves.\n readonly fileChunkBytes: number;\n // Longest output long-poll the wire accepts.\n readonly outputWaitMs: number;\n stat(args: { path: string; signal?: AbortSignal }): Promise<TransportStatIface | null>;\n readDir(args: { path: string; signal?: AbortSignal }): Promise<TransportDirEntryIface[]>;\n // At most `length` bytes from `offset`; `eof` once the chunk reaches the end of\n // the file (an empty chunk also ends a read).\n readChunk(args: {\n path: string;\n offset: number;\n length: number;\n signal?: AbortSignal;\n }): Promise<TransportChunkIface>;\n // Chunks arrive in order from offset 0; offset 0 replaces the file.\n writeChunk(args: {\n path: string;\n offset: number;\n bytes: Uint8Array;\n signal?: AbortSignal;\n }): Promise<void>;\n createDirectory(args: { path: string; signal?: AbortSignal }): Promise<void>;\n remove(args: {\n path: string;\n recursive: boolean;\n force: boolean;\n signal?: AbortSignal;\n }): Promise<void>;\n exec(args: TransportExecArgsIface): Promise<TransportProcessIface>;\n output(args: {\n process: TransportProcessIface;\n stdoutOffset: number;\n stderrOffset: number;\n waitMs: number;\n signal?: AbortSignal;\n }): Promise<TransportOutputIface>;\n input(args: TransportInputArgsIface): Promise<void>;\n close(): void;\n}\n\nconst SIGNAL_NUMBERS = new Map([\n ['SIGHUP', 1],\n ['SIGINT', 2],\n ['SIGKILL', 9],\n ['SIGTERM', 15],\n]);\n\n// A shell-style exit status: the exit code, or 128 + the signal that ended the process.\nexport const exitStatus = (exitCode: number | null, exitSignal: string | null): number => {\n if (exitCode != null) return exitCode;\n if (exitSignal == null) return 1;\n const numeric = Number.parseInt(exitSignal, 10);\n const number = Number.isFinite(numeric) ? numeric : SIGNAL_NUMBERS.get(exitSignal);\n return number == null ? 1 : 128 + number;\n};\n\nexport const entryTypeOf = (flags: {\n isDirectory: boolean;\n isFile: boolean;\n isSymlink: boolean;\n}): SandboxEntryType => {\n if (flags.isSymlink) return SandboxEntryType.Symlink;\n if (flags.isDirectory) return SandboxEntryType.Directory;\n if (flags.isFile) return SandboxEntryType.File;\n return SandboxEntryType.Other;\n};\n"],
5
- "mappings": "AAAA,SAAS,wBAAwB;AAyGjC,MAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B,CAAC,UAAU,CAAC;AAAA,EACZ,CAAC,UAAU,CAAC;AAAA,EACZ,CAAC,WAAW,CAAC;AAAA,EACb,CAAC,WAAW,EAAE;AAChB,CAAC;AAGM,MAAM,aAAa,CAAC,UAAyB,eAAsC;AACxF,MAAI,YAAY,KAAM,QAAO;AAC7B,MAAI,cAAc,KAAM,QAAO;AAC/B,QAAM,UAAU,OAAO,SAAS,YAAY,EAAE;AAC9C,QAAM,SAAS,OAAO,SAAS,OAAO,IAAI,UAAU,eAAe,IAAI,UAAU;AACjF,SAAO,UAAU,OAAO,IAAI,MAAM;AACpC;AAEO,MAAM,cAAc,CAAC,UAIJ;AACtB,MAAI,MAAM,UAAW,QAAO,iBAAiB;AAC7C,MAAI,MAAM,YAAa,QAAO,iBAAiB;AAC/C,MAAI,MAAM,OAAQ,QAAO,iBAAiB;AAC1C,SAAO,iBAAiB;AAC1B;",
4
+ "sourcesContent": ["import { SandboxEntryType } from '@finalai/agent-provider/sandbox';\n\n// What the session handle needs from whatever reaches the sandbox: the Computers API\n// (REST), the computer's `final-node-v2` data plane, or a Device job over the gateway.\n// Every method maps to one request on the underlying wire.\n\nexport interface TransportStatIface {\n type: SandboxEntryType;\n size: number;\n modifiedAtMs: number | null;\n mode: number | null;\n}\n\nexport interface TransportDirEntryIface {\n name: string;\n type: SandboxEntryType;\n size: number | null;\n}\n\nexport interface TransportChunkIface {\n bytes: Uint8Array;\n eof: boolean;\n}\n\nexport interface TransportExecArgsIface {\n // Client-chosen process id (UUID); the data plane deduplicates on it.\n id: string;\n // Command text for the sandbox's selected shell.\n command: string;\n cwd: string;\n env: Record<string, string>;\n timeoutMs: number | null;\n signal?: AbortSignal;\n}\n\nexport interface TransportProcessIface {\n id: string;\n bootId: string;\n}\n\n// Output read from an offset: `data` starts at `nextOffset - data.byteLength`,\n// later than the offset asked for when the backend no longer retains those bytes.\nexport interface TransportOutputStreamIface {\n data: Uint8Array;\n nextOffset: number;\n eof: boolean;\n}\n\nexport interface TransportOutputIface {\n stdout: TransportOutputStreamIface;\n stderr: TransportOutputStreamIface;\n exited: boolean;\n exitCode: number | null;\n exitSignal: string | null;\n}\n\nexport interface TransportInputArgsIface {\n process: TransportProcessIface;\n data?: Uint8Array;\n eof?: boolean;\n signal?: string;\n terminate?: boolean;\n abortSignal?: AbortSignal;\n}\n\nexport interface SandboxTransportIface {\n readonly kind: string;\n // Largest file chunk one request moves.\n readonly fileChunkBytes: number;\n // Longest output long-poll the wire accepts.\n readonly outputWaitMs: number;\n stat(args: { path: string; signal?: AbortSignal }): Promise<TransportStatIface | null>;\n readDir(args: { path: string; signal?: AbortSignal }): Promise<TransportDirEntryIface[]>;\n // At most `length` bytes from `offset`; `eof` once the chunk reaches the end of\n // the file (an empty chunk also ends a read).\n readChunk(args: {\n path: string;\n offset: number;\n length: number;\n signal?: AbortSignal;\n }): Promise<TransportChunkIface>;\n // Chunks arrive in order from offset 0; offset 0 replaces the file.\n writeChunk(args: {\n path: string;\n offset: number;\n bytes: Uint8Array;\n signal?: AbortSignal;\n }): Promise<void>;\n createDirectory(args: { path: string; signal?: AbortSignal }): Promise<void>;\n remove(args: {\n path: string;\n recursive: boolean;\n force: boolean;\n signal?: AbortSignal;\n }): Promise<void>;\n exec(args: TransportExecArgsIface): Promise<TransportProcessIface>;\n output(args: {\n process: TransportProcessIface;\n stdoutOffset: number;\n stderrOffset: number;\n waitMs: number;\n signal?: AbortSignal;\n }): Promise<TransportOutputIface>;\n input(args: TransportInputArgsIface): Promise<void>;\n // Present when processes run in the sandbox, outliving this client, and are\n // found again by id and boot (durable processes): whether a failed `output`\n // means the backend no longer knows the process.\n processGone?(cause: unknown): boolean;\n close(): void;\n}\n\nconst SIGNAL_NUMBERS = new Map([\n ['SIGHUP', 1],\n ['SIGINT', 2],\n ['SIGKILL', 9],\n ['SIGTERM', 15],\n]);\n\n// A shell-style exit status: the exit code, or 128 + the signal that ended the process.\nexport const exitStatus = (exitCode: number | null, exitSignal: string | null): number => {\n if (exitCode != null) return exitCode;\n if (exitSignal == null) return 1;\n const numeric = Number.parseInt(exitSignal, 10);\n const number = Number.isFinite(numeric) ? numeric : SIGNAL_NUMBERS.get(exitSignal);\n return number == null ? 1 : 128 + number;\n};\n\nexport const entryTypeOf = (flags: {\n isDirectory: boolean;\n isFile: boolean;\n isSymlink: boolean;\n}): SandboxEntryType => {\n if (flags.isSymlink) return SandboxEntryType.Symlink;\n if (flags.isDirectory) return SandboxEntryType.Directory;\n if (flags.isFile) return SandboxEntryType.File;\n return SandboxEntryType.Other;\n};\n"],
5
+ "mappings": "AAAA,SAAS,wBAAwB;AA+GjC,MAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B,CAAC,UAAU,CAAC;AAAA,EACZ,CAAC,UAAU,CAAC;AAAA,EACZ,CAAC,WAAW,CAAC;AAAA,EACb,CAAC,WAAW,EAAE;AAChB,CAAC;AAGM,MAAM,aAAa,CAAC,UAAyB,eAAsC;AACxF,MAAI,YAAY,KAAM,QAAO;AAC7B,MAAI,cAAc,KAAM,QAAO;AAC/B,QAAM,UAAU,OAAO,SAAS,YAAY,EAAE;AAC9C,QAAM,SAAS,OAAO,SAAS,OAAO,IAAI,UAAU,eAAe,IAAI,UAAU;AACjF,SAAO,UAAU,OAAO,IAAI,MAAM;AACpC;AAEO,MAAM,cAAc,CAAC,UAIJ;AACtB,MAAI,MAAM,UAAW,QAAO,iBAAiB;AAC7C,MAAI,MAAM,YAAa,QAAO,iBAAiB;AAC/C,MAAI,MAAM,OAAQ,QAAO,iBAAiB;AAC1C,SAAO,iBAAiB;AAC1B;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finalai/final-agent-provider",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "private": false,
5
5
  "description": "Final provider helpers for harnesses, computer and device sandboxes, the Final room surface, and external provider channels.",
6
6
  "license": "Apache-2.0",
@@ -201,41 +201,6 @@
201
201
  "types": "./dist/modules/sandboxes/egress-bootstrap.d.mts",
202
202
  "import": "./dist/modules/sandboxes/egress-bootstrap.mjs",
203
203
  "default": "./dist/modules/sandboxes/egress-bootstrap.mjs"
204
- },
205
- "./channels/slack": {
206
- "types": "./dist/modules/channels/slack.d.mts",
207
- "import": "./dist/modules/channels/slack.mjs",
208
- "default": "./dist/modules/channels/slack.mjs"
209
- },
210
- "./channels/discord": {
211
- "types": "./dist/modules/channels/discord.d.mts",
212
- "import": "./dist/modules/channels/discord.mjs",
213
- "default": "./dist/modules/channels/discord.mjs"
214
- },
215
- "./channels/github": {
216
- "types": "./dist/modules/channels/github.d.mts",
217
- "import": "./dist/modules/channels/github.mjs",
218
- "default": "./dist/modules/channels/github.mjs"
219
- },
220
- "./channels/linear": {
221
- "types": "./dist/modules/channels/linear.d.mts",
222
- "import": "./dist/modules/channels/linear.mjs",
223
- "default": "./dist/modules/channels/linear.mjs"
224
- },
225
- "./channels/webhook": {
226
- "types": "./dist/modules/channels/webhook.d.mts",
227
- "import": "./dist/modules/channels/webhook.mjs",
228
- "default": "./dist/modules/channels/webhook.mjs"
229
- },
230
- "./channels/voice": {
231
- "types": "./dist/modules/channels/voice.d.mts",
232
- "import": "./dist/modules/channels/voice.mjs",
233
- "default": "./dist/modules/channels/voice.mjs"
234
- },
235
- "./channels/phone": {
236
- "types": "./dist/modules/channels/phone.d.mts",
237
- "import": "./dist/modules/channels/phone.mjs",
238
- "default": "./dist/modules/channels/phone.mjs"
239
204
  }
240
205
  },
241
206
  "publishConfig": {
@@ -245,14 +210,13 @@
245
210
  "dependencies": {
246
211
  "@agentclientprotocol/sdk": "1.8.0",
247
212
  "@finalai/acp-session": "0.1.11",
248
- "@finalai/agent-protocol": "0.7.0",
249
- "@finalai/agent-provider": "0.6.0",
213
+ "@finalai/agent-protocol": "0.8.0",
214
+ "@finalai/agent-provider": "0.8.0",
250
215
  "@finalai/computers": "0.3.1",
251
216
  "@finalai/websockets": "0.1.7",
252
217
  "ws": "^8.21.3",
253
218
  "zod": "4.6.6",
254
- "@finalai/agent-definition": "0.6.0",
255
- "gemoji": "^8.1.0"
219
+ "@finalai/agent-definition": "0.7.0"
256
220
  },
257
221
  "engines": {
258
222
  "node": ">=26"