zeitlich 0.2.21 → 0.2.22

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 (117) hide show
  1. package/README.md +70 -55
  2. package/dist/adapters/sandbox/daytona/index.cjs +3 -0
  3. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  4. package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
  5. package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
  6. package/dist/adapters/sandbox/daytona/index.js +3 -0
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  8. package/dist/adapters/sandbox/daytona/workflow.cjs +32 -0
  9. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -0
  10. package/dist/adapters/sandbox/daytona/workflow.d.cts +27 -0
  11. package/dist/adapters/sandbox/daytona/workflow.d.ts +27 -0
  12. package/dist/adapters/sandbox/daytona/workflow.js +30 -0
  13. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -0
  14. package/dist/adapters/sandbox/inmemory/index.cjs +4 -1
  15. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  16. package/dist/adapters/sandbox/inmemory/index.d.cts +3 -2
  17. package/dist/adapters/sandbox/inmemory/index.d.ts +3 -2
  18. package/dist/adapters/sandbox/inmemory/index.js +4 -1
  19. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  20. package/dist/adapters/sandbox/inmemory/workflow.cjs +32 -0
  21. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -0
  22. package/dist/adapters/sandbox/inmemory/workflow.d.cts +25 -0
  23. package/dist/adapters/sandbox/inmemory/workflow.d.ts +25 -0
  24. package/dist/adapters/sandbox/inmemory/workflow.js +30 -0
  25. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -0
  26. package/dist/adapters/sandbox/virtual/index.cjs +3 -0
  27. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  28. package/dist/adapters/sandbox/virtual/index.d.cts +6 -4
  29. package/dist/adapters/sandbox/virtual/index.d.ts +6 -4
  30. package/dist/adapters/sandbox/virtual/index.js +3 -0
  31. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  32. package/dist/adapters/sandbox/virtual/workflow.cjs +32 -0
  33. package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -0
  34. package/dist/adapters/sandbox/virtual/workflow.d.cts +27 -0
  35. package/dist/adapters/sandbox/virtual/workflow.d.ts +27 -0
  36. package/dist/adapters/sandbox/virtual/workflow.js +30 -0
  37. package/dist/adapters/sandbox/virtual/workflow.js.map +1 -0
  38. package/dist/adapters/thread/google-genai/index.cjs +9 -1
  39. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  40. package/dist/adapters/thread/google-genai/index.d.cts +30 -18
  41. package/dist/adapters/thread/google-genai/index.d.ts +30 -18
  42. package/dist/adapters/thread/google-genai/index.js +9 -1
  43. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  44. package/dist/adapters/thread/google-genai/workflow.cjs +33 -0
  45. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -0
  46. package/dist/adapters/thread/google-genai/workflow.d.cts +32 -0
  47. package/dist/adapters/thread/google-genai/workflow.d.ts +32 -0
  48. package/dist/adapters/thread/google-genai/workflow.js +31 -0
  49. package/dist/adapters/thread/google-genai/workflow.js.map +1 -0
  50. package/dist/adapters/thread/langchain/index.cjs +9 -1
  51. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  52. package/dist/adapters/thread/langchain/index.d.cts +26 -15
  53. package/dist/adapters/thread/langchain/index.d.ts +26 -15
  54. package/dist/adapters/thread/langchain/index.js +9 -1
  55. package/dist/adapters/thread/langchain/index.js.map +1 -1
  56. package/dist/adapters/thread/langchain/workflow.cjs +33 -0
  57. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -0
  58. package/dist/adapters/thread/langchain/workflow.d.cts +32 -0
  59. package/dist/adapters/thread/langchain/workflow.d.ts +32 -0
  60. package/dist/adapters/thread/langchain/workflow.js +31 -0
  61. package/dist/adapters/thread/langchain/workflow.js.map +1 -0
  62. package/dist/index.cjs +36 -34
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +35 -14
  65. package/dist/index.d.ts +35 -14
  66. package/dist/index.js +38 -34
  67. package/dist/index.js.map +1 -1
  68. package/dist/queries-Bw6WEPMw.d.cts +44 -0
  69. package/dist/queries-C27raDaB.d.ts +44 -0
  70. package/dist/{queries-CHa2iv_I.d.cts → types-BJ8itUAl.d.cts} +2 -43
  71. package/dist/{types-BkAYmc96.d.ts → types-C5bkx6kQ.d.ts} +33 -5
  72. package/dist/{types-CES_30qx.d.cts → types-ClsHhtwL.d.cts} +33 -5
  73. package/dist/{queries-6Avfh74U.d.ts → types-ENYCKFBk.d.ts} +2 -43
  74. package/dist/{types-BMRzfELQ.d.cts → types-HBosetv3.d.cts} +15 -1
  75. package/dist/{types-BMRzfELQ.d.ts → types-HBosetv3.d.ts} +15 -1
  76. package/dist/workflow.cjs +4 -30
  77. package/dist/workflow.cjs.map +1 -1
  78. package/dist/workflow.d.cts +13 -41
  79. package/dist/workflow.d.ts +13 -41
  80. package/dist/workflow.js +6 -30
  81. package/dist/workflow.js.map +1 -1
  82. package/package.json +53 -1
  83. package/src/adapters/sandbox/daytona/index.ts +4 -0
  84. package/src/adapters/sandbox/daytona/proxy.ts +55 -0
  85. package/src/adapters/sandbox/e2b/filesystem.ts +147 -0
  86. package/src/adapters/sandbox/e2b/index.ts +159 -0
  87. package/src/adapters/sandbox/e2b/types.ts +23 -0
  88. package/src/adapters/sandbox/inmemory/index.ts +5 -1
  89. package/src/adapters/sandbox/inmemory/proxy.ts +53 -0
  90. package/src/adapters/sandbox/virtual/provider.ts +5 -1
  91. package/src/adapters/sandbox/virtual/proxy.ts +52 -0
  92. package/src/adapters/thread/google-genai/activities.ts +51 -17
  93. package/src/adapters/thread/google-genai/index.ts +1 -0
  94. package/src/adapters/thread/google-genai/proxy.ts +61 -0
  95. package/src/adapters/thread/langchain/activities.ts +47 -14
  96. package/src/adapters/thread/langchain/index.ts +1 -0
  97. package/src/adapters/thread/langchain/proxy.ts +61 -0
  98. package/src/lib/sandbox/manager.ts +40 -6
  99. package/src/lib/sandbox/sandbox.test.ts +12 -11
  100. package/src/lib/sandbox/types.ts +18 -0
  101. package/src/lib/session/index.ts +3 -5
  102. package/src/lib/session/session-edge-cases.integration.test.ts +45 -34
  103. package/src/lib/session/session.integration.test.ts +40 -48
  104. package/src/lib/session/session.ts +4 -66
  105. package/src/lib/session/types.ts +32 -1
  106. package/src/lib/subagent/define.ts +1 -1
  107. package/src/lib/subagent/handler.ts +9 -2
  108. package/src/lib/subagent/index.ts +1 -0
  109. package/src/lib/subagent/subagent.integration.test.ts +62 -0
  110. package/src/lib/subagent/types.ts +7 -2
  111. package/src/lib/tool-router/router-edge-cases.integration.test.ts +4 -1
  112. package/src/lib/tool-router/router.integration.test.ts +4 -1
  113. package/src/lib/workflow.test.ts +19 -10
  114. package/src/lib/workflow.ts +4 -1
  115. package/src/tools/bash/bash.test.ts +16 -7
  116. package/src/workflow.ts +6 -14
  117. package/tsup.config.ts +6 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitlich",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
4
4
  "description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -37,6 +37,16 @@
37
37
  "default": "./dist/adapters/thread/langchain/index.js"
38
38
  }
39
39
  },
40
+ "./adapters/thread/langchain/workflow": {
41
+ "import": {
42
+ "types": "./dist/adapters/thread/langchain/workflow.d.ts",
43
+ "default": "./dist/adapters/thread/langchain/workflow.js"
44
+ },
45
+ "require": {
46
+ "types": "./dist/adapters/thread/langchain/workflow.d.ts",
47
+ "default": "./dist/adapters/thread/langchain/workflow.js"
48
+ }
49
+ },
40
50
  "./adapters/thread/google-genai": {
41
51
  "import": {
42
52
  "types": "./dist/adapters/thread/google-genai/index.d.ts",
@@ -47,6 +57,16 @@
47
57
  "default": "./dist/adapters/thread/google-genai/index.js"
48
58
  }
49
59
  },
60
+ "./adapters/thread/google-genai/workflow": {
61
+ "import": {
62
+ "types": "./dist/adapters/thread/google-genai/workflow.d.ts",
63
+ "default": "./dist/adapters/thread/google-genai/workflow.js"
64
+ },
65
+ "require": {
66
+ "types": "./dist/adapters/thread/google-genai/workflow.d.ts",
67
+ "default": "./dist/adapters/thread/google-genai/workflow.js"
68
+ }
69
+ },
50
70
  "./adapters/sandbox/inmemory": {
51
71
  "import": {
52
72
  "types": "./dist/adapters/sandbox/inmemory/index.d.ts",
@@ -57,6 +77,16 @@
57
77
  "default": "./dist/adapters/sandbox/inmemory/index.js"
58
78
  }
59
79
  },
80
+ "./adapters/sandbox/inmemory/workflow": {
81
+ "import": {
82
+ "types": "./dist/adapters/sandbox/inmemory/workflow.d.ts",
83
+ "default": "./dist/adapters/sandbox/inmemory/workflow.js"
84
+ },
85
+ "require": {
86
+ "types": "./dist/adapters/sandbox/inmemory/workflow.d.ts",
87
+ "default": "./dist/adapters/sandbox/inmemory/workflow.js"
88
+ }
89
+ },
60
90
  "./adapters/sandbox/virtual": {
61
91
  "import": {
62
92
  "types": "./dist/adapters/sandbox/virtual/index.d.ts",
@@ -67,6 +97,16 @@
67
97
  "default": "./dist/adapters/sandbox/virtual/index.js"
68
98
  }
69
99
  },
100
+ "./adapters/sandbox/virtual/workflow": {
101
+ "import": {
102
+ "types": "./dist/adapters/sandbox/virtual/workflow.d.ts",
103
+ "default": "./dist/adapters/sandbox/virtual/workflow.js"
104
+ },
105
+ "require": {
106
+ "types": "./dist/adapters/sandbox/virtual/workflow.d.ts",
107
+ "default": "./dist/adapters/sandbox/virtual/workflow.js"
108
+ }
109
+ },
70
110
  "./adapters/sandbox/daytona": {
71
111
  "import": {
72
112
  "types": "./dist/adapters/sandbox/daytona/index.d.ts",
@@ -76,6 +116,16 @@
76
116
  "types": "./dist/adapters/sandbox/daytona/index.d.ts",
77
117
  "default": "./dist/adapters/sandbox/daytona/index.js"
78
118
  }
119
+ },
120
+ "./adapters/sandbox/daytona/workflow": {
121
+ "import": {
122
+ "types": "./dist/adapters/sandbox/daytona/workflow.d.ts",
123
+ "default": "./dist/adapters/sandbox/daytona/workflow.js"
124
+ },
125
+ "require": {
126
+ "types": "./dist/adapters/sandbox/daytona/workflow.d.ts",
127
+ "default": "./dist/adapters/sandbox/daytona/workflow.js"
128
+ }
79
129
  }
80
130
  },
81
131
  "files": [
@@ -121,6 +171,7 @@
121
171
  },
122
172
  "devDependencies": {
123
173
  "@daytonaio/sdk": "^0.149.0",
174
+ "@e2b/code-interpreter": "^2.3.3",
124
175
  "@eslint/js": "^10.0.1",
125
176
  "@google/genai": "^1.44.0",
126
177
  "@langchain/core": "^1.1.30",
@@ -139,6 +190,7 @@
139
190
  },
140
191
  "peerDependencies": {
141
192
  "@daytonaio/sdk": ">=0.100.0",
193
+ "@e2b/code-interpreter": "^2.3.3",
142
194
  "@google/genai": "^1.43.0",
143
195
  "@langchain/core": ">=1.0.0",
144
196
  "ioredis": ">=5.0.0",
@@ -141,6 +141,10 @@ export class DaytonaSandboxProvider implements SandboxProvider<
141
141
  }
142
142
  }
143
143
 
144
+ async fork(_sandboxId: string): Promise<Sandbox> {
145
+ throw new Error("Not implemented");
146
+ }
147
+
144
148
  async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
145
149
  throw new SandboxNotSupportedError(
146
150
  "snapshot (use Daytona's native snapshot API directly)"
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Workflow-safe proxy for Daytona sandbox operations.
3
+ *
4
+ * Uses longer timeouts than in-memory providers since Daytona
5
+ * sandboxes are remote and creation involves provisioning.
6
+ *
7
+ * Import this from `zeitlich/adapters/sandbox/daytona/workflow`
8
+ * in your Temporal workflow files.
9
+ *
10
+ * By default the scope is derived from `workflowInfo().workflowType`,
11
+ * so activities are automatically namespaced per workflow.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { proxyDaytonaSandboxOps } from 'zeitlich/adapters/sandbox/daytona/workflow';
16
+ *
17
+ * const sandbox = proxyDaytonaSandboxOps();
18
+ * ```
19
+ */
20
+ import { proxyActivities, workflowInfo } from "@temporalio/workflow";
21
+ import type { SandboxOps } from "../../../lib/sandbox/types";
22
+
23
+ const ADAPTER_PREFIX = "daytona";
24
+
25
+ export function proxyDaytonaSandboxOps(
26
+ scope?: string,
27
+ options?: Parameters<typeof proxyActivities>[0]
28
+ ): SandboxOps {
29
+ const resolvedScope = scope ?? workflowInfo().workflowType;
30
+
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ const acts = proxyActivities<Record<string, (...args: any[]) => any>>(
33
+ options ?? {
34
+ startToCloseTimeout: "120s",
35
+ retry: {
36
+ maximumAttempts: 3,
37
+ initialInterval: "5s",
38
+ maximumInterval: "60s",
39
+ backoffCoefficient: 3,
40
+ },
41
+ }
42
+ );
43
+
44
+ const prefix =
45
+ `${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
46
+ const p = (key: string): string =>
47
+ `${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
48
+
49
+ return {
50
+ createSandbox: acts[p("createSandbox")],
51
+ destroySandbox: acts[p("destroySandbox")],
52
+ snapshotSandbox: acts[p("snapshotSandbox")],
53
+ forkSandbox: acts[p("forkSandbox")],
54
+ } as SandboxOps;
55
+ }
@@ -0,0 +1,147 @@
1
+ import { FileType, type Sandbox as E2bSdkSandbox } from "@e2b/code-interpreter";
2
+ import type {
3
+ SandboxFileSystem,
4
+ DirentEntry,
5
+ FileStat,
6
+ } from "../../../lib/sandbox/types";
7
+ import { posix } from "node:path";
8
+
9
+ function toArrayBuffer(u8: Uint8Array): ArrayBuffer {
10
+ return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer;
11
+ }
12
+
13
+ /**
14
+ * {@link SandboxFileSystem} backed by an E2B SDK sandbox.
15
+ *
16
+ * Maps zeitlich's filesystem interface to E2B's `sandbox.files` and
17
+ * `sandbox.commands` APIs. Operations that have no direct E2B equivalent
18
+ * (e.g. `appendFile`, `cp`) are composed from primitives.
19
+ */
20
+ export class E2bSandboxFileSystem implements SandboxFileSystem {
21
+ readonly workspaceBase: string;
22
+
23
+ constructor(
24
+ private sandbox: E2bSdkSandbox,
25
+ workspaceBase = "/home/user"
26
+ ) {
27
+ this.workspaceBase = posix.resolve("/", workspaceBase);
28
+ }
29
+
30
+ private normalisePath(path: string): string {
31
+ return posix.resolve(this.workspaceBase, path);
32
+ }
33
+
34
+ async readFile(path: string): Promise<string> {
35
+ return this.sandbox.files.read(this.normalisePath(path));
36
+ }
37
+
38
+ async readFileBuffer(path: string): Promise<Uint8Array> {
39
+ return this.sandbox.files.read(this.normalisePath(path), {
40
+ format: "bytes",
41
+ });
42
+ }
43
+
44
+ async writeFile(path: string, content: string | Uint8Array): Promise<void> {
45
+ const norm = this.normalisePath(path);
46
+ if (typeof content === "string") {
47
+ await this.sandbox.files.write(norm, content);
48
+ } else {
49
+ await this.sandbox.files.write(norm, toArrayBuffer(content));
50
+ }
51
+ }
52
+
53
+ async appendFile(path: string, content: string | Uint8Array): Promise<void> {
54
+ const norm = this.normalisePath(path);
55
+ let existing = "";
56
+ try {
57
+ existing = await this.sandbox.files.read(norm);
58
+ } catch {
59
+ // file doesn't exist yet — write from scratch
60
+ }
61
+ const addition =
62
+ typeof content === "string"
63
+ ? content
64
+ : new TextDecoder().decode(content);
65
+ await this.sandbox.files.write(norm, existing + addition);
66
+ }
67
+
68
+ async exists(path: string): Promise<boolean> {
69
+ return this.sandbox.files.exists(this.normalisePath(path));
70
+ }
71
+
72
+ async stat(path: string): Promise<FileStat> {
73
+ const norm = this.normalisePath(path);
74
+ const info = await this.sandbox.files.getInfo(norm);
75
+ const isSymlink = !!info.symlinkTarget;
76
+ return {
77
+ isFile: isSymlink ? false : info.type === FileType.FILE,
78
+ isDirectory: isSymlink ? false : info.type === FileType.DIR,
79
+ isSymbolicLink: isSymlink,
80
+ size: info.size,
81
+ mtime: info.modifiedTime ?? new Date(0),
82
+ };
83
+ }
84
+
85
+ async mkdir(path: string, _options?: { recursive?: boolean }): Promise<void> {
86
+ await this.sandbox.files.makeDir(this.normalisePath(path));
87
+ }
88
+
89
+ async readdir(path: string): Promise<string[]> {
90
+ const entries = await this.sandbox.files.list(this.normalisePath(path));
91
+ return entries.map((e) => posix.basename(e.path));
92
+ }
93
+
94
+ async readdirWithFileTypes(path: string): Promise<DirentEntry[]> {
95
+ const entries = await this.sandbox.files.list(this.normalisePath(path));
96
+ return entries.map((e) => {
97
+ const isSymlink = !!e.symlinkTarget;
98
+ return {
99
+ name: posix.basename(e.path),
100
+ isFile: isSymlink ? false : e.type === FileType.FILE,
101
+ isDirectory: isSymlink ? false : e.type === FileType.DIR,
102
+ isSymbolicLink: isSymlink,
103
+ };
104
+ });
105
+ }
106
+
107
+ async rm(
108
+ path: string,
109
+ options?: { recursive?: boolean; force?: boolean }
110
+ ): Promise<void> {
111
+ const norm = this.normalisePath(path);
112
+ try {
113
+ await this.sandbox.files.remove(norm);
114
+ } catch (err) {
115
+ if (!options?.force) throw err;
116
+ }
117
+ }
118
+
119
+ async cp(
120
+ src: string,
121
+ dest: string,
122
+ _options?: { recursive?: boolean }
123
+ ): Promise<void> {
124
+ const normSrc = this.normalisePath(src);
125
+ const normDest = this.normalisePath(dest);
126
+ await this.sandbox.commands.run(`cp -r "${normSrc}" "${normDest}"`);
127
+ }
128
+
129
+ async mv(src: string, dest: string): Promise<void> {
130
+ const normSrc = this.normalisePath(src);
131
+ const normDest = this.normalisePath(dest);
132
+ await this.sandbox.files.rename(normSrc, normDest);
133
+ }
134
+
135
+ async readlink(path: string): Promise<string> {
136
+ const norm = this.normalisePath(path);
137
+ const info = await this.sandbox.files.getInfo(norm);
138
+ if (!info.symlinkTarget) {
139
+ throw new Error(`EINVAL: invalid argument, readlink '${path}'`);
140
+ }
141
+ return info.symlinkTarget;
142
+ }
143
+
144
+ resolvePath(base: string, path: string): string {
145
+ return posix.resolve(this.normalisePath(base), path);
146
+ }
147
+ }
@@ -0,0 +1,159 @@
1
+ import { Sandbox as E2bSdkSandbox } from "@e2b/code-interpreter";
2
+ import type {
3
+ Sandbox,
4
+ SandboxCapabilities,
5
+ SandboxCreateResult,
6
+ SandboxProvider,
7
+ SandboxSnapshot,
8
+ ExecOptions,
9
+ ExecResult,
10
+ } from "../../../lib/sandbox/types";
11
+ import {
12
+ SandboxNotFoundError,
13
+ SandboxNotSupportedError,
14
+ } from "../../../lib/sandbox/types";
15
+ import { E2bSandboxFileSystem } from "./filesystem";
16
+ import type {
17
+ E2bSandbox,
18
+ E2bSandboxConfig,
19
+ E2bSandboxCreateOptions,
20
+ } from "./types";
21
+
22
+ // ============================================================================
23
+ // E2bSandbox
24
+ // ============================================================================
25
+
26
+ class E2bSandboxImpl implements Sandbox {
27
+ readonly capabilities: SandboxCapabilities = {
28
+ filesystem: true,
29
+ execution: true,
30
+ persistence: true,
31
+ };
32
+
33
+ readonly fs: E2bSandboxFileSystem;
34
+
35
+ constructor(
36
+ readonly id: string,
37
+ private sdkSandbox: E2bSdkSandbox,
38
+ workspaceBase = "/home/user"
39
+ ) {
40
+ this.fs = new E2bSandboxFileSystem(sdkSandbox, workspaceBase);
41
+ }
42
+
43
+ async exec(command: string, options?: ExecOptions): Promise<ExecResult> {
44
+ const result = await this.sdkSandbox.commands.run(command, {
45
+ cwd: options?.cwd,
46
+ envs: options?.env,
47
+ timeoutMs: options?.timeout,
48
+ });
49
+ return {
50
+ exitCode: result.exitCode,
51
+ stdout: result.stdout,
52
+ stderr: result.stderr,
53
+ };
54
+ }
55
+
56
+ async destroy(): Promise<void> {
57
+ await this.sdkSandbox.kill();
58
+ }
59
+ }
60
+
61
+ // ============================================================================
62
+ // E2bSandboxProvider
63
+ // ============================================================================
64
+
65
+ export class E2bSandboxProvider
66
+ implements SandboxProvider<E2bSandboxCreateOptions, E2bSandbox>
67
+ {
68
+ readonly id = "e2b";
69
+ readonly capabilities: SandboxCapabilities = {
70
+ filesystem: true,
71
+ execution: true,
72
+ persistence: true,
73
+ };
74
+
75
+ private readonly defaultTemplate?: string;
76
+ private readonly defaultWorkspaceBase: string;
77
+ private readonly defaultTimeoutMs?: number;
78
+
79
+ constructor(config?: E2bSandboxConfig) {
80
+ this.defaultTemplate = config?.template;
81
+ this.defaultWorkspaceBase = config?.workspaceBase ?? "/home/user";
82
+ this.defaultTimeoutMs = config?.timeoutMs;
83
+ }
84
+
85
+ async create(
86
+ options?: E2bSandboxCreateOptions
87
+ ): Promise<SandboxCreateResult> {
88
+ const template = options?.template ?? this.defaultTemplate;
89
+ const workspaceBase = this.defaultWorkspaceBase;
90
+ const createOpts = {
91
+ envs: options?.env,
92
+ timeoutMs: options?.timeoutMs ?? this.defaultTimeoutMs,
93
+ };
94
+
95
+ const sdkSandbox = template
96
+ ? await E2bSdkSandbox.create(template, createOpts)
97
+ : await E2bSdkSandbox.create(createOpts);
98
+
99
+ const sandbox = new E2bSandboxImpl(
100
+ sdkSandbox.sandboxId,
101
+ sdkSandbox,
102
+ workspaceBase
103
+ );
104
+
105
+ if (options?.initialFiles) {
106
+ await Promise.all(
107
+ Object.entries(options.initialFiles).map(([path, content]) =>
108
+ sandbox.fs.writeFile(path, content)
109
+ )
110
+ );
111
+ }
112
+
113
+ return { sandbox };
114
+ }
115
+
116
+ async get(sandboxId: string): Promise<E2bSandbox> {
117
+ try {
118
+ const sdkSandbox = await E2bSdkSandbox.connect(sandboxId);
119
+ return new E2bSandboxImpl(sandboxId, sdkSandbox, this.defaultWorkspaceBase);
120
+ } catch {
121
+ throw new SandboxNotFoundError(sandboxId);
122
+ }
123
+ }
124
+
125
+ async destroy(sandboxId: string): Promise<void> {
126
+ try {
127
+ const sdkSandbox = await E2bSdkSandbox.connect(sandboxId);
128
+ await sdkSandbox.kill();
129
+ } catch {
130
+ // Already gone or not found
131
+ }
132
+ }
133
+
134
+ async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
135
+ throw new SandboxNotSupportedError("snapshot");
136
+ }
137
+
138
+ async restore(_snapshot: SandboxSnapshot): Promise<Sandbox> {
139
+ throw new SandboxNotSupportedError("restore");
140
+ }
141
+
142
+ async fork(sandboxId: string): Promise<Sandbox> {
143
+ const { snapshotId } = await E2bSdkSandbox.createSnapshot(sandboxId);
144
+ const sdkSandbox = await E2bSdkSandbox.create(snapshotId);
145
+ return new E2bSandboxImpl(
146
+ sdkSandbox.sandboxId,
147
+ sdkSandbox,
148
+ this.defaultWorkspaceBase
149
+ );
150
+ }
151
+ }
152
+
153
+ // Re-exports
154
+ export { E2bSandboxFileSystem } from "./filesystem";
155
+ export type {
156
+ E2bSandbox,
157
+ E2bSandboxConfig,
158
+ E2bSandboxCreateOptions,
159
+ } from "./types";
@@ -0,0 +1,23 @@
1
+ import type { Sandbox, SandboxCreateOptions } from "../../../lib/sandbox/types";
2
+ import type { E2bSandboxFileSystem } from "./filesystem";
3
+
4
+ /**
5
+ * An E2B-backed {@link Sandbox} with its typed filesystem.
6
+ */
7
+ export type E2bSandbox = Sandbox & { fs: E2bSandboxFileSystem };
8
+
9
+ export interface E2bSandboxConfig {
10
+ /** Sandbox template name or ID */
11
+ template?: string;
12
+ /** Default working directory inside the sandbox */
13
+ workspaceBase?: string;
14
+ /** Sandbox idle timeout in milliseconds */
15
+ timeoutMs?: number;
16
+ }
17
+
18
+ export interface E2bSandboxCreateOptions extends SandboxCreateOptions {
19
+ /** Sandbox template name or ID — overrides the provider default */
20
+ template?: string;
21
+ /** Sandbox idle timeout in milliseconds — overrides the provider default */
22
+ timeoutMs?: number;
23
+ }
@@ -132,7 +132,7 @@ class InMemorySandboxImpl implements Sandbox {
132
132
  // ============================================================================
133
133
 
134
134
  export class InMemorySandboxProvider implements SandboxProvider {
135
- readonly id = "inmemory";
135
+ readonly id = "inMemory";
136
136
  readonly capabilities: SandboxCapabilities = {
137
137
  filesystem: true,
138
138
  execution: true,
@@ -200,6 +200,10 @@ export class InMemorySandboxProvider implements SandboxProvider {
200
200
  };
201
201
  }
202
202
 
203
+ async fork(_sandboxId: string): Promise<Sandbox> {
204
+ throw new Error("Not implemented");
205
+ }
206
+
203
207
  async restore(snapshot: SandboxSnapshot): Promise<Sandbox> {
204
208
  const { files } = snapshot.data as { files: Record<string, string> };
205
209
  const initialFiles: InitialFiles = {};
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Workflow-safe proxy for in-memory sandbox operations.
3
+ *
4
+ * Import this from `zeitlich/adapters/sandbox/inmemory/workflow`
5
+ * in your Temporal workflow files.
6
+ *
7
+ * By default the scope is derived from `workflowInfo().workflowType`,
8
+ * so activities are automatically namespaced per workflow.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { proxyInMemorySandboxOps } from 'zeitlich/adapters/sandbox/inmemory/workflow';
13
+ *
14
+ * // Auto-scoped to the current workflow name
15
+ * const sandbox = proxyInMemorySandboxOps();
16
+ * ```
17
+ */
18
+ import { proxyActivities, workflowInfo } from "@temporalio/workflow";
19
+ import type { SandboxOps } from "../../../lib/sandbox/types";
20
+
21
+ const ADAPTER_PREFIX = "inMemory";
22
+
23
+ export function proxyInMemorySandboxOps(
24
+ scope?: string,
25
+ options?: Parameters<typeof proxyActivities>[0]
26
+ ): SandboxOps {
27
+ const resolvedScope = scope ?? workflowInfo().workflowType;
28
+
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ const acts = proxyActivities<Record<string, (...args: any[]) => any>>(
31
+ options ?? {
32
+ startToCloseTimeout: "30s",
33
+ retry: {
34
+ maximumAttempts: 3,
35
+ initialInterval: "2s",
36
+ maximumInterval: "30s",
37
+ backoffCoefficient: 2,
38
+ },
39
+ }
40
+ );
41
+
42
+ const prefix =
43
+ `${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
44
+ const p = (key: string): string =>
45
+ `${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
46
+
47
+ return {
48
+ createSandbox: acts[p("createSandbox")],
49
+ destroySandbox: acts[p("destroySandbox")],
50
+ snapshotSandbox: acts[p("snapshotSandbox")],
51
+ forkSandbox: acts[p("forkSandbox")],
52
+ } as SandboxOps;
53
+ }
@@ -26,7 +26,7 @@ import type {
26
26
  * const manager = new SandboxManager(provider);
27
27
  *
28
28
  * export const activities = {
29
- * ...manager.createActivities(),
29
+ * ...manager.createActivities("CodingAgent"),
30
30
  * readFile: withVirtualSandbox(client, provider, readHandler),
31
31
  * };
32
32
  * ```
@@ -90,6 +90,10 @@ export class VirtualSandboxProvider<
90
90
  // No-op — no internal state to clean up
91
91
  }
92
92
 
93
+ async fork(_sandboxId: string): Promise<never> {
94
+ throw new Error("Not implemented");
95
+ }
96
+
93
97
  async snapshot(): Promise<never> {
94
98
  throw new SandboxNotSupportedError(
95
99
  "snapshot (virtual sandbox state lives in workflow AgentState)"
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Workflow-safe proxy for virtual sandbox operations.
3
+ *
4
+ * Import this from `zeitlich/adapters/sandbox/virtual/workflow`
5
+ * in your Temporal workflow files.
6
+ *
7
+ * By default the scope is derived from `workflowInfo().workflowType`,
8
+ * so activities are automatically namespaced per workflow.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { proxyVirtualSandboxOps } from 'zeitlich/adapters/sandbox/virtual/workflow';
13
+ *
14
+ * const sandbox = proxyVirtualSandboxOps();
15
+ * ```
16
+ */
17
+ import { proxyActivities, workflowInfo } from "@temporalio/workflow";
18
+ import type { SandboxOps } from "../../../lib/sandbox/types";
19
+ import type { VirtualSandboxCreateOptions } from "./types";
20
+
21
+ const ADAPTER_PREFIX = "virtual";
22
+
23
+ export function proxyVirtualSandboxOps(
24
+ scope?: string,
25
+ options?: Parameters<typeof proxyActivities>[0]
26
+ ): SandboxOps<VirtualSandboxCreateOptions<unknown>> {
27
+ const resolvedScope = scope ?? workflowInfo().workflowType;
28
+
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ const acts = proxyActivities<Record<string, (...args: any[]) => any>>(
31
+ options ?? {
32
+ startToCloseTimeout: "30s",
33
+ retry: {
34
+ maximumAttempts: 3,
35
+ initialInterval: "2s",
36
+ maximumInterval: "30s",
37
+ backoffCoefficient: 2,
38
+ },
39
+ }
40
+ );
41
+
42
+ const prefix = `${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
43
+ const p = (key: string): string =>
44
+ `${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
45
+
46
+ return {
47
+ createSandbox: acts[p("createSandbox")],
48
+ destroySandbox: acts[p("destroySandbox")],
49
+ snapshotSandbox: acts[p("snapshotSandbox")],
50
+ forkSandbox: acts[p("forkSandbox")],
51
+ } as SandboxOps<VirtualSandboxCreateOptions<unknown>>;
52
+ }