@zoowork-ai/sdk 0.6.0 → 0.8.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,39 @@
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.8.0 — 2026-09-20
7
+
8
+ ### Added
9
+
10
+ - Model catalog lifecycle metadata, including `selectable` and replacement hints.
11
+ - Deleted Session tombstones in filtered cursor pages through `includeDeleted`.
12
+ - Agent declaration fields for global-Skill opt-out and named user timezones.
13
+
14
+ ### Changed
15
+
16
+ - Model-selection examples now reject non-selectable catalog rows; selecting one can return
17
+ `409 model_not_selectable`.
18
+
19
+ ## 0.7.0 — 2026-09-14
20
+
21
+ ### Added
22
+
23
+ - **Application-executed custom tools.** Agent resources can declare `custom_tools`; event
24
+ helpers expose requested calls; and the client can list pending calls, resolve a call, or
25
+ post typed `user.custom_tool_result` content.
26
+ - **Filtered cursor session listing.** `listSessionPage()` adds the channel, surface,
27
+ runtime-mode and archive-filtered cursor lane without changing the existing numeric-page
28
+ behavior of `listSessions()`. Session archive and delete operations are also exposed.
29
+
30
+ ### Changed
31
+
32
+ - **MCP configuration now covers runtime context and permissions.** Types include metadata
33
+ context, a server permission default, exact per-tool overrides, and trailing-prefix tool
34
+ policy selectors.
35
+ - **Channel contracts include direct DingTalk configuration and Feishu document capability
36
+ state.** Callers can inspect provider, sync, missing-scope and approval-state details from
37
+ returned capabilities.
38
+
6
39
  ## 0.6.0 — 2026-09-11
7
40
 
8
41
  ### Changed
package/README.md CHANGED
@@ -29,8 +29,10 @@ The base URL has a working default, so you do not configure an endpoint. Overrid
29
29
  // send. Select a model returned by this deployment instead of relying on a
30
30
  // remembered id or on a server default that can rotate.
31
31
  const models = await zc.listModels()
32
- const primary = models.find((model) => model.model === 'litellm/gpt-5.6-terra')?.model
33
- if (!primary) throw new Error('Choose a model returned by listModels()')
32
+ const primary = models.find(
33
+ (model) => model.selectable !== false && model.model === 'litellm/gpt-5.6-terra',
34
+ )?.model
35
+ if (!primary) throw new Error('Choose a selectable model returned by listModels()')
34
36
 
35
37
  const agent = await zc.createAgent({
36
38
  resource: { name: 'research-agent', model: { primary } },
@@ -45,6 +47,15 @@ const session = await zc.createSession(agent.agent_id, {
45
47
  })
46
48
  ```
47
49
 
50
+ `listModels()` can include a model whose retirement has started. Check `selectable !== false`
51
+ before using a catalog row in a new Agent or config. A non-selectable choice returns
52
+ `409 model_not_selectable`; `expired_fallback_to` names the reviewed replacement when present.
53
+
54
+ Agent resources also accept `userTimezone`, a named IANA timezone used for prompt and message
55
+ time context, and `include_global_skills: false` to disable automatic global Skills while keeping
56
+ explicitly listed Skills. An explicit `skills: []` also opts out. Schedule timezones are configured
57
+ separately.
58
+
48
59
  ## Configuration
49
60
 
50
61
  | Option | Environment variable | Default |
@@ -115,7 +126,7 @@ instead of silently returning an empty or apparently complete array.
115
126
  `run.finished` ends a turn; assistant text arrives on `agent.assistant`.
116
127
 
117
128
  ```ts
118
- import { assistantText, isRunFinished, runOutcome, toolCall } from '@zoowork-ai/sdk'
129
+ import { assistantText, customToolUse, isRunFinished, runOutcome, toolCall } from '@zoowork-ai/sdk'
119
130
 
120
131
  for await (const ev of zc.streamEvents(agent.agent_id, session.session_id)) {
121
132
  process.stdout.write(assistantText(ev)) // '' for every non-assistant event
@@ -201,10 +212,33 @@ const { exit_code, stdout } = await zc.exec(agent.agent_id, ['bash', '-lc', 'pwd
201
212
 
202
213
  ## Sessions, approvals, environments
203
214
 
204
- `listSessions`, `archiveSession` and `deleteSession` round out the session surface. There is no
215
+ `listSessions` keeps the legacy numeric-page contract. Use `listSessionPage` for the filtered
216
+ cursor lane: it starts with `sls1:0`, accepts channel/surface/runtime/archive filters, and returns
217
+ `next_cursor` plus a `list_cursor` on each row. Cursors are opaque and bound to the same filters.
218
+ Pass `{ includeDeleted: true }` to include deletion tombstones for reconciliation; returned rows
219
+ then carry `deleted` and the page carries `includes_deleted: true`. This flag is part of the cursor
220
+ scope, so do not reuse a cursor created without it.
221
+ `archiveSession` and `deleteSession` round out the session surface. There is no
205
222
  `patchSession`: the gateway does not proxy `PATCH` at all (405), so session `metadata` is fixed at
206
223
  creation time.
207
224
 
225
+ An application-executed tool is declared in `resource.custom_tools`. When
226
+ `customToolUse(ev)?.phase === 'requested'`, execute the named operation and call
227
+ `resolveCustomToolCall`; `listCustomToolCalls` recovers pending work after a restart. You may also
228
+ post a typed `user.custom_tool_result` event to the owning session. The run reports
229
+ `awaiting_approval` while paused, so use `pending_custom_tool_calls` to distinguish this wait from
230
+ a normal approval. These contracts are source-reviewed and need deployment verification.
231
+
232
+ ```ts
233
+ const call = customToolUse(ev)
234
+ if (call?.phase === 'requested') {
235
+ await zc.resolveCustomToolCall(agentId, call.callId, {
236
+ content: [{ type: 'json', value: { price: 42 } }],
237
+ resolvedBy: 'pricing-service',
238
+ })
239
+ }
240
+ ```
241
+
208
242
  `listApprovals` / `resolveApproval` expose the approvals resource — `decision` is one of
209
243
  `allow-once`, `allow-always`, `deny`. End-to-end approval and turn-budget behavior need
210
244
  separate verification on the deployment you use.
@@ -291,18 +325,19 @@ temporary Agent/Session, one potentially billable model turn and cleanup. JSON r
291
325
  in its printed private directory. The test never publishes. Normal `pnpm test` is offline
292
326
  and needs no key. See [E2E and recovery instructions](e2e/README.md) for scope and options.
293
327
 
294
- To publish the checked-out version, use npm normally:
328
+ Publishing runs through [`.github/workflows/release.yml`](.github/workflows/release.yml). Configure
329
+ the npm package's Trusted Publisher once with organization `SerendipityOneInc`, repository
330
+ `zoowork-sdk-typescript`, workflow `release.yml`, and direct publish permission. No npm token or
331
+ repeated `npm login` is needed after that.
295
332
 
296
- ```sh
297
- npm login
298
- npm publish
299
- ```
333
+ For each release, merge the intended version and changelog, then publish a GitHub Release whose
334
+ tag is exactly `v<package version>` — for example, `v0.7.0`. The workflow verifies that match,
335
+ runs the offline test and build gates, and publishes the public package with npm OIDC. A mismatched
336
+ tag fails before publication, and an existing npm version cannot be overwritten.
300
337
 
301
- The `prepack` hook rebuilds `dist` from source; `npm publish` then publishes that package with
302
- your npm account. It does not run E2E, read a staging key or require an E2E result directory.
303
- You choose when to test and publish, including on different machines. Check the version and
304
- changelog before publishing; existing npm versions cannot be overwritten. Use
305
- `npm publish --dry-run` to inspect the package without uploading it.
338
+ The release workflow does not run live E2E or read a staging key. Run `pnpm test:e2e` separately
339
+ before creating the GitHub Release when live verification is required. Use
340
+ `npm publish --dry-run` locally to inspect the package without uploading it.
306
341
 
307
342
  ## License
308
343
 
package/dist/client.d.ts CHANGED
@@ -121,8 +121,29 @@ export interface ModelInfo {
121
121
  display_name?: string;
122
122
  family?: string;
123
123
  api?: string;
124
+ expired_at?: string | null;
125
+ expired_fallback_to?: string | null;
126
+ retired_at?: string | null;
127
+ revision?: number;
128
+ lifecycle_status?: 'active' | 'scheduled' | 'draining' | 'retired' | string;
129
+ /** `false` means this catalog row cannot be selected for a new Agent or config. */
130
+ selectable?: boolean;
131
+ retire_not_before?: string | null;
132
+ default_for?: string[];
124
133
  [k: string]: unknown;
125
134
  }
135
+ /** Approval behavior for one MCP server or one of its tools. */
136
+ export type McpToolPermission = 'always_ask' | 'always_allow';
137
+ /** Opt-in runtime coordinates sent only while a tool is executing, never during catalog discovery. */
138
+ export interface McpContextConfig {
139
+ /** Add the coordinates under `params._meta['ai.zooclaw/context']` on `tools/call`. */
140
+ meta?: boolean;
141
+ /** Add the coordinates as `x-zooclaw-*` headers on the tool call's HTTP requests. */
142
+ headers?: boolean;
143
+ }
144
+ export interface McpToolPermissionOverride {
145
+ permission: McpToolPermission;
146
+ }
126
147
  /**
127
148
  * One remote MCP server, declared as `resource.mcp[]` on create or update.
128
149
  *
@@ -161,8 +182,36 @@ export interface McpServerDeclaration {
161
182
  * There is no `auto` value.
162
183
  */
163
184
  exposure?: 'deferred' | 'direct';
185
+ /**
186
+ * Opt in to runtime coordinates for this server. Both switches default to false. The context
187
+ * can include agent/session/computer ids and, when available, run/turn/config/actor fields.
188
+ * Catalog discovery never receives it. Header delivery may be unavailable through proxies;
189
+ * `_meta` is the portable option.
190
+ */
191
+ context?: McpContextConfig;
192
+ /** Default approval behavior for every tool on this server. Omit for the existing default-allow behavior. */
193
+ permission?: McpToolPermission;
194
+ /**
195
+ * Per-tool approval overrides keyed by the server's original tool name, not its
196
+ * `mcp__<server>__<tool>` name. Keys are exact and cannot contain `*`; at most 64 entries.
197
+ */
198
+ tools?: Record<string, McpToolPermissionOverride>;
164
199
  [k: string]: unknown;
165
200
  }
201
+ /** One application-executed tool declared under {@link AgentResource.custom_tools}. */
202
+ export interface CustomToolDeclaration {
203
+ /** Unique within the Agent; 1–64 ASCII letters, numbers, `_` or `-`. Reserved runtime names are rejected. */
204
+ name: string;
205
+ /** Non-empty tool description, at most 4 KiB UTF-8. */
206
+ description: string;
207
+ /** JSON Schema for the input. Its top-level `type` must be `object`; at most 16 KiB serialized. */
208
+ input_schema: {
209
+ type: 'object';
210
+ [k: string]: unknown;
211
+ };
212
+ /** Result wait budget in milliseconds. Defaults to 600,000; maximum 86,400,000. */
213
+ timeoutMs?: number;
214
+ }
166
215
  /**
167
216
  * The system-prompt pin (`resource.system_prompt`).
168
217
  *
@@ -229,6 +278,8 @@ export interface OutcomeConfig {
229
278
  }
230
279
  export interface AgentResource {
231
280
  name: string;
281
+ /** Named IANA timezone used in prompt context and message timestamps, e.g. `Asia/Shanghai`. */
282
+ userTimezone?: string;
232
283
  /**
233
284
  * Omit this section to pin the platform's current model defaults at create time. Those
234
285
  * defaults can rotate; call `listModels()` and set `primary` explicitly for deterministic
@@ -251,10 +302,27 @@ export interface AgentResource {
251
302
  skill_id: string;
252
303
  version?: number | 'latest';
253
304
  }[];
305
+ /**
306
+ * Defaults to true. False disables automatic global Skills without removing explicitly
307
+ * listed Skills. An explicit empty `skills` array also opts out.
308
+ */
309
+ include_global_skills?: boolean;
254
310
  labels?: Record<string, string>;
311
+ /**
312
+ * Tool-surface and approval policy. Name selectors in `allow`, `deny`, `rules[].match.tool`,
313
+ * `afterRules[].match.tool`, and MCP names in `deferred.pinned` accept an exact name, `*`, or
314
+ * one trailing `prefix*`. Other wildcard forms match nothing. `alsoAllow` and
315
+ * `permissions` keys remain exact. Kept open for forward-compatible policy fields.
316
+ */
255
317
  tool_policy?: Record<string, unknown>;
256
318
  /** Remote MCP servers. Only unauthenticated ones work today — see {@link McpServerDeclaration}. */
257
319
  mcp?: McpServerDeclaration[];
320
+ /**
321
+ * Tools executed by your application. At most 32. A model call emits
322
+ * `agent.custom_tool_use`; return the result with `resolveCustomToolCall` or a
323
+ * `user.custom_tool_result` event. Source-reviewed; deployment availability is unverified.
324
+ */
325
+ custom_tools?: CustomToolDeclaration[];
258
326
  /**
259
327
  * System-prompt pin. Omitted on create means "the platform version active right now", pinned
260
328
  * from then on. REPLACE-ON-WRITE on PUT, like `tool_policy` — see {@link SystemPromptDeclaration}.
@@ -312,6 +380,27 @@ export interface AgentSkill {
312
380
  }[];
313
381
  [k: string]: unknown;
314
382
  }
383
+ /** Source-reviewed asynchronous capability-configuration state for one channel feature. */
384
+ export interface AgentChannelCapabilitySync {
385
+ state: 'pending' | 'applied' | 'retry' | 'error';
386
+ [k: string]: unknown;
387
+ }
388
+ export interface FeishuChannelProviderStatus {
389
+ state: 'ready' | 'degraded';
390
+ missing_scopes: string[];
391
+ approval_state?: 'pending_admin' | null;
392
+ [k: string]: unknown;
393
+ }
394
+ export interface FeishuDocumentsCapability {
395
+ permission_admin_enabled: boolean;
396
+ sync: AgentChannelCapabilitySync;
397
+ provider: FeishuChannelProviderStatus;
398
+ [k: string]: unknown;
399
+ }
400
+ export interface AgentChannelCapabilities {
401
+ feishu_documents?: FeishuDocumentsCapability | null;
402
+ [k: string]: unknown;
403
+ }
315
404
  /**
316
405
  * One platform account bound to an agent, as the channel service reports it.
317
406
  * `dm_policy` / `group_policy` are the reachability policies (`'open'` is the
@@ -328,10 +417,13 @@ export interface AgentChannel {
328
417
  health?: string;
329
418
  status?: string;
330
419
  status_code?: string | null;
420
+ /** Source-reviewed capability state. Omitted when the platform reports none. */
421
+ capabilities?: AgentChannelCapabilities | null;
331
422
  [k: string]: unknown;
332
423
  }
333
424
  /**
334
- * The chat platforms you can bind, staging-verified 2026-08-28.
425
+ * The chat platforms you can bind. Feishu, Slack, WeCom and WeChat were staging-verified
426
+ * 2026-08-28. Direct DingTalk support is source-reviewed, not deployment-verified here.
335
427
  *
336
428
  * Three of them have a server-driven QR flow ({@link GuidedSetupPlatform}); Slack does not,
337
429
  * and structurally cannot — a Slack app is created by a person and its tokens only ever exist
@@ -340,15 +432,15 @@ export interface AgentChannel {
340
432
  *
341
433
  * WeChat is the one platform that goes the other way: `'weixin'`/`'wechat'` on
342
434
  * {@link ZooworkClient.addChannel} answers `400 channel.weixin_setup_required`, so the QR flow
343
- * is its ONLY path. See {@link AddChannelPlatform}. Any name outside this type answers
344
- * `400 channel.invalid_request`.
435
+ * is its ONLY path. DingTalk uses `'dingtalk-connector'` and currently has a direct config path
436
+ * only on the public API; its product QR flow is not exposed here. See {@link AddChannelPlatform}.
345
437
  */
346
- export type ChannelPlatform = 'feishu' | 'slack' | 'wecom' | 'weixin';
438
+ export type ChannelPlatform = 'feishu' | 'slack' | 'wecom' | 'weixin' | 'dingtalk-connector';
347
439
  /**
348
440
  * The platforms {@link ZooworkClient.addChannel} accepts — every {@link ChannelPlatform}
349
441
  * except WeChat, which refuses explicit config and takes the QR flow only.
350
442
  */
351
- export type AddChannelPlatform = 'feishu' | 'slack' | 'wecom';
443
+ export type AddChannelPlatform = 'feishu' | 'slack' | 'wecom' | 'dingtalk-connector';
352
444
  /**
353
445
  * The platforms with a server-driven QR flow: {@link ZooworkClient.startChannelSetup} →
354
446
  * render the URI → poll. Slack is absent by design, not by omission.
@@ -397,7 +489,7 @@ export interface AddChannelInput {
397
489
  */
398
490
  account?: string;
399
491
  display_name?: string;
400
- /** Server default: `'open'`. `'pairing'` is rejected with `400 channel.pairing_unsupported`. */
492
+ /** Server default: `'open'`. DingTalk accepts only `'open'`; `'pairing'` is rejected everywhere. */
401
493
  dm_policy?: string;
402
494
  /** Server default: `'open'`. */
403
495
  group_policy?: string;
@@ -411,8 +503,11 @@ export interface AddChannelInput {
411
503
  * needs the app-level token as well as the bot token)
412
504
  * - `wecom` — `{ botId, secret }`, both required
413
505
  * - `feishu` — `{ appId, appSecret, domain }`, only when skipping the QR flow
506
+ * - `dingtalk-connector` — `{ clientId, clientSecret }`; no public guided QR route
414
507
  */
415
508
  config?: Record<string, unknown>;
509
+ /** Feishu only. Enable document-permission administration; defaults to false. */
510
+ permission_admin_enabled?: boolean;
416
511
  }
417
512
  export interface UpdateChannelInput {
418
513
  /** Which binding to touch — see {@link AddChannelInput.account}. Server default: `'default'`. */
@@ -420,6 +515,8 @@ export interface UpdateChannelInput {
420
515
  dm_policy?: string;
421
516
  group_policy?: string;
422
517
  enabled?: boolean;
518
+ /** Feishu only. Other platforms reject this field when it is present. */
519
+ permission_admin_enabled?: boolean;
423
520
  }
424
521
  /**
425
522
  * Body for {@link ZooworkClient.startChannelSetup}. Every field is optional, and each platform
@@ -462,6 +559,8 @@ export interface ChannelSetupInput {
462
559
  dm_policy?: string;
463
560
  /** Server default: `'open'`. Ignored by WeChat, which forces `'disabled'`. */
464
561
  group_policy?: string;
562
+ /** Feishu only. Enable document-permission administration; defaults to false. */
563
+ permission_admin_enabled?: boolean;
465
564
  }
466
565
  /** @deprecated Use {@link ChannelSetupInput}; this is the same shape under the old name. */
467
566
  export type FeishuSetupInput = ChannelSetupInput;
@@ -624,6 +723,8 @@ export interface SessionRecord {
624
723
  run_status?: string | null;
625
724
  /** Pending approval count on getSession; not included in every session projection. */
626
725
  pending_approvals?: number;
726
+ /** Pending application-executed custom-tool count on getSession. */
727
+ pending_custom_tool_calls?: number;
627
728
  /**
628
729
  * `running` on a `createSession` receipt, nullable on `getSession`, and absent from
629
730
  * `listSessions` rows. This is not the run outcome; read {@link SessionRecord.run_status}
@@ -632,12 +733,20 @@ export interface SessionRecord {
632
733
  status?: string | null;
633
734
  metadata?: Record<string, unknown>;
634
735
  archived?: boolean;
736
+ /** Present on filtered pages only when `includeDeleted` was requested. */
737
+ deleted?: boolean;
738
+ runtime_mode?: 'active' | 'preview' | 'authoring' | 'evaluation' | string;
739
+ config_version?: number;
635
740
  updated_at?: string;
741
+ /** Activity sort key used by filtered cursor listing. */
742
+ last_activity_at?: string;
743
+ /** Opaque per-row resume cursor returned only by {@link ZooworkClient.listSessionPage}. */
744
+ list_cursor?: string;
636
745
  /** Present only when the read asked for `history: true`; the most recent `limit` rows, in order. */
637
746
  history?: SessionHistoryEntry[];
638
747
  [k: string]: unknown;
639
748
  }
640
- /** Write-side events: user.message / user.interrupt / user.tool_confirmation / system.message */
749
+ /** Write-side events, including `user.custom_tool_result`; unsupported types are rejected by the API. */
641
750
  export interface OutboundEvent {
642
751
  type: string;
643
752
  content?: unknown;
@@ -655,6 +764,59 @@ export interface OutboundEvent {
655
764
  };
656
765
  [k: string]: unknown;
657
766
  }
767
+ export type CustomToolResultImageMimeType = 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp';
768
+ /** One content block returned by an application-executed custom tool. */
769
+ export type CustomToolResultContent = {
770
+ type: 'text';
771
+ text: string;
772
+ } | {
773
+ type: 'json';
774
+ value: unknown;
775
+ } | {
776
+ type: 'image';
777
+ source: {
778
+ type: 'base64';
779
+ media_type: CustomToolResultImageMimeType;
780
+ data: string;
781
+ };
782
+ } | {
783
+ type: 'image';
784
+ data: string;
785
+ mime_type: CustomToolResultImageMimeType;
786
+ };
787
+ /** Write-side result event for {@link ZooworkClient.postEvents}. */
788
+ export type CustomToolResultEvent = OutboundEvent & {
789
+ type: 'user.custom_tool_result';
790
+ content: CustomToolResultContent[];
791
+ is_error?: boolean;
792
+ idempotency_key?: string;
793
+ } & ({
794
+ custom_tool_use_id: string;
795
+ call_id?: never;
796
+ } | {
797
+ call_id: string;
798
+ custom_tool_use_id?: never;
799
+ });
800
+ /** Options for the filtered cursor lane. Filters are part of the cursor scope. */
801
+ export interface SessionListPageOptions {
802
+ /** Opaque cursor returned by this same filter scope. Omit to start at `sls1:0`. */
803
+ cursor?: string;
804
+ /** 1–100; server default 50. */
805
+ limit?: number;
806
+ excludeChannels?: string[];
807
+ includeSurfaces?: string[];
808
+ runtimeModes?: Array<'active' | 'preview' | 'authoring' | 'evaluation'>;
809
+ includeArchived?: boolean;
810
+ /** Include deleted Session tombstones for history reconciliation. Changes the cursor scope. */
811
+ includeDeleted?: boolean;
812
+ }
813
+ /** One filtered session page. `next_cursor` is null at the end. */
814
+ export interface SessionListPage {
815
+ sessions: SessionRecord[];
816
+ next_cursor: string | null;
817
+ /** Present and true only when deleted tombstones were requested. */
818
+ includes_deleted?: true;
819
+ }
658
820
  /** One `postEvents` receipt. An accepted event carries the full event object's fields too. */
659
821
  export interface PostEventReceipt {
660
822
  id?: string | null;
@@ -898,6 +1060,24 @@ export interface ApprovalRecord {
898
1060
  created_at?: string;
899
1061
  [k: string]: unknown;
900
1062
  }
1063
+ export type CustomToolCallStatus = 'pending' | 'completed' | 'timeout' | 'cancelled' | string;
1064
+ /** One application-executed custom-tool call. Unknown response fields are preserved. */
1065
+ export interface CustomToolCallRecord {
1066
+ call_id: string;
1067
+ session_id: string;
1068
+ tool_call_id: string;
1069
+ name: string;
1070
+ input: Record<string, unknown>;
1071
+ status: CustomToolCallStatus;
1072
+ requested_at: string;
1073
+ timeout_at?: string;
1074
+ resolved_by?: string;
1075
+ resolved_at?: string;
1076
+ is_error?: boolean;
1077
+ /** Resolve receipt: true when a pending call was signaled, false when it was already terminal. */
1078
+ signaled?: boolean;
1079
+ [k: string]: unknown;
1080
+ }
901
1081
  /** Artifact lifecycle. Only a `ready` row carries a resolvable `url`. */
902
1082
  export type ArtifactStatus = 'pending' | 'ready' | 'failed' | 'deleted' | string;
903
1083
  /**
@@ -1416,10 +1596,15 @@ export interface ZooworkClient {
1416
1596
  history?: boolean;
1417
1597
  limit?: number;
1418
1598
  }): Promise<SessionRecord>;
1419
- /** Newest first by `updated_at`, 50 per page, `page` is 1-based. Single page per call — there is no cursor. */
1599
+ /** Legacy newest-first numeric page lane: 50 per page, `page` is 1-based. */
1420
1600
  listSessions(agentId: string, opts?: {
1421
1601
  page?: number;
1422
1602
  }): Promise<SessionRecord[]>;
1603
+ /**
1604
+ * Filtered cursor lane. It is separate from {@link listSessions} so existing numeric-page
1605
+ * callers keep their contract. Cursors are opaque and valid only with the same filters.
1606
+ */
1607
+ listSessionPage(agentId: string, opts?: SessionListPageOptions): Promise<SessionListPage>;
1423
1608
  /**
1424
1609
  * Stamp `archived_at`. Afterwards writes are `409 session_archived` while reads keep working.
1425
1610
  * Interrupt an in-flight run first, or the archive races it.
@@ -1490,6 +1675,19 @@ export interface ZooworkClient {
1490
1675
  cursor?: string;
1491
1676
  signal?: AbortSignal;
1492
1677
  }): AsyncGenerator<SessionEvent>;
1678
+ /** Pending calls only. Any other status is rejected by the API. */
1679
+ listCustomToolCalls(agentId: string, opts?: {
1680
+ status?: 'pending';
1681
+ }): Promise<CustomToolCallRecord[]>;
1682
+ /**
1683
+ * Return one call's result. A pending call answers 202/signaled:true but stays pending until
1684
+ * the paused run consumes it; an already-terminal call answers 200/signaled:false.
1685
+ */
1686
+ resolveCustomToolCall(agentId: string, callId: string, input: {
1687
+ content: CustomToolResultContent[];
1688
+ isError?: boolean;
1689
+ resolvedBy?: string;
1690
+ }): Promise<CustomToolCallRecord>;
1493
1691
  /**
1494
1692
  * Tool calls parked on a human decision. `status` may ONLY be omitted or `'pending'` —
1495
1693
  * staging-verified 2026-08-07; any other value is rejected, so there is no way to list
package/dist/client.js CHANGED
@@ -543,6 +543,22 @@ export function createZooworkClient(cfg = {}) {
543
543
  const data = await json(`${sessions(agentId)}${query({ page: opts.page })}`);
544
544
  return data.sessions ?? [];
545
545
  },
546
+ listSessionPage: async (agentId, opts = {}) => {
547
+ const data = await json(`${sessions(agentId)}${query({
548
+ cursor: opts.cursor ?? 'sls1:0',
549
+ limit: opts.limit,
550
+ exclude_channels: opts.excludeChannels?.join(','),
551
+ include_surfaces: opts.includeSurfaces?.join(','),
552
+ runtime_modes: opts.runtimeModes?.join(','),
553
+ include_archived: opts.includeArchived === undefined ? undefined : String(opts.includeArchived),
554
+ include_deleted: opts.includeDeleted === undefined ? undefined : String(opts.includeDeleted),
555
+ })}`);
556
+ return {
557
+ sessions: data.sessions ?? [],
558
+ next_cursor: data.next_cursor ?? null,
559
+ ...(data.includes_deleted === true ? { includes_deleted: true } : {}),
560
+ };
561
+ },
546
562
  archiveSession: async (agentId, sessionId) => {
547
563
  const data = await json(`${sessions(agentId)}/${encodeURIComponent(sessionId)}/archive`, { method: 'POST' });
548
564
  return { ...data, archived: data.archived ?? false };
@@ -651,6 +667,14 @@ export function createZooworkClient(cfg = {}) {
651
667
  throw e;
652
668
  }
653
669
  },
670
+ listCustomToolCalls: async (agentId, opts = {}) => {
671
+ const data = await json(`${agents(agentId)}/custom_tool_calls${query({ status: opts.status })}`);
672
+ return data.custom_tool_calls ?? [];
673
+ },
674
+ resolveCustomToolCall: (agentId, callId, input) => json(`${agents(agentId)}/custom_tool_calls/${encodeURIComponent(callId)}/result`, {
675
+ method: 'POST',
676
+ body: JSON.stringify(input),
677
+ }),
654
678
  listApprovals: async (agentId, opts = {}) => {
655
679
  const data = await json(`${agents(agentId)}/approvals${query({ status: opts.status })}`);
656
680
  return data.approvals ?? [];
package/dist/events.d.ts CHANGED
@@ -17,10 +17,10 @@
17
17
  * and may add types within a version.
18
18
  */
19
19
  /** SESSION_EVENT_TYPES, mirrored from the API. */
20
- export declare const SESSION_EVENT_TYPES: readonly ["run.started", "run.finished", "chat.delta", "chat.final", "chat.aborted", "chat.error", "agent.lifecycle", "agent.assistant", "agent.thinking", "agent.tool", "agent.item", "agent.plan", "agent.approval", "agent.command_output", "agent.patch", "agent.compaction", "agent.error", "attachment.created", "message.outbound"];
20
+ export declare const SESSION_EVENT_TYPES: readonly ["run.started", "run.finished", "chat.delta", "chat.final", "chat.aborted", "chat.error", "agent.lifecycle", "agent.assistant", "agent.thinking", "agent.tool", "agent.item", "agent.plan", "agent.approval", "agent.custom_tool_use", "agent.command_output", "agent.patch", "agent.compaction", "agent.error", "attachment.created", "message.outbound"];
21
21
  export type SessionEventType = (typeof SESSION_EVENT_TYPES)[number];
22
22
  /** Your own inputs, echoed back in the unified event history. */
23
- export declare const PUBLIC_INPUT_EVENT_TYPES: readonly ["user.message", "user.interrupt", "user.tool_confirmation", "system.message"];
23
+ export declare const PUBLIC_INPUT_EVENT_TYPES: readonly ["user.message", "user.interrupt", "user.tool_confirmation", "user.custom_tool_result", "system.message"];
24
24
  export type PublicInputEventType = (typeof PUBLIC_INPUT_EVENT_TYPES)[number];
25
25
  /** A durable session event, normalized across the REST and SSE shapes. */
26
26
  export interface SessionEvent {
@@ -74,6 +74,20 @@ export interface ToolCall {
74
74
  isError?: boolean;
75
75
  resultPreview?: string;
76
76
  }
77
+ export interface CustomToolUse {
78
+ phase: 'requested' | 'resolved';
79
+ callId: string;
80
+ toolCallId?: string;
81
+ name?: string;
82
+ input?: Record<string, unknown>;
83
+ timeoutAt?: string;
84
+ outcome?: 'completed' | 'timeout' | 'cancelled';
85
+ isError?: boolean;
86
+ resolvedBy?: string;
87
+ resolutionChannel?: string;
88
+ }
89
+ /** Application-executed custom-tool activity; undefined for every other event type. */
90
+ export declare function customToolUse(e: SessionEvent): CustomToolUse | undefined;
77
91
  /**
78
92
  * Tool activity for an `agent.tool` event; undefined for every other type.
79
93
  *
package/dist/events.js CHANGED
@@ -31,6 +31,7 @@ export const SESSION_EVENT_TYPES = [
31
31
  'agent.item',
32
32
  'agent.plan',
33
33
  'agent.approval',
34
+ 'agent.custom_tool_use',
34
35
  'agent.command_output',
35
36
  'agent.patch',
36
37
  'agent.compaction',
@@ -43,6 +44,7 @@ export const PUBLIC_INPUT_EVENT_TYPES = [
43
44
  'user.message',
44
45
  'user.interrupt',
45
46
  'user.tool_confirmation',
47
+ 'user.custom_tool_result',
46
48
  'system.message',
47
49
  ];
48
50
  const isObj = (v) => !!v && typeof v === 'object';
@@ -124,6 +126,25 @@ export function thinkingText(e) {
124
126
  return '';
125
127
  return typeof e.payload.text === 'string' ? e.payload.text : '';
126
128
  }
129
+ /** Application-executed custom-tool activity; undefined for every other event type. */
130
+ export function customToolUse(e) {
131
+ if (e.eventType !== 'agent.custom_tool_use')
132
+ return undefined;
133
+ const p = e.payload;
134
+ const outcome = p.outcome === 'completed' || p.outcome === 'timeout' || p.outcome === 'cancelled' ? p.outcome : undefined;
135
+ return {
136
+ phase: p.phase === 'resolved' ? 'resolved' : 'requested',
137
+ callId: typeof p.callId === 'string' ? p.callId : '',
138
+ ...(typeof p.toolCallId === 'string' ? { toolCallId: p.toolCallId } : {}),
139
+ ...(typeof p.name === 'string' ? { name: p.name } : {}),
140
+ ...(isObj(p.input) ? { input: p.input } : {}),
141
+ ...(typeof p.timeoutAt === 'string' ? { timeoutAt: p.timeoutAt } : {}),
142
+ ...(outcome ? { outcome } : {}),
143
+ ...(typeof p.isError === 'boolean' ? { isError: p.isError } : {}),
144
+ ...(typeof p.resolvedBy === 'string' ? { resolvedBy: p.resolvedBy } : {}),
145
+ ...(typeof p.resolutionChannel === 'string' ? { resolutionChannel: p.resolutionChannel } : {}),
146
+ };
147
+ }
127
148
  /**
128
149
  * Tool activity for an `agent.tool` event; undefined for every other type.
129
150
  *
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { createZooworkClient, DEFAULT_BASE_URL, ZooworkError, type ZooworkClient, type ZooworkConfig, type ZooworkAuth, type Ownership, type ModelInfo, type AgentResource, type AgentRecord, type AgentListParams, type AgentPage, type AgentPagePromise, type AgentStatus, type AgentSkill, type AgentChannel, type ChannelPlatform, type AddChannelPlatform, type GuidedSetupPlatform, type AddChannelInput, type UpdateChannelInput, type ChannelSetupInput, type ChannelSetupSession, type ChannelPollResult, type FeishuSetupInput, type FeishuSetupSession, type FeishuPollResult, type McpServerDeclaration, type SkillRecord, type SkillVersionRecord, type SessionRecord, type SessionHistoryEntry, type SessionEvent, type SessionEventPage, type OutboundEvent, type PostEventReceipt, type ApprovalDecision, type ApprovalRecord, type ArtifactPage, type ArtifactRecord, type ArtifactStatus, type OutcomeConfig, type OutcomeEvaluator, type SystemPromptDeclaration, type SystemPromptInfo, type SystemPromptPreview, type SystemPromptPreviewInput, type SystemPromptUpgrade, type ScheduleSpec, type SchedulePayload, type ScheduleInput, type ScheduleUpdate, type ScheduleRecord, type ScheduleRun, type WakeResult, type ExecResult, type EnvironmentConfig, type EnvironmentResource, type EnvironmentRecord, type EnvironmentVersionRecord, } from './client.js';
2
- export { SESSION_EVENT_TYPES, type SessionEventType, PUBLIC_INPUT_EVENT_TYPES, type PublicInputEventType, normalizeEvent, isRunFinished, runOutcome, messageText, assistantText, thinkingText, toolCall, type ToolCall, } from './events.js';
1
+ export { createZooworkClient, DEFAULT_BASE_URL, ZooworkError, type ZooworkClient, type ZooworkConfig, type ZooworkAuth, type Ownership, type ModelInfo, type AgentResource, type AgentRecord, type AgentListParams, type AgentPage, type AgentPagePromise, type AgentStatus, type AgentSkill, type AgentChannel, type AgentChannelCapabilitySync, type AgentChannelCapabilities, type FeishuChannelProviderStatus, type FeishuDocumentsCapability, type ChannelPlatform, type AddChannelPlatform, type GuidedSetupPlatform, type AddChannelInput, type UpdateChannelInput, type ChannelSetupInput, type ChannelSetupSession, type ChannelPollResult, type FeishuSetupInput, type FeishuSetupSession, type FeishuPollResult, type McpContextConfig, type McpServerDeclaration, type McpToolPermission, type McpToolPermissionOverride, type CustomToolDeclaration, type CustomToolResultImageMimeType, type CustomToolResultContent, type CustomToolResultEvent, type SkillRecord, type SkillVersionRecord, type SessionRecord, type SessionListPageOptions, type SessionListPage, type SessionHistoryEntry, type SessionEvent, type SessionEventPage, type OutboundEvent, type PostEventReceipt, type ApprovalDecision, type ApprovalRecord, type CustomToolCallStatus, type CustomToolCallRecord, type ArtifactPage, type ArtifactRecord, type ArtifactStatus, type OutcomeConfig, type OutcomeEvaluator, type SystemPromptDeclaration, type SystemPromptInfo, type SystemPromptPreview, type SystemPromptPreviewInput, type SystemPromptUpgrade, type ScheduleSpec, type SchedulePayload, type ScheduleInput, type ScheduleUpdate, type ScheduleRecord, type ScheduleRun, type WakeResult, type ExecResult, type EnvironmentConfig, type EnvironmentResource, type EnvironmentRecord, type EnvironmentVersionRecord, } from './client.js';
2
+ export { SESSION_EVENT_TYPES, type SessionEventType, PUBLIC_INPUT_EVENT_TYPES, type PublicInputEventType, normalizeEvent, isRunFinished, runOutcome, messageText, assistantText, thinkingText, customToolUse, type CustomToolUse, toolCall, type ToolCall, } from './events.js';
3
3
  export { parseSSE, type SSEMessage } from './sse.js';
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { createZooworkClient, DEFAULT_BASE_URL, ZooworkError, } from './client.js';
2
- export { SESSION_EVENT_TYPES, PUBLIC_INPUT_EVENT_TYPES, normalizeEvent, isRunFinished, runOutcome, messageText, assistantText, thinkingText, toolCall, } from './events.js';
2
+ export { SESSION_EVENT_TYPES, PUBLIC_INPUT_EVENT_TYPES, normalizeEvent, isRunFinished, runOutcome, messageText, assistantText, thinkingText, customToolUse, toolCall, } from './events.js';
3
3
  export { parseSSE } from './sse.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zoowork-ai/sdk",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "TypeScript SDK for the ZooWork Managed Agents API (Developer Preview)",
5
5
  "keywords": [
6
6
  "zoowork",
@@ -39,12 +39,6 @@
39
39
  "publishConfig": {
40
40
  "access": "public"
41
41
  },
42
- "devDependencies": {
43
- "@types/node": "^22.20.1",
44
- "tsx": "^4.23.7",
45
- "typescript": "^5.6.0",
46
- "vitest": "^3.0.0"
47
- },
48
42
  "scripts": {
49
43
  "build": "tsc -p tsconfig.build.json",
50
44
  "typecheck": "tsc --noEmit",
@@ -55,6 +49,13 @@
55
49
  "test:e2e:offline": "node --import tsx --test --test-reporter=spec e2e/*.test.ts",
56
50
  "e2e:prepare": "node e2e/runner.ts prepare",
57
51
  "e2e:run": "node e2e/runner.ts run",
58
- "e2e:verify": "node e2e/runner.ts verify"
52
+ "e2e:verify": "node e2e/runner.ts verify",
53
+ "prepack": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && npm run build"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^22.20.1",
57
+ "tsx": "^4.23.7",
58
+ "typescript": "^5.6.0",
59
+ "vitest": "^3.0.0"
59
60
  }
60
- }
61
+ }