@loomcycle/client 0.8.19
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 +276 -0
- package/dist/client.d.ts +213 -0
- package/dist/client.js +366 -0
- package/dist/errors.d.ts +135 -0
- package/dist/errors.js +138 -0
- package/dist/fetch-helpers.d.ts +89 -0
- package/dist/fetch-helpers.js +198 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +62 -0
- package/dist/stream.d.ts +17 -0
- package/dist/stream.js +81 -0
- package/dist/types.d.ts +479 -0
- package/dist/types.js +10 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# @loomcycle/client
|
|
2
|
+
|
|
3
|
+
TypeScript client for the [loomcycle](https://github.com/denn-gubsky/loomcycle) sidecar — the agentic-OS substrate for production agents.
|
|
4
|
+
|
|
5
|
+
`@loomcycle/client` speaks HTTP+SSE to the loomcycle server's `/v1/*` surface. The same operation surface is exposed via gRPC (`adapters/python/loomcycle`) and stdio MCP (`loomcycle mcp`); this client is the HTTP-side adapter, suitable for Node.js orchestrators, automation scripts, and operator tooling.
|
|
6
|
+
|
|
7
|
+
## Status
|
|
8
|
+
|
|
9
|
+
**v0.8.18** — full Python-adapter parity + hook management. 27 methods covering run streaming, agent metadata, transcript, pause/resume/state, snapshot lifecycle, memory admin, interruption resolve, hook registration, and health.
|
|
10
|
+
|
|
11
|
+
> Migrating from raw `fetch` against `/v1/*`? See **[docs/MIGRATING-FROM-HTTP.md](./docs/MIGRATING-FROM-HTTP.md)** for a side-by-side walkthrough.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @loomcycle/client
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Node ≥ 18. Bun and Deno likely work but are untested. Browser support is not a target — for browser-side operator control, use loomcycle's built-in Web UI at `/ui`.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { LoomcycleClient } from "@loomcycle/client";
|
|
25
|
+
|
|
26
|
+
const client = new LoomcycleClient({
|
|
27
|
+
baseUrl: process.env.LOOMCYCLE_BASE_URL ?? "http://127.0.0.1:8787",
|
|
28
|
+
authToken: process.env.LOOMCYCLE_AUTH_TOKEN,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Run an agent, stream events
|
|
32
|
+
for await (const ev of client.runStreaming({
|
|
33
|
+
agent: "qa-agent",
|
|
34
|
+
segments: [
|
|
35
|
+
{ role: "user", content: [{ type: "trusted-text", text: "Hello, world." }] },
|
|
36
|
+
],
|
|
37
|
+
})) {
|
|
38
|
+
if (ev.type === "text") process.stdout.write(ev.text ?? "");
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Cancellation
|
|
43
|
+
|
|
44
|
+
Every method accepts an optional `signal?: AbortSignal`. The streaming methods (`runStreaming`, `continueSession`) also break out of the iterator when the abort fires.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const ac = new AbortController();
|
|
48
|
+
setTimeout(() => ac.abort(), 30_000); // 30s budget
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
for await (const ev of client.runStreaming({ agent: "...", segments: [...], signal: ac.signal })) {
|
|
52
|
+
// ...
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
if (e instanceof DOMException && e.name === "AbortError") {
|
|
56
|
+
// timed out
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
All methods are async / return `Promise<T>` unless noted; streaming methods return `AsyncIterable<AgentEvent>`.
|
|
64
|
+
|
|
65
|
+
### Run lifecycle
|
|
66
|
+
|
|
67
|
+
| Method | Returns | Notes |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `runStreaming(opts: RunOptions)` | `AsyncIterable<AgentEvent>` | Server-streams provider events for a fresh run. |
|
|
70
|
+
| `continueSession(opts: ContinueOptions)` | `AsyncIterable<AgentEvent>` | Continues an existing session. |
|
|
71
|
+
|
|
72
|
+
### Agent metadata
|
|
73
|
+
|
|
74
|
+
| Method | Returns | Notes |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `getAgent(agentId)` | `Promise<Agent>` | One agent's status + usage. Raises `AgentNotFoundError` if unknown. |
|
|
77
|
+
| `cancelAgent(agentId, opts?)` | `Promise<{ cancelledCount: number }>` | Cascades to children via `parent_agent_id`. Idempotent. |
|
|
78
|
+
| `listUserAgents(userId, opts?)` | `Promise<Agent[]>` | Optional filter by status (`running` / `completed` / `failed` / `cancelled`). |
|
|
79
|
+
| `getTranscript(sessionId)` | `Promise<TranscriptResponse>` | Persisted event log; one row per event with seq/run_id/ts_ns/type/event. |
|
|
80
|
+
| `health()` | `Promise<HealthResponse>` | Liveness probe. Hits `/healthz` (no `/v1` prefix). Unauthenticated. |
|
|
81
|
+
| `listUsers()` | `Promise<ListUsersResponse>` | Admin: known users with running-count summary. |
|
|
82
|
+
|
|
83
|
+
### Pause / Resume / State (v0.8.17 / v0.8.18)
|
|
84
|
+
|
|
85
|
+
| Method | Returns | Notes |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `pauseRuntime(opts?: { timeoutMs? })` | `Promise<PauseResult>` | Quiesce the runtime. Raises `AlreadyPausingError` on 409, `PauseNotConfiguredError` on 503. |
|
|
88
|
+
| `resumeRuntime()` | `Promise<ResumeResult>` | Release the quiesce. Raises `NotPausedError` on 409. |
|
|
89
|
+
| `getRuntimeState()` | `Promise<RuntimeStateResponse>` | Current state + paused-runs count. |
|
|
90
|
+
|
|
91
|
+
### Snapshot lifecycle (v0.8.17 / v0.8.18)
|
|
92
|
+
|
|
93
|
+
| Method | Returns | Notes |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `createSnapshot(opts?: CreateSnapshotOptions)` | `Promise<SnapshotCreateResponse>` | Capture envelope. Raises `SnapshotTooLargeError` on 413. |
|
|
96
|
+
| `listSnapshots(opts?: { limit?, labelContains? })` | `Promise<SnapshotDescriptor[]>` | Metadata only. |
|
|
97
|
+
| `getSnapshot(id)` | `Promise<SnapshotEnvelope>` | Full envelope including `json_content`. Raises `SnapshotNotFoundError` on 404. |
|
|
98
|
+
| `exportSnapshotURL(id)` | `string` | **Synchronous** — returns the download URL. Suitable for `<a href>` or piping to a HTTP download tool. |
|
|
99
|
+
| `restoreSnapshot(opts: { snapshotId? \| json?, includeHistory? })` | `Promise<SnapshotRestoreResponse>` | Restore from same-instance id OR inline envelope. Raises `SnapshotVersionError` on 422. |
|
|
100
|
+
| `deleteSnapshot(id)` | `Promise<void>` | Idempotent — 204 on both new and missing rows. |
|
|
101
|
+
|
|
102
|
+
Round-trip example:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const created = await client.createSnapshot({ label: "before-deploy" });
|
|
106
|
+
const env = await client.getSnapshot(created.id);
|
|
107
|
+
// ... move bytes to another loomcycle instance ...
|
|
108
|
+
const result = await otherClient.restoreSnapshot({ json: env.json_content });
|
|
109
|
+
console.log(`restored memory rows: ${result.memory_restored}`);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Memory admin
|
|
113
|
+
|
|
114
|
+
| Method | Returns | Notes |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `listMemoryScopes()` | `Promise<MemoryScopesResponse>` | Scope kinds (agent, user, etc.). |
|
|
117
|
+
| `listMemoryScopeIDs(scope)` | `Promise<MemoryScopeIDsResponse>` | scope_ids with row counts. |
|
|
118
|
+
| `listMemoryEntries(scope, scopeID, opts?)` | `Promise<MemoryEntriesResponse>` | Optional `prefix` + `limit`. |
|
|
119
|
+
| `getMemoryEntry(scope, scopeID, key)` | `Promise<MemoryEntryResponse>` | Single row read. |
|
|
120
|
+
|
|
121
|
+
### Interruption (v0.8.16)
|
|
122
|
+
|
|
123
|
+
| Method | Returns | Notes |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `listUserInterrupts(userId, opts?)` | `Promise<InterruptListResponse>` | Default filter: `status=pending`. |
|
|
126
|
+
| `listRunInterrupts(runId, opts?)` | `Promise<InterruptListResponse>` | Per-run interrupts. |
|
|
127
|
+
| `resolveInterrupt(runId, interruptId, opts: ResolveInterruptOptions)` | `Promise<unknown>` | Answer a pending interrupt. |
|
|
128
|
+
|
|
129
|
+
### Hook management (v0.8.18)
|
|
130
|
+
|
|
131
|
+
| Method | Returns | Notes |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `registerHook(opts: RegisterHookOptions)` | `Promise<RegisterHookResponse>` | Register a pre- or post-tool webhook. Re-registering the same `(owner, name)` replaces in-place with a fresh id. Raises `InvalidArgumentError` on 400 (bad URL / phase / missing field). |
|
|
134
|
+
| `listHooks()` | `Promise<Hook[]>` | Every registered hook. **In-memory only — empty after a loomcycle restart.** |
|
|
135
|
+
| `deleteHook(id)` | `Promise<void>` | Raises `HookNotFoundError` on 404. |
|
|
136
|
+
|
|
137
|
+
Hook registration is one side; the other side is the **callback receiver** — a small HTTP endpoint your app runs at the URL you registered. The adapter exports the wire shapes (`PreHookCall` / `PostHookCall` / `PreHookResult` / `PostHookResult`) so you can type the handler against the same JSON loomcycle posts.
|
|
138
|
+
|
|
139
|
+
**Register from your app's startup:**
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { LoomcycleClient } from "@loomcycle/client";
|
|
143
|
+
|
|
144
|
+
const client = new LoomcycleClient({
|
|
145
|
+
baseUrl: process.env.LOOMCYCLE_BASE_URL!,
|
|
146
|
+
authToken: process.env.LOOMCYCLE_AUTH_TOKEN,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await client.registerHook({
|
|
150
|
+
owner: "jobember-web", // (owner, name) is the identity tuple
|
|
151
|
+
name: "scan-webfetch", // re-registering same pair replaces in place
|
|
152
|
+
phase: "post", // "pre" or "post"
|
|
153
|
+
tools: ["WebFetch"], // empty/omitted = all tools
|
|
154
|
+
callbackUrl: "https://jobember.example/hooks/scan",
|
|
155
|
+
failMode: "open", // "open" = errors pass through; "closed" = errors fail the tool call
|
|
156
|
+
timeoutMs: 3000, // 0 = registry default (5s)
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Run the callback receiver** (Next.js App Router example — adapt to your framework):
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// app/hooks/scan/route.ts
|
|
164
|
+
import { NextResponse } from "next/server";
|
|
165
|
+
import type { PostHookCall, PostHookResult } from "@loomcycle/client";
|
|
166
|
+
|
|
167
|
+
export async function POST(req: Request) {
|
|
168
|
+
const body = (await req.json()) as PostHookCall;
|
|
169
|
+
// body.phase === "post", body.agent, body.tool_call.{id,name,input}, body.tool_result.{text,is_error}
|
|
170
|
+
|
|
171
|
+
// Telemetry-shaped: log + pass through.
|
|
172
|
+
console.log(`[hook] ${body.agent}.${body.tool_call.name} -> ${body.tool_result.is_error ? "error" : "ok"}`);
|
|
173
|
+
|
|
174
|
+
// Empty body = pass through unchanged. Return a PostHookResult to rewrite:
|
|
175
|
+
const reply: PostHookResult = {}; // or { result: { text: "redacted", is_error: false } }
|
|
176
|
+
return NextResponse.json(reply);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Pre-hook example** (short-circuit a tool call):
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
// app/hooks/guard/route.ts
|
|
184
|
+
import { NextResponse } from "next/server";
|
|
185
|
+
import type { PreHookCall, PreHookResult } from "@loomcycle/client";
|
|
186
|
+
|
|
187
|
+
export async function POST(req: Request) {
|
|
188
|
+
const body = (await req.json()) as PreHookCall;
|
|
189
|
+
|
|
190
|
+
// Deny outbound fetches to disallowed hosts
|
|
191
|
+
const input = body.tool_call.input as { url?: string };
|
|
192
|
+
if (input.url && new URL(input.url).hostname.endsWith(".internal")) {
|
|
193
|
+
const reply: PreHookResult = {
|
|
194
|
+
deny: { text: "internal hosts are not reachable from agents", is_error: true },
|
|
195
|
+
};
|
|
196
|
+
return NextResponse.json(reply);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return NextResponse.json({}); // pass through
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Important constraints**:
|
|
204
|
+
|
|
205
|
+
- Hook registrations are **in-memory** on the loomcycle server. Re-register on every app startup; the `(owner, name)` idempotency contract makes this safe (replaces in place).
|
|
206
|
+
- Auth flows one-way: loomcycle → your callback URL. Loomcycle does NOT attach a bearer token to callback POSTs by default. If you need to authenticate the caller, validate by source IP or include a shared secret in the `callback_url` path/query (`https://jobember.example/hooks/scan?secret=...`).
|
|
207
|
+
- `fail_mode: "open"` (default) is right for telemetry hooks where a down receiver shouldn't break tool dispatch. `"closed"` is right for security hooks where a down receiver should fail the tool call (don't let bypassed payloads through).
|
|
208
|
+
- `allow_hosts` in `PreHookResult` is a **trust-sensitive surface** — it widens the agent's outbound network policy for one tool call. Server enforces an operator-yaml allowlist (`hooks.permit_host_widen.owners`); your owner has to be on that list for `allow_hosts` to take effect. See the SECURITY note in `internal/hooks/types.go` before using.
|
|
209
|
+
|
|
210
|
+
## Errors
|
|
211
|
+
|
|
212
|
+
Non-2xx responses throw typed subclasses of `LoomcycleError`. The original HTTP status is on `e.status`; the truncated response body is on `e.bodyText` (≤1 KiB).
|
|
213
|
+
|
|
214
|
+
| HTTP status / body | Exception class |
|
|
215
|
+
|---|---|
|
|
216
|
+
| 400 | `InvalidArgumentError` |
|
|
217
|
+
| 401 | `AuthError` |
|
|
218
|
+
| 404 + "snapshot" | `SnapshotNotFoundError` ⎫ |
|
|
219
|
+
| 404 + "session" | `SessionNotFoundError` ⎬ all extend `NotFoundError` |
|
|
220
|
+
| 404 + "hook" | `HookNotFoundError` ⎬ |
|
|
221
|
+
| 404 + "agent" | `AgentNotFoundError` ⎬ |
|
|
222
|
+
| 404 (other) | `NotFoundError` (base) ⎭ catch any 404 with one `instanceof` |
|
|
223
|
+
| 409 + "already_pausing" / "already paused" | `AlreadyPausingError` |
|
|
224
|
+
| 409 + "not_paused" / "not paused" | `NotPausedError` |
|
|
225
|
+
| 409 + "session" | `SessionBusyError` |
|
|
226
|
+
| 409 + "agent_id" | `AgentIDInUseError` |
|
|
227
|
+
| 409 (other) | `LoomcycleError` (base) |
|
|
228
|
+
| 413 | `SnapshotTooLargeError` |
|
|
229
|
+
| 422 | `SnapshotVersionError` |
|
|
230
|
+
| 429 | `BackpressureError` |
|
|
231
|
+
| 503 + "pause manager not configured" | `PauseNotConfiguredError` (subclass of `UnavailableError` — back-compat) |
|
|
232
|
+
| 503 (other) | `UnavailableError` |
|
|
233
|
+
| 500 / other | `LoomcycleError` (base) |
|
|
234
|
+
|
|
235
|
+
Priority within `404`: most-specific keyword wins (`snapshot` → `session` → `hook` → `agent` → base). The dispatch is keyword-matched on the response body lowercase; a hook with id `hook_agent_scan` still routes to `HookNotFoundError`, not `AgentNotFoundError`.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import {
|
|
239
|
+
BackpressureError,
|
|
240
|
+
SnapshotNotFoundError,
|
|
241
|
+
LoomcycleError,
|
|
242
|
+
} from "@loomcycle/client";
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
for await (const ev of client.runStreaming({ /* ... */ })) {}
|
|
246
|
+
} catch (e) {
|
|
247
|
+
if (e instanceof BackpressureError) {
|
|
248
|
+
console.warn(`loomcycle backpressure (status=${e.status}): ${e.message}`);
|
|
249
|
+
} else if (e instanceof LoomcycleError) {
|
|
250
|
+
console.error(`loomcycle error ${e.status}: ${e.bodyText}`);
|
|
251
|
+
} else {
|
|
252
|
+
throw e;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Why HTTP, not gRPC
|
|
258
|
+
|
|
259
|
+
Loomcycle's HTTP+SSE surface is the canonical wire contract — every gRPC RPC has an HTTP equivalent (see `internal/api/http/server.go` for the route registrations). The Python adapter (gRPC) and this TS adapter (HTTP) cover the same surface; the choice between them is about ecosystem fit, not capability. HTTP+SSE works through every reverse proxy without special config; gRPC needs HTTP/2 + protoc round trips. For Node.js orchestrators that already have `fetch` in scope, HTTP is the simpler dependency.
|
|
260
|
+
|
|
261
|
+
## Development
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
cd adapters/ts
|
|
265
|
+
npm install
|
|
266
|
+
npm run typecheck # tsc --noEmit
|
|
267
|
+
npm run build # tsc → dist/
|
|
268
|
+
npm test # vitest run
|
|
269
|
+
npm run test:watch # vitest --watch
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Tests use Vitest with a Node environment. They mock `fetch` via constructor injection (no global monkey-patching). See `tests/helpers.ts` for the request-mock pattern.
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
Apache-2.0.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LoomcycleClient — the single public class exported by
|
|
3
|
+
* @loomcycle/client. Speaks HTTP+SSE to a running loomcycle sidecar.
|
|
4
|
+
*
|
|
5
|
+
* hooks-connector PR C: full Python-adapter parity + hook management.
|
|
6
|
+
* 27 methods total — 26 async (run streaming, continuation, agent
|
|
7
|
+
* metadata, transcript, health, users, pause/resume/state, snapshot
|
|
8
|
+
* lifecycle capture / list / get / restore / delete, memory admin,
|
|
9
|
+
* interruption listing + resolve, hook registration / list / delete)
|
|
10
|
+
* plus one synchronous helper (exportSnapshotURL builds a URL string
|
|
11
|
+
* without issuing a request).
|
|
12
|
+
*
|
|
13
|
+
* Construction:
|
|
14
|
+
*
|
|
15
|
+
* const client = new LoomcycleClient({
|
|
16
|
+
* baseUrl: "http://127.0.0.1:8787", // or process.env.LOOMCYCLE_BASE_URL
|
|
17
|
+
* authToken: "...", // or process.env.LOOMCYCLE_AUTH_TOKEN
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* Streaming methods (`runStreaming`, `continueSession`) return
|
|
21
|
+
* AsyncIterable<AgentEvent>; non-streaming methods return
|
|
22
|
+
* Promise<T>. Non-2xx responses throw typed errors from errors.ts
|
|
23
|
+
* via fetch-helpers.ts:raiseFromResponse — see README.md for the
|
|
24
|
+
* full mapping table.
|
|
25
|
+
*/
|
|
26
|
+
import type { Agent, AgentEvent, AgentStatus, CancelAgentResult, ClientOptions, ContinueOptions, CreateSnapshotOptions, HealthResponse, Hook, InterruptListResponse, InterruptStatus, ListUsersResponse, MemoryEntriesResponse, MemoryEntryResponse, MemoryScopeIDsResponse, MemoryScopesResponse, PauseResult, RegisterHookOptions, RegisterHookResponse, ResolveInterruptOptions, ResumeResult, RunOptions, RuntimeStateResponse, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotRestoreResponse, TranscriptResponse } from "./types.js";
|
|
27
|
+
export declare class LoomcycleClient {
|
|
28
|
+
private ctx;
|
|
29
|
+
constructor(opts?: ClientOptions);
|
|
30
|
+
/**
|
|
31
|
+
* Run an agent and stream events. Returns AsyncIterable<AgentEvent>;
|
|
32
|
+
* the iterator completes when the server closes the SSE stream.
|
|
33
|
+
*
|
|
34
|
+
* Errors during the run surface as `{ type: "error", error }` events;
|
|
35
|
+
* only transport / HTTP-level failures throw — and those throw typed
|
|
36
|
+
* errors (e.g. AuthError for 401, BackpressureError for 429).
|
|
37
|
+
*/
|
|
38
|
+
runStreaming(opts: RunOptions): AsyncIterable<AgentEvent>;
|
|
39
|
+
/**
|
|
40
|
+
* Continue an existing session with a new run. The session's prior
|
|
41
|
+
* transcript is replayed into the model's context server-side;
|
|
42
|
+
* this iterator yields only the NEW events from the continuation.
|
|
43
|
+
*
|
|
44
|
+
* Raises SessionNotFoundError when sessionId is unknown,
|
|
45
|
+
* SessionBusyError when another request is in flight on the same
|
|
46
|
+
* session.
|
|
47
|
+
*/
|
|
48
|
+
continueSession(opts: ContinueOptions): AsyncIterable<AgentEvent>;
|
|
49
|
+
/** Read one agent's status + usage stats. Raises AgentNotFoundError
|
|
50
|
+
* when the agent_id is unknown. */
|
|
51
|
+
getAgent(agentId: string, opts?: {
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
}): Promise<Agent>;
|
|
54
|
+
/** Cancel a live agent (cascades to children via parent_agent_id).
|
|
55
|
+
* Returns count of agents cancelled. Idempotent — already-terminated
|
|
56
|
+
* agents return 0. */
|
|
57
|
+
cancelAgent(agentId: string, opts?: {
|
|
58
|
+
reason?: string;
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}): Promise<CancelAgentResult>;
|
|
61
|
+
/** List a user's recent agent runs, optionally filtered by status. */
|
|
62
|
+
listUserAgents(userId: string, opts?: {
|
|
63
|
+
status?: AgentStatus;
|
|
64
|
+
signal?: AbortSignal;
|
|
65
|
+
}): Promise<Agent[]>;
|
|
66
|
+
/** Read the full event log for a session. Each entry has seq,
|
|
67
|
+
* run_id, ts_ns, type, event (the providers.Event payload). */
|
|
68
|
+
getTranscript(sessionId: string, opts?: {
|
|
69
|
+
signal?: AbortSignal;
|
|
70
|
+
}): Promise<TranscriptResponse>;
|
|
71
|
+
/** Liveness probe. Unauthenticated. Returns build info + uptime.
|
|
72
|
+
* Hits /healthz, not /v1/. */
|
|
73
|
+
health(opts?: {
|
|
74
|
+
signal?: AbortSignal;
|
|
75
|
+
}): Promise<HealthResponse>;
|
|
76
|
+
/** Admin: list known users with running-count summary. Drives the
|
|
77
|
+
* Web UI's user picker; operators with bearer auth can call too. */
|
|
78
|
+
listUsers(opts?: {
|
|
79
|
+
signal?: AbortSignal;
|
|
80
|
+
}): Promise<ListUsersResponse>;
|
|
81
|
+
/** Quiesce the runtime. Idempotent tools cancel immediately;
|
|
82
|
+
* non-idempotent + external tools get a grace window then
|
|
83
|
+
* force-cancel. Raises AlreadyPausingError on 409,
|
|
84
|
+
* PauseNotConfiguredError on 503. */
|
|
85
|
+
pauseRuntime(opts?: {
|
|
86
|
+
timeoutMs?: number;
|
|
87
|
+
signal?: AbortSignal;
|
|
88
|
+
}): Promise<PauseResult>;
|
|
89
|
+
/** Release the runtime quiesce. Raises NotPausedError on 409. */
|
|
90
|
+
resumeRuntime(opts?: {
|
|
91
|
+
signal?: AbortSignal;
|
|
92
|
+
}): Promise<ResumeResult>;
|
|
93
|
+
/** Current runtime state. Cheap query — atomic state + a
|
|
94
|
+
* bounded snapshots count. */
|
|
95
|
+
getRuntimeState(opts?: {
|
|
96
|
+
signal?: AbortSignal;
|
|
97
|
+
}): Promise<RuntimeStateResponse>;
|
|
98
|
+
/** Capture running-state into a per-section-semver JSON envelope.
|
|
99
|
+
* Raises SnapshotTooLargeError on 413 when the envelope exceeds
|
|
100
|
+
* LOOMCYCLE_SNAPSHOT_MAX_BYTES (default 512 MiB). */
|
|
101
|
+
createSnapshot(opts?: CreateSnapshotOptions & {
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
}): Promise<SnapshotCreateResponse>;
|
|
104
|
+
/** List captured snapshots (most-recent first). Capped at 200
|
|
105
|
+
* server-side; the limit param defaults to 200 too. */
|
|
106
|
+
listSnapshots(opts?: {
|
|
107
|
+
limit?: number;
|
|
108
|
+
labelContains?: string;
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
}): Promise<SnapshotDescriptor[]>;
|
|
111
|
+
/** Fetch the full snapshot envelope including JSON content.
|
|
112
|
+
* Distinct from exportSnapshot (which is operator-facing
|
|
113
|
+
* "where did this land on the host" semantics with a download
|
|
114
|
+
* URL). Raises SnapshotNotFoundError on 404. */
|
|
115
|
+
getSnapshot(snapshotId: string, opts?: {
|
|
116
|
+
signal?: AbortSignal;
|
|
117
|
+
}): Promise<SnapshotEnvelope>;
|
|
118
|
+
/** Returns the URL of the snapshot's canonical envelope —
|
|
119
|
+
* synchronous and side-effect-free; does NOT issue an HTTP
|
|
120
|
+
* request. The endpoint is bearer-authed like every other
|
|
121
|
+
* `/v1/_snapshots/*` route, so callers must attach the same
|
|
122
|
+
* `Authorization: Bearer <token>` header when fetching this
|
|
123
|
+
* URL (e.g. `curl -H "Authorization: Bearer $TOKEN" ...`).
|
|
124
|
+
* There is no token query-param fallback. */
|
|
125
|
+
exportSnapshotURL(snapshotId: string): string;
|
|
126
|
+
/** Restore from a same-instance snapshot id OR an inline
|
|
127
|
+
* envelope JSON. Idempotent: ON CONFLICT DO NOTHING per row;
|
|
128
|
+
* the returned counters reflect rows actually written.
|
|
129
|
+
* Raises SnapshotVersionError on 422 when a section's
|
|
130
|
+
* declared version is newer than the reader supports. */
|
|
131
|
+
restoreSnapshot(opts: {
|
|
132
|
+
snapshotId?: string;
|
|
133
|
+
/** Pass a parsed JSON object (e.g. `getSnapshot.json_content` or
|
|
134
|
+
* the result of JSON.parse on an exported envelope). */
|
|
135
|
+
json?: unknown;
|
|
136
|
+
includeHistory?: boolean;
|
|
137
|
+
signal?: AbortSignal;
|
|
138
|
+
}): Promise<SnapshotRestoreResponse>;
|
|
139
|
+
/** Delete a snapshot. Idempotent — succeeds whether or not the
|
|
140
|
+
* row existed (server returns 204 in both cases). */
|
|
141
|
+
deleteSnapshot(snapshotId: string, opts?: {
|
|
142
|
+
signal?: AbortSignal;
|
|
143
|
+
}): Promise<void>;
|
|
144
|
+
/** List the kinds of memory scopes the server knows about
|
|
145
|
+
* (agent, user — or whatever the operator yaml declares). */
|
|
146
|
+
listMemoryScopes(opts?: {
|
|
147
|
+
signal?: AbortSignal;
|
|
148
|
+
}): Promise<MemoryScopesResponse>;
|
|
149
|
+
/** List the scope_ids that have at least one memory row under
|
|
150
|
+
* a given scope. */
|
|
151
|
+
listMemoryScopeIDs(scope: string, opts?: {
|
|
152
|
+
signal?: AbortSignal;
|
|
153
|
+
}): Promise<MemoryScopeIDsResponse>;
|
|
154
|
+
/** List memory entries under a (scope, scope_id) tuple.
|
|
155
|
+
* Optional prefix narrows by key prefix. */
|
|
156
|
+
listMemoryEntries(scope: string, scopeID: string, opts?: {
|
|
157
|
+
prefix?: string;
|
|
158
|
+
limit?: number;
|
|
159
|
+
signal?: AbortSignal;
|
|
160
|
+
}): Promise<MemoryEntriesResponse>;
|
|
161
|
+
/** Read a single memory entry by (scope, scope_id, key). */
|
|
162
|
+
getMemoryEntry(scope: string, scopeID: string, key: string, opts?: {
|
|
163
|
+
signal?: AbortSignal;
|
|
164
|
+
}): Promise<MemoryEntryResponse>;
|
|
165
|
+
/** List interrupts addressable to a user_id. Default filter is
|
|
166
|
+
* status=pending. */
|
|
167
|
+
listUserInterrupts(userId: string, opts?: {
|
|
168
|
+
status?: InterruptStatus;
|
|
169
|
+
signal?: AbortSignal;
|
|
170
|
+
}): Promise<InterruptListResponse>;
|
|
171
|
+
/** List interrupts emitted by a specific run. */
|
|
172
|
+
listRunInterrupts(runId: string, opts?: {
|
|
173
|
+
status?: InterruptStatus;
|
|
174
|
+
signal?: AbortSignal;
|
|
175
|
+
}): Promise<InterruptListResponse>;
|
|
176
|
+
/** Resolve a pending Interruption.ask from outside the agent
|
|
177
|
+
* loop. Lets a TS-side dashboard or service act as the human
|
|
178
|
+
* answerer when operator yaml configures the consumer-MCP
|
|
179
|
+
* backend. */
|
|
180
|
+
resolveInterrupt(runId: string, interruptId: string, opts: ResolveInterruptOptions & {
|
|
181
|
+
signal?: AbortSignal;
|
|
182
|
+
}): Promise<unknown>;
|
|
183
|
+
/** Register a pre- or post-tool webhook. The callback_url must be
|
|
184
|
+
* an http:// or https:// endpoint the CONSUMER runs — loomcycle
|
|
185
|
+
* POSTs PreHookCall / PostHookCall payloads to it. This method
|
|
186
|
+
* manages registration only; the receiver is the consumer's own
|
|
187
|
+
* HTTP framework (Express, Next.js, etc.).
|
|
188
|
+
*
|
|
189
|
+
* Re-registering the same (owner, name) replaces the prior entry
|
|
190
|
+
* with a fresh id (idempotent app-restart contract).
|
|
191
|
+
*
|
|
192
|
+
* Raises InvalidArgumentError on 400 (bad URL / phase / missing
|
|
193
|
+
* required fields). */
|
|
194
|
+
registerHook(opts: RegisterHookOptions & {
|
|
195
|
+
signal?: AbortSignal;
|
|
196
|
+
}): Promise<RegisterHookResponse>;
|
|
197
|
+
/** List every currently-registered hook. Returns the array
|
|
198
|
+
* unwrapped (the wire envelope is `{hooks: [...]}` — we strip
|
|
199
|
+
* the envelope to match listUserAgents). In-memory only — empty
|
|
200
|
+
* after a loomcycle restart. */
|
|
201
|
+
listHooks(opts?: {
|
|
202
|
+
signal?: AbortSignal;
|
|
203
|
+
}): Promise<Hook[]>;
|
|
204
|
+
/** Delete a registered hook by id. Raises HookNotFoundError on
|
|
205
|
+
* 404. Returns void on success (the HTTP 200 body `{deleted: id}`
|
|
206
|
+
* is dropped — callers already know the id they passed). */
|
|
207
|
+
deleteHook(id: string, opts?: {
|
|
208
|
+
signal?: AbortSignal;
|
|
209
|
+
}): Promise<void>;
|
|
210
|
+
/** Shared SSE POST → stream-of-AgentEvent path. Used by
|
|
211
|
+
* runStreaming + continueSession. */
|
|
212
|
+
private streamSSE;
|
|
213
|
+
}
|