@nebutra/code-execution 0.1.2 → 0.1.4
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/CHANGELOG.md +11 -0
- package/package.json +20 -9
- package/.nebutra/debug/code-execution.jsonl +0 -30
- package/.turbo/turbo-build.log +0 -22
- package/.turbo/turbo-test.log +0 -14
- package/.turbo/turbo-typecheck.log +0 -4
- package/examples/edit-by-diff.ts +0 -17
- package/examples/policy.ts +0 -10
- package/examples/shell.ts +0 -11
- package/src/cli.ts +0 -44
- package/src/index.test.ts +0 -110
- package/src/index.ts +0 -436
- package/tsconfig.json +0 -12
- package/tsup.config.ts +0 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @nebutra/code-execution
|
|
2
2
|
|
|
3
|
+
## 0.1.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies []:
|
|
8
|
+
- @nebutra/capability-kit@2.0.0
|
|
9
|
+
- @nebutra/errors@2.0.0
|
|
10
|
+
- @nebutra/event-log@2.0.0
|
|
11
|
+
- @nebutra/execution-policy@2.0.0
|
|
12
|
+
- @nebutra/sandbox-runtime@2.0.0
|
|
13
|
+
|
|
3
14
|
## 0.1.2
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebutra/code-execution",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Action and observation execution layer backed by sandbox-runtime",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"nebutra": {
|
|
9
|
+
"graph": "labs",
|
|
9
10
|
"status": "wip",
|
|
10
11
|
"productionReady": false,
|
|
11
12
|
"surface": "execution-capability",
|
|
@@ -22,17 +23,21 @@
|
|
|
22
23
|
"category": "ai",
|
|
23
24
|
"summary": "Shell, read, edit-by-diff, policy, replay, doctor, and debug tools"
|
|
24
25
|
},
|
|
25
|
-
"main": "./
|
|
26
|
-
"types": "./
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
27
28
|
"exports": {
|
|
28
|
-
".":
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
}
|
|
29
34
|
},
|
|
30
35
|
"dependencies": {
|
|
31
|
-
"@nebutra/capability-kit": "0.
|
|
32
|
-
"@nebutra/
|
|
33
|
-
"@nebutra/
|
|
34
|
-
"@nebutra/
|
|
35
|
-
"@nebutra/
|
|
36
|
+
"@nebutra/capability-kit": "2.0.0",
|
|
37
|
+
"@nebutra/errors": "2.0.0",
|
|
38
|
+
"@nebutra/event-log": "2.0.0",
|
|
39
|
+
"@nebutra/execution-policy": "2.0.0",
|
|
40
|
+
"@nebutra/sandbox-runtime": "2.0.0"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|
|
38
43
|
"@types/node": "^25.9.1",
|
|
@@ -52,6 +57,12 @@
|
|
|
52
57
|
"publishConfig": {
|
|
53
58
|
"access": "public"
|
|
54
59
|
},
|
|
60
|
+
"files": [
|
|
61
|
+
"dist",
|
|
62
|
+
"README.md",
|
|
63
|
+
"LICENSE",
|
|
64
|
+
"CHANGELOG.md"
|
|
65
|
+
],
|
|
55
66
|
"scripts": {
|
|
56
67
|
"build": "tsup",
|
|
57
68
|
"test": "vitest run",
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{"at":"2026-05-17T18:26:14.046Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"pnpm test","cwd":"/workspace","timeoutS":60,"tenantId":"tenant_a"},"observation":{"type":"shell_output","actionId":"shell_mpa3w97h","stdout":"ran pnpm test for tenant_a","stderr":"","exitCode":0,"durationMs":0,"executedOn":"test_sandbox"}}
|
|
2
|
-
{"at":"2026-05-17T18:26:14.048Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"rm -rf build","cwd":"/workspace","tenantId":"tenant_a"},"observation":{"type":"error","actionId":"shell_mpa3w97j","kind":"ApprovalRequired","message":"Command requires approval: destructive recursive removal","suggestion":"Ask the user for explicit approval, then retry the action with approved: true."}}
|
|
3
|
-
{"at":"2026-05-17T18:26:14.049Z","type":"action","tenantId":"tenant_a","action":{"type":"edit","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","tenantId":"tenant_a"},"observation":{"type":"edit_applied","actionId":"edit_mpa3w97k","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","conflicts":[]}}
|
|
4
|
-
{"at":"2026-05-17T18:30:10.786Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"pnpm test","cwd":"/workspace","timeoutS":60,"tenantId":"tenant_a"},"observation":{"type":"shell_output","actionId":"shell_mpa41bvm","stdout":"ran pnpm test for tenant_a","stderr":"","exitCode":0,"durationMs":0,"executedOn":"test_sandbox"}}
|
|
5
|
-
{"at":"2026-05-17T18:30:10.788Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"rm -rf build","cwd":"/workspace","tenantId":"tenant_a"},"observation":{"type":"error","actionId":"shell_mpa41bvo","kind":"ApprovalRequired","message":"Command requires approval: destructive recursive removal","suggestion":"Ask the user for explicit approval, then retry the action with approved: true."}}
|
|
6
|
-
{"at":"2026-05-17T18:30:10.789Z","type":"action","tenantId":"tenant_a","action":{"type":"edit","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","tenantId":"tenant_a"},"observation":{"type":"edit_applied","actionId":"edit_mpa41bvp","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","conflicts":[]}}
|
|
7
|
-
{"at":"2026-05-17T18:36:14.801Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"pnpm test","cwd":"/workspace","timeoutS":60,"tenantId":"tenant_a"},"observation":{"type":"shell_output","actionId":"shell_mpa494r4","stdout":"ran pnpm test for tenant_a","stderr":"","exitCode":0,"durationMs":1,"executedOn":"test_sandbox"}}
|
|
8
|
-
{"at":"2026-05-17T18:36:14.803Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"rm -rf build","cwd":"/workspace","tenantId":"tenant_a"},"observation":{"type":"error","actionId":"shell_mpa494r7","kind":"ApprovalRequired","message":"Command requires approval: destructive recursive removal","suggestion":"Ask the user for explicit approval, then retry the action with approved: true."}}
|
|
9
|
-
{"at":"2026-05-17T18:36:14.805Z","type":"action","tenantId":"tenant_a","action":{"type":"edit","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","tenantId":"tenant_a"},"observation":{"type":"edit_applied","actionId":"edit_mpa494r8","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","conflicts":[]}}
|
|
10
|
-
{"at":"2026-05-18T05:20:19.961Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"pnpm test","cwd":"/workspace","timeoutS":60,"tenantId":"tenant_a"},"observation":{"type":"shell_output","actionId":"shell_mpar9fjs","stdout":"ran pnpm test for tenant_a","stderr":"","exitCode":0,"durationMs":1,"executedOn":"test_sandbox"}}
|
|
11
|
-
{"at":"2026-05-18T05:20:19.966Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"rm -rf build","cwd":"/workspace","tenantId":"tenant_a"},"observation":{"type":"error","actionId":"shell_mpar9fjy","kind":"ApprovalRequired","message":"Command requires approval: destructive recursive removal","suggestion":"Ask the user for explicit approval, then retry the action with approved: true."}}
|
|
12
|
-
{"at":"2026-05-18T05:20:19.970Z","type":"action","tenantId":"tenant_a","action":{"type":"edit","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","tenantId":"tenant_a"},"observation":{"type":"edit_applied","actionId":"edit_mpar9fk0","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","conflicts":[]}}
|
|
13
|
-
{"at":"2026-05-18T05:41:12.696Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"pnpm test","cwd":"/workspace","timeoutS":60,"tenantId":"tenant_a"},"observation":{"type":"shell_output","actionId":"shell_mpas0a5z","stdout":"ran pnpm test for tenant_a","stderr":"","exitCode":0,"durationMs":1,"executedOn":"test_sandbox"}}
|
|
14
|
-
{"at":"2026-05-18T05:41:12.701Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"rm -rf build","cwd":"/workspace","tenantId":"tenant_a"},"observation":{"type":"error","actionId":"shell_mpas0a64","kind":"ApprovalRequired","message":"Command requires approval: destructive recursive removal","suggestion":"Ask the user for explicit approval, then retry the action with approved: true."}}
|
|
15
|
-
{"at":"2026-05-18T05:41:12.704Z","type":"action","tenantId":"tenant_a","action":{"type":"edit","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","tenantId":"tenant_a"},"observation":{"type":"edit_applied","actionId":"edit_mpas0a67","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","conflicts":[]}}
|
|
16
|
-
{"at":"2026-05-18T05:42:00.492Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"pnpm test","cwd":"/workspace","timeoutS":60,"tenantId":"tenant_a"},"observation":{"type":"shell_output","actionId":"shell_mpas1b1m","stdout":"ran pnpm test for tenant_a","stderr":"","exitCode":0,"durationMs":1,"executedOn":"test_sandbox"}}
|
|
17
|
-
{"at":"2026-05-18T05:42:00.503Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"rm -rf build","cwd":"/workspace","tenantId":"tenant_a"},"observation":{"type":"error","actionId":"shell_mpas1b1y","kind":"ApprovalRequired","message":"Command requires approval: destructive recursive removal","suggestion":"Ask the user for explicit approval, then retry the action with approved: true."}}
|
|
18
|
-
{"at":"2026-05-18T05:42:00.511Z","type":"action","tenantId":"tenant_a","action":{"type":"edit","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","tenantId":"tenant_a"},"observation":{"type":"edit_applied","actionId":"edit_mpas1b26","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","conflicts":[]}}
|
|
19
|
-
{"at":"2026-05-19T05:21:57.551Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"pnpm test","cwd":"/workspace","timeoutS":60,"tenantId":"tenant_a"},"observation":{"type":"shell_output","actionId":"shell_mpc6rdim","stdout":"ran pnpm test for tenant_a","stderr":"","exitCode":0,"durationMs":1,"executedOn":"test_sandbox"}}
|
|
20
|
-
{"at":"2026-05-19T05:21:57.554Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"rm -rf build","cwd":"/workspace","tenantId":"tenant_a"},"observation":{"type":"error","actionId":"shell_mpc6rdiq","kind":"ApprovalRequired","message":"Command requires approval: destructive recursive removal","suggestion":"Ask the user for explicit approval, then retry the action with approved: true."}}
|
|
21
|
-
{"at":"2026-05-19T05:21:57.556Z","type":"action","tenantId":"tenant_a","action":{"type":"edit","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","tenantId":"tenant_a"},"observation":{"type":"edit_applied","actionId":"edit_mpc6rdir","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","conflicts":[]}}
|
|
22
|
-
{"at":"2026-05-19T05:25:36.159Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"pnpm test","cwd":"/workspace","timeoutS":60,"tenantId":"tenant_a"},"observation":{"type":"shell_output","actionId":"shell_mpc6w271","stdout":"ran pnpm test for tenant_a","stderr":"","exitCode":0,"durationMs":1,"executedOn":"test_sandbox"}}
|
|
23
|
-
{"at":"2026-05-19T05:25:36.163Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"rm -rf build","cwd":"/workspace","tenantId":"tenant_a"},"observation":{"type":"error","actionId":"shell_mpc6w276","kind":"ApprovalRequired","message":"Command requires approval: destructive recursive removal","suggestion":"Ask the user for explicit approval, then retry the action with approved: true."}}
|
|
24
|
-
{"at":"2026-05-19T05:25:36.169Z","type":"action","tenantId":"tenant_a","action":{"type":"edit","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","tenantId":"tenant_a"},"observation":{"type":"edit_applied","actionId":"edit_mpc6w27c","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","conflicts":[]}}
|
|
25
|
-
{"at":"2026-05-30T21:07:14.340Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"pnpm test","cwd":"/workspace","timeoutS":60,"tenantId":"tenant_a"},"observation":{"type":"shell_output","actionId":"shell_mpsuddsz","stdout":"ran pnpm test for tenant_a","stderr":"","exitCode":0,"durationMs":1,"executedOn":"test_sandbox"}}
|
|
26
|
-
{"at":"2026-05-30T21:07:14.344Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"rm -rf build","cwd":"/workspace","tenantId":"tenant_a"},"observation":{"type":"error","actionId":"shell_mpsuddt4","kind":"ApprovalRequired","message":"Command requires approval: destructive recursive removal","suggestion":"Ask the user for explicit approval, then retry the action with approved: true."}}
|
|
27
|
-
{"at":"2026-05-30T21:07:14.354Z","type":"action","tenantId":"tenant_a","action":{"type":"edit","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","tenantId":"tenant_a"},"observation":{"type":"edit_applied","actionId":"edit_mpsuddtc","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","conflicts":[]}}
|
|
28
|
-
{"at":"2026-07-01T01:40:40.888Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"pnpm test","cwd":"/workspace","timeoutS":60,"tenantId":"tenant_a"},"observation":{"type":"shell_output","actionId":"shell_mr1esfue","stdout":"ran pnpm test for tenant_a","stderr":"","exitCode":0,"durationMs":1,"executedOn":"test_sandbox"}}
|
|
29
|
-
{"at":"2026-07-01T01:40:40.939Z","type":"action","tenantId":"tenant_a","action":{"type":"shell","cmd":"rm -rf build","cwd":"/workspace","tenantId":"tenant_a"},"observation":{"type":"error","actionId":"shell_mr1esfum","kind":"ApprovalRequired","message":"Command requires approval: destructive recursive removal","suggestion":"Ask the user for explicit approval, then retry the action with approved: true."}}
|
|
30
|
-
{"at":"2026-07-01T01:40:40.955Z","type":"action","tenantId":"tenant_a","action":{"type":"edit","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","tenantId":"tenant_a"},"observation":{"type":"edit_applied","actionId":"edit_mr1esfw7","path":"main.ts","diff":"--- a/main.ts\n+++ b/main.ts\n@@ -1,2 +1,2 @@\n-const value = 1;\n+const value = 2;\n console.log(value);\n","conflicts":[]}}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @nebutra/code-execution@0.1.2 build /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/code-execution
|
|
3
|
-
> tsup
|
|
4
|
-
|
|
5
|
-
CLI Building entry: src/cli.ts, src/index.ts
|
|
6
|
-
CLI Using tsconfig: tsconfig.json
|
|
7
|
-
CLI tsup v8.5.1
|
|
8
|
-
CLI Using tsup config: /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/code-execution/tsup.config.ts
|
|
9
|
-
CLI Target: es2022
|
|
10
|
-
CLI Cleaning output folder
|
|
11
|
-
ESM Build start
|
|
12
|
-
ESM dist/index.js 232.00 B
|
|
13
|
-
ESM dist/cli.js 1.59 KB
|
|
14
|
-
ESM dist/chunk-LLPO5OG2.js 9.10 KB
|
|
15
|
-
ESM dist/index.js.map 71.00 B
|
|
16
|
-
ESM dist/cli.js.map 2.87 KB
|
|
17
|
-
ESM dist/chunk-LLPO5OG2.js.map 19.58 KB
|
|
18
|
-
ESM ⚡️ Build success in 40ms
|
|
19
|
-
DTS Build start
|
|
20
|
-
DTS ⚡️ Build success in 2309ms
|
|
21
|
-
DTS dist/cli.d.ts 13.00 B
|
|
22
|
-
DTS dist/index.d.ts 3.79 KB
|
package/.turbo/turbo-test.log
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @nebutra/code-execution@0.1.1 test /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/code-execution
|
|
3
|
-
> vitest run
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
RUN v4.1.4 /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/code-execution
|
|
7
|
-
|
|
8
|
-
✓ src/index.test.ts (5 tests) 234ms
|
|
9
|
-
|
|
10
|
-
Test Files 1 passed (1)
|
|
11
|
-
Tests 5 passed (5)
|
|
12
|
-
Start at 09:40:38
|
|
13
|
-
Duration 2.24s (transform 590ms, setup 0ms, import 734ms, tests 234ms, environment 0ms)
|
|
14
|
-
|
package/examples/edit-by-diff.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { CodeExecutor } from "@nebutra/code-execution";
|
|
2
|
-
|
|
3
|
-
const exec = new CodeExecutor({ tenantId: "local", workspaceRoot: process.cwd() });
|
|
4
|
-
const observation = await exec.run({
|
|
5
|
-
type: "edit",
|
|
6
|
-
tenantId: "local",
|
|
7
|
-
path: "scratch/example.txt",
|
|
8
|
-
diff: [
|
|
9
|
-
"--- a/scratch/example.txt",
|
|
10
|
-
"+++ b/scratch/example.txt",
|
|
11
|
-
"@@ -1 +1 @@",
|
|
12
|
-
"-old",
|
|
13
|
-
"+new",
|
|
14
|
-
"",
|
|
15
|
-
].join("\n"),
|
|
16
|
-
});
|
|
17
|
-
process.stdout.write(`${JSON.stringify(observation, null, 2)}\n`);
|
package/examples/policy.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { CodeExecutor } from "@nebutra/code-execution";
|
|
2
|
-
|
|
3
|
-
const exec = new CodeExecutor({ tenantId: "local" });
|
|
4
|
-
const observation = await exec.run({
|
|
5
|
-
type: "shell",
|
|
6
|
-
tenantId: "local",
|
|
7
|
-
cwd: process.cwd(),
|
|
8
|
-
cmd: "git push origin main",
|
|
9
|
-
});
|
|
10
|
-
process.stdout.write(`${JSON.stringify(observation, null, 2)}\n`);
|
package/examples/shell.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { CodeExecutor } from "@nebutra/code-execution";
|
|
2
|
-
|
|
3
|
-
const exec = new CodeExecutor({ tenantId: "local" });
|
|
4
|
-
const observation = await exec.run({
|
|
5
|
-
type: "shell",
|
|
6
|
-
tenantId: "local",
|
|
7
|
-
cwd: process.cwd(),
|
|
8
|
-
cmd: "echo sandbox ok",
|
|
9
|
-
timeoutS: 5,
|
|
10
|
-
});
|
|
11
|
-
process.stdout.write(`${JSON.stringify(observation, null, 2)}\n`);
|
package/src/cli.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { CodeExecutor, readExecutionDebug } from "./index";
|
|
2
|
-
|
|
3
|
-
const command = process.argv[2] ?? "doctor";
|
|
4
|
-
const exec = new CodeExecutor({ tenantId: process.env.NEBUTRA_TENANT_ID ?? "local" });
|
|
5
|
-
|
|
6
|
-
if (command === "doctor") {
|
|
7
|
-
process.stdout.write(`${JSON.stringify(await exec.doctor(), null, 2)}\n`);
|
|
8
|
-
} else if (command === "debug") {
|
|
9
|
-
process.stdout.write(
|
|
10
|
-
`${JSON.stringify({ capability: "code-execution", entries: await readExecutionDebug() }, null, 2)}\n`,
|
|
11
|
-
);
|
|
12
|
-
} else if (command === "policy") {
|
|
13
|
-
const rules = exec.policy().map((rule) => ({
|
|
14
|
-
...rule,
|
|
15
|
-
match: rule.match instanceof RegExp ? rule.match.source : rule.match,
|
|
16
|
-
}));
|
|
17
|
-
process.stdout.write(`${JSON.stringify({ capability: "code-execution", rules }, null, 2)}\n`);
|
|
18
|
-
} else if (command === "replay") {
|
|
19
|
-
const actionId = process.argv[3];
|
|
20
|
-
const entries = await readExecutionDebug(process.cwd(), 100);
|
|
21
|
-
const entry = entries.find((item) => {
|
|
22
|
-
if (!item || typeof item !== "object") return false;
|
|
23
|
-
const value = item as { action?: { actionId?: string }; observation?: { actionId?: string } };
|
|
24
|
-
return value.action?.actionId === actionId || value.observation?.actionId === actionId;
|
|
25
|
-
});
|
|
26
|
-
process.stdout.write(
|
|
27
|
-
`${JSON.stringify(
|
|
28
|
-
{
|
|
29
|
-
capability: "code-execution",
|
|
30
|
-
actionId,
|
|
31
|
-
replayable: Boolean(entry),
|
|
32
|
-
entry,
|
|
33
|
-
suggestion: entry
|
|
34
|
-
? "Use the stored action payload as the deterministic replay input."
|
|
35
|
-
: "Run `pnpm exec:debug` and choose an action id from the recent log.",
|
|
36
|
-
},
|
|
37
|
-
null,
|
|
38
|
-
2,
|
|
39
|
-
)}\n`,
|
|
40
|
-
);
|
|
41
|
-
} else {
|
|
42
|
-
process.stderr.write(`Unknown code-execution command: ${command}\n`);
|
|
43
|
-
process.exitCode = 1;
|
|
44
|
-
}
|
package/src/index.test.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { CodeExecutor, DefaultPolicy, readExecutionDebug } from "./index";
|
|
6
|
-
|
|
7
|
-
let root: string | undefined;
|
|
8
|
-
|
|
9
|
-
afterEach(async () => {
|
|
10
|
-
if (root) await rm(root, { recursive: true, force: true });
|
|
11
|
-
root = undefined;
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe("CodeExecutor", () => {
|
|
15
|
-
it("runs shell actions through sandbox-runtime with tenant context", async () => {
|
|
16
|
-
const exec = new CodeExecutor({
|
|
17
|
-
tenantId: "tenant_a",
|
|
18
|
-
sandboxRuntime: {
|
|
19
|
-
async exec(request) {
|
|
20
|
-
return {
|
|
21
|
-
exitCode: 0,
|
|
22
|
-
aggregatedOutput: `ran ${request.cmd} for ${request.tenantId}`,
|
|
23
|
-
executedOn: "test_sandbox",
|
|
24
|
-
};
|
|
25
|
-
},
|
|
26
|
-
async doctor() {
|
|
27
|
-
return [{ provider: "test_sandbox", ok: true }];
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
await expect(
|
|
33
|
-
exec.run({
|
|
34
|
-
type: "shell",
|
|
35
|
-
cmd: "pnpm test",
|
|
36
|
-
cwd: "/workspace",
|
|
37
|
-
timeoutS: 60,
|
|
38
|
-
tenantId: "tenant_a",
|
|
39
|
-
}),
|
|
40
|
-
).resolves.toMatchObject({
|
|
41
|
-
type: "shell_output",
|
|
42
|
-
stdout: "ran pnpm test for tenant_a",
|
|
43
|
-
exitCode: 0,
|
|
44
|
-
executedOn: "test_sandbox",
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("blocks destructive shell commands until approval is explicit", async () => {
|
|
49
|
-
const exec = new CodeExecutor({
|
|
50
|
-
tenantId: "tenant_a",
|
|
51
|
-
sandboxRuntime: {
|
|
52
|
-
async exec() {
|
|
53
|
-
throw new Error("should not run");
|
|
54
|
-
},
|
|
55
|
-
async doctor() {
|
|
56
|
-
return [];
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
policy: DefaultPolicy,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
await expect(
|
|
63
|
-
exec.run({ type: "shell", cmd: "rm -rf build", cwd: "/workspace", tenantId: "tenant_a" }),
|
|
64
|
-
).resolves.toMatchObject({
|
|
65
|
-
type: "error",
|
|
66
|
-
kind: "ApprovalRequired",
|
|
67
|
-
suggestion: expect.stringContaining("approval"),
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("applies edits by unified diff instead of overwrite", async () => {
|
|
72
|
-
root = await mkdtemp(join(tmpdir(), "code-execution-"));
|
|
73
|
-
await writeFile(join(root, "main.ts"), "const value = 1;\nconsole.log(value);\n", "utf8");
|
|
74
|
-
const exec = new CodeExecutor({ tenantId: "tenant_a", workspaceRoot: root });
|
|
75
|
-
|
|
76
|
-
await expect(
|
|
77
|
-
exec.run({
|
|
78
|
-
type: "edit",
|
|
79
|
-
path: "main.ts",
|
|
80
|
-
diff: [
|
|
81
|
-
"--- a/main.ts",
|
|
82
|
-
"+++ b/main.ts",
|
|
83
|
-
"@@ -1,2 +1,2 @@",
|
|
84
|
-
"-const value = 1;",
|
|
85
|
-
"+const value = 2;",
|
|
86
|
-
" console.log(value);",
|
|
87
|
-
"",
|
|
88
|
-
].join("\n"),
|
|
89
|
-
tenantId: "tenant_a",
|
|
90
|
-
}),
|
|
91
|
-
).resolves.toMatchObject({ type: "edit_applied", path: "main.ts" });
|
|
92
|
-
|
|
93
|
-
await expect(readFile(join(root, "main.ts"), "utf8")).resolves.toContain("value = 2");
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("requires tenant context for every action", async () => {
|
|
97
|
-
const exec = new CodeExecutor();
|
|
98
|
-
await expect(exec.run({ type: "read", path: "main.ts" })).rejects.toMatchObject({
|
|
99
|
-
capability: "code-execution",
|
|
100
|
-
suggestion: expect.stringContaining("tenantId"),
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("writes replayable debug entries", async () => {
|
|
105
|
-
root = await mkdtemp(join(tmpdir(), "code-execution-"));
|
|
106
|
-
const exec = new CodeExecutor({ tenantId: "tenant_a", debugRoot: root });
|
|
107
|
-
await exec.run({ type: "read", path: "missing.ts", tenantId: "tenant_a" });
|
|
108
|
-
expect(await readExecutionDebug(root)).toEqual(expect.any(Array));
|
|
109
|
-
});
|
|
110
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,436 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { join, normalize } from "node:path";
|
|
3
|
-
import { requireCapabilityTenant } from "@nebutra/capability-kit";
|
|
4
|
-
import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
5
|
-
import { CapabilityError } from "@nebutra/errors";
|
|
6
|
-
import {
|
|
7
|
-
DEFAULT_SHELL_APPROVAL_RULES,
|
|
8
|
-
type ShellApprovalMode,
|
|
9
|
-
type ShellApprovalRule,
|
|
10
|
-
shellApprovalRequired,
|
|
11
|
-
} from "@nebutra/execution-policy";
|
|
12
|
-
import {
|
|
13
|
-
type ExecRequest,
|
|
14
|
-
type ExecResponse,
|
|
15
|
-
type SandboxHealth,
|
|
16
|
-
SandboxRuntime,
|
|
17
|
-
} from "@nebutra/sandbox-runtime";
|
|
18
|
-
|
|
19
|
-
export type ApprovalMode = ShellApprovalMode;
|
|
20
|
-
export type PolicyRule = ShellApprovalRule;
|
|
21
|
-
export const DefaultPolicy = DEFAULT_SHELL_APPROVAL_RULES;
|
|
22
|
-
|
|
23
|
-
export interface BaseAction {
|
|
24
|
-
readonly tenantId?: string;
|
|
25
|
-
readonly threadId?: string;
|
|
26
|
-
readonly actionId?: string;
|
|
27
|
-
readonly approved?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ShellAction extends BaseAction {
|
|
31
|
-
readonly type: "shell";
|
|
32
|
-
readonly cmd: string;
|
|
33
|
-
readonly cwd: string;
|
|
34
|
-
readonly timeoutS?: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface ReadAction extends BaseAction {
|
|
38
|
-
readonly type: "read";
|
|
39
|
-
readonly path: string;
|
|
40
|
-
readonly lineRange?: readonly [number, number];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface EditAction extends BaseAction {
|
|
44
|
-
readonly type: "edit";
|
|
45
|
-
readonly path: string;
|
|
46
|
-
readonly diff: string;
|
|
47
|
-
readonly lineRange?: readonly [number, number];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface IPythonAction extends BaseAction {
|
|
51
|
-
readonly type: "ipython";
|
|
52
|
-
readonly code: string;
|
|
53
|
-
readonly kernelId: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface GitAction extends BaseAction {
|
|
57
|
-
readonly type: "git";
|
|
58
|
-
readonly op: "status" | "log" | "push";
|
|
59
|
-
readonly args?: readonly string[];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export type Action = ShellAction | ReadAction | EditAction | IPythonAction | GitAction;
|
|
63
|
-
|
|
64
|
-
export interface ShellOutputObservation {
|
|
65
|
-
readonly type: "shell_output";
|
|
66
|
-
readonly actionId: string;
|
|
67
|
-
readonly stdout: string;
|
|
68
|
-
readonly stderr: string;
|
|
69
|
-
readonly exitCode: number;
|
|
70
|
-
readonly durationMs: number;
|
|
71
|
-
readonly executedOn: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface FileContentObservation {
|
|
75
|
-
readonly type: "file_content";
|
|
76
|
-
readonly actionId: string;
|
|
77
|
-
readonly path: string;
|
|
78
|
-
readonly content: string;
|
|
79
|
-
readonly lines: readonly [number, number];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface EditAppliedObservation {
|
|
83
|
-
readonly type: "edit_applied";
|
|
84
|
-
readonly actionId: string;
|
|
85
|
-
readonly path: string;
|
|
86
|
-
readonly diff: string;
|
|
87
|
-
readonly conflicts: readonly string[];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface ErrorObservation {
|
|
91
|
-
readonly type: "error";
|
|
92
|
-
readonly actionId: string;
|
|
93
|
-
readonly kind: string;
|
|
94
|
-
readonly message: string;
|
|
95
|
-
readonly suggestion: string;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export type Observation =
|
|
99
|
-
| ShellOutputObservation
|
|
100
|
-
| FileContentObservation
|
|
101
|
-
| EditAppliedObservation
|
|
102
|
-
| ErrorObservation;
|
|
103
|
-
|
|
104
|
-
export interface SandboxRuntimeLike {
|
|
105
|
-
exec(request: ExecRequest): Promise<ExecResponse>;
|
|
106
|
-
doctor(): Promise<readonly SandboxHealth[]>;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export interface EventLogLike {
|
|
110
|
-
commit(event: {
|
|
111
|
-
traceId: string;
|
|
112
|
-
kind: "tool_call" | "content_write" | "sandbox_exec";
|
|
113
|
-
affected: readonly string[];
|
|
114
|
-
parent: string | null;
|
|
115
|
-
snapshot?: Record<string, string>;
|
|
116
|
-
}): Promise<string>;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export interface CodeExecutorOptions {
|
|
120
|
-
readonly tenantId?: string;
|
|
121
|
-
readonly workspaceRoot?: string;
|
|
122
|
-
readonly debugRoot?: string;
|
|
123
|
-
readonly sandboxRuntime?: SandboxRuntimeLike;
|
|
124
|
-
readonly eventLog?: EventLogLike;
|
|
125
|
-
readonly policy?: readonly PolicyRule[];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export async function readExecutionDebug(root = process.cwd(), limit = 20): Promise<unknown[]> {
|
|
129
|
-
return readCapabilityDebug("code-execution", { root, limit });
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function actionIdFrom(action: Action): string {
|
|
133
|
-
return action.actionId ?? `${action.type}_${Date.now().toString(36)}`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function safeWorkspacePath(root: string, path: string): string {
|
|
137
|
-
const normalized = normalize(path);
|
|
138
|
-
if (normalized.startsWith("/") || normalized.includes("..")) {
|
|
139
|
-
throw new CapabilityError("code-execution", "Unsafe workspace path rejected", {
|
|
140
|
-
suggestion: "Use a path relative to the configured workspace root.",
|
|
141
|
-
metadata: { path },
|
|
142
|
-
statusCode: 400,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
return join(root, normalized);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export class CodeExecutor {
|
|
149
|
-
readonly #tenantId: string | undefined;
|
|
150
|
-
readonly #workspaceRoot: string;
|
|
151
|
-
readonly #debugRoot: string;
|
|
152
|
-
readonly #sandboxRuntime: SandboxRuntimeLike;
|
|
153
|
-
readonly #eventLog: EventLogLike | undefined;
|
|
154
|
-
readonly #policy: readonly PolicyRule[];
|
|
155
|
-
|
|
156
|
-
constructor(options: CodeExecutorOptions = {}) {
|
|
157
|
-
this.#tenantId = options.tenantId;
|
|
158
|
-
this.#workspaceRoot = options.workspaceRoot ?? process.cwd();
|
|
159
|
-
this.#debugRoot = options.debugRoot ?? process.cwd();
|
|
160
|
-
this.#sandboxRuntime = options.sandboxRuntime ?? SandboxRuntime.fromConfig();
|
|
161
|
-
this.#eventLog = options.eventLog;
|
|
162
|
-
this.#policy = options.policy ?? DefaultPolicy;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async run(action: Action): Promise<Observation> {
|
|
166
|
-
const tenantId = requireCapabilityTenant({
|
|
167
|
-
explicit: action.tenantId,
|
|
168
|
-
fallback: this.#tenantId,
|
|
169
|
-
onMissing: () =>
|
|
170
|
-
new CapabilityError("code-execution", "Code execution requires tenant context", {
|
|
171
|
-
suggestion:
|
|
172
|
-
"Pass tenantId on the action or construct CodeExecutor with a tenantId default.",
|
|
173
|
-
statusCode: 400,
|
|
174
|
-
}),
|
|
175
|
-
});
|
|
176
|
-
const actionId = actionIdFrom(action);
|
|
177
|
-
const started = Date.now();
|
|
178
|
-
let observation: Observation;
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
observation = await this.#runWithTenant({ ...action, tenantId }, actionId, started);
|
|
182
|
-
} catch (cause) {
|
|
183
|
-
observation = errorObservation(actionId, cause);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
await appendCapabilityDebug(
|
|
187
|
-
"code-execution",
|
|
188
|
-
{ type: "action", tenantId, action, observation },
|
|
189
|
-
{ root: this.#debugRoot },
|
|
190
|
-
);
|
|
191
|
-
return observation;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async doctor(): Promise<{
|
|
195
|
-
readonly capability: "code-execution";
|
|
196
|
-
readonly sandbox: readonly SandboxHealth[];
|
|
197
|
-
readonly policies: number;
|
|
198
|
-
}> {
|
|
199
|
-
const sandbox = await this.#sandboxRuntime.doctor();
|
|
200
|
-
await appendCapabilityDebug(
|
|
201
|
-
"code-execution",
|
|
202
|
-
{ type: "doctor", sandbox },
|
|
203
|
-
{ root: this.#debugRoot },
|
|
204
|
-
);
|
|
205
|
-
return { capability: "code-execution", sandbox, policies: this.#policy.length };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
policy(): readonly PolicyRule[] {
|
|
209
|
-
return this.#policy;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async #runWithTenant(
|
|
213
|
-
action: Action & { readonly tenantId: string },
|
|
214
|
-
actionId: string,
|
|
215
|
-
started: number,
|
|
216
|
-
): Promise<Observation> {
|
|
217
|
-
switch (action.type) {
|
|
218
|
-
case "shell":
|
|
219
|
-
return this.#runShell(action, actionId, started);
|
|
220
|
-
case "read":
|
|
221
|
-
return this.#readFile(action, actionId);
|
|
222
|
-
case "edit":
|
|
223
|
-
return this.#editFile(action, actionId);
|
|
224
|
-
case "git":
|
|
225
|
-
return this.#runGit(action, actionId, started);
|
|
226
|
-
case "ipython":
|
|
227
|
-
return {
|
|
228
|
-
type: "error",
|
|
229
|
-
actionId,
|
|
230
|
-
kind: "AdapterUnavailable",
|
|
231
|
-
message: "IPython kernel execution is not configured",
|
|
232
|
-
suggestion: "Configure a notebook kernel sidecar and route it through sandbox-runtime.",
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async #runShell(
|
|
238
|
-
action: ShellAction & { readonly tenantId: string },
|
|
239
|
-
actionId: string,
|
|
240
|
-
started: number,
|
|
241
|
-
): Promise<Observation> {
|
|
242
|
-
const approval = this.#approvalRequired(action.cmd);
|
|
243
|
-
if (approval && !action.approved) {
|
|
244
|
-
return {
|
|
245
|
-
type: "error",
|
|
246
|
-
actionId,
|
|
247
|
-
kind: "ApprovalRequired",
|
|
248
|
-
message: `Command requires approval: ${approval.reason}`,
|
|
249
|
-
suggestion:
|
|
250
|
-
"Ask the user for explicit approval, then retry the action with approved: true.",
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const result = await this.#sandboxRuntime.exec({
|
|
255
|
-
cmd: action.cmd,
|
|
256
|
-
tenantId: action.tenantId,
|
|
257
|
-
...(action.threadId ? { threadId: action.threadId } : {}),
|
|
258
|
-
...(action.timeoutS !== undefined ? { hints: { durationS: action.timeoutS } } : {}),
|
|
259
|
-
});
|
|
260
|
-
await this.#eventLog?.commit({
|
|
261
|
-
traceId: action.threadId ?? actionId,
|
|
262
|
-
kind: "sandbox_exec",
|
|
263
|
-
affected: [],
|
|
264
|
-
parent: null,
|
|
265
|
-
});
|
|
266
|
-
return {
|
|
267
|
-
type: "shell_output",
|
|
268
|
-
actionId,
|
|
269
|
-
stdout: result.aggregatedOutput,
|
|
270
|
-
stderr: "",
|
|
271
|
-
exitCode: result.exitCode,
|
|
272
|
-
durationMs: Date.now() - started,
|
|
273
|
-
executedOn: result.executedOn,
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async #runGit(
|
|
278
|
-
action: GitAction & { readonly tenantId: string },
|
|
279
|
-
actionId: string,
|
|
280
|
-
started: number,
|
|
281
|
-
): Promise<Observation> {
|
|
282
|
-
const command = `git ${action.op}${action.args?.length ? ` ${action.args.join(" ")}` : ""}`;
|
|
283
|
-
return this.#runShell(
|
|
284
|
-
{
|
|
285
|
-
type: "shell",
|
|
286
|
-
cmd: command,
|
|
287
|
-
cwd: this.#workspaceRoot,
|
|
288
|
-
tenantId: action.tenantId,
|
|
289
|
-
...(action.threadId ? { threadId: action.threadId } : {}),
|
|
290
|
-
...(action.approved !== undefined ? { approved: action.approved } : {}),
|
|
291
|
-
},
|
|
292
|
-
actionId,
|
|
293
|
-
started,
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
async #readFile(
|
|
298
|
-
action: ReadAction & { readonly tenantId: string },
|
|
299
|
-
actionId: string,
|
|
300
|
-
): Promise<Observation> {
|
|
301
|
-
try {
|
|
302
|
-
const content = await readFile(safeWorkspacePath(this.#workspaceRoot, action.path), "utf8");
|
|
303
|
-
const lines = content.split("\n");
|
|
304
|
-
const start = action.lineRange?.[0] ?? 1;
|
|
305
|
-
const end = action.lineRange?.[1] ?? lines.length;
|
|
306
|
-
return {
|
|
307
|
-
type: "file_content",
|
|
308
|
-
actionId,
|
|
309
|
-
path: action.path,
|
|
310
|
-
content: lines.slice(start - 1, end).join("\n"),
|
|
311
|
-
lines: [start, end],
|
|
312
|
-
};
|
|
313
|
-
} catch (cause) {
|
|
314
|
-
return errorObservation(actionId, cause, {
|
|
315
|
-
kind: "FileReadFailed",
|
|
316
|
-
suggestion: "Check the path with a read/list action before asking the executor to edit it.",
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
async #editFile(
|
|
322
|
-
action: EditAction & { readonly tenantId: string },
|
|
323
|
-
actionId: string,
|
|
324
|
-
): Promise<Observation> {
|
|
325
|
-
const full = safeWorkspacePath(this.#workspaceRoot, action.path);
|
|
326
|
-
try {
|
|
327
|
-
const before = await readFile(full, "utf8");
|
|
328
|
-
const after = applyUnifiedDiff(before, action.diff);
|
|
329
|
-
await writeFile(full, after, "utf8");
|
|
330
|
-
await this.#eventLog?.commit({
|
|
331
|
-
traceId: action.threadId ?? actionId,
|
|
332
|
-
kind: "content_write",
|
|
333
|
-
affected: [action.path],
|
|
334
|
-
parent: null,
|
|
335
|
-
snapshot: { [action.path]: after },
|
|
336
|
-
});
|
|
337
|
-
return {
|
|
338
|
-
type: "edit_applied",
|
|
339
|
-
actionId,
|
|
340
|
-
path: action.path,
|
|
341
|
-
diff: action.diff,
|
|
342
|
-
conflicts: [],
|
|
343
|
-
};
|
|
344
|
-
} catch (cause) {
|
|
345
|
-
return errorObservation(actionId, cause, {
|
|
346
|
-
kind: "EditFailed",
|
|
347
|
-
suggestion:
|
|
348
|
-
"Refresh the file, regenerate a unified diff against the current contents, and retry without overwrite semantics.",
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
#approvalRequired(command: string): PolicyRule | null {
|
|
354
|
-
return shellApprovalRequired(command, this.#policy);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function errorObservation(
|
|
359
|
-
actionId: string,
|
|
360
|
-
cause: unknown,
|
|
361
|
-
override: { readonly kind?: string; readonly suggestion?: string } = {},
|
|
362
|
-
): ErrorObservation {
|
|
363
|
-
const message = cause instanceof Error ? cause.message : String(cause);
|
|
364
|
-
return {
|
|
365
|
-
type: "error",
|
|
366
|
-
actionId,
|
|
367
|
-
kind:
|
|
368
|
-
override.kind ?? (cause instanceof CapabilityError ? "CapabilityError" : "ExecutionError"),
|
|
369
|
-
message,
|
|
370
|
-
suggestion:
|
|
371
|
-
override.suggestion ??
|
|
372
|
-
(cause instanceof CapabilityError && cause.suggestion
|
|
373
|
-
? cause.suggestion
|
|
374
|
-
: "Inspect the action, sandbox route, and tenant context, then retry with a narrower command."),
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
export function applyUnifiedDiff(original: string, diff: string): string {
|
|
379
|
-
const originalHadTrailingNewline = original.endsWith("\n");
|
|
380
|
-
let lines = originalHadTrailingNewline ? original.slice(0, -1).split("\n") : original.split("\n");
|
|
381
|
-
if (original === "") lines = [];
|
|
382
|
-
|
|
383
|
-
const diffLines = diff.split("\n");
|
|
384
|
-
let index = 0;
|
|
385
|
-
while (index < diffLines.length) {
|
|
386
|
-
const line = diffLines[index] ?? "";
|
|
387
|
-
if (!line.startsWith("@@")) {
|
|
388
|
-
index += 1;
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
index += 1;
|
|
392
|
-
const oldLines: string[] = [];
|
|
393
|
-
const newLines: string[] = [];
|
|
394
|
-
while (index < diffLines.length && !(diffLines[index] ?? "").startsWith("@@")) {
|
|
395
|
-
const current = diffLines[index] ?? "";
|
|
396
|
-
if (current.startsWith("\\ No newline")) {
|
|
397
|
-
index += 1;
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
if (current.startsWith("-")) oldLines.push(current.slice(1));
|
|
401
|
-
else if (current.startsWith("+")) newLines.push(current.slice(1));
|
|
402
|
-
else if (current.startsWith(" ")) {
|
|
403
|
-
oldLines.push(current.slice(1));
|
|
404
|
-
newLines.push(current.slice(1));
|
|
405
|
-
}
|
|
406
|
-
index += 1;
|
|
407
|
-
}
|
|
408
|
-
const position = findSubsequence(lines, oldLines);
|
|
409
|
-
if (position < 0) {
|
|
410
|
-
throw new CapabilityError("code-execution", "Diff hunk does not match current file", {
|
|
411
|
-
suggestion:
|
|
412
|
-
"Re-read the file and generate a fresh unified diff against the latest content.",
|
|
413
|
-
metadata: { oldLines },
|
|
414
|
-
statusCode: 409,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
lines.splice(position, oldLines.length, ...newLines);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return `${lines.join("\n")}${originalHadTrailingNewline ? "\n" : ""}`;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function findSubsequence(haystack: readonly string[], needle: readonly string[]): number {
|
|
424
|
-
if (needle.length === 0) return 0;
|
|
425
|
-
for (let start = 0; start <= haystack.length - needle.length; start += 1) {
|
|
426
|
-
let matched = true;
|
|
427
|
-
for (let offset = 0; offset < needle.length; offset += 1) {
|
|
428
|
-
if (haystack[start + offset] !== needle[offset]) {
|
|
429
|
-
matched = false;
|
|
430
|
-
break;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
if (matched) return start;
|
|
434
|
-
}
|
|
435
|
-
return -1;
|
|
436
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"target": "esnext",
|
|
7
|
-
"types": ["node"],
|
|
8
|
-
"incremental": false
|
|
9
|
-
},
|
|
10
|
-
"include": ["src", "examples"],
|
|
11
|
-
"exclude": ["node_modules", "dist"]
|
|
12
|
-
}
|
package/tsup.config.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from "tsup";
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
entry: ["src/index.ts", "src/cli.ts"],
|
|
5
|
-
format: ["esm"],
|
|
6
|
-
dts: true,
|
|
7
|
-
sourcemap: true,
|
|
8
|
-
clean: true,
|
|
9
|
-
target: "es2022",
|
|
10
|
-
external: [
|
|
11
|
-
"@nebutra/capability-kit/debug",
|
|
12
|
-
"@nebutra/errors",
|
|
13
|
-
"@nebutra/event-log",
|
|
14
|
-
"@nebutra/execution-policy",
|
|
15
|
-
"@nebutra/sandbox-runtime",
|
|
16
|
-
],
|
|
17
|
-
});
|