@zoowork-ai/sdk 0.4.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.
- package/CHANGELOG.md +366 -0
- package/LICENSE +21 -0
- package/README.md +198 -0
- package/dist/client.d.ts +1424 -0
- package/dist/client.js +622 -0
- package/dist/events.d.ts +88 -0
- package/dist/events.js +151 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/sse.d.ts +17 -0
- package/dist/sse.js +69 -0
- package/package.json +54 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,1424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZooWork Managed Agents SDK — core client. Developer Preview.
|
|
3
|
+
*
|
|
4
|
+
* Authenticate with your organization API key (`zct_…`). It carries full tenant
|
|
5
|
+
* authority, so it is SERVER-SIDE ONLY: never ship it in a browser or mobile bundle.
|
|
6
|
+
*
|
|
7
|
+
* IDs are opaque strings and unknown response fields must be ignored — both are
|
|
8
|
+
* forward-compatibility rules, not suggestions. Errors surface an `error.type`; match on
|
|
9
|
+
* that, never on the message text.
|
|
10
|
+
*/
|
|
11
|
+
import { type SessionEvent } from './events.js';
|
|
12
|
+
/**
|
|
13
|
+
* The production API base URL — the default. You should not need to set this:
|
|
14
|
+
* `ZOOWORK_BASE_URL` overrides it, and so does the `baseUrl` option, only when you
|
|
15
|
+
* need to point at a different deployment.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_BASE_URL = "https://clawapi.ecap.gsmo.ai/service/v1";
|
|
18
|
+
export type ZooworkAuth = {
|
|
19
|
+
serviceToken: string;
|
|
20
|
+
} | {
|
|
21
|
+
apiKey: string;
|
|
22
|
+
};
|
|
23
|
+
export interface ZooworkConfig {
|
|
24
|
+
/**
|
|
25
|
+
* Your API key (`zct_...`). Defaults to `ZOOWORK_API_KEY`.
|
|
26
|
+
*
|
|
27
|
+
* Server-side only: it authenticates as your whole organization, not as one end user.
|
|
28
|
+
*/
|
|
29
|
+
apiKey?: string;
|
|
30
|
+
/**
|
|
31
|
+
* API base including the version prefix. Defaults to `ZOOWORK_BASE_URL`, then to
|
|
32
|
+
* {@link DEFAULT_BASE_URL} (production). Set it only to point at a deployment other
|
|
33
|
+
* than the production one.
|
|
34
|
+
*/
|
|
35
|
+
baseUrl?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Advanced. `{ apiKey }` is equivalent to the top-level `apiKey` field. `{ serviceToken }`
|
|
38
|
+
* selects a privileged deployment-internal credential and is not available to API-key
|
|
39
|
+
* callers.
|
|
40
|
+
*/
|
|
41
|
+
auth?: ZooworkAuth;
|
|
42
|
+
/** Injected fetch for edge runtimes/tests; defaults to globalThis.fetch. */
|
|
43
|
+
fetch?: (input: string, init?: RequestInit) => Promise<Response>;
|
|
44
|
+
}
|
|
45
|
+
export declare class ZooworkError extends Error {
|
|
46
|
+
status: number;
|
|
47
|
+
/**
|
|
48
|
+
* The machine-readable error code. Match on this, never on the message.
|
|
49
|
+
*
|
|
50
|
+
* TWO VOCABULARIES, because there are two error envelopes — staging-verified 2026-08-07. The
|
|
51
|
+
* sessions/schedules/environments family answers `{ error: { type, message } }` with a bare code
|
|
52
|
+
* (`agent_not_running`, `session_archived`, `environment_not_ready`); the agents family answers
|
|
53
|
+
* `{ code, detail }` with a DOTTED one (`service_api.not_found`). Both land here, so a caller
|
|
54
|
+
* always gets something — but do not assume one spelling covers both, and prefer `status` when
|
|
55
|
+
* you only need the class of failure.
|
|
56
|
+
*/
|
|
57
|
+
type?: string;
|
|
58
|
+
constructor(status: number, message: string, type?: string);
|
|
59
|
+
}
|
|
60
|
+
export interface Ownership {
|
|
61
|
+
owner_uid: string;
|
|
62
|
+
org_id: string;
|
|
63
|
+
}
|
|
64
|
+
export interface ModelInfo {
|
|
65
|
+
model: string;
|
|
66
|
+
display_name?: string;
|
|
67
|
+
family?: string;
|
|
68
|
+
api?: string;
|
|
69
|
+
[k: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One remote MCP server, declared as `resource.mcp[]` on create or update.
|
|
73
|
+
*
|
|
74
|
+
* Staging-verified 2026-08-07, end to end, for UNAUTHENTICATED public servers: the tools appear
|
|
75
|
+
* in the model's manifest as `mcp__<server>__<tool>` and really execute. Two traps:
|
|
76
|
+
*
|
|
77
|
+
* - `name` must not contain an underscore. The tool-name grammar is `mcp__<server>__<tool>`,
|
|
78
|
+
* so an underscore in the server name makes the split ambiguous and the server is rejected.
|
|
79
|
+
* - `credential` is a dead end for API-key callers. The slug is accepted and stored on the
|
|
80
|
+
* agent, but the endpoint that would hold the secret it points at
|
|
81
|
+
* (`PUT /agents/{id}/credentials/{app}`) is 404 through the gateway BY DESIGN. There is no
|
|
82
|
+
* other way to store it, so an authenticated MCP server cannot be made to work today.
|
|
83
|
+
* Declare public servers only.
|
|
84
|
+
*
|
|
85
|
+
* Phase 1 is remote HTTP only: no stdio, no OAuth. A server that fails its catalog probe does
|
|
86
|
+
* not fail the run — it pins an empty catalog and emits `agent.error` with
|
|
87
|
+
* `kind: 'mcp_connection_failed'`.
|
|
88
|
+
*/
|
|
89
|
+
export interface McpServerDeclaration {
|
|
90
|
+
/** Server slug. Appears in every tool name as `mcp__<name>__<tool>`. No underscores. */
|
|
91
|
+
name: string;
|
|
92
|
+
/** Absolute URL of the MCP endpoint. Loopback, private ranges, cloud metadata and redirects are refused. */
|
|
93
|
+
url: string;
|
|
94
|
+
/** Defaults to `streamable-http`. */
|
|
95
|
+
transport?: 'streamable-http' | 'sse';
|
|
96
|
+
/** Credential slug for a static bearer. Declarable, but unusable through the gateway — see above. */
|
|
97
|
+
credential?: string;
|
|
98
|
+
/** Expose only these tool names from this server. Omit for all of them. */
|
|
99
|
+
toolFilter?: string[];
|
|
100
|
+
[k: string]: unknown;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The system-prompt pin (`resource.system_prompt`).
|
|
104
|
+
*
|
|
105
|
+
* Pin an immutable PLATFORM template version, or override the whole template with a CUSTOM one
|
|
106
|
+
* (`base_version` records the platform version it derives from; the template must fill all 13
|
|
107
|
+
* functional slots exactly once and stay under 64 KiB UTF-8). Create pins the platform version
|
|
108
|
+
* active at that moment on its own — a plain create answered `{source:'platform',version:1}`,
|
|
109
|
+
* staging-verified 2026-08-14 — and the pin NEVER follows a later activation on its own:
|
|
110
|
+
* ordinary PUTs, skill changes and rerenders keep it. Moving it is an explicit call,
|
|
111
|
+
* {@link ZooworkClient.upgradeSystemPrompt}.
|
|
112
|
+
*
|
|
113
|
+
* On PUT this section is REPLACE-ON-WRITE, like `tool_policy` — not merged.
|
|
114
|
+
*/
|
|
115
|
+
export type SystemPromptDeclaration = {
|
|
116
|
+
source: 'platform';
|
|
117
|
+
version: number;
|
|
118
|
+
} | {
|
|
119
|
+
source: 'custom';
|
|
120
|
+
base_version: number;
|
|
121
|
+
template: string;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* How an unattended cron fire is judged (`command`: a sandbox command whose exit 0 means
|
|
125
|
+
* satisfied — ≤8 KiB, `timeoutSec` 1–600 (default 120); `rubric`: an LLM grader in a fresh
|
|
126
|
+
* context — `rubric.type` must be `'text'`, ≤32 KiB). `subagent` is a reserved slot the API
|
|
127
|
+
* still rejects. Validation is write-strict at EVERY level: an unknown key anywhere in the
|
|
128
|
+
* outcome object is a 400 naming the field, so a typo cannot silently drop a limit.
|
|
129
|
+
*/
|
|
130
|
+
export type OutcomeEvaluator = {
|
|
131
|
+
type: 'command';
|
|
132
|
+
command: string;
|
|
133
|
+
timeoutSec?: number;
|
|
134
|
+
cwd?: string;
|
|
135
|
+
skipIfUnchanged?: boolean;
|
|
136
|
+
} | {
|
|
137
|
+
type: 'rubric';
|
|
138
|
+
rubric: {
|
|
139
|
+
type: 'text';
|
|
140
|
+
text: string;
|
|
141
|
+
};
|
|
142
|
+
model?: string;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* What "done" looks like for an unattended cron fire — evaluated INSIDE the run.
|
|
146
|
+
*
|
|
147
|
+
* Attach it to a cron schedule (`payload.outcome`) or as an agent-level default (top-level
|
|
148
|
+
* `outcome` on {@link AgentResource}); the job-level value overrides the default, and an
|
|
149
|
+
* explicit `null` on the job disables the default for that job. It governs cron fires ONLY —
|
|
150
|
+
* heartbeats and interactive sessions never evaluate. The runtime loops
|
|
151
|
+
* evaluate → revise → finalize inside the same run, up to `maxIterations` (1–5, default 3),
|
|
152
|
+
* and under the default `publish: 'after_satisfied'` nothing that failed evaluation is
|
|
153
|
+
* announced or published. `description` is what the evaluator judges against, ≤4096 chars.
|
|
154
|
+
*
|
|
155
|
+
* Staging-verified 2026-08-14: a schedule carrying this was accepted (201) and read back
|
|
156
|
+
* verbatim under `payload.outcome`, and an agent-level PUT landed in `declared.outcome`.
|
|
157
|
+
*/
|
|
158
|
+
export interface OutcomeConfig {
|
|
159
|
+
description: string;
|
|
160
|
+
evaluator: OutcomeEvaluator;
|
|
161
|
+
/** 1–5; the engine defaults an omitted value to 3 at run time (nothing is stored for you). */
|
|
162
|
+
maxIterations?: number;
|
|
163
|
+
/** Defaults to `after_satisfied` at run time: an unsatisfied result stays unpublished. */
|
|
164
|
+
publish?: 'after_satisfied' | 'always' | 'never';
|
|
165
|
+
}
|
|
166
|
+
export interface AgentResource {
|
|
167
|
+
name: string;
|
|
168
|
+
/** `max_tokens` caps output tokens per model request. Omit to use the platform default. */
|
|
169
|
+
model?: {
|
|
170
|
+
primary: string;
|
|
171
|
+
input?: string[];
|
|
172
|
+
max_tokens?: number;
|
|
173
|
+
};
|
|
174
|
+
persona?: {
|
|
175
|
+
docs: {
|
|
176
|
+
name: string;
|
|
177
|
+
content: string;
|
|
178
|
+
seed_policy?: string;
|
|
179
|
+
}[];
|
|
180
|
+
};
|
|
181
|
+
skills?: {
|
|
182
|
+
skill_id: string;
|
|
183
|
+
version?: number | 'latest';
|
|
184
|
+
}[];
|
|
185
|
+
labels?: Record<string, string>;
|
|
186
|
+
tool_policy?: Record<string, unknown>;
|
|
187
|
+
/** Remote MCP servers. Only unauthenticated ones work today — see {@link McpServerDeclaration}. */
|
|
188
|
+
mcp?: McpServerDeclaration[];
|
|
189
|
+
/**
|
|
190
|
+
* System-prompt pin. Omitted on create means "the platform version active right now", pinned
|
|
191
|
+
* from then on. REPLACE-ON-WRITE on PUT, like `tool_policy` — see {@link SystemPromptDeclaration}.
|
|
192
|
+
*/
|
|
193
|
+
system_prompt?: SystemPromptDeclaration;
|
|
194
|
+
/**
|
|
195
|
+
* Agent-level default outcome for unattended cron fires. A schedule's `payload.outcome`
|
|
196
|
+
* overrides it per job; an explicit `null` there opts that job out. See {@link OutcomeConfig}.
|
|
197
|
+
*/
|
|
198
|
+
outcome?: OutcomeConfig | null;
|
|
199
|
+
sandbox?: {
|
|
200
|
+
scope: 'agent' | 'session';
|
|
201
|
+
};
|
|
202
|
+
environment_id?: string;
|
|
203
|
+
environment_version?: number;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Agent lifecycle state. Two fields, two very different meanings — staging-verified
|
|
207
|
+
* 2026-08-06:
|
|
208
|
+
*
|
|
209
|
+
* - `desired_state` is the one that gates the API. `running` is the precondition for
|
|
210
|
+
* createSession/postEvents; anything else is `409 agent_not_running`.
|
|
211
|
+
* - `actual_state` is CHANNEL health (Mattermost/Feishu route connectivity), not API
|
|
212
|
+
* readiness. An API-only agent has no channels to connect, so it sits at
|
|
213
|
+
* `activating` forever and `active` is unreachable. Never gate on it, and never
|
|
214
|
+
* poll for `running` — that is not one of its values.
|
|
215
|
+
*/
|
|
216
|
+
export interface AgentStatus {
|
|
217
|
+
desired_state?: 'running' | 'stopped' | 'deleted' | string;
|
|
218
|
+
actual_state?: 'activating' | 'active' | 'degraded' | 'error' | 'stopped' | 'deleting' | string;
|
|
219
|
+
/** Authoritative config version on the READ path (GET/PUT). */
|
|
220
|
+
config_version?: number;
|
|
221
|
+
render_state?: string;
|
|
222
|
+
status_message?: string | null;
|
|
223
|
+
channels?: {
|
|
224
|
+
expected?: number;
|
|
225
|
+
connected?: number;
|
|
226
|
+
degraded_since?: string | null;
|
|
227
|
+
};
|
|
228
|
+
[k: string]: unknown;
|
|
229
|
+
}
|
|
230
|
+
export interface AgentSkill {
|
|
231
|
+
skill_id?: string;
|
|
232
|
+
name?: string;
|
|
233
|
+
version?: number | string;
|
|
234
|
+
scope?: 'global' | 'org' | 'personal' | 'pack' | string;
|
|
235
|
+
eligible?: boolean;
|
|
236
|
+
files?: {
|
|
237
|
+
path: string;
|
|
238
|
+
size?: number;
|
|
239
|
+
sha256?: string;
|
|
240
|
+
}[];
|
|
241
|
+
[k: string]: unknown;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* One platform account bound to an agent, as the channel service reports it.
|
|
245
|
+
* `dm_policy` / `group_policy` are the reachability policies (`'open'` is the
|
|
246
|
+
* server default); `health`/`status`/`status_code` are the connection's state
|
|
247
|
+
* as the platform adapter sees it.
|
|
248
|
+
*/
|
|
249
|
+
export interface AgentChannel {
|
|
250
|
+
platform: string;
|
|
251
|
+
account: string;
|
|
252
|
+
display_name?: string | null;
|
|
253
|
+
dm_policy?: string;
|
|
254
|
+
group_policy?: string;
|
|
255
|
+
enabled?: boolean;
|
|
256
|
+
health?: string;
|
|
257
|
+
status?: string;
|
|
258
|
+
status_code?: string | null;
|
|
259
|
+
[k: string]: unknown;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The chat platforms you can bind, staging-verified 2026-08-25.
|
|
263
|
+
*
|
|
264
|
+
* Only `'feishu'` has a server-driven QR flow here, and the two reasons the others lack one
|
|
265
|
+
* are different. Slack structurally cannot have one — a Slack app is created by a person, and
|
|
266
|
+
* its tokens only ever exist in that person's browser — so `addChannel` with `botToken` +
|
|
267
|
+
* `appToken` is its permanent path. WeCom's flow exists in the product but is not exposed on
|
|
268
|
+
* this API yet, so today it also binds through `addChannel`.
|
|
269
|
+
*
|
|
270
|
+
* WeChat (`'weixin'`/`'wechat'`) is absent because it cannot be bound here at all: it answers
|
|
271
|
+
* `400 channel.weixin_setup_required`, naming a QR flow this API does not expose. Any other
|
|
272
|
+
* name answers `400 channel.invalid_request`.
|
|
273
|
+
*/
|
|
274
|
+
export type ChannelPlatform = 'feishu' | 'slack' | 'wecom';
|
|
275
|
+
export interface AddChannelInput {
|
|
276
|
+
/** See {@link ChannelPlatform}. Typed loosely so a newly supported platform needs no SDK release. */
|
|
277
|
+
platform: ChannelPlatform | (string & {});
|
|
278
|
+
/** Server default: `'default'`. */
|
|
279
|
+
account?: string;
|
|
280
|
+
display_name?: string;
|
|
281
|
+
/** Server default: `'open'`. `'pairing'` is rejected with `400 channel.pairing_unsupported`. */
|
|
282
|
+
dm_policy?: string;
|
|
283
|
+
/** Server default: `'open'`. */
|
|
284
|
+
group_policy?: string;
|
|
285
|
+
/** Write-once at create; updates cannot edit it. */
|
|
286
|
+
allow_from?: string[];
|
|
287
|
+
/**
|
|
288
|
+
* Platform credentials for the direct (non-QR) path. Keys are platform-specific and
|
|
289
|
+
* **camelCase**; anything else is stored and ignored:
|
|
290
|
+
*
|
|
291
|
+
* - `slack` — `{ botToken: 'xoxb-…', appToken: 'xapp-…' }`, both required (socket mode
|
|
292
|
+
* needs the app-level token as well as the bot token)
|
|
293
|
+
* - `wecom` — `{ botId, secret }`, both required
|
|
294
|
+
* - `feishu` — `{ appId, appSecret, domain }`, only when skipping the QR flow
|
|
295
|
+
*/
|
|
296
|
+
config?: Record<string, unknown>;
|
|
297
|
+
}
|
|
298
|
+
export interface UpdateChannelInput {
|
|
299
|
+
/** Which platform account to touch. Server default: `'default'`. */
|
|
300
|
+
account?: string;
|
|
301
|
+
dm_policy?: string;
|
|
302
|
+
group_policy?: string;
|
|
303
|
+
enabled?: boolean;
|
|
304
|
+
}
|
|
305
|
+
export interface FeishuSetupInput {
|
|
306
|
+
/** `'feishu'` (default) or `'lark'` — the international brand of the same platform. */
|
|
307
|
+
brand?: 'feishu' | 'lark';
|
|
308
|
+
/** Server default: `'default'`. */
|
|
309
|
+
account?: string;
|
|
310
|
+
/** Server default: `'open'`. */
|
|
311
|
+
dm_policy?: string;
|
|
312
|
+
/** Server default: `'open'`. */
|
|
313
|
+
group_policy?: string;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* A running Feishu QR registration. Render `verification_uri_complete` to the person
|
|
317
|
+
* doing the binding (typically as a QR code), then poll with `pollFeishuSetup` /
|
|
318
|
+
* `waitForFeishuSetup` until it leaves `pending`. The session expires after
|
|
319
|
+
* `expires_in` seconds.
|
|
320
|
+
*/
|
|
321
|
+
export interface FeishuSetupSession {
|
|
322
|
+
session_id: string;
|
|
323
|
+
verification_uri_complete: string;
|
|
324
|
+
expires_in: number;
|
|
325
|
+
/** Suggested seconds between polls; the server may omit it. */
|
|
326
|
+
poll_interval?: number | null;
|
|
327
|
+
[k: string]: unknown;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* One poll of a Feishu setup session. The gateway's own vocabulary for `status` is
|
|
331
|
+
* `pending | success | expired | denied | error`; treat anything unknown as
|
|
332
|
+
* still-in-flight rather than throwing.
|
|
333
|
+
*/
|
|
334
|
+
export interface FeishuPollResult {
|
|
335
|
+
status: string;
|
|
336
|
+
channel_configured?: boolean;
|
|
337
|
+
message?: string | null;
|
|
338
|
+
poll_interval?: number | null;
|
|
339
|
+
[k: string]: unknown;
|
|
340
|
+
}
|
|
341
|
+
export interface AgentRecord {
|
|
342
|
+
agent_id: string;
|
|
343
|
+
computer_id?: string;
|
|
344
|
+
/**
|
|
345
|
+
* CREATE ONLY. `POST /agents` answers with a flat create receipt carrying this
|
|
346
|
+
* field; `GET`/`PUT` answer with the projection instead, where the version lives at
|
|
347
|
+
* `status.config_version`. Read it as `agent.status?.config_version ?? agent.config_version`.
|
|
348
|
+
*/
|
|
349
|
+
config_version?: number;
|
|
350
|
+
/** The agent's configuration (name/model/persona/labels/mcp/...). Absent from the create receipt. */
|
|
351
|
+
declared?: Record<string, unknown>;
|
|
352
|
+
resolved_skills?: {
|
|
353
|
+
skill_id: string;
|
|
354
|
+
name?: string;
|
|
355
|
+
version?: number | string;
|
|
356
|
+
eligible?: boolean;
|
|
357
|
+
}[];
|
|
358
|
+
/**
|
|
359
|
+
* The Environment version this agent is actually pinned to. Audit-grade, but the
|
|
360
|
+
* `environment_id` in here is NOT queryable: `getEnvironment()` on the platform default
|
|
361
|
+
* answers 404, because the gateway forces an org selector and the default belongs to no org.
|
|
362
|
+
* That is a selector mismatch, not a permission problem.
|
|
363
|
+
*/
|
|
364
|
+
resolved_environment?: {
|
|
365
|
+
environment_id?: string;
|
|
366
|
+
version?: number;
|
|
367
|
+
provider?: string;
|
|
368
|
+
template_ref?: string;
|
|
369
|
+
build_id?: string;
|
|
370
|
+
/** Defaults to `{ type: 'unrestricted' }` when the Environment declares no networking. */
|
|
371
|
+
networking?: {
|
|
372
|
+
type?: 'unrestricted' | 'limited' | string;
|
|
373
|
+
allowed_hosts?: string[];
|
|
374
|
+
};
|
|
375
|
+
[k: string]: unknown;
|
|
376
|
+
};
|
|
377
|
+
/**
|
|
378
|
+
* `true` once the first sandbox has been created — from then on the Environment pin is
|
|
379
|
+
* FROZEN and every attempt to change it is `409 environment_locked`. Stopping the agent does
|
|
380
|
+
* not clear it, and there is no escape hatch through the gateway
|
|
381
|
+
* (`POST /agents/{id}:replace-environment` is 404 there). Choose the Environment at create
|
|
382
|
+
* time or live with it. Staging-verified 2026-08-07.
|
|
383
|
+
*/
|
|
384
|
+
environment_locked?: boolean;
|
|
385
|
+
/** ISO 8601 instant the lock was written; `null` while still unlocked. */
|
|
386
|
+
environment_locked_at?: string | null;
|
|
387
|
+
status?: AgentStatus;
|
|
388
|
+
ownership?: Ownership;
|
|
389
|
+
[k: string]: unknown;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* A skill registry row, as returned by `uploadSkill` / `listSkills`.
|
|
393
|
+
*
|
|
394
|
+
* `latest_version` came back as the STRING `"1"` from the multipart create on staging
|
|
395
|
+
* (2026-08-07) while other surfaces spell it as a number — compare loosely, or `Number()` it.
|
|
396
|
+
*/
|
|
397
|
+
export interface SkillRecord {
|
|
398
|
+
skill_id: string;
|
|
399
|
+
scope?: 'org' | 'personal' | 'global' | 'pack' | string;
|
|
400
|
+
name?: string;
|
|
401
|
+
description?: string;
|
|
402
|
+
latest_version?: number | string | null;
|
|
403
|
+
/** `active`, … */
|
|
404
|
+
status?: string;
|
|
405
|
+
pack_id?: string | null;
|
|
406
|
+
created_by?: string;
|
|
407
|
+
created_at?: string;
|
|
408
|
+
updated_at?: string;
|
|
409
|
+
/**
|
|
410
|
+
* The tenant the gateway rewrote your upload to. `owner_uid` comes back `null` on an
|
|
411
|
+
* `org`-scope skill — it belongs to the org, not to a person — so this is deliberately looser
|
|
412
|
+
* than {@link Ownership}, which requires both.
|
|
413
|
+
*/
|
|
414
|
+
ownership?: {
|
|
415
|
+
owner_uid?: string | null;
|
|
416
|
+
org_id?: string | null;
|
|
417
|
+
[k: string]: unknown;
|
|
418
|
+
};
|
|
419
|
+
[k: string]: unknown;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* One `session_transcripts` row, as projected by `getSession(…, { history: true })`.
|
|
423
|
+
*
|
|
424
|
+
* This is the AT-REST transcript, not the event log: conversation text lives under
|
|
425
|
+
* `entry.message` (`{ role, content }`) for `entry_type: 'message'`. Use it to recover an
|
|
426
|
+
* answer whose events you missed; use `listEvents` when you want the event stream.
|
|
427
|
+
*/
|
|
428
|
+
export interface SessionHistoryEntry {
|
|
429
|
+
seq: number;
|
|
430
|
+
entry_type: string;
|
|
431
|
+
entry: Record<string, unknown>;
|
|
432
|
+
created_at?: string;
|
|
433
|
+
}
|
|
434
|
+
export interface SessionRecord {
|
|
435
|
+
session_id: string;
|
|
436
|
+
/** `api:{session_id}` for a session you created; `agent:{agent_id}:cron:{schedule_id}:…` for a scheduled fire. */
|
|
437
|
+
session_key?: string;
|
|
438
|
+
/** `api` for sessions you create, `cron` for ones a schedule fired. */
|
|
439
|
+
channel?: string;
|
|
440
|
+
/**
|
|
441
|
+
* `listSessions` is the surface that carries the run outcome (`succeeded`, …) — and it spells
|
|
442
|
+
* it `run_status`, not `status`. Staging-verified 2026-08-07: `getSession` returns a `status`
|
|
443
|
+
* of `null` for the very same session, so reading `status` off a list row gets you nothing.
|
|
444
|
+
*/
|
|
445
|
+
run_status?: string;
|
|
446
|
+
/** Observed `null` on `getSession`. Prefer {@link SessionRecord.run_status} from `listSessions`. */
|
|
447
|
+
status?: string | null;
|
|
448
|
+
metadata?: Record<string, unknown>;
|
|
449
|
+
archived?: boolean;
|
|
450
|
+
updated_at?: string;
|
|
451
|
+
/** Present only when the read asked for `history: true`; the most recent `limit` rows, in order. */
|
|
452
|
+
history?: SessionHistoryEntry[];
|
|
453
|
+
[k: string]: unknown;
|
|
454
|
+
}
|
|
455
|
+
/** Write-side events: user.message / user.interrupt / user.tool_confirmation / system.message */
|
|
456
|
+
export interface OutboundEvent {
|
|
457
|
+
type: string;
|
|
458
|
+
content?: unknown;
|
|
459
|
+
[k: string]: unknown;
|
|
460
|
+
}
|
|
461
|
+
/** One `postEvents` receipt. An accepted event carries the full event object's fields too. */
|
|
462
|
+
export interface PostEventReceipt {
|
|
463
|
+
id?: string | null;
|
|
464
|
+
type?: string;
|
|
465
|
+
accepted?: boolean;
|
|
466
|
+
[k: string]: unknown;
|
|
467
|
+
}
|
|
468
|
+
/** One page of the unified event history, with its pagination fields when the server sends them. */
|
|
469
|
+
export interface SessionEventPage {
|
|
470
|
+
events: SessionEvent[];
|
|
471
|
+
hasMore?: boolean;
|
|
472
|
+
nextCursor?: string | null;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* When a schedule fires. Three kinds, and only `cron` has its field names pinned by the engine
|
|
476
|
+
* reference (`{"kind":"cron","expr":"0 9 * * *","tz":"Asia/Singapore"}`); `every` and `at` are
|
|
477
|
+
* documented by prose only — "the management plane supports cron/every/at", and an `at`
|
|
478
|
+
* schedule "uses the supplied ISO instant". Their extra fields are therefore left open rather
|
|
479
|
+
* than guessed at, so anything the engine accepts still type-checks.
|
|
480
|
+
*
|
|
481
|
+
* Cron is a five-field expression. Macros and a `CRON_TZ=` prefix are rejected; overlap is
|
|
482
|
+
* fixed to SKIP server-side, so a fire that lands on a still-running one is dropped, not queued.
|
|
483
|
+
*/
|
|
484
|
+
export type ScheduleSpec = {
|
|
485
|
+
kind: 'cron';
|
|
486
|
+
expr: string;
|
|
487
|
+
tz?: string;
|
|
488
|
+
[k: string]: unknown;
|
|
489
|
+
} | {
|
|
490
|
+
kind: 'every';
|
|
491
|
+
every: string | number;
|
|
492
|
+
tz?: string;
|
|
493
|
+
[k: string]: unknown;
|
|
494
|
+
} | {
|
|
495
|
+
kind: 'at';
|
|
496
|
+
at: string;
|
|
497
|
+
tz?: string;
|
|
498
|
+
[k: string]: unknown;
|
|
499
|
+
};
|
|
500
|
+
/** What a schedule does when it fires. `agentTurn` is the one the management plane accepts. */
|
|
501
|
+
export interface SchedulePayload {
|
|
502
|
+
kind: 'agentTurn' | string;
|
|
503
|
+
message?: string;
|
|
504
|
+
/**
|
|
505
|
+
* Evaluate-revise-finalize gate for THIS job — overrides the agent-level default, and an
|
|
506
|
+
* explicit `null` opts this job out of that default. Cron fires only. Staging-verified
|
|
507
|
+
* 2026-08-14 (201, read back verbatim). See {@link OutcomeConfig}.
|
|
508
|
+
*/
|
|
509
|
+
outcome?: OutcomeConfig | null;
|
|
510
|
+
[k: string]: unknown;
|
|
511
|
+
}
|
|
512
|
+
export interface ScheduleInput {
|
|
513
|
+
/** Caller-chosen id, `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`. Re-creating it with a DIFFERENT definition is 409. */
|
|
514
|
+
schedule_id: string;
|
|
515
|
+
schedule: ScheduleSpec;
|
|
516
|
+
payload: SchedulePayload;
|
|
517
|
+
jobKind?: string;
|
|
518
|
+
/**
|
|
519
|
+
* Where the turn runs. Omit for `isolated` (a fresh session per fire); `session:<id>` targets
|
|
520
|
+
* an existing session of this agent. `current` is agent-only and rejected here. IMMUTABLE
|
|
521
|
+
* after create — see {@link ScheduleUpdate}.
|
|
522
|
+
*/
|
|
523
|
+
sessionTarget?: 'isolated' | string;
|
|
524
|
+
/** `{ mode: 'none' }` or a typed `announce`. Webhook delivery is rejected. */
|
|
525
|
+
delivery?: {
|
|
526
|
+
mode: 'none' | 'announce' | string;
|
|
527
|
+
[k: string]: unknown;
|
|
528
|
+
};
|
|
529
|
+
enabled?: boolean;
|
|
530
|
+
deleteAfterRun?: boolean;
|
|
531
|
+
[k: string]: unknown;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* The PUT body. Same vocabulary as {@link ScheduleInput}, minus the fields the server owns.
|
|
535
|
+
*
|
|
536
|
+
* The six `never` fields below are the six ways a caller loses time here, and every one of
|
|
537
|
+
* them is something a `getSchedule()` result contains — which is why they are compile errors
|
|
538
|
+
* rather than runtime surprises. `updateSchedule` also strips them at runtime, so a JavaScript
|
|
539
|
+
* caller round-tripping a read still succeeds. Staging-verified 2026-08-07.
|
|
540
|
+
*/
|
|
541
|
+
export interface ScheduleUpdate {
|
|
542
|
+
/** The cadence, in the INPUT vocabulary. `{ kind: 'cron', expr, tz }` — verified to apply. */
|
|
543
|
+
schedule?: ScheduleSpec;
|
|
544
|
+
payload?: SchedulePayload;
|
|
545
|
+
jobKind?: string;
|
|
546
|
+
/** Immutable. Sending it back — even unchanged — is `400 sessionTarget is immutable`. */
|
|
547
|
+
sessionTarget?: never;
|
|
548
|
+
/**
|
|
549
|
+
* The READ shape of the cadence. Accepted with a 200 and then SILENTLY IGNORED — a PUT
|
|
550
|
+
* carrying `scheduleSpec: { cronExpressions: ['45 9 * * *'] }` leaves the old expression in
|
|
551
|
+
* place while any sibling field in the same body applies. Send `schedule` instead.
|
|
552
|
+
*/
|
|
553
|
+
scheduleSpec?: never;
|
|
554
|
+
/** Server-derived: `400 execution, originMetadata, creatorPrincipalRef, and contextSnapshot are server-derived`. */
|
|
555
|
+
execution?: never;
|
|
556
|
+
/** Server-derived — same 400 as `execution`. */
|
|
557
|
+
originMetadata?: never;
|
|
558
|
+
/** Server-derived — same 400 as `execution`. */
|
|
559
|
+
contextSnapshot?: never;
|
|
560
|
+
/** Server-derived — same 400 as `execution`. */
|
|
561
|
+
creatorPrincipalRef?: never;
|
|
562
|
+
delivery?: {
|
|
563
|
+
mode: 'none' | 'announce' | string;
|
|
564
|
+
[k: string]: unknown;
|
|
565
|
+
};
|
|
566
|
+
enabled?: boolean;
|
|
567
|
+
deleteAfterRun?: boolean;
|
|
568
|
+
[k: string]: unknown;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* A schedule as the API spells it — and it spells it three ways, staging-verified 2026-08-07.
|
|
572
|
+
*
|
|
573
|
+
* NOTHING YOU SENT COMES BACK UNDER THE NAME YOU SENT IT. The write shape ({@link ScheduleInput})
|
|
574
|
+
* and the read shape are different documents:
|
|
575
|
+
*
|
|
576
|
+
* - `schedule: { kind: 'cron', expr, tz }` is stored and read back as
|
|
577
|
+
* {@link ScheduleRecord.scheduleSpec} — `{ timezoneName, catchupWindowMs, cronExpressions[] }`.
|
|
578
|
+
* There is no `schedule` key on any read. Reach for `scheduleSpec.cronExpressions[0]`.
|
|
579
|
+
* - `sessionTarget: 'isolated'` is read back as `execution: { kind: 'isolated' }`. There is no
|
|
580
|
+
* `sessionTarget` key on any read either.
|
|
581
|
+
* - `scheduleId` is the FULLY-QUALIFIED name `cron/{computer_id}/{agent_id}/{schedule_id}`, not
|
|
582
|
+
* the id you chose. Your id is `name`.
|
|
583
|
+
*
|
|
584
|
+
* The three responses also disagree with each other: `POST` answers with only snake_case
|
|
585
|
+
* `schedule_name`; `GET /schedules/{id}` answers the camelCase projection below; `GET /schedules`
|
|
586
|
+
* answers the raw Temporal describe (`spec` / `state` / `memo` / `next_action_times`) with the
|
|
587
|
+
* camelCase projection merged on top. Read defensively and match on what you find.
|
|
588
|
+
*/
|
|
589
|
+
export interface ScheduleRecord {
|
|
590
|
+
/** FULLY-QUALIFIED: `cron/{computer_id}/{agent_id}/{schedule_id}`. Not the id you passed in. */
|
|
591
|
+
scheduleId?: string;
|
|
592
|
+
/** The `schedule_id` you chose. This is the one you pass back to get/update/delete. */
|
|
593
|
+
name?: string;
|
|
594
|
+
computerId?: string;
|
|
595
|
+
agentId?: string;
|
|
596
|
+
apiAgentId?: string;
|
|
597
|
+
/** snake_case, from the create/trigger receipts: `cron/{computer_id}/{agent_id}/{schedule_id}`. */
|
|
598
|
+
schedule_name?: string;
|
|
599
|
+
/**
|
|
600
|
+
* The NORMALIZED cadence — what `ScheduleInput.schedule` became. Read-only: sending it back on
|
|
601
|
+
* a PUT is silently ignored (see {@link ScheduleUpdate.scheduleSpec}).
|
|
602
|
+
*/
|
|
603
|
+
scheduleSpec?: {
|
|
604
|
+
timezoneName?: string;
|
|
605
|
+
catchupWindowMs?: number;
|
|
606
|
+
cronExpressions?: string[];
|
|
607
|
+
[k: string]: unknown;
|
|
608
|
+
};
|
|
609
|
+
/** Where the turn runs — what `ScheduleInput.sessionTarget` became. `{ kind: 'isolated' }`, etc. */
|
|
610
|
+
execution?: {
|
|
611
|
+
kind?: string;
|
|
612
|
+
sessionId?: string;
|
|
613
|
+
[k: string]: unknown;
|
|
614
|
+
};
|
|
615
|
+
payload?: SchedulePayload;
|
|
616
|
+
jobKind?: string;
|
|
617
|
+
delivery?: Record<string, unknown>;
|
|
618
|
+
enabled?: boolean;
|
|
619
|
+
deleteAfterRun?: boolean;
|
|
620
|
+
/** Server-derived, and rejected on the way back in — see {@link ZooworkClient.updateSchedule}. */
|
|
621
|
+
originMetadata?: Record<string, unknown>;
|
|
622
|
+
/** Server-derived, and rejected on the way back in. */
|
|
623
|
+
contextSnapshot?: unknown[];
|
|
624
|
+
origin?: string;
|
|
625
|
+
consecutiveErrors?: number;
|
|
626
|
+
createdAt?: string;
|
|
627
|
+
updatedAt?: string;
|
|
628
|
+
/** `GET /schedules` (list) only: the raw Temporal describe, in a different vocabulary again. */
|
|
629
|
+
spec?: Record<string, unknown>;
|
|
630
|
+
/** List only. `{ paused, note }` — NOT the lifecycle state, and unrelated to `enabled`. */
|
|
631
|
+
state?: {
|
|
632
|
+
paused?: boolean;
|
|
633
|
+
note?: string;
|
|
634
|
+
[k: string]: unknown;
|
|
635
|
+
};
|
|
636
|
+
/** List only. Carries the un-qualified `schedule_id` at `memo.schedule_id`. */
|
|
637
|
+
memo?: Record<string, unknown>;
|
|
638
|
+
/** List only. The next few fire instants, ISO 8601, in UTC. */
|
|
639
|
+
next_action_times?: string[];
|
|
640
|
+
[k: string]: unknown;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* One past fire. `runs[]` mixes TWO ROW SHAPES and you have to switch on `source` to read one —
|
|
644
|
+
* staging-verified 2026-08-07, where a single `listScheduleRuns` returned both:
|
|
645
|
+
*
|
|
646
|
+
* - `source: 'temporal'` — a DISPATCH record: `scheduled_at` / `taken_at` / `workflow_id` /
|
|
647
|
+
* `temporal_run_id`. It says a workflow was handed the fire, and nothing about the outcome.
|
|
648
|
+
* - `source: 'run_projection'` — an OUTCOME record: `fired_at` / `status` / `consecutive_errors`.
|
|
649
|
+
* This is the only row that carries `status` (e.g. `skipped` for a fire against a disabled
|
|
650
|
+
* schedule).
|
|
651
|
+
*
|
|
652
|
+
* NEITHER carries `session_id`, which is the field people expect most: you cannot walk from a
|
|
653
|
+
* fire to the session it created. To find that session, list the agent's sessions and match
|
|
654
|
+
* `channel: 'cron'` with the `session_key` prefix `agent:{agent_id}:cron:{schedule_id}:`.
|
|
655
|
+
*/
|
|
656
|
+
export interface ScheduleRun {
|
|
657
|
+
/** Which projection this row came from. Decides which of the field groups below is populated. */
|
|
658
|
+
source?: 'temporal' | 'run_projection' | string;
|
|
659
|
+
/** Un-qualified `schedule_id`. */
|
|
660
|
+
schedule_id?: string;
|
|
661
|
+
fired_at?: string;
|
|
662
|
+
/** e.g. `skipped`. Present on `run_projection` rows only — a `temporal` row has no status. */
|
|
663
|
+
status?: string;
|
|
664
|
+
consecutive_errors?: number;
|
|
665
|
+
/** When the fire was due. */
|
|
666
|
+
scheduled_at?: string;
|
|
667
|
+
/** When the worker picked it up. */
|
|
668
|
+
taken_at?: string;
|
|
669
|
+
/** `{schedule_name}-workflow-{ISO instant}`. */
|
|
670
|
+
workflow_id?: string;
|
|
671
|
+
temporal_run_id?: string;
|
|
672
|
+
[k: string]: unknown;
|
|
673
|
+
}
|
|
674
|
+
/** The exact resolution vocabulary. Anything else is a 400. */
|
|
675
|
+
export type ApprovalDecision = 'allow-once' | 'allow-always' | 'deny';
|
|
676
|
+
/**
|
|
677
|
+
* A tool call parked on a human decision.
|
|
678
|
+
*
|
|
679
|
+
* Field names are unverified: the only staging observation (2026-08-07) is a 200 with an EMPTY
|
|
680
|
+
* `approvals` array, because producing a real pending approval requires a tool policy that asks.
|
|
681
|
+
* Read defensively.
|
|
682
|
+
*/
|
|
683
|
+
export interface ApprovalRecord {
|
|
684
|
+
approval_id?: string;
|
|
685
|
+
session_id?: string;
|
|
686
|
+
tool_name?: string;
|
|
687
|
+
status?: string;
|
|
688
|
+
created_at?: string;
|
|
689
|
+
[k: string]: unknown;
|
|
690
|
+
}
|
|
691
|
+
/** Artifact lifecycle. Only a `ready` row carries a resolvable `url`. */
|
|
692
|
+
export type ArtifactStatus = 'pending' | 'ready' | 'failed' | 'deleted' | string;
|
|
693
|
+
/**
|
|
694
|
+
* One published artifact — an immutable snapshot of a `/workspace` file, created by the
|
|
695
|
+
* agent's OWN in-loop `artifact_publish` tool during a turn. There is no API for publishing
|
|
696
|
+
* from outside the loop; this surface lists, reads, re-resolves and deletes what the agent
|
|
697
|
+
* published. Publishing is a COPY: the source file stays in the workspace.
|
|
698
|
+
*
|
|
699
|
+
* `url` is a revocable bearer capability — treat it like a secret, and expect its lifetime to
|
|
700
|
+
* be deployment policy (staging hands out 24-hour presigned GETs).
|
|
701
|
+
*/
|
|
702
|
+
export interface ArtifactRecord {
|
|
703
|
+
artifact_id: string;
|
|
704
|
+
agent_id?: string;
|
|
705
|
+
session_id?: string;
|
|
706
|
+
run_id?: string;
|
|
707
|
+
turn?: number;
|
|
708
|
+
/** Where in `/workspace` it was published from. */
|
|
709
|
+
source_path?: string;
|
|
710
|
+
file_name?: string;
|
|
711
|
+
content_type?: string;
|
|
712
|
+
size?: number;
|
|
713
|
+
sha256?: string;
|
|
714
|
+
status?: ArtifactStatus;
|
|
715
|
+
/** Absent until the upload finalizes — a row without it answers `409 artifact_not_ready` to download. */
|
|
716
|
+
url?: string;
|
|
717
|
+
created_at?: string;
|
|
718
|
+
finalized_at?: string | null;
|
|
719
|
+
deleted_at?: string | null;
|
|
720
|
+
[k: string]: unknown;
|
|
721
|
+
}
|
|
722
|
+
/** One page of artifacts. `has_more` is real — this list DOES tell you when it truncated. */
|
|
723
|
+
export interface ArtifactPage {
|
|
724
|
+
artifacts: ArtifactRecord[];
|
|
725
|
+
page?: number;
|
|
726
|
+
has_more?: boolean;
|
|
727
|
+
[k: string]: unknown;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* `getSystemPrompt` result: the pin as declared, and what is actually in effect.
|
|
731
|
+
*
|
|
732
|
+
* `declaration: null` together with `effective.source: 'legacy'` marks a pre-templates agent
|
|
733
|
+
* still on virtual legacy behavior — only an explicit engine-side upgrade migrates those.
|
|
734
|
+
* Agents created after templates landed carry a real declaration from birth.
|
|
735
|
+
*/
|
|
736
|
+
export interface SystemPromptInfo {
|
|
737
|
+
agent_id?: string;
|
|
738
|
+
config_version?: number;
|
|
739
|
+
declaration?: SystemPromptDeclaration | null;
|
|
740
|
+
/** Rendered-template metadata (source/profile/version/hashes…), or the virtual-legacy marker. */
|
|
741
|
+
effective?: Record<string, unknown>;
|
|
742
|
+
[k: string]: unknown;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* The runtime facts `previewSystemPrompt` assembles from. The required ones are required by
|
|
746
|
+
* the engine — omitting any is a 400 naming it.
|
|
747
|
+
*/
|
|
748
|
+
export interface SystemPromptPreviewInput {
|
|
749
|
+
/** Must equal the agent's CURRENT `status.config_version`, or the answer is `409 config_version_changed`. */
|
|
750
|
+
config_version: number;
|
|
751
|
+
now_ms: number;
|
|
752
|
+
session_id: string;
|
|
753
|
+
model_display: string;
|
|
754
|
+
workspace_dir: string;
|
|
755
|
+
tool_names: string[];
|
|
756
|
+
channel?: string;
|
|
757
|
+
chat_type?: string;
|
|
758
|
+
session_key?: string;
|
|
759
|
+
subagent?: {
|
|
760
|
+
role?: string;
|
|
761
|
+
taskName?: string;
|
|
762
|
+
};
|
|
763
|
+
[k: string]: unknown;
|
|
764
|
+
}
|
|
765
|
+
export interface SystemPromptPreview {
|
|
766
|
+
agent_id?: string;
|
|
767
|
+
config_version?: number;
|
|
768
|
+
/** The assembled prompt, verbatim. */
|
|
769
|
+
system_prompt?: string;
|
|
770
|
+
char_count?: number;
|
|
771
|
+
/** One hash per functional slot on a templated agent; `null` when legacy assembly produced no slots. */
|
|
772
|
+
slot_hashes?: Record<string, string> | null;
|
|
773
|
+
/** Always `[]` — the preview never reads the transcript. */
|
|
774
|
+
transcript?: unknown[];
|
|
775
|
+
[k: string]: unknown;
|
|
776
|
+
}
|
|
777
|
+
/** `upgradeSystemPrompt` receipt: the new pin and the version bump it cost. */
|
|
778
|
+
export interface SystemPromptUpgrade {
|
|
779
|
+
agent_id?: string;
|
|
780
|
+
/** The NEW config version — the upgrade writes a config change like any other. */
|
|
781
|
+
config_version?: number;
|
|
782
|
+
declaration?: SystemPromptDeclaration;
|
|
783
|
+
template_hash?: string;
|
|
784
|
+
[k: string]: unknown;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* An Environment build spec. The top level accepts EXACTLY these four keys — any other key is
|
|
788
|
+
* `400 invalid_environment_config`, which is why this type has no index signature.
|
|
789
|
+
*
|
|
790
|
+
* Package install order is fixed apt → npm → pip. Files land under
|
|
791
|
+
* `/opt/zooclaw/environment/`, and a top-level `bin/*` marked executable is linked into
|
|
792
|
+
* `/usr/local/bin`. No user-defined secrets, env vars, or start hooks — the platform injects
|
|
793
|
+
* its own runtime credentials for built-in skills, but that layer is internal and not
|
|
794
|
+
* extensible.
|
|
795
|
+
*/
|
|
796
|
+
export interface EnvironmentConfig {
|
|
797
|
+
packages?: {
|
|
798
|
+
apt?: string[];
|
|
799
|
+
npm?: string[];
|
|
800
|
+
pip?: string[];
|
|
801
|
+
};
|
|
802
|
+
files?: {
|
|
803
|
+
path?: string;
|
|
804
|
+
contentBase64?: string;
|
|
805
|
+
upload_id?: string;
|
|
806
|
+
executable?: boolean;
|
|
807
|
+
}[];
|
|
808
|
+
build?: {
|
|
809
|
+
script?: string;
|
|
810
|
+
verify_script?: string;
|
|
811
|
+
};
|
|
812
|
+
/**
|
|
813
|
+
* Omitted means `{ type: 'unrestricted' }` — the sandbox reaches the whole internet by
|
|
814
|
+
* default. `allowed_hosts` is accepted only with `type: 'limited'`.
|
|
815
|
+
*/
|
|
816
|
+
networking?: {
|
|
817
|
+
type: 'unrestricted' | 'limited';
|
|
818
|
+
allowed_hosts?: string[];
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
export interface EnvironmentResource {
|
|
822
|
+
name: string;
|
|
823
|
+
description?: string;
|
|
824
|
+
/** Supply to create a new version lineage under an existing id; omit to mint one. */
|
|
825
|
+
environment_id?: string;
|
|
826
|
+
config: EnvironmentConfig;
|
|
827
|
+
[k: string]: unknown;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* An Environment row. Staging-verified 2026-08-07.
|
|
831
|
+
*
|
|
832
|
+
* THE TWO VERSION NUMBERS ARE NOT THE SAME NUMBER, and picking the wrong one pins an agent to a
|
|
833
|
+
* build that does not exist yet: `latest_version` is the newest version that has been CREATED
|
|
834
|
+
* (it is `1` the instant you create an Environment, while that version is still `queued`), and
|
|
835
|
+
* `latest_ready_version` is the newest one that finished building — `null` until a build lands.
|
|
836
|
+
* Pin `latest_ready_version`, or `createAgent` answers `409 environment_not_ready`.
|
|
837
|
+
*
|
|
838
|
+
* There is no `state` here and no nested `ownership`: lifecycle is `status`
|
|
839
|
+
* (`active` / `archived`) and the tenant is flat, as `scope` + `org_id`.
|
|
840
|
+
*/
|
|
841
|
+
export interface EnvironmentRecord {
|
|
842
|
+
environment_id: string;
|
|
843
|
+
name?: string;
|
|
844
|
+
description?: string;
|
|
845
|
+
scope?: 'org' | string;
|
|
846
|
+
org_id?: string;
|
|
847
|
+
/** `active` | `archived`. */
|
|
848
|
+
status?: string;
|
|
849
|
+
/** Newest version CREATED — may still be building. Not safe to pin. */
|
|
850
|
+
latest_version?: number | null;
|
|
851
|
+
/** Newest version that reached `ready`. `null` until a build finishes. Pin THIS. */
|
|
852
|
+
latest_ready_version?: number | null;
|
|
853
|
+
created_by?: string;
|
|
854
|
+
created_at?: string;
|
|
855
|
+
updated_at?: string;
|
|
856
|
+
/** Set once archived; `null` while active. */
|
|
857
|
+
archived_at?: string | null;
|
|
858
|
+
/** CREATE ONLY: the first version, inline — no follow-up `getEnvironmentVersion` needed to see it. */
|
|
859
|
+
version?: EnvironmentVersionRecord;
|
|
860
|
+
[k: string]: unknown;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* One immutable Environment version. Builds walk
|
|
864
|
+
* `queued → submitting → building → verifying → ready`, and any phase can land in `failed`.
|
|
865
|
+
* Poll THIS, not the Environment's top-level row: a `ready` version is the only thing an agent
|
|
866
|
+
* can pin, and it always carries both `e2b_build_id` and a matching `template_ref`.
|
|
867
|
+
*
|
|
868
|
+
* THE FIELD IS `status`, NOT `state` — staging-verified 2026-08-07. A poll loop written against
|
|
869
|
+
* `state` compares `undefined` to `'ready'` forever and never terminates, which is precisely the
|
|
870
|
+
* hang this note exists to prevent.
|
|
871
|
+
*/
|
|
872
|
+
export interface EnvironmentVersionRecord {
|
|
873
|
+
environment_id?: string;
|
|
874
|
+
version?: number;
|
|
875
|
+
/** `queued` → `submitting` → `building` → `verifying` → `ready`, or `failed`. */
|
|
876
|
+
status?: 'queued' | 'submitting' | 'building' | 'verifying' | 'ready' | 'failed' | string;
|
|
877
|
+
/** The normalized config this version was built from — not the one you posted verbatim. */
|
|
878
|
+
config?: EnvironmentConfig;
|
|
879
|
+
base_environment_id?: string;
|
|
880
|
+
base_version?: number;
|
|
881
|
+
source_hash?: string;
|
|
882
|
+
spec_hash?: string;
|
|
883
|
+
e2b_template_name?: string | null;
|
|
884
|
+
e2b_template_id?: string | null;
|
|
885
|
+
/** `null` until the build reaches `ready`. */
|
|
886
|
+
e2b_build_id?: string | null;
|
|
887
|
+
/** `null` until the build reaches `ready`. */
|
|
888
|
+
template_ref?: string | null;
|
|
889
|
+
/** The exact base-image build this version layers on — recorded 2026-08-14, new on the wire. */
|
|
890
|
+
base_template_ref?: string | null;
|
|
891
|
+
/** Which phase failed, on `status: 'failed'`. */
|
|
892
|
+
failure_stage?: string | null;
|
|
893
|
+
failure_message?: string | null;
|
|
894
|
+
created_by?: string;
|
|
895
|
+
created_at?: string;
|
|
896
|
+
ready_at?: string | null;
|
|
897
|
+
[k: string]: unknown;
|
|
898
|
+
}
|
|
899
|
+
/** `POST /agents/{id}/exec` result. A failed command still arrives here, not as a rejection. */
|
|
900
|
+
export interface ExecResult {
|
|
901
|
+
/** Non-zero means the COMMAND failed. The HTTP call succeeded regardless. */
|
|
902
|
+
exit_code: number;
|
|
903
|
+
stdout: string;
|
|
904
|
+
stderr: string;
|
|
905
|
+
}
|
|
906
|
+
export interface WakeResult {
|
|
907
|
+
mode: 'now' | 'next-heartbeat' | string;
|
|
908
|
+
/** The reminder was written to the pending queue. */
|
|
909
|
+
queued: boolean;
|
|
910
|
+
/** `mode: 'now'` only — whether the heartbeat schedule was actually kicked. */
|
|
911
|
+
triggered: boolean;
|
|
912
|
+
}
|
|
913
|
+
export type { SessionEvent } from './events.js';
|
|
914
|
+
export interface ZooworkClient {
|
|
915
|
+
listModels(): Promise<ModelInfo[]>;
|
|
916
|
+
/**
|
|
917
|
+
* Create an agent. The gateway derives ownership from your API key and always skips the
|
|
918
|
+
* interactive onboarding interview — the agent answers your first message directly.
|
|
919
|
+
* `ownership` is accepted for callers that talk to the engine without the gateway;
|
|
920
|
+
* through the gateway it is overwritten and can be omitted.
|
|
921
|
+
*/
|
|
922
|
+
createAgent(input: {
|
|
923
|
+
resource: AgentResource;
|
|
924
|
+
ownership?: Ownership;
|
|
925
|
+
}, idempotencyKey?: string): Promise<AgentRecord>;
|
|
926
|
+
/**
|
|
927
|
+
* List the agents owned by your key's bound user (engine query: `owner_uid AND org_id`,
|
|
928
|
+
* both injected by the gateway). `labels` filters on declared labels — e.g.
|
|
929
|
+
* `{ labels: { workspace_id: '…' } }` resolves an app workspace id (the first path
|
|
930
|
+
* segment of a ZooWork chat URL) to its agent. Page size is fixed at 100 by the engine.
|
|
931
|
+
*
|
|
932
|
+
* Note the scope is `owner_uid AND org_id`: an agent a colleague created in your org is
|
|
933
|
+
* fetchable by `getAgent` but will not appear here.
|
|
934
|
+
*/
|
|
935
|
+
listAgents(opts?: {
|
|
936
|
+
labels?: Record<string, string>;
|
|
937
|
+
page?: number;
|
|
938
|
+
}): Promise<AgentRecord[]>;
|
|
939
|
+
getAgent(agentId: string): Promise<AgentRecord>;
|
|
940
|
+
/** PUT declared sections; bumps config_version on EVERY call — gate on drift, don't blind-retry. */
|
|
941
|
+
updateAgent(agentId: string, sections: Record<string, unknown>): Promise<AgentRecord>;
|
|
942
|
+
/**
|
|
943
|
+
* Soft delete: the agent stops resolving on the API, but this is not a resource purge.
|
|
944
|
+
* On gateway releases that carry the channels surface, a successful delete also
|
|
945
|
+
* best-effort disables the agent's bound channels — a failure there never turns the
|
|
946
|
+
* delete into an error, so a chat binding can in rare cases outlive its agent.
|
|
947
|
+
*/
|
|
948
|
+
deleteAgent(agentId: string): Promise<void>;
|
|
949
|
+
/**
|
|
950
|
+
* Flip `desired_state` to `running` — the precondition for every session call.
|
|
951
|
+
* Fast (sub-second on staging). The returned warnings are informational: an
|
|
952
|
+
* API-only agent reports `channel_routes_reload_failed` on every start/stop
|
|
953
|
+
* because it has no chat-channel routes to reload. Do not treat it as failure.
|
|
954
|
+
*/
|
|
955
|
+
startAgent(agentId: string): Promise<{
|
|
956
|
+
warnings: string[];
|
|
957
|
+
}>;
|
|
958
|
+
stopAgent(agentId: string): Promise<{
|
|
959
|
+
warnings: string[];
|
|
960
|
+
}>;
|
|
961
|
+
/**
|
|
962
|
+
* Poll until `status.desired_state === 'running'`, then hand back that projection.
|
|
963
|
+
*
|
|
964
|
+
* READ THIS BEFORE WRITING YOUR OWN LOOP. `desired_state` is the only readiness signal.
|
|
965
|
+
* Polling `status.actual_state` for `'running'` is the documented way to hang forever:
|
|
966
|
+
* `actual_state` reports CHAT-CHANNEL health, `running` is not one of its values, and an
|
|
967
|
+
* API-only agent parks at `activating` for the rest of its life. Every hand-rolled readiness
|
|
968
|
+
* loop we have seen gets this wrong.
|
|
969
|
+
*
|
|
970
|
+
* Defaults: 30s budget, 500ms between polls — start is sub-second on staging, so the budget
|
|
971
|
+
* is for a bad day, not the normal one. On timeout it throws a {@link ZooworkError} with
|
|
972
|
+
* `status: 408` / `type: 'timeout'`; on abort, `status: 0` / `type: 'aborted'`. Both are
|
|
973
|
+
* synthesized locally — the server never sends either.
|
|
974
|
+
*
|
|
975
|
+
* Both bounds cover an IN-FLIGHT poll, not just the gap between polls: each request carries a
|
|
976
|
+
* signal that fires on the caller's `signal` or on whatever is left of the budget. A gateway
|
|
977
|
+
* that accepts the connection and then never answers therefore ends this wait on schedule
|
|
978
|
+
* instead of hanging it (`fetch` imposes no timeout of its own).
|
|
979
|
+
*/
|
|
980
|
+
waitUntilRunning(agentId: string, opts?: {
|
|
981
|
+
timeoutMs?: number;
|
|
982
|
+
intervalMs?: number;
|
|
983
|
+
signal?: AbortSignal;
|
|
984
|
+
}): Promise<AgentRecord>;
|
|
985
|
+
/** Skills already attached to the agent, resolved and merged. `verbose` includes ineligible/excluded entries. */
|
|
986
|
+
listAgentSkills(agentId: string, opts?: {
|
|
987
|
+
verbose?: boolean;
|
|
988
|
+
}): Promise<AgentSkill[]>;
|
|
989
|
+
/**
|
|
990
|
+
* Attach a skill by id. Only skills the caller's tenant owns are installable
|
|
991
|
+
* through the `/service/v1` gateway (`org` / `personal` scope); `global`
|
|
992
|
+
* catalog entries are listable but answer 404 here.
|
|
993
|
+
*/
|
|
994
|
+
putAgentSkill(agentId: string, skillId: string, opts?: {
|
|
995
|
+
enabled?: boolean;
|
|
996
|
+
versionPin?: number | null;
|
|
997
|
+
}): Promise<{
|
|
998
|
+
config_version?: number;
|
|
999
|
+
warnings?: string[];
|
|
1000
|
+
}>;
|
|
1001
|
+
deleteAgentSkill(agentId: string, skillId: string): Promise<void>;
|
|
1002
|
+
/**
|
|
1003
|
+
* Channels currently bound to the agent. Empty for a pure API agent.
|
|
1004
|
+
*
|
|
1005
|
+
* Lists the platforms in {@link ChannelPlatform}. The deployment's own internal channels are
|
|
1006
|
+
* filtered out server-side and never appear here.
|
|
1007
|
+
*/
|
|
1008
|
+
listChannels(agentId: string): Promise<AgentChannel[]>;
|
|
1009
|
+
/**
|
|
1010
|
+
* Bind a channel from explicit platform config (the non-QR path) — `config` carries the
|
|
1011
|
+
* platform's own credential keys. Answers the created channel (HTTP 201). This is the ONLY
|
|
1012
|
+
* path for Slack and WeCom; Feishu also has the QR flow. See {@link ChannelPlatform} for what
|
|
1013
|
+
* binds and what does not.
|
|
1014
|
+
*
|
|
1015
|
+
* **It is an upsert, not a create.** Binding the same `platform` + `account` twice answers
|
|
1016
|
+
* `201` again and overwrites the first binding rather than conflicting.
|
|
1017
|
+
*
|
|
1018
|
+
* ⚠️ **201 means STORED, not WORKING.** Credentials are not validated at bind time: a channel
|
|
1019
|
+
* created from deliberately bogus credentials still answered 201 with `health: 'unknown'`,
|
|
1020
|
+
* `status: 'configured'`, and only turned `health: 'unhealthy'` / `status: 'error'` moments
|
|
1021
|
+
* later (staging-verified 2026-08-25). Read the verdict from `health`/`status` on a follow-up
|
|
1022
|
+
* {@link listChannels}; treating the 201 as success ships a silently broken binding.
|
|
1023
|
+
*/
|
|
1024
|
+
addChannel(agentId: string, input: AddChannelInput): Promise<AgentChannel>;
|
|
1025
|
+
/**
|
|
1026
|
+
* Change `dm_policy` / `group_policy` / `enabled` on one bound platform account, and get the
|
|
1027
|
+
* channel back in its NEW state. `allow_from` is deliberately not editable — write-once at
|
|
1028
|
+
* create.
|
|
1029
|
+
*
|
|
1030
|
+
* `enabled: false` is not just a flag: it was observed moving `status` to `'disabled'` and
|
|
1031
|
+
* resetting `health` to `'unknown'`. Updating a platform with no binding answers
|
|
1032
|
+
* `404 channel.not_found`.
|
|
1033
|
+
*/
|
|
1034
|
+
updateChannel(agentId: string, platform: string, input?: UpdateChannelInput): Promise<AgentChannel>;
|
|
1035
|
+
/**
|
|
1036
|
+
* Unbind one platform account (server default account: `'default'`).
|
|
1037
|
+
*
|
|
1038
|
+
* Idempotent, unlike {@link updateChannel}: removing a binding that is not there answers
|
|
1039
|
+
* `200 { ok: true }` rather than `404 channel.not_found`.
|
|
1040
|
+
*/
|
|
1041
|
+
removeChannel(agentId: string, platform: string, opts?: {
|
|
1042
|
+
account?: string;
|
|
1043
|
+
}): Promise<void>;
|
|
1044
|
+
/**
|
|
1045
|
+
* Start the Feishu/Lark QR registration. YOU own the UI: render
|
|
1046
|
+
* `verification_uri_complete` (usually as a QR code) and drive the poll loop —
|
|
1047
|
+
* `waitForFeishuSetup` does the loop part for you.
|
|
1048
|
+
*
|
|
1049
|
+
* Observed defaults: `expires_in: 600`, `poll_interval: 5`. `brand` picks the real host —
|
|
1050
|
+
* `'feishu'` answers an `open.feishu.cn` URI, `'lark'` an `open.larksuite.com` one, so the
|
|
1051
|
+
* brand has to match the workspace the person will approve it in.
|
|
1052
|
+
*/
|
|
1053
|
+
startFeishuSetup(agentId: string, input?: FeishuSetupInput): Promise<FeishuSetupSession>;
|
|
1054
|
+
/**
|
|
1055
|
+
* One poll of a setup session. `status: 'pending'` means keep going; a cancelled or expired
|
|
1056
|
+
* session answers `404 channel.feishu_session_not_found` rather than a terminal status, so
|
|
1057
|
+
* a hand-rolled loop must treat that 404 as an end condition, not as a transport error.
|
|
1058
|
+
*/
|
|
1059
|
+
pollFeishuSetup(agentId: string, sessionId: string): Promise<FeishuPollResult>;
|
|
1060
|
+
/** Abandon a setup session. Afterwards polling it answers `404 channel.feishu_session_not_found`. */
|
|
1061
|
+
cancelFeishuSetup(agentId: string, sessionId: string): Promise<void>;
|
|
1062
|
+
/**
|
|
1063
|
+
* Poll a Feishu setup session until it leaves `pending`, then hand back that terminal poll.
|
|
1064
|
+
* A status the server reports in the body — `success` / `expired` / `denied` / `error` — is
|
|
1065
|
+
* RETURNED, not thrown: "the person rejected it" is an outcome, not an exception.
|
|
1066
|
+
*
|
|
1067
|
+
* But a session can also stop existing, and then polling answers
|
|
1068
|
+
* `404 channel.feishu_session_not_found`, which surfaces here as a thrown
|
|
1069
|
+
* {@link ZooworkError} carrying that `type`. Confirmed for a cancelled session
|
|
1070
|
+
* (staging 2026-08-25); whether a session that simply runs past `expires_in` reports
|
|
1071
|
+
* `status: 'expired'` in a 200 or disappears into this 404 was NOT observed — handle both.
|
|
1072
|
+
*
|
|
1073
|
+
* Pacing follows the server's `poll_interval` when present (observed default 5s; the local
|
|
1074
|
+
* fallback matches). The default budget is 600s, which is also the observed `expires_in` —
|
|
1075
|
+
* pass the session's own value when you have it. On timeout it throws `status: 408` /
|
|
1076
|
+
* `type: 'timeout'`; on abort, `status: 0` / `type: 'aborted'` — both synthesized locally,
|
|
1077
|
+
* and every in-flight poll is bounded the way {@link waitUntilRunning} bounds its polls.
|
|
1078
|
+
* `onPoll` fires after every poll, terminal one included, for progress UI.
|
|
1079
|
+
*/
|
|
1080
|
+
waitForFeishuSetup(agentId: string, sessionId: string, opts?: {
|
|
1081
|
+
timeoutMs?: number;
|
|
1082
|
+
signal?: AbortSignal;
|
|
1083
|
+
onPoll?: (poll: FeishuPollResult) => void;
|
|
1084
|
+
}): Promise<FeishuPollResult>;
|
|
1085
|
+
/**
|
|
1086
|
+
* The agent's system-prompt pin and the rendered template in effect. Staging-verified
|
|
1087
|
+
* 2026-08-14 — a fresh agent answers a real `declaration` (`{source:'platform',version:1}`),
|
|
1088
|
+
* a pre-templates agent answers `declaration: null` with a virtual-legacy `effective`.
|
|
1089
|
+
*/
|
|
1090
|
+
getSystemPrompt(agentId: string): Promise<SystemPromptInfo>;
|
|
1091
|
+
/**
|
|
1092
|
+
* Assemble the EXACT prompt for a given set of runtime facts, without touching any session.
|
|
1093
|
+
* Deterministic for fixed inputs (`transcript` is always `[]`), and `slot_hashes` names each
|
|
1094
|
+
* template slot for diffing. `config_version` must be the agent's CURRENT one.
|
|
1095
|
+
* Staging-verified 2026-08-14; the raw `:` in `system-prompt:preview` passes the gateway.
|
|
1096
|
+
*/
|
|
1097
|
+
previewSystemPrompt(agentId: string, input: SystemPromptPreviewInput): Promise<SystemPromptPreview>;
|
|
1098
|
+
/**
|
|
1099
|
+
* Move the system-prompt pin — the ONE write that does, since nothing else ever follows a
|
|
1100
|
+
* later platform activation. Omit `template_version` to upgrade to the currently ACTIVE
|
|
1101
|
+
* platform version; pass one to pin a specific immutable version.
|
|
1102
|
+
*
|
|
1103
|
+
* `expected_config_version` is REQUIRED and it is a real CAS: it must equal the agent's
|
|
1104
|
+
* current `status.config_version` or the answer is `409 config_version_changed` — read
|
|
1105
|
+
* fresh, then upgrade. The 200 receipt carries the NEW `config_version` (the upgrade is a
|
|
1106
|
+
* config write like any other) plus the pinned `declaration` and `template_hash`.
|
|
1107
|
+
*
|
|
1108
|
+
* Staging-verified 2026-08-14, the day gateway fix #3387 opened the `{id}:verb` route
|
|
1109
|
+
* grammar (this route was 404 through the gateway until then — do not expect it on older
|
|
1110
|
+
* gateway deployments).
|
|
1111
|
+
*/
|
|
1112
|
+
upgradeSystemPrompt(agentId: string, input: {
|
|
1113
|
+
expected_config_version: number;
|
|
1114
|
+
template_version?: number;
|
|
1115
|
+
}): Promise<SystemPromptUpgrade>;
|
|
1116
|
+
/**
|
|
1117
|
+
* Upload a skill package as a zip. One call creates the skill row AND version 1.
|
|
1118
|
+
*
|
|
1119
|
+
* THE RULE THAT COSTS EVERYONE THEIR FIRST ATTEMPT: the zip's single top-level directory name
|
|
1120
|
+
* must equal the `name` in `SKILL.md`'s frontmatter (compared case- and underscore-
|
|
1121
|
+
* insensitively), or the whole thing is a 400 —
|
|
1122
|
+
* `top-level directory 'my-test-skill' must match SKILL.md name 'fulong-probe-skill'`.
|
|
1123
|
+
* So `zip -r skill.zip market-research/` where `market-research/SKILL.md` declares
|
|
1124
|
+
* `name: market-research`. A zip whose ROOT is the skill (SKILL.md at the top) is also
|
|
1125
|
+
* accepted. `SKILL.md` must be non-empty and declare both `name` and `description`.
|
|
1126
|
+
* 50 MB expanded, zip only (store/deflate), encrypted zips rejected.
|
|
1127
|
+
*
|
|
1128
|
+
* `scope` may only be `org` or `personal`; `global` and `pack` are 403 and are published
|
|
1129
|
+
* through an admin surface the gateway does not proxy. The gateway rewrites ownership to your
|
|
1130
|
+
* key's tenant either way. Staging-verified 2026-08-07 — this is the ONLY path by which an
|
|
1131
|
+
* API-key caller can add a skill the agent will actually load.
|
|
1132
|
+
*/
|
|
1133
|
+
uploadSkill(zip: Blob | ArrayBuffer | Uint8Array, opts: {
|
|
1134
|
+
scope: 'org' | 'personal';
|
|
1135
|
+
fileName?: string;
|
|
1136
|
+
description?: string;
|
|
1137
|
+
idempotencyKey?: string;
|
|
1138
|
+
}): Promise<SkillRecord>;
|
|
1139
|
+
/**
|
|
1140
|
+
* Publish a new version of an existing skill from a zip. Same zip rules as
|
|
1141
|
+
* {@link ZooworkClient.uploadSkill}, plus: the frontmatter `name` must match the target
|
|
1142
|
+
* skill's name. `description` overrides the one in the frontmatter.
|
|
1143
|
+
*
|
|
1144
|
+
* Agents that installed the skill unpinned follow the new version on their own — the registry
|
|
1145
|
+
* bumps their `config_version`; you do not re-`putAgentSkill`.
|
|
1146
|
+
*/
|
|
1147
|
+
uploadSkillVersion(skillId: string, zip: Blob | ArrayBuffer | Uint8Array, opts?: {
|
|
1148
|
+
fileName?: string;
|
|
1149
|
+
description?: string;
|
|
1150
|
+
idempotencyKey?: string;
|
|
1151
|
+
}): Promise<SkillRecord>;
|
|
1152
|
+
/**
|
|
1153
|
+
* The catalog visible to your key: global skills plus your org/personal ones. `q` matches on
|
|
1154
|
+
* name; `page` is 1-based with a fixed page size of 100. Only the `org`/`personal` rows are
|
|
1155
|
+
* installable — `global` entries list but answer 404 from `putAgentSkill`.
|
|
1156
|
+
*/
|
|
1157
|
+
listSkills(opts?: {
|
|
1158
|
+
scope?: 'org' | 'personal' | 'global' | string;
|
|
1159
|
+
q?: string;
|
|
1160
|
+
page?: number;
|
|
1161
|
+
}): Promise<SkillRecord[]>;
|
|
1162
|
+
/** 204. No in-use guard for org/personal skills: agents holding it just lose it. */
|
|
1163
|
+
deleteSkill(skillId: string): Promise<void>;
|
|
1164
|
+
createSession(agentId: string, input: {
|
|
1165
|
+
initial_events?: OutboundEvent[];
|
|
1166
|
+
metadata?: Record<string, unknown>;
|
|
1167
|
+
}, idempotencyKey?: string): Promise<SessionRecord>;
|
|
1168
|
+
getSession(agentId: string, sessionId: string, opts?: {
|
|
1169
|
+
history?: boolean;
|
|
1170
|
+
limit?: number;
|
|
1171
|
+
}): Promise<SessionRecord>;
|
|
1172
|
+
/** Newest first by `updated_at`, 50 per page, `page` is 1-based. Single page per call — there is no cursor. */
|
|
1173
|
+
listSessions(agentId: string, opts?: {
|
|
1174
|
+
page?: number;
|
|
1175
|
+
}): Promise<SessionRecord[]>;
|
|
1176
|
+
/**
|
|
1177
|
+
* Stamp `archived_at`. Afterwards writes are `409 session_archived` while reads keep working.
|
|
1178
|
+
* Interrupt an in-flight run first, or the archive races it.
|
|
1179
|
+
*/
|
|
1180
|
+
archiveSession(agentId: string, sessionId: string): Promise<{
|
|
1181
|
+
session_id?: string;
|
|
1182
|
+
archived: boolean;
|
|
1183
|
+
}>;
|
|
1184
|
+
/** Soft delete (204). An in-flight run is cancelled first; transcripts and events survive for audit. */
|
|
1185
|
+
deleteSession(agentId: string, sessionId: string): Promise<void>;
|
|
1186
|
+
/**
|
|
1187
|
+
* 202; `user.interrupt` with no in-flight run returns `accepted:false` — not an error.
|
|
1188
|
+
* Accepted events come back as full event objects (with `seq`) where the server supports the
|
|
1189
|
+
* unified history; give each event an `idempotency_key` to make timeout retries safe.
|
|
1190
|
+
*/
|
|
1191
|
+
postEvents(agentId: string, sessionId: string, events: OutboundEvent[]): Promise<{
|
|
1192
|
+
events: PostEventReceipt[];
|
|
1193
|
+
}>;
|
|
1194
|
+
/**
|
|
1195
|
+
* ONE page of durable events — the unified history, which includes your own inputs
|
|
1196
|
+
* (`user.message`, …) alongside engine events. `limit` defaults to 100 and is capped at 500;
|
|
1197
|
+
* the page's pagination fields are dropped, so use {@link ZooworkClient.listAllEvents}, or
|
|
1198
|
+
* {@link ZooworkClient.listEventsPage} to page by hand.
|
|
1199
|
+
* Passing `after` selects the deprecated engine-only lane — old cursors only.
|
|
1200
|
+
*/
|
|
1201
|
+
listEvents(agentId: string, sessionId: string, opts?: {
|
|
1202
|
+
after?: number;
|
|
1203
|
+
cursor?: string;
|
|
1204
|
+
types?: string[];
|
|
1205
|
+
limit?: number;
|
|
1206
|
+
}): Promise<SessionEvent[]>;
|
|
1207
|
+
/**
|
|
1208
|
+
* The page-returning primitive under `listEvents`: same options, plus the page's `hasMore`
|
|
1209
|
+
* and `nextCursor` (absent on servers without cursor pagination). Feed `nextCursor` back as
|
|
1210
|
+
* `cursor` to page by hand.
|
|
1211
|
+
*/
|
|
1212
|
+
listEventsPage(agentId: string, sessionId: string, opts?: {
|
|
1213
|
+
after?: number;
|
|
1214
|
+
cursor?: string;
|
|
1215
|
+
types?: string[];
|
|
1216
|
+
limit?: number;
|
|
1217
|
+
}): Promise<SessionEventPage>;
|
|
1218
|
+
/**
|
|
1219
|
+
* Every durable event: follows the server's `next_cursor` until `has_more` is false, and
|
|
1220
|
+
* falls back to walking `after` against servers without cursor pagination (that walk stops
|
|
1221
|
+
* when a page fails to advance, so a server that ignored `after` cannot spin it forever).
|
|
1222
|
+
*
|
|
1223
|
+
* `pageSize` is the per-request `limit` (default and maximum 500). Events come back in
|
|
1224
|
+
* ascending `seq`, deduplicated across page boundaries. Passing `after` forces the
|
|
1225
|
+
* deprecated engine-only lane.
|
|
1226
|
+
*/
|
|
1227
|
+
listAllEvents(agentId: string, sessionId: string, opts?: {
|
|
1228
|
+
after?: number;
|
|
1229
|
+
types?: string[];
|
|
1230
|
+
pageSize?: number;
|
|
1231
|
+
}): Promise<SessionEvent[]>;
|
|
1232
|
+
/**
|
|
1233
|
+
* Durable event stream with server-side resume.
|
|
1234
|
+
*
|
|
1235
|
+
* The stream is SESSION-scoped and unbounded: it does NOT close when a turn ends, and the
|
|
1236
|
+
* server closes it on idle. Detect turn end with `isRunFinished`, and resume by passing the
|
|
1237
|
+
* last event's `cursor` (its `after` fallback selects the deprecated engine-only lane).
|
|
1238
|
+
* `chat.delta` preview frames are skipped — they are snapshot-replace frames on a separate
|
|
1239
|
+
* Redis-only lane, not durable events.
|
|
1240
|
+
*/
|
|
1241
|
+
streamEvents(agentId: string, sessionId: string, opts?: {
|
|
1242
|
+
after?: number;
|
|
1243
|
+
cursor?: string;
|
|
1244
|
+
signal?: AbortSignal;
|
|
1245
|
+
}): AsyncGenerator<SessionEvent>;
|
|
1246
|
+
/**
|
|
1247
|
+
* Tool calls parked on a human decision. `status` may ONLY be omitted or `'pending'` —
|
|
1248
|
+
* staging-verified 2026-08-07; any other value is rejected, so there is no way to list
|
|
1249
|
+
* resolved ones.
|
|
1250
|
+
*
|
|
1251
|
+
* Approvals are a REST resource here, NOT the `user.tool_confirmation` event loop; the two
|
|
1252
|
+
* shapes describe the same act and do not line up. Without a Temporal signaler the route
|
|
1253
|
+
* answers `501 not_configured`. We have never produced a real pending approval, so the
|
|
1254
|
+
* round trip is unproven: treat human-in-the-loop as unavailable, and note that a run parked
|
|
1255
|
+
* on an approval burns its whole turn budget waiting.
|
|
1256
|
+
*/
|
|
1257
|
+
listApprovals(agentId: string, opts?: {
|
|
1258
|
+
status?: 'pending';
|
|
1259
|
+
}): Promise<ApprovalRecord[]>;
|
|
1260
|
+
/** Resolve one approval. `decision` is exactly one of allow-once / allow-always / deny. */
|
|
1261
|
+
resolveApproval(agentId: string, approvalId: string, input: {
|
|
1262
|
+
decision: ApprovalDecision;
|
|
1263
|
+
resolvedBy?: string;
|
|
1264
|
+
}): Promise<Record<string, unknown>>;
|
|
1265
|
+
/**
|
|
1266
|
+
* Artifacts this agent published, one page per call — but unlike `listEvents`, the page SAYS
|
|
1267
|
+
* when it truncated: read `has_more`. `limit` defaults to 50, capped at 100; filter by
|
|
1268
|
+
* `sessionId`, `sourcePath`, or `createdBefore` (ISO timestamp). Empty list staging-verified
|
|
1269
|
+
* 2026-08-14; a populated one needs a turn that called `artifact_publish`.
|
|
1270
|
+
*/
|
|
1271
|
+
listArtifacts(agentId: string, opts?: {
|
|
1272
|
+
page?: number;
|
|
1273
|
+
limit?: number;
|
|
1274
|
+
sessionId?: string;
|
|
1275
|
+
sourcePath?: string;
|
|
1276
|
+
createdBefore?: string;
|
|
1277
|
+
}): Promise<ArtifactPage>;
|
|
1278
|
+
/** One artifact row. A foreign or unknown id is 404 (hidden, not 403). */
|
|
1279
|
+
getArtifact(agentId: string, artifactId: string): Promise<ArtifactRecord>;
|
|
1280
|
+
/**
|
|
1281
|
+
* Mint a fresh access URL for a `ready` artifact: `{artifact_id, url}`. A row that never
|
|
1282
|
+
* finalized is `409 artifact_not_ready`. The colon in `:download` goes RAW on the wire —
|
|
1283
|
+
* this family matches the literal colon, unlike the environments family's `%3A`.
|
|
1284
|
+
*/
|
|
1285
|
+
downloadArtifact(agentId: string, artifactId: string): Promise<{
|
|
1286
|
+
artifact_id?: string;
|
|
1287
|
+
url?: string;
|
|
1288
|
+
}>;
|
|
1289
|
+
/**
|
|
1290
|
+
* Delete one artifact; the 200 body is the row as the engine leaves it. Not yet exercised
|
|
1291
|
+
* against staging — doing so needs an artifact a real turn published first.
|
|
1292
|
+
*/
|
|
1293
|
+
deleteArtifact(agentId: string, artifactId: string): Promise<ArtifactRecord>;
|
|
1294
|
+
listSchedules(agentId: string): Promise<ScheduleRecord[]>;
|
|
1295
|
+
/**
|
|
1296
|
+
* 201 with a create receipt carrying only `schedule_name`
|
|
1297
|
+
* (`cron/{computer_id}/{agent_id}/{schedule_id}`) — not the definition. Read it back with
|
|
1298
|
+
* `getSchedule` if you need the stored shape.
|
|
1299
|
+
*
|
|
1300
|
+
* Schedules outlive their agent: `stopAgent`/`deleteAgent` do not remove them. List and
|
|
1301
|
+
* delete them yourself before deleting an agent. Re-creating an existing `schedule_id` with a
|
|
1302
|
+
* different definition is a 409; an identical retry is accepted.
|
|
1303
|
+
*/
|
|
1304
|
+
createSchedule(agentId: string, input: ScheduleInput, idempotencyKey?: string): Promise<ScheduleRecord>;
|
|
1305
|
+
/** Note the camelCase body (`scheduleId`/`computerId`/`agentId`) — create answered in snake_case. */
|
|
1306
|
+
getSchedule(agentId: string, scheduleId: string): Promise<ScheduleRecord>;
|
|
1307
|
+
/**
|
|
1308
|
+
* Update the definition.
|
|
1309
|
+
*
|
|
1310
|
+
* A `getSchedule()` result is NOT a legal PUT body, and this is the method where that costs
|
|
1311
|
+
* you. Six of its fields are refused or ignored on the way back in:
|
|
1312
|
+
* `execution` / `originMetadata` / `contextSnapshot` / `creatorPrincipalRef` are
|
|
1313
|
+
* `400 execution, originMetadata, creatorPrincipalRef, and contextSnapshot are server-derived`,
|
|
1314
|
+
* `sessionTarget` is `400 sessionTarget is immutable`, and `scheduleSpec` — the only place a
|
|
1315
|
+
* read puts the cadence — is accepted and then SILENTLY IGNORED. The type refuses all six at
|
|
1316
|
+
* compile time and the SDK strips them before sending, so the obvious JavaScript round trip
|
|
1317
|
+
* (read, tweak, write) both succeeds and preserves the schedule. Staging-verified 2026-08-07.
|
|
1318
|
+
*
|
|
1319
|
+
* TO CHANGE THE CADENCE, send `schedule: { kind: 'cron', expr, tz }` — the INPUT vocabulary.
|
|
1320
|
+
* Echoing back the `scheduleSpec` you just read leaves the old expression in place while every
|
|
1321
|
+
* other field in the same body applies. That is the nastiest failure on this route, because it
|
|
1322
|
+
* answers 200.
|
|
1323
|
+
*
|
|
1324
|
+
* PUT and DELETE carry no cross-timeout idempotency guarantee; after a timeout, reconcile by
|
|
1325
|
+
* listing and reading runs rather than blind-retrying.
|
|
1326
|
+
*/
|
|
1327
|
+
updateSchedule(agentId: string, scheduleId: string, update: ScheduleUpdate): Promise<ScheduleRecord>;
|
|
1328
|
+
deleteSchedule(agentId: string, scheduleId: string): Promise<void>;
|
|
1329
|
+
/** Fire it once, now, out of band. Does not disturb the cadence. */
|
|
1330
|
+
triggerSchedule(agentId: string, scheduleId: string): Promise<{
|
|
1331
|
+
schedule_name?: string;
|
|
1332
|
+
triggered: boolean;
|
|
1333
|
+
}>;
|
|
1334
|
+
/** Past fires, newest first. `limit` defaults to 20 and is capped at 100. */
|
|
1335
|
+
listScheduleRuns(agentId: string, scheduleId: string, opts?: {
|
|
1336
|
+
limit?: number;
|
|
1337
|
+
}): Promise<ScheduleRun[]>;
|
|
1338
|
+
/**
|
|
1339
|
+
* Push a reminder into the agent's heartbeat queue.
|
|
1340
|
+
*
|
|
1341
|
+
* `next-heartbeat` (the default) only writes the pending row — it needs no Temporal client,
|
|
1342
|
+
* but nothing consumes it unless the agent has a heartbeat configured. `now` writes the row
|
|
1343
|
+
* AND kicks the heartbeat schedule, and is `409` when no heartbeat is enabled; if the
|
|
1344
|
+
* heartbeat is already busy the kick is skipped (overlap SKIP) and the row waits for the next
|
|
1345
|
+
* one. `deliverToUser: false` keeps the reminder internal to the agent's own reasoning.
|
|
1346
|
+
*/
|
|
1347
|
+
wake(agentId: string, input: {
|
|
1348
|
+
text: string;
|
|
1349
|
+
mode?: 'now' | 'next-heartbeat';
|
|
1350
|
+
deliverToUser?: boolean;
|
|
1351
|
+
}): Promise<WakeResult>;
|
|
1352
|
+
/**
|
|
1353
|
+
* Run a command in the agent's sandbox. `args` is argv, not a shell string — use
|
|
1354
|
+
* `['bash', '-lc', 'pwd']` for shell semantics.
|
|
1355
|
+
*
|
|
1356
|
+
* A NON-ZERO EXIT IS STILL HTTP 200. This promise resolves; check `exit_code`. It does not
|
|
1357
|
+
* reject on a failed command, only on a failed call.
|
|
1358
|
+
*
|
|
1359
|
+
* cwd is fixed to `/workspace`. Requires an agent-scope sandbox and a rendered config:
|
|
1360
|
+
* a session-scope agent is `409 exec_requires_agent_scope`, an unrendered one is
|
|
1361
|
+
* `409 exec_config_not_ready`, and a deployment with no sandbox backend is
|
|
1362
|
+
* `501 not_configured`. Default timeout 300s; stdout and stderr are each capped at 200,000
|
|
1363
|
+
* characters. This is an operations side door — it bypasses nothing, because it is not the
|
|
1364
|
+
* agent's tool path.
|
|
1365
|
+
*/
|
|
1366
|
+
exec(agentId: string, args: string[]): Promise<ExecResult>;
|
|
1367
|
+
/**
|
|
1368
|
+
* Environments visible to your org, 1-based `page`. Note that the platform DEFAULT
|
|
1369
|
+
* Environment — the one a fresh agent is pinned to — is not in here and is not fetchable: the
|
|
1370
|
+
* gateway forces an org selector and the default belongs to no org.
|
|
1371
|
+
*/
|
|
1372
|
+
listEnvironments(opts?: {
|
|
1373
|
+
page?: number;
|
|
1374
|
+
}): Promise<EnvironmentRecord[]>;
|
|
1375
|
+
/** 404 for any Environment outside your org, including the platform default. */
|
|
1376
|
+
getEnvironment(environmentId: string): Promise<EnvironmentRecord>;
|
|
1377
|
+
/**
|
|
1378
|
+
* Create an Environment (and its first version). Give it a stable `idempotencyKey`.
|
|
1379
|
+
*
|
|
1380
|
+
* `resource.config` takes exactly four keys — packages / files / build / networking — and
|
|
1381
|
+
* anything else is `400 invalid_environment_config`. Building is asynchronous: poll
|
|
1382
|
+
* `getEnvironmentVersion` until `status === 'ready'` before pinning it on an agent, or the
|
|
1383
|
+
* create answers `409 environment_not_ready`. The field is `status` — there is no `state` on a
|
|
1384
|
+
* version, and a loop written against one never terminates.
|
|
1385
|
+
*/
|
|
1386
|
+
createEnvironment(input: {
|
|
1387
|
+
resource: EnvironmentResource;
|
|
1388
|
+
ownership: Ownership;
|
|
1389
|
+
}, idempotencyKey?: string): Promise<EnvironmentRecord>;
|
|
1390
|
+
/**
|
|
1391
|
+
* Archive an Environment.
|
|
1392
|
+
*
|
|
1393
|
+
* THE COLON MUST BE PERCENT-ENCODED. The route is `POST /environments/{id}:archive`, and a
|
|
1394
|
+
* raw `:` makes the engine miss the route and answer 404 — verified twice on 2026-08-07. The
|
|
1395
|
+
* SDK sends `%3A` for you; this is the whole reason this method exists rather than you
|
|
1396
|
+
* building the path.
|
|
1397
|
+
*/
|
|
1398
|
+
archiveEnvironment(environmentId: string): Promise<EnvironmentRecord>;
|
|
1399
|
+
/**
|
|
1400
|
+
* Add an immutable version to an existing Environment. Versions never mutate: a retry after a
|
|
1401
|
+
* failed build retries THAT version and keeps its attempt log.
|
|
1402
|
+
*
|
|
1403
|
+
* The route is reachable, but the request body was not exercised against staging on
|
|
1404
|
+
* 2026-08-07 — the SDK sends `{ resource: { config } }`, mirroring create.
|
|
1405
|
+
*/
|
|
1406
|
+
createEnvironmentVersion(environmentId: string, config: EnvironmentConfig, idempotencyKey?: string): Promise<EnvironmentVersionRecord>;
|
|
1407
|
+
/** Poll this — not the Environment's top-level state — to decide whether a version is usable. */
|
|
1408
|
+
getEnvironmentVersion(environmentId: string, version: number): Promise<EnvironmentVersionRecord>;
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Create a client.
|
|
1412
|
+
*
|
|
1413
|
+
* ```ts
|
|
1414
|
+
* const zc = createZooworkClient({ apiKey: 'zct_...' })
|
|
1415
|
+
* const zc = createZooworkClient() // reads ZOOWORK_API_KEY
|
|
1416
|
+
* ```
|
|
1417
|
+
*
|
|
1418
|
+
* Resolution order for both settings is the same: explicit argument, then environment
|
|
1419
|
+
* variable, then (for `baseUrl` only) the built-in default.
|
|
1420
|
+
*
|
|
1421
|
+
* @throws if no API key can be resolved — a missing key is a setup mistake worth failing
|
|
1422
|
+
* loudly at construction rather than as a 401 on the first call.
|
|
1423
|
+
*/
|
|
1424
|
+
export declare function createZooworkClient(cfg?: ZooworkConfig): ZooworkClient;
|