@tangle-network/agent-provider-cli-bridge 0.9.3 → 0.9.5

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
@@ -8,6 +8,10 @@ import { createCliBridgeProvider } from '@tangle-network/agent-provider-cli-brid
8
8
  const provider = createCliBridgeProvider({
9
9
  baseUrl: 'http://127.0.0.1:8787',
10
10
  bearerToken: process.env.CLI_BRIDGE_TOKEN,
11
+ defaultExecution: {
12
+ kind: 'host',
13
+ jail: { mode: 'fs-jail' },
14
+ },
11
15
  })
12
16
 
13
17
  const environment = await provider.create({
@@ -43,10 +47,38 @@ The provider rejects any coordinate or digest mismatch instead of attaching to a
43
47
  If the caller crashes before saving `controlRef`, call `provider.lookupRun()` with the planned run coordinates.
44
48
  The lookup returns the server-issued digest only when all five planned coordinates match the retained run.
45
49
 
50
+ ## Which call adds a turn
51
+
52
+ Continuation has two tiers, and the tier depends on whether the environment object came from `create()` or from `get()`.
53
+
54
+ In the process that created the environment, `environment.dispatch()`, `environment.stream()`, and `session.prompt()` all add a turn.
55
+ Passing the same `sessionId` continues the same CLI conversation.
56
+
57
+ After `provider.get(environmentId)` reconstructs the environment, only `session.continueNative(request, { turn })` can add a turn.
58
+ It requires the `nativeContinuation` capability, which the provider publishes only for the Pi harness today, and only when the running Bridge proves the same contract.
59
+ The reconstructed environment deliberately refuses every other way to start work: `environment.stream()` and `environment.dispatch()` throw "a reconstructed cli-bridge environment can only control an existing run", and `session.prompt()` throws "a reconstructed cli-bridge session cannot start another turn".
60
+ A reconstructed environment for any other harness therefore controls its existing runs (status, cursor replay, result, cancellation, and interaction responses) but adds no turn.
61
+ Restart-safe continuation for those harnesses needs native sessions in the Bridge server itself, which the Bridge does not have yet; it is tracked in the `drewstone/cli-bridge` repository and cannot be reached from this adapter.
62
+
63
+ The capability document keeps `sessions.continue: true` on a reconstructed environment because the Agent Interface couples that flag to `retainedControl`: a document that reports `sessions.continue: false` while keeping retained run control fails `AgentEnvironmentCapabilitiesSchema` with "retained control requires exact run, result, event, cancellation, replay, detach, turn, and session identity together".
64
+ `nativeContinuation` is the flag that states whether a reconstructed environment can add a turn: present means `continueNative` works, absent means no call on that environment starts one.
65
+
66
+ A runtime caller continues a retained run with `RetainedRunHandle.continueNative`, not with `startRetainedRunInEnvironment`.
67
+ `startRetainedRunInEnvironment` requires `provider.list` for its ownership proof, and this provider exposes no `list`; it then calls `environment.dispatch()`, which a reconstructed environment refuses.
68
+
46
69
  The bridge model is selected from run data in this order: the turn, the provider default, or the profile's `harness` plus `model.default`.
47
70
  Execution fails before network use when none is present.
48
71
 
49
- Passing the same `sessionId` on later turns continues the same CLI conversation.
72
+ `defaultExecution` is the existing cli-bridge `execution` request object.
73
+ The provider forwards it unchanged to the Bridge session or chat request.
74
+ Host execution accepts `jail`, `netJail`, and `timeoutMs`.
75
+ Sandbox execution accepts `repoUrl`, `gitRef`, `capability`, `ttlSeconds`, `netJail`, and `timeoutMs`.
76
+ The Bridge validates the object and applies its operator confinement rules.
77
+ Retained native sessions currently require `kind: 'host'`.
78
+ The provider rejects a sandbox default before it creates the retained session; use one-shot execution for `kind: 'sandbox'`.
79
+ The Bridge currently refuses `netJail` with `kind: 'sandbox'`; use the sandbox's own egress policy.
80
+
81
+ Passing the same `sessionId` on later turns continues the same CLI conversation while the creating process still holds the environment.
50
82
  `executionId` gives a turn stable bridge identity, and `lastEventId` reattaches after a reader failure.
51
83
  `dispatch()` starts a bridge-owned durable run and returns after detaching its HTTP reader.
52
84
  The returned `AgentSession` exposes status, cursor-based event replay, the terminal result, continuation, and cancellation.
@@ -66,7 +98,7 @@ Replay and result reads fail loudly after cli-bridge's configured replay retenti
66
98
  Stopping a `session.events()` reader detaches only that replay observer.
67
99
  Stopping a direct `environment.stream()` reader or destroying the environment cancels its active bridge runs and waits for terminal confirmation.
68
100
 
69
- Pi retained sessions also expose typed permission interactions.
101
+ Pi retained sessions also expose typed permission interactions and native continuation.
70
102
  The provider stores the selected harness, exact model route, and canonical create digest inside its opaque environment identifier.
71
103
  This identifier lets `provider.get()` reconstruct profile-selected routes after process death without a cache or repeated configuration.
72
104
  The create digest prevents an altered profile or workspace from reusing the previous retained identity.
@@ -83,6 +115,8 @@ The environment and session then expose `respondToInteraction()` for exact respo
83
115
  Each command carries its run, session, execution, request digest, and stable operation identifier.
84
116
  Native turns require caller-stable `turnId` and `executionId` values so an ambiguous admission cannot create another run.
85
117
  The bridge records repeated operations and rejects a different answer for an existing operation.
118
+ Repeating one operation identifier returns its stored acknowledgement; a new operation identifier for an interaction that is already resolved returns `already_resolved_same` when the answer matches and `already_resolved_different` when it does not, and answering an interaction that a run cancellation closed returns `cancelled`.
119
+ The `expired` acknowledgement stays in the contract, but the Bridge never emits it, so only the unit contradiction table covers it.
86
120
  The provider reports an unconfirmed network result as retryable and never reports it as accepted.
87
121
  Native replay reads `/v1/runs/:runId/events` and validates each canonical envelope through Agent Interface before exposing it.
88
122
  When a turn supplies `interactions`, the provider carries its exact map, including an explicit `{}`; an omitted map remains omitted.
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import { type AgentExactRunControlRef, type HarnessType } from "@tangle-network/
3
3
  import { type CliBridgeRunLookupInput } from "./retained-control.js";
4
4
  import { type CliBridgeProviderOptions } from "./provider-options.js";
5
5
  export type { CliBridgeProviderOptions } from "./provider-options.js";
6
+ export type { CliBridgeExecution } from "./provider-options.js";
6
7
  export type { CliBridgeRunLookupInput } from "./retained-control.js";
7
8
  export { safeEndpointFromBaseUrl } from "./observation.js";
8
9
  export interface CliBridgeProvider extends AgentEnvironmentProvider {
package/dist/index.js CHANGED
@@ -76,6 +76,7 @@ export function createCliBridgeProvider(options) {
76
76
  environmentId,
77
77
  allowDispatch: true,
78
78
  cancelRunsOnDestroy: true,
79
+ creation: "created",
79
80
  capabilities: await resolveCapabilities(selectedBackend, model, true, selectedBackend === "pi" ||
80
81
  (model !== undefined && remotelyVerifiedRoutes.has(model))),
81
82
  selectedBackend,
@@ -1,19 +1,40 @@
1
1
  import type { AgentEnvironmentCapabilities } from "@tangle-network/agent-interface/environment-provider";
2
2
  export declare const MAX_CLI_BRIDGE_TIMEOUT_MS = 2147483647;
3
+ /**
4
+ * The execution object accepted by cli-bridge's request wire contract.
5
+ *
6
+ * The provider forwards this object unchanged as `execution`; cli-bridge owns
7
+ * validation, confinement resolution, and execution placement.
8
+ */
9
+ export type CliBridgeExecution = {
10
+ kind: "host";
11
+ jail?: {
12
+ mode?: "off" | "write-jail" | "fs-jail";
13
+ root?: string;
14
+ };
15
+ netJail?: {
16
+ mode?: "off" | "net-jail";
17
+ allow?: string[];
18
+ };
19
+ timeoutMs?: number;
20
+ } | {
21
+ kind: "sandbox";
22
+ repoUrl?: string;
23
+ gitRef?: string;
24
+ capability?: string;
25
+ ttlSeconds?: number;
26
+ netJail?: {
27
+ mode?: "off" | "net-jail";
28
+ allow?: string[];
29
+ };
30
+ timeoutMs?: number;
31
+ };
3
32
  export interface CliBridgeProviderOptions {
4
33
  baseUrl: string;
5
34
  bearerToken?: string;
6
35
  defaultModel?: string;
7
36
  defaultMode?: "byob" | "hosted-safe" | "hosted-sandboxed";
8
- defaultExecution?: {
9
- kind: "host";
10
- } | {
11
- kind: "sandbox";
12
- repoUrl?: string;
13
- gitRef?: string;
14
- capability?: string;
15
- ttlSeconds?: number;
16
- };
37
+ defaultExecution?: CliBridgeExecution;
17
38
  /** Maximum wait for response headers. Defaults to no timeout. */
18
39
  headersTimeoutMs?: number;
19
40
  /** Maximum idle time between response body chunks. Defaults to no timeout. */
@@ -1,4 +1,4 @@
1
- import type { AgentEnvironment, AgentEnvironmentCapabilities, CreateAgentEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
1
+ import type { AgentEnvironment, AgentEnvironmentCapabilities, AgentEnvironmentCreation, CreateAgentEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
2
2
  import type { CliBridgeProviderOptions } from "./provider-options.js";
3
3
  export interface CreateCliBridgeEnvironmentArgs {
4
4
  readonly options: CliBridgeProviderOptions;
@@ -7,6 +7,13 @@ export interface CreateCliBridgeEnvironmentArgs {
7
7
  readonly environmentId: string;
8
8
  readonly allowDispatch: boolean;
9
9
  readonly cancelRunsOnDestroy: boolean;
10
+ /**
11
+ * The verdict of the create call that builds this environment. The create
12
+ * path states `created` because it provisions the handle that tracks every
13
+ * run it starts; the in-process idempotency helper supplies `replayed`; a
14
+ * reconstruction through `get()` states nothing.
15
+ */
16
+ readonly creation?: AgentEnvironmentCreation;
10
17
  /**
11
18
  * The document this provider publishes. The environment offers an optional
12
19
  * operation only where the document claims it, so a caller never selects an
@@ -5,6 +5,18 @@ import { cliBridgeInteractionResponder } from "./interaction-response.js";
5
5
  import { assertCliBridgeRequestedInteractions, assertCliBridgeTurnModel, continueCliBridgeNative, readCliBridgeNativeContextBoundary, supportsCliBridgeNativeContinuation, supportsCliBridgeNativeInteractions, } from "./retained-native.js";
6
6
  import { assertRunMatchesControlRef, exactControlRefForSession, prepareCliBridgeRun, runFromControlRef, } from "./retained-run-state.js";
7
7
  import { createCliBridgeTransport } from "./transport.js";
8
+ /**
9
+ * One refusal for every call that would add a turn to a reconstructed
10
+ * environment. `provider.get()` rebuilds control of existing runs from the
11
+ * opaque environment id alone, so it holds no create request that a new native
12
+ * session could reuse. Only `session.continueNative` advances a retained run
13
+ * after reconstruction, and only where the Bridge proves `nativeContinuation`.
14
+ */
15
+ function assertCliBridgeTurnAllowed(allowed, surface) {
16
+ if (allowed)
17
+ return;
18
+ throw new Error(`a reconstructed cli-bridge environment cannot start a turn through ${surface}; use session.continueNative, which requires the nativeContinuation capability`);
19
+ }
8
20
  export function createCliBridgeEnvironment(args) {
9
21
  const { options, providerName, environmentInput, environmentId } = args;
10
22
  const transport = createCliBridgeTransport(options);
@@ -27,9 +39,7 @@ export function createCliBridgeEnvironment(args) {
27
39
  const stream = async function* (turn) {
28
40
  if (destroyed)
29
41
  throw new Error("cli-bridge environment is destroyed");
30
- if (!args.allowDispatch) {
31
- throw new Error("a reconstructed cli-bridge environment can only control an existing run");
32
- }
42
+ assertCliBridgeTurnAllowed(args.allowDispatch, "environment.stream()");
33
43
  if (args.selectedModel !== undefined &&
34
44
  args.capabilities.retainedControl !== undefined) {
35
45
  assertCliBridgeTurnModel(options, environmentInput, turn, args.selectedModel);
@@ -48,6 +58,7 @@ export function createCliBridgeEnvironment(args) {
48
58
  return {
49
59
  id: environmentId,
50
60
  provider: providerName,
61
+ ...(args.creation === undefined ? {} : { creation: args.creation }),
51
62
  capabilities: args.capabilities,
52
63
  ...(environmentInput.name ? { name: environmentInput.name } : {}),
53
64
  status: async (statusOptions) => {
@@ -58,9 +69,7 @@ export function createCliBridgeEnvironment(args) {
58
69
  async dispatch(turn) {
59
70
  if (destroyed)
60
71
  throw new Error("cli-bridge environment is destroyed");
61
- if (!args.allowDispatch) {
62
- throw new Error("a reconstructed cli-bridge environment cannot dispatch new work");
63
- }
72
+ assertCliBridgeTurnAllowed(args.allowDispatch, "environment.dispatch()");
64
73
  if (args.selectedModel !== undefined &&
65
74
  args.capabilities.retainedControl !== undefined) {
66
75
  assertCliBridgeTurnModel(options, environmentInput, turn, args.selectedModel);
@@ -229,9 +238,7 @@ function createCliBridgeSession(args) {
229
238
  return result;
230
239
  },
231
240
  async prompt(input) {
232
- if (!args.allowPrompt) {
233
- throw new Error("a reconstructed cli-bridge session cannot start another turn");
234
- }
241
+ assertCliBridgeTurnAllowed(args.allowPrompt, "session.prompt()");
235
242
  if (input.sessionId && input.sessionId !== args.id) {
236
243
  throw new Error(`cli-bridge session "${args.id}" cannot prompt session "${input.sessionId}"`);
237
244
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-provider-cli-bridge",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "AgentEnvironmentProvider adapter for cli-bridge retained sessions and one-shot turns",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -59,7 +59,7 @@
59
59
  "LICENSE"
60
60
  ],
61
61
  "dependencies": {
62
- "@tangle-network/agent-interface": "^1.3.0",
62
+ "@tangle-network/agent-interface": "^1.4.0",
63
63
  "undici": "7.29.0"
64
64
  },
65
65
  "devDependencies": {