@tangle-network/agent-provider-cli-bridge 2.0.5 → 2.0.6
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/README.md +1 -0
- package/dist/sandbox/index.d.ts +1 -0
- package/dist/sandbox/index.js +1 -0
- package/dist/sandbox/provider.js +6 -6
- package/dist/sandbox/shell.d.ts +16 -0
- package/dist/sandbox/shell.js +50 -0
- package/package.json +5 -49
package/README.md
CHANGED
|
@@ -200,6 +200,7 @@ The endpoint holds only a scheme, a host, and an explicit port; userinfo, path,
|
|
|
200
200
|
|
|
201
201
|
`@tangle-network/agent-provider-cli-bridge/sandbox` turns a vendor sandbox that runs CLI Bridge into an `AgentEnvironmentProvider`.
|
|
202
202
|
The vendor adapter implements `BridgeSandboxVendor`: `create`, `connect`, `list`, and per sandbox `exec`, `spawn`, `read`, `write`, `endpoint`, `status` and `destroy`.
|
|
203
|
+
A vendor whose command call runs to completion, as Daytona's and computesdk's do, builds `exec` from `markedExec` and `spawn` from `envFileLaunch`.
|
|
203
204
|
`createBridgeSandboxProvider({ vendor, router: { key } })` does the rest:
|
|
204
205
|
|
|
205
206
|
- It refuses every create field the vendor cannot honor with an `EnvironmentProviderError` that names the field, before any vendor call, and validates the profile with the materializer the Bridge in the box uses.
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export { createBridgeSandboxProvider, BRIDGE_RUN_TAG } from "./provider.js";
|
|
|
2
2
|
export type { BridgeSandboxProviderOptions } from "./provider.js";
|
|
3
3
|
export type { BridgeSandbox, BridgeSandboxCreateRequest, BridgeSandboxSummary, BridgeSandboxVendor, BridgeVendorSupport, } from "./vendor.js";
|
|
4
4
|
export { validateBoxProfile } from "./refusal.js";
|
|
5
|
+
export { envFileLaunch, markedExec, untilAborted } from "./shell.js";
|
|
5
6
|
export { BOX_HARNESSES, BOX_HOME, BOX_PINS, BOX_TEMPLATE_NAME, BOX_WORKSPACE_DIR, BRIDGE_PORT, boxHarnessVersion, } from "./box.js";
|
|
6
7
|
export { renderBoxDockerfile } from "./image.js";
|
package/dist/sandbox/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createBridgeSandboxProvider, BRIDGE_RUN_TAG } from "./provider.js";
|
|
2
2
|
export { validateBoxProfile } from "./refusal.js";
|
|
3
|
+
export { envFileLaunch, markedExec, untilAborted } from "./shell.js";
|
|
3
4
|
export { BOX_HARNESSES, BOX_HOME, BOX_PINS, BOX_TEMPLATE_NAME, BOX_WORKSPACE_DIR, BRIDGE_PORT, boxHarnessVersion, } from "./box.js";
|
|
4
5
|
export { renderBoxDockerfile } from "./image.js";
|
package/dist/sandbox/provider.js
CHANGED
|
@@ -3,6 +3,7 @@ import { posix } from "node:path";
|
|
|
3
3
|
import { Agent, fetch as undiciFetch } from "undici";
|
|
4
4
|
import { canonicalAgentProfileDigest, TANGLE_ROUTER_DEFAULT_ROOT_URL, } from "@tangle-network/agent-interface";
|
|
5
5
|
import { AgentEnvironmentGetOptionsSchema, AgentTurnInputSchema, EnvironmentProviderError, } from "@tangle-network/agent-interface/environment-provider";
|
|
6
|
+
import { shellQuote } from "@tangle-network/agent-core/node";
|
|
6
7
|
import { harnessModelSupportFor } from "@tangle-network/cli-agent-registry";
|
|
7
8
|
import { createCliBridgeProvider, defaultCliBridgeCapabilities } from "../index.js";
|
|
8
9
|
import { BOX_WORKSPACE_DIR, BRIDGE_PORT, BRIDGE_START_COMMAND, bridgeProcessEnv, boxHarness, boxHarnessVersion, } from "./box.js";
|
|
@@ -214,12 +215,11 @@ async function cloneRepository(sandbox, input, secrets) {
|
|
|
214
215
|
if (repoUrl === undefined)
|
|
215
216
|
return;
|
|
216
217
|
const gitRef = input.workspace?.gitRef;
|
|
217
|
-
const quote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
218
218
|
const command = [
|
|
219
|
-
`rm -rf ${
|
|
220
|
-
`git clone ${
|
|
221
|
-
...(gitRef === undefined ? [] : [`git -C ${
|
|
222
|
-
`cp -a ${
|
|
219
|
+
`rm -rf ${shellQuote(BOX_WORKSPACE_DIR)}/.git`,
|
|
220
|
+
`git clone ${shellQuote(repoUrl)} ${shellQuote(BOX_WORKSPACE_DIR)}.clone`,
|
|
221
|
+
...(gitRef === undefined ? [] : [`git -C ${shellQuote(BOX_WORKSPACE_DIR)}.clone checkout ${shellQuote(gitRef)}`]),
|
|
222
|
+
`cp -a ${shellQuote(BOX_WORKSPACE_DIR)}.clone/. ${shellQuote(BOX_WORKSPACE_DIR)}/ && rm -rf ${shellQuote(BOX_WORKSPACE_DIR)}.clone`,
|
|
223
223
|
].join(" && ");
|
|
224
224
|
const result = await sandbox.exec(command, { timeoutMs: 600_000, ...signalOption(input.signal) });
|
|
225
225
|
if (result.exitCode !== 0) {
|
|
@@ -227,7 +227,7 @@ async function cloneRepository(sandbox, input, secrets) {
|
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
async function ensureDirectory(sandbox, directory) {
|
|
230
|
-
const result = await sandbox.exec(`mkdir -p
|
|
230
|
+
const result = await sandbox.exec(`mkdir -p ${shellQuote(directory)}`, { timeoutMs: 30_000 });
|
|
231
231
|
if (result.exitCode !== 0) {
|
|
232
232
|
throw new EnvironmentProviderError("provision_failed", `cannot create workspace directory ${directory}: ${result.stderr.trim().slice(0, 200)}`, { path: "workspace.cwd" });
|
|
233
233
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ExecRequest, type ExecResult } from "@tangle-network/agent-interface/environment-provider";
|
|
2
|
+
/** Exec over a run-to-completion command call: a timeout or abort kills every process carrying the exec's marker, so the remote work stops too. */
|
|
3
|
+
export declare function markedExec(command: string, options: ExecRequest | undefined, provider: string, run: (script: string, signal: AbortSignal) => Promise<ExecResult>): Promise<ExecResult>;
|
|
4
|
+
/** The Bridge's credentials go to a file in a directory only the box user can open, sourced and deleted before exec, so no value enters an argv. */
|
|
5
|
+
export declare function envFileLaunch(command: string, { cwd, env }: {
|
|
6
|
+
cwd?: string;
|
|
7
|
+
env: Record<string, string>;
|
|
8
|
+
}): {
|
|
9
|
+
prepare: string;
|
|
10
|
+
file: string;
|
|
11
|
+
content: string;
|
|
12
|
+
launch: string;
|
|
13
|
+
cleanup: string;
|
|
14
|
+
};
|
|
15
|
+
/** For an SDK that takes no AbortSignal: an abort settles the call at once, and `late` receives a result that arrives afterwards. */
|
|
16
|
+
export declare function untilAborted<T>(pending: Promise<T>, signal: AbortSignal | undefined, late?: (value: T) => Promise<unknown>): Promise<T>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { shellQuote } from "@tangle-network/agent-core/node";
|
|
3
|
+
import { EnvironmentProviderError } from "@tangle-network/agent-interface/environment-provider";
|
|
4
|
+
/** Exec over a run-to-completion command call: a timeout or abort kills every process carrying the exec's marker, so the remote work stops too. */
|
|
5
|
+
export async function markedExec(command, options, provider, run) {
|
|
6
|
+
options?.signal?.throwIfAborted();
|
|
7
|
+
const marker = randomBytes(6).toString("hex");
|
|
8
|
+
const env = Object.entries(options?.env ?? {}).map(([key, value]) => `export ${key}=${shellQuote(value)}; `).join("");
|
|
9
|
+
const ended = new AbortController();
|
|
10
|
+
const timer = options?.timeoutMs === undefined ? undefined : setTimeout(() => ended.abort(new EnvironmentProviderError("timeout", `exec exceeded ${options.timeoutMs} ms`, { provider })), options.timeoutMs);
|
|
11
|
+
const signal = options?.signal === undefined ? ended.signal : AbortSignal.any([options.signal, ended.signal]);
|
|
12
|
+
try {
|
|
13
|
+
return await run(`export TBOX_EXEC=${marker}; ${env}${options?.cwd === undefined ? "" : `cd ${shellQuote(options.cwd)} && `}${command}`, signal);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (signal.aborted)
|
|
17
|
+
await run(`for p in /proc/[0-9]*; do grep -qz 'TBOX_EXEC=${marker}' "$p/environ" 2>/dev/null && kill -9 "\${p#/proc/}" 2>/dev/null; done; true`, AbortSignal.timeout(30_000)).catch(() => undefined);
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** The Bridge's credentials go to a file in a directory only the box user can open, sourced and deleted before exec, so no value enters an argv. */
|
|
25
|
+
export function envFileLaunch(command, { cwd, env }) {
|
|
26
|
+
const dir = `/tmp/tbox-${randomBytes(8).toString("hex")}`;
|
|
27
|
+
return {
|
|
28
|
+
prepare: `umask 077 && mkdir ${dir}`,
|
|
29
|
+
file: `${dir}/env`,
|
|
30
|
+
content: Object.entries(env).map(([key, value]) => `export ${key}=${shellQuote(value)}\n`).join(""),
|
|
31
|
+
launch: `. ${dir}/env && rm -rf ${dir} && ${cwd === undefined ? "" : `cd ${shellQuote(cwd)} && `}exec ${command} >/tmp/bridge.log 2>&1`,
|
|
32
|
+
cleanup: `rm -rf ${dir}`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** For an SDK that takes no AbortSignal: an abort settles the call at once, and `late` receives a result that arrives afterwards. */
|
|
36
|
+
export async function untilAborted(pending, signal, late) {
|
|
37
|
+
if (signal === undefined)
|
|
38
|
+
return pending;
|
|
39
|
+
signal.throwIfAborted();
|
|
40
|
+
let onAbort = () => undefined;
|
|
41
|
+
const aborted = new Promise((_, reject) => signal.addEventListener("abort", (onAbort = () => reject(signal.reason)), { once: true }));
|
|
42
|
+
try {
|
|
43
|
+
return await Promise.race([pending, aborted]);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
signal.removeEventListener("abort", onAbort);
|
|
47
|
+
if (signal.aborted && late !== undefined)
|
|
48
|
+
void pending.then(late).catch(() => undefined);
|
|
49
|
+
}
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-provider-cli-bridge",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "AgentEnvironmentProvider adapter for cli-bridge retained sessions and one-shot turns",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,60 +31,16 @@
|
|
|
31
31
|
"registry": "https://registry.npmjs.org"
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
|
-
"dist
|
|
35
|
-
"dist/index.js",
|
|
36
|
-
"dist/environment-identity.d.ts",
|
|
37
|
-
"dist/environment-identity.js",
|
|
38
|
-
"dist/capability-discovery.d.ts",
|
|
39
|
-
"dist/capability-discovery.js",
|
|
40
|
-
"dist/context-transfer.d.ts",
|
|
41
|
-
"dist/context-transfer.js",
|
|
42
|
-
"dist/interaction-response.d.ts",
|
|
43
|
-
"dist/interaction-response.js",
|
|
44
|
-
"dist/observation.d.ts",
|
|
45
|
-
"dist/observation.js",
|
|
46
|
-
"dist/provider-options.d.ts",
|
|
47
|
-
"dist/provider-options.js",
|
|
48
|
-
"dist/retained-canonical-event.d.ts",
|
|
49
|
-
"dist/retained-canonical-event.js",
|
|
50
|
-
"dist/retained-control.d.ts",
|
|
51
|
-
"dist/retained-control.js",
|
|
52
|
-
"dist/retained-environment.d.ts",
|
|
53
|
-
"dist/retained-environment.js",
|
|
54
|
-
"dist/retained-event-cursor.d.ts",
|
|
55
|
-
"dist/retained-event-cursor.js",
|
|
56
|
-
"dist/retained-execution.d.ts",
|
|
57
|
-
"dist/retained-execution.js",
|
|
58
|
-
"dist/retained-native.d.ts",
|
|
59
|
-
"dist/retained-native.js",
|
|
60
|
-
"dist/retained-run-state.d.ts",
|
|
61
|
-
"dist/retained-run-state.js",
|
|
62
|
-
"dist/retained-stream.d.ts",
|
|
63
|
-
"dist/retained-stream.js",
|
|
64
|
-
"dist/transport.d.ts",
|
|
65
|
-
"dist/transport.js",
|
|
66
|
-
"dist/wire.d.ts",
|
|
67
|
-
"dist/wire.js",
|
|
68
|
-
"dist/sandbox/index.d.ts",
|
|
69
|
-
"dist/sandbox/index.js",
|
|
70
|
-
"dist/sandbox/box.d.ts",
|
|
71
|
-
"dist/sandbox/box.js",
|
|
72
|
-
"dist/sandbox/image.d.ts",
|
|
73
|
-
"dist/sandbox/image.js",
|
|
74
|
-
"dist/sandbox/provider.d.ts",
|
|
75
|
-
"dist/sandbox/provider.js",
|
|
76
|
-
"dist/sandbox/refusal.d.ts",
|
|
77
|
-
"dist/sandbox/refusal.js",
|
|
78
|
-
"dist/sandbox/vendor.d.ts",
|
|
79
|
-
"dist/sandbox/vendor.js",
|
|
34
|
+
"dist",
|
|
80
35
|
"README.md",
|
|
81
36
|
"LICENSE"
|
|
82
37
|
],
|
|
83
38
|
"dependencies": {
|
|
39
|
+
"@tangle-network/agent-core": "^1.0.2",
|
|
84
40
|
"@tangle-network/agent-interface": "^5.10.12",
|
|
85
|
-
"undici": "8.12.3",
|
|
86
41
|
"@tangle-network/agent-profile-materialize": "1.1.4",
|
|
87
|
-
"@tangle-network/cli-agent-registry": "^0.14.0"
|
|
42
|
+
"@tangle-network/cli-agent-registry": "^0.14.0",
|
|
43
|
+
"undici": "8.12.3"
|
|
88
44
|
},
|
|
89
45
|
"devDependencies": {
|
|
90
46
|
"@tangle-network/agent-provider-testkit": "0.12.4",
|