@x1a0f3n9/dsh-e2b 0.1.5-rc.3
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +145 -0
- package/README.zh.md +145 -0
- package/lib/index.js +145 -0
- package/lib/types/api-url.d.ts +15 -0
- package/lib/types/index.d.ts +62 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/e2b/e2b/README.md
|
|
5
|
+
README.md: 09c23e4540b708ada5fc6d485806058793937a09
|
|
6
|
+
README.zh.md: 4bf1c6f220242b366b8f09118a6fd0fee6151d11
|
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "One shared remote Linux sandbox for E2B-backed file and command work: configuration, lifetime, and what happens at startup and shutdown."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @x1a0f3n9/dsh-e2b
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
`dsh-e2b` runs the agent's file operations, shell commands, and terminals in one shared remote Linux sandbox instead of on your machine. The sandbox is created at startup and deleted when its configured lifetime expires or the app shuts down, so everything it holds is ephemeral. Configure an API key, an absolute remote working directory, and the sandbox lifetime. Use it with `dsh-fs-e2b` and `dsh-subprocess-e2b`; by itself it adds no user-visible capability. It sends nothing to the model, and no shipped composition enables E2B by default.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Use this package when you want the agent's file and command work to run in a remote Linux sandbox rather than on your machine. It is the foundation of the E2B family: with the filesystem and subprocess packages mounted, all of that work shares one remote working directory and process world.
|
|
29
|
+
|
|
30
|
+
### When to choose it
|
|
31
|
+
|
|
32
|
+
Choose the E2B family when work should be isolated from the host machine — for example, when you want the agent's file edits and command runs to happen somewhere disposable. Choose the local filesystem and subprocess packages when running on the host is fine. This package is invisible to the model and adds no request cost.
|
|
33
|
+
|
|
34
|
+
### Minimal configuration
|
|
35
|
+
|
|
36
|
+
Three settings matter: an API key (or the `E2B_API_KEY` environment variable), an absolute remote working directory, and the sandbox lifetime. A bad key, a relative working directory, or an invalid lifetime rejects startup before any remote work happens.
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@x1a0f3n9/dsh-e2b'
|
|
40
|
+
config:
|
|
41
|
+
apiKey: <E2B API key>
|
|
42
|
+
cwd: /home/user/workspace
|
|
43
|
+
timeoutMs: 300000
|
|
44
|
+
|
|
45
|
+
- name: '@x1a0f3n9/dsh-subprocess-e2b'
|
|
46
|
+
- name: '@x1a0f3n9/dsh-fs-e2b'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Field | Default | Meaning |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `apiKey` | `E2B_API_KEY` | API key for the host SDK connection; never installed in the sandbox |
|
|
52
|
+
| `cwd` | `/home/user/workspace` | Remote working directory the family shares; absolute POSIX path |
|
|
53
|
+
| `timeoutMs` | `300,000` | Sandbox lifetime in milliseconds; the sandbox is deleted when it expires |
|
|
54
|
+
|
|
55
|
+
The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-e2b) is the exhaustive source for every accepted field and its JSDoc.
|
|
56
|
+
|
|
57
|
+
### What you get
|
|
58
|
+
|
|
59
|
+
With this package mounted, file reads and writes, shell commands, and terminals all operate inside the sandbox's working directory, so the agent sees one consistent remote world: what it writes with the file features is what its commands can read, and vice versa. The remote working directory is created if it does not exist yet.
|
|
60
|
+
|
|
61
|
+
### Starting and stopping the sandbox
|
|
62
|
+
|
|
63
|
+
Loading the plugin starts the sandbox in the background; the filesystem and subprocess features are ready once it is up. The sandbox lives for the configured lifetime (default five minutes) unless the app stops first — in both cases it is deleted, so save anything you still need before then. If the sandbox disappears while running (expired or removed elsewhere), the family treats that as a clean end rather than an error.
|
|
64
|
+
|
|
65
|
+
-----
|
|
66
|
+
|
|
67
|
+
<a id="understand-the-implementation"></a>
|
|
68
|
+
## Understand the implementation
|
|
69
|
+
|
|
70
|
+
<details>
|
|
71
|
+
<summary>Implementation internals — click to expand</summary>
|
|
72
|
+
|
|
73
|
+
This section explains the design decisions behind the owner and points at the code that realizes them; the observable behavior is fully covered in [Use this package](#use-this-package).
|
|
74
|
+
|
|
75
|
+
### Design philosophy
|
|
76
|
+
|
|
77
|
+
- **One sandbox, one handle.** All adapters await the same `getSandbox()` promise, so filesystem and process operations share one remote Linux world.
|
|
78
|
+
- **Secure by construction.** The sandbox is created with `secure: true` and `lifecycle: { onTimeout: 'kill' }`, so expiry always deletes it.
|
|
79
|
+
- **Isolated control shells.** `e2bControlEnvs()` gives every internal command shell a fresh randomized `HOME`, and `quoteE2BShellArg()` preserves opaque arguments through the SDK's unavoidable `/bin/bash -l -c` layer.
|
|
80
|
+
|
|
81
|
+
### Source map
|
|
82
|
+
|
|
83
|
+
| File | Role |
|
|
84
|
+
|---|---|
|
|
85
|
+
| [`src/index.ts`](src/index.ts) | Plugin entry: `E2BRuntime` service, `Config` schema, validation, sandbox open and teardown |
|
|
86
|
+
| — | No runtime invariant companion is published; sandbox creation and teardown have one SDK promise and no independent event or mutable-data relationship to cross-check. |
|
|
87
|
+
|
|
88
|
+
### Lifecycle
|
|
89
|
+
|
|
90
|
+
`open()` creates the sandbox, prepares `cwd` and the private runtime root, rejects a non-directory or symlink runtime root, and applies `chmod 700`. Disposal prevents new handle acquisition, awaits setup, and deletes the sandbox, accepting `SandboxNotFoundError` as quiescence. `getSandbox()` re-checks the disposed flag after awaiting readiness, so disposal racing readiness still rejects acquisition; an eager connection failure stays observed but does not reject plugin load, and `getSandbox()` surfaces it.
|
|
91
|
+
|
|
92
|
+
### Setup failure handling
|
|
93
|
+
|
|
94
|
+
Any directory-setup failure makes one deletion attempt and preserves the original error; a failed rollback is bounded by E2B's configured sandbox timeout (see the Dev Note). Provider plugins must load after this owner and dispose before it, because every adapter awaits the same handle.
|
|
95
|
+
|
|
96
|
+
</details>
|
|
97
|
+
|
|
98
|
+
-----
|
|
99
|
+
|
|
100
|
+
<a id="further-exploration"></a>
|
|
101
|
+
## Further Exploration
|
|
102
|
+
|
|
103
|
+
Read these pages when the package-level contract is not enough. They move from the family composition to the subprocess seam surface and the decision evidence behind the remote execution world.
|
|
104
|
+
|
|
105
|
+
- [E2B provider family map](../README.md) — the three packages and the opt-in composition.
|
|
106
|
+
- [Subprocess subsystem](../../../docs/subsystems/subprocess.md) — the subprocess seam contract and the generated Cordis surface, including `ctx.e2b`.
|
|
107
|
+
- [Portable execution-world decision](../../../.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md) — why consumers delegate to `ctx.fs` and `ctx.subprocess`, and what stays in the host.
|
|
108
|
+
- [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-e2b) — every accepted config field and its source declaration.
|
|
109
|
+
|
|
110
|
+
-----
|
|
111
|
+
|
|
112
|
+
<a id="model-experience"></a>
|
|
113
|
+
## Model Experience
|
|
114
|
+
|
|
115
|
+
None, as the shared remote-runtime owner registers no model context; provider adapters and consumers own rendered effects.
|
|
116
|
+
|
|
117
|
+
#### KV Cache effect
|
|
118
|
+
|
|
119
|
+
No direct invalidation: the owner contributes no request tokens and never mutates a request prefix, so provider cache reuse is unaffected.
|
|
120
|
+
|
|
121
|
+
## Known Limitations and Deferred Work
|
|
122
|
+
|
|
123
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
These limits define when the E2B family is a poor fit or needs special operational care. They are current package constraints, not a task backlog.
|
|
127
|
+
|
|
128
|
+
- **Not a whole-harness runtime** — Cordis services, agent/session state, session logs, LLM requests, skills, and SDK-side buffers stay in the host process.
|
|
129
|
+
- **Sandbox state is ephemeral** — disposal and timeout delete the sandbox; reconnect, pause/leave retention, templates, volumes, and snapshots are outside this POC.
|
|
130
|
+
- **No deployment platform is configured** — network policy, host-workspace synchronization, and sandbox discovery are outside this POC.
|
|
131
|
+
- **`cwd` is a resolution convention, not containment** — adapters and commands can address other sandbox paths; E2B network access retains the base image's policy.
|
|
132
|
+
|
|
133
|
+
<a id="dev-note"></a>
|
|
134
|
+
### Dev Note
|
|
135
|
+
|
|
136
|
+
<details>
|
|
137
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
138
|
+
|
|
139
|
+
This Dev Note is working context for maintainers: open questions and directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above and the package code.
|
|
140
|
+
|
|
141
|
+
#### Open: sandbox setup rollback
|
|
142
|
+
|
|
143
|
+
The `open()` failure path makes a single deletion attempt and preserves the original setup failure. Retry state stays deferred unless a real double failure outlives E2B's configured sandbox timeout (TODO(e2b-setup-rollback)).
|
|
144
|
+
|
|
145
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "E2B 文件与命令工作的共享远程 Linux 沙箱:配置、生命周期,以及启动与关闭时会发生什么。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @x1a0f3n9/dsh-e2b
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
`dsh-e2b` 让 agent(智能体)的文件操作、shell 命令与终端在一个共享的远程 Linux 沙箱内运行,而不是在你的机器上。应用启动时会创建沙箱,并在配置的生命周期到期或应用关闭时删除它,因此其中保存的一切都是短暂的。请配置 API 密钥、绝对远程工作目录与沙箱生命周期。请与 `dsh-fs-e2b`、`dsh-subprocess-e2b` 一起使用;单独使用它不会带来任何用户可见的能力。它不会向模型发送任何内容,而且任何已发布的组合都不会默认启用 E2B。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
当你希望 agent 的文件操作与命令执行在远程 Linux 沙箱而非你的机器上进行时,使用本包。它是 E2B 家族的基础:挂载文件系统与子进程包之后,所有这些工作都会共享同一个远程工作目录与进程环境。
|
|
29
|
+
|
|
30
|
+
### 何时选择
|
|
31
|
+
|
|
32
|
+
当工作应与宿主机器隔离时——例如你希望 agent 的文件编辑与命令运行发生在某个可丢弃的环境中——选择 E2B 家族。当在宿主上运行没有问题的时候,选择本地的文件系统与子进程包。本包对模型不可见,也不增加任何请求成本。
|
|
33
|
+
|
|
34
|
+
### 最小配置
|
|
35
|
+
|
|
36
|
+
三个设置很重要:API 密钥(或 `E2B_API_KEY` 环境变量)、绝对远程工作目录与沙箱生命周期。密钥错误、相对工作目录或无效生命周期都会在任何远程工作开始前拒绝启动。
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@x1a0f3n9/dsh-e2b'
|
|
40
|
+
config:
|
|
41
|
+
apiKey: <E2B API key>
|
|
42
|
+
cwd: /home/user/workspace
|
|
43
|
+
timeoutMs: 300000
|
|
44
|
+
|
|
45
|
+
- name: '@x1a0f3n9/dsh-subprocess-e2b'
|
|
46
|
+
- name: '@x1a0f3n9/dsh-fs-e2b'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| 字段 | 默认值 | 含义 |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `apiKey` | `E2B_API_KEY` | 宿主 SDK 连接的 API 密钥;绝不会安装进沙箱 |
|
|
52
|
+
| `cwd` | `/home/user/workspace` | 家族共享的远程工作目录;必须是绝对 POSIX 路径 |
|
|
53
|
+
| `timeoutMs` | `300,000` | 沙箱生命周期(毫秒);到期后沙箱被删除 |
|
|
54
|
+
|
|
55
|
+
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-e2b)完整列出了每个受支持字段及其 JSDoc,是这些信息的真源。
|
|
56
|
+
|
|
57
|
+
### 你能得到什么
|
|
58
|
+
|
|
59
|
+
挂载本包后,文件读写、shell 命令与终端都会在沙箱的工作目录内运行,因此 agent 看到的是一个一致的远程世界:它用文件功能写入的内容,正是它的命令能够读取的内容,反之亦然。远程工作目录若不存在,会自动创建。
|
|
60
|
+
|
|
61
|
+
### 沙箱的启动与停止
|
|
62
|
+
|
|
63
|
+
加载插件会在后台启动沙箱;文件系统与子进程功能在其就绪后即可使用。沙箱存活时间为配置的生命周期(默认五分钟),除非应用先停止——两种情况下沙箱都会被删除,因此请在此之前保存你仍需要的内容。如果运行期间沙箱消失(到期或被别处删除),家族会将其视为正常终止,而不是错误。
|
|
64
|
+
|
|
65
|
+
-----
|
|
66
|
+
|
|
67
|
+
<a id="understand-the-implementation"></a>
|
|
68
|
+
## 理解实现
|
|
69
|
+
|
|
70
|
+
<details>
|
|
71
|
+
<summary>实现细节——点击展开</summary>
|
|
72
|
+
|
|
73
|
+
本节解释所有者背后的设计决策,并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
|
74
|
+
|
|
75
|
+
### 设计理念
|
|
76
|
+
|
|
77
|
+
- **一个沙箱,一个句柄。** 所有适配器都等待同一个 `getSandbox()` promise,因此文件系统与进程操作共享同一个远程 Linux 世界。
|
|
78
|
+
- **构造即安全。** 沙箱以 `secure: true` 和 `lifecycle: { onTimeout: 'kill' }` 创建,因此超时必定删除它。
|
|
79
|
+
- **隔离的控制 shell。** `e2bControlEnvs()` 为每个内部命令 shell 提供全新随机生成的 `HOME`,`quoteE2BShellArg()` 则通过 SDK 不可避免的 `/bin/bash -l -c` 层保留不透明参数。
|
|
80
|
+
|
|
81
|
+
### 源码索引
|
|
82
|
+
|
|
83
|
+
| 文件 | 职责 |
|
|
84
|
+
|---|---|
|
|
85
|
+
| [`src/index.ts`](src/index.ts) | 插件入口:`E2BRuntime` 服务、`Config` schema、校验、沙箱创建与拆除 |
|
|
86
|
+
| — | 不发布运行时不变式伴生入口;沙箱创建与拆除只有一个 SDK promise,没有可交叉核对的独立事件或可变数据关系。 |
|
|
87
|
+
|
|
88
|
+
### 生命周期
|
|
89
|
+
|
|
90
|
+
`open()` 创建沙箱、准备 `cwd` 与私有运行时根目录、拒绝非目录或符号链接的运行时根目录,并执行 `chmod 700`。dispose(资源释放)会阻止新的句柄获取、等待初始化完成并删除沙箱,把 `SandboxNotFoundError` 视为完全停稳。`getSandbox()` 在等待就绪后重新检查已释放标志,因此与就绪发生竞态的资源释放仍会拒绝获取句柄;预先发起的连接即使失败也会保持可观察状态,但不会导致插件加载失败;`getSandbox()` 会将该失败暴露给调用方。
|
|
91
|
+
|
|
92
|
+
### 初始化失败处理
|
|
93
|
+
|
|
94
|
+
任何目录初始化失败都会尝试删除一次并保留原始错误;回滚失败由 E2B 配置的沙箱超时约束(见开发备注)。提供方插件必须在该所有者之后加载、并在其之前 dispose,因为每个适配器都等待同一个句柄。
|
|
95
|
+
|
|
96
|
+
</details>
|
|
97
|
+
|
|
98
|
+
-----
|
|
99
|
+
|
|
100
|
+
<a id="further-exploration"></a>
|
|
101
|
+
## 进一步探索
|
|
102
|
+
|
|
103
|
+
当包级约定不够用时阅读以下页面。它们从家族组合逐步进入子进程 seam 表面,以及远程执行世界背后的决策证据。
|
|
104
|
+
|
|
105
|
+
- [E2B 提供方家族地图](../README.zh.md)——三个包与可选组合。
|
|
106
|
+
- [子进程子系统](../../../docs/subsystems/subprocess.zh.md)——子进程 seam 约定与生成的 Cordis 表面,包括 `ctx.e2b`。
|
|
107
|
+
- [可移植执行世界决策](../../../.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md)——消费方为何委托给 `ctx.fs` 与 `ctx.subprocess`,以及留在宿主中的内容。
|
|
108
|
+
- [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-e2b)——每个受支持配置字段及其源声明。
|
|
109
|
+
|
|
110
|
+
-----
|
|
111
|
+
|
|
112
|
+
<a id="model-experience"></a>
|
|
113
|
+
## 模型体验
|
|
114
|
+
|
|
115
|
+
无。本共享远程运行时所有者不注册任何模型上下文;提供方适配器与消费方拥有所有渲染效果。
|
|
116
|
+
|
|
117
|
+
#### KV Cache 影响
|
|
118
|
+
|
|
119
|
+
不会直接失效:所有者不贡献任何请求 token,也从不改变请求前缀,因此提供方缓存复用不受影响。
|
|
120
|
+
|
|
121
|
+
## 已知限制与延期工作
|
|
122
|
+
|
|
123
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
这些限制说明 E2B 家族何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
|
|
127
|
+
|
|
128
|
+
- **不是完整的 harness 运行时**:Cordis 服务、agent/会话状态、会话日志、LLM(大语言模型)请求、skill(技能)和 SDK 侧缓冲仍留在宿主进程中。
|
|
129
|
+
- **沙箱状态是短暂的**:dispose 与超时都会删除沙箱;重新连接、pause/leave 保留、模板、卷和快照均不在本 POC 范围内。
|
|
130
|
+
- **没有配置部署平台**:网络策略、宿主工作区同步与沙箱发现均不在本 POC 范围内。
|
|
131
|
+
- **`cwd` 是解析约定,而不是包含边界**:适配器与命令可以访问沙箱中的其他路径;E2B 网络访问也继续采用基础镜像的策略。
|
|
132
|
+
|
|
133
|
+
<a id="dev-note"></a>
|
|
134
|
+
### 开发备注
|
|
135
|
+
|
|
136
|
+
<details>
|
|
137
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
138
|
+
|
|
139
|
+
本开发备注是维护者的工作上下文:开放问题与尚未决定的探索方向。它明确不具权威性——已交付的行为、限制与既定理由以上文和包代码为准。
|
|
140
|
+
|
|
141
|
+
#### 开放:沙箱初始化回滚
|
|
142
|
+
|
|
143
|
+
`open()` 的失败路径只会尝试删除一次,并保留原始初始化失败。除非真实的双重失败超出 E2B 配置的沙箱超时,否则重试状态保持延后(TODO(e2b-setup-rollback))。
|
|
144
|
+
|
|
145
|
+
</details>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { posix } from "node:path";
|
|
3
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
4
|
+
import z from "@deepseek-ai/schemastery";
|
|
5
|
+
import { CommandExitError, FileNotFoundError, FileType, FileType as FileType$1, Sandbox, Sandbox as Sandbox$1, SandboxNotFoundError, SandboxNotFoundError as SandboxNotFoundError$1 } from "e2b";
|
|
6
|
+
import { proxyRouteFor } from "@x1a0f3n9/dsh-http-proxy";
|
|
7
|
+
//#region lib/types/api-url.js
|
|
8
|
+
/**
|
|
9
|
+
* The E2B control-plane URL, derived the way the SDK derives it.
|
|
10
|
+
* @module @x1a0f3n9/dsh-e2b/src/api-url.ts
|
|
11
|
+
*/
|
|
12
|
+
/** The SDK's own default control-plane domain; `E2B_DOMAIN` overrides it there and here alike. */
|
|
13
|
+
const E2B_DEFAULT_DOMAIN = "e2b.app";
|
|
14
|
+
/** The debug control plane the SDK substitutes, on loopback and plain HTTP. */
|
|
15
|
+
const E2B_DEBUG_API_URL = "http://localhost:3000";
|
|
16
|
+
/**
|
|
17
|
+
* The control-plane URL the SDK will actually call, derived the way the SDK derives it: an explicit
|
|
18
|
+
* `E2B_API_URL` first, then the debug substitute, then the domain default. Choosing a proxy for
|
|
19
|
+
* anything else would pick the wrong scheme's proxy, ignore a bypass entry naming the real host, and
|
|
20
|
+
* — for the loopback debug plane — hand a proxy the control-plane traffic and its API key.
|
|
21
|
+
*
|
|
22
|
+
* @param env - the process environment to read; overridable so tests need no ambient state.
|
|
23
|
+
* @returns the absolute control-plane URL.
|
|
24
|
+
*/
|
|
25
|
+
function e2bApiUrl(env = process.env) {
|
|
26
|
+
const explicit = env.E2B_API_URL;
|
|
27
|
+
if (explicit !== void 0 && explicit !== "") return explicit;
|
|
28
|
+
if ((env.E2B_DEBUG ?? "false").toLowerCase() === "true") return E2B_DEBUG_API_URL;
|
|
29
|
+
return `https://api.${env.E2B_DOMAIN ?? E2B_DEFAULT_DOMAIN}`;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region lib/types/index.js
|
|
33
|
+
/**
|
|
34
|
+
* Shared ownership of one E2B sandbox. Capability adapters await the same SDK
|
|
35
|
+
* handle, so filesystem and process operations inhabit one remote Linux world.
|
|
36
|
+
* @module @x1a0f3n9/dsh-e2b
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Quote one opaque argument for the SDK's unavoidable `/bin/bash -l -c` layer.
|
|
40
|
+
* @param value - Exact argument value to preserve.
|
|
41
|
+
* @returns A single shell word with no interpolation.
|
|
42
|
+
*/
|
|
43
|
+
function quoteE2BShellArg(value) {
|
|
44
|
+
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Isolate E2B's hard-coded login shell behind a fresh randomized home path.
|
|
48
|
+
* @param overrides - Additional environment entries for the internal command.
|
|
49
|
+
* @returns A fresh mutable map that the E2B SDK may extend.
|
|
50
|
+
*/
|
|
51
|
+
function e2bControlEnvs(overrides = {}) {
|
|
52
|
+
return {
|
|
53
|
+
...overrides,
|
|
54
|
+
HOME: `/.dsh-e2b-control-${randomUUID()}`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Creates one lazily consumable E2B SDK handle and deletes the sandbox at
|
|
59
|
+
* timeout or disposal. Creation begins at plugin construction; adapters await
|
|
60
|
+
* {@link getSandbox} before their first operation.
|
|
61
|
+
*/
|
|
62
|
+
var E2BRuntime = class extends Service {
|
|
63
|
+
static Config = z.object({
|
|
64
|
+
apiKey: z.string(),
|
|
65
|
+
cwd: z.string().default("/home/user/workspace"),
|
|
66
|
+
timeoutMs: z.number().default(3e5)
|
|
67
|
+
});
|
|
68
|
+
/** Validated remote working directory shared by provider adapters. */
|
|
69
|
+
cwd;
|
|
70
|
+
/** Remote directory reserved for adapter-owned process and terminal state. */
|
|
71
|
+
runtimeRoot;
|
|
72
|
+
config;
|
|
73
|
+
ready;
|
|
74
|
+
disposed = false;
|
|
75
|
+
constructor(ctx, config) {
|
|
76
|
+
super(ctx, "e2b");
|
|
77
|
+
const resolved = config;
|
|
78
|
+
const apiKey = config.apiKey ?? process.env.E2B_API_KEY;
|
|
79
|
+
this.config = {
|
|
80
|
+
apiKey: apiKey ?? "",
|
|
81
|
+
cwd: resolved.cwd,
|
|
82
|
+
timeoutMs: resolved.timeoutMs
|
|
83
|
+
};
|
|
84
|
+
this.validate();
|
|
85
|
+
this.cwd = this.config.cwd;
|
|
86
|
+
this.runtimeRoot = posix.join(this.cwd, ".dsh-e2b");
|
|
87
|
+
this.ready = this.open();
|
|
88
|
+
this.ready.catch(() => {});
|
|
89
|
+
ctx.effect(() => async () => {
|
|
90
|
+
this.disposed = true;
|
|
91
|
+
let sandbox;
|
|
92
|
+
try {
|
|
93
|
+
sandbox = await this.ready;
|
|
94
|
+
} catch (_sandboxSetupFailure) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await sandbox.kill();
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (!(error instanceof SandboxNotFoundError$1)) throw error;
|
|
101
|
+
}
|
|
102
|
+
}, "e2b sandbox teardown");
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Return the shared live SDK handle.
|
|
106
|
+
* @returns the created sandbox after the configured cwd exists.
|
|
107
|
+
* @throws when E2B rejects creation or the service is disposing.
|
|
108
|
+
*/
|
|
109
|
+
async getSandbox() {
|
|
110
|
+
if (this.disposed) throw new Error("E2B sandbox service is disposing");
|
|
111
|
+
const sandbox = await this.ready;
|
|
112
|
+
if (this.disposed) throw new Error("E2B sandbox service is disposing");
|
|
113
|
+
return sandbox;
|
|
114
|
+
}
|
|
115
|
+
validate() {
|
|
116
|
+
if (this.config.apiKey.length === 0) throw new Error("dsh-e2b: configure apiKey or set E2B_API_KEY");
|
|
117
|
+
if (!posix.isAbsolute(this.config.cwd)) throw new Error(`dsh-e2b: cwd must be an absolute Linux path: ${this.config.cwd}`);
|
|
118
|
+
if (!Number.isFinite(this.config.timeoutMs) || this.config.timeoutMs <= 0) throw new Error("dsh-e2b: timeoutMs must be a positive finite number");
|
|
119
|
+
}
|
|
120
|
+
async open() {
|
|
121
|
+
const route = proxyRouteFor(new URL(e2bApiUrl()));
|
|
122
|
+
const sandbox = await Sandbox$1.create({
|
|
123
|
+
apiKey: this.config.apiKey,
|
|
124
|
+
timeoutMs: this.config.timeoutMs,
|
|
125
|
+
secure: true,
|
|
126
|
+
lifecycle: { onTimeout: "kill" },
|
|
127
|
+
...route.proxied ? { proxy: route.proxy } : {}
|
|
128
|
+
});
|
|
129
|
+
try {
|
|
130
|
+
await sandbox.files.makeDir(this.cwd);
|
|
131
|
+
await sandbox.files.makeDir(this.runtimeRoot);
|
|
132
|
+
const runtimeRoot = await sandbox.files.getInfo(this.runtimeRoot);
|
|
133
|
+
if (runtimeRoot.type !== FileType$1.DIR || runtimeRoot.symlinkTarget !== void 0) throw new Error(`dsh-e2b: runtime root must be a real directory: ${this.runtimeRoot}`);
|
|
134
|
+
await sandbox.commands.run(`chmod 700 -- ${quoteE2BShellArg(this.runtimeRoot)}`, { envs: e2bControlEnvs() });
|
|
135
|
+
return sandbox;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
try {
|
|
138
|
+
await sandbox.kill();
|
|
139
|
+
} catch (_sandboxSetupRollbackFailure) {}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
//#endregion
|
|
145
|
+
export { CommandExitError, E2BRuntime, E2BRuntime as default, FileNotFoundError, FileType, Sandbox, SandboxNotFoundError, e2bControlEnvs, quoteE2BShellArg };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The E2B control-plane URL, derived the way the SDK derives it.
|
|
3
|
+
* @module @x1a0f3n9/dsh-e2b/src/api-url.ts
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The control-plane URL the SDK will actually call, derived the way the SDK derives it: an explicit
|
|
7
|
+
* `E2B_API_URL` first, then the debug substitute, then the domain default. Choosing a proxy for
|
|
8
|
+
* anything else would pick the wrong scheme's proxy, ignore a bypass entry naming the real host, and
|
|
9
|
+
* — for the loopback debug plane — hand a proxy the control-plane traffic and its API key.
|
|
10
|
+
*
|
|
11
|
+
* @param env - the process environment to read; overridable so tests need no ambient state.
|
|
12
|
+
* @returns the absolute control-plane URL.
|
|
13
|
+
*/
|
|
14
|
+
export declare function e2bApiUrl(env?: NodeJS.ProcessEnv): string;
|
|
15
|
+
//# sourceMappingURL=api-url.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ownership of one E2B sandbox. Capability adapters await the same SDK
|
|
3
|
+
* handle, so filesystem and process operations inhabit one remote Linux world.
|
|
4
|
+
* @module @x1a0f3n9/dsh-e2b
|
|
5
|
+
*/
|
|
6
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
7
|
+
import z from '@deepseek-ai/schemastery';
|
|
8
|
+
import { Sandbox } from 'e2b';
|
|
9
|
+
export { CommandExitError, FileNotFoundError, FileType, Sandbox, SandboxNotFoundError, } from 'e2b';
|
|
10
|
+
export type { CommandHandle, CommandResult, EntryInfo } from 'e2b';
|
|
11
|
+
/**
|
|
12
|
+
* Quote one opaque argument for the SDK's unavoidable `/bin/bash -l -c` layer.
|
|
13
|
+
* @param value - Exact argument value to preserve.
|
|
14
|
+
* @returns A single shell word with no interpolation.
|
|
15
|
+
*/
|
|
16
|
+
export declare function quoteE2BShellArg(value: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Isolate E2B's hard-coded login shell behind a fresh randomized home path.
|
|
19
|
+
* @param overrides - Additional environment entries for the internal command.
|
|
20
|
+
* @returns A fresh mutable map that the E2B SDK may extend.
|
|
21
|
+
*/
|
|
22
|
+
export declare function e2bControlEnvs(overrides?: Readonly<Record<string, string>>): Record<string, string>;
|
|
23
|
+
/** Configuration for the shared E2B sandbox owner. */
|
|
24
|
+
export interface Config {
|
|
25
|
+
/** API key; omission reads `E2B_API_KEY`. It is never forwarded into the sandbox. */
|
|
26
|
+
apiKey?: string;
|
|
27
|
+
/** Shared remote working directory, created before adapters receive the sandbox. */
|
|
28
|
+
cwd?: string;
|
|
29
|
+
/** E2B sandbox lifetime in milliseconds; expiry always deletes the sandbox. */
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
}
|
|
32
|
+
declare module '@deepseek-ai/cordis' {
|
|
33
|
+
interface Context {
|
|
34
|
+
e2b: E2BRuntime;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates one lazily consumable E2B SDK handle and deletes the sandbox at
|
|
39
|
+
* timeout or disposal. Creation begins at plugin construction; adapters await
|
|
40
|
+
* {@link getSandbox} before their first operation.
|
|
41
|
+
*/
|
|
42
|
+
export declare class E2BRuntime extends Service {
|
|
43
|
+
static Config: z<Config>;
|
|
44
|
+
/** Validated remote working directory shared by provider adapters. */
|
|
45
|
+
readonly cwd: string;
|
|
46
|
+
/** Remote directory reserved for adapter-owned process and terminal state. */
|
|
47
|
+
readonly runtimeRoot: string;
|
|
48
|
+
private readonly config;
|
|
49
|
+
private readonly ready;
|
|
50
|
+
private disposed;
|
|
51
|
+
constructor(ctx: Context, config: Config);
|
|
52
|
+
/**
|
|
53
|
+
* Return the shared live SDK handle.
|
|
54
|
+
* @returns the created sandbox after the configured cwd exists.
|
|
55
|
+
* @throws when E2B rejects creation or the service is disposing.
|
|
56
|
+
*/
|
|
57
|
+
getSandbox(): Promise<Sandbox>;
|
|
58
|
+
private validate;
|
|
59
|
+
private open;
|
|
60
|
+
}
|
|
61
|
+
export default E2BRuntime;
|
|
62
|
+
//# sourceMappingURL=index.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@x1a0f3n9/dsh-e2b",
|
|
3
|
+
"description": "Shared E2B sandbox lifecycle for DeepSeek Harness provider adapters",
|
|
4
|
+
"version": "0.1.5-rc.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/e2b/e2b"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./src/*": "./src/*",
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"lib/index.js",
|
|
26
|
+
"lib/types/**/*.d.ts"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
31
|
+
"@x1a0f3n9/dsh-http-proxy": "^0.1.5-rc.3"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"e2b": "2.29.1",
|
|
35
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
39
|
+
"@x1a0f3n9/dsh-agent": "^0.1.5-rc.3",
|
|
40
|
+
"@x1a0f3n9/dsh-agent-loop-testkit": "^0.1.5-rc.3",
|
|
41
|
+
"@x1a0f3n9/dsh-app-boot": "^0.1.5-rc.3",
|
|
42
|
+
"@x1a0f3n9/dsh-bash-local": "^0.1.5-rc.3",
|
|
43
|
+
"@x1a0f3n9/dsh-fs-e2b": "^0.1.5-rc.3",
|
|
44
|
+
"@x1a0f3n9/dsh-loader-smoke": "^0.1.5-rc.3",
|
|
45
|
+
"@x1a0f3n9/dsh-lsp": "^0.1.5-rc.3",
|
|
46
|
+
"@x1a0f3n9/dsh-lsp-stdio": "^0.1.5-rc.3",
|
|
47
|
+
"@x1a0f3n9/dsh-sandbox-policy": "^0.1.5-rc.3",
|
|
48
|
+
"@x1a0f3n9/dsh-session": "^0.1.5-rc.3",
|
|
49
|
+
"@x1a0f3n9/dsh-session-projection": "^0.1.5-rc.3",
|
|
50
|
+
"@x1a0f3n9/dsh-subprocess-e2b": "^0.1.5-rc.3",
|
|
51
|
+
"@x1a0f3n9/dsh-terminal": "^0.1.5-rc.3",
|
|
52
|
+
"@x1a0f3n9/dsh-terminal-bash": "^0.1.5-rc.3",
|
|
53
|
+
"@x1a0f3n9/dsh-http-proxy": "^0.1.5-rc.3",
|
|
54
|
+
"@x1a0f3n9/dsh-launch-environment": "^0.1.5-rc.3"
|
|
55
|
+
}
|
|
56
|
+
}
|