zeitlich 0.2.21 → 0.2.23

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 (129) hide show
  1. package/README.md +303 -105
  2. package/dist/adapters/sandbox/daytona/index.cjs +7 -1
  3. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  4. package/dist/adapters/sandbox/daytona/index.d.cts +3 -1
  5. package/dist/adapters/sandbox/daytona/index.d.ts +3 -1
  6. package/dist/adapters/sandbox/daytona/index.js +7 -1
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  8. package/dist/adapters/sandbox/daytona/workflow.cjs +33 -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 +31 -0
  13. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -0
  14. package/dist/adapters/sandbox/inmemory/index.cjs +18 -1
  15. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  16. package/dist/adapters/sandbox/inmemory/index.d.cts +4 -2
  17. package/dist/adapters/sandbox/inmemory/index.d.ts +4 -2
  18. package/dist/adapters/sandbox/inmemory/index.js +18 -1
  19. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  20. package/dist/adapters/sandbox/inmemory/workflow.cjs +33 -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 +31 -0
  25. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -0
  26. package/dist/adapters/sandbox/virtual/index.cjs +36 -9
  27. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  28. package/dist/adapters/sandbox/virtual/index.d.cts +8 -5
  29. package/dist/adapters/sandbox/virtual/index.d.ts +8 -5
  30. package/dist/adapters/sandbox/virtual/index.js +36 -9
  31. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  32. package/dist/adapters/sandbox/virtual/workflow.cjs +33 -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 +31 -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 +31 -19
  41. package/dist/adapters/thread/google-genai/index.d.ts +31 -19
  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 +27 -16
  53. package/dist/adapters/thread/langchain/index.d.ts +27 -16
  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 +282 -90
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +38 -16
  65. package/dist/index.d.ts +38 -16
  66. package/dist/index.js +281 -87
  67. package/dist/index.js.map +1 -1
  68. package/dist/queries-DModcWRy.d.cts +44 -0
  69. package/dist/queries-byD0jr1Y.d.ts +44 -0
  70. package/dist/{types-BkAYmc96.d.ts → types-B50pBPEV.d.ts} +190 -38
  71. package/dist/{types-YbL7JpEA.d.cts → types-Bll19FZJ.d.cts} +7 -0
  72. package/dist/{types-YbL7JpEA.d.ts → types-Bll19FZJ.d.ts} +7 -0
  73. package/dist/{queries-6Avfh74U.d.ts → types-BuXdFhaZ.d.cts} +7 -48
  74. package/dist/{types-BMRzfELQ.d.cts → types-ChAMwU3q.d.cts} +17 -1
  75. package/dist/{types-BMRzfELQ.d.ts → types-ChAMwU3q.d.ts} +17 -1
  76. package/dist/{types-CES_30qx.d.cts → types-DQW8l7pY.d.cts} +190 -38
  77. package/dist/{queries-CHa2iv_I.d.cts → types-GZ76HZSj.d.ts} +7 -48
  78. package/dist/workflow.cjs +244 -86
  79. package/dist/workflow.cjs.map +1 -1
  80. package/dist/workflow.d.cts +54 -65
  81. package/dist/workflow.d.ts +54 -65
  82. package/dist/workflow.js +243 -83
  83. package/dist/workflow.js.map +1 -1
  84. package/package.json +54 -2
  85. package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
  86. package/src/adapters/sandbox/daytona/index.ts +8 -0
  87. package/src/adapters/sandbox/daytona/proxy.ts +56 -0
  88. package/src/adapters/sandbox/e2b/filesystem.ts +147 -0
  89. package/src/adapters/sandbox/e2b/index.ts +164 -0
  90. package/src/adapters/sandbox/e2b/types.ts +23 -0
  91. package/src/adapters/sandbox/inmemory/index.ts +27 -3
  92. package/src/adapters/sandbox/inmemory/proxy.ts +53 -0
  93. package/src/adapters/sandbox/virtual/filesystem.ts +41 -17
  94. package/src/adapters/sandbox/virtual/provider.ts +9 -1
  95. package/src/adapters/sandbox/virtual/proxy.ts +53 -0
  96. package/src/adapters/sandbox/virtual/types.ts +9 -4
  97. package/src/adapters/thread/google-genai/activities.ts +51 -17
  98. package/src/adapters/thread/google-genai/index.ts +1 -0
  99. package/src/adapters/thread/google-genai/proxy.ts +61 -0
  100. package/src/adapters/thread/langchain/activities.ts +47 -14
  101. package/src/adapters/thread/langchain/index.ts +1 -0
  102. package/src/adapters/thread/langchain/proxy.ts +61 -0
  103. package/src/lib/lifecycle.ts +57 -0
  104. package/src/lib/sandbox/manager.ts +52 -6
  105. package/src/lib/sandbox/sandbox.test.ts +12 -11
  106. package/src/lib/sandbox/types.ts +31 -4
  107. package/src/lib/session/index.ts +4 -5
  108. package/src/lib/session/session-edge-cases.integration.test.ts +491 -66
  109. package/src/lib/session/session.integration.test.ts +92 -80
  110. package/src/lib/session/session.ts +108 -96
  111. package/src/lib/session/types.ts +87 -17
  112. package/src/lib/subagent/define.ts +6 -5
  113. package/src/lib/subagent/handler.ts +148 -16
  114. package/src/lib/subagent/index.ts +4 -0
  115. package/src/lib/subagent/register.ts +10 -3
  116. package/src/lib/subagent/signals.ts +8 -0
  117. package/src/lib/subagent/subagent.integration.test.ts +893 -128
  118. package/src/lib/subagent/tool.ts +2 -2
  119. package/src/lib/subagent/types.ts +84 -21
  120. package/src/lib/subagent/workflow.ts +83 -12
  121. package/src/lib/tool-router/router-edge-cases.integration.test.ts +4 -1
  122. package/src/lib/tool-router/router.integration.test.ts +141 -5
  123. package/src/lib/tool-router/router.ts +13 -3
  124. package/src/lib/tool-router/types.ts +7 -0
  125. package/src/lib/workflow.test.ts +104 -27
  126. package/src/lib/workflow.ts +37 -19
  127. package/src/tools/bash/bash.test.ts +16 -7
  128. package/src/workflow.ts +11 -14
  129. 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.23",
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",
@@ -138,7 +189,8 @@
138
189
  "vitest": "^4.0.18"
139
190
  },
140
191
  "peerDependencies": {
141
- "@daytonaio/sdk": ">=0.100.0",
192
+ "@daytonaio/sdk": ">=0.153.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",
@@ -55,7 +55,7 @@ export class DaytonaSandboxFileSystem implements SandboxFileSystem {
55
55
  await this.sandbox.fs.uploadFiles(
56
56
  files.map((f) => ({
57
57
  source: Buffer.from(f.content),
58
- destination: f.path,
58
+ destination: this.normalisePath(f.path),
59
59
  }))
60
60
  );
61
61
  }
@@ -141,6 +141,14 @@ export class DaytonaSandboxProvider implements SandboxProvider<
141
141
  }
142
142
  }
143
143
 
144
+ async pause(_sandboxId: string, _ttlSeconds?: number): Promise<void> {
145
+ throw new SandboxNotSupportedError("pause");
146
+ }
147
+
148
+ async fork(_sandboxId: string): Promise<Sandbox> {
149
+ throw new Error("Not implemented");
150
+ }
151
+
144
152
  async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
145
153
  throw new SandboxNotSupportedError(
146
154
  "snapshot (use Daytona's native snapshot API directly)"
@@ -0,0 +1,56 @@
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
+ import type { DaytonaSandboxCreateOptions } from "./types";
23
+
24
+ const ADAPTER_PREFIX = "daytona";
25
+
26
+ export function proxyDaytonaSandboxOps(
27
+ scope?: string,
28
+ options?: Parameters<typeof proxyActivities>[0]
29
+ ): SandboxOps {
30
+ const resolvedScope = scope ?? workflowInfo().workflowType;
31
+
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ const acts = proxyActivities<Record<string, (...args: any[]) => any>>(
34
+ options ?? {
35
+ startToCloseTimeout: "120s",
36
+ retry: {
37
+ maximumAttempts: 3,
38
+ initialInterval: "5s",
39
+ maximumInterval: "60s",
40
+ backoffCoefficient: 3,
41
+ },
42
+ }
43
+ );
44
+
45
+ const prefix = `${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
+ pauseSandbox: acts[p("pauseSandbox")],
53
+ snapshotSandbox: acts[p("snapshotSandbox")],
54
+ forkSandbox: acts[p("forkSandbox")],
55
+ } as SandboxOps<DaytonaSandboxCreateOptions>;
56
+ }
@@ -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,164 @@
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 pause(sandboxId: string, _ttlSeconds?: number): Promise<void> {
135
+ const sdkSandbox = await E2bSdkSandbox.connect(sandboxId);
136
+ await sdkSandbox.pause();
137
+ }
138
+
139
+ async snapshot(_sandboxId: string): Promise<SandboxSnapshot> {
140
+ throw new SandboxNotSupportedError("snapshot");
141
+ }
142
+
143
+ async restore(_snapshot: SandboxSnapshot): Promise<Sandbox> {
144
+ throw new SandboxNotSupportedError("restore");
145
+ }
146
+
147
+ async fork(sandboxId: string): Promise<Sandbox> {
148
+ const { snapshotId } = await E2bSdkSandbox.createSnapshot(sandboxId);
149
+ const sdkSandbox = await E2bSdkSandbox.create(snapshotId);
150
+ return new E2bSandboxImpl(
151
+ sdkSandbox.sandboxId,
152
+ sdkSandbox,
153
+ this.defaultWorkspaceBase
154
+ );
155
+ }
156
+ }
157
+
158
+ // Re-exports
159
+ export { E2bSandboxFileSystem } from "./filesystem";
160
+ export type {
161
+ E2bSandbox,
162
+ E2bSandboxConfig,
163
+ E2bSandboxCreateOptions,
164
+ } 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
+ }
@@ -27,7 +27,8 @@ import { getShortId } from "../../../lib/thread/id";
27
27
 
28
28
  function toSandboxFs(fs: IFileSystem): SandboxFileSystem {
29
29
  const workspaceBase = "/";
30
- const normalisePath = (path: string): string => fs.resolvePath(workspaceBase, path);
30
+ const normalisePath = (path: string): string =>
31
+ fs.resolvePath(workspaceBase, path);
31
32
 
32
33
  return {
33
34
  workspaceBase,
@@ -68,7 +69,8 @@ function toSandboxFs(fs: IFileSystem): SandboxFileSystem {
68
69
  return fs.readdirWithFileTypes(dirPath);
69
70
  },
70
71
  rm: (path, opts) => fs.rm(normalisePath(path), opts),
71
- cp: (src, dest, opts) => fs.cp(normalisePath(src), normalisePath(dest), opts),
72
+ cp: (src, dest, opts) =>
73
+ fs.cp(normalisePath(src), normalisePath(dest), opts),
72
74
  mv: (src, dest) => fs.mv(normalisePath(src), normalisePath(dest)),
73
75
  readlink: (path) => fs.readlink(normalisePath(path)),
74
76
  resolvePath: (base, p) => fs.resolvePath(normalisePath(base), p),
@@ -132,7 +134,7 @@ class InMemorySandboxImpl implements Sandbox {
132
134
  // ============================================================================
133
135
 
134
136
  export class InMemorySandboxProvider implements SandboxProvider {
135
- readonly id = "inmemory";
137
+ readonly id = "inMemory";
136
138
  readonly capabilities: SandboxCapabilities = {
137
139
  filesystem: true,
138
140
  execution: true,
@@ -157,6 +159,10 @@ export class InMemorySandboxProvider implements SandboxProvider {
157
159
  }
158
160
  }
159
161
 
162
+ async pause(_sandboxId: string, _ttlSeconds?: number): Promise<void> {
163
+ // In-memory: nothing to pause
164
+ }
165
+
160
166
  async create(options?: SandboxCreateOptions): Promise<SandboxCreateResult> {
161
167
  const id = options?.id ?? getShortId();
162
168
  const initialFiles: InitialFiles = {};
@@ -200,6 +206,24 @@ export class InMemorySandboxProvider implements SandboxProvider {
200
206
  };
201
207
  }
202
208
 
209
+ async fork(sandboxId: string): Promise<Sandbox> {
210
+ const sandbox = await this.get(sandboxId);
211
+
212
+ const entries = await sandbox.fs.readdirWithFileTypes("/");
213
+ const initialFiles: Record<string, Uint8Array> = {};
214
+ for (const entry of entries) {
215
+ if (entry.isFile) {
216
+ initialFiles[entry.name] = await sandbox.fs.readFileBuffer(entry.name);
217
+ }
218
+ }
219
+
220
+ const newSandbox = await this.create({
221
+ id: getShortId(),
222
+ initialFiles,
223
+ });
224
+ return newSandbox.sandbox;
225
+ }
226
+
203
227
  async restore(snapshot: SandboxSnapshot): Promise<Sandbox> {
204
228
  const { files } = snapshot.data as { files: Record<string, string> };
205
229
  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 = `${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
+ pauseSandbox: acts[p("pauseSandbox")],
50
+ snapshotSandbox: acts[p("snapshotSandbox")],
51
+ forkSandbox: acts[p("forkSandbox")],
52
+ } as SandboxOps;
53
+ }