@coltonkirsten/a12s 0.4.3 → 0.5.0

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.
@@ -49,13 +49,13 @@ export interface EventsOptions {
49
49
  onGap?: "yield" | "throw";
50
50
  }
51
51
  export type SharedEventsOptions = Omit<EventsOptions, "turn" | "after">;
52
- export interface PromptOptions extends Omit<EnqueueRequest, "body"> {
52
+ export interface WaitOptions {
53
53
  signal?: AbortSignal;
54
54
  /** Every event of the Turn, as it streams. */
55
55
  onEvent?: (ev: A12sEvent) => void;
56
56
  }
57
- /** What `prompt()` resolves with: the finished Turn and the Message that started it. */
58
- export interface PromptResult extends Turn {
57
+ /** What `send(...).wait()` resolves with: the finished Turn that delivered the Message, and the Message. */
58
+ export interface Reply extends Turn {
59
59
  message: EnqueueResponse;
60
60
  }
61
61
  /**
@@ -82,8 +82,13 @@ export declare class A12sClient {
82
82
  * batched with anything else pending. Works while the Computer is busy or asleep.
83
83
  */
84
84
  send(computer: string, msg: EnqueueRequest | string): Promise<EnqueueResponse>;
85
- /** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends: what `a12s prompt` does. */
86
- prompt(computer: string, body: string, opts?: PromptOptions): Promise<PromptResult>;
85
+ /** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends. */
86
+ prompt(computer: string, body: string, opts?: Omit<EnqueueRequest, "body"> & WaitOptions): Promise<Reply>;
87
+ /**
88
+ * @internal Waits for the Turn that delivers the Message and resolves when it ends. Delivery is read from the
89
+ * Message's own snapshot and the Turn is replayed from its start, so it works however late it is called.
90
+ */
91
+ reply(computer: string, message: EnqueueResponse, opts?: WaitOptions): Promise<Reply>;
87
92
  /**
88
93
  * Live events of one Computer, several, or (no argument) every Computer in the Fleet, merged. Each event carries
89
94
  * its `computer`. Reconnects after a network drop without losing or repeating events. Call `await stream.ready()`
@@ -107,11 +112,11 @@ export declare class A12sClient {
107
112
  /** Interrupts a Turn (default: the active one). The Turn ends `interrupted`; nothing is replayed. */
108
113
  stop(computer: string, turn?: string): Promise<void>;
109
114
  /**
110
- * Stops the agent computer's processes and keeps its disks, session and inbox. It stays asleep until wake(); with
111
- * `allowWake` its power policy (wakeOnMessage) may wake it instead.
115
+ * Stops the agent computer's processes and keeps its disks, session and inbox. A Message from a sender its power
116
+ * policy lists (wakeOnMessage) wakes it; with `hold` it stays asleep until wake().
112
117
  */
113
118
  sleep(computer: string, opts?: {
114
- allowWake?: boolean;
119
+ hold?: boolean;
115
120
  }): Promise<void>;
116
121
  /** Starts a sleeping agent computer; its session resumes on the next Turn. `clearInbox` discards pending Messages. */
117
122
  wake(computer: string, opts?: {
@@ -1,4 +1,4 @@
1
- // Runs in Node, Deno, Bun and browsers: no node: imports here (`npm run check:browser` enforces it).
1
+ // Runs in Node, Deno, Bun and browsers: no node: imports here (`npm run dashboard` bundles it for the browser).
2
2
  import { readSSE } from "./sse.js";
3
3
  /** A refused call: the HTTP status and the Controller's error, including its fix line. */
4
4
  export class A12sError extends Error {
@@ -94,10 +94,17 @@ export class A12sClient {
94
94
  m.idempotencyKey ??= globalThis.crypto.randomUUID();
95
95
  return this.request("POST", `/v1/agent-computers/${enc(computer)}/inbox`, m);
96
96
  }
97
- /** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends: what `a12s prompt` does. */
97
+ /** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends. */
98
98
  async prompt(computer, body, opts = {}) {
99
99
  const { signal, onEvent, ...msg } = opts;
100
- const message = await this.send(computer, { ...msg, body });
100
+ return this.reply(computer, await this.send(computer, { ...msg, body }), { signal, onEvent });
101
+ }
102
+ /**
103
+ * @internal Waits for the Turn that delivers the Message and resolves when it ends. Delivery is read from the
104
+ * Message's own snapshot and the Turn is replayed from its start, so it works however late it is called.
105
+ */
106
+ async reply(computer, message, opts = {}) {
107
+ const { signal, onEvent } = opts;
101
108
  const turn = await this.deliveredIn(computer, message.id, signal);
102
109
  const builder = new TurnBuilder();
103
110
  // A gap mid-Turn would silently drop text: fail loudly instead.
@@ -170,13 +177,13 @@ export class A12sClient {
170
177
  await this.request("POST", `/v1/agent-computers/${enc(computer)}/turns/${enc(id)}/stop`, {});
171
178
  }
172
179
  /**
173
- * Stops the agent computer's processes and keeps its disks, session and inbox. It stays asleep until wake(); with
174
- * `allowWake` its power policy (wakeOnMessage) may wake it instead.
180
+ * Stops the agent computer's processes and keeps its disks, session and inbox. A Message from a sender its power
181
+ * policy lists (wakeOnMessage) wakes it; with `hold` it stays asleep until wake().
175
182
  */
176
183
  async sleep(computer, opts = {}) {
177
184
  await this.request("POST", `/v1/agent-computers/${enc(computer)}/power`, {
178
185
  state: "asleep",
179
- ...(opts.allowWake ? { allowWake: true } : {}),
186
+ ...(opts.hold ? { hold: true } : {}),
180
187
  });
181
188
  }
182
189
  /** Starts a sleeping agent computer; its session resumes on the next Turn. `clearInbox` discards pending Messages. */
@@ -1,4 +1,4 @@
1
- import { A12sClient, type A12sClientOptions, type EventsOptions, TurnStream, type EventStream, type PromptOptions, type PromptResult, type SharedEventsOptions } from "./client.js";
1
+ import { A12sClient, type A12sClientOptions, type EventsOptions, TurnStream, type EventStream, type Reply, type SharedEventsOptions, type WaitOptions } from "./client.js";
2
2
  import type { AgentComputerData, AgentComputerSpec, Change, ComputerStatus, CredentialInfo, EnqueueRequest, EnqueueResponse, FleetSpec, InboxListOptions, InboxListResponse, MergePatch, Message, ServiceStatus, StatusResponse, Token, TokenAction, Turn } from "./types.js";
3
3
  /** The Fleet as written, and its version for a conditional write. */
4
4
  export interface FleetState {
@@ -18,7 +18,7 @@ export interface CreateOptions {
18
18
  * ```ts
19
19
  * const fleet = await Fleet.connect();
20
20
  * const hello = await fleet.agentComputers.create("hello", { instructions: "Be helpful" });
21
- * await hello.prompt("hi");
21
+ * const reply = await hello.send("hi").wait();
22
22
  * await hello.remove();
23
23
  * ```
24
24
  */
@@ -58,7 +58,11 @@ export declare class Fleet {
58
58
  export declare class AgentComputers {
59
59
  private readonly client;
60
60
  constructor(client: A12sClient);
61
- /** Adds an agent computer to the Fleet and starts it. Fails with code `exists` if the name is taken. */
61
+ /**
62
+ * Adds an agent computer to the Fleet and starts it. One that names a credential with no value yet resolves at once,
63
+ * waiting in Provisioning (`status.waiting`, and a console warning); it starts when the value is set. Fails with
64
+ * code `exists` if the name is taken.
65
+ */
62
66
  create(name: string, spec?: AgentComputerSpec & CreateOptions): Promise<AgentComputer>;
63
67
  /** Throws {@link NotFound} if there is none. */
64
68
  get(name: string): Promise<AgentComputer>;
@@ -80,10 +84,11 @@ export declare class AgentComputer {
80
84
  update(changes: MergePatch<AgentComputerSpec>): Promise<this>;
81
85
  /** Removes it from the Fleet and stops it; its drives are kept. */
82
86
  remove(): Promise<void>;
83
- /** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends. */
84
- prompt(text: string, opts?: PromptOptions): Promise<PromptResult>;
85
- /** Puts a Message in the inbox and returns at once; it is delivered at the next idle moment. */
86
- send(text: string, opts?: Omit<EnqueueRequest, "body">): Promise<EnqueueResponse>;
87
+ /**
88
+ * Puts a Message in the inbox; it is delivered at the next idle moment. Await it for the queued Message, or call
89
+ * `wait()` on it (or on the awaited Message, at any time later) for the Turn that delivers it.
90
+ */
91
+ send(text: string, opts?: Omit<EnqueueRequest, "body">): Sending;
87
92
  /** Its events, live, from the Fleet's shared connection; `turn` replays one Turn from its start instead. */
88
93
  events(opts?: EventsOptions): EventStream;
89
94
  /** Its last `limit` finished Turns, newest first. */
@@ -95,9 +100,9 @@ export declare class AgentComputer {
95
100
  limit?: undefined;
96
101
  }): TurnStream;
97
102
  private pastTurns;
98
- /** Stops its processes and keeps its disks, session and inbox until wake(); `allowWake` lets wakeOnMessage wake it. */
103
+ /** Stops its processes and keeps its disks, session and inbox; a sender in wakeOnMessage wakes it, unless `hold`. */
99
104
  sleep(opts?: {
100
- allowWake?: boolean;
105
+ hold?: boolean;
101
106
  }): Promise<void>;
102
107
  /** Wakes it; `clearInbox` discards the pending Messages first. */
103
108
  wake(opts?: {
@@ -116,6 +121,25 @@ export declare class AgentComputer {
116
121
  /** Withdraws a pending Message; a 409 A12sError names its state if it is too late. */
117
122
  withdraw(id: string): Promise<Message>;
118
123
  }
124
+ /** A queued Message. */
125
+ export interface SentMessage extends EnqueueResponse {
126
+ /**
127
+ * Waits for the Turn that delivers this Message and resolves with it once it ends, also if it already has. A failed
128
+ * Turn resolves with status "failed"; a Message that is withdrawn, expired, replaced or held throws A12sError.
129
+ */
130
+ wait(opts?: WaitOptions): Promise<Reply>;
131
+ }
132
+ /** What `send()` returns: a promise of the {@link SentMessage}, with its `wait()` one call away. */
133
+ export declare class Sending implements Promise<SentMessage> {
134
+ private readonly sent;
135
+ readonly [Symbol.toStringTag] = "Sending";
136
+ constructor(sent: Promise<SentMessage>);
137
+ then<A = SentMessage, B = never>(onfulfilled?: ((m: SentMessage) => A | PromiseLike<A>) | null, onrejected?: ((reason: unknown) => B | PromiseLike<B>) | null): Promise<A | B>;
138
+ catch<B = never>(onrejected?: ((reason: unknown) => B | PromiseLike<B>) | null): Promise<SentMessage | B>;
139
+ finally(onfinally?: (() => void) | null): Promise<SentMessage>;
140
+ /** See {@link SentMessage.wait}. */
141
+ wait(opts?: WaitOptions): Promise<Reply>;
142
+ }
119
143
  export declare class Services {
120
144
  private readonly client;
121
145
  constructor(client: A12sClient);
package/dist/src/fleet.js CHANGED
@@ -1,4 +1,4 @@
1
- // Runs in Node, Deno, Bun and browsers: no node: imports here (`npm run check:browser` enforces it).
1
+ // Runs in Node, Deno, Bun and browsers: no node: imports here (`npm run dashboard` bundles it for the browser).
2
2
  import { A12sClient, A12sError, TurnStream, } from "./client.js";
3
3
  const enc = encodeURIComponent;
4
4
  const maxReplaceTries = 5;
@@ -9,7 +9,7 @@ const maxReplaceTries = 5;
9
9
  * ```ts
10
10
  * const fleet = await Fleet.connect();
11
11
  * const hello = await fleet.agentComputers.create("hello", { instructions: "Be helpful" });
12
- * await hello.prompt("hi");
12
+ * const reply = await hello.send("hi").wait();
13
13
  * await hello.remove();
14
14
  * ```
15
15
  */
@@ -98,7 +98,11 @@ export class AgentComputers {
98
98
  constructor(client) {
99
99
  this.client = client;
100
100
  }
101
- /** Adds an agent computer to the Fleet and starts it. Fails with code `exists` if the name is taken. */
101
+ /**
102
+ * Adds an agent computer to the Fleet and starts it. One that names a credential with no value yet resolves at once,
103
+ * waiting in Provisioning (`status.waiting`, and a console warning); it starts when the value is set. Fails with
104
+ * code `exists` if the name is taken.
105
+ */
102
106
  async create(name, spec = {}) {
103
107
  const { replace, ...body } = spec;
104
108
  const r = await this.client.request("PUT", `/v1/agent-computers/${enc(name)}`, body, {
@@ -151,13 +155,15 @@ export class AgentComputer {
151
155
  async remove() {
152
156
  await this.client.request("DELETE", this.path);
153
157
  }
154
- /** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends. */
155
- prompt(text, opts = {}) {
156
- return this.client.prompt(this.name, text, opts);
157
- }
158
- /** Puts a Message in the inbox and returns at once; it is delivered at the next idle moment. */
158
+ /**
159
+ * Puts a Message in the inbox; it is delivered at the next idle moment. Await it for the queued Message, or call
160
+ * `wait()` on it (or on the awaited Message, at any time later) for the Turn that delivers it.
161
+ */
159
162
  send(text, opts = {}) {
160
- return this.client.send(this.name, { ...opts, body: text });
163
+ return new Sending(this.client.send(this.name, { ...opts, body: text }).then((message) => ({
164
+ ...message,
165
+ wait: (w) => this.client.reply(this.name, message, w),
166
+ })));
161
167
  }
162
168
  /** Its events, live, from the Fleet's shared connection; `turn` replays one Turn from its start instead. */
163
169
  events(opts = {}) {
@@ -179,7 +185,7 @@ export class AgentComputer {
179
185
  tools: t.tools.map((tc) => ({ ...tc, isError: tc.isError ?? false })),
180
186
  }));
181
187
  }
182
- /** Stops its processes and keeps its disks, session and inbox until wake(); `allowWake` lets wakeOnMessage wake it. */
188
+ /** Stops its processes and keeps its disks, session and inbox; a sender in wakeOnMessage wakes it, unless `hold`. */
183
189
  sleep(opts = {}) {
184
190
  return this.client.sleep(this.name, opts);
185
191
  }
@@ -210,6 +216,27 @@ export class AgentComputer {
210
216
  return this.client.withdraw(this.name, id);
211
217
  }
212
218
  }
219
+ /** What `send()` returns: a promise of the {@link SentMessage}, with its `wait()` one call away. */
220
+ export class Sending {
221
+ sent;
222
+ [Symbol.toStringTag] = "Sending";
223
+ constructor(sent) {
224
+ this.sent = sent;
225
+ }
226
+ then(onfulfilled, onrejected) {
227
+ return this.sent.then(onfulfilled, onrejected);
228
+ }
229
+ catch(onrejected) {
230
+ return this.sent.catch(onrejected);
231
+ }
232
+ finally(onfinally) {
233
+ return this.sent.finally(onfinally);
234
+ }
235
+ /** See {@link SentMessage.wait}. */
236
+ async wait(opts) {
237
+ return (await this.sent).wait(opts);
238
+ }
239
+ }
213
240
  export class Services {
214
241
  client;
215
242
  constructor(client) {
@@ -317,8 +344,8 @@ export class Credentials {
317
344
  await this.client.request("DELETE", `/v1/credentials/${enc(name)}`);
318
345
  }
319
346
  }
320
- /** A write's warnings (a grant to an agent computer that doesn't exist yet, a missing harness credential) go to the
321
- * console instead of being dropped. */
347
+ /** A write's warnings (a grant to an agent computer that doesn't exist yet, a missing harness credential, a credential
348
+ * with no value yet) go to the console instead of being dropped. */
322
349
  function warnWrites(r) {
323
350
  for (const w of r?.warnings ?? [])
324
351
  console.warn(`a12s: ${w}`);
@@ -3,6 +3,12 @@ export interface Failure {
3
3
  reason: string;
4
4
  retryable: boolean;
5
5
  }
6
+ /** Credentials an agent computer or Service waits for, in Provisioning; it starts by itself once they are set. */
7
+ export interface Waiting {
8
+ credentials: string[];
9
+ /** The commands that set them, e.g. "a12s creds set dbname". */
10
+ fix: string;
11
+ }
6
12
  export interface Harness {
7
13
  kind: string;
8
14
  version: string;
@@ -204,6 +210,7 @@ export interface ComputerStatus {
204
210
  name: string;
205
211
  phase: Phase;
206
212
  error?: Failure;
213
+ waiting?: Waiting;
207
214
  resumePending?: boolean;
208
215
  session?: string;
209
216
  activeTurn?: string;
@@ -240,6 +247,7 @@ export interface ServiceStatus {
240
247
  name: string;
241
248
  phase: Phase;
242
249
  error?: Failure;
250
+ waiting?: Waiting;
243
251
  image: string;
244
252
  pin?: string;
245
253
  generation: number;
@@ -405,6 +413,7 @@ export interface FleetChangedData {
405
413
  export interface PhaseData {
406
414
  phase: Phase;
407
415
  error?: Failure;
416
+ waiting?: Waiting;
408
417
  }
409
418
  /** A Service's phase, health or restart count changed. */
410
419
  export interface ServicePhaseData {
@@ -414,6 +423,7 @@ export interface ServicePhaseData {
414
423
  health?: string;
415
424
  restarts: number;
416
425
  error?: Failure;
426
+ waiting?: Waiting;
417
427
  }
418
428
  export interface ToolStartedData {
419
429
  id: string;
@@ -502,7 +512,7 @@ export interface ToolCall {
502
512
  output?: unknown;
503
513
  isError: boolean;
504
514
  }
505
- /** A finished Turn: what `turns()` yields and `prompt()` returns. */
515
+ /** A finished Turn: what `turns()` yields and `wait()` returns. */
506
516
  export interface Turn {
507
517
  agentComputer: string;
508
518
  /** `agentComputer` under its old name, for one release. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coltonkirsten/a12s",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "TypeScript client for a12s: send work to agent Computers, stream their events and Turns, in Node or the browser",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,12 +16,10 @@
16
16
  "types": "./dist/src/index.d.ts",
17
17
  "default": "./dist/src/index.js"
18
18
  }
19
- },
20
- "./browser.js": "./dist/a12s.js"
19
+ }
21
20
  },
22
21
  "files": [
23
- "dist/src",
24
- "dist/a12s.js"
22
+ "dist/src"
25
23
  ],
26
24
  "engines": {
27
25
  "node": ">=20"
@@ -37,10 +35,10 @@
37
35
  "provenance": true
38
36
  },
39
37
  "scripts": {
40
- "build": "rm -rf dist && tsc -p tsconfig.json && npm run check:browser",
38
+ "build": "rm -rf dist && tsc -p tsconfig.json && npm run dashboard",
41
39
  "lint": "tsc -p tsconfig.json --noEmit",
42
40
  "test": "npm run build && node --test 'dist/test/*.test.js'",
43
- "check:browser": "esbuild src/index.ts --bundle --platform=browser --format=esm --log-level=warning --outfile=dist/a12s.js"
41
+ "dashboard": "esbuild ../../internal/gateway/dashboard/main.js --bundle --platform=browser --format=esm --alias:@coltonkirsten/a12s=./src/index.ts --log-level=warning --outfile=dist/dashboard.js"
44
42
  },
45
43
  "devDependencies": {
46
44
  "@types/node": "^24.19.1",