@nebutra/code-execution 0.1.0
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/.nebutra/debug/code-execution.jsonl +3 -0
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-test.log +14 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/LICENSE +676 -0
- package/README.md +29 -0
- package/dist/chunk-26GNZJ27.js +300 -0
- package/dist/chunk-26GNZJ27.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +51 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/examples/edit-by-diff.ts +17 -0
- package/examples/policy.ts +10 -0
- package/examples/shell.ts +11 -0
- package/package.json +58 -0
- package/src/cli.ts +44 -0
- package/src/index.test.ts +110 -0
- package/src/index.ts +475 -0
- package/tsconfig.json +12 -0
- package/tsup.config.ts +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @nebutra/code-execution
|
|
2
|
+
|
|
3
|
+
Status: WIP — Not yet integrated into any production app.
|
|
4
|
+
|
|
5
|
+
`@nebutra/code-execution` owns action/observation execution for shell, read,
|
|
6
|
+
edit-by-diff, git, policy checks, replay, doctor output, and debug inspection.
|
|
7
|
+
It routes process execution through sandbox contracts and returns structured
|
|
8
|
+
observations with suggestions.
|
|
9
|
+
|
|
10
|
+
It does not own Thread/Turn/Item state, prompt generation, model calls,
|
|
11
|
+
sub-agent scheduling, or approval lifecycle. Approval requests are surfaced as
|
|
12
|
+
observations for the runtime or UI to handle.
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm exec:doctor
|
|
18
|
+
pnpm exec:debug <action_id>
|
|
19
|
+
pnpm exec:replay <action_id>
|
|
20
|
+
pnpm exec:policy
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Examples
|
|
24
|
+
|
|
25
|
+
Executable examples live under `examples/`:
|
|
26
|
+
|
|
27
|
+
- `edit-by-diff.ts`
|
|
28
|
+
- `policy.ts`
|
|
29
|
+
- `shell.ts`
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3
|
+
import { dirname, join, normalize } from "path";
|
|
4
|
+
import { CapabilityError } from "@nebutra/errors";
|
|
5
|
+
import {
|
|
6
|
+
SandboxRuntime
|
|
7
|
+
} from "@nebutra/sandbox-runtime";
|
|
8
|
+
var DefaultPolicy = [
|
|
9
|
+
{ match: /^rm\s+-rf\b/, requireApproval: "always", reason: "destructive recursive removal" },
|
|
10
|
+
{
|
|
11
|
+
match: /\b(format|mkfs)\b/,
|
|
12
|
+
requireApproval: "always",
|
|
13
|
+
reason: "destructive filesystem operation"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
match: /\bDROP\s+(DATABASE|SCHEMA|TABLE)\b/i,
|
|
17
|
+
requireApproval: "always",
|
|
18
|
+
reason: "destructive database operation"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
match: /\b(npm|pnpm|yarn)\s+publish\b/,
|
|
22
|
+
requireApproval: "always",
|
|
23
|
+
reason: "package publishing"
|
|
24
|
+
},
|
|
25
|
+
{ match: /^git\s+push\b/, requireApproval: "once_per_session", reason: "remote git mutation" }
|
|
26
|
+
];
|
|
27
|
+
function debugPath(root = process.cwd()) {
|
|
28
|
+
return join(root, ".nebutra", "debug", "code-execution.jsonl");
|
|
29
|
+
}
|
|
30
|
+
async function appendDebug(root, entry) {
|
|
31
|
+
const path = debugPath(root);
|
|
32
|
+
await mkdir(dirname(path), { recursive: true });
|
|
33
|
+
await writeFile(path, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry })}
|
|
34
|
+
`, {
|
|
35
|
+
flag: "a"
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function readExecutionDebug(root = process.cwd(), limit = 20) {
|
|
39
|
+
try {
|
|
40
|
+
const raw = await readFile(debugPath(root), "utf8");
|
|
41
|
+
return raw.trim().split("\n").filter(Boolean).slice(-limit).map((line) => JSON.parse(line));
|
|
42
|
+
} catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function requireTenant(explicit, fallback) {
|
|
47
|
+
const tenantId = explicit ?? fallback;
|
|
48
|
+
if (!tenantId) {
|
|
49
|
+
throw new CapabilityError("code-execution", "Code execution requires tenant context", {
|
|
50
|
+
suggestion: "Pass tenantId on the action or construct CodeExecutor with a tenantId default.",
|
|
51
|
+
statusCode: 400
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return tenantId;
|
|
55
|
+
}
|
|
56
|
+
function actionIdFrom(action) {
|
|
57
|
+
return action.actionId ?? `${action.type}_${Date.now().toString(36)}`;
|
|
58
|
+
}
|
|
59
|
+
function matchesRule(command, rule) {
|
|
60
|
+
return typeof rule.match === "string" ? command.startsWith(rule.match) : rule.match.test(command);
|
|
61
|
+
}
|
|
62
|
+
function safeWorkspacePath(root, path) {
|
|
63
|
+
const normalized = normalize(path);
|
|
64
|
+
if (normalized.startsWith("/") || normalized.includes("..")) {
|
|
65
|
+
throw new CapabilityError("code-execution", "Unsafe workspace path rejected", {
|
|
66
|
+
suggestion: "Use a path relative to the configured workspace root.",
|
|
67
|
+
metadata: { path },
|
|
68
|
+
statusCode: 400
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return join(root, normalized);
|
|
72
|
+
}
|
|
73
|
+
var CodeExecutor = class {
|
|
74
|
+
#tenantId;
|
|
75
|
+
#workspaceRoot;
|
|
76
|
+
#debugRoot;
|
|
77
|
+
#sandboxRuntime;
|
|
78
|
+
#eventLog;
|
|
79
|
+
#policy;
|
|
80
|
+
constructor(options = {}) {
|
|
81
|
+
this.#tenantId = options.tenantId;
|
|
82
|
+
this.#workspaceRoot = options.workspaceRoot ?? process.cwd();
|
|
83
|
+
this.#debugRoot = options.debugRoot ?? process.cwd();
|
|
84
|
+
this.#sandboxRuntime = options.sandboxRuntime ?? SandboxRuntime.fromConfig();
|
|
85
|
+
this.#eventLog = options.eventLog;
|
|
86
|
+
this.#policy = options.policy ?? DefaultPolicy;
|
|
87
|
+
}
|
|
88
|
+
async run(action) {
|
|
89
|
+
const tenantId = requireTenant(action.tenantId, this.#tenantId);
|
|
90
|
+
const actionId = actionIdFrom(action);
|
|
91
|
+
const started = Date.now();
|
|
92
|
+
let observation;
|
|
93
|
+
try {
|
|
94
|
+
observation = await this.#runWithTenant({ ...action, tenantId }, actionId, started);
|
|
95
|
+
} catch (cause) {
|
|
96
|
+
observation = errorObservation(actionId, cause);
|
|
97
|
+
}
|
|
98
|
+
await appendDebug(this.#debugRoot, { type: "action", tenantId, action, observation });
|
|
99
|
+
return observation;
|
|
100
|
+
}
|
|
101
|
+
async doctor() {
|
|
102
|
+
const sandbox = await this.#sandboxRuntime.doctor();
|
|
103
|
+
await appendDebug(this.#debugRoot, { type: "doctor", sandbox });
|
|
104
|
+
return { capability: "code-execution", sandbox, policies: this.#policy.length };
|
|
105
|
+
}
|
|
106
|
+
policy() {
|
|
107
|
+
return this.#policy;
|
|
108
|
+
}
|
|
109
|
+
async #runWithTenant(action, actionId, started) {
|
|
110
|
+
switch (action.type) {
|
|
111
|
+
case "shell":
|
|
112
|
+
return this.#runShell(action, actionId, started);
|
|
113
|
+
case "read":
|
|
114
|
+
return this.#readFile(action, actionId);
|
|
115
|
+
case "edit":
|
|
116
|
+
return this.#editFile(action, actionId);
|
|
117
|
+
case "git":
|
|
118
|
+
return this.#runGit(action, actionId, started);
|
|
119
|
+
case "ipython":
|
|
120
|
+
return {
|
|
121
|
+
type: "error",
|
|
122
|
+
actionId,
|
|
123
|
+
kind: "AdapterUnavailable",
|
|
124
|
+
message: "IPython kernel execution is not configured",
|
|
125
|
+
suggestion: "Configure a notebook kernel sidecar and route it through sandbox-runtime."
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async #runShell(action, actionId, started) {
|
|
130
|
+
const approval = this.#approvalRequired(action.cmd);
|
|
131
|
+
if (approval && !action.approved) {
|
|
132
|
+
return {
|
|
133
|
+
type: "error",
|
|
134
|
+
actionId,
|
|
135
|
+
kind: "ApprovalRequired",
|
|
136
|
+
message: `Command requires approval: ${approval.reason}`,
|
|
137
|
+
suggestion: "Ask the user for explicit approval, then retry the action with approved: true."
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const result = await this.#sandboxRuntime.exec({
|
|
141
|
+
cmd: action.cmd,
|
|
142
|
+
tenantId: action.tenantId,
|
|
143
|
+
...action.threadId ? { threadId: action.threadId } : {},
|
|
144
|
+
...action.timeoutS !== void 0 ? { hints: { durationS: action.timeoutS } } : {}
|
|
145
|
+
});
|
|
146
|
+
await this.#eventLog?.commit({
|
|
147
|
+
traceId: action.threadId ?? actionId,
|
|
148
|
+
kind: "sandbox_exec",
|
|
149
|
+
affected: [],
|
|
150
|
+
parent: null
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
type: "shell_output",
|
|
154
|
+
actionId,
|
|
155
|
+
stdout: result.aggregatedOutput,
|
|
156
|
+
stderr: "",
|
|
157
|
+
exitCode: result.exitCode,
|
|
158
|
+
durationMs: Date.now() - started,
|
|
159
|
+
executedOn: result.executedOn
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async #runGit(action, actionId, started) {
|
|
163
|
+
const command = `git ${action.op}${action.args?.length ? ` ${action.args.join(" ")}` : ""}`;
|
|
164
|
+
return this.#runShell(
|
|
165
|
+
{
|
|
166
|
+
type: "shell",
|
|
167
|
+
cmd: command,
|
|
168
|
+
cwd: this.#workspaceRoot,
|
|
169
|
+
tenantId: action.tenantId,
|
|
170
|
+
...action.threadId ? { threadId: action.threadId } : {},
|
|
171
|
+
...action.approved !== void 0 ? { approved: action.approved } : {}
|
|
172
|
+
},
|
|
173
|
+
actionId,
|
|
174
|
+
started
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
async #readFile(action, actionId) {
|
|
178
|
+
try {
|
|
179
|
+
const content = await readFile(safeWorkspacePath(this.#workspaceRoot, action.path), "utf8");
|
|
180
|
+
const lines = content.split("\n");
|
|
181
|
+
const start = action.lineRange?.[0] ?? 1;
|
|
182
|
+
const end = action.lineRange?.[1] ?? lines.length;
|
|
183
|
+
return {
|
|
184
|
+
type: "file_content",
|
|
185
|
+
actionId,
|
|
186
|
+
path: action.path,
|
|
187
|
+
content: lines.slice(start - 1, end).join("\n"),
|
|
188
|
+
lines: [start, end]
|
|
189
|
+
};
|
|
190
|
+
} catch (cause) {
|
|
191
|
+
return errorObservation(actionId, cause, {
|
|
192
|
+
kind: "FileReadFailed",
|
|
193
|
+
suggestion: "Check the path with a read/list action before asking the executor to edit it."
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async #editFile(action, actionId) {
|
|
198
|
+
const full = safeWorkspacePath(this.#workspaceRoot, action.path);
|
|
199
|
+
try {
|
|
200
|
+
const before = await readFile(full, "utf8");
|
|
201
|
+
const after = applyUnifiedDiff(before, action.diff);
|
|
202
|
+
await writeFile(full, after, "utf8");
|
|
203
|
+
await this.#eventLog?.commit({
|
|
204
|
+
traceId: action.threadId ?? actionId,
|
|
205
|
+
kind: "content_write",
|
|
206
|
+
affected: [action.path],
|
|
207
|
+
parent: null,
|
|
208
|
+
snapshot: { [action.path]: after }
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
type: "edit_applied",
|
|
212
|
+
actionId,
|
|
213
|
+
path: action.path,
|
|
214
|
+
diff: action.diff,
|
|
215
|
+
conflicts: []
|
|
216
|
+
};
|
|
217
|
+
} catch (cause) {
|
|
218
|
+
return errorObservation(actionId, cause, {
|
|
219
|
+
kind: "EditFailed",
|
|
220
|
+
suggestion: "Refresh the file, regenerate a unified diff against the current contents, and retry without overwrite semantics."
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
#approvalRequired(command) {
|
|
225
|
+
return this.#policy.find((rule) => rule.requireApproval !== "never" && matchesRule(command, rule)) ?? null;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
function errorObservation(actionId, cause, override = {}) {
|
|
229
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
230
|
+
return {
|
|
231
|
+
type: "error",
|
|
232
|
+
actionId,
|
|
233
|
+
kind: override.kind ?? (cause instanceof CapabilityError ? "CapabilityError" : "ExecutionError"),
|
|
234
|
+
message,
|
|
235
|
+
suggestion: override.suggestion ?? (cause instanceof CapabilityError && cause.suggestion ? cause.suggestion : "Inspect the action, sandbox route, and tenant context, then retry with a narrower command.")
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function applyUnifiedDiff(original, diff) {
|
|
239
|
+
const originalHadTrailingNewline = original.endsWith("\n");
|
|
240
|
+
let lines = originalHadTrailingNewline ? original.slice(0, -1).split("\n") : original.split("\n");
|
|
241
|
+
if (original === "") lines = [];
|
|
242
|
+
const diffLines = diff.split("\n");
|
|
243
|
+
let index = 0;
|
|
244
|
+
while (index < diffLines.length) {
|
|
245
|
+
const line = diffLines[index] ?? "";
|
|
246
|
+
if (!line.startsWith("@@")) {
|
|
247
|
+
index += 1;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
index += 1;
|
|
251
|
+
const oldLines = [];
|
|
252
|
+
const newLines = [];
|
|
253
|
+
while (index < diffLines.length && !(diffLines[index] ?? "").startsWith("@@")) {
|
|
254
|
+
const current = diffLines[index] ?? "";
|
|
255
|
+
if (current.startsWith("\\ No newline")) {
|
|
256
|
+
index += 1;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (current.startsWith("-")) oldLines.push(current.slice(1));
|
|
260
|
+
else if (current.startsWith("+")) newLines.push(current.slice(1));
|
|
261
|
+
else if (current.startsWith(" ")) {
|
|
262
|
+
oldLines.push(current.slice(1));
|
|
263
|
+
newLines.push(current.slice(1));
|
|
264
|
+
}
|
|
265
|
+
index += 1;
|
|
266
|
+
}
|
|
267
|
+
const position = findSubsequence(lines, oldLines);
|
|
268
|
+
if (position < 0) {
|
|
269
|
+
throw new CapabilityError("code-execution", "Diff hunk does not match current file", {
|
|
270
|
+
suggestion: "Re-read the file and generate a fresh unified diff against the latest content.",
|
|
271
|
+
metadata: { oldLines },
|
|
272
|
+
statusCode: 409
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
lines.splice(position, oldLines.length, ...newLines);
|
|
276
|
+
}
|
|
277
|
+
return `${lines.join("\n")}${originalHadTrailingNewline ? "\n" : ""}`;
|
|
278
|
+
}
|
|
279
|
+
function findSubsequence(haystack, needle) {
|
|
280
|
+
if (needle.length === 0) return 0;
|
|
281
|
+
for (let start = 0; start <= haystack.length - needle.length; start += 1) {
|
|
282
|
+
let matched = true;
|
|
283
|
+
for (let offset = 0; offset < needle.length; offset += 1) {
|
|
284
|
+
if (haystack[start + offset] !== needle[offset]) {
|
|
285
|
+
matched = false;
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (matched) return start;
|
|
290
|
+
}
|
|
291
|
+
return -1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export {
|
|
295
|
+
DefaultPolicy,
|
|
296
|
+
readExecutionDebug,
|
|
297
|
+
CodeExecutor,
|
|
298
|
+
applyUnifiedDiff
|
|
299
|
+
};
|
|
300
|
+
//# sourceMappingURL=chunk-26GNZJ27.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, normalize } from \"node:path\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport {\n type ExecRequest,\n type ExecResponse,\n type SandboxHealth,\n SandboxRuntime,\n} from \"@nebutra/sandbox-runtime\";\n\nexport type ApprovalMode = \"always\" | \"once_per_session\" | \"never\";\n\nexport interface PolicyRule {\n readonly match: string | RegExp;\n readonly requireApproval: ApprovalMode;\n readonly reason: string;\n}\n\nexport const DefaultPolicy: readonly PolicyRule[] = [\n { match: /^rm\\s+-rf\\b/, requireApproval: \"always\", reason: \"destructive recursive removal\" },\n {\n match: /\\b(format|mkfs)\\b/,\n requireApproval: \"always\",\n reason: \"destructive filesystem operation\",\n },\n {\n match: /\\bDROP\\s+(DATABASE|SCHEMA|TABLE)\\b/i,\n requireApproval: \"always\",\n reason: \"destructive database operation\",\n },\n {\n match: /\\b(npm|pnpm|yarn)\\s+publish\\b/,\n requireApproval: \"always\",\n reason: \"package publishing\",\n },\n { match: /^git\\s+push\\b/, requireApproval: \"once_per_session\", reason: \"remote git mutation\" },\n];\n\nexport interface BaseAction {\n readonly tenantId?: string;\n readonly threadId?: string;\n readonly actionId?: string;\n readonly approved?: boolean;\n}\n\nexport interface ShellAction extends BaseAction {\n readonly type: \"shell\";\n readonly cmd: string;\n readonly cwd: string;\n readonly timeoutS?: number;\n}\n\nexport interface ReadAction extends BaseAction {\n readonly type: \"read\";\n readonly path: string;\n readonly lineRange?: readonly [number, number];\n}\n\nexport interface EditAction extends BaseAction {\n readonly type: \"edit\";\n readonly path: string;\n readonly diff: string;\n readonly lineRange?: readonly [number, number];\n}\n\nexport interface IPythonAction extends BaseAction {\n readonly type: \"ipython\";\n readonly code: string;\n readonly kernelId: string;\n}\n\nexport interface GitAction extends BaseAction {\n readonly type: \"git\";\n readonly op: \"status\" | \"log\" | \"push\";\n readonly args?: readonly string[];\n}\n\nexport type Action = ShellAction | ReadAction | EditAction | IPythonAction | GitAction;\n\nexport interface ShellOutputObservation {\n readonly type: \"shell_output\";\n readonly actionId: string;\n readonly stdout: string;\n readonly stderr: string;\n readonly exitCode: number;\n readonly durationMs: number;\n readonly executedOn: string;\n}\n\nexport interface FileContentObservation {\n readonly type: \"file_content\";\n readonly actionId: string;\n readonly path: string;\n readonly content: string;\n readonly lines: readonly [number, number];\n}\n\nexport interface EditAppliedObservation {\n readonly type: \"edit_applied\";\n readonly actionId: string;\n readonly path: string;\n readonly diff: string;\n readonly conflicts: readonly string[];\n}\n\nexport interface ErrorObservation {\n readonly type: \"error\";\n readonly actionId: string;\n readonly kind: string;\n readonly message: string;\n readonly suggestion: string;\n}\n\nexport type Observation =\n | ShellOutputObservation\n | FileContentObservation\n | EditAppliedObservation\n | ErrorObservation;\n\nexport interface SandboxRuntimeLike {\n exec(request: ExecRequest): Promise<ExecResponse>;\n doctor(): Promise<readonly SandboxHealth[]>;\n}\n\nexport interface EventLogLike {\n commit(event: {\n traceId: string;\n kind: \"tool_call\" | \"content_write\" | \"sandbox_exec\";\n affected: readonly string[];\n parent: string | null;\n snapshot?: Record<string, string>;\n }): Promise<string>;\n}\n\nexport interface CodeExecutorOptions {\n readonly tenantId?: string;\n readonly workspaceRoot?: string;\n readonly debugRoot?: string;\n readonly sandboxRuntime?: SandboxRuntimeLike;\n readonly eventLog?: EventLogLike;\n readonly policy?: readonly PolicyRule[];\n}\n\nfunction debugPath(root = process.cwd()): string {\n return join(root, \".nebutra\", \"debug\", \"code-execution.jsonl\");\n}\n\nasync function appendDebug(root: string, entry: Record<string, unknown>): Promise<void> {\n const path = debugPath(root);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\\n`, {\n flag: \"a\",\n });\n}\n\nexport async function readExecutionDebug(root = process.cwd(), limit = 20): Promise<unknown[]> {\n try {\n const raw = await readFile(debugPath(root), \"utf8\");\n return raw\n .trim()\n .split(\"\\n\")\n .filter(Boolean)\n .slice(-limit)\n .map((line) => JSON.parse(line) as unknown);\n } catch {\n return [];\n }\n}\n\nfunction requireTenant(explicit: string | undefined, fallback: string | undefined): string {\n const tenantId = explicit ?? fallback;\n if (!tenantId) {\n throw new CapabilityError(\"code-execution\", \"Code execution requires tenant context\", {\n suggestion: \"Pass tenantId on the action or construct CodeExecutor with a tenantId default.\",\n statusCode: 400,\n });\n }\n return tenantId;\n}\n\nfunction actionIdFrom(action: Action): string {\n return action.actionId ?? `${action.type}_${Date.now().toString(36)}`;\n}\n\nfunction matchesRule(command: string, rule: PolicyRule): boolean {\n return typeof rule.match === \"string\" ? command.startsWith(rule.match) : rule.match.test(command);\n}\n\nfunction safeWorkspacePath(root: string, path: string): string {\n const normalized = normalize(path);\n if (normalized.startsWith(\"/\") || normalized.includes(\"..\")) {\n throw new CapabilityError(\"code-execution\", \"Unsafe workspace path rejected\", {\n suggestion: \"Use a path relative to the configured workspace root.\",\n metadata: { path },\n statusCode: 400,\n });\n }\n return join(root, normalized);\n}\n\nexport class CodeExecutor {\n readonly #tenantId: string | undefined;\n readonly #workspaceRoot: string;\n readonly #debugRoot: string;\n readonly #sandboxRuntime: SandboxRuntimeLike;\n readonly #eventLog: EventLogLike | undefined;\n readonly #policy: readonly PolicyRule[];\n\n constructor(options: CodeExecutorOptions = {}) {\n this.#tenantId = options.tenantId;\n this.#workspaceRoot = options.workspaceRoot ?? process.cwd();\n this.#debugRoot = options.debugRoot ?? process.cwd();\n this.#sandboxRuntime = options.sandboxRuntime ?? SandboxRuntime.fromConfig();\n this.#eventLog = options.eventLog;\n this.#policy = options.policy ?? DefaultPolicy;\n }\n\n async run(action: Action): Promise<Observation> {\n const tenantId = requireTenant(action.tenantId, this.#tenantId);\n const actionId = actionIdFrom(action);\n const started = Date.now();\n let observation: Observation;\n\n try {\n observation = await this.#runWithTenant({ ...action, tenantId }, actionId, started);\n } catch (cause) {\n observation = errorObservation(actionId, cause);\n }\n\n await appendDebug(this.#debugRoot, { type: \"action\", tenantId, action, observation });\n return observation;\n }\n\n async doctor(): Promise<{\n readonly capability: \"code-execution\";\n readonly sandbox: readonly SandboxHealth[];\n readonly policies: number;\n }> {\n const sandbox = await this.#sandboxRuntime.doctor();\n await appendDebug(this.#debugRoot, { type: \"doctor\", sandbox });\n return { capability: \"code-execution\", sandbox, policies: this.#policy.length };\n }\n\n policy(): readonly PolicyRule[] {\n return this.#policy;\n }\n\n async #runWithTenant(\n action: Action & { readonly tenantId: string },\n actionId: string,\n started: number,\n ): Promise<Observation> {\n switch (action.type) {\n case \"shell\":\n return this.#runShell(action, actionId, started);\n case \"read\":\n return this.#readFile(action, actionId);\n case \"edit\":\n return this.#editFile(action, actionId);\n case \"git\":\n return this.#runGit(action, actionId, started);\n case \"ipython\":\n return {\n type: \"error\",\n actionId,\n kind: \"AdapterUnavailable\",\n message: \"IPython kernel execution is not configured\",\n suggestion: \"Configure a notebook kernel sidecar and route it through sandbox-runtime.\",\n };\n }\n }\n\n async #runShell(\n action: ShellAction & { readonly tenantId: string },\n actionId: string,\n started: number,\n ): Promise<Observation> {\n const approval = this.#approvalRequired(action.cmd);\n if (approval && !action.approved) {\n return {\n type: \"error\",\n actionId,\n kind: \"ApprovalRequired\",\n message: `Command requires approval: ${approval.reason}`,\n suggestion:\n \"Ask the user for explicit approval, then retry the action with approved: true.\",\n };\n }\n\n const result = await this.#sandboxRuntime.exec({\n cmd: action.cmd,\n tenantId: action.tenantId,\n ...(action.threadId ? { threadId: action.threadId } : {}),\n ...(action.timeoutS !== undefined ? { hints: { durationS: action.timeoutS } } : {}),\n });\n await this.#eventLog?.commit({\n traceId: action.threadId ?? actionId,\n kind: \"sandbox_exec\",\n affected: [],\n parent: null,\n });\n return {\n type: \"shell_output\",\n actionId,\n stdout: result.aggregatedOutput,\n stderr: \"\",\n exitCode: result.exitCode,\n durationMs: Date.now() - started,\n executedOn: result.executedOn,\n };\n }\n\n async #runGit(\n action: GitAction & { readonly tenantId: string },\n actionId: string,\n started: number,\n ): Promise<Observation> {\n const command = `git ${action.op}${action.args?.length ? ` ${action.args.join(\" \")}` : \"\"}`;\n return this.#runShell(\n {\n type: \"shell\",\n cmd: command,\n cwd: this.#workspaceRoot,\n tenantId: action.tenantId,\n ...(action.threadId ? { threadId: action.threadId } : {}),\n ...(action.approved !== undefined ? { approved: action.approved } : {}),\n },\n actionId,\n started,\n );\n }\n\n async #readFile(\n action: ReadAction & { readonly tenantId: string },\n actionId: string,\n ): Promise<Observation> {\n try {\n const content = await readFile(safeWorkspacePath(this.#workspaceRoot, action.path), \"utf8\");\n const lines = content.split(\"\\n\");\n const start = action.lineRange?.[0] ?? 1;\n const end = action.lineRange?.[1] ?? lines.length;\n return {\n type: \"file_content\",\n actionId,\n path: action.path,\n content: lines.slice(start - 1, end).join(\"\\n\"),\n lines: [start, end],\n };\n } catch (cause) {\n return errorObservation(actionId, cause, {\n kind: \"FileReadFailed\",\n suggestion: \"Check the path with a read/list action before asking the executor to edit it.\",\n });\n }\n }\n\n async #editFile(\n action: EditAction & { readonly tenantId: string },\n actionId: string,\n ): Promise<Observation> {\n const full = safeWorkspacePath(this.#workspaceRoot, action.path);\n try {\n const before = await readFile(full, \"utf8\");\n const after = applyUnifiedDiff(before, action.diff);\n await writeFile(full, after, \"utf8\");\n await this.#eventLog?.commit({\n traceId: action.threadId ?? actionId,\n kind: \"content_write\",\n affected: [action.path],\n parent: null,\n snapshot: { [action.path]: after },\n });\n return {\n type: \"edit_applied\",\n actionId,\n path: action.path,\n diff: action.diff,\n conflicts: [],\n };\n } catch (cause) {\n return errorObservation(actionId, cause, {\n kind: \"EditFailed\",\n suggestion:\n \"Refresh the file, regenerate a unified diff against the current contents, and retry without overwrite semantics.\",\n });\n }\n }\n\n #approvalRequired(command: string): PolicyRule | null {\n return (\n this.#policy.find((rule) => rule.requireApproval !== \"never\" && matchesRule(command, rule)) ??\n null\n );\n }\n}\n\nfunction errorObservation(\n actionId: string,\n cause: unknown,\n override: { readonly kind?: string; readonly suggestion?: string } = {},\n): ErrorObservation {\n const message = cause instanceof Error ? cause.message : String(cause);\n return {\n type: \"error\",\n actionId,\n kind:\n override.kind ?? (cause instanceof CapabilityError ? \"CapabilityError\" : \"ExecutionError\"),\n message,\n suggestion:\n override.suggestion ??\n (cause instanceof CapabilityError && cause.suggestion\n ? cause.suggestion\n : \"Inspect the action, sandbox route, and tenant context, then retry with a narrower command.\"),\n };\n}\n\nexport function applyUnifiedDiff(original: string, diff: string): string {\n const originalHadTrailingNewline = original.endsWith(\"\\n\");\n let lines = originalHadTrailingNewline ? original.slice(0, -1).split(\"\\n\") : original.split(\"\\n\");\n if (original === \"\") lines = [];\n\n const diffLines = diff.split(\"\\n\");\n let index = 0;\n while (index < diffLines.length) {\n const line = diffLines[index] ?? \"\";\n if (!line.startsWith(\"@@\")) {\n index += 1;\n continue;\n }\n index += 1;\n const oldLines: string[] = [];\n const newLines: string[] = [];\n while (index < diffLines.length && !(diffLines[index] ?? \"\").startsWith(\"@@\")) {\n const current = diffLines[index] ?? \"\";\n if (current.startsWith(\"\\\\ No newline\")) {\n index += 1;\n continue;\n }\n if (current.startsWith(\"-\")) oldLines.push(current.slice(1));\n else if (current.startsWith(\"+\")) newLines.push(current.slice(1));\n else if (current.startsWith(\" \")) {\n oldLines.push(current.slice(1));\n newLines.push(current.slice(1));\n }\n index += 1;\n }\n const position = findSubsequence(lines, oldLines);\n if (position < 0) {\n throw new CapabilityError(\"code-execution\", \"Diff hunk does not match current file\", {\n suggestion:\n \"Re-read the file and generate a fresh unified diff against the latest content.\",\n metadata: { oldLines },\n statusCode: 409,\n });\n }\n lines.splice(position, oldLines.length, ...newLines);\n }\n\n return `${lines.join(\"\\n\")}${originalHadTrailingNewline ? \"\\n\" : \"\"}`;\n}\n\nfunction findSubsequence(haystack: readonly string[], needle: readonly string[]): number {\n if (needle.length === 0) return 0;\n for (let start = 0; start <= haystack.length - needle.length; start += 1) {\n let matched = true;\n for (let offset = 0; offset < needle.length; offset += 1) {\n if (haystack[start + offset] !== needle[offset]) {\n matched = false;\n break;\n }\n }\n if (matched) return start;\n }\n return -1;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CodeExecutor,
|
|
3
|
+
readExecutionDebug
|
|
4
|
+
} from "./chunk-26GNZJ27.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
var command = process.argv[2] ?? "doctor";
|
|
8
|
+
var exec = new CodeExecutor({ tenantId: process.env.NEBUTRA_TENANT_ID ?? "local" });
|
|
9
|
+
if (command === "doctor") {
|
|
10
|
+
process.stdout.write(`${JSON.stringify(await exec.doctor(), null, 2)}
|
|
11
|
+
`);
|
|
12
|
+
} else if (command === "debug") {
|
|
13
|
+
process.stdout.write(
|
|
14
|
+
`${JSON.stringify({ capability: "code-execution", entries: await readExecutionDebug() }, null, 2)}
|
|
15
|
+
`
|
|
16
|
+
);
|
|
17
|
+
} else if (command === "policy") {
|
|
18
|
+
const rules = exec.policy().map((rule) => ({
|
|
19
|
+
...rule,
|
|
20
|
+
match: rule.match instanceof RegExp ? rule.match.source : rule.match
|
|
21
|
+
}));
|
|
22
|
+
process.stdout.write(`${JSON.stringify({ capability: "code-execution", rules }, null, 2)}
|
|
23
|
+
`);
|
|
24
|
+
} else if (command === "replay") {
|
|
25
|
+
const actionId = process.argv[3];
|
|
26
|
+
const entries = await readExecutionDebug(process.cwd(), 100);
|
|
27
|
+
const entry = entries.find((item) => {
|
|
28
|
+
if (!item || typeof item !== "object") return false;
|
|
29
|
+
const value = item;
|
|
30
|
+
return value.action?.actionId === actionId || value.observation?.actionId === actionId;
|
|
31
|
+
});
|
|
32
|
+
process.stdout.write(
|
|
33
|
+
`${JSON.stringify(
|
|
34
|
+
{
|
|
35
|
+
capability: "code-execution",
|
|
36
|
+
actionId,
|
|
37
|
+
replayable: Boolean(entry),
|
|
38
|
+
entry,
|
|
39
|
+
suggestion: entry ? "Use the stored action payload as the deterministic replay input." : "Run `pnpm exec:debug` and choose an action id from the recent log."
|
|
40
|
+
},
|
|
41
|
+
null,
|
|
42
|
+
2
|
|
43
|
+
)}
|
|
44
|
+
`
|
|
45
|
+
);
|
|
46
|
+
} else {
|
|
47
|
+
process.stderr.write(`Unknown code-execution command: ${command}
|
|
48
|
+
`);
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { CodeExecutor, readExecutionDebug } from \"./index\";\n\nconst command = process.argv[2] ?? \"doctor\";\nconst exec = new CodeExecutor({ tenantId: process.env.NEBUTRA_TENANT_ID ?? \"local\" });\n\nif (command === \"doctor\") {\n process.stdout.write(`${JSON.stringify(await exec.doctor(), null, 2)}\\n`);\n} else if (command === \"debug\") {\n process.stdout.write(\n `${JSON.stringify({ capability: \"code-execution\", entries: await readExecutionDebug() }, null, 2)}\\n`,\n );\n} else if (command === \"policy\") {\n const rules = exec.policy().map((rule) => ({\n ...rule,\n match: rule.match instanceof RegExp ? rule.match.source : rule.match,\n }));\n process.stdout.write(`${JSON.stringify({ capability: \"code-execution\", rules }, null, 2)}\\n`);\n} else if (command === \"replay\") {\n const actionId = process.argv[3];\n const entries = await readExecutionDebug(process.cwd(), 100);\n const entry = entries.find((item) => {\n if (!item || typeof item !== \"object\") return false;\n const value = item as { action?: { actionId?: string }; observation?: { actionId?: string } };\n return value.action?.actionId === actionId || value.observation?.actionId === actionId;\n });\n process.stdout.write(\n `${JSON.stringify(\n {\n capability: \"code-execution\",\n actionId,\n replayable: Boolean(entry),\n entry,\n suggestion: entry\n ? \"Use the stored action payload as the deterministic replay input.\"\n : \"Run `pnpm exec:debug` and choose an action id from the recent log.\",\n },\n null,\n 2,\n )}\\n`,\n );\n} else {\n process.stderr.write(`Unknown code-execution command: ${command}\\n`);\n process.exitCode = 1;\n}\n"],"mappings":";;;;;;AAEA,IAAM,UAAU,QAAQ,KAAK,CAAC,KAAK;AACnC,IAAM,OAAO,IAAI,aAAa,EAAE,UAAU,QAAQ,IAAI,qBAAqB,QAAQ,CAAC;AAEpF,IAAI,YAAY,UAAU;AACxB,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,MAAM,KAAK,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAC1E,WAAW,YAAY,SAAS;AAC9B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,kBAAkB,SAAS,MAAM,mBAAmB,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EACnG;AACF,WAAW,YAAY,UAAU;AAC/B,QAAM,QAAQ,KAAK,OAAO,EAAE,IAAI,CAAC,UAAU;AAAA,IACzC,GAAG;AAAA,IACH,OAAO,KAAK,iBAAiB,SAAS,KAAK,MAAM,SAAS,KAAK;AAAA,EACjE,EAAE;AACF,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,YAAY,kBAAkB,MAAM,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAC9F,WAAW,YAAY,UAAU;AAC/B,QAAM,WAAW,QAAQ,KAAK,CAAC;AAC/B,QAAM,UAAU,MAAM,mBAAmB,QAAQ,IAAI,GAAG,GAAG;AAC3D,QAAM,QAAQ,QAAQ,KAAK,CAAC,SAAS;AACnC,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,UAAM,QAAQ;AACd,WAAO,MAAM,QAAQ,aAAa,YAAY,MAAM,aAAa,aAAa;AAAA,EAChF,CAAC;AACD,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK;AAAA,MACN;AAAA,QACE,YAAY;AAAA,QACZ;AAAA,QACA,YAAY,QAAQ,KAAK;AAAA,QACzB;AAAA,QACA,YAAY,QACR,qEACA;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA;AAAA,EACH;AACF,OAAO;AACL,UAAQ,OAAO,MAAM,mCAAmC,OAAO;AAAA,CAAI;AACnE,UAAQ,WAAW;AACrB;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ExecRequest, ExecResponse, SandboxHealth } from '@nebutra/sandbox-runtime';
|
|
2
|
+
|
|
3
|
+
type ApprovalMode = "always" | "once_per_session" | "never";
|
|
4
|
+
interface PolicyRule {
|
|
5
|
+
readonly match: string | RegExp;
|
|
6
|
+
readonly requireApproval: ApprovalMode;
|
|
7
|
+
readonly reason: string;
|
|
8
|
+
}
|
|
9
|
+
declare const DefaultPolicy: readonly PolicyRule[];
|
|
10
|
+
interface BaseAction {
|
|
11
|
+
readonly tenantId?: string;
|
|
12
|
+
readonly threadId?: string;
|
|
13
|
+
readonly actionId?: string;
|
|
14
|
+
readonly approved?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface ShellAction extends BaseAction {
|
|
17
|
+
readonly type: "shell";
|
|
18
|
+
readonly cmd: string;
|
|
19
|
+
readonly cwd: string;
|
|
20
|
+
readonly timeoutS?: number;
|
|
21
|
+
}
|
|
22
|
+
interface ReadAction extends BaseAction {
|
|
23
|
+
readonly type: "read";
|
|
24
|
+
readonly path: string;
|
|
25
|
+
readonly lineRange?: readonly [number, number];
|
|
26
|
+
}
|
|
27
|
+
interface EditAction extends BaseAction {
|
|
28
|
+
readonly type: "edit";
|
|
29
|
+
readonly path: string;
|
|
30
|
+
readonly diff: string;
|
|
31
|
+
readonly lineRange?: readonly [number, number];
|
|
32
|
+
}
|
|
33
|
+
interface IPythonAction extends BaseAction {
|
|
34
|
+
readonly type: "ipython";
|
|
35
|
+
readonly code: string;
|
|
36
|
+
readonly kernelId: string;
|
|
37
|
+
}
|
|
38
|
+
interface GitAction extends BaseAction {
|
|
39
|
+
readonly type: "git";
|
|
40
|
+
readonly op: "status" | "log" | "push";
|
|
41
|
+
readonly args?: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
type Action = ShellAction | ReadAction | EditAction | IPythonAction | GitAction;
|
|
44
|
+
interface ShellOutputObservation {
|
|
45
|
+
readonly type: "shell_output";
|
|
46
|
+
readonly actionId: string;
|
|
47
|
+
readonly stdout: string;
|
|
48
|
+
readonly stderr: string;
|
|
49
|
+
readonly exitCode: number;
|
|
50
|
+
readonly durationMs: number;
|
|
51
|
+
readonly executedOn: string;
|
|
52
|
+
}
|
|
53
|
+
interface FileContentObservation {
|
|
54
|
+
readonly type: "file_content";
|
|
55
|
+
readonly actionId: string;
|
|
56
|
+
readonly path: string;
|
|
57
|
+
readonly content: string;
|
|
58
|
+
readonly lines: readonly [number, number];
|
|
59
|
+
}
|
|
60
|
+
interface EditAppliedObservation {
|
|
61
|
+
readonly type: "edit_applied";
|
|
62
|
+
readonly actionId: string;
|
|
63
|
+
readonly path: string;
|
|
64
|
+
readonly diff: string;
|
|
65
|
+
readonly conflicts: readonly string[];
|
|
66
|
+
}
|
|
67
|
+
interface ErrorObservation {
|
|
68
|
+
readonly type: "error";
|
|
69
|
+
readonly actionId: string;
|
|
70
|
+
readonly kind: string;
|
|
71
|
+
readonly message: string;
|
|
72
|
+
readonly suggestion: string;
|
|
73
|
+
}
|
|
74
|
+
type Observation = ShellOutputObservation | FileContentObservation | EditAppliedObservation | ErrorObservation;
|
|
75
|
+
interface SandboxRuntimeLike {
|
|
76
|
+
exec(request: ExecRequest): Promise<ExecResponse>;
|
|
77
|
+
doctor(): Promise<readonly SandboxHealth[]>;
|
|
78
|
+
}
|
|
79
|
+
interface EventLogLike {
|
|
80
|
+
commit(event: {
|
|
81
|
+
traceId: string;
|
|
82
|
+
kind: "tool_call" | "content_write" | "sandbox_exec";
|
|
83
|
+
affected: readonly string[];
|
|
84
|
+
parent: string | null;
|
|
85
|
+
snapshot?: Record<string, string>;
|
|
86
|
+
}): Promise<string>;
|
|
87
|
+
}
|
|
88
|
+
interface CodeExecutorOptions {
|
|
89
|
+
readonly tenantId?: string;
|
|
90
|
+
readonly workspaceRoot?: string;
|
|
91
|
+
readonly debugRoot?: string;
|
|
92
|
+
readonly sandboxRuntime?: SandboxRuntimeLike;
|
|
93
|
+
readonly eventLog?: EventLogLike;
|
|
94
|
+
readonly policy?: readonly PolicyRule[];
|
|
95
|
+
}
|
|
96
|
+
declare function readExecutionDebug(root?: string, limit?: number): Promise<unknown[]>;
|
|
97
|
+
declare class CodeExecutor {
|
|
98
|
+
#private;
|
|
99
|
+
constructor(options?: CodeExecutorOptions);
|
|
100
|
+
run(action: Action): Promise<Observation>;
|
|
101
|
+
doctor(): Promise<{
|
|
102
|
+
readonly capability: "code-execution";
|
|
103
|
+
readonly sandbox: readonly SandboxHealth[];
|
|
104
|
+
readonly policies: number;
|
|
105
|
+
}>;
|
|
106
|
+
policy(): readonly PolicyRule[];
|
|
107
|
+
}
|
|
108
|
+
declare function applyUnifiedDiff(original: string, diff: string): string;
|
|
109
|
+
|
|
110
|
+
export { type Action, type ApprovalMode, type BaseAction, CodeExecutor, type CodeExecutorOptions, DefaultPolicy, type EditAction, type EditAppliedObservation, type ErrorObservation, type EventLogLike, type FileContentObservation, type GitAction, type IPythonAction, type Observation, type PolicyRule, type ReadAction, type SandboxRuntimeLike, type ShellAction, type ShellOutputObservation, applyUnifiedDiff, readExecutionDebug };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
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`);
|
|
@@ -0,0 +1,10 @@
|
|
|
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`);
|
|
@@ -0,0 +1,11 @@
|
|
|
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/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nebutra/code-execution",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Action and observation execution layer backed by sandbox-runtime",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"nebutra": {
|
|
9
|
+
"status": "wip",
|
|
10
|
+
"productionReady": false,
|
|
11
|
+
"surface": "execution-capability",
|
|
12
|
+
"requires": [
|
|
13
|
+
"@nebutra/sandbox-runtime for shell and git execution",
|
|
14
|
+
"@nebutra/event-log for standalone action audit"
|
|
15
|
+
],
|
|
16
|
+
"gaps": [
|
|
17
|
+
"Notebook kernel execution is adapter-gated",
|
|
18
|
+
"Remote workspace providers depend on sandbox-runtime configuration",
|
|
19
|
+
"Approval UI is surfaced as observations; GUI handoff is not wired here"
|
|
20
|
+
],
|
|
21
|
+
"featureId": "code-execution",
|
|
22
|
+
"category": "ai",
|
|
23
|
+
"summary": "Shell, read, edit-by-diff, policy, replay, doctor, and debug tools"
|
|
24
|
+
},
|
|
25
|
+
"main": "./src/index.ts",
|
|
26
|
+
"types": "./src/index.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": "./src/index.ts"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@nebutra/errors": "0.1.0",
|
|
32
|
+
"@nebutra/event-log": "0.1.0",
|
|
33
|
+
"@nebutra/sandbox-runtime": "0.1.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.19.15",
|
|
37
|
+
"tsup": "^8.5.1",
|
|
38
|
+
"typescript": "^5.9.3",
|
|
39
|
+
"vitest": "^4.1.4"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/code-execution#readme",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/Nebutra/Nebutra-Sailor.git",
|
|
45
|
+
"directory": "packages/ai/code-execution"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/Nebutra/Nebutra-Sailor/issues"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"typecheck": "tsc --noEmit"
|
|
57
|
+
}
|
|
58
|
+
}
|