@yolk-sdk/sandbox 0.1.0-canary.78 → 0.1.0-canary.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -70,6 +70,14 @@ const lifecycle = DisposableSandboxLifecycle.make({
70
70
  })
71
71
  ```
72
72
 
73
+ ## Workspace paths
74
+
75
+ `normalizeWorkspaceCwd(rawCwd)` returns an Effect with a `NormalizedWorkspaceCwd` branded string
76
+ (exported from `@yolk-sdk/sandbox`). Raw `Sandbox.run({ cwd })` inputs stay strings. The normalizer
77
+ trims raw outer whitespace, resolves dot segments, and rejects absolute/NUL/escaping paths.
78
+ Whitespace belonging to remaining directory names is preserved. The brand records lexical path
79
+ validation, not filesystem or symlink confinement.
80
+
73
81
  ## Initial source model
74
82
 
75
83
  `SandboxInitialSource` is a tagged union:
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { SandboxConfigError, SandboxError, SandboxExpiredError, SandboxInputError, SandboxInputErrorCause, SandboxProviderError, SandboxStateError, SandboxStateStoreError } from "./errors.mjs";
2
2
  import { DisposableSandboxLifecycle, EmptySandboxInitialSource, GitSandboxBasicAuth, GitSandboxInitialSource, PersistentSandboxLifecycle, SandboxCommandInput, SandboxCommandResult, SandboxInitialSource, SandboxLifecycle, SandboxPreviewUrl, SandboxResources, SandboxSnapshotRetention, SandboxState, SnapshotSandboxInitialSource, TarballSandboxInitialSource, VercelSandboxState } from "./model.mjs";
3
3
  import { Sandbox, SandboxApi } from "./service.mjs";
4
- import { backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand } from "./lifecycle.mjs";
4
+ import { NormalizedWorkspaceCwd, backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand } from "./lifecycle.mjs";
5
5
  import { makeVercelSandboxName } from "./name.mjs";
6
6
  import { SandboxStateStore, SandboxStateStoreApi } from "./state-store.mjs";
7
- export { DisposableSandboxLifecycle, EmptySandboxInitialSource, GitSandboxBasicAuth, GitSandboxInitialSource, PersistentSandboxLifecycle, Sandbox, type SandboxApi, SandboxCommandInput, SandboxCommandResult, SandboxConfigError, type SandboxError, SandboxExpiredError, SandboxInitialSource, SandboxInputError, SandboxInputErrorCause, SandboxLifecycle, SandboxPreviewUrl, SandboxProviderError, SandboxResources, SandboxSnapshotRetention, SandboxState, SandboxStateError, SandboxStateStore, type SandboxStateStoreApi, SandboxStateStoreError, SnapshotSandboxInitialSource, TarballSandboxInitialSource, VercelSandboxState, backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, makeVercelSandboxName, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand };
7
+ export { DisposableSandboxLifecycle, EmptySandboxInitialSource, GitSandboxBasicAuth, GitSandboxInitialSource, NormalizedWorkspaceCwd, PersistentSandboxLifecycle, Sandbox, type SandboxApi, SandboxCommandInput, SandboxCommandResult, SandboxConfigError, type SandboxError, SandboxExpiredError, SandboxInitialSource, SandboxInputError, SandboxInputErrorCause, SandboxLifecycle, SandboxPreviewUrl, SandboxProviderError, SandboxResources, SandboxSnapshotRetention, SandboxState, SandboxStateError, SandboxStateStore, type SandboxStateStoreApi, SandboxStateStoreError, SnapshotSandboxInitialSource, TarballSandboxInitialSource, VercelSandboxState, backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, makeVercelSandboxName, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { SandboxConfigError, SandboxExpiredError, SandboxInputError, SandboxInputErrorCause, SandboxProviderError, SandboxStateError, SandboxStateStoreError } from "./errors.mjs";
2
2
  import { DisposableSandboxLifecycle, EmptySandboxInitialSource, GitSandboxBasicAuth, GitSandboxInitialSource, PersistentSandboxLifecycle, SandboxCommandInput, SandboxCommandResult, SandboxInitialSource, SandboxLifecycle, SandboxPreviewUrl, SandboxResources, SandboxSnapshotRetention, SandboxState, SnapshotSandboxInitialSource, TarballSandboxInitialSource, VercelSandboxState } from "./model.mjs";
3
- import { backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand } from "./lifecycle.mjs";
3
+ import { NormalizedWorkspaceCwd, backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand } from "./lifecycle.mjs";
4
4
  import { Sandbox } from "./service.mjs";
5
5
  import { makeVercelSandboxName } from "./name.mjs";
6
6
  import { SandboxStateStore } from "./state-store.mjs";
7
- export { DisposableSandboxLifecycle, EmptySandboxInitialSource, GitSandboxBasicAuth, GitSandboxInitialSource, PersistentSandboxLifecycle, Sandbox, SandboxCommandInput, SandboxCommandResult, SandboxConfigError, SandboxExpiredError, SandboxInitialSource, SandboxInputError, SandboxInputErrorCause, SandboxLifecycle, SandboxPreviewUrl, SandboxProviderError, SandboxResources, SandboxSnapshotRetention, SandboxState, SandboxStateError, SandboxStateStore, SandboxStateStoreError, SnapshotSandboxInitialSource, TarballSandboxInitialSource, VercelSandboxState, backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, makeVercelSandboxName, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand };
7
+ export { DisposableSandboxLifecycle, EmptySandboxInitialSource, GitSandboxBasicAuth, GitSandboxInitialSource, NormalizedWorkspaceCwd, PersistentSandboxLifecycle, Sandbox, SandboxCommandInput, SandboxCommandResult, SandboxConfigError, SandboxExpiredError, SandboxInitialSource, SandboxInputError, SandboxInputErrorCause, SandboxLifecycle, SandboxPreviewUrl, SandboxProviderError, SandboxResources, SandboxSnapshotRetention, SandboxState, SandboxStateError, SandboxStateStore, SandboxStateStoreError, SnapshotSandboxInitialSource, TarballSandboxInitialSource, VercelSandboxState, backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, makeVercelSandboxName, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand };
@@ -12,6 +12,8 @@ declare const backgroundSandboxProbeMs = 2000;
12
12
  declare const sandboxToolOutputLimit = 50000;
13
13
  declare const defaultSandboxPorts: ReadonlyArray<number>;
14
14
  declare const defaultSandboxWorkspaceRoot = "/vercel/sandbox";
15
+ declare const NormalizedWorkspaceCwd: Schema.brand<Schema.String, "NormalizedWorkspaceCwd">;
16
+ type NormalizedWorkspaceCwd = typeof NormalizedWorkspaceCwd.Type;
15
17
  declare const defaultSandboxLifecycle: DisposableSandboxLifecycle;
16
18
  declare const defaultSandboxInitialSource: EmptySandboxInitialSource;
17
19
  type SandboxRecreateReason = 'idle_expired' | 'max_expired' | 'name_mismatch';
@@ -27,8 +29,8 @@ declare const sandboxLifecycleOrDefault: (lifecycle?: SandboxLifecycle) => Sandb
27
29
  declare const sandboxSourceOrDefault: (source?: SandboxInitialSource) => SandboxInitialSource;
28
30
  declare const sandboxCommandTimeoutMs: (timeoutMs?: number) => number;
29
31
  declare const validateSandboxCommand: (command: string) => Effect.Effect<never, SandboxInputError, never> | Effect.Effect<string, never, never>;
30
- declare const normalizeWorkspaceCwd: (cwd?: string | null) => Effect.Effect<string, SandboxInputError>;
31
- declare const absoluteSandboxCwd: (workspaceRoot: string, normalizedCwd: string) => string;
32
+ declare const normalizeWorkspaceCwd: (cwd?: string | null) => Effect.Effect<NormalizedWorkspaceCwd, SandboxInputError>;
33
+ declare const absoluteSandboxCwd: (workspaceRoot: string, normalizedCwd: NormalizedWorkspaceCwd) => string;
32
34
  declare const initialSandboxState: (input: {
33
35
  readonly name: string;
34
36
  readonly nowMs: number;
@@ -50,5 +52,5 @@ declare const sandboxStateDecision: (input: {
50
52
  readonly lifecycle: SandboxLifecycle;
51
53
  }) => SandboxStateDecision;
52
54
  //#endregion
53
- export { SandboxRecreateReason, SandboxStateDecision, absoluteSandboxCwd, backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, initialSandboxState, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand };
55
+ export { NormalizedWorkspaceCwd, SandboxRecreateReason, SandboxStateDecision, absoluteSandboxCwd, backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, initialSandboxState, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand };
54
56
  //# sourceMappingURL=lifecycle.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.mts","names":[],"sources":["../src/lifecycle.ts"],"mappings":";;;;;;cAYa,uBAAA;AAAA,cAEA,2BAAA;AAAA,cAEA,8BAAA;AAAA,cAEA,0BAAA;AAAA,cAEA,wBAAA;AAAA,cAEA,sBAAA;AAAA,cAEA,mBAAA,EAAqB,aAAa;AAAA,cAElC,2BAAA;AAAA,cAEA,uBAAA,EAAuB,0BAGlC;AAAA,cAEW,2BAAA,EAA2B,yBAAqC;AAAA,KAEjE,qBAAA;AAAA,cAMN,qBAAA,EAAqB,MAAA,CAAA,YAAA;EAAA;;;KAKf,oBAAA;EAAA,SAEG,IAAA;EAAA,SACA,KAAA,EAAO,YAAA;AAAA,WAEX,qBAAA,CAAsB,IAAI;AAAA,cAUxB,yBAAA,GAA6B,SAAA,GAAY,gBAAA,KAAmB,gBACnC;AAAA,cAEzB,sBAAA,GAA0B,MAAA,GAAS,oBAAA,KAAuB,oBAChC;AAAA,cAE1B,uBAAA,GAA2B,SAAkB;AAAA,cAG7C,sBAAA,GAA0B,OAAA,aAAe,MAAA,CAAA,MAAA,QAAA,iBAAA,WAAA,MAAA,CAAA,MAAA;AAAA,cAazC,qBAAA,GACX,GAAA,qBACC,MAAA,CAAO,MAAM,SAAS,iBAAA;AAAA,cA4CZ,kBAAA,GAAsB,aAAA,UAAuB,aAAqB;AAAA,cAGlE,mBAAA,GAAuB,KAAA;EAAA,SACzB,IAAA;EAAA,SACA,KAAA;EAAA,SACA,SAAA,EAAW,gBAAA;AAAA,MACrB,kBAsBE;AAAA,cAEU,iBAAA,GAAqB,KAAA;EAAA,SACvB,KAAA,EAAO,YAAA;EAAA,SACP,KAAA;EAAA,SACA,SAAA,EAAW,gBAAA;AAAA,MACrB,kBAAA;AAAA,cAoBY,2BAAA,GAA+B,KAAA;EAAA,SACjC,MAAA,EAAQ,YAAA;EAAA,SACR,KAAA,EAAO,YAAY;AAAA;AAAA,cAGjB,oBAAA,GAAwB,KAAA;EAAA,SAC1B,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,YAAA;EAAA,SACrB,IAAA;EAAA,SACA,KAAA;EAAA,SACA,SAAA,EAAW,gBAAA;AAAA,MAClB,oBAAA"}
1
+ {"version":3,"file":"lifecycle.d.mts","names":[],"sources":["../src/lifecycle.ts"],"mappings":";;;;;;cAYa,uBAAA;AAAA,cAEA,2BAAA;AAAA,cAEA,8BAAA;AAAA,cAEA,0BAAA;AAAA,cAEA,wBAAA;AAAA,cAEA,sBAAA;AAAA,cAEA,mBAAA,EAAqB,aAAa;AAAA,cAElC,2BAAA;AAAA,cAcA,sBAAA,EAAsB,MAAA,CAAA,KAAA,CAAA,MAAA,CAAA,MAAA;AAAA,KAOvB,sBAAA,UAAgC,sBAAA,CAAuB,IAAI;AAAA,cAE1D,uBAAA,EAAuB,0BAGlC;AAAA,cAEW,2BAAA,EAA2B,yBAAqC;AAAA,KAEjE,qBAAA;AAAA,cAMN,qBAAA,EAAqB,MAAA,CAAA,YAAA;EAAA;;;KAKf,oBAAA;EAAA,SAEG,IAAA;EAAA,SACA,KAAA,EAAO,YAAA;AAAA,WAEX,qBAAA,CAAsB,IAAI;AAAA,cAUxB,yBAAA,GAA6B,SAAA,GAAY,gBAAA,KAAmB,gBACnC;AAAA,cAEzB,sBAAA,GAA0B,MAAA,GAAS,oBAAA,KAAuB,oBAChC;AAAA,cAE1B,uBAAA,GAA2B,SAAkB;AAAA,cAG7C,sBAAA,GAA0B,OAAA,aAAe,MAAA,CAAA,MAAA,QAAA,iBAAA,WAAA,MAAA,CAAA,MAAA;AAAA,cAazC,qBAAA,GACX,GAAA,qBACC,MAAA,CAAO,MAAA,CAAO,sBAAA,EAAwB,iBAAA;AAAA,cAsD5B,kBAAA,GAAsB,aAAA,UAAuB,aAAA,EAAe,sBAAsB;AAAA,cAGlF,mBAAA,GAAuB,KAAA;EAAA,SACzB,IAAA;EAAA,SACA,KAAA;EAAA,SACA,SAAA,EAAW,gBAAA;AAAA,MACrB,kBAsBE;AAAA,cAEU,iBAAA,GAAqB,KAAA;EAAA,SACvB,KAAA,EAAO,YAAA;EAAA,SACP,KAAA;EAAA,SACA,SAAA,EAAW,gBAAA;AAAA,MACrB,kBAAA;AAAA,cAoBY,2BAAA,GAA+B,KAAA;EAAA,SACjC,MAAA,EAAQ,YAAA;EAAA,SACR,KAAA,EAAO,YAAY;AAAA;AAAA,cAGjB,oBAAA,GAAwB,KAAA;EAAA,SAC1B,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,YAAA;EAAA,SACrB,IAAA;EAAA,SACA,KAAA;EAAA,SACA,SAAA,EAAW,gBAAA;AAAA,MAClB,oBAAA"}
@@ -16,6 +16,13 @@ const defaultSandboxPorts = [
16
16
  8e3
17
17
  ];
18
18
  const defaultSandboxWorkspaceRoot = "/vercel/sandbox";
19
+ const isNormalizedWorkspaceCwd = (value) => {
20
+ if (value.length === 0 || value.includes("\0")) return false;
21
+ if (value === ".") return true;
22
+ if (value.startsWith("/") || value.endsWith("/") || value.includes("//")) return false;
23
+ return value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
24
+ };
25
+ const NormalizedWorkspaceCwd = Schema.String.pipe(Schema.check(Schema.makeFilter(isNormalizedWorkspaceCwd, { identifier: "NormalizedWorkspaceCwd" })), Schema.brand("NormalizedWorkspaceCwd"));
19
26
  const defaultSandboxLifecycle = DisposableSandboxLifecycle.make({
20
27
  idleTtlMs: defaultSandboxIdleTtlMs,
21
28
  maxLifetimeMs: defaultSandboxMaxLifetimeMs
@@ -46,7 +53,7 @@ const validateSandboxCommand = (command) => {
46
53
  return Effect.succeed(command);
47
54
  };
48
55
  const normalizeWorkspaceCwd = (cwd) => {
49
- if (cwd === void 0 || cwd === null || cwd.trim().length === 0) return Effect.succeed(".");
56
+ if (cwd === void 0 || cwd === null || cwd.trim().length === 0) return Effect.succeed(NormalizedWorkspaceCwd.make("."));
50
57
  const trimmed = cwd.trim();
51
58
  if (trimmed.startsWith("/") || trimmed.includes("\0")) return Effect.fail(new SandboxInputError({
52
59
  cause: "invalid_cwd",
@@ -64,7 +71,10 @@ const normalizeWorkspaceCwd = (cwd) => {
64
71
  }
65
72
  parts.push(segment);
66
73
  }
67
- return Effect.succeed(parts.length === 0 ? "." : parts.join("/"));
74
+ return Schema.decodeUnknownEffect(NormalizedWorkspaceCwd)(parts.length === 0 ? "." : parts.join("/")).pipe(Effect.mapError(() => new SandboxInputError({
75
+ cause: "invalid_cwd",
76
+ message: "cwd must be workspace-relative"
77
+ })));
68
78
  };
69
79
  const absoluteSandboxCwd = (workspaceRoot, normalizedCwd) => normalizedCwd === "." ? workspaceRoot : `${workspaceRoot}/${normalizedCwd}`;
70
80
  const initialSandboxState = (input) => Match.value(input.lifecycle).pipe(Match.withReturnType(), Match.tag("Disposable", (lifecycle) => VercelSandboxState.make({
@@ -109,6 +119,6 @@ const sandboxStateDecision = (input) => {
109
119
  return existingSandboxStateDecision(state);
110
120
  };
111
121
  //#endregion
112
- export { absoluteSandboxCwd, backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, initialSandboxState, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand };
122
+ export { NormalizedWorkspaceCwd, absoluteSandboxCwd, backgroundSandboxProbeMs, defaultSandboxCommandTimeoutMs, defaultSandboxIdleTtlMs, defaultSandboxInitialSource, defaultSandboxLifecycle, defaultSandboxMaxLifetimeMs, defaultSandboxPorts, defaultSandboxWorkspaceRoot, initialSandboxState, maxSandboxCommandTimeoutMs, normalizeWorkspaceCwd, sandboxCommandTimeoutMs, sandboxLifecycleOrDefault, sandboxSourceOrDefault, sandboxStateDecision, sandboxTimeoutExtendDeltaMs, sandboxToolOutputLimit, touchSandboxState, validateSandboxCommand };
113
123
 
114
124
  //# sourceMappingURL=lifecycle.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.mjs","names":[],"sources":["../src/lifecycle.ts"],"sourcesContent":["import { Effect, Match, Option, Predicate } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { SandboxInputError } from './errors.ts'\nimport {\n DisposableSandboxLifecycle,\n EmptySandboxInitialSource,\n type SandboxInitialSource,\n type SandboxLifecycle,\n type SandboxState,\n VercelSandboxState\n} from './model.ts'\n\nexport const defaultSandboxIdleTtlMs = 30 * 60_000\n\nexport const defaultSandboxMaxLifetimeMs = 45 * 60_000\n\nexport const defaultSandboxCommandTimeoutMs = 120_000\n\nexport const maxSandboxCommandTimeoutMs = 600_000\n\nexport const backgroundSandboxProbeMs = 2_000\n\nexport const sandboxToolOutputLimit = 50_000\n\nexport const defaultSandboxPorts: ReadonlyArray<number> = [3000, 5173, 4321, 8000]\n\nexport const defaultSandboxWorkspaceRoot = '/vercel/sandbox'\n\nexport const defaultSandboxLifecycle = DisposableSandboxLifecycle.make({\n idleTtlMs: defaultSandboxIdleTtlMs,\n maxLifetimeMs: defaultSandboxMaxLifetimeMs\n})\n\nexport const defaultSandboxInitialSource = EmptySandboxInitialSource.make({})\n\nexport type SandboxRecreateReason = 'idle_expired' | 'max_expired' | 'name_mismatch'\n\nconst SandboxUseExistingDecision = Schema.TaggedStruct('UseExisting', {\n state: Schema.Unknown\n})\n\nconst SandboxCreateDecision = Schema.TaggedStruct('Create', {\n workspaceReset: Schema.Boolean,\n reason: Schema.optionalKey(Schema.Literals(['idle_expired', 'max_expired', 'name_mismatch']))\n})\n\nexport type SandboxStateDecision =\n | {\n readonly _tag: 'UseExisting'\n readonly state: SandboxState\n }\n | typeof SandboxCreateDecision.Type\n\nconst existingSandboxStateDecision = (state: SandboxState): SandboxStateDecision => ({\n ...SandboxUseExistingDecision.make({ state }),\n state\n})\n\nconst positiveOr = (value: number | undefined, fallback: number) =>\n value === undefined || !Number.isFinite(value) || value <= 0 ? fallback : Math.floor(value)\n\nexport const sandboxLifecycleOrDefault = (lifecycle?: SandboxLifecycle): SandboxLifecycle =>\n lifecycle ?? defaultSandboxLifecycle\n\nexport const sandboxSourceOrDefault = (source?: SandboxInitialSource): SandboxInitialSource =>\n source ?? defaultSandboxInitialSource\n\nexport const sandboxCommandTimeoutMs = (timeoutMs?: number) =>\n Math.min(maxSandboxCommandTimeoutMs, positiveOr(timeoutMs, defaultSandboxCommandTimeoutMs))\n\nexport const validateSandboxCommand = (command: string) => {\n if (command.trim().length === 0) {\n return Effect.fail(\n new SandboxInputError({\n cause: 'empty_command',\n message: 'Sandbox command must not be empty'\n })\n )\n }\n\n return Effect.succeed(command)\n}\n\nexport const normalizeWorkspaceCwd = (\n cwd?: string | null\n): Effect.Effect<string, SandboxInputError> => {\n if (cwd === undefined || cwd === null || cwd.trim().length === 0) {\n return Effect.succeed('.')\n }\n\n const trimmed = cwd.trim()\n\n if (trimmed.startsWith('/') || trimmed.includes('\\0')) {\n return Effect.fail(\n new SandboxInputError({\n cause: 'invalid_cwd',\n message: 'cwd must be workspace-relative'\n })\n )\n }\n\n const parts: Array<string> = []\n\n for (const segment of trimmed.split('/')) {\n if (segment.length === 0 || segment === '.') {\n continue\n }\n\n if (segment === '..') {\n const previous = parts.pop()\n\n if (previous === undefined) {\n return Effect.fail(\n new SandboxInputError({\n cause: 'invalid_cwd',\n message: 'cwd must not escape the workspace'\n })\n )\n }\n\n continue\n }\n\n parts.push(segment)\n }\n\n return Effect.succeed(parts.length === 0 ? '.' : parts.join('/'))\n}\n\nexport const absoluteSandboxCwd = (workspaceRoot: string, normalizedCwd: string) =>\n normalizedCwd === '.' ? workspaceRoot : `${workspaceRoot}/${normalizedCwd}`\n\nexport const initialSandboxState = (input: {\n readonly name: string\n readonly nowMs: number\n readonly lifecycle: SandboxLifecycle\n}) =>\n Match.value(input.lifecycle).pipe(\n Match.withReturnType<SandboxState>(),\n Match.tag('Disposable', lifecycle =>\n VercelSandboxState.make({\n name: input.name,\n createdAtMs: input.nowMs,\n lastUsedAtMs: input.nowMs,\n expiresAtMs: input.nowMs + lifecycle.idleTtlMs,\n maxExpiresAtMs: input.nowMs + lifecycle.maxLifetimeMs\n })\n ),\n Match.tag('Persistent', lifecycle =>\n VercelSandboxState.make({\n name: input.name,\n createdAtMs: input.nowMs,\n lastUsedAtMs: input.nowMs,\n expiresAtMs: input.nowMs + lifecycle.idleTtlMs,\n maxExpiresAtMs: Number.MAX_SAFE_INTEGER\n })\n ),\n Match.exhaustive\n )\n\nexport const touchSandboxState = (input: {\n readonly state: SandboxState\n readonly nowMs: number\n readonly lifecycle: SandboxLifecycle\n}) =>\n Match.value(input.lifecycle).pipe(\n Match.withReturnType<SandboxState>(),\n Match.tag('Disposable', lifecycle =>\n VercelSandboxState.make({\n ...input.state,\n lastUsedAtMs: input.nowMs,\n expiresAtMs: Math.min(input.nowMs + lifecycle.idleTtlMs, input.state.maxExpiresAtMs)\n })\n ),\n Match.tag('Persistent', lifecycle =>\n VercelSandboxState.make({\n ...input.state,\n lastUsedAtMs: input.nowMs,\n expiresAtMs: input.nowMs + lifecycle.idleTtlMs\n })\n ),\n Match.exhaustive\n )\n\nexport const sandboxTimeoutExtendDeltaMs = (input: {\n readonly before: SandboxState\n readonly after: SandboxState\n}) => Math.max(0, input.after.expiresAtMs - input.before.expiresAtMs)\n\nexport const sandboxStateDecision = (input: {\n readonly state: Option.Option<SandboxState>\n readonly name: string\n readonly nowMs: number\n readonly lifecycle: SandboxLifecycle\n}): SandboxStateDecision => {\n if (Option.isNone(input.state)) {\n return SandboxCreateDecision.make({ workspaceReset: false })\n }\n\n const state = input.state.value\n\n if (state.name !== input.name) {\n return SandboxCreateDecision.make({ workspaceReset: true, reason: 'name_mismatch' })\n }\n\n if (Predicate.isTagged(input.lifecycle, 'Persistent')) {\n return existingSandboxStateDecision(state)\n }\n\n if (input.nowMs >= state.maxExpiresAtMs) {\n return SandboxCreateDecision.make({ workspaceReset: true, reason: 'max_expired' })\n }\n\n if (input.nowMs >= state.expiresAtMs) {\n return SandboxCreateDecision.make({ workspaceReset: true, reason: 'idle_expired' })\n }\n\n return existingSandboxStateDecision(state)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecycle.mjs","names":[],"sources":["../src/lifecycle.ts"],"sourcesContent":["import { Effect, Match, Option, Predicate } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { SandboxInputError } from './errors.ts'\nimport {\n DisposableSandboxLifecycle,\n EmptySandboxInitialSource,\n type SandboxInitialSource,\n type SandboxLifecycle,\n type SandboxState,\n VercelSandboxState\n} from './model.ts'\n\nexport const defaultSandboxIdleTtlMs = 30 * 60_000\n\nexport const defaultSandboxMaxLifetimeMs = 45 * 60_000\n\nexport const defaultSandboxCommandTimeoutMs = 120_000\n\nexport const maxSandboxCommandTimeoutMs = 600_000\n\nexport const backgroundSandboxProbeMs = 2_000\n\nexport const sandboxToolOutputLimit = 50_000\n\nexport const defaultSandboxPorts: ReadonlyArray<number> = [3000, 5173, 4321, 8000]\n\nexport const defaultSandboxWorkspaceRoot = '/vercel/sandbox'\n\nconst isNormalizedWorkspaceCwd = (value: string): boolean => {\n if (value.length === 0 || value.includes('\\0')) return false\n\n if (value === '.') return true\n\n // Segment whitespace is a valid filename: removing './' or a trailing '/.'\n // can expose it even though the raw input was trimmed before normalization.\n if (value.startsWith('/') || value.endsWith('/') || value.includes('//')) return false\n\n return value.split('/').every(segment => segment !== '' && segment !== '.' && segment !== '..')\n}\n\nexport const NormalizedWorkspaceCwd = Schema.String.pipe(\n Schema.check(\n Schema.makeFilter(isNormalizedWorkspaceCwd, { identifier: 'NormalizedWorkspaceCwd' })\n ),\n Schema.brand('NormalizedWorkspaceCwd')\n)\n\nexport type NormalizedWorkspaceCwd = typeof NormalizedWorkspaceCwd.Type\n\nexport const defaultSandboxLifecycle = DisposableSandboxLifecycle.make({\n idleTtlMs: defaultSandboxIdleTtlMs,\n maxLifetimeMs: defaultSandboxMaxLifetimeMs\n})\n\nexport const defaultSandboxInitialSource = EmptySandboxInitialSource.make({})\n\nexport type SandboxRecreateReason = 'idle_expired' | 'max_expired' | 'name_mismatch'\n\nconst SandboxUseExistingDecision = Schema.TaggedStruct('UseExisting', {\n state: Schema.Unknown\n})\n\nconst SandboxCreateDecision = Schema.TaggedStruct('Create', {\n workspaceReset: Schema.Boolean,\n reason: Schema.optionalKey(Schema.Literals(['idle_expired', 'max_expired', 'name_mismatch']))\n})\n\nexport type SandboxStateDecision =\n | {\n readonly _tag: 'UseExisting'\n readonly state: SandboxState\n }\n | typeof SandboxCreateDecision.Type\n\nconst existingSandboxStateDecision = (state: SandboxState): SandboxStateDecision => ({\n ...SandboxUseExistingDecision.make({ state }),\n state\n})\n\nconst positiveOr = (value: number | undefined, fallback: number) =>\n value === undefined || !Number.isFinite(value) || value <= 0 ? fallback : Math.floor(value)\n\nexport const sandboxLifecycleOrDefault = (lifecycle?: SandboxLifecycle): SandboxLifecycle =>\n lifecycle ?? defaultSandboxLifecycle\n\nexport const sandboxSourceOrDefault = (source?: SandboxInitialSource): SandboxInitialSource =>\n source ?? defaultSandboxInitialSource\n\nexport const sandboxCommandTimeoutMs = (timeoutMs?: number) =>\n Math.min(maxSandboxCommandTimeoutMs, positiveOr(timeoutMs, defaultSandboxCommandTimeoutMs))\n\nexport const validateSandboxCommand = (command: string) => {\n if (command.trim().length === 0) {\n return Effect.fail(\n new SandboxInputError({\n cause: 'empty_command',\n message: 'Sandbox command must not be empty'\n })\n )\n }\n\n return Effect.succeed(command)\n}\n\nexport const normalizeWorkspaceCwd = (\n cwd?: string | null\n): Effect.Effect<NormalizedWorkspaceCwd, SandboxInputError> => {\n if (cwd === undefined || cwd === null || cwd.trim().length === 0) {\n return Effect.succeed(NormalizedWorkspaceCwd.make('.'))\n }\n\n const trimmed = cwd.trim()\n\n if (trimmed.startsWith('/') || trimmed.includes('\\0')) {\n return Effect.fail(\n new SandboxInputError({\n cause: 'invalid_cwd',\n message: 'cwd must be workspace-relative'\n })\n )\n }\n\n const parts: Array<string> = []\n\n for (const segment of trimmed.split('/')) {\n if (segment.length === 0 || segment === '.') {\n continue\n }\n\n if (segment === '..') {\n const previous = parts.pop()\n\n if (previous === undefined) {\n return Effect.fail(\n new SandboxInputError({\n cause: 'invalid_cwd',\n message: 'cwd must not escape the workspace'\n })\n )\n }\n\n continue\n }\n\n parts.push(segment)\n }\n\n return Schema.decodeUnknownEffect(NormalizedWorkspaceCwd)(\n parts.length === 0 ? '.' : parts.join('/')\n ).pipe(\n Effect.mapError(\n () =>\n new SandboxInputError({\n cause: 'invalid_cwd',\n message: 'cwd must be workspace-relative'\n })\n )\n )\n}\n\nexport const absoluteSandboxCwd = (workspaceRoot: string, normalizedCwd: NormalizedWorkspaceCwd) =>\n normalizedCwd === '.' ? workspaceRoot : `${workspaceRoot}/${normalizedCwd}`\n\nexport const initialSandboxState = (input: {\n readonly name: string\n readonly nowMs: number\n readonly lifecycle: SandboxLifecycle\n}) =>\n Match.value(input.lifecycle).pipe(\n Match.withReturnType<SandboxState>(),\n Match.tag('Disposable', lifecycle =>\n VercelSandboxState.make({\n name: input.name,\n createdAtMs: input.nowMs,\n lastUsedAtMs: input.nowMs,\n expiresAtMs: input.nowMs + lifecycle.idleTtlMs,\n maxExpiresAtMs: input.nowMs + lifecycle.maxLifetimeMs\n })\n ),\n Match.tag('Persistent', lifecycle =>\n VercelSandboxState.make({\n name: input.name,\n createdAtMs: input.nowMs,\n lastUsedAtMs: input.nowMs,\n expiresAtMs: input.nowMs + lifecycle.idleTtlMs,\n maxExpiresAtMs: Number.MAX_SAFE_INTEGER\n })\n ),\n Match.exhaustive\n )\n\nexport const touchSandboxState = (input: {\n readonly state: SandboxState\n readonly nowMs: number\n readonly lifecycle: SandboxLifecycle\n}) =>\n Match.value(input.lifecycle).pipe(\n Match.withReturnType<SandboxState>(),\n Match.tag('Disposable', lifecycle =>\n VercelSandboxState.make({\n ...input.state,\n lastUsedAtMs: input.nowMs,\n expiresAtMs: Math.min(input.nowMs + lifecycle.idleTtlMs, input.state.maxExpiresAtMs)\n })\n ),\n Match.tag('Persistent', lifecycle =>\n VercelSandboxState.make({\n ...input.state,\n lastUsedAtMs: input.nowMs,\n expiresAtMs: input.nowMs + lifecycle.idleTtlMs\n })\n ),\n Match.exhaustive\n )\n\nexport const sandboxTimeoutExtendDeltaMs = (input: {\n readonly before: SandboxState\n readonly after: SandboxState\n}) => Math.max(0, input.after.expiresAtMs - input.before.expiresAtMs)\n\nexport const sandboxStateDecision = (input: {\n readonly state: Option.Option<SandboxState>\n readonly name: string\n readonly nowMs: number\n readonly lifecycle: SandboxLifecycle\n}): SandboxStateDecision => {\n if (Option.isNone(input.state)) {\n return SandboxCreateDecision.make({ workspaceReset: false })\n }\n\n const state = input.state.value\n\n if (state.name !== input.name) {\n return SandboxCreateDecision.make({ workspaceReset: true, reason: 'name_mismatch' })\n }\n\n if (Predicate.isTagged(input.lifecycle, 'Persistent')) {\n return existingSandboxStateDecision(state)\n }\n\n if (input.nowMs >= state.maxExpiresAtMs) {\n return SandboxCreateDecision.make({ workspaceReset: true, reason: 'max_expired' })\n }\n\n if (input.nowMs >= state.expiresAtMs) {\n return SandboxCreateDecision.make({ workspaceReset: true, reason: 'idle_expired' })\n }\n\n return existingSandboxStateDecision(state)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yolk-sdk/sandbox",
3
- "version": "0.1.0-canary.78",
3
+ "version": "0.1.0-canary.80",
4
4
  "description": "Effect-native sandbox execution contracts, an agent tool, testing fakes, and a Vercel Sandbox adapter.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -61,7 +61,7 @@
61
61
  "dependencies": {
62
62
  "@vercel/sandbox": "2.2.1",
63
63
  "effect": "4.0.0-rc.115",
64
- "@yolk-sdk/agent": "^0.1.0-canary.78"
64
+ "@yolk-sdk/agent": "^0.1.0-canary.80"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsdown",
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ export {
8
8
  defaultSandboxPorts,
9
9
  defaultSandboxWorkspaceRoot,
10
10
  maxSandboxCommandTimeoutMs,
11
+ NormalizedWorkspaceCwd,
11
12
  normalizeWorkspaceCwd,
12
13
  sandboxCommandTimeoutMs,
13
14
  sandboxLifecycleOrDefault,
package/src/lifecycle.ts CHANGED
@@ -26,6 +26,27 @@ export const defaultSandboxPorts: ReadonlyArray<number> = [3000, 5173, 4321, 800
26
26
 
27
27
  export const defaultSandboxWorkspaceRoot = '/vercel/sandbox'
28
28
 
29
+ const isNormalizedWorkspaceCwd = (value: string): boolean => {
30
+ if (value.length === 0 || value.includes('\0')) return false
31
+
32
+ if (value === '.') return true
33
+
34
+ // Segment whitespace is a valid filename: removing './' or a trailing '/.'
35
+ // can expose it even though the raw input was trimmed before normalization.
36
+ if (value.startsWith('/') || value.endsWith('/') || value.includes('//')) return false
37
+
38
+ return value.split('/').every(segment => segment !== '' && segment !== '.' && segment !== '..')
39
+ }
40
+
41
+ export const NormalizedWorkspaceCwd = Schema.String.pipe(
42
+ Schema.check(
43
+ Schema.makeFilter(isNormalizedWorkspaceCwd, { identifier: 'NormalizedWorkspaceCwd' })
44
+ ),
45
+ Schema.brand('NormalizedWorkspaceCwd')
46
+ )
47
+
48
+ export type NormalizedWorkspaceCwd = typeof NormalizedWorkspaceCwd.Type
49
+
29
50
  export const defaultSandboxLifecycle = DisposableSandboxLifecycle.make({
30
51
  idleTtlMs: defaultSandboxIdleTtlMs,
31
52
  maxLifetimeMs: defaultSandboxMaxLifetimeMs
@@ -83,9 +104,9 @@ export const validateSandboxCommand = (command: string) => {
83
104
 
84
105
  export const normalizeWorkspaceCwd = (
85
106
  cwd?: string | null
86
- ): Effect.Effect<string, SandboxInputError> => {
107
+ ): Effect.Effect<NormalizedWorkspaceCwd, SandboxInputError> => {
87
108
  if (cwd === undefined || cwd === null || cwd.trim().length === 0) {
88
- return Effect.succeed('.')
109
+ return Effect.succeed(NormalizedWorkspaceCwd.make('.'))
89
110
  }
90
111
 
91
112
  const trimmed = cwd.trim()
@@ -124,10 +145,20 @@ export const normalizeWorkspaceCwd = (
124
145
  parts.push(segment)
125
146
  }
126
147
 
127
- return Effect.succeed(parts.length === 0 ? '.' : parts.join('/'))
148
+ return Schema.decodeUnknownEffect(NormalizedWorkspaceCwd)(
149
+ parts.length === 0 ? '.' : parts.join('/')
150
+ ).pipe(
151
+ Effect.mapError(
152
+ () =>
153
+ new SandboxInputError({
154
+ cause: 'invalid_cwd',
155
+ message: 'cwd must be workspace-relative'
156
+ })
157
+ )
158
+ )
128
159
  }
129
160
 
130
- export const absoluteSandboxCwd = (workspaceRoot: string, normalizedCwd: string) =>
161
+ export const absoluteSandboxCwd = (workspaceRoot: string, normalizedCwd: NormalizedWorkspaceCwd) =>
131
162
  normalizedCwd === '.' ? workspaceRoot : `${workspaceRoot}/${normalizedCwd}`
132
163
 
133
164
  export const initialSandboxState = (input: {