mindwire 0.1.0 → 0.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.
@@ -0,0 +1,476 @@
1
+ import { Http, type FetchLike } from "./http.js";
2
+ import { Run } from "./run.js";
3
+ import { type Target } from "./target/index.js";
4
+ import type { EnsureEvent } from "./target/host.js";
5
+ import type { AgentInfo, AuthMethod, AuthState, AuthStatus, Catalog, ChatSummary, CustomProvider, DeleteResult, DoctorReport, Health, MCPServer, MemoryDoc, MemoryScope, Message, ModelInfo, Notification, NotifyChannel, NotifyChannelInput, NotifyChannelTestResult, NotifyConfigInput, NotifyConfigStatus, NotifyRule, NotifyRuleInput, ProcessFrame, PromptTemplate, ResolveOptions, SetupStatus, Stats, Subagent, TurnOptions } from "./types.js";
6
+ export interface MindwireOptions {
7
+ /**
8
+ * Default agent type for agent-scoped calls (e.g. `"claude-code"`). When omitted, the daemon
9
+ * uses its own default. Override per call with the `agent` option, or scope a whole client
10
+ * with {@link Mindwire.withAgent}.
11
+ */
12
+ agent?: string;
13
+ /**
14
+ * **Where the daemon runs and how the SDK reaches it.** A {@link Target} factory: {@link local}
15
+ * (the default — an embedded loopback daemon, auto-spawned on a server runtime), {@link remote}
16
+ * (a daemon you already run, required in the browser), or {@link import("./target/ssh.js").ssh} /
17
+ * {@link import("./target/docker.js").docker} / {@link import("./target/oblien.js").oblien} — or any
18
+ * object implementing `Target`. One `new Mindwire` = one target = one daemon = one environment; to
19
+ * isolate agents, create another `Mindwire` with its own target. Omit for the zero-config default.
20
+ */
21
+ target?: Target;
22
+ /**
23
+ * Receives a step {@link EnsureEvent} while a target provisions (connect → probe → upload → launch →
24
+ * ready, or skip). Handy to surface the seconds-long "upload the daemon + launch + health-poll" of a
25
+ * fresh SSH/Docker/Oblien box. A throwing callback can't abort provisioning.
26
+ */
27
+ logger?: (e: EnsureEvent) => void;
28
+ /** Custom fetch. Defaults to the global `fetch` (Node 18+, Bun, Deno, browsers). */
29
+ fetch?: FetchLike;
30
+ /** Extra headers merged into every request. */
31
+ headers?: Record<string, string>;
32
+ /**
33
+ * Deadline in milliseconds for a single unary request (health, config, `turn` creation, memory,
34
+ * …). Guards against a hung daemon or a dead tunnel blocking a call forever — on expiry the call
35
+ * rejects with {@link import("./errors.js").TimeoutError}. Does **not** apply to the event stream,
36
+ * which is long-lived. Defaults to 120000; pass `0` to disable.
37
+ */
38
+ requestTimeoutMs?: number;
39
+ }
40
+ /** Options accepted by any agent-scoped method to override the client's default agent. */
41
+ export interface AgentScoped {
42
+ agent?: string;
43
+ }
44
+ /**
45
+ * The mindwire client — one typed surface over the daemon, regardless of which coding-agent
46
+ * harness runs behind `?agent=<type>`.
47
+ *
48
+ * **Where** it runs is one option — `target`. By default it's an **embedded** daemon
49
+ * ({@link local}): on a server runtime it auto-spawns the bundled `mindwired` on loopback, so there's
50
+ * nothing to deploy. Point elsewhere with {@link remote} (a daemon you run; also required in the
51
+ * browser), {@link import("./target/ssh.js").ssh}, {@link import("./target/docker.js").docker}, or
52
+ * {@link import("./target/oblien.js").oblien}. One client = one target = one daemon = one environment.
53
+ *
54
+ * ```ts
55
+ * // Local embedded (default) — Node/Bun/Deno:
56
+ * const mw = new Mindwire({ agent: "claude-code" });
57
+ *
58
+ * // Remote — or in the browser:
59
+ * const mw = new Mindwire({ target: remote("https://mindwire.yourco.com", { token }) });
60
+ *
61
+ * // A fresh box over SSH, watching it provision:
62
+ * const mw = new Mindwire({ target: ssh({ host, username: "root" }), logger: (e) => console.log(e.phase, e.message) });
63
+ * await mw.ensure();
64
+ *
65
+ * const run = await mw.turn({ chatId: "c1", message: "add a health check" });
66
+ * for await (const ev of run) console.log(ev.type, ev.text ?? "");
67
+ * ```
68
+ */
69
+ export declare class Mindwire {
70
+ readonly http: Http;
71
+ /** The default agent type applied to agent-scoped calls, if set. */
72
+ readonly defaultAgent: string | undefined;
73
+ /** Step-flow auth, scoped to this client's default agent (override per call). */
74
+ readonly auth: AuthApi;
75
+ /** Persistent memory files + saved prompt templates, scoped to this client's default agent. */
76
+ readonly prompts: PromptsApi;
77
+ /** Persistent MCP-server config (the config an agent loads every run), scoped to this client's default agent. */
78
+ readonly mcp: McpApi;
79
+ /** Custom LLM-provider registration (opencode/Codex native config), scoped to this client's default agent. */
80
+ readonly providers: ProvidersApi;
81
+ /** Daemon-driven notification channels + routing rules (webhook/slack/discord/telegram; per-agent/session/global). */
82
+ readonly notify: NotifyApi;
83
+ constructor(opts?: MindwireOptions);
84
+ /**
85
+ * Provision the destination and await readiness **now**, rather than lazily on the first request —
86
+ * useful to front-load a fresh SSH/Docker/Oblien box (and to stream its {@link EnsureEvent}s to the
87
+ * `logger`) before the first `turn()`. Idempotent and memoized: repeated `ensure()` calls, and the
88
+ * first real request, all await the same provisioning, so the target connects exactly once.
89
+ */
90
+ ensure(): Promise<void>;
91
+ /** Return a new client bound to a different default agent (shares the same transport config). */
92
+ withAgent(agent: string): Mindwire;
93
+ /** The `?agent=` value to send for a scoped call: explicit override → client default → none. */
94
+ agentParam(scoped?: AgentScoped): {
95
+ agent: string;
96
+ } | undefined;
97
+ /**
98
+ * Release target-owned resources by calling the {@link TargetHandle}'s `stop()` — once, even across
99
+ * {@link withAgent} clones that share the transport. For an `ssh`/`docker`/`oblien` target this reaps
100
+ * the box (tear down the tunnel, stop or delete the container/workspace, per `stopOnExit`); a no-op
101
+ * for `local`/`remote` (embedded self-cleans on process exit; remote is not ours to stop). Safe to
102
+ * call before the target has connected (nothing to reap yet).
103
+ */
104
+ close(): Promise<void>;
105
+ /** `GET /healthz` — public liveness check. Resolves with the daemon's health payload when it is up. */
106
+ health(): Promise<Health>;
107
+ /**
108
+ * `GET /stats` — the daemon **process's** resource snapshot (heap in use, memory reserved from the
109
+ * OS, goroutines, GC cycles, cores, platform, uptime). Cheap enough to call on demand — the daemon
110
+ * reads its own Go runtime, not the machine — so a UI can fetch it when a user opens a daemon's page
111
+ * without any background polling. See {@link Stats} for what each field means and doesn't.
112
+ */
113
+ stats(): Promise<Stats>;
114
+ /** `GET /catalog` — every agent this daemon binary supports. */
115
+ catalog(): Promise<Catalog>;
116
+ /** `GET /agent` — capabilities + settings schema + auth methods/status for the selected agent. */
117
+ agent(scoped?: AgentScoped): Promise<AgentInfo>;
118
+ /**
119
+ * `GET /models` — the models the selected agent can run for the configured account. An empty array
120
+ * is valid (no credentials yet / offline). Throws a 400 {@link ApiError} for an agent whose model is
121
+ * free text (check `capabilities.models` first).
122
+ */
123
+ models(scoped?: AgentScoped): Promise<ModelInfo[]>;
124
+ /** `GET /doctor` — daemon-level health plus the selected agent's own checks. */
125
+ doctor(scoped?: AgentScoped): Promise<DoctorReport>;
126
+ /** `POST /setup` — start the agent's install toolchain (background; poll {@link setupStatus}). */
127
+ setup(scoped?: AgentScoped): Promise<SetupStatus>;
128
+ /** `POST /update` — re-run the toolchain, forcing reinstall of installable steps. */
129
+ update(scoped?: AgentScoped): Promise<SetupStatus>;
130
+ /** `GET /setup` — current toolchain install progress. */
131
+ setupStatus(scoped?: AgentScoped): Promise<SetupStatus>;
132
+ /** `GET /config` — the declared, non-secret settings for the agent. */
133
+ getConfig(scoped?: AgentScoped): Promise<Record<string, string>>;
134
+ /** `PUT /config` — merge recognized (non-secret) setting keys. Unknown keys are ignored server-side. */
135
+ setConfig(values: Record<string, string>, scoped?: AgentScoped): Promise<void>;
136
+ /** `GET /chats` — recorded chats (newest first), shared across agents. */
137
+ chats(): Promise<ChatSummary[]>;
138
+ /**
139
+ * `PUT /chats/{id}` — rename a chat. The user title wins over the agent's native auto-title in
140
+ * every listing; an empty title clears the rename (reverting to the native/derived title).
141
+ * Returns the updated summary.
142
+ */
143
+ renameChat(chatId: string, title: string): Promise<ChatSummary>;
144
+ /**
145
+ * `DELETE /chats/{id}` — a true, irreversible delete: purges ALL of the chat's mindwire
146
+ * bookkeeping and, for every session the chat mapped to, removes that agent's native transcript
147
+ * (the source of truth). Rejects with a 409 error if a turn is live. Native deletion is
148
+ * best-effort per agent; the result reports what was purged vs. failed.
149
+ */
150
+ deleteChat(chatId: string): Promise<DeleteResult>;
151
+ /**
152
+ * `POST /chats/{id}/fork` — clone a chat into a new id (generated when `newChatId` is omitted).
153
+ * The fork shares the source's native session until its first turn, which branches it (natively
154
+ * on Claude via `--fork-session`; a fresh session on agents without native fork). Rejects with a
155
+ * 409 if the source has a live turn, 404 if the source is unknown, 400 if the target id is in
156
+ * use. Returns the new chat's summary.
157
+ */
158
+ forkChat(chatId: string, opts?: {
159
+ newChatId?: string;
160
+ }): Promise<ChatSummary>;
161
+ /**
162
+ * `GET /chats/{id}/messages` — a chat's transcript (native when the agent supports it, else
163
+ * the recorded fallback). `limit` caps to the newest N; `before` pages older history.
164
+ */
165
+ messages(chatId: string, opts?: AgentScoped & {
166
+ limit?: number;
167
+ before?: string;
168
+ }): Promise<Message[]>;
169
+ /** `GET /chats/{id}/run` — the latest run for a chat (reattach anchor), or `null` if none yet. */
170
+ latestRun(chatId: string): Promise<Run | null>;
171
+ /**
172
+ * `POST /turns` — start a turn. Returns a {@link Run} handle you can stream, cancel, or await.
173
+ * Rejects with an {@link ApiError} (409) if a turn is already running for the chat.
174
+ *
175
+ * `mode` defaults to `"turn"` — one agent turn that ends when the CLI settles. Pass
176
+ * `mode: "resolve"` (with optional {@link ResolveOptions} `resolve` caps) for a global-resolve run
177
+ * that auto-continues the agent's multi-step work until it's done; {@link Mindwire.resolve} is the
178
+ * clearer entry point for that. The returned {@link Run} is then the parent of the run tree.
179
+ */
180
+ turn(input: {
181
+ chatId: string;
182
+ message: string;
183
+ cwd?: string;
184
+ options?: TurnOptions;
185
+ mode?: "turn" | "resolve";
186
+ resolve?: ResolveOptions;
187
+ } & AgentScoped): Promise<Run>;
188
+ /**
189
+ * `POST /turns {mode:"resolve"}` — start a **global-resolve** run: instead of returning after one
190
+ * turn, the daemon holds the task open and auto-continues the agent (resuming on continuable stops
191
+ * and probing for completion) until the work is globally resolved, then aggregates one final result.
192
+ *
193
+ * The returned {@link Run} is the **parent** of a run tree: each auto-continued iteration is a child
194
+ * turn ({@link Run.children}) whose events stream onto the parent's topic, delimited by `continuation`
195
+ * boundary events. `run.wait()` resolves once with the aggregated result; `run.stopReason` /
196
+ * `run.iterations` report how the loop ended. Resolve turns run unattended (no mid-turn approvals)
197
+ * and are bounded by {@link ResolveOptions} caps — see the resolve guide. Rejects with an
198
+ * {@link ApiError} (409) if a turn is already running for the chat.
199
+ */
200
+ resolve(input: {
201
+ chatId: string;
202
+ message: string;
203
+ cwd?: string;
204
+ options?: TurnOptions;
205
+ resolve?: ResolveOptions;
206
+ } & AgentScoped): Promise<Run>;
207
+ /** `GET /runs/{id}` — fetch an existing run as a {@link Run} handle. */
208
+ run(id: string): Promise<Run>;
209
+ /**
210
+ * `POST /chats/{id}/compact` — run an on-demand conversation compaction as a first-class {@link Run}
211
+ * you can stream or await. The agent folds prior context into a summary it carries forward, emitting
212
+ * a `compaction` event on the stream and recording the boundary in history exactly like an
213
+ * auto-compaction. Optional `instructions` focus the continuation summary (Claude's
214
+ * `/compact <instructions>`; agents that don't honor focus still compact). Rejects with an
215
+ * {@link ApiError}: 400 if the agent doesn't support compaction (`capabilities.compactNow`) or the
216
+ * chat has no conversation yet, 409 if a turn is already running for the chat.
217
+ */
218
+ compact(chatId: string, opts?: {
219
+ instructions?: string;
220
+ } & AgentScoped): Promise<Run>;
221
+ /** `GET /notify/config` — whether a notification channel is wired (token never returned). */
222
+ getNotifyConfig(): Promise<NotifyConfigStatus>;
223
+ /** `PUT /notify/config` — store the provisioned notification channel (daemon-wide). */
224
+ setNotifyConfig(input: NotifyConfigInput): Promise<void>;
225
+ /** `GET /notify/stream` — SSE feed of the daemon's notifications (replay, then live). */
226
+ notifications(opts?: {
227
+ signal?: AbortSignal;
228
+ }): AsyncGenerator<Notification>;
229
+ /**
230
+ * `GET /processes/stream` — SSE feed of live per-turn CPU/memory, one {@link ProcessFrame} per tick.
231
+ * Sampling is **on demand**: the daemon starts measuring only while a client is connected and stops
232
+ * the instant the last one disconnects, so aborting the `signal` (or ending the loop) tells the
233
+ * daemon to stop — no background work, no leak. Pass `agent` to filter each frame's samples to one
234
+ * agent type. Frames are live snapshots (no replay); an empty `samples` array is a valid keep-alive.
235
+ */
236
+ processes(opts?: {
237
+ agent?: string;
238
+ signal?: AbortSignal;
239
+ }): AsyncGenerator<ProcessFrame>;
240
+ }
241
+ /** Step-flow auth (`methods → begin → step → status`) for a scoped agent. */
242
+ export declare class AuthApi {
243
+ private readonly mw;
244
+ constructor(mw: Mindwire);
245
+ /** `GET /auth/methods` — the options list to present. */
246
+ methods(scoped?: AgentScoped): Promise<AuthMethod[]>;
247
+ /** `POST /auth/begin` — start one method (may return `{ url, code, fields, pending }`). */
248
+ begin(method: string, scoped?: AgentScoped): Promise<AuthState>;
249
+ /** `POST /auth/step` — submit fields, or poll an interactive login to completion. */
250
+ step(input: Record<string, string>, scoped?: AgentScoped): Promise<AuthState>;
251
+ /** `GET /auth/status` — is the agent authenticated, and via which method. */
252
+ status(scoped?: AgentScoped): Promise<AuthStatus>;
253
+ }
254
+ /**
255
+ * The persistent prompt/memory surface for a scoped agent — the three layers beneath a turn's
256
+ * per-turn `systemPrompt`: the agent's **memory file** (`CLAUDE.md` / `AGENTS.md`) and its saved
257
+ * **prompt templates** (slash-commands / saved prompts). One shape regardless of which agent runs;
258
+ * a call 400s if the selected agent doesn't support the layer.
259
+ *
260
+ * `dir` selects the project-scope working directory (defaults to the daemon cwd); `scope` picks
261
+ * `project` vs `user` (the agent's home config dir). Only agents whose `Capabilities.memory` /
262
+ * `Capabilities.promptTemplates` is set expose these — read that first to decide what to render.
263
+ */
264
+ export declare class PromptsApi {
265
+ private readonly mw;
266
+ constructor(mw: Mindwire);
267
+ private query;
268
+ /**
269
+ * `GET /memory` — the agent's memory file at every supported scope (project + user for both
270
+ * Claude and Codex). Each entry carries the resolved `path` and `exists`; `content` is `""` for
271
+ * an absent file.
272
+ */
273
+ memory(opts?: AgentScoped & {
274
+ dir?: string;
275
+ }): Promise<MemoryDoc[]>;
276
+ /** `PUT /memory` — write the memory file at `scope`. Returns the resulting {@link MemoryDoc}. */
277
+ setMemory(input: {
278
+ scope: MemoryScope;
279
+ content: string;
280
+ }, opts?: AgentScoped & {
281
+ dir?: string;
282
+ }): Promise<MemoryDoc>;
283
+ /**
284
+ * `DELETE /memory` — remove the memory file at `scope` (defaults to `user`). Returns the resulting
285
+ * {@link MemoryDoc} (`exists: false` at the resolved path). Idempotent: deleting an absent file still
286
+ * succeeds.
287
+ */
288
+ deleteMemory(opts?: AgentScoped & {
289
+ scope?: MemoryScope;
290
+ dir?: string;
291
+ }): Promise<MemoryDoc>;
292
+ /**
293
+ * `GET /prompts` — saved prompt templates across every supported scope (Claude: project + user;
294
+ * Codex: user only). `content` is omitted here; fetch it with {@link get}. A missing project
295
+ * directory yields an empty list for that scope rather than an error.
296
+ */
297
+ list(opts?: AgentScoped & {
298
+ dir?: string;
299
+ }): Promise<PromptTemplate[]>;
300
+ /** `GET /prompts/{name}` — one template's full body. Rejects with a 404 `ApiError` if absent. */
301
+ get(name: string, opts?: AgentScoped & {
302
+ scope?: MemoryScope;
303
+ dir?: string;
304
+ }): Promise<PromptTemplate>;
305
+ /** `PUT /prompts/{name}` — create or overwrite a template. Returns the resulting {@link PromptTemplate}. */
306
+ set(name: string, content: string, opts?: AgentScoped & {
307
+ scope?: MemoryScope;
308
+ dir?: string;
309
+ }): Promise<PromptTemplate>;
310
+ /**
311
+ * `DELETE /prompts/{name}` — remove one template at `scope` (defaults to `user`). Idempotent:
312
+ * deleting an absent template still succeeds. A traversal name rejects with a 400 `ApiError`.
313
+ */
314
+ delete(name: string, opts?: AgentScoped & {
315
+ scope?: MemoryScope;
316
+ dir?: string;
317
+ }): Promise<void>;
318
+ /**
319
+ * `GET /subagents` — persistent subagent definitions (Claude `.claude/agents/*.md`) across every
320
+ * supported scope. `content` is omitted here (fetch it with {@link subagent}); `meta` is the parsed
321
+ * frontmatter view. Rejects with a 400 `ApiError` on an agent without the subagent-definition module.
322
+ * Distinct from a turn's per-turn `subagents` passthrough — this is the on-disk definition store.
323
+ */
324
+ subagents(opts?: AgentScoped & {
325
+ dir?: string;
326
+ }): Promise<Subagent[]>;
327
+ /** `GET /subagents/{name}` — one definition's raw body + parsed meta. 404 `ApiError` if absent. */
328
+ subagent(name: string, opts?: AgentScoped & {
329
+ scope?: MemoryScope;
330
+ dir?: string;
331
+ }): Promise<Subagent>;
332
+ /** `PUT /subagents/{name}` — create or overwrite a definition (raw content is canonical). Returns it. */
333
+ setSubagent(name: string, content: string, opts?: AgentScoped & {
334
+ scope?: MemoryScope;
335
+ dir?: string;
336
+ }): Promise<Subagent>;
337
+ /**
338
+ * `DELETE /subagents/{name}` — remove one definition at `scope` (defaults to `user`). Idempotent:
339
+ * deleting an absent definition still succeeds. A traversal name rejects with a 400 `ApiError`.
340
+ */
341
+ deleteSubagent(name: string, opts?: AgentScoped & {
342
+ scope?: MemoryScope;
343
+ dir?: string;
344
+ }): Promise<void>;
345
+ }
346
+ /**
347
+ * The persistent MCP-server surface for a scoped agent — the servers an agent loads on **every run**
348
+ * from its own on-disk config (Claude's project `.mcp.json` + user `.claude.json`, Codex's
349
+ * `config.toml`), as opposed to a turn's per-turn {@link TurnOptions.mcpServers} overlay. One shape
350
+ * regardless of which agent runs; a call 400s if the selected agent doesn't expose the config
351
+ * (check `capabilities.mcpConfig` first).
352
+ *
353
+ * `scope` picks `project` vs `user` (defaults to `user` — the scope every supporting agent has;
354
+ * Codex is user-only); `dir` selects the project-scope working directory (defaults to the daemon cwd).
355
+ * No secret ever crosses this surface — HTTP auth travels as `bearerTokenEnvVar` (an env-var name).
356
+ */
357
+ export declare class McpApi {
358
+ private readonly mw;
359
+ constructor(mw: Mindwire);
360
+ private query;
361
+ /**
362
+ * `GET /mcp` — every persistent MCP server across the agent's supported scopes, keyed
363
+ * `scope → name → server` (Claude: project + user; Codex: user only). A missing config file yields
364
+ * an empty object for that scope rather than an error.
365
+ */
366
+ list(opts?: AgentScoped & {
367
+ dir?: string;
368
+ }): Promise<Partial<Record<MemoryScope, Record<string, MCPServer>>>>;
369
+ /** `GET /mcp/{name}` — one server's definition. Rejects with a 404 `ApiError` if it isn't configured. */
370
+ get(name: string, opts?: AgentScoped & {
371
+ scope?: MemoryScope;
372
+ dir?: string;
373
+ }): Promise<MCPServer>;
374
+ /** `PUT /mcp/{name}` — create or overwrite one server. Returns the stored definition. */
375
+ set(name: string, server: MCPServer, opts?: AgentScoped & {
376
+ scope?: MemoryScope;
377
+ dir?: string;
378
+ }): Promise<MCPServer>;
379
+ /** `DELETE /mcp/{name}` — remove one server. Idempotent: deleting an absent server still succeeds. */
380
+ delete(name: string, opts?: AgentScoped & {
381
+ scope?: MemoryScope;
382
+ dir?: string;
383
+ }): Promise<void>;
384
+ }
385
+ /**
386
+ * The custom LLM-provider surface for a scoped agent — the OpenAI-compatible endpoints an agent loads on
387
+ * **every run** from its own native config (opencode's `opencode.json` `provider.<id>`, Codex's
388
+ * `config.toml` `[model_providers.<id>]`). One shape regardless of agent; a call 400s if the selected
389
+ * agent can't materialize one (check `capabilities.customProviders` — Claude uses its gateway auth lane
390
+ * instead). Registered models then appear in {@link Mindwire.models} with `custom:true`.
391
+ *
392
+ * `scope` picks `project` vs `user` (defaults to `user`; opencode/Codex are user-only); `dir` selects the
393
+ * project-scope working directory (defaults to the daemon cwd).
394
+ *
395
+ * SECURITY: secrets are passed write-only via {@link set}'s `apiKey` / `secrets` options and reported only
396
+ * as `hasKey` (plus the stored `envVars` NAMES). They never cross this surface on the way back and are never
397
+ * written literally into any config file — the harness references them through env-var placeholders and the
398
+ * daemon exports them at run time.
399
+ */
400
+ export declare class ProvidersApi {
401
+ private readonly mw;
402
+ constructor(mw: Mindwire);
403
+ private query;
404
+ /**
405
+ * `GET /providers` — every registered custom provider across the agent's supported scopes, keyed
406
+ * `scope → id → provider` (opencode/Codex: user only). A missing config file yields an empty object for
407
+ * that scope rather than an error. `hasKey` reports whether a secret is stored; the key is never returned.
408
+ */
409
+ list(opts?: AgentScoped & {
410
+ dir?: string;
411
+ }): Promise<Partial<Record<MemoryScope, Record<string, CustomProvider>>>>;
412
+ /** `GET /providers/{id}` — one provider's definition. Rejects with a 404 `ApiError` if it isn't configured. */
413
+ get(id: string, opts?: AgentScoped & {
414
+ scope?: MemoryScope;
415
+ dir?: string;
416
+ }): Promise<CustomProvider>;
417
+ /**
418
+ * `PUT /providers/{id}` — create or overwrite one provider, returning the stored definition (with
419
+ * `hasKey` and the stored `envVars`). Two write-only secret channels, both optional: `opts.apiKey` is a
420
+ * single key (custom endpoints, single-key catalog brands); `opts.secrets` is a NAME→VALUE map for a
421
+ * catalog provider whose entry declares MULTIPLE env vars (e.g. AWS Bedrock). Omitting both leaves any
422
+ * previously stored secret intact. The path `id` wins over any `id` on the provider value.
423
+ */
424
+ set(id: string, provider: Omit<CustomProvider, "id" | "hasKey"> & Partial<Pick<CustomProvider, "hasKey">>, opts?: AgentScoped & {
425
+ scope?: MemoryScope;
426
+ dir?: string;
427
+ apiKey?: string;
428
+ secrets?: Record<string, string>;
429
+ }): Promise<CustomProvider>;
430
+ /** `DELETE /providers/{id}` — remove one provider and clear its stored key. Idempotent. */
431
+ delete(id: string, opts?: AgentScoped & {
432
+ scope?: MemoryScope;
433
+ dir?: string;
434
+ }): Promise<void>;
435
+ }
436
+ /**
437
+ * Daemon-driven notification fan-out: named **channels** (a webhook URL shaped for
438
+ * webhook/Slack/Discord/Telegram, with optional headers, a bearer token, and — for the raw webhook
439
+ * type — an HMAC signing secret) and the **rules** that route matching notifications to them
440
+ * (`global` / per-`agent` / per-`session`, with optional event selection). This is daemon-wide
441
+ * config (NOT agent-scoped): the daemon evaluates every rule against each notification it emits and
442
+ * POSTs to the union of the matched channels — additive over the single {@link Mindwire.setNotifyConfig}
443
+ * webhook.
444
+ *
445
+ * Secrets are **write-only**: a channel read back via {@link channels} never carries the URL, token,
446
+ * secret, or header values — only their presence (and the URL host). On {@link setChannel}, omitting
447
+ * `url`/`token`/`secret` preserves the stored value; send a new value to rotate it.
448
+ */
449
+ export declare class NotifyApi {
450
+ private readonly mw;
451
+ constructor(mw: Mindwire);
452
+ /** `GET /notify/channels` — every channel, masked (no secrets). */
453
+ channels(): Promise<NotifyChannel[]>;
454
+ /** `POST /notify/channels` — create a channel (server-assigns the id). Returns it masked. */
455
+ createChannel(input: NotifyChannelInput): Promise<NotifyChannel>;
456
+ /**
457
+ * `PUT /notify/channels/{id}` — update a channel, merge-preserving any omitted secret
458
+ * (`url`/`token`/`secret`). Returns the updated masked channel. 404 `ApiError` if unknown.
459
+ */
460
+ setChannel(id: string, input: NotifyChannelInput): Promise<NotifyChannel>;
461
+ /** `DELETE /notify/channels/{id}` — remove a channel. Idempotent. */
462
+ deleteChannel(id: string): Promise<void>;
463
+ /**
464
+ * `POST /notify/channels/{id}/test` — deliver a synthetic notification to a channel. A failed
465
+ * delivery is DATA (`{ ok: false, error }`), not a thrown error; only an unknown id rejects (404).
466
+ */
467
+ testChannel(id: string): Promise<NotifyChannelTestResult>;
468
+ /** `GET /notify/rules` — every routing rule. */
469
+ rules(): Promise<NotifyRule[]>;
470
+ /** `POST /notify/rules` — create a rule (server-assigns the id). Returns it. */
471
+ createRule(input: NotifyRuleInput): Promise<NotifyRule>;
472
+ /** `PUT /notify/rules/{id}` — replace a rule. Returns it. 404 `ApiError` if unknown. */
473
+ setRule(id: string, input: NotifyRuleInput): Promise<NotifyRule>;
474
+ /** `DELETE /notify/rules/{id}` — remove a rule. Idempotent. */
475
+ deleteRule(id: string): Promise<void>;
476
+ }
@@ -0,0 +1,13 @@
1
+ export type DaemonPlatform = "darwin" | "linux" | "win32";
2
+ export type DaemonArch = "x64" | "arm64";
3
+ export interface EnsureDaemonBinaryOptions {
4
+ version?: string;
5
+ platform?: DaemonPlatform;
6
+ arch?: DaemonArch;
7
+ cacheDir?: string;
8
+ /** Override for tests or a private GitHub Releases mirror. */
9
+ releaseBaseUrl?: string;
10
+ fetch?: typeof fetch;
11
+ }
12
+ /** Ensure the SDK-matched daemon binary is present locally and return its executable path. */
13
+ export declare function ensureDaemonBinary(opts?: EnsureDaemonBinaryOptions): Promise<string>;
@@ -0,0 +1,14 @@
1
+ export interface EmbeddedOptions {
2
+ /** Working directory the daemon runs agents in. Defaults to the process cwd. */
3
+ cwd?: string;
4
+ /** Local state file for the embedded daemon. */
5
+ statePath?: string;
6
+ /** Explicit path to the `mindwired` binary (overrides discovery). */
7
+ bin?: string;
8
+ }
9
+ export interface EmbeddedDaemon {
10
+ baseUrl: string;
11
+ token?: string;
12
+ stop(): void;
13
+ }
14
+ export declare function startEmbedded(opts?: EmbeddedOptions): Promise<EmbeddedDaemon>;
@@ -0,0 +1,46 @@
1
+ /** Base class for every error thrown by the SDK. */
2
+ export declare class MindwireError extends Error {
3
+ constructor(message: string, options?: {
4
+ cause?: unknown;
5
+ });
6
+ }
7
+ /**
8
+ * A non-2xx HTTP response from the daemon. `body` is the parsed JSON error payload when the
9
+ * daemon returned one (it uses `{ "error": "..." }`), otherwise the raw text.
10
+ */
11
+ export declare class ApiError extends MindwireError {
12
+ readonly status: number;
13
+ readonly url: string;
14
+ readonly method: string;
15
+ readonly body: unknown;
16
+ constructor(args: {
17
+ status: number;
18
+ url: string;
19
+ method: string;
20
+ body: unknown;
21
+ });
22
+ private static messageFor;
23
+ }
24
+ /**
25
+ * Thrown by {@link import("./run.js").Run.wait} when a run ends in a non-success terminal state
26
+ * (`error`/`cancelled`), or when the event stream ends before the run reaches any terminal state —
27
+ * a dropped/truncated stream. The `status` is whatever the run was in when `wait()` gave up, so a
28
+ * truncated stream surfaces as e.g. `run <id> running: event stream ended …` rather than silently
29
+ * returning a success-shaped result.
30
+ */
31
+ export declare class RunFailedError extends MindwireError {
32
+ readonly status: string;
33
+ readonly runId: string;
34
+ constructor(runId: string, status: string, detail?: string);
35
+ }
36
+ /**
37
+ * Thrown when a unary request exceeds the client's `requestTimeoutMs` deadline. A hung daemon or a
38
+ * dead tunnel would otherwise block the caller forever; this bounds every non-streaming call. SSE
39
+ * streams are long-lived and are never subject to this timeout.
40
+ */
41
+ export declare class TimeoutError extends MindwireError {
42
+ readonly method: string;
43
+ readonly path: string;
44
+ readonly timeoutMs: number;
45
+ constructor(method: string, path: string, timeoutMs: number);
46
+ }
package/dist/http.d.ts ADDED
@@ -0,0 +1,97 @@
1
+ /** A `fetch` implementation. Defaults to the global `fetch` (Node 18+, Bun, Deno, browsers). */
2
+ export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
3
+ /**
4
+ * Fetches the current bearer token for a transport whose credential rotates (e.g. the Oblien
5
+ * gateway JWT). Called per request — implementations should cache and only mint on `{force:true}`
6
+ * or expiry. `request()`/`open()` call it with `{force:true}` once on a `401`, then retry.
7
+ */
8
+ export type TokenGetter = (opts?: {
9
+ force?: boolean;
10
+ }) => Promise<string | undefined>;
11
+ /**
12
+ * Resolves the daemon's base URL, plus an optional static token, an optional dynamic `getToken`
13
+ * (for rotating credentials), and optional per-transport `headers` merged into every request
14
+ * (e.g. the Oblien proxy target). Called once, lazily, and memoized.
15
+ */
16
+ export type BaseResolver = () => Promise<{
17
+ baseUrl: string;
18
+ token?: string;
19
+ getToken?: TokenGetter;
20
+ headers?: Record<string, string>;
21
+ /**
22
+ * A transport-specific `fetch` for this base — used for both unary requests and SSE streams. A
23
+ * sandbox adapter supplies it to route every call through its runtime (e.g. Oblien's `rt.proxy`);
24
+ * when omitted the client's default fetch is used, so embedded/remote transports are unchanged.
25
+ */
26
+ fetch?: FetchLike;
27
+ }>;
28
+ export interface HttpOptions {
29
+ /** Base URL of a running daemon, e.g. `http://127.0.0.1:8790`. */
30
+ baseUrl?: string;
31
+ /** Per-sandbox bearer token. */
32
+ token?: string;
33
+ /** Lazily resolve the base URL (used by the embedded transport to spawn on first call). */
34
+ resolveBase?: BaseResolver;
35
+ /** Custom fetch. Defaults to global `fetch`. */
36
+ fetch?: FetchLike;
37
+ /** Extra headers merged into every request. */
38
+ headers?: Record<string, string>;
39
+ /**
40
+ * Deadline in milliseconds for a single unary request. Does NOT apply to SSE streams (`open()`),
41
+ * which are long-lived by design. Defaults to {@link DEFAULT_TIMEOUT_MS}; pass `0` to disable.
42
+ */
43
+ timeoutMs?: number;
44
+ }
45
+ export interface RequestInitLike {
46
+ query?: Record<string, string | number | boolean | undefined>;
47
+ body?: unknown;
48
+ signal?: AbortSignal;
49
+ headers?: Record<string, string>;
50
+ }
51
+ /**
52
+ * The low-level transport: URL/auth/JSON/error plumbing shared by every high-level method.
53
+ * The base URL is resolved lazily and memoized — a fixed `baseUrl` resolves instantly; the
54
+ * embedded transport spawns the daemon on the first request.
55
+ */
56
+ export declare class Http {
57
+ private readonly fetchImpl;
58
+ private readonly baseHeaders;
59
+ private readonly resolver;
60
+ private readonly timeoutMs;
61
+ private cached;
62
+ private pending;
63
+ constructor(opts: HttpOptions);
64
+ /**
65
+ * One fetch, with raw network failures wrapped as {@link MindwireError} so callers see a typed
66
+ * SDK error instead of a bare `TypeError: fetch failed`. An abort (the request's own signal, or
67
+ * our timeout controller) is re-thrown untouched — `fetchWithTimeout` classifies it.
68
+ */
69
+ private fetchOnce;
70
+ /**
71
+ * `fetchOnce` plus a timeout that composes with the caller's `signal`. A hand-rolled controller
72
+ * (not `AbortSignal.timeout`/`AbortSignal.any`, which need Node 20.3+/18.17+) fires after
73
+ * `timeoutMs`; whichever aborts first wins. Timeout → {@link TimeoutError}; the caller's own abort
74
+ * propagates as-is (its `reason`). `timeoutMs<=0` disables the deadline entirely.
75
+ */
76
+ private fetchWithTimeout;
77
+ /**
78
+ * The bearer token for the next request. Prefers the transport's dynamic `getToken` (which
79
+ * caches and mints on demand); falls back to the static token resolved at base time. `force`
80
+ * asks the getter to re-mint — used once on a 401 before retrying.
81
+ */
82
+ private authToken;
83
+ private base;
84
+ /**
85
+ * Resolve the base eagerly and memoize it — provisions the transport (spawns the embedded daemon,
86
+ * connects the sandbox/SSH/Docker target, …) now instead of on the first request. Idempotent: this
87
+ * awaits the *same* memoized promise the first `request()`/`open()` awaits, so the target's
88
+ * `connect()` fires exactly once. Backs {@link import("./client.js").Mindwire.ensure}.
89
+ */
90
+ ready(): Promise<void>;
91
+ private url;
92
+ private headers;
93
+ request<T>(method: string, path: string, init?: RequestInitLike): Promise<T>;
94
+ /** Open a streaming response (SSE). Caller owns the body. */
95
+ open(method: string, path: string, init?: RequestInitLike): Promise<Response>;
96
+ private toApiError;
97
+ }