@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 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.
@@ -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;
@@ -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",
@@ -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 { BOX_HARNESSES, BOX_HOME, BOX_PINS, BOX_WORKSPACE_DIR, BRIDGE_PORT, boxHarnessVersion, } from "./box.js";
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";
@@ -1,4 +1,5 @@
1
1
  export { createBridgeSandboxProvider, BRIDGE_RUN_TAG } from "./provider.js";
2
2
  export { validateBoxProfile } from "./refusal.js";
3
- export { BOX_HARNESSES, BOX_HOME, BOX_PINS, BOX_WORKSPACE_DIR, BRIDGE_PORT, boxHarnessVersion, } from "./box.js";
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";
@@ -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 ${quote(BOX_WORKSPACE_DIR)}/.git`,
221
- `git clone ${quote(repoUrl)} ${quote(BOX_WORKSPACE_DIR)}.clone`,
222
- ...(gitRef === undefined ? [] : [`git -C ${quote(BOX_WORKSPACE_DIR)}.clone checkout ${quote(gitRef)}`]),
223
- `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`,
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 '${directory.replace(/'/g, `'\\''`)}'`, { timeoutMs: 30_000 });
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.4",
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/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
- "@tangle-network/agent-provider-testkit": "0.12.2",
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"