@loomcycle/client 0.34.0 → 1.1.1
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 +52 -1
- package/dist/cjs/client.js +125 -0
- package/dist/cjs/index.js +8 -1
- package/dist/cjs/interactive.js +76 -0
- package/dist/client.d.ts +95 -1
- package/dist/client.js +125 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +6 -0
- package/dist/interactive.d.ts +53 -0
- package/dist/interactive.js +72 -0
- package/dist/types.d.ts +61 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ TypeScript client for the [loomcycle](https://github.com/denn-gubsky/loomcycle)
|
|
|
12
12
|
|
|
13
13
|
### What's new since v0.8.18
|
|
14
14
|
|
|
15
|
+
- **`interactiveSession` / `sendRunInput` / `streamRunByID` + the `interactive` flag** (v1.1.1, RFC AI) — the interactive agentic session, the adapter port of the Web UI's run terminal. Pass `interactive: true` to `runStreaming` / `continueSession` to start a **persistent** run that parks at end_turn (an `awaiting_input` frame) instead of ending; **`sendRunInput(runId, text)`** steers it (the response arrives on the same stream); **`streamRunByID(runId, {fromSeq})`** re-attaches by run_id (the operator's prior turns replay as `steer` events, `user_input.source === "replay"`, so a cold client — e.g. another device — reconstructs the whole conversation). The high-level **`client.interactiveSession({agent, segments})`** returns an `InteractiveSession` with `events()` / `send()` / `cancel()`; **`attachInteractiveSession(runId)`** resumes one. The `AgentEvent` union gains `awaiting_input` / `steer` / `context_compaction`.
|
|
16
|
+
- **`volumeDef` / `listVolumes` / `listEphemeralVolumes`** (v0.35.0, RFC AH) — the dynamic filesystem-volume surface. `volumeDef` is the op-discriminated substrate tool (`create` / `get` / `list` / `delete` / `purge`); a Volume is **flat** (a pointer to mutable on-disk state, not a versioned def), so `delete` unmaps + leaves files while `purge` removes the row **and** the directory tree — there is no retire/promote/fork. Tenant-confined (`ScopeTenant`): the runtime derives the path inside an operator-blessed `dynamic_root`, so you pass `{name, mode}`, never a host path. `listVolumes()` / `listEphemeralVolumes()` return the tenant's persistent + live run-scoped volumes; host paths are redacted (`""`) for a non-operator caller.
|
|
15
17
|
- **`ensureMcpServer` / `mcpServerDefVerify`** (v0.18.0) — typed ergonomics for the dynamic-MCP dedup flow. `ensureMcpServer({name, url, headers?, rediscover?})` registers a callback MCP server **idempotently**: it runs `create` (a no-op in loomcycle ≥ v0.18.0 when the active def already carries identical content) plus an optional `rediscover` (a no-op on unchanged tools), and returns `{defId, version, changed, discoveredToolCount?}` — so a consumer re-registering on every startup gets `changed: false` once its registration content is stable. Keep `${run.*}` / `${LOOMCYCLE_*}` header placeholders **literal** (don't bake a per-restart token) or the content varies each boot and dedup can't engage. `mcpServerDefVerify(name, sha)` is the typed `op: verify` wrapper (`matches: true` = no-op signal).
|
|
16
18
|
- **`operatorTokenDef` / `whoami` + tenant-scoped reads** (v0.17.0, RFC L) — the OSS multi-tenant authorization surface. `operatorTokenDef` is the op-discriminated admin tool over the `OperatorTokenDef` substrate (create / rotate / retire per-principal bearer tokens); `whoami()` returns the authoritative `(tenant, subject, scopes, is_admin)` resolved from the calling bearer; `listUsers({ tenant })` / `listUserAgents(userId, { tenant })` accept a super-admin tenant-focus (ignored server-side for a tenant principal — its own tenant is forced).
|
|
17
19
|
|
|
@@ -104,8 +106,31 @@ All methods are async / return `Promise<T>` unless noted; streaming methods retu
|
|
|
104
106
|
|
|
105
107
|
| Method | Returns | Notes |
|
|
106
108
|
|---|---|---|
|
|
107
|
-
| `runStreaming(opts: RunOptions)` | `AsyncIterable<AgentEvent>` | Server-streams provider events for a fresh run. |
|
|
109
|
+
| `runStreaming(opts: RunOptions)` | `AsyncIterable<AgentEvent>` | Server-streams provider events for a fresh run. `interactive: true` parks at end_turn for steering (RFC AI). |
|
|
108
110
|
| `continueSession(opts: ContinueOptions)` | `AsyncIterable<AgentEvent>` | Continues an existing session. |
|
|
111
|
+
| `sendRunInput(runId, text)` | `{run_id, delivered}` | RFC AI — steer a live interactive run (`POST /v1/runs/{id}/input`). |
|
|
112
|
+
| `streamRunByID(runId, {fromSeq})` | `AsyncIterable<AgentEvent>` | RFC AI — re-attach by run_id (`GET /v1/runs/{id}/stream`); replays operator turns as `steer` events. |
|
|
113
|
+
| `interactiveSession(opts)` / `attachInteractiveSession(runId)` | `InteractiveSession` | RFC AI — high-level driver: `events()` / `send()` / `cancel()`. |
|
|
114
|
+
|
|
115
|
+
### Interactive sessions (RFC AI)
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const sess = client.interactiveSession({
|
|
119
|
+
agent: "assistant",
|
|
120
|
+
segments: [{ role: "user", content: [{ type: "trusted-text", text: "help me debug" }] }],
|
|
121
|
+
});
|
|
122
|
+
for await (const ev of sess.events()) {
|
|
123
|
+
if (ev.type === "text") process.stdout.write(ev.text ?? "");
|
|
124
|
+
if (ev.type === "awaiting_input") {
|
|
125
|
+
await sess.send(await prompt("you> ")); // steers; response arrives on this same loop
|
|
126
|
+
}
|
|
127
|
+
if (ev.type === "done") break;
|
|
128
|
+
}
|
|
129
|
+
// later, from anywhere (another process / device): resume the same run
|
|
130
|
+
const resumed = client.attachInteractiveSession(sess.runId);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The low-level primitives (`runStreaming({interactive:true})` + `sendRunInput` + `streamRunByID`) are the escape hatch if you'd rather drive the stream yourself.
|
|
109
134
|
|
|
110
135
|
### Agent metadata
|
|
111
136
|
|
|
@@ -274,6 +299,32 @@ Operations on AgentDef: `create` / `fork` / `get` / `list` / `promote` / `retire
|
|
|
274
299
|
|
|
275
300
|
Refusals throw `SubstrateToolRefusedError` (a scope deny / empty body / allowed-tools widening); transport failures throw the usual typed errors (`AuthError`, `UnavailableError`, etc.).
|
|
276
301
|
|
|
302
|
+
### Dynamic filesystem volumes (v0.35.0 — RFC AH)
|
|
303
|
+
|
|
304
|
+
Per-tenant, ro/rw filesystem roots an agent can be bound to. `volumeDef` provisions and manages them at runtime; the two list methods render the volume universe. Tenant-confined (`ScopeTenant`).
|
|
305
|
+
|
|
306
|
+
| Method | Returns | Notes |
|
|
307
|
+
|---|---|---|
|
|
308
|
+
| `volumeDef(input)` | `Promise<SubstrateToolResponse>` | Op-discriminated (`create` / `get` / `list` / `delete` / `purge`). Mirrors `POST /v1/_volumedef`. |
|
|
309
|
+
| `listVolumes()` | `Promise<PersistentVolumesResponse>` | Static (read-only floor) + the tenant's dynamic volumes. `GET /v1/_volumes`. |
|
|
310
|
+
| `listEphemeralVolumes()` | `Promise<EphemeralVolumesResponse>` | Live, run-scoped volumes (auto-purged at run completion). `GET /v1/_volumes/ephemeral`. |
|
|
311
|
+
|
|
312
|
+
A Volume is **flat** — a pointer to mutable on-disk state, not a versioned definition — so the op set is `create` / `get` / `list` / `delete` / `purge` (no retire/promote/fork). The runtime DERIVES the path inside an operator-blessed `dynamic_root` (`<root>/<tenant>/<name>`), so you pass `{name, mode}` and never a host path:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
// Provision a writable per-tenant volume (the runtime mkdir's it).
|
|
316
|
+
await client.volumeDef({ op: "create", name: "repo-a", mode: "rw" });
|
|
317
|
+
|
|
318
|
+
// Unmap (keeps files) vs. destroy (RemoveAll's the tree).
|
|
319
|
+
await client.volumeDef({ op: "delete", name: "repo-a" }); // non-destructive
|
|
320
|
+
await client.volumeDef({ op: "purge", name: "repo-a" }); // destructive
|
|
321
|
+
|
|
322
|
+
const { entries } = await client.listVolumes();
|
|
323
|
+
// entries[].path is "" (redacted) unless the caller is operator-equivalent.
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Refusals throw `SubstrateToolRefusedError` (collision with a static volume name, no `dynamic_root` configured, cross-tenant); transport failures throw the usual typed errors.
|
|
327
|
+
|
|
277
328
|
### Channels + run-state stream (v0.9.x n8n Phase 0)
|
|
278
329
|
|
|
279
330
|
Two substrate-side surfaces added in the n8n integration's Phase 0 wire-API work. Useful for any orchestrator (not just n8n) that needs to see channel state or subscribe to run-state transitions.
|
package/dist/cjs/client.js
CHANGED
|
@@ -28,6 +28,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
28
28
|
exports.LoomcycleClient = void 0;
|
|
29
29
|
const fetch_helpers_js_1 = require("./fetch-helpers.js");
|
|
30
30
|
const stream_js_1 = require("./stream.js");
|
|
31
|
+
const interactive_js_1 = require("./interactive.js");
|
|
31
32
|
/** samplingToWire maps the camelCase SamplingOptions to the snake_case wire
|
|
32
33
|
* object, omitting unset fields so they inherit the agent's value. */
|
|
33
34
|
function samplingToWire(s) {
|
|
@@ -107,6 +108,8 @@ function runBody(opts) {
|
|
|
107
108
|
body.sampling = samplingToWire(opts.sampling);
|
|
108
109
|
if (opts.compaction !== undefined)
|
|
109
110
|
body.compaction = compactionToWire(opts.compaction);
|
|
111
|
+
if (opts.interactive !== undefined)
|
|
112
|
+
body.interactive = opts.interactive;
|
|
110
113
|
return body;
|
|
111
114
|
}
|
|
112
115
|
class LoomcycleClient {
|
|
@@ -189,8 +192,65 @@ class LoomcycleClient {
|
|
|
189
192
|
body.sampling = samplingToWire(opts.sampling);
|
|
190
193
|
if (opts.compaction !== undefined)
|
|
191
194
|
body.compaction = compactionToWire(opts.compaction);
|
|
195
|
+
if (opts.interactive !== undefined)
|
|
196
|
+
body.interactive = opts.interactive;
|
|
192
197
|
yield* this.streamSSE(`/v1/sessions/${encodeURIComponent(opts.sessionId)}/messages`, body, opts.signal, opts.debug);
|
|
193
198
|
}
|
|
199
|
+
/** Push an operator steering message into a LIVE interactive run (RFC AI).
|
|
200
|
+
* Mirrors `POST /v1/runs/{run_id}/input`. The run must be in-flight —
|
|
201
|
+
* parked at end_turn awaiting input, or mid-turn (the message is drained
|
|
202
|
+
* at the next iteration boundary). Does NOT open a stream; the operator's
|
|
203
|
+
* turn + the model's response arrive on the run's OWN event stream
|
|
204
|
+
* (the open `runStreaming` iterator, or a `streamRunByID` re-attach).
|
|
205
|
+
* Returns `{ run_id, delivered }`.
|
|
206
|
+
*
|
|
207
|
+
* Raises {@link UnavailableError} (503, steering off / no run),
|
|
208
|
+
* {@link AuthError} (401). A full steer queue surfaces as a 429. */
|
|
209
|
+
async sendRunInput(runId, text, opts) {
|
|
210
|
+
return (0, fetch_helpers_js_1.postJSON)(this.ctx, `/v1/runs/${encodeURIComponent(runId)}/input`, { text }, opts);
|
|
211
|
+
}
|
|
212
|
+
/** Re-attach to a run's event stream by `run_id` (RFC AI), replaying from
|
|
213
|
+
* `fromSeq` (default 0) then live-tailing — the gRPC-free twin of the Web
|
|
214
|
+
* UI's resume-in-terminal. Mirrors `GET /v1/runs/{run_id}/stream`. The
|
|
215
|
+
* operator's own prior turns are replayed as `steer` events
|
|
216
|
+
* (`user_input.source === "replay"`), so a cold client — e.g. resuming on
|
|
217
|
+
* another device — reconstructs the whole conversation. A PARKED
|
|
218
|
+
* interactive run keeps streaming until it ends or `signal` aborts; a
|
|
219
|
+
* disconnect does NOT stop the run.
|
|
220
|
+
*
|
|
221
|
+
* The first frame is an `agent` side-channel carrying the run's
|
|
222
|
+
* agent_id / session_id (for `sendRunInput` / `cancelAgent`). */
|
|
223
|
+
async *streamRunByID(runId, opts) {
|
|
224
|
+
const q = opts?.fromSeq ? `?from_seq=${opts.fromSeq}` : "";
|
|
225
|
+
yield* this.streamSSEGet(`/v1/runs/${encodeURIComponent(runId)}/stream${q}`, opts?.signal);
|
|
226
|
+
}
|
|
227
|
+
/** Start a high-level INTERACTIVE session (RFC AI) — the ergonomic driver
|
|
228
|
+
* for a persistent run you converse with turn by turn. Starts an
|
|
229
|
+
* `interactive` run and returns an {@link InteractiveSession} whose
|
|
230
|
+
* `events()` you iterate and whose `send()` / `cancel()` steer it. See
|
|
231
|
+
* {@link InteractiveSession}. For the raw primitives, use
|
|
232
|
+
* {@link LoomcycleClient.runStreaming} with `interactive: true` +
|
|
233
|
+
* {@link LoomcycleClient.sendRunInput}. */
|
|
234
|
+
interactiveSession(opts) {
|
|
235
|
+
const source = this.runStreaming({ ...opts, interactive: true });
|
|
236
|
+
return new interactive_js_1.InteractiveSession(source, {
|
|
237
|
+
sendRunInput: (rid, t) => this.sendRunInput(rid, t),
|
|
238
|
+
cancelAgent: (aid) => this.cancelAgent(aid),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/** Re-attach to a detached interactive run by `run_id` as a high-level
|
|
242
|
+
* {@link InteractiveSession} — e.g. resuming on another device. The run_id
|
|
243
|
+
* is known up front, so `send()` works immediately. Replays the
|
|
244
|
+
* conversation (incl. the operator's prior turns, RFC AI) then live-tails. */
|
|
245
|
+
attachInteractiveSession(runId, opts) {
|
|
246
|
+
const source = this.streamRunByID(runId, opts);
|
|
247
|
+
const session = new interactive_js_1.InteractiveSession(source, {
|
|
248
|
+
sendRunInput: (rid, t) => this.sendRunInput(rid, t),
|
|
249
|
+
cancelAgent: (aid) => this.cancelAgent(aid),
|
|
250
|
+
});
|
|
251
|
+
session.runId = runId;
|
|
252
|
+
return session;
|
|
253
|
+
}
|
|
194
254
|
/**
|
|
195
255
|
* Spawn N fresh runs concurrently in ONE call (RFC Y external fan-out) and
|
|
196
256
|
* resolve once they ALL settle, returning the combined index-aligned
|
|
@@ -744,6 +804,48 @@ class LoomcycleClient {
|
|
|
744
804
|
async operatorTokenDef(input, opts) {
|
|
745
805
|
return (0, fetch_helpers_js_1.postJSON)(this.ctx, "/v1/_operatortokendef", input, opts);
|
|
746
806
|
}
|
|
807
|
+
// ---- RFC AH (v0.35.0) dynamic filesystem volumes ----
|
|
808
|
+
/** Invoke the RFC AH VolumeDef substrate tool over HTTP. Provision +
|
|
809
|
+
* manage CONFINED, per-tenant filesystem volumes at runtime.
|
|
810
|
+
* Tenant-confined (ScopeTenant, not operator-admin-only): the runtime
|
|
811
|
+
* stamps the caller's authoritative tenant and opaque-404s cross-tenant.
|
|
812
|
+
*
|
|
813
|
+
* Op-discriminated input. A VolumeDef is FLAT (a pointer to mutable
|
|
814
|
+
* on-disk state, not a versioned definition), so the op set is
|
|
815
|
+
* `{op: "create" | "get" | "list" | "delete" | "purge", ...}` — NOT the
|
|
816
|
+
* content-addressed retire/promote/fork of the other Def families:
|
|
817
|
+
* - `create` — `{op, name, mode}`. The runtime DERIVES the path inside the
|
|
818
|
+
* operator-blessed `dynamic_root` (`<root>/<tenant>/<name>`) and
|
|
819
|
+
* `MkdirAll`s it; you never supply a host path.
|
|
820
|
+
* - `delete` — non-destructive: drops the mapping, leaves files on disk.
|
|
821
|
+
* - `purge` — destructive: drops the mapping AND `RemoveAll`s the tree.
|
|
822
|
+
*
|
|
823
|
+
* Raises {@link SubstrateToolRefusedError} on tool-level refusals
|
|
824
|
+
* (collision with a static volume name, no `dynamic_root` configured,
|
|
825
|
+
* cross-tenant); {@link InvalidArgumentError} on 400; {@link AuthError}
|
|
826
|
+
* on 401. */
|
|
827
|
+
async volumeDef(input, opts) {
|
|
828
|
+
return (0, fetch_helpers_js_1.postJSON)(this.ctx, "/v1/_volumedef", input, opts);
|
|
829
|
+
}
|
|
830
|
+
/** List the PERSISTENT volume universe for the caller's tenant — every
|
|
831
|
+
* static volume (the shared bind floor, `source: "static"`, read-only)
|
|
832
|
+
* plus the tenant's own dynamic VolumeDefs (`source: "dynamic"`). Host
|
|
833
|
+
* paths are redacted (`""`) for a non-operator (tenant) caller.
|
|
834
|
+
*
|
|
835
|
+
* Wraps `GET /v1/_volumes` (RFC AH Phase 4). Bearer-authed; tenant-scoped
|
|
836
|
+
* from the authoritative principal. */
|
|
837
|
+
async listVolumes(opts) {
|
|
838
|
+
return (0, fetch_helpers_js_1.jsonFetch)(this.ctx, "/v1/_volumes", opts);
|
|
839
|
+
}
|
|
840
|
+
/** List the LIVE ephemeral (run-scoped) volumes for the caller's tenant —
|
|
841
|
+
* created mid-run, inherited by sub-agents, auto-purged when the run
|
|
842
|
+
* completes. Host paths are redacted (`""`) for a non-operator caller.
|
|
843
|
+
*
|
|
844
|
+
* Wraps `GET /v1/_volumes/ephemeral` (RFC AH Phase 4). Bearer-authed;
|
|
845
|
+
* tenant-scoped from the authoritative principal. */
|
|
846
|
+
async listEphemeralVolumes(opts) {
|
|
847
|
+
return (0, fetch_helpers_js_1.jsonFetch)(this.ctx, "/v1/_volumes/ephemeral", opts);
|
|
848
|
+
}
|
|
747
849
|
// ---- v0.10.3 Library v2 enumeration (read-only, merged yaml+substrate) ----
|
|
748
850
|
/** List every agent the runtime knows about — yaml-static + dynamic
|
|
749
851
|
* AgentDefs merged into one envelope per name. Each entry carries
|
|
@@ -875,6 +977,29 @@ class LoomcycleClient {
|
|
|
875
977
|
* events AND a `{ type: "_meta", meta_subtype: "stream_close",
|
|
876
978
|
* meta_reason }` on EOF / abort / error. The default is silent
|
|
877
979
|
* (matches pre-v0.9.x behaviour). */
|
|
980
|
+
/** GET-based SSE stream (RFC AI re-attach). The interactive re-attach
|
|
981
|
+
* endpoint is a GET with a query string, not a POST body, so it can't reuse
|
|
982
|
+
* streamSSE; the parse + error handling are identical otherwise. No debug
|
|
983
|
+
* shape — re-attach is a plain tail. */
|
|
984
|
+
async *streamSSEGet(path, signal) {
|
|
985
|
+
const headers = {
|
|
986
|
+
Accept: "text/event-stream, application/json",
|
|
987
|
+
};
|
|
988
|
+
if (this.ctx.authToken)
|
|
989
|
+
headers.Authorization = `Bearer ${this.ctx.authToken}`;
|
|
990
|
+
const resp = await this.ctx.fetchImpl(this.ctx.baseUrl + path, {
|
|
991
|
+
method: "GET",
|
|
992
|
+
headers,
|
|
993
|
+
signal,
|
|
994
|
+
});
|
|
995
|
+
if (!resp.ok) {
|
|
996
|
+
await (0, fetch_helpers_js_1.raiseFromResponse)(resp);
|
|
997
|
+
}
|
|
998
|
+
if (!resp.body) {
|
|
999
|
+
throw new Error("loomcycle: response has no body");
|
|
1000
|
+
}
|
|
1001
|
+
yield* (0, stream_js_1.parseSSE)(resp.body.getReader());
|
|
1002
|
+
}
|
|
878
1003
|
async *streamSSE(path, body, signal, debug) {
|
|
879
1004
|
const headers = {
|
|
880
1005
|
"Content-Type": "application/json",
|
package/dist/cjs/index.js
CHANGED
|
@@ -53,6 +53,11 @@
|
|
|
53
53
|
* ensureMcpServer(opts): Promise<EnsureMcpServerResult> // v0.18.0 — idempotent register-if-changed
|
|
54
54
|
* scheduleDef(input): Promise<SubstrateToolResponse>
|
|
55
55
|
*
|
|
56
|
+
* // Dynamic filesystem volumes (v0.35.0 — RFC AH; tenant-confined)
|
|
57
|
+
* volumeDef(input): Promise<SubstrateToolResponse> // create/get/list/delete/purge
|
|
58
|
+
* listVolumes(): Promise<PersistentVolumesResponse>
|
|
59
|
+
* listEphemeralVolumes(): Promise<EphemeralVolumesResponse>
|
|
60
|
+
*
|
|
56
61
|
* // Library v2 enumeration (v0.10.3 — yaml+substrate merged)
|
|
57
62
|
* listLibraryAgents(): Promise<LibraryListResponse<LibraryAgentDefinition>>
|
|
58
63
|
* listLibrarySkills(): Promise<LibraryListResponse<LibrarySkillDefinition>>
|
|
@@ -83,9 +88,11 @@
|
|
|
83
88
|
* See `adapters/ts/README.md` for usage examples.
|
|
84
89
|
*/
|
|
85
90
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
86
|
-
exports.UnavailableError = exports.SubstrateToolRefusedError = exports.SnapshotVersionError = exports.SnapshotTooLargeError = exports.SnapshotNotFoundError = exports.SessionNotFoundError = exports.SessionBusyError = exports.PerUserQuotaExhaustedError = exports.PauseNotConfiguredError = exports.NotPausedError = exports.LoomcycleError = exports.ChannelCursorRegressionError = exports.InvalidArgumentError = exports.NotFoundError = exports.HookNotFoundError = exports.BackpressureError = exports.AuthError = exports.AlreadyPausingError = exports.AgentNotFoundError = exports.AgentIDInUseError = exports.LoomcycleClient = void 0;
|
|
91
|
+
exports.UnavailableError = exports.SubstrateToolRefusedError = exports.SnapshotVersionError = exports.SnapshotTooLargeError = exports.SnapshotNotFoundError = exports.SessionNotFoundError = exports.SessionBusyError = exports.PerUserQuotaExhaustedError = exports.PauseNotConfiguredError = exports.NotPausedError = exports.LoomcycleError = exports.ChannelCursorRegressionError = exports.InvalidArgumentError = exports.NotFoundError = exports.HookNotFoundError = exports.BackpressureError = exports.AuthError = exports.AlreadyPausingError = exports.AgentNotFoundError = exports.AgentIDInUseError = exports.InteractiveSession = exports.LoomcycleClient = void 0;
|
|
87
92
|
var client_js_1 = require("./client.js");
|
|
88
93
|
Object.defineProperty(exports, "LoomcycleClient", { enumerable: true, get: function () { return client_js_1.LoomcycleClient; } });
|
|
94
|
+
var interactive_js_1 = require("./interactive.js");
|
|
95
|
+
Object.defineProperty(exports, "InteractiveSession", { enumerable: true, get: function () { return interactive_js_1.InteractiveSession; } });
|
|
89
96
|
var errors_js_1 = require("./errors.js");
|
|
90
97
|
Object.defineProperty(exports, "AgentIDInUseError", { enumerable: true, get: function () { return errors_js_1.AgentIDInUseError; } });
|
|
91
98
|
Object.defineProperty(exports, "AgentNotFoundError", { enumerable: true, get: function () { return errors_js_1.AgentNotFoundError; } });
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InteractiveSession = void 0;
|
|
4
|
+
/** A high-level driver for an interactive agentic session (RFC AI) — the
|
|
5
|
+
* adapter port of the Web UI's run terminal. It wraps the underlying event
|
|
6
|
+
* stream (a fresh `interactive` run, or a `streamRunByID` re-attach), taps
|
|
7
|
+
* each frame to track the run's tracking IDs + parked state, and routes
|
|
8
|
+
* operator input + cancel through the client.
|
|
9
|
+
*
|
|
10
|
+
* Typical loop:
|
|
11
|
+
* ```ts
|
|
12
|
+
* const sess = client.interactiveSession({ agent: "chat", segments: [...] });
|
|
13
|
+
* for await (const ev of sess.events()) {
|
|
14
|
+
* if (ev.type === "text") process.stdout.write(ev.text ?? "");
|
|
15
|
+
* if (ev.type === "awaiting_input") await sess.send(await prompt("you> "));
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
* `send()` does NOT open a new stream — the operator's turn and the model's
|
|
19
|
+
* response arrive on the same `events()` iterator. */
|
|
20
|
+
class InteractiveSession {
|
|
21
|
+
source;
|
|
22
|
+
ops;
|
|
23
|
+
/** The run's id, from the first `agent` frame (set up-front on re-attach). */
|
|
24
|
+
runId = "";
|
|
25
|
+
/** The run's agent_id (for cancel), from the first `agent` frame. */
|
|
26
|
+
agentId = "";
|
|
27
|
+
/** The run's session_id, from the `session` / `agent` frames. */
|
|
28
|
+
sessionId = "";
|
|
29
|
+
/** True after an `awaiting_input` frame; cleared on the next activity. */
|
|
30
|
+
awaitingInput = false;
|
|
31
|
+
constructor(source, ops) {
|
|
32
|
+
this.source = source;
|
|
33
|
+
this.ops = ops;
|
|
34
|
+
}
|
|
35
|
+
/** The merged event stream. Consume with `for await`. Each frame updates the
|
|
36
|
+
* session's runId / agentId / sessionId / awaitingInput BEFORE it is
|
|
37
|
+
* yielded, so a consumer that reacts to `awaiting_input` can immediately
|
|
38
|
+
* `send()`. */
|
|
39
|
+
async *events() {
|
|
40
|
+
for await (const ev of this.source) {
|
|
41
|
+
if (ev.type === "agent") {
|
|
42
|
+
if (ev.run_id)
|
|
43
|
+
this.runId = ev.run_id;
|
|
44
|
+
if (ev.agent_id)
|
|
45
|
+
this.agentId = ev.agent_id;
|
|
46
|
+
if (ev.session_id)
|
|
47
|
+
this.sessionId = ev.session_id;
|
|
48
|
+
}
|
|
49
|
+
else if (ev.type === "session" && ev.session_id) {
|
|
50
|
+
this.sessionId = ev.session_id;
|
|
51
|
+
}
|
|
52
|
+
this.awaitingInput = ev.type === "awaiting_input";
|
|
53
|
+
yield ev;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Steer the live run with an operator message. The response arrives on the
|
|
57
|
+
* `events()` stream (NOT a new stream). Returns the server's `delivered`
|
|
58
|
+
* flag. Throws if the run_id isn't known yet — for a fresh session, consume
|
|
59
|
+
* `events()` until the `agent` frame (or the first `awaiting_input`) first;
|
|
60
|
+
* a re-attached session has the run_id up front. */
|
|
61
|
+
async send(text) {
|
|
62
|
+
if (!this.runId) {
|
|
63
|
+
throw new Error("loomcycle: run_id not known yet — consume events() until the `agent` frame before send()");
|
|
64
|
+
}
|
|
65
|
+
const { delivered } = await this.ops.sendRunInput(this.runId, text);
|
|
66
|
+
this.awaitingInput = false;
|
|
67
|
+
return delivered;
|
|
68
|
+
}
|
|
69
|
+
/** Cancel the run (and its sub-agents). No-op if the agent_id isn't known
|
|
70
|
+
* yet (nothing to cancel). */
|
|
71
|
+
async cancel() {
|
|
72
|
+
if (this.agentId)
|
|
73
|
+
await this.ops.cancelAgent(this.agentId);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.InteractiveSession = InteractiveSession;
|
package/dist/client.d.ts
CHANGED
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* via fetch-helpers.ts:raiseFromResponse — see README.md for the
|
|
24
24
|
* full mapping table.
|
|
25
25
|
*/
|
|
26
|
-
import
|
|
26
|
+
import { InteractiveSession } from "./interactive.js";
|
|
27
|
+
import type { Agent, AgentEvent, AgentStatus, CancelAgentResult, ClientOptions, ContinueOptions, CreateSnapshotOptions, EnsureCodeAgentOptions, EnsureCodeAgentResult, EnsureMcpServerOptions, EnsureMcpServerResult, HealthResponse, Hook, InterruptListResponse, InterruptStatus, AckChannelOptions, AwaitChannelsOptions, BroadcastChannelsOptions, ChannelAckResult, ChannelAwaitResult, ChannelBroadcastResult, ChannelDescriptor, ChannelPeekResult, ChannelPublishResult, ChannelPurgeResult, ChannelSubscribeResult, CreateChannelOptions, ListChannelsResponse, PeekChannelOptions, PublishChannelOptions, SetMemoryEntryOptions, SetMemoryEntryResponse, SubscribeChannelOptions, UpdateChannelOptions, LibraryAgentDefinition, LibraryListResponse, LibraryMcpServerDefinition, LibrarySkillDefinition, ListUsersResponse, LLMChatOptions, LLMChatResponse, LLMChatStreamItem, LLMEmbeddingsOptions, LLMEmbeddingsResponse, MCPServerDefVerifyResult, MemoryEntriesResponse, MemoryEntryResponse, MemoryScopeIDsResponse, MemoryScopesResponse, PauseResult, PersistentVolumesResponse, EphemeralVolumesResponse, RegisterHookOptions, RegisterHookResponse, ResolveInterruptOptions, ResumeResult, ResolverMatrix, CompactRunResult, RunBatchOptions, RunBatchResult, RunOptions, RunStateStreamItem, RuntimeStateResponse, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotRestoreResponse, StreamUserRunStatesOptions, SubstrateToolInput, SubstrateToolResponse, TranscriptResponse, WhoamiResponse } from "./types.js";
|
|
27
28
|
export declare class LoomcycleClient {
|
|
28
29
|
private ctx;
|
|
29
30
|
constructor(opts?: ClientOptions);
|
|
@@ -67,6 +68,53 @@ export declare class LoomcycleClient {
|
|
|
67
68
|
* `_meta` open/close events.
|
|
68
69
|
*/
|
|
69
70
|
continueSession(opts: ContinueOptions): AsyncIterable<AgentEvent>;
|
|
71
|
+
/** Push an operator steering message into a LIVE interactive run (RFC AI).
|
|
72
|
+
* Mirrors `POST /v1/runs/{run_id}/input`. The run must be in-flight —
|
|
73
|
+
* parked at end_turn awaiting input, or mid-turn (the message is drained
|
|
74
|
+
* at the next iteration boundary). Does NOT open a stream; the operator's
|
|
75
|
+
* turn + the model's response arrive on the run's OWN event stream
|
|
76
|
+
* (the open `runStreaming` iterator, or a `streamRunByID` re-attach).
|
|
77
|
+
* Returns `{ run_id, delivered }`.
|
|
78
|
+
*
|
|
79
|
+
* Raises {@link UnavailableError} (503, steering off / no run),
|
|
80
|
+
* {@link AuthError} (401). A full steer queue surfaces as a 429. */
|
|
81
|
+
sendRunInput(runId: string, text: string, opts?: {
|
|
82
|
+
signal?: AbortSignal;
|
|
83
|
+
}): Promise<{
|
|
84
|
+
run_id: string;
|
|
85
|
+
delivered: boolean;
|
|
86
|
+
}>;
|
|
87
|
+
/** Re-attach to a run's event stream by `run_id` (RFC AI), replaying from
|
|
88
|
+
* `fromSeq` (default 0) then live-tailing — the gRPC-free twin of the Web
|
|
89
|
+
* UI's resume-in-terminal. Mirrors `GET /v1/runs/{run_id}/stream`. The
|
|
90
|
+
* operator's own prior turns are replayed as `steer` events
|
|
91
|
+
* (`user_input.source === "replay"`), so a cold client — e.g. resuming on
|
|
92
|
+
* another device — reconstructs the whole conversation. A PARKED
|
|
93
|
+
* interactive run keeps streaming until it ends or `signal` aborts; a
|
|
94
|
+
* disconnect does NOT stop the run.
|
|
95
|
+
*
|
|
96
|
+
* The first frame is an `agent` side-channel carrying the run's
|
|
97
|
+
* agent_id / session_id (for `sendRunInput` / `cancelAgent`). */
|
|
98
|
+
streamRunByID(runId: string, opts?: {
|
|
99
|
+
fromSeq?: number;
|
|
100
|
+
signal?: AbortSignal;
|
|
101
|
+
}): AsyncIterable<AgentEvent>;
|
|
102
|
+
/** Start a high-level INTERACTIVE session (RFC AI) — the ergonomic driver
|
|
103
|
+
* for a persistent run you converse with turn by turn. Starts an
|
|
104
|
+
* `interactive` run and returns an {@link InteractiveSession} whose
|
|
105
|
+
* `events()` you iterate and whose `send()` / `cancel()` steer it. See
|
|
106
|
+
* {@link InteractiveSession}. For the raw primitives, use
|
|
107
|
+
* {@link LoomcycleClient.runStreaming} with `interactive: true` +
|
|
108
|
+
* {@link LoomcycleClient.sendRunInput}. */
|
|
109
|
+
interactiveSession(opts: Omit<RunOptions, "interactive">): InteractiveSession;
|
|
110
|
+
/** Re-attach to a detached interactive run by `run_id` as a high-level
|
|
111
|
+
* {@link InteractiveSession} — e.g. resuming on another device. The run_id
|
|
112
|
+
* is known up front, so `send()` works immediately. Replays the
|
|
113
|
+
* conversation (incl. the operator's prior turns, RFC AI) then live-tails. */
|
|
114
|
+
attachInteractiveSession(runId: string, opts?: {
|
|
115
|
+
fromSeq?: number;
|
|
116
|
+
signal?: AbortSignal;
|
|
117
|
+
}): InteractiveSession;
|
|
70
118
|
/**
|
|
71
119
|
* Spawn N fresh runs concurrently in ONE call (RFC Y external fan-out) and
|
|
72
120
|
* resolve once they ALL settle, returning the combined index-aligned
|
|
@@ -486,6 +534,47 @@ export declare class LoomcycleClient {
|
|
|
486
534
|
operatorTokenDef(input: SubstrateToolInput, opts?: {
|
|
487
535
|
signal?: AbortSignal;
|
|
488
536
|
}): Promise<SubstrateToolResponse>;
|
|
537
|
+
/** Invoke the RFC AH VolumeDef substrate tool over HTTP. Provision +
|
|
538
|
+
* manage CONFINED, per-tenant filesystem volumes at runtime.
|
|
539
|
+
* Tenant-confined (ScopeTenant, not operator-admin-only): the runtime
|
|
540
|
+
* stamps the caller's authoritative tenant and opaque-404s cross-tenant.
|
|
541
|
+
*
|
|
542
|
+
* Op-discriminated input. A VolumeDef is FLAT (a pointer to mutable
|
|
543
|
+
* on-disk state, not a versioned definition), so the op set is
|
|
544
|
+
* `{op: "create" | "get" | "list" | "delete" | "purge", ...}` — NOT the
|
|
545
|
+
* content-addressed retire/promote/fork of the other Def families:
|
|
546
|
+
* - `create` — `{op, name, mode}`. The runtime DERIVES the path inside the
|
|
547
|
+
* operator-blessed `dynamic_root` (`<root>/<tenant>/<name>`) and
|
|
548
|
+
* `MkdirAll`s it; you never supply a host path.
|
|
549
|
+
* - `delete` — non-destructive: drops the mapping, leaves files on disk.
|
|
550
|
+
* - `purge` — destructive: drops the mapping AND `RemoveAll`s the tree.
|
|
551
|
+
*
|
|
552
|
+
* Raises {@link SubstrateToolRefusedError} on tool-level refusals
|
|
553
|
+
* (collision with a static volume name, no `dynamic_root` configured,
|
|
554
|
+
* cross-tenant); {@link InvalidArgumentError} on 400; {@link AuthError}
|
|
555
|
+
* on 401. */
|
|
556
|
+
volumeDef(input: SubstrateToolInput, opts?: {
|
|
557
|
+
signal?: AbortSignal;
|
|
558
|
+
}): Promise<SubstrateToolResponse>;
|
|
559
|
+
/** List the PERSISTENT volume universe for the caller's tenant — every
|
|
560
|
+
* static volume (the shared bind floor, `source: "static"`, read-only)
|
|
561
|
+
* plus the tenant's own dynamic VolumeDefs (`source: "dynamic"`). Host
|
|
562
|
+
* paths are redacted (`""`) for a non-operator (tenant) caller.
|
|
563
|
+
*
|
|
564
|
+
* Wraps `GET /v1/_volumes` (RFC AH Phase 4). Bearer-authed; tenant-scoped
|
|
565
|
+
* from the authoritative principal. */
|
|
566
|
+
listVolumes(opts?: {
|
|
567
|
+
signal?: AbortSignal;
|
|
568
|
+
}): Promise<PersistentVolumesResponse>;
|
|
569
|
+
/** List the LIVE ephemeral (run-scoped) volumes for the caller's tenant —
|
|
570
|
+
* created mid-run, inherited by sub-agents, auto-purged when the run
|
|
571
|
+
* completes. Host paths are redacted (`""`) for a non-operator caller.
|
|
572
|
+
*
|
|
573
|
+
* Wraps `GET /v1/_volumes/ephemeral` (RFC AH Phase 4). Bearer-authed;
|
|
574
|
+
* tenant-scoped from the authoritative principal. */
|
|
575
|
+
listEphemeralVolumes(opts?: {
|
|
576
|
+
signal?: AbortSignal;
|
|
577
|
+
}): Promise<EphemeralVolumesResponse>;
|
|
489
578
|
/** List every agent the runtime knows about — yaml-static + dynamic
|
|
490
579
|
* AgentDefs merged into one envelope per name. Each entry carries
|
|
491
580
|
* `source: "static-only" | "dynamic-only" | "both"` so callers can
|
|
@@ -583,6 +672,11 @@ export declare class LoomcycleClient {
|
|
|
583
672
|
* events AND a `{ type: "_meta", meta_subtype: "stream_close",
|
|
584
673
|
* meta_reason }` on EOF / abort / error. The default is silent
|
|
585
674
|
* (matches pre-v0.9.x behaviour). */
|
|
675
|
+
/** GET-based SSE stream (RFC AI re-attach). The interactive re-attach
|
|
676
|
+
* endpoint is a GET with a query string, not a POST body, so it can't reuse
|
|
677
|
+
* streamSSE; the parse + error handling are identical otherwise. No debug
|
|
678
|
+
* shape — re-attach is a plain tail. */
|
|
679
|
+
private streamSSEGet;
|
|
586
680
|
private streamSSE;
|
|
587
681
|
/** List every operator-declared channel with aggregate stats
|
|
588
682
|
* (message_count, oldest_visible_at, newest_visible_at).
|
package/dist/client.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { authHeaders, deleteRequest, jsonFetch, patchJSON, postJSON, putJSON, raiseFromResponse, } from "./fetch-helpers.js";
|
|
27
27
|
import { parseSSE } from "./stream.js";
|
|
28
|
+
import { InteractiveSession } from "./interactive.js";
|
|
28
29
|
/** samplingToWire maps the camelCase SamplingOptions to the snake_case wire
|
|
29
30
|
* object, omitting unset fields so they inherit the agent's value. */
|
|
30
31
|
function samplingToWire(s) {
|
|
@@ -104,6 +105,8 @@ function runBody(opts) {
|
|
|
104
105
|
body.sampling = samplingToWire(opts.sampling);
|
|
105
106
|
if (opts.compaction !== undefined)
|
|
106
107
|
body.compaction = compactionToWire(opts.compaction);
|
|
108
|
+
if (opts.interactive !== undefined)
|
|
109
|
+
body.interactive = opts.interactive;
|
|
107
110
|
return body;
|
|
108
111
|
}
|
|
109
112
|
export class LoomcycleClient {
|
|
@@ -186,8 +189,65 @@ export class LoomcycleClient {
|
|
|
186
189
|
body.sampling = samplingToWire(opts.sampling);
|
|
187
190
|
if (opts.compaction !== undefined)
|
|
188
191
|
body.compaction = compactionToWire(opts.compaction);
|
|
192
|
+
if (opts.interactive !== undefined)
|
|
193
|
+
body.interactive = opts.interactive;
|
|
189
194
|
yield* this.streamSSE(`/v1/sessions/${encodeURIComponent(opts.sessionId)}/messages`, body, opts.signal, opts.debug);
|
|
190
195
|
}
|
|
196
|
+
/** Push an operator steering message into a LIVE interactive run (RFC AI).
|
|
197
|
+
* Mirrors `POST /v1/runs/{run_id}/input`. The run must be in-flight —
|
|
198
|
+
* parked at end_turn awaiting input, or mid-turn (the message is drained
|
|
199
|
+
* at the next iteration boundary). Does NOT open a stream; the operator's
|
|
200
|
+
* turn + the model's response arrive on the run's OWN event stream
|
|
201
|
+
* (the open `runStreaming` iterator, or a `streamRunByID` re-attach).
|
|
202
|
+
* Returns `{ run_id, delivered }`.
|
|
203
|
+
*
|
|
204
|
+
* Raises {@link UnavailableError} (503, steering off / no run),
|
|
205
|
+
* {@link AuthError} (401). A full steer queue surfaces as a 429. */
|
|
206
|
+
async sendRunInput(runId, text, opts) {
|
|
207
|
+
return postJSON(this.ctx, `/v1/runs/${encodeURIComponent(runId)}/input`, { text }, opts);
|
|
208
|
+
}
|
|
209
|
+
/** Re-attach to a run's event stream by `run_id` (RFC AI), replaying from
|
|
210
|
+
* `fromSeq` (default 0) then live-tailing — the gRPC-free twin of the Web
|
|
211
|
+
* UI's resume-in-terminal. Mirrors `GET /v1/runs/{run_id}/stream`. The
|
|
212
|
+
* operator's own prior turns are replayed as `steer` events
|
|
213
|
+
* (`user_input.source === "replay"`), so a cold client — e.g. resuming on
|
|
214
|
+
* another device — reconstructs the whole conversation. A PARKED
|
|
215
|
+
* interactive run keeps streaming until it ends or `signal` aborts; a
|
|
216
|
+
* disconnect does NOT stop the run.
|
|
217
|
+
*
|
|
218
|
+
* The first frame is an `agent` side-channel carrying the run's
|
|
219
|
+
* agent_id / session_id (for `sendRunInput` / `cancelAgent`). */
|
|
220
|
+
async *streamRunByID(runId, opts) {
|
|
221
|
+
const q = opts?.fromSeq ? `?from_seq=${opts.fromSeq}` : "";
|
|
222
|
+
yield* this.streamSSEGet(`/v1/runs/${encodeURIComponent(runId)}/stream${q}`, opts?.signal);
|
|
223
|
+
}
|
|
224
|
+
/** Start a high-level INTERACTIVE session (RFC AI) — the ergonomic driver
|
|
225
|
+
* for a persistent run you converse with turn by turn. Starts an
|
|
226
|
+
* `interactive` run and returns an {@link InteractiveSession} whose
|
|
227
|
+
* `events()` you iterate and whose `send()` / `cancel()` steer it. See
|
|
228
|
+
* {@link InteractiveSession}. For the raw primitives, use
|
|
229
|
+
* {@link LoomcycleClient.runStreaming} with `interactive: true` +
|
|
230
|
+
* {@link LoomcycleClient.sendRunInput}. */
|
|
231
|
+
interactiveSession(opts) {
|
|
232
|
+
const source = this.runStreaming({ ...opts, interactive: true });
|
|
233
|
+
return new InteractiveSession(source, {
|
|
234
|
+
sendRunInput: (rid, t) => this.sendRunInput(rid, t),
|
|
235
|
+
cancelAgent: (aid) => this.cancelAgent(aid),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** Re-attach to a detached interactive run by `run_id` as a high-level
|
|
239
|
+
* {@link InteractiveSession} — e.g. resuming on another device. The run_id
|
|
240
|
+
* is known up front, so `send()` works immediately. Replays the
|
|
241
|
+
* conversation (incl. the operator's prior turns, RFC AI) then live-tails. */
|
|
242
|
+
attachInteractiveSession(runId, opts) {
|
|
243
|
+
const source = this.streamRunByID(runId, opts);
|
|
244
|
+
const session = new InteractiveSession(source, {
|
|
245
|
+
sendRunInput: (rid, t) => this.sendRunInput(rid, t),
|
|
246
|
+
cancelAgent: (aid) => this.cancelAgent(aid),
|
|
247
|
+
});
|
|
248
|
+
session.runId = runId;
|
|
249
|
+
return session;
|
|
250
|
+
}
|
|
191
251
|
/**
|
|
192
252
|
* Spawn N fresh runs concurrently in ONE call (RFC Y external fan-out) and
|
|
193
253
|
* resolve once they ALL settle, returning the combined index-aligned
|
|
@@ -741,6 +801,48 @@ export class LoomcycleClient {
|
|
|
741
801
|
async operatorTokenDef(input, opts) {
|
|
742
802
|
return postJSON(this.ctx, "/v1/_operatortokendef", input, opts);
|
|
743
803
|
}
|
|
804
|
+
// ---- RFC AH (v0.35.0) dynamic filesystem volumes ----
|
|
805
|
+
/** Invoke the RFC AH VolumeDef substrate tool over HTTP. Provision +
|
|
806
|
+
* manage CONFINED, per-tenant filesystem volumes at runtime.
|
|
807
|
+
* Tenant-confined (ScopeTenant, not operator-admin-only): the runtime
|
|
808
|
+
* stamps the caller's authoritative tenant and opaque-404s cross-tenant.
|
|
809
|
+
*
|
|
810
|
+
* Op-discriminated input. A VolumeDef is FLAT (a pointer to mutable
|
|
811
|
+
* on-disk state, not a versioned definition), so the op set is
|
|
812
|
+
* `{op: "create" | "get" | "list" | "delete" | "purge", ...}` — NOT the
|
|
813
|
+
* content-addressed retire/promote/fork of the other Def families:
|
|
814
|
+
* - `create` — `{op, name, mode}`. The runtime DERIVES the path inside the
|
|
815
|
+
* operator-blessed `dynamic_root` (`<root>/<tenant>/<name>`) and
|
|
816
|
+
* `MkdirAll`s it; you never supply a host path.
|
|
817
|
+
* - `delete` — non-destructive: drops the mapping, leaves files on disk.
|
|
818
|
+
* - `purge` — destructive: drops the mapping AND `RemoveAll`s the tree.
|
|
819
|
+
*
|
|
820
|
+
* Raises {@link SubstrateToolRefusedError} on tool-level refusals
|
|
821
|
+
* (collision with a static volume name, no `dynamic_root` configured,
|
|
822
|
+
* cross-tenant); {@link InvalidArgumentError} on 400; {@link AuthError}
|
|
823
|
+
* on 401. */
|
|
824
|
+
async volumeDef(input, opts) {
|
|
825
|
+
return postJSON(this.ctx, "/v1/_volumedef", input, opts);
|
|
826
|
+
}
|
|
827
|
+
/** List the PERSISTENT volume universe for the caller's tenant — every
|
|
828
|
+
* static volume (the shared bind floor, `source: "static"`, read-only)
|
|
829
|
+
* plus the tenant's own dynamic VolumeDefs (`source: "dynamic"`). Host
|
|
830
|
+
* paths are redacted (`""`) for a non-operator (tenant) caller.
|
|
831
|
+
*
|
|
832
|
+
* Wraps `GET /v1/_volumes` (RFC AH Phase 4). Bearer-authed; tenant-scoped
|
|
833
|
+
* from the authoritative principal. */
|
|
834
|
+
async listVolumes(opts) {
|
|
835
|
+
return jsonFetch(this.ctx, "/v1/_volumes", opts);
|
|
836
|
+
}
|
|
837
|
+
/** List the LIVE ephemeral (run-scoped) volumes for the caller's tenant —
|
|
838
|
+
* created mid-run, inherited by sub-agents, auto-purged when the run
|
|
839
|
+
* completes. Host paths are redacted (`""`) for a non-operator caller.
|
|
840
|
+
*
|
|
841
|
+
* Wraps `GET /v1/_volumes/ephemeral` (RFC AH Phase 4). Bearer-authed;
|
|
842
|
+
* tenant-scoped from the authoritative principal. */
|
|
843
|
+
async listEphemeralVolumes(opts) {
|
|
844
|
+
return jsonFetch(this.ctx, "/v1/_volumes/ephemeral", opts);
|
|
845
|
+
}
|
|
744
846
|
// ---- v0.10.3 Library v2 enumeration (read-only, merged yaml+substrate) ----
|
|
745
847
|
/** List every agent the runtime knows about — yaml-static + dynamic
|
|
746
848
|
* AgentDefs merged into one envelope per name. Each entry carries
|
|
@@ -872,6 +974,29 @@ export class LoomcycleClient {
|
|
|
872
974
|
* events AND a `{ type: "_meta", meta_subtype: "stream_close",
|
|
873
975
|
* meta_reason }` on EOF / abort / error. The default is silent
|
|
874
976
|
* (matches pre-v0.9.x behaviour). */
|
|
977
|
+
/** GET-based SSE stream (RFC AI re-attach). The interactive re-attach
|
|
978
|
+
* endpoint is a GET with a query string, not a POST body, so it can't reuse
|
|
979
|
+
* streamSSE; the parse + error handling are identical otherwise. No debug
|
|
980
|
+
* shape — re-attach is a plain tail. */
|
|
981
|
+
async *streamSSEGet(path, signal) {
|
|
982
|
+
const headers = {
|
|
983
|
+
Accept: "text/event-stream, application/json",
|
|
984
|
+
};
|
|
985
|
+
if (this.ctx.authToken)
|
|
986
|
+
headers.Authorization = `Bearer ${this.ctx.authToken}`;
|
|
987
|
+
const resp = await this.ctx.fetchImpl(this.ctx.baseUrl + path, {
|
|
988
|
+
method: "GET",
|
|
989
|
+
headers,
|
|
990
|
+
signal,
|
|
991
|
+
});
|
|
992
|
+
if (!resp.ok) {
|
|
993
|
+
await raiseFromResponse(resp);
|
|
994
|
+
}
|
|
995
|
+
if (!resp.body) {
|
|
996
|
+
throw new Error("loomcycle: response has no body");
|
|
997
|
+
}
|
|
998
|
+
yield* parseSSE(resp.body.getReader());
|
|
999
|
+
}
|
|
875
1000
|
async *streamSSE(path, body, signal, debug) {
|
|
876
1001
|
const headers = {
|
|
877
1002
|
"Content-Type": "application/json",
|
package/dist/index.d.ts
CHANGED
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
* ensureMcpServer(opts): Promise<EnsureMcpServerResult> // v0.18.0 — idempotent register-if-changed
|
|
53
53
|
* scheduleDef(input): Promise<SubstrateToolResponse>
|
|
54
54
|
*
|
|
55
|
+
* // Dynamic filesystem volumes (v0.35.0 — RFC AH; tenant-confined)
|
|
56
|
+
* volumeDef(input): Promise<SubstrateToolResponse> // create/get/list/delete/purge
|
|
57
|
+
* listVolumes(): Promise<PersistentVolumesResponse>
|
|
58
|
+
* listEphemeralVolumes(): Promise<EphemeralVolumesResponse>
|
|
59
|
+
*
|
|
55
60
|
* // Library v2 enumeration (v0.10.3 — yaml+substrate merged)
|
|
56
61
|
* listLibraryAgents(): Promise<LibraryListResponse<LibraryAgentDefinition>>
|
|
57
62
|
* listLibrarySkills(): Promise<LibraryListResponse<LibrarySkillDefinition>>
|
|
@@ -82,5 +87,7 @@
|
|
|
82
87
|
* See `adapters/ts/README.md` for usage examples.
|
|
83
88
|
*/
|
|
84
89
|
export { LoomcycleClient } from "./client.js";
|
|
85
|
-
export
|
|
90
|
+
export { InteractiveSession } from "./interactive.js";
|
|
91
|
+
export type { InteractiveSessionOps } from "./interactive.js";
|
|
92
|
+
export type { AgentEvent, ClientOptions, ContinueOptions, EventType, HostWidening, ParentContext, PromptContent, PromptSegment, RetryInfo, RunOptions, SamplingOptions, CompactionOptions, ToolUse, Usage, Agent, AgentStatus, AgentUsage, CancelAgentResult, ListAgentsResponse, RunBatchOptions, RunBatchResult, SpawnRunResult, CompactRunResult, TranscriptEvent, TranscriptResponse, HealthResponse, ListUsersResponse, UserSummary, WhoamiResponse, PauseResult, ResumeResult, RuntimeStateResponse, RuntimeStateStatus, ResolverMatrix, ResolverModelStatus, ResolverProviderAvailability, CreateSnapshotOptions, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotListResponse, SnapshotRestoreResponse, MemoryEntriesResponse, MemoryEntry, MemoryEntryResponse, MemoryScopeIDsResponse, MemoryScopeIDSummary, MemoryScopeKind, MemoryScopesResponse, InterruptListResponse, InterruptRow, InterruptStatus, ResolveInterruptOptions, Hook, HookFailMode, HookPhase, HookToolCall, HookToolResult, ListHooksResponse, PostHookCall, PostHookResult, PreHookCall, PreHookResult, RegisterHookOptions, RegisterHookResponse, SubstrateToolInput, SubstrateToolResponse, VolumeMode, PersistentVolumeEntry, PersistentVolumesResponse, EphemeralVolumeEntry, EphemeralVolumesResponse, SystemPromptPayload, UserInputPayload, ChannelDescriptor, ListChannelsResponse, RunStateEvent, RunStateStreamClose, RunStateStreamItem, RunStateStreamOpen, StreamUserRunStatesOptions, AckChannelOptions, ChannelAckResult, ChannelMessageItem, ChannelPeekResult, ChannelPublishResult, ChannelPurgeResult, ChannelScope, ChannelSubscribeResult, PeekChannelOptions, PublishChannelOptions, SubscribeChannelOptions, AwaitChannelsOptions, BroadcastChannelsOptions, ChannelAwaitEntry, ChannelAwaitMode, ChannelAwaitResult, ChannelBroadcastEntry, ChannelBroadcastResult, CreateChannelOptions, UpdateChannelOptions, SetMemoryEntryOptions, SetMemoryEntryResponse, AgentDefRowResponse, AgentDefVerifyResult, SkillDefVerifyResult, EnsureMcpServerOptions, EnsureMcpServerResult, MCPServerDefRowResponse, MCPServerDefVerifyResult, AgentDefOverlay, EnsureCodeAgentOptions, EnsureCodeAgentResult, LibraryAgentDefinition, LibraryEntry, LibraryListResponse, LibraryMcpServerDefinition, LibrarySkillDefinition, LLMChatContent, LLMChatMessage, LLMChatOptions, LLMChatResponse, LLMChatStreamDelta, LLMChatStreamItem, LLMChatToolCall, LLMChatUsage, LLMTool, LLMEmbeddingItem, LLMEmbeddingsOptions, LLMEmbeddingsResponse, LLMEmbeddingsUsage, } from "./types.js";
|
|
86
93
|
export { AgentIDInUseError, AgentNotFoundError, AlreadyPausingError, AuthError, BackpressureError, HookNotFoundError, NotFoundError, InvalidArgumentError, ChannelCursorRegressionError, LoomcycleError, NotPausedError, PauseNotConfiguredError, PerUserQuotaExhaustedError, SessionBusyError, SessionNotFoundError, SnapshotNotFoundError, SnapshotTooLargeError, SnapshotVersionError, SubstrateToolRefusedError, UnavailableError, } from "./errors.js";
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
* ensureMcpServer(opts): Promise<EnsureMcpServerResult> // v0.18.0 — idempotent register-if-changed
|
|
53
53
|
* scheduleDef(input): Promise<SubstrateToolResponse>
|
|
54
54
|
*
|
|
55
|
+
* // Dynamic filesystem volumes (v0.35.0 — RFC AH; tenant-confined)
|
|
56
|
+
* volumeDef(input): Promise<SubstrateToolResponse> // create/get/list/delete/purge
|
|
57
|
+
* listVolumes(): Promise<PersistentVolumesResponse>
|
|
58
|
+
* listEphemeralVolumes(): Promise<EphemeralVolumesResponse>
|
|
59
|
+
*
|
|
55
60
|
* // Library v2 enumeration (v0.10.3 — yaml+substrate merged)
|
|
56
61
|
* listLibraryAgents(): Promise<LibraryListResponse<LibraryAgentDefinition>>
|
|
57
62
|
* listLibrarySkills(): Promise<LibraryListResponse<LibrarySkillDefinition>>
|
|
@@ -82,4 +87,5 @@
|
|
|
82
87
|
* See `adapters/ts/README.md` for usage examples.
|
|
83
88
|
*/
|
|
84
89
|
export { LoomcycleClient } from "./client.js";
|
|
90
|
+
export { InteractiveSession } from "./interactive.js";
|
|
85
91
|
export { AgentIDInUseError, AgentNotFoundError, AlreadyPausingError, AuthError, BackpressureError, HookNotFoundError, NotFoundError, InvalidArgumentError, ChannelCursorRegressionError, LoomcycleError, NotPausedError, PauseNotConfiguredError, PerUserQuotaExhaustedError, SessionBusyError, SessionNotFoundError, SnapshotNotFoundError, SnapshotTooLargeError, SnapshotVersionError, SubstrateToolRefusedError, UnavailableError, } from "./errors.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AgentEvent } from "./types.js";
|
|
2
|
+
/** The client-side operations an {@link InteractiveSession} routes through.
|
|
3
|
+
* Supplied by LoomcycleClient.interactiveSession / attachInteractiveSession —
|
|
4
|
+
* the session itself holds no transport logic. */
|
|
5
|
+
export interface InteractiveSessionOps {
|
|
6
|
+
sendRunInput: (runId: string, text: string) => Promise<{
|
|
7
|
+
delivered: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
cancelAgent: (agentId: string) => Promise<unknown>;
|
|
10
|
+
}
|
|
11
|
+
/** A high-level driver for an interactive agentic session (RFC AI) — the
|
|
12
|
+
* adapter port of the Web UI's run terminal. It wraps the underlying event
|
|
13
|
+
* stream (a fresh `interactive` run, or a `streamRunByID` re-attach), taps
|
|
14
|
+
* each frame to track the run's tracking IDs + parked state, and routes
|
|
15
|
+
* operator input + cancel through the client.
|
|
16
|
+
*
|
|
17
|
+
* Typical loop:
|
|
18
|
+
* ```ts
|
|
19
|
+
* const sess = client.interactiveSession({ agent: "chat", segments: [...] });
|
|
20
|
+
* for await (const ev of sess.events()) {
|
|
21
|
+
* if (ev.type === "text") process.stdout.write(ev.text ?? "");
|
|
22
|
+
* if (ev.type === "awaiting_input") await sess.send(await prompt("you> "));
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
* `send()` does NOT open a new stream — the operator's turn and the model's
|
|
26
|
+
* response arrive on the same `events()` iterator. */
|
|
27
|
+
export declare class InteractiveSession {
|
|
28
|
+
private readonly source;
|
|
29
|
+
private readonly ops;
|
|
30
|
+
/** The run's id, from the first `agent` frame (set up-front on re-attach). */
|
|
31
|
+
runId: string;
|
|
32
|
+
/** The run's agent_id (for cancel), from the first `agent` frame. */
|
|
33
|
+
agentId: string;
|
|
34
|
+
/** The run's session_id, from the `session` / `agent` frames. */
|
|
35
|
+
sessionId: string;
|
|
36
|
+
/** True after an `awaiting_input` frame; cleared on the next activity. */
|
|
37
|
+
awaitingInput: boolean;
|
|
38
|
+
constructor(source: AsyncIterable<AgentEvent>, ops: InteractiveSessionOps);
|
|
39
|
+
/** The merged event stream. Consume with `for await`. Each frame updates the
|
|
40
|
+
* session's runId / agentId / sessionId / awaitingInput BEFORE it is
|
|
41
|
+
* yielded, so a consumer that reacts to `awaiting_input` can immediately
|
|
42
|
+
* `send()`. */
|
|
43
|
+
events(): AsyncIterable<AgentEvent>;
|
|
44
|
+
/** Steer the live run with an operator message. The response arrives on the
|
|
45
|
+
* `events()` stream (NOT a new stream). Returns the server's `delivered`
|
|
46
|
+
* flag. Throws if the run_id isn't known yet — for a fresh session, consume
|
|
47
|
+
* `events()` until the `agent` frame (or the first `awaiting_input`) first;
|
|
48
|
+
* a re-attached session has the run_id up front. */
|
|
49
|
+
send(text: string): Promise<boolean>;
|
|
50
|
+
/** Cancel the run (and its sub-agents). No-op if the agent_id isn't known
|
|
51
|
+
* yet (nothing to cancel). */
|
|
52
|
+
cancel(): Promise<void>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** A high-level driver for an interactive agentic session (RFC AI) — the
|
|
2
|
+
* adapter port of the Web UI's run terminal. It wraps the underlying event
|
|
3
|
+
* stream (a fresh `interactive` run, or a `streamRunByID` re-attach), taps
|
|
4
|
+
* each frame to track the run's tracking IDs + parked state, and routes
|
|
5
|
+
* operator input + cancel through the client.
|
|
6
|
+
*
|
|
7
|
+
* Typical loop:
|
|
8
|
+
* ```ts
|
|
9
|
+
* const sess = client.interactiveSession({ agent: "chat", segments: [...] });
|
|
10
|
+
* for await (const ev of sess.events()) {
|
|
11
|
+
* if (ev.type === "text") process.stdout.write(ev.text ?? "");
|
|
12
|
+
* if (ev.type === "awaiting_input") await sess.send(await prompt("you> "));
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
* `send()` does NOT open a new stream — the operator's turn and the model's
|
|
16
|
+
* response arrive on the same `events()` iterator. */
|
|
17
|
+
export class InteractiveSession {
|
|
18
|
+
source;
|
|
19
|
+
ops;
|
|
20
|
+
/** The run's id, from the first `agent` frame (set up-front on re-attach). */
|
|
21
|
+
runId = "";
|
|
22
|
+
/** The run's agent_id (for cancel), from the first `agent` frame. */
|
|
23
|
+
agentId = "";
|
|
24
|
+
/** The run's session_id, from the `session` / `agent` frames. */
|
|
25
|
+
sessionId = "";
|
|
26
|
+
/** True after an `awaiting_input` frame; cleared on the next activity. */
|
|
27
|
+
awaitingInput = false;
|
|
28
|
+
constructor(source, ops) {
|
|
29
|
+
this.source = source;
|
|
30
|
+
this.ops = ops;
|
|
31
|
+
}
|
|
32
|
+
/** The merged event stream. Consume with `for await`. Each frame updates the
|
|
33
|
+
* session's runId / agentId / sessionId / awaitingInput BEFORE it is
|
|
34
|
+
* yielded, so a consumer that reacts to `awaiting_input` can immediately
|
|
35
|
+
* `send()`. */
|
|
36
|
+
async *events() {
|
|
37
|
+
for await (const ev of this.source) {
|
|
38
|
+
if (ev.type === "agent") {
|
|
39
|
+
if (ev.run_id)
|
|
40
|
+
this.runId = ev.run_id;
|
|
41
|
+
if (ev.agent_id)
|
|
42
|
+
this.agentId = ev.agent_id;
|
|
43
|
+
if (ev.session_id)
|
|
44
|
+
this.sessionId = ev.session_id;
|
|
45
|
+
}
|
|
46
|
+
else if (ev.type === "session" && ev.session_id) {
|
|
47
|
+
this.sessionId = ev.session_id;
|
|
48
|
+
}
|
|
49
|
+
this.awaitingInput = ev.type === "awaiting_input";
|
|
50
|
+
yield ev;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Steer the live run with an operator message. The response arrives on the
|
|
54
|
+
* `events()` stream (NOT a new stream). Returns the server's `delivered`
|
|
55
|
+
* flag. Throws if the run_id isn't known yet — for a fresh session, consume
|
|
56
|
+
* `events()` until the `agent` frame (or the first `awaiting_input`) first;
|
|
57
|
+
* a re-attached session has the run_id up front. */
|
|
58
|
+
async send(text) {
|
|
59
|
+
if (!this.runId) {
|
|
60
|
+
throw new Error("loomcycle: run_id not known yet — consume events() until the `agent` frame before send()");
|
|
61
|
+
}
|
|
62
|
+
const { delivered } = await this.ops.sendRunInput(this.runId, text);
|
|
63
|
+
this.awaitingInput = false;
|
|
64
|
+
return delivered;
|
|
65
|
+
}
|
|
66
|
+
/** Cancel the run (and its sub-agents). No-op if the agent_id isn't known
|
|
67
|
+
* yet (nothing to cancel). */
|
|
68
|
+
async cancel() {
|
|
69
|
+
if (this.agentId)
|
|
70
|
+
await this.ops.cancelAgent(this.agentId);
|
|
71
|
+
}
|
|
72
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `client.ts` for the input shapes (RunOptions, CreateSnapshotOptions,
|
|
8
8
|
* etc.) — those are translated to snake_case in the request body.
|
|
9
9
|
*/
|
|
10
|
-
export type EventType = "started" | "text" | "tool_call" | "tool_result" | "usage" | "done" | "error" | "retry" | "host_widened" | "session" | "agent" | "_meta";
|
|
10
|
+
export type EventType = "started" | "text" | "tool_call" | "tool_result" | "usage" | "done" | "error" | "retry" | "host_widened" | "session" | "agent" | "awaiting_input" | "steer" | "context_compaction" | "_meta";
|
|
11
11
|
export interface ToolUse {
|
|
12
12
|
id: string;
|
|
13
13
|
name: string;
|
|
@@ -70,6 +70,18 @@ export interface AgentEvent {
|
|
|
70
70
|
/** Host-widening payload on `event: host_widened`. Nil on all other
|
|
71
71
|
* event types. */
|
|
72
72
|
host_widening?: HostWidening;
|
|
73
|
+
/** Payload on `event: awaiting_input` (RFC AI) — a persistent interactive
|
|
74
|
+
* run parked at end_turn. `since_turn` is the iteration it parked after. */
|
|
75
|
+
awaiting_input?: {
|
|
76
|
+
since_turn?: number;
|
|
77
|
+
};
|
|
78
|
+
/** Payload on `event: steer` (RFC AI) — the operator's drained turn. On a
|
|
79
|
+
* re-attach replay, `source` is `"replay"`. Nil on all other event types. */
|
|
80
|
+
user_input?: {
|
|
81
|
+
text?: string;
|
|
82
|
+
source?: string;
|
|
83
|
+
seen_at?: string;
|
|
84
|
+
};
|
|
73
85
|
agent_id?: string;
|
|
74
86
|
run_id?: string;
|
|
75
87
|
session_id?: string;
|
|
@@ -177,6 +189,13 @@ export interface RunOptions {
|
|
|
177
189
|
* Omitted = inherit entirely. Trigger compaction mid-run with
|
|
178
190
|
* {@link LoomcycleClient.compactRun}. */
|
|
179
191
|
compaction?: CompactionOptions;
|
|
192
|
+
/** RFC AI — start a PERSISTENT interactive run that parks at end_turn
|
|
193
|
+
* awaiting operator steering instead of terminating. The stream emits an
|
|
194
|
+
* `awaiting_input` frame when it parks; drive it with
|
|
195
|
+
* {@link LoomcycleClient.sendRunInput}, re-attach with
|
|
196
|
+
* {@link LoomcycleClient.streamRunByID}, and `cancelAgent` to end it.
|
|
197
|
+
* Higher-level: {@link LoomcycleClient.interactiveSession}. */
|
|
198
|
+
interactive?: boolean;
|
|
180
199
|
/** Opt-in observability: when true, the iterator emits client-
|
|
181
200
|
* synthesized `{ type: "_meta", meta_subtype: "stream_open" | "stream_close" }`
|
|
182
201
|
* events around the real event stream. `meta_reason` carries the
|
|
@@ -279,6 +298,9 @@ export interface ContinueOptions {
|
|
|
279
298
|
sampling?: SamplingOptions;
|
|
280
299
|
/** Per-continuation context-compaction override — see {@link RunOptions.compaction}. */
|
|
281
300
|
compaction?: CompactionOptions;
|
|
301
|
+
/** RFC AI — park this continuation at end_turn for operator steering. See
|
|
302
|
+
* {@link RunOptions.interactive}. */
|
|
303
|
+
interactive?: boolean;
|
|
282
304
|
/** Opt-in observability: see {@link RunOptions.debug}. Same shape. */
|
|
283
305
|
debug?: boolean;
|
|
284
306
|
signal?: AbortSignal;
|
|
@@ -725,7 +747,7 @@ export interface PostHookCall {
|
|
|
725
747
|
* the adapter doesn't re-validate. Use the optional `extra` index
|
|
726
748
|
* signature for forward-compat fields. */
|
|
727
749
|
export type SubstrateToolInput = {
|
|
728
|
-
op: "create" | "fork" | "get" | "list" | "promote" | "retire" | "rediscover" | "verify";
|
|
750
|
+
op: "create" | "fork" | "get" | "list" | "promote" | "retire" | "rediscover" | "verify" | "delete" | "purge";
|
|
729
751
|
name?: string;
|
|
730
752
|
def_id?: string;
|
|
731
753
|
parent_def_id?: string;
|
|
@@ -733,6 +755,7 @@ export type SubstrateToolInput = {
|
|
|
733
755
|
description?: string;
|
|
734
756
|
promote?: boolean;
|
|
735
757
|
retired?: boolean;
|
|
758
|
+
mode?: VolumeMode;
|
|
736
759
|
[extra: string]: unknown;
|
|
737
760
|
};
|
|
738
761
|
/** Response shape for {@link LoomcycleClient.agentDef} and
|
|
@@ -741,6 +764,42 @@ export type SubstrateToolInput = {
|
|
|
741
764
|
* `{name, versions: [...]}`, promote/retire return summary shapes.
|
|
742
765
|
* Callers narrow as needed. */
|
|
743
766
|
export type SubstrateToolResponse = unknown;
|
|
767
|
+
/** Volume access mode — read-only or read-write (RFC AH). */
|
|
768
|
+
export type VolumeMode = "ro" | "rw";
|
|
769
|
+
/** One row of {@link LoomcycleClient.listVolumes} (`GET /v1/_volumes`). */
|
|
770
|
+
export interface PersistentVolumeEntry {
|
|
771
|
+
name: string;
|
|
772
|
+
/** "static" (operator yaml, read-only) or "dynamic" (a tenant VolumeDef). */
|
|
773
|
+
source: "static" | "dynamic";
|
|
774
|
+
/** Host path. Redacted to "" for a non-operator (tenant) caller — the
|
|
775
|
+
* volume universe is visible to a tenant operator, the host location is
|
|
776
|
+
* not. Operator-equivalent callers see the real path. */
|
|
777
|
+
path: string;
|
|
778
|
+
mode: VolumeMode;
|
|
779
|
+
/** True for the static volume flagged `default: true` (never for dynamic). */
|
|
780
|
+
default: boolean;
|
|
781
|
+
/** True for the static volume dynamic VolumeDefs are provisioned inside. */
|
|
782
|
+
dynamic_root: boolean;
|
|
783
|
+
/** Set for dynamic rows (the substrate stamps it); absent for static. */
|
|
784
|
+
created_at?: string;
|
|
785
|
+
}
|
|
786
|
+
export interface PersistentVolumesResponse {
|
|
787
|
+
entries: PersistentVolumeEntry[];
|
|
788
|
+
}
|
|
789
|
+
/** One row of {@link LoomcycleClient.listEphemeralVolumes}
|
|
790
|
+
* (`GET /v1/_volumes/ephemeral`). */
|
|
791
|
+
export interface EphemeralVolumeEntry {
|
|
792
|
+
name: string;
|
|
793
|
+
root_run_id: string;
|
|
794
|
+
/** Host path — redacted to "" for a non-operator caller (see
|
|
795
|
+
* {@link PersistentVolumeEntry.path}). */
|
|
796
|
+
path: string;
|
|
797
|
+
mode: VolumeMode;
|
|
798
|
+
created_at: string;
|
|
799
|
+
}
|
|
800
|
+
export interface EphemeralVolumesResponse {
|
|
801
|
+
entries: EphemeralVolumeEntry[];
|
|
802
|
+
}
|
|
744
803
|
/** Response a Post webhook returns. When result is omitted the tool
|
|
745
804
|
* result passes through unchanged. */
|
|
746
805
|
export interface PostHookResult {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomcycle/client",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "TypeScript client for the loomcycle sidecar (HTTP+SSE).
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "TypeScript client for the loomcycle sidecar (HTTP+SSE). 61 methods covering run streaming, agent metadata, pause/resume/state, resolver re-probe (resolveProbe — issue #88 operator escape hatch), operator-token admin (operatorTokenDef — RFC L OSS multi-tenant auth) + whoami (RFC L authoritative principal, v0.17.0) + tenant-scoped listUsers / listUserAgents, dynamic MCP registration (mcpServerDef + v0.18.0 typed mcpServerDefVerify + ensureMcpServer idempotent register-if-changed), snapshot lifecycle, memory admin (incl. v0.9.0 Vector Memory embed_stats + reembed AND v0.11.5 setMemoryEntry + deleteMemoryEntry), interruption resolve, hook management, v0.8.22 substrate admin (agentDef + skillDef), v0.9.x n8n Phase 0 (listChannels + streamUserRunStates), v0.9.x Channel CRUD (publishChannel + subscribeChannel + peekChannel + ackChannel), v0.11.5 Channel admin CRUD (createChannel + updateChannel + deleteChannel — runtime substrate; yaml channels refuse mutation with HTTP 409), v0.9.x content_sha256 verify, v0.9.1 transcript first-cycle, v0.9.x dynamic MCP server registration (mcpServerDef + MCPServerDefVerifyResult), v0.10.3 Library v2 enumeration (listLibraryAgents + listLibrarySkills + listLibraryMcpServers), and v0.11.0 LLM Gateway (llmChat + llmStream — direct provider routing without agent overhead; primary target is n8n's LoomCycleChatModel AI Agent sub-node and any LangChain-compatible consumer). v0.10.1 — dual ESM + CommonJS distribution (additive — ESM consumers unchanged; CJS consumers like n8n's community-node loader now work). v0.19.0 — typed inline code-js agent ingestion (AgentDefOverlay.code_body + ensureCodeAgent — register a deterministic code agent through the substrate with no host filesystem bind; RFC J). v0.20.0 — ensureMcpServer surfaces discoveredToolCount straight from create (loomcycle now auto-discovers MCP tools at ingestion); rediscover is now an explicit force-refresh. v0.21.0 — run/continue accept optional non-secret `metadata` (repo name, review policy, …) passed to the agent, symmetric with the WebHook/Schedule trigger paths. v0.22.0 — version-aligned lockstep release; no client-surface change (RFC N tenant isolation of the agent/skill/MCP/Schedule/Webhook definition plane + real op-schemas on the builtin MCP meta-tools are both server-side). v0.23.0 — version-aligned lockstep release; no client-surface change (RFCs O/P/R MCP-server hardening + thin client and the RFC Q DeepSeek tool-content fix are all server-side / MCP-transport-side). v0.24.0 — purgeChannel clears a channel's buffered messages without deleting its definition (allowed on yaml-declared channels too, unlike deleteChannel — F20); AgentDefOverlay gains channels / evaluation_scopes / interruption so a COMPLETE interactive/multi-agent agent round-trips over the substrate (F14). (Consolidates the interim 0.24/0.25 package bumps, which were never tag-published, back into lockstep with the loomcycle v0.24.0 tag.) v0.25.0 — adds the RFC S channel fan-in/fan-out client twins: awaitChannels() (wait for any/all/at_least N messages across channels, or a timeout — non-committing) + broadcastChannels() (publish one payload to N channels in one atomic-pre-flight call), the client-facing counterparts of the in-band Channel.await / Channel.broadcast tool ops (the rest of v0.25.0 — the manual-management Web UI console + Context op=time + max_fires self-retiring schedules — is server-side / in-band, no client-surface change). v0.29.1 — Usage gains optional `max_context_tokens` (the serving model's context-window ceiling, stamped by the loop from Provider.Capabilities() on each usage event) so a consumer can render a 'context used / max' gauge without a hard-coded per-model table; additive + optional, no behavior change. (Lockstep catch-up: the field landed in loomcycle v0.29.0 but the adapter publish skipped on a version mismatch; v0.29.1 realigns the package version with the release tag so it publishes.) v0.33.0 — gRPC + TS client parity for the RFC Y external fan-out and the compaction surface: spawnRunBatch() (POST /v1/runs:batch — spawn up to 32 fresh runs concurrently in one call, combined index-aligned envelope, per-child failures in-envelope) + compactRun(runId) (POST /v1/runs/{run_id}/compact — summarize a parked run's context); plus per-run sampling + compaction overrides now accepted on runStreaming / continueSession (an explicit temperature 0 is preserved as deterministic, not dropped as falsy). (The gRPC half adds the matching SpawnRunBatch / CompactRun RPCs + the sampling/compaction fields on RunRequest/ContinueRequest — server-side.) v0.34.0 — version-aligned lockstep release; no client-surface change (context-transform plugins / RFC Z Phase 1a are server-side config; the exp7 hardening pass is server-side; the R2 cross-provider thinking-model downgrade surfaces a new `model_downgraded` SSE event the generic stream passes through unchanged). v0.35.0 — RFC AH dynamic filesystem volumes: volumeDef() (POST /v1/_volumedef — op-discriminated create/get/list/delete/purge; a Volume is flat, so delete unmaps + purge RemoveAll's, no retire/promote/fork) + listVolumes() / listEphemeralVolumes() (GET /v1/_volumes[/ephemeral] — tenant-scoped; host paths redacted for non-operator callers). Tenant-confined; the runtime derives the path inside an operator-blessed dynamic_root, so callers pass name + mode, never a host path. v1.1.1 — RFC AI interactive agentic sessions: an `interactive: true` flag on runStreaming/continueSession (a run that parks at end_turn for steering) + sendRunInput(runId, text) (POST /v1/runs/{id}/input — steer a live run) + streamRunByID(runId, {fromSeq}) (GET /v1/runs/{id}/stream — re-attach by run_id; the operator's prior turns replay as `steer` events so a cold client reconstructs the whole conversation) + a high-level InteractiveSession driver (client.interactiveSession / attachInteractiveSession — events()/send()/cancel(), the adapter port of the Web UI run terminal). The AgentEvent union gains awaiting_input/steer/context_compaction frames. Version-aligned with the loomcycle v1.1.x line so the v1.1.1 tag publishes it (also carrying the previously-unpublished v0.35.0 volume surface).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|