@effect-agent/sandbox-local 0.1.0-beta.45 → 0.1.0-beta.47

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.
@@ -0,0 +1,22 @@
1
+ import { Sandbox, SandboxImplementation } from "@effect-agent/sandbox/Sandbox";
2
+ import { Layer } from "effect";
3
+ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
4
+ declare namespace LocalSandbox_d_exports {
5
+ export { layer, sandboxLayer, unisolatedImplementation };
6
+ }
7
+ /**
8
+ * The only implementation identity produced by this package. It deliberately states that local
9
+ * process execution is unisolated development tooling, not a security sandbox.
10
+ */
11
+ declare const unisolatedImplementation: SandboxImplementation;
12
+ /**
13
+ * Development-only local process adapter with its `ChildProcessSpawner` requirement kept visible
14
+ * so composition roots and tests can inject a spawner double. It is unisolated and must never be
15
+ * used as a security boundary for untrusted code or commands.
16
+ */
17
+ declare const sandboxLayer: Layer.Layer<Sandbox, never, ChildProcessSpawner>;
18
+ /** A scoped Node process implementation whose events are always labeled `unisolated`. */
19
+ declare const layer: Layer.Layer<Sandbox>;
20
+ //#endregion
21
+ export { layer, sandboxLayer, LocalSandbox_d_exports as t, unisolatedImplementation };
22
+ //# sourceMappingURL=LocalSandbox.d.mts.map
@@ -0,0 +1,154 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { SANDBOX_DIAGNOSTIC_MAX_LENGTH, Sandbox, SandboxExitError, SandboxExited, SandboxImplementation, SandboxOutput, SandboxOutputLimitError, SandboxResourceUse, SandboxSpawnError, SandboxStarted, SandboxTimeoutError, SandboxUnsupportedRequestError } from "@effect-agent/sandbox/Sandbox";
3
+ import { NodeServices } from "@effect/platform-node";
4
+ import { Clock, Config, Duration, Effect, Layer, Option, Ref, Stream } from "effect";
5
+ import { ChildProcess } from "effect/unstable/process";
6
+ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
7
+ //#region src/LocalSandbox.ts
8
+ var LocalSandbox_exports = /* @__PURE__ */ __exportAll({
9
+ layer: () => layer,
10
+ sandboxLayer: () => sandboxLayer,
11
+ unisolatedImplementation: () => unisolatedImplementation
12
+ });
13
+ /**
14
+ * The only implementation identity produced by this package. It deliberately states that local
15
+ * process execution is unisolated development tooling, not a security sandbox.
16
+ */
17
+ const unisolatedImplementation = SandboxImplementation.make({
18
+ isolation: "unisolated",
19
+ identity: "local-process"
20
+ });
21
+ const zeroOutput = {
22
+ stdout: 0,
23
+ stderr: 0
24
+ };
25
+ const boundedDiagnostic = (message) => message.slice(0, SANDBOX_DIAGNOSTIC_MAX_LENGTH);
26
+ const unsupported = (feature, message) => SandboxUnsupportedRequestError.make({
27
+ implementation: unisolatedImplementation,
28
+ feature,
29
+ message: boundedDiagnostic(message)
30
+ });
31
+ const validateRequest = Effect.fn("LocalSandbox.validateRequest")(function* (request) {
32
+ if (request.runtime.kind !== "unisolated-process" || request.runtime.identity !== unisolatedImplementation.identity) return yield* unsupported("runtime", "The local process runner only accepts runtime kind 'unisolated-process' with identity 'local-process'.");
33
+ if (request.mounts.length > 0) return yield* unsupported("mounts", "The unisolated local process runner cannot enforce mount access modes.");
34
+ if (request.network._tag !== "NetworkDisabled") return yield* unsupported("network", "The unisolated local process runner cannot enforce workload network policy.");
35
+ if (request.limits.cpuCores !== void 0) return yield* unsupported("cpu-limit", "The unisolated local process runner cannot enforce CPU limits.");
36
+ if (request.limits.memoryBytes !== void 0) return yield* unsupported("memory-limit", "The unisolated local process runner cannot enforce memory limits.");
37
+ if (request.secretHandles.length > 0) return yield* unsupported("secret-handles", "The unisolated local process runner does not resolve secret handles into an environment.");
38
+ if (request.artifactRules.length > 0) return yield* unsupported("artifacts", "The unisolated local process runner does not collect artifacts.");
39
+ });
40
+ const spawnError = (request, message, cause) => SandboxSpawnError.make({
41
+ implementation: unisolatedImplementation,
42
+ command: request.command,
43
+ message: boundedDiagnostic(message),
44
+ ...cause === void 0 ? {} : { cause }
45
+ });
46
+ const exitError = (message, cause, exitCode = -1) => SandboxExitError.make({
47
+ implementation: unisolatedImplementation,
48
+ exitCode,
49
+ message: boundedDiagnostic(message),
50
+ ...cause === void 0 ? {} : { cause }
51
+ });
52
+ const environmentFromAllowlist = Effect.fn("LocalSandbox.environmentFromAllowlist")(function* (request) {
53
+ const environment = {};
54
+ for (const name of request.environment.allow) {
55
+ const value = yield* Config.option(Config.string(name)).pipe(Effect.mapError((error) => spawnError(request, `Could not read allowed environment variable '${name}': ${error.message}`, error)));
56
+ if (Option.isSome(value)) environment[name] = value.value;
57
+ }
58
+ return environment;
59
+ });
60
+ const outputEvent = (counts, stream, bytes, decoder, limit) => Ref.modify(counts, (current) => {
61
+ const streamBytes = current[stream] + bytes.byteLength;
62
+ const next = {
63
+ ...current,
64
+ [stream]: streamBytes
65
+ };
66
+ return [next.stdout + next.stderr, next];
67
+ }).pipe(Effect.flatMap((observed) => observed > limit ? Effect.fail(SandboxOutputLimitError.make({
68
+ implementation: unisolatedImplementation,
69
+ stream,
70
+ limit,
71
+ observed
72
+ })) : Effect.succeed(SandboxOutput.make({
73
+ eventVersion: 1,
74
+ implementation: unisolatedImplementation,
75
+ stream,
76
+ text: decoder.decode(bytes, { stream: true }),
77
+ bytes: bytes.byteLength
78
+ }))));
79
+ /**
80
+ * Emits any text the streaming decoder still holds once its source stream ends. The final decode
81
+ * must run at end-of-stream, not at pipeline construction, so the flush is suspended; a trailing
82
+ * incomplete UTF-8 sequence surfaces as replacement text. Its raw bytes were already counted when
83
+ * the chunk arrived, so the flush event carries zero bytes.
84
+ */
85
+ const flushDecoder = (stream, decoder) => Stream.suspend(() => {
86
+ const text = decoder.decode();
87
+ return text.length === 0 ? Stream.empty : Stream.succeed(SandboxOutput.make({
88
+ eventVersion: 1,
89
+ implementation: unisolatedImplementation,
90
+ stream,
91
+ text,
92
+ bytes: 0
93
+ }));
94
+ });
95
+ const makeExecute = (spawner) => (request) => Stream.unwrap(Effect.gen(function* () {
96
+ yield* validateRequest(request);
97
+ const startedAt = yield* Clock.currentTimeMillis;
98
+ const counts = yield* Ref.make(zeroOutput);
99
+ const stdoutDecoder = new TextDecoder();
100
+ const stderrDecoder = new TextDecoder();
101
+ const environment = yield* environmentFromAllowlist(request);
102
+ const command = ChildProcess.make(request.command, request.args, {
103
+ cwd: request.cwd,
104
+ env: environment,
105
+ extendEnv: false,
106
+ stdout: "pipe",
107
+ stderr: "pipe"
108
+ });
109
+ const child = yield* spawner.spawn(command).pipe(Effect.mapError((error) => spawnError(request, error.message, error)));
110
+ const streamOutput = (stream, decoder) => {
111
+ return (stream === "stdout" ? child.stdout : child.stderr).pipe(Stream.mapError((error) => exitError(error.message, error)), Stream.mapEffect((bytes) => outputEvent(counts, stream, bytes, decoder, request.limits.maxOutputBytes)), Stream.concat(flushDecoder(stream, decoder)));
112
+ };
113
+ const terminal = Stream.unwrap(Effect.gen(function* () {
114
+ const exitCode = yield* child.exitCode.pipe(Effect.mapError((error) => exitError(error.message, error)));
115
+ const endedAt = yield* Clock.currentTimeMillis;
116
+ const output = yield* Ref.get(counts);
117
+ const resourceUse = SandboxResourceUse.make({
118
+ wallTime: Duration.millis(endedAt - startedAt),
119
+ stdoutBytes: output.stdout,
120
+ stderrBytes: output.stderr
121
+ });
122
+ const exited = SandboxExited.make({
123
+ eventVersion: 1,
124
+ implementation: unisolatedImplementation,
125
+ exitCode,
126
+ resourceUse,
127
+ artifacts: []
128
+ });
129
+ return exitCode === 0 ? Stream.succeed(exited) : Stream.concat(Stream.succeed(exited), Stream.fail(exitError(`Unisolated local process exited with code ${exitCode}.`, void 0, exitCode)));
130
+ }));
131
+ return Stream.concat(Stream.succeed(SandboxStarted.make({
132
+ eventVersion: 1,
133
+ implementation: unisolatedImplementation,
134
+ runtime: request.runtime
135
+ })), Stream.concat(Stream.merge(streamOutput("stdout", stdoutDecoder), streamOutput("stderr", stderrDecoder)), terminal)).pipe(Stream.interruptWhen(Effect.sleep(request.limits.maxWallTime).pipe(Effect.andThen(Effect.fail(SandboxTimeoutError.make({
136
+ implementation: unisolatedImplementation,
137
+ maxWallTime: request.limits.maxWallTime
138
+ }))))));
139
+ })).pipe(Stream.withSpan("LocalSandbox.execute"));
140
+ /**
141
+ * Development-only local process adapter with its `ChildProcessSpawner` requirement kept visible
142
+ * so composition roots and tests can inject a spawner double. It is unisolated and must never be
143
+ * used as a security boundary for untrusted code or commands.
144
+ */
145
+ const sandboxLayer = Layer.effect(Sandbox)(Effect.gen(function* () {
146
+ const spawner = yield* ChildProcessSpawner;
147
+ return Sandbox.of({ execute: makeExecute(spawner) });
148
+ }));
149
+ /** A scoped Node process implementation whose events are always labeled `unisolated`. */
150
+ const layer = sandboxLayer.pipe(Layer.provide(NodeServices.layer));
151
+ //#endregion
152
+ export { layer, sandboxLayer, LocalSandbox_exports as t, unisolatedImplementation };
153
+
154
+ //# sourceMappingURL=LocalSandbox.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalSandbox.mjs","names":[],"sources":["../src/LocalSandbox.ts"],"sourcesContent":["import {\n SANDBOX_DIAGNOSTIC_MAX_LENGTH,\n Sandbox,\n SandboxExited,\n SandboxExitError,\n SandboxImplementation,\n SandboxOutput,\n SandboxOutputLimitError,\n SandboxResourceUse,\n SandboxSpawnError,\n SandboxStarted,\n SandboxTimeoutError,\n SandboxUnsupportedRequestError,\n type SandboxEvent,\n type SandboxExecute,\n type SandboxRequest,\n} from \"@effect-agent/sandbox/Sandbox\";\nimport { NodeServices } from \"@effect/platform-node\";\nimport { Clock, Config, Duration, Effect, Layer, Option, Ref, Stream } from \"effect\";\nimport { ChildProcess } from \"effect/unstable/process\";\nimport { ChildProcessSpawner } from \"effect/unstable/process/ChildProcessSpawner\";\n\n/**\n * The only implementation identity produced by this package. It deliberately states that local\n * process execution is unisolated development tooling, not a security sandbox.\n */\nexport const unisolatedImplementation = SandboxImplementation.make({\n isolation: \"unisolated\",\n identity: \"local-process\",\n});\n\ntype OutputStream = \"stdout\" | \"stderr\";\ntype OutputCounts = Readonly<Record<OutputStream, number>>;\n\nconst zeroOutput: OutputCounts = { stdout: 0, stderr: 0 };\n\nconst boundedDiagnostic = (message: string): string =>\n message.slice(0, SANDBOX_DIAGNOSTIC_MAX_LENGTH);\n\nconst unsupported = (\n feature: Parameters<typeof SandboxUnsupportedRequestError.make>[0][\"feature\"],\n message: string,\n) =>\n SandboxUnsupportedRequestError.make({\n implementation: unisolatedImplementation,\n feature,\n message: boundedDiagnostic(message),\n });\n\nconst validateRequest = Effect.fn(\"LocalSandbox.validateRequest\")(function* (\n request: SandboxRequest,\n) {\n if (\n request.runtime.kind !== \"unisolated-process\" ||\n request.runtime.identity !== unisolatedImplementation.identity\n ) {\n return yield* unsupported(\n \"runtime\",\n \"The local process runner only accepts runtime kind 'unisolated-process' with identity 'local-process'.\",\n );\n }\n if (request.mounts.length > 0) {\n return yield* unsupported(\n \"mounts\",\n \"The unisolated local process runner cannot enforce mount access modes.\",\n );\n }\n if (request.network._tag !== \"NetworkDisabled\") {\n return yield* unsupported(\n \"network\",\n \"The unisolated local process runner cannot enforce workload network policy.\",\n );\n }\n if (request.limits.cpuCores !== undefined) {\n return yield* unsupported(\n \"cpu-limit\",\n \"The unisolated local process runner cannot enforce CPU limits.\",\n );\n }\n if (request.limits.memoryBytes !== undefined) {\n return yield* unsupported(\n \"memory-limit\",\n \"The unisolated local process runner cannot enforce memory limits.\",\n );\n }\n if (request.secretHandles.length > 0) {\n return yield* unsupported(\n \"secret-handles\",\n \"The unisolated local process runner does not resolve secret handles into an environment.\",\n );\n }\n if (request.artifactRules.length > 0) {\n return yield* unsupported(\n \"artifacts\",\n \"The unisolated local process runner does not collect artifacts.\",\n );\n }\n});\n\nconst spawnError = (request: SandboxRequest, message: string, cause?: unknown) =>\n SandboxSpawnError.make({\n implementation: unisolatedImplementation,\n command: request.command,\n message: boundedDiagnostic(message),\n ...(cause === undefined ? {} : { cause }),\n });\n\nconst exitError = (message: string, cause?: unknown, exitCode = -1) =>\n SandboxExitError.make({\n implementation: unisolatedImplementation,\n exitCode,\n message: boundedDiagnostic(message),\n ...(cause === undefined ? {} : { cause }),\n });\n\nconst environmentFromAllowlist = Effect.fn(\"LocalSandbox.environmentFromAllowlist\")(function* (\n request: SandboxRequest,\n) {\n const environment: Record<string, string> = {};\n\n for (const name of request.environment.allow) {\n const value = yield* Config.option(Config.string(name)).pipe(\n Effect.mapError((error) =>\n spawnError(\n request,\n `Could not read allowed environment variable '${name}': ${error.message}`,\n error,\n ),\n ),\n );\n\n if (Option.isSome(value)) {\n environment[name] = value.value;\n }\n }\n\n return environment;\n});\n\nconst outputEvent = (\n counts: Ref.Ref<OutputCounts>,\n stream: OutputStream,\n bytes: Uint8Array,\n decoder: TextDecoder,\n limit: number,\n): Effect.Effect<SandboxOutput, SandboxOutputLimitError> =>\n Ref.modify(counts, (current) => {\n const streamBytes = current[stream] + bytes.byteLength;\n const next = { ...current, [stream]: streamBytes };\n\n return [next.stdout + next.stderr, next] as const;\n }).pipe(\n Effect.flatMap((observed) =>\n observed > limit\n ? Effect.fail(\n SandboxOutputLimitError.make({\n implementation: unisolatedImplementation,\n stream,\n limit,\n observed,\n }),\n )\n : Effect.succeed(\n SandboxOutput.make({\n eventVersion: 1,\n implementation: unisolatedImplementation,\n stream,\n text: decoder.decode(bytes, { stream: true }),\n bytes: bytes.byteLength,\n }),\n ),\n ),\n );\n\n/**\n * Emits any text the streaming decoder still holds once its source stream ends. The final decode\n * must run at end-of-stream, not at pipeline construction, so the flush is suspended; a trailing\n * incomplete UTF-8 sequence surfaces as replacement text. Its raw bytes were already counted when\n * the chunk arrived, so the flush event carries zero bytes.\n */\nconst flushDecoder = (stream: OutputStream, decoder: TextDecoder): Stream.Stream<SandboxOutput> =>\n Stream.suspend(() => {\n const text = decoder.decode();\n\n return text.length === 0\n ? Stream.empty\n : Stream.succeed(\n SandboxOutput.make({\n eventVersion: 1,\n implementation: unisolatedImplementation,\n stream,\n text,\n bytes: 0,\n }),\n );\n });\n\nconst makeExecute =\n (spawner: ChildProcessSpawner[\"Service\"]): SandboxExecute =>\n (request) =>\n Stream.unwrap(\n Effect.gen(function* () {\n yield* validateRequest(request);\n const startedAt = yield* Clock.currentTimeMillis;\n const counts = yield* Ref.make(zeroOutput);\n const stdoutDecoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n const environment = yield* environmentFromAllowlist(request);\n\n const command = ChildProcess.make(request.command, request.args, {\n cwd: request.cwd,\n env: environment,\n extendEnv: false,\n stdout: \"pipe\",\n stderr: \"pipe\",\n });\n\n const child = yield* spawner\n .spawn(command)\n .pipe(Effect.mapError((error) => spawnError(request, error.message, error)));\n\n const streamOutput = (stream: OutputStream, decoder: TextDecoder) => {\n const source = stream === \"stdout\" ? child.stdout : child.stderr;\n\n return source.pipe(\n Stream.mapError((error) => exitError(error.message, error)),\n Stream.mapEffect((bytes) =>\n outputEvent(counts, stream, bytes, decoder, request.limits.maxOutputBytes),\n ),\n Stream.concat(flushDecoder(stream, decoder)),\n );\n };\n\n const terminal = Stream.unwrap(\n Effect.gen(function* () {\n const exitCode = yield* child.exitCode.pipe(\n Effect.mapError((error) => exitError(error.message, error)),\n );\n\n const endedAt = yield* Clock.currentTimeMillis;\n const output = yield* Ref.get(counts);\n\n const resourceUse = SandboxResourceUse.make({\n wallTime: Duration.millis(endedAt - startedAt),\n stdoutBytes: output.stdout,\n stderrBytes: output.stderr,\n });\n\n const exited = SandboxExited.make({\n eventVersion: 1,\n implementation: unisolatedImplementation,\n exitCode,\n resourceUse,\n artifacts: [],\n });\n\n return exitCode === 0\n ? Stream.succeed<SandboxEvent>(exited)\n : Stream.concat(\n Stream.succeed<SandboxEvent>(exited),\n Stream.fail(\n exitError(\n `Unisolated local process exited with code ${exitCode}.`,\n undefined,\n exitCode,\n ),\n ),\n );\n }),\n );\n\n const execution = Stream.concat(\n Stream.succeed<SandboxEvent>(\n SandboxStarted.make({\n eventVersion: 1,\n implementation: unisolatedImplementation,\n runtime: request.runtime,\n }),\n ),\n Stream.concat(\n Stream.merge(\n streamOutput(\"stdout\", stdoutDecoder),\n streamOutput(\"stderr\", stderrDecoder),\n ),\n terminal,\n ),\n );\n\n return execution.pipe(\n Stream.interruptWhen(\n Effect.sleep(request.limits.maxWallTime).pipe(\n Effect.andThen(\n Effect.fail(\n SandboxTimeoutError.make({\n implementation: unisolatedImplementation,\n maxWallTime: request.limits.maxWallTime,\n }),\n ),\n ),\n ),\n ),\n );\n }),\n ).pipe(Stream.withSpan(\"LocalSandbox.execute\"));\n\n/**\n * Development-only local process adapter with its `ChildProcessSpawner` requirement kept visible\n * so composition roots and tests can inject a spawner double. It is unisolated and must never be\n * used as a security boundary for untrusted code or commands.\n */\nexport const sandboxLayer: Layer.Layer<Sandbox, never, ChildProcessSpawner> = Layer.effect(Sandbox)(\n Effect.gen(function* () {\n const spawner = yield* ChildProcessSpawner;\n\n return Sandbox.of({ execute: makeExecute(spawner) });\n }),\n);\n\n/** A scoped Node process implementation whose events are always labeled `unisolated`. */\nexport const layer: Layer.Layer<Sandbox> = sandboxLayer.pipe(Layer.provide(NodeServices.layer));\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,20 +1,2 @@
1
- import { Sandbox, SandboxImplementation } from "@effect-agent/sandbox";
2
- import { Layer } from "effect";
3
- import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
4
- //#region src/local-sandbox.d.ts
5
- /**
6
- * The only implementation identity produced by this package. It deliberately states that local
7
- * process execution is unisolated development tooling, not a security sandbox.
8
- */
9
- declare const unisolatedImplementation: SandboxImplementation;
10
- /**
11
- * Development-only local process adapter with its `ChildProcessSpawner` requirement kept visible
12
- * so composition roots and tests can inject a spawner double. It is unisolated and must never be
13
- * used as a security boundary for untrusted code or commands.
14
- */
15
- declare const sandboxLayer: Layer.Layer<Sandbox, never, ChildProcessSpawner>;
16
- /** A scoped Node process implementation whose events are always labeled `unisolated`. */
17
- declare const layer: Layer.Layer<Sandbox>;
18
- //#endregion
19
- export { layer, sandboxLayer, unisolatedImplementation };
20
- //# sourceMappingURL=index.d.mts.map
1
+ import { t as LocalSandbox_d_exports } from "./LocalSandbox.mjs";
2
+ export { LocalSandbox_d_exports as LocalSandbox };
package/dist/index.mjs CHANGED
@@ -1,148 +1,2 @@
1
- import { SANDBOX_DIAGNOSTIC_MAX_LENGTH, Sandbox, SandboxExitError, SandboxExited, SandboxImplementation, SandboxOutput, SandboxOutputLimitError, SandboxResourceUse, SandboxSpawnError, SandboxStarted, SandboxTimeoutError, SandboxUnsupportedRequestError } from "@effect-agent/sandbox";
2
- import { NodeServices } from "@effect/platform-node";
3
- import { Clock, Config, Duration, Effect, Layer, Option, Ref, Stream } from "effect";
4
- import { ChildProcess } from "effect/unstable/process";
5
- import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
6
- //#region src/local-sandbox.ts
7
- /**
8
- * The only implementation identity produced by this package. It deliberately states that local
9
- * process execution is unisolated development tooling, not a security sandbox.
10
- */
11
- const unisolatedImplementation = SandboxImplementation.make({
12
- isolation: "unisolated",
13
- identity: "local-process"
14
- });
15
- const zeroOutput = {
16
- stdout: 0,
17
- stderr: 0
18
- };
19
- const boundedDiagnostic = (message) => message.slice(0, SANDBOX_DIAGNOSTIC_MAX_LENGTH);
20
- const unsupported = (feature, message) => SandboxUnsupportedRequestError.make({
21
- implementation: unisolatedImplementation,
22
- feature,
23
- message: boundedDiagnostic(message)
24
- });
25
- const validateRequest = Effect.fn("LocalSandbox.validateRequest")(function* (request) {
26
- if (request.runtime.kind !== "unisolated-process" || request.runtime.identity !== unisolatedImplementation.identity) return yield* unsupported("runtime", "The local process runner only accepts runtime kind 'unisolated-process' with identity 'local-process'.");
27
- if (request.mounts.length > 0) return yield* unsupported("mounts", "The unisolated local process runner cannot enforce mount access modes.");
28
- if (request.network._tag !== "NetworkDisabled") return yield* unsupported("network", "The unisolated local process runner cannot enforce workload network policy.");
29
- if (request.limits.cpuCores !== void 0) return yield* unsupported("cpu-limit", "The unisolated local process runner cannot enforce CPU limits.");
30
- if (request.limits.memoryBytes !== void 0) return yield* unsupported("memory-limit", "The unisolated local process runner cannot enforce memory limits.");
31
- if (request.secretHandles.length > 0) return yield* unsupported("secret-handles", "The unisolated local process runner does not resolve secret handles into an environment.");
32
- if (request.artifactRules.length > 0) return yield* unsupported("artifacts", "The unisolated local process runner does not collect artifacts.");
33
- });
34
- const spawnError = (request, message, cause) => SandboxSpawnError.make({
35
- implementation: unisolatedImplementation,
36
- command: request.command,
37
- message: boundedDiagnostic(message),
38
- ...cause === void 0 ? {} : { cause }
39
- });
40
- const exitError = (message, cause, exitCode = -1) => SandboxExitError.make({
41
- implementation: unisolatedImplementation,
42
- exitCode,
43
- message: boundedDiagnostic(message),
44
- ...cause === void 0 ? {} : { cause }
45
- });
46
- const environmentFromAllowlist = Effect.fn("LocalSandbox.environmentFromAllowlist")(function* (request) {
47
- const environment = {};
48
- for (const name of request.environment.allow) {
49
- const value = yield* Config.option(Config.string(name)).pipe(Effect.mapError((error) => spawnError(request, `Could not read allowed environment variable '${name}': ${error.message}`, error)));
50
- if (Option.isSome(value)) environment[name] = value.value;
51
- }
52
- return environment;
53
- });
54
- const outputEvent = (counts, stream, bytes, decoder, limit) => Ref.modify(counts, (current) => {
55
- const streamBytes = current[stream] + bytes.byteLength;
56
- const next = {
57
- ...current,
58
- [stream]: streamBytes
59
- };
60
- return [next.stdout + next.stderr, next];
61
- }).pipe(Effect.flatMap((observed) => observed > limit ? Effect.fail(SandboxOutputLimitError.make({
62
- implementation: unisolatedImplementation,
63
- stream,
64
- limit,
65
- observed
66
- })) : Effect.succeed(SandboxOutput.make({
67
- eventVersion: 1,
68
- implementation: unisolatedImplementation,
69
- stream,
70
- text: decoder.decode(bytes, { stream: true }),
71
- bytes: bytes.byteLength
72
- }))));
73
- /**
74
- * Emits any text the streaming decoder still holds once its source stream ends. The final decode
75
- * must run at end-of-stream, not at pipeline construction, so the flush is suspended; a trailing
76
- * incomplete UTF-8 sequence surfaces as replacement text. Its raw bytes were already counted when
77
- * the chunk arrived, so the flush event carries zero bytes.
78
- */
79
- const flushDecoder = (stream, decoder) => Stream.suspend(() => {
80
- const text = decoder.decode();
81
- return text.length === 0 ? Stream.empty : Stream.succeed(SandboxOutput.make({
82
- eventVersion: 1,
83
- implementation: unisolatedImplementation,
84
- stream,
85
- text,
86
- bytes: 0
87
- }));
88
- });
89
- const makeExecute = (spawner) => (request) => Stream.unwrap(Effect.gen(function* () {
90
- yield* validateRequest(request);
91
- const startedAt = yield* Clock.currentTimeMillis;
92
- const counts = yield* Ref.make(zeroOutput);
93
- const stdoutDecoder = new TextDecoder();
94
- const stderrDecoder = new TextDecoder();
95
- const environment = yield* environmentFromAllowlist(request);
96
- const command = ChildProcess.make(request.command, request.args, {
97
- cwd: request.cwd,
98
- env: environment,
99
- extendEnv: false,
100
- stdout: "pipe",
101
- stderr: "pipe"
102
- });
103
- const child = yield* spawner.spawn(command).pipe(Effect.mapError((error) => spawnError(request, error.message, error)));
104
- const streamOutput = (stream, decoder) => {
105
- return (stream === "stdout" ? child.stdout : child.stderr).pipe(Stream.mapError((error) => exitError(error.message, error)), Stream.mapEffect((bytes) => outputEvent(counts, stream, bytes, decoder, request.limits.maxOutputBytes)), Stream.concat(flushDecoder(stream, decoder)));
106
- };
107
- const terminal = Stream.unwrap(Effect.gen(function* () {
108
- const exitCode = yield* child.exitCode.pipe(Effect.mapError((error) => exitError(error.message, error)));
109
- const endedAt = yield* Clock.currentTimeMillis;
110
- const output = yield* Ref.get(counts);
111
- const resourceUse = SandboxResourceUse.make({
112
- wallTime: Duration.millis(endedAt - startedAt),
113
- stdoutBytes: output.stdout,
114
- stderrBytes: output.stderr
115
- });
116
- const exited = SandboxExited.make({
117
- eventVersion: 1,
118
- implementation: unisolatedImplementation,
119
- exitCode,
120
- resourceUse,
121
- artifacts: []
122
- });
123
- return exitCode === 0 ? Stream.succeed(exited) : Stream.concat(Stream.succeed(exited), Stream.fail(exitError(`Unisolated local process exited with code ${exitCode}.`, void 0, exitCode)));
124
- }));
125
- return Stream.concat(Stream.succeed(SandboxStarted.make({
126
- eventVersion: 1,
127
- implementation: unisolatedImplementation,
128
- runtime: request.runtime
129
- })), Stream.concat(Stream.merge(streamOutput("stdout", stdoutDecoder), streamOutput("stderr", stderrDecoder)), terminal)).pipe(Stream.interruptWhen(Effect.sleep(request.limits.maxWallTime).pipe(Effect.andThen(Effect.fail(SandboxTimeoutError.make({
130
- implementation: unisolatedImplementation,
131
- maxWallTime: request.limits.maxWallTime
132
- }))))));
133
- })).pipe(Stream.withSpan("LocalSandbox.execute"));
134
- /**
135
- * Development-only local process adapter with its `ChildProcessSpawner` requirement kept visible
136
- * so composition roots and tests can inject a spawner double. It is unisolated and must never be
137
- * used as a security boundary for untrusted code or commands.
138
- */
139
- const sandboxLayer = Layer.effect(Sandbox)(Effect.gen(function* () {
140
- const spawner = yield* ChildProcessSpawner;
141
- return Sandbox.of({ execute: makeExecute(spawner) });
142
- }));
143
- /** A scoped Node process implementation whose events are always labeled `unisolated`. */
144
- const layer = sandboxLayer.pipe(Layer.provide(NodeServices.layer));
145
- //#endregion
146
- export { layer, sandboxLayer, unisolatedImplementation };
147
-
148
- //# sourceMappingURL=index.mjs.map
1
+ import { t as LocalSandbox_exports } from "./LocalSandbox.mjs";
2
+ export { LocalSandbox_exports as LocalSandbox };
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll as t };
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@effect-agent/sandbox-local","version":"0.1.0-beta.45","dependencies":{"@effect-agent/sandbox":"0.1.0-beta.45","@effect/platform-node":"4.0.0-rc.112"},"devDependencies":{"@effect/vitest":"4.0.0-rc.112","@types/node":"26.1.2","effect":"4.0.0-rc.112","typescript":"7.0.2","vite-plus":"0.3.0"},"peerDependencies":{"effect":"^4.0.0-rc.112"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"}},"description":"Node-local child-process Sandbox adapter for Effect Agent; honestly labeled unisolated development tooling, not a security boundary.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/sandbox-local"},"files":["dist","src"],"type":"module","publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}
1
+ {"name":"@effect-agent/sandbox-local","version":"0.1.0-beta.47","dependencies":{"@effect-agent/sandbox":"0.1.0-beta.47","@effect/platform-node":"4.0.0-rc.112"},"devDependencies":{"@effect/vitest":"4.0.0-rc.112","@types/node":"26.1.2","effect":"4.0.0-rc.112","typescript":"7.0.2","vite-plus":"0.3.0"},"peerDependencies":{"effect":"^4.0.0-rc.112"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./LocalSandbox":{"types":"./dist/LocalSandbox.d.mts","default":"./dist/LocalSandbox.mjs"}},"description":"Node-local child-process Sandbox adapter for Effect Agent; honestly labeled unisolated development tooling, not a security boundary.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/sandbox-local"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}
@@ -14,7 +14,7 @@ import {
14
14
  type SandboxEvent,
15
15
  type SandboxExecute,
16
16
  type SandboxRequest,
17
- } from "@effect-agent/sandbox";
17
+ } from "@effect-agent/sandbox/Sandbox";
18
18
  import { NodeServices } from "@effect/platform-node";
19
19
  import { Clock, Config, Duration, Effect, Layer, Option, Ref, Stream } from "effect";
20
20
  import { ChildProcess } from "effect/unstable/process";
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export * from "./local-sandbox.ts";
1
+ export * as LocalSandbox from "./LocalSandbox.ts";
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/local-sandbox.ts"],"sourcesContent":["import {\n SANDBOX_DIAGNOSTIC_MAX_LENGTH,\n Sandbox,\n SandboxExited,\n SandboxExitError,\n SandboxImplementation,\n SandboxOutput,\n SandboxOutputLimitError,\n SandboxResourceUse,\n SandboxSpawnError,\n SandboxStarted,\n SandboxTimeoutError,\n SandboxUnsupportedRequestError,\n type SandboxEvent,\n type SandboxExecute,\n type SandboxRequest,\n} from \"@effect-agent/sandbox\";\nimport { NodeServices } from \"@effect/platform-node\";\nimport { Clock, Config, Duration, Effect, Layer, Option, Ref, Stream } from \"effect\";\nimport { ChildProcess } from \"effect/unstable/process\";\nimport { ChildProcessSpawner } from \"effect/unstable/process/ChildProcessSpawner\";\n\n/**\n * The only implementation identity produced by this package. It deliberately states that local\n * process execution is unisolated development tooling, not a security sandbox.\n */\nexport const unisolatedImplementation = SandboxImplementation.make({\n isolation: \"unisolated\",\n identity: \"local-process\",\n});\n\ntype OutputStream = \"stdout\" | \"stderr\";\ntype OutputCounts = Readonly<Record<OutputStream, number>>;\n\nconst zeroOutput: OutputCounts = { stdout: 0, stderr: 0 };\n\nconst boundedDiagnostic = (message: string): string =>\n message.slice(0, SANDBOX_DIAGNOSTIC_MAX_LENGTH);\n\nconst unsupported = (\n feature: Parameters<typeof SandboxUnsupportedRequestError.make>[0][\"feature\"],\n message: string,\n) =>\n SandboxUnsupportedRequestError.make({\n implementation: unisolatedImplementation,\n feature,\n message: boundedDiagnostic(message),\n });\n\nconst validateRequest = Effect.fn(\"LocalSandbox.validateRequest\")(function* (\n request: SandboxRequest,\n) {\n if (\n request.runtime.kind !== \"unisolated-process\" ||\n request.runtime.identity !== unisolatedImplementation.identity\n ) {\n return yield* unsupported(\n \"runtime\",\n \"The local process runner only accepts runtime kind 'unisolated-process' with identity 'local-process'.\",\n );\n }\n if (request.mounts.length > 0) {\n return yield* unsupported(\n \"mounts\",\n \"The unisolated local process runner cannot enforce mount access modes.\",\n );\n }\n if (request.network._tag !== \"NetworkDisabled\") {\n return yield* unsupported(\n \"network\",\n \"The unisolated local process runner cannot enforce workload network policy.\",\n );\n }\n if (request.limits.cpuCores !== undefined) {\n return yield* unsupported(\n \"cpu-limit\",\n \"The unisolated local process runner cannot enforce CPU limits.\",\n );\n }\n if (request.limits.memoryBytes !== undefined) {\n return yield* unsupported(\n \"memory-limit\",\n \"The unisolated local process runner cannot enforce memory limits.\",\n );\n }\n if (request.secretHandles.length > 0) {\n return yield* unsupported(\n \"secret-handles\",\n \"The unisolated local process runner does not resolve secret handles into an environment.\",\n );\n }\n if (request.artifactRules.length > 0) {\n return yield* unsupported(\n \"artifacts\",\n \"The unisolated local process runner does not collect artifacts.\",\n );\n }\n});\n\nconst spawnError = (request: SandboxRequest, message: string, cause?: unknown) =>\n SandboxSpawnError.make({\n implementation: unisolatedImplementation,\n command: request.command,\n message: boundedDiagnostic(message),\n ...(cause === undefined ? {} : { cause }),\n });\n\nconst exitError = (message: string, cause?: unknown, exitCode = -1) =>\n SandboxExitError.make({\n implementation: unisolatedImplementation,\n exitCode,\n message: boundedDiagnostic(message),\n ...(cause === undefined ? {} : { cause }),\n });\n\nconst environmentFromAllowlist = Effect.fn(\"LocalSandbox.environmentFromAllowlist\")(function* (\n request: SandboxRequest,\n) {\n const environment: Record<string, string> = {};\n\n for (const name of request.environment.allow) {\n const value = yield* Config.option(Config.string(name)).pipe(\n Effect.mapError((error) =>\n spawnError(\n request,\n `Could not read allowed environment variable '${name}': ${error.message}`,\n error,\n ),\n ),\n );\n\n if (Option.isSome(value)) {\n environment[name] = value.value;\n }\n }\n\n return environment;\n});\n\nconst outputEvent = (\n counts: Ref.Ref<OutputCounts>,\n stream: OutputStream,\n bytes: Uint8Array,\n decoder: TextDecoder,\n limit: number,\n): Effect.Effect<SandboxOutput, SandboxOutputLimitError> =>\n Ref.modify(counts, (current) => {\n const streamBytes = current[stream] + bytes.byteLength;\n const next = { ...current, [stream]: streamBytes };\n\n return [next.stdout + next.stderr, next] as const;\n }).pipe(\n Effect.flatMap((observed) =>\n observed > limit\n ? Effect.fail(\n SandboxOutputLimitError.make({\n implementation: unisolatedImplementation,\n stream,\n limit,\n observed,\n }),\n )\n : Effect.succeed(\n SandboxOutput.make({\n eventVersion: 1,\n implementation: unisolatedImplementation,\n stream,\n text: decoder.decode(bytes, { stream: true }),\n bytes: bytes.byteLength,\n }),\n ),\n ),\n );\n\n/**\n * Emits any text the streaming decoder still holds once its source stream ends. The final decode\n * must run at end-of-stream, not at pipeline construction, so the flush is suspended; a trailing\n * incomplete UTF-8 sequence surfaces as replacement text. Its raw bytes were already counted when\n * the chunk arrived, so the flush event carries zero bytes.\n */\nconst flushDecoder = (stream: OutputStream, decoder: TextDecoder): Stream.Stream<SandboxOutput> =>\n Stream.suspend(() => {\n const text = decoder.decode();\n\n return text.length === 0\n ? Stream.empty\n : Stream.succeed(\n SandboxOutput.make({\n eventVersion: 1,\n implementation: unisolatedImplementation,\n stream,\n text,\n bytes: 0,\n }),\n );\n });\n\nconst makeExecute =\n (spawner: ChildProcessSpawner[\"Service\"]): SandboxExecute =>\n (request) =>\n Stream.unwrap(\n Effect.gen(function* () {\n yield* validateRequest(request);\n const startedAt = yield* Clock.currentTimeMillis;\n const counts = yield* Ref.make(zeroOutput);\n const stdoutDecoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n const environment = yield* environmentFromAllowlist(request);\n\n const command = ChildProcess.make(request.command, request.args, {\n cwd: request.cwd,\n env: environment,\n extendEnv: false,\n stdout: \"pipe\",\n stderr: \"pipe\",\n });\n\n const child = yield* spawner\n .spawn(command)\n .pipe(Effect.mapError((error) => spawnError(request, error.message, error)));\n\n const streamOutput = (stream: OutputStream, decoder: TextDecoder) => {\n const source = stream === \"stdout\" ? child.stdout : child.stderr;\n\n return source.pipe(\n Stream.mapError((error) => exitError(error.message, error)),\n Stream.mapEffect((bytes) =>\n outputEvent(counts, stream, bytes, decoder, request.limits.maxOutputBytes),\n ),\n Stream.concat(flushDecoder(stream, decoder)),\n );\n };\n\n const terminal = Stream.unwrap(\n Effect.gen(function* () {\n const exitCode = yield* child.exitCode.pipe(\n Effect.mapError((error) => exitError(error.message, error)),\n );\n\n const endedAt = yield* Clock.currentTimeMillis;\n const output = yield* Ref.get(counts);\n\n const resourceUse = SandboxResourceUse.make({\n wallTime: Duration.millis(endedAt - startedAt),\n stdoutBytes: output.stdout,\n stderrBytes: output.stderr,\n });\n\n const exited = SandboxExited.make({\n eventVersion: 1,\n implementation: unisolatedImplementation,\n exitCode,\n resourceUse,\n artifacts: [],\n });\n\n return exitCode === 0\n ? Stream.succeed<SandboxEvent>(exited)\n : Stream.concat(\n Stream.succeed<SandboxEvent>(exited),\n Stream.fail(\n exitError(\n `Unisolated local process exited with code ${exitCode}.`,\n undefined,\n exitCode,\n ),\n ),\n );\n }),\n );\n\n const execution = Stream.concat(\n Stream.succeed<SandboxEvent>(\n SandboxStarted.make({\n eventVersion: 1,\n implementation: unisolatedImplementation,\n runtime: request.runtime,\n }),\n ),\n Stream.concat(\n Stream.merge(\n streamOutput(\"stdout\", stdoutDecoder),\n streamOutput(\"stderr\", stderrDecoder),\n ),\n terminal,\n ),\n );\n\n return execution.pipe(\n Stream.interruptWhen(\n Effect.sleep(request.limits.maxWallTime).pipe(\n Effect.andThen(\n Effect.fail(\n SandboxTimeoutError.make({\n implementation: unisolatedImplementation,\n maxWallTime: request.limits.maxWallTime,\n }),\n ),\n ),\n ),\n ),\n );\n }),\n ).pipe(Stream.withSpan(\"LocalSandbox.execute\"));\n\n/**\n * Development-only local process adapter with its `ChildProcessSpawner` requirement kept visible\n * so composition roots and tests can inject a spawner double. It is unisolated and must never be\n * used as a security boundary for untrusted code or commands.\n */\nexport const sandboxLayer: Layer.Layer<Sandbox, never, ChildProcessSpawner> = Layer.effect(Sandbox)(\n Effect.gen(function* () {\n const spawner = yield* ChildProcessSpawner;\n\n return Sandbox.of({ execute: makeExecute(spawner) });\n }),\n);\n\n/** A scoped Node process implementation whose events are always labeled `unisolated`. */\nexport const layer: Layer.Layer<Sandbox> = sandboxLayer.pipe(Layer.provide(NodeServices.layer));\n"],"mappings":"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"}