@zoowork-ai/sdk 0.8.0 → 0.10.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 CHANGED
@@ -3,6 +3,59 @@
3
3
  All notable changes to `@zoowork-ai/sdk` (formerly `@zooclaw-agents/sdk`). Dates are the
4
4
  day the behaviour was verified, not the day it was written.
5
5
 
6
+ ## 0.10.0 — 2026-10-03
7
+
8
+ - Add Files, read-only Agent Database, Usage, Run Output, action paging/detail and Agent webhook management helpers.
9
+ - Add typed MCP confirmation, active Session creation and configuration concurrency metadata.
10
+ - Align onboarding and capability examples with Platform Project keys.
11
+
12
+ ### Known service and documentation limitations
13
+
14
+ Verified against production on 2026-10-03; these are not resolved by this SDK release:
15
+
16
+ - Workspace Files read/write APIs return HTTP 502 with both Platform and legacy keys ([ecap #4006](https://github.com/SerendipityOneInc/ecap-workspace/issues/4006)).
17
+ - Legacy keys cannot update or remove visible global Skill assignments on their own Agents; Platform keys succeed ([ecap #4007](https://github.com/SerendipityOneInc/ecap-workspace/issues/4007)).
18
+ - Published Authentication URLs redirect to Quickstart, including the AI Markdown entry ([docs #56](https://github.com/SerendipityOneInc/zoowork-agents-docs/issues/56)).
19
+ - Agent Database reads depend on deployment support. The production viewer was disabled, so its successful read path has not been validated.
20
+
21
+ ## 0.9.0 — 2026-10-01
22
+
23
+ ### Added
24
+
25
+ - **Webhook receiving.** `verifyWebhookSignature` and `unwrapWebhook` check a delivery's Standard
26
+ Webhooks signature over the raw body and hand back the envelope; `signWebhook` is exported so a
27
+ receiver can build a delivery for its own tests. The HMAC is WebCrypto, so the zero-runtime-
28
+ dependency guarantee holds and the functions are `async`. Headers can come from a fetch
29
+ `Headers`, a record in any casing, or Node's `IncomingHttpHeaders`. Failures throw
30
+ `ZooworkWebhookError` with a machine-readable `code` and a message that carries no secret,
31
+ signature or body.
32
+ - **Rotation without a deploy.** `ZOOWORK_WEBHOOK_SECRET` accepts several secrets separated by
33
+ whitespace or commas, and a delivery signed under any of them verifies — the sender double-signs
34
+ for the length of the window. The Python SDK reads the variable identically, so one deployment's
35
+ configuration serves both. A `secret` passed explicitly is never split.
36
+ - **The webhook event vocabulary.** `WEBHOOK_EVENT_TYPES` lists the sixteen types Engine
37
+ delivers today, with a `data` shape per type behind `WebhookEventFor<T>` and
38
+ `knownWebhookEvent()`. `WEBHOOK_SCHEDULE_CONFIG_EVENT_TYPES` declares the five schedule
39
+ configuration events for schedule configuration changes. A type the release does not know
40
+ passes through with its raw `type` and `data` instead of throwing, so a receiver stays
41
+ forward compatible.
42
+ - Fixed cross-language signing vectors in `src/__vectors__/webhook-vectors.json`, copied verbatim
43
+ from Engine's `packages/webhook-signing` and asserted three ways (an independent WebCrypto
44
+ computation, the recorded JSON, and this SDK). They are the compatibility contract with the
45
+ official `standardwebhooks` libraries; adding a vector is fine, editing one is not.
46
+
47
+ ### Fixed
48
+
49
+ - `streamEvents()` cancels and releases its response reader when a consumer exits early,
50
+ including a loop that stops at `run.finished`. This prevents an open SSE connection from
51
+ keeping a Node.js consumer alive after its turn has completed.
52
+
53
+ ### Changed
54
+
55
+ - Local E2E checks accept Platform Project keys. Production checks require an explicit URL
56
+ and confirmation, and record their scope separately from staging checks.
57
+ - Live smoke tests select only selectable catalog models.
58
+
6
59
  ## 0.8.0 — 2026-09-20
7
60
 
8
61
  ### Added
@@ -179,8 +232,7 @@ No runtime change — comments only.
179
232
  - `createZooclawClient` → `createZooworkClient`; `ZooclawClient`, `ZooclawError`,
180
233
  `ZooclawAuth`, `ZooclawConfig` → `Zoowork*`.
181
234
  - `ZOOCLAW_API_KEY` / `ZOOCLAW_BASE_URL` → `ZOOWORK_API_KEY` / `ZOOWORK_BASE_URL`.
182
- - Server-side identifiers are unchanged: API keys still start with `zct_`, and skill or
183
- environment names the API returns (e.g. `zooclaw-tts`) are whatever the server says.
235
+ - Server-side identifiers and resource names remain opaque.
184
236
 
185
237
  ## 0.3.4 — 2026-08-25
186
238
 
package/README.md CHANGED
@@ -10,7 +10,7 @@ npm install @zoowork-ai/sdk
10
10
 
11
11
  ## Quickstart
12
12
 
13
- You need an API key (`zct_...`) issued for your organization — create one in the ZooWork App under **Settings → API Keys** (any personal org; enterprise orgs need the admin role), or ask your org admin for one. The secret is shown exactly once at creation. Keep it server-side: it authenticates as your whole organization, not as one end user.
13
+ Create a Project API key in [ZooWork Platform](https://platform.zoowork.ai). The secret is shown once. Save it as `ZOOWORK_API_KEY` on your server. Agent and Session access is scoped to the key's Project. Initialize Organization billing and bind owner credentials; sign in and rebind when the API requests it.
14
14
 
15
15
  ```ts
16
16
  import { createZooworkClient } from '@zoowork-ai/sdk'
@@ -66,12 +66,6 @@ separately.
66
66
 
67
67
  An explicit option always beats the environment variable.
68
68
 
69
- > **Finding the agent you built in the app.** The first path segment of a ZooWork chat URL
70
- > (`/chat/<32-hex>/sessions/…`) is a *workspace* id, not an `agt_…`. Resolve it with
71
- > `zc.listAgents({ labels: { workspace_id: '<32-hex>' } })`; use the pagination patterns
72
- > below to read one page or traverse every match. Scope is `owner_uid AND org_id` — an agent a *colleague*
73
- > created in your org is fetchable by id but will not appear in your list.
74
-
75
69
  > **Wait on `status.desired_state`, never on `status.actual_state`.**
76
70
  > `actual_state` reports chat-channel connectivity. An API-only agent has no channels,
77
71
  > but its projection depends on the channel-status capability: a GET can report `active`
@@ -153,30 +147,9 @@ must authenticate the user and authorize the session; `actor.ref` does neither.
153
147
  isolate the shared agent's sandbox files or erase a session's previous context. Omit `actor`
154
148
  to use the owner; IM sessions reject it. See the field's SDK comment for input constraints.
155
149
 
156
- ## Bring your own skill
157
-
158
- A skill is a zip. One upload creates the skill *and* its first version; `putAgentSkill` attaches it.
159
-
160
- ```ts
161
- import { readFile } from 'node:fs/promises'
162
-
163
- const skill = await zc.uploadSkill(await readFile('market-research.zip'), { scope: 'org' })
164
- await zc.putAgentSkill(agent.agent_id, skill.skill_id)
165
- ```
166
-
167
- The zip's single top-level directory must be named exactly like the `name` in its `SKILL.md`
168
- frontmatter — `market-research/SKILL.md` declaring `name: market-research`. A mismatch is a 400,
169
- and it is the first one nearly everyone gets. `scope` is `org` or `personal`; the preinstalled
170
- `global` skills are listable but not installable with an API key, so this is the only way to
171
- control what a skill says. `uploadSkillVersion` publishes an update, and agents that installed it
172
- unpinned follow along without another `putAgentSkill`.
150
+ ## Agent Skills
173
151
 
174
- `uploadSkillVersion` returns a `SkillVersionRecord` with `version` and `state`, not the
175
- `latest_version` and `status` of the `SkillRecord` returned by `uploadSkill`. This return
176
- contract is source-reviewed, not a new live recording. On initial create, put the description
177
- in the zip's frontmatter: the gateway drops the `description` option. Version uploads can
178
- use that override. A successful create retried under the same name can return `409 skill_exists`;
179
- read back first. Version uploads deduplicate identical content for the same skill, not HTTP keys.
152
+ New Agents receive global Skills by default. Use `listAgentSkills(agentId)` to inspect attached Skills. At create time, `resource.skills` accepts a catalog name or `skill_id`; `include_global_skills: false` or an explicit empty list opts out of automatic Skills. Root Skill uploads are unavailable to Project keys. Keep source changes in your application or the Agent workspace through the Files API.
180
153
 
181
154
  ## Schedules, wake and exec
182
155
 
@@ -247,20 +220,7 @@ Approval response fields are source-reviewed, not end-to-end verified: read `req
247
220
  `allowed_decisions` and optional timeout/resolution fields defensively. `signaled: true` means
248
221
  the resolution was accepted; a returned `status: 'pending'` is not completed execution.
249
222
 
250
- `listEnvironments`, `getEnvironment`, `createEnvironment`, `createEnvironmentVersion`,
251
- `getEnvironmentVersion` and `archiveEnvironment` manage prebuilt sandbox images (apt/npm/pip
252
- packages, files, a build script, and an outbound allowlist). Two facts worth having before you
253
- start: an agent's Environment **freezes on its first sandbox creation** — after that every change
254
- is `409 environment_locked`, and stopping the agent does not clear it — and sandbox networking
255
- defaults to unrestricted unless the Environment declares `networking: { type: 'limited' }`.
256
-
257
- Build polling must have a deadline and handle `partial_ready`: some resource classes can be
258
- ready while others are building or failed. `getEnvironmentVersion(id, version, { resourceClass:
259
- 'starter' })` selects one class; omitting the option keeps the aggregate read. Re-read the
260
- aggregate before concluding the build is fully ready. These details are source-reviewed.
261
-
262
- Channel callers must not use `allow_from` as an access-control list: the public gateway ignores
263
- it. Use supported `dm_policy` settings.
223
+ Platform uses its managed Environment. Root Environment administration and Channel binding return `404 service_api.not_found` for Project keys. An exported method does not expand a key's permissions.
264
224
 
265
225
  ## Artifacts and the system prompt
266
226
 
@@ -285,6 +245,106 @@ activation on its own — moving it is one explicit call, `upgradeSystemPrompt`,
285
245
  the agent's current `config_version` as a CAS (`409 config_version_changed` on a stale one)
286
246
  and answers the new pin plus the version bump it cost.
287
247
 
248
+ ## Receiving webhooks
249
+
250
+ Verification needs the **raw request bytes**. `express.json()` and most framework body parsers
251
+ consume them, and a `JSON.parse` → `JSON.stringify` round trip does not reproduce them byte for
252
+ byte, so read the body before anything else touches it. The example requires your
253
+ application to supply `acceptOnce`, an atomic durable insert plus work-item enqueue, before it
254
+ returns 204. A worker processes that item separately:
255
+
256
+ ```ts
257
+ import { createServer } from 'node:http'
258
+ import { knownWebhookEvent, unwrapWebhook, ZooworkWebhookError } from '@zoowork-ai/sdk'
259
+
260
+ createServer(async (req, res) => {
261
+ const chunks: Buffer[] = []
262
+ for await (const chunk of req) chunks.push(chunk as Buffer)
263
+ const rawBody = Buffer.concat(chunks)
264
+
265
+ let event
266
+ try {
267
+ // Defaults to ZOOWORK_WEBHOOK_SECRET, or pass `secret: [newest, previous]` explicitly.
268
+ event = await unwrapWebhook({ headers: req.headers, rawBody })
269
+ } catch (error) {
270
+ if (error instanceof ZooworkWebhookError) {
271
+ console.warn('rejected webhook', error.code) // never carries the secret, signature or body
272
+ res.writeHead(400).end()
273
+ return
274
+ }
275
+ throw error
276
+ }
277
+
278
+ // Validate identity, then atomically persist the event and a durable work item.
279
+ // Implement acceptOnce with a unique webhook-id; a duplicate is successful too.
280
+ if (event.id !== req.headers['webhook-id']) { res.writeHead(400).end(); return }
281
+ try { await acceptOnce(event.id, event) } catch { res.writeHead(503).end(); return }
282
+ res.writeHead(204).end()
283
+
284
+ // Dispatch this switch in the durable worker.
285
+ const known = knownWebhookEvent(event)
286
+ if (!known) return // keep unknown events accepted without side effects
287
+ switch (known.type) {
288
+ case 'run.finished':
289
+ console.log(known.data.run_id, known.data.status)
290
+ break
291
+ case 'approval.requested':
292
+ console.log(known.data.approval_id, known.data.tool_name)
293
+ break
294
+ }
295
+ }).listen(3000)
296
+ ```
297
+
298
+ `verifyWebhookSignature` is the same check without the parse, for a handler that wants the bytes.
299
+ Both are `async`: the HMAC is WebCrypto (`crypto.subtle`), which is how this SDK verifies
300
+ signatures with no runtime dependency and still runs in Workers, Deno and the browser.
301
+
302
+ ### Rotating the secret without a deploy
303
+
304
+ `ZOOWORK_WEBHOOK_SECRET` accepts **several secrets**, separated by whitespace or commas. During a
305
+ rotation window the sender signs under every active key, so a receiver that lists both accepts
306
+ whichever one signed a given delivery:
307
+
308
+ ```sh
309
+ ZOOWORK_WEBHOOK_SECRET="whsec_<new> whsec_<previous>" # commas work too
310
+ ```
311
+
312
+ Splitting is unambiguous because a secret is `whsec_` plus standard base64 of 32 bytes — no comma,
313
+ no whitespace. The Python SDK reads the variable the same way, so one deployment's configuration
314
+ serves both. A `secret` you pass explicitly is never split: a string is one secret, and several go
315
+ in an array.
316
+
317
+ Four more things worth knowing:
318
+
319
+ - **The clock window is checked against the `webhook-timestamp` header, not the envelope's
320
+ `created_at`.** A retry of an old event carries a fresh timestamp and verifies; `created_at`
321
+ still says when the fact happened. The default tolerance is ±300s.
322
+ - **A body over 16 KiB is refused before it is hashed** — the sender's own envelope ceiling.
323
+ - **Unknown event types are normal.** New types ship within a schema version, and a receiver that
324
+ fails one only makes the sender retry it and then dead-letter it. Acknowledge and ignore.
325
+ - **A repeated `webhook-id`, `webhook-timestamp` or `webhook-signature` header is rejected**, not
326
+ resolved to one of its values: choosing would be a guess about which send arrived. The Python
327
+ SDK rejects it too.
328
+ - **`unwrapWebhook` checks six fields and no more** — `object`, `id`, `type`, `schema_version`,
329
+ `created_at`, `data` — so a field or event type a later API release adds is not a reason to
330
+ drop a delivery. `schema_version` must be a number with an **integer value**: `1` and `1.0` both
331
+ pass, since JSON has one numeric type and they are the same number, while `1.5` is rejected with
332
+ `invalid_payload`. The Python SDK reaches the same verdict on the same envelope.
333
+
334
+ `ZooworkWebhookError.code` is the contract to match on — `invalid_secret`, `body_too_large`,
335
+ `missing_header`, `invalid_header`, `timestamp_out_of_window`, `signature_mismatch`,
336
+ `invalid_payload` — and those strings are identical in the Python SDK. The class shape is not: here
337
+ it extends `Error`, while the Python SDK makes it a subclass of its own `ZooworkError` carrying a
338
+ 400. Match on `code`, and do not port `instanceof` checks between the two.
339
+
340
+ You do not have to use this SDK to verify. ZooWork emits plain
341
+ [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks), so the official
342
+ library for any language works with the same `whsec_` secret and the same raw body — npm
343
+ [`standardwebhooks`](https://www.npmjs.com/package/standardwebhooks), PyPI
344
+ [`standardwebhooks`](https://pypi.org/project/standardwebhooks/), or another of the ports. The
345
+ fixed vectors in [`src/__vectors__/webhook-vectors.json`](src/__vectors__/webhook-vectors.json)
346
+ are copied verbatim from Engine and are byte-compatible with all of them.
347
+
288
348
  ## Two helpers
289
349
 
290
350
  ```ts
@@ -342,3 +402,39 @@ before creating the GitHub Release when live verification is required. Use
342
402
  ## License
343
403
 
344
404
  MIT
405
+
406
+ ## Developer API helpers
407
+
408
+ These helpers require an SDK release that includes them. Check the installed declarations before using an example; use the documented HTTP endpoint if the installed release lacks the method.
409
+
410
+ ```ts
411
+ const files = await zc.getWorkspaceFile(agentId, '/workspace')
412
+ await zc.writeWorkspaceFile(agentId, '/workspace/input.txt', 'hello')
413
+ const bytes = await zc.getWorkspaceFileContent(agentId, '/workspace/result.bin')
414
+ const database = await zc.getAgentDatabase(agentId) // reading never provisions it
415
+ const rows = await zc.getAgentDatabaseRows(agentId, 'results', { limit: 100, offset: 0 })
416
+ const usage = await zc.getUsage({ range: '7d', view: 'both' })
417
+ const endpoint = await zc.createAgentWebhook(agentId, {
418
+ url: 'https://receiver.example/webhook', event_types: ['run.finished'],
419
+ }, 'register-hook-v1')
420
+ // Save signing_secret securely when present; an idempotent replay can return null.
421
+ const hooks = await zc.listAgentWebhooks(agentId) // hooks.webhooks
422
+ const output = await zc.getRunOutput(agentId, sessionId, runId)
423
+ const approvals = await zc.listApprovalPage(agentId, { sessionId })
424
+ ```
425
+
426
+ File reads derive `owner_uid` and `org_id` from the Agent projection. Raw file content is
427
+ `Uint8Array`; text writes are not binary uploads. Database inspection is read-only. Usage stays
428
+ within the current key scope. Page helpers retain `next_cursor` and `has_more`; replay cursors
429
+ verbatim. `getApproval` and `getCustomToolCall` read terminal as well as pending actions.
430
+
431
+ Agent webhook methods include get/update/delete, `rotateAgentWebhookSecret`, `testAgentWebhook`,
432
+ `getAgentWebhookEvent`, delivery list/detail and single/batch redelivery. Pass a stable idempotency
433
+ key to create, rotation, test and redelivery. A 202 receipt confirms queuing; query delivery
434
+ records to learn the result. SDK writes do not retry automatically.
435
+
436
+ `createSession` accepts `runtime_mode: 'active'` to pin the current active configuration at
437
+ creation. Omit it to resolve active configuration on later turns. `idle_compaction` preserves
438
+ false, null and omission. MCP tool overrides accept `requireConfirmation?: boolean`.
439
+ `updateAgent` accepts `expected_config_version`; a stale version returns
440
+ `409 active_config_changed`. Ownership-only changes do not increment the configuration version.
package/dist/client.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * ZooWork Managed Agents SDK — core client. Developer Preview.
3
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.
4
+ * Create a Project API key at https://platform.zoowork.ai. Keep it server-side: never ship it in a browser or mobile bundle.
6
5
  *
7
6
  * IDs are opaque strings and unknown response fields must be ignored — both are
8
7
  * forward-compatibility rules, not suggestions. Errors surface an `error.type`; match on
9
8
  * that, never on the message text.
10
9
  */
10
+ import { type DeveloperApi } from './developer-api.js';
11
11
  import { type SessionEvent } from './events.js';
12
12
  /**
13
13
  * The production API base URL — the default. You should not need to set this:
@@ -22,9 +22,9 @@ export type ZooworkAuth = {
22
22
  };
23
23
  export interface ZooworkConfig {
24
24
  /**
25
- * Your API key (`zct_...`). Defaults to `ZOOWORK_API_KEY`.
25
+ * Your Platform Project API key (`zwp_live_...`). Defaults to `ZOOWORK_API_KEY`.
26
26
  *
27
- * Server-side only: it authenticates as your whole organization, not as one end user.
27
+ * Server-side only: access is scoped to the selected Project. Authorize application users separately.
28
28
  */
29
29
  apiKey?: string;
30
30
  /**
@@ -115,6 +115,8 @@ export declare class ZooworkError extends Error {
115
115
  export interface Ownership {
116
116
  owner_uid: string;
117
117
  org_id: string;
118
+ project_id?: string | null;
119
+ visibility?: 'private' | 'project';
118
120
  }
119
121
  export interface ModelInfo {
120
122
  model: string;
@@ -143,6 +145,9 @@ export interface McpContextConfig {
143
145
  }
144
146
  export interface McpToolPermissionOverride {
145
147
  permission: McpToolPermission;
148
+ /** True requires permission: 'always_ask' and a decision for each call, even with a persistent grant.
149
+ * Only allow-once or deny is offered. False/omission retains permission policy behavior. */
150
+ requireConfirmation?: boolean;
146
151
  }
147
152
  /**
148
153
  * One remote MCP server, declared as `resource.mcp[]` on create or update.
@@ -298,10 +303,15 @@ export interface AgentResource {
298
303
  seed_policy?: string;
299
304
  }[];
300
305
  };
301
- skills?: {
306
+ skills?: (({
302
307
  skill_id: string;
308
+ name?: string;
309
+ } | {
310
+ name: string;
311
+ skill_id?: string;
312
+ }) & {
303
313
  version?: number | 'latest';
304
- }[];
314
+ })[];
305
315
  /**
306
316
  * Defaults to true. False disables automatic global Skills without removing explicitly
307
317
  * listed Skills. An explicit empty `skills` array also opts out.
@@ -655,6 +665,7 @@ export interface AgentRecord {
655
665
  environment_locked_at?: string | null;
656
666
  status?: AgentStatus;
657
667
  ownership?: Ownership;
668
+ sandbox_resource_class?: string;
658
669
  [k: string]: unknown;
659
670
  }
660
671
  /**
@@ -725,6 +736,10 @@ export interface SessionRecord {
725
736
  pending_approvals?: number;
726
737
  /** Pending application-executed custom-tool count on getSession. */
727
738
  pending_custom_tool_calls?: number;
739
+ pending_approval_ids?: string[];
740
+ pending_approval_ids_complete?: boolean;
741
+ pending_custom_tool_call_ids?: string[];
742
+ pending_custom_tool_call_ids_complete?: boolean;
728
743
  /**
729
744
  * `running` on a `createSession` receipt, nullable on `getSession`, and absent from
730
745
  * `listSessions` rows. This is not the run outcome; read {@link SessionRecord.run_status}
@@ -735,6 +750,8 @@ export interface SessionRecord {
735
750
  archived?: boolean;
736
751
  /** Present on filtered pages only when `includeDeleted` was requested. */
737
752
  deleted?: boolean;
753
+ idle_compaction?: boolean | null;
754
+ pinned_config_version?: number | null;
738
755
  runtime_mode?: 'active' | 'preview' | 'authoring' | 'evaluation' | string;
739
756
  config_version?: number;
740
757
  updated_at?: string;
@@ -940,12 +957,13 @@ export interface ScheduleUpdate {
940
957
  * - `scheduleId` is the FULLY-QUALIFIED name `cron/{computer_id}/{agent_id}/{schedule_id}`, not
941
958
  * the id you chose. Your id is `name`.
942
959
  *
943
- * The three responses also disagree with each other: `POST` answers with only snake_case
944
- * `schedule_name`; `GET /schedules/{id}` answers the camelCase projection below; `GET /schedules`
960
+ * Create receipts include the public `schedule_id` and snake_case `schedule_name`; `GET /schedules/{id}` answers the camelCase projection below; `GET /schedules`
945
961
  * answers the raw Temporal describe (`spec` / `state` / `memo` / `next_action_times`) with the
946
962
  * camelCase projection merged on top. Read defensively and match on what you find.
947
963
  */
948
964
  export interface ScheduleRecord {
965
+ /** Public opaque id, also present on create receipts. */
966
+ schedule_id?: string;
949
967
  /** FULLY-QUALIFIED: `cron/{computer_id}/{agent_id}/{schedule_id}`. Not the id you passed in. */
950
968
  scheduleId?: string;
951
969
  /** The `schedule_id` you chose. This is the one you pass back to get/update/delete. */
@@ -1016,6 +1034,9 @@ export interface ScheduleRecord {
1016
1034
  export interface ScheduleRun {
1017
1035
  /** Which projection this row came from. Decides which of the field groups below is populated. */
1018
1036
  source?: 'temporal' | 'run_projection' | string;
1037
+ linked_by?: string;
1038
+ run_id?: string;
1039
+ trigger?: string;
1019
1040
  /** Present only when a session can be associated with this fire. */
1020
1041
  session_id?: string;
1021
1042
  /** Un-qualified `schedule_id`. */
@@ -1043,6 +1064,9 @@ export type ApprovalDecision = 'allow-once' | 'allow-always' | 'deny';
1043
1064
  * completion of the tool or run. Verify the resulting state separately.
1044
1065
  */
1045
1066
  export interface ApprovalRecord {
1067
+ run_id?: string;
1068
+ tool_call_id?: string;
1069
+ resolution_channel?: string;
1046
1070
  approval_id?: string;
1047
1071
  session_id?: string;
1048
1072
  tool_name?: string;
@@ -1063,6 +1087,8 @@ export interface ApprovalRecord {
1063
1087
  export type CustomToolCallStatus = 'pending' | 'completed' | 'timeout' | 'cancelled' | string;
1064
1088
  /** One application-executed custom-tool call. Unknown response fields are preserved. */
1065
1089
  export interface CustomToolCallRecord {
1090
+ run_id?: string;
1091
+ resolution_channel?: string;
1066
1092
  call_id: string;
1067
1093
  session_id: string;
1068
1094
  tool_call_id: string;
@@ -1302,26 +1328,21 @@ export interface WakeResult {
1302
1328
  triggered: boolean;
1303
1329
  }
1304
1330
  export type { SessionEvent } from './events.js';
1305
- export interface ZooworkClient {
1331
+ export interface ZooworkClient extends DeveloperApi {
1306
1332
  listModels(): Promise<ModelInfo[]>;
1307
1333
  /**
1308
1334
  * Create an agent. The gateway derives ownership from your API key and always skips the
1309
1335
  * interactive onboarding interview — the agent answers your first message directly.
1310
- * `ownership` is accepted for callers that talk to the engine without the gateway;
1311
- * through the gateway it is overwritten and can be omitted.
1336
+ * Ownership is derived from the key. Omit caller-supplied ownership.
1312
1337
  */
1313
1338
  createAgent(input: {
1314
1339
  resource: AgentResource;
1315
1340
  ownership?: Ownership;
1316
1341
  }, idempotencyKey?: string): Promise<AgentRecord>;
1317
1342
  /**
1318
- * List the agents owned by your key's bound user (engine query: `owner_uid AND org_id`,
1319
- * both injected by the gateway). `labels` filters on declared labels — e.g.
1320
- * `{ labels: { workspace_id: '…' } }` resolves an app workspace id (the first path
1321
- * segment of a ZooWork chat URL) to its agent. Page size is fixed at 100 by the engine.
1322
- *
1323
- * Note the scope is `owner_uid AND org_id`: an agent a colleague created in your org is
1324
- * fetchable by `getAgent` but will not appear here.
1343
+ * List Agents within the key's Project and applicable owner visibility. Labels filter
1344
+ * declared labels, and page size is 100. Reads by id still recheck authorization;
1345
+ * a colleague's Agent is not automatically accessible.
1325
1346
  *
1326
1347
  * `await listAgents()` returns one {@link AgentPage}; read `.data` for its agents and
1327
1348
  * `.next_page` / `.hasNextPage()` for continuation. `for await (const agent of listAgents())`
@@ -1329,7 +1350,8 @@ export interface ZooworkClient {
1329
1350
  */
1330
1351
  listAgents(opts?: AgentListParams): AgentPagePromise;
1331
1352
  getAgent(agentId: string): Promise<AgentRecord>;
1332
- /** PUT declared sections; bumps config_version on EVERY call — gate on drift, don't blind-retry. */
1353
+ /** PUT declared sections. Ownership-only changes do not bump config_version.
1354
+ * Supply expected_config_version for an atomic version check; a mismatch returns 409 active_config_changed. */
1333
1355
  updateAgent(agentId: string, sections: Record<string, unknown>): Promise<AgentRecord>;
1334
1356
  /**
1335
1357
  * Soft delete: the agent stops resolving on the API, but this is not a resource purge.
@@ -1591,6 +1613,8 @@ export interface ZooworkClient {
1591
1613
  createSession(agentId: string, input: {
1592
1614
  initial_events?: OutboundEvent[];
1593
1615
  metadata?: Record<string, unknown>;
1616
+ runtime_mode?: 'active';
1617
+ idle_compaction?: boolean | null;
1594
1618
  }, idempotencyKey?: string): Promise<SessionRecord>;
1595
1619
  getSession(agentId: string, sessionId: string, opts?: {
1596
1620
  history?: boolean;
@@ -1678,6 +1702,7 @@ export interface ZooworkClient {
1678
1702
  /** Pending calls only. Any other status is rejected by the API. */
1679
1703
  listCustomToolCalls(agentId: string, opts?: {
1680
1704
  status?: 'pending';
1705
+ sessionId?: string;
1681
1706
  }): Promise<CustomToolCallRecord[]>;
1682
1707
  /**
1683
1708
  * Return one call's result. A pending call answers 202/signaled:true but stays pending until
@@ -1700,6 +1725,7 @@ export interface ZooworkClient {
1700
1725
  */
1701
1726
  listApprovals(agentId: string, opts?: {
1702
1727
  status?: 'pending';
1728
+ sessionId?: string;
1703
1729
  }): Promise<ApprovalRecord[]>;
1704
1730
  /** Resolve one approval. `decision` is exactly one of allow-once / allow-always / deny. */
1705
1731
  resolveApproval(agentId: string, approvalId: string, input: {
@@ -1857,7 +1883,7 @@ export interface ZooworkClient {
1857
1883
  * Create a client.
1858
1884
  *
1859
1885
  * ```ts
1860
- * const zc = createZooworkClient({ apiKey: 'zct_...' })
1886
+ * const zc = createZooworkClient({ apiKey: 'zwp_live_...' })
1861
1887
  * const zc = createZooworkClient() // reads ZOOWORK_API_KEY
1862
1888
  * ```
1863
1889
  *
package/dist/client.js CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * ZooWork Managed Agents SDK — core client. Developer Preview.
3
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.
4
+ * Create a Project API key at https://platform.zoowork.ai. Keep it server-side: never ship it in a browser or mobile bundle.
6
5
  *
7
6
  * IDs are opaque strings and unknown response fields must be ignored — both are
8
7
  * forward-compatibility rules, not suggestions. Errors surface an `error.type`; match on
9
8
  * that, never on the message text.
10
9
  */
10
+ import { createDeveloperApi } from './developer-api.js';
11
11
  import { parseSSE, isObj } from './sse.js';
12
12
  import { normalizeEvent } from './events.js';
13
13
  /**
@@ -143,7 +143,7 @@ function httpError(res, text) {
143
143
  * Create a client.
144
144
  *
145
145
  * ```ts
146
- * const zc = createZooworkClient({ apiKey: 'zct_...' })
146
+ * const zc = createZooworkClient({ apiKey: 'zwp_live_...' })
147
147
  * const zc = createZooworkClient() // reads ZOOWORK_API_KEY
148
148
  * ```
149
149
  *
@@ -165,7 +165,7 @@ export function createZooworkClient(cfg = {}) {
165
165
  })());
166
166
  if (!auth) {
167
167
  throw new Error('No ZooWork API key. Pass createZooworkClient({ apiKey }) or set ZOOWORK_API_KEY. ' +
168
- 'Keys look like zct_… and are issued by an organization administrator.');
168
+ 'Create a Project API key at https://platform.zoowork.ai.');
169
169
  }
170
170
  const bearer = 'serviceToken' in auth ? auth.serviceToken : auth.apiKey;
171
171
  /**
@@ -282,13 +282,20 @@ export function createZooworkClient(cfg = {}) {
282
282
  const own = projection.ownership;
283
283
  if (!own?.owner_uid || !own.org_id) {
284
284
  throw new ZooworkError(500, `agent ${agentId} projection carries no ownership — cannot derive the owner_uid/org_id ` +
285
- 'selectors the artifact routes require', 'ownership_unavailable');
285
+ 'selectors the file and artifact routes require', 'ownership_unavailable');
286
286
  }
287
287
  const sel = { owner_uid: own.owner_uid, org_id: own.org_id };
288
288
  ownershipByAgent.set(agentId, sel);
289
289
  return sel;
290
290
  };
291
+ const bytes = async (path) => {
292
+ const res = await doFetch(`${base}${path}`, { headers: { Authorization: `Bearer ${bearer}` } });
293
+ if (!res.ok)
294
+ throw httpError(res, await res.text());
295
+ return new Uint8Array(await res.arrayBuffer());
296
+ };
291
297
  const client = {
298
+ ...createDeveloperApi(json, bytes, artifactSelectors),
292
299
  listModels: async () => {
293
300
  const data = await json('/models');
294
301
  return Array.isArray(data) ? data : (data.models ?? []);
@@ -668,7 +675,7 @@ export function createZooworkClient(cfg = {}) {
668
675
  }
669
676
  },
670
677
  listCustomToolCalls: async (agentId, opts = {}) => {
671
- const data = await json(`${agents(agentId)}/custom_tool_calls${query({ status: opts.status })}`);
678
+ const data = await json(`${agents(agentId)}/custom_tool_calls${query({ status: opts.status, session_id: opts.sessionId })}`);
672
679
  return data.custom_tool_calls ?? [];
673
680
  },
674
681
  resolveCustomToolCall: (agentId, callId, input) => json(`${agents(agentId)}/custom_tool_calls/${encodeURIComponent(callId)}/result`, {
@@ -676,7 +683,7 @@ export function createZooworkClient(cfg = {}) {
676
683
  body: JSON.stringify(input),
677
684
  }),
678
685
  listApprovals: async (agentId, opts = {}) => {
679
- const data = await json(`${agents(agentId)}/approvals${query({ status: opts.status })}`);
686
+ const data = await json(`${agents(agentId)}/approvals${query({ status: opts.status, session_id: opts.sessionId })}`);
680
687
  return data.approvals ?? [];
681
688
  },
682
689
  resolveApproval: (agentId, approvalId, input) => json(`${agents(agentId)}/approvals/${encodeURIComponent(approvalId)}/resolve`, {