@tangle-network/agent-provider-cli-bridge 2.0.4 → 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/box.d.ts +2 -0
- package/dist/sandbox/box.js +2 -0
- package/dist/sandbox/index.d.ts +2 -1
- package/dist/sandbox/index.js +2 -1
- package/dist/sandbox/provider.js +7 -8
- package/dist/sandbox/shell.d.ts +16 -0
- package/dist/sandbox/shell.js +50 -0
- package/package.json +6 -50
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/box.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export declare const BOX_PINS: {
|
|
|
23
23
|
readonly cliBridgeCommit: "263f700dc5ca8b7b6e53b986d376102e0d82b42f";
|
|
24
24
|
readonly agentProfileMaterialize: "1.1.4";
|
|
25
25
|
};
|
|
26
|
+
/** The E2B template and Daytona snapshot name `pnpm build:sandbox-image` publishes for these pins. */
|
|
27
|
+
export declare const BOX_TEMPLATE_NAME: string;
|
|
26
28
|
export interface BoxRouter {
|
|
27
29
|
/** Router root URL, without a path. */
|
|
28
30
|
baseUrl: string;
|
package/dist/sandbox/box.js
CHANGED
|
@@ -23,6 +23,8 @@ export const BOX_PINS = {
|
|
|
23
23
|
cliBridgeCommit: "263f700dc5ca8b7b6e53b986d376102e0d82b42f",
|
|
24
24
|
agentProfileMaterialize: "1.1.4",
|
|
25
25
|
};
|
|
26
|
+
/** The E2B template and Daytona snapshot name `pnpm build:sandbox-image` publishes for these pins. */
|
|
27
|
+
export const BOX_TEMPLATE_NAME = `tangle-cli-bridge-box-${BOX_PINS.cliBridgeCommit.slice(0, 12)}`;
|
|
26
28
|
export const BOX_HARNESSES = {
|
|
27
29
|
"claude-code": {
|
|
28
30
|
harness: "claude-code",
|
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 {
|
|
5
|
+
export { envFileLaunch, markedExec, untilAborted } from "./shell.js";
|
|
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 {
|
|
3
|
+
export { envFileLaunch, markedExec, untilAborted } from "./shell.js";
|
|
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,11 +3,11 @@ 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
|
-
import { plannerLowers } from "./refusal.js";
|
|
9
9
|
import { BOX_WORKSPACE_DIR, BRIDGE_PORT, BRIDGE_START_COMMAND, bridgeProcessEnv, boxHarness, boxHarnessVersion, } from "./box.js";
|
|
10
|
-
import { assertProfileValid, declarableHarnesses, refuseUnsupportedCreateInput, resolveBoxHarness, validateBoxProfile } from "./refusal.js";
|
|
10
|
+
import { assertProfileValid, declarableHarnesses, plannerLowers, refuseUnsupportedCreateInput, resolveBoxHarness, validateBoxProfile } from "./refusal.js";
|
|
11
11
|
export const BRIDGE_RUN_TAG = "tangle-bridge-run";
|
|
12
12
|
const DEFAULT_LIFETIME_MS = 2 * 60 * 60 * 1000;
|
|
13
13
|
const DEFAULT_READY_TIMEOUT_MS = 120_000;
|
|
@@ -215,12 +215,11 @@ async function cloneRepository(sandbox, input, secrets) {
|
|
|
215
215
|
if (repoUrl === undefined)
|
|
216
216
|
return;
|
|
217
217
|
const gitRef = input.workspace?.gitRef;
|
|
218
|
-
const quote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
219
218
|
const command = [
|
|
220
|
-
`rm -rf ${
|
|
221
|
-
`git clone ${
|
|
222
|
-
...(gitRef === undefined ? [] : [`git -C ${
|
|
223
|
-
`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`,
|
|
224
223
|
].join(" && ");
|
|
225
224
|
const result = await sandbox.exec(command, { timeoutMs: 600_000, ...signalOption(input.signal) });
|
|
226
225
|
if (result.exitCode !== 0) {
|
|
@@ -228,7 +227,7 @@ async function cloneRepository(sandbox, input, secrets) {
|
|
|
228
227
|
}
|
|
229
228
|
}
|
|
230
229
|
async function ensureDirectory(sandbox, directory) {
|
|
231
|
-
const result = await sandbox.exec(`mkdir -p
|
|
230
|
+
const result = await sandbox.exec(`mkdir -p ${shellQuote(directory)}`, { timeoutMs: 30_000 });
|
|
232
231
|
if (result.exitCode !== 0) {
|
|
233
232
|
throw new EnvironmentProviderError("provision_failed", `cannot create workspace directory ${directory}: ${result.stderr.trim().slice(0, 200)}`, { path: "workspace.cwd" });
|
|
234
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,63 +31,19 @@
|
|
|
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
|
-
"@tangle-network/agent-provider-testkit": "0.12.
|
|
46
|
+
"@tangle-network/agent-provider-testkit": "0.12.4",
|
|
91
47
|
"@types/node": "26.6.5",
|
|
92
48
|
"typescript": "6.0.3",
|
|
93
49
|
"vitest": "5.0.5"
|