@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 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.
@@ -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";
@@ -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";
@@ -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 ${quote(BOX_WORKSPACE_DIR)}/.git`,
220
- `git clone ${quote(repoUrl)} ${quote(BOX_WORKSPACE_DIR)}.clone`,
221
- ...(gitRef === undefined ? [] : [`git -C ${quote(BOX_WORKSPACE_DIR)}.clone checkout ${quote(gitRef)}`]),
222
- `cp -a ${quote(BOX_WORKSPACE_DIR)}.clone/. ${quote(BOX_WORKSPACE_DIR)}/ && rm -rf ${quote(BOX_WORKSPACE_DIR)}.clone`,
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 '${directory.replace(/'/g, `'\\''`)}'`, { timeoutMs: 30_000 });
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.5",
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/index.d.ts",
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",