@zoowork-ai/sdk 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,34 @@
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.4.1 — 2026-08-25
7
+
8
+ ### Internal
9
+
10
+ - **Trailing slashes are stripped from the base URL by a scan rather than `/\/+$/`.** Same
11
+ output for every input; the regex retried at every start position on a long run of
12
+ slashes, which CodeQL flags as polynomial. Nothing hostile reaches it — the input is the
13
+ caller's own base URL — so this closes an alert rather than a vulnerability.
14
+
15
+ ### Documentation
16
+
17
+ - **`account` on `addChannel` / `startFeishuSetup` now documents what it actually is.** It
18
+ names a binding and is part of its identity — `updateChannel` and `removeChannel` look a
19
+ binding up by `platform` + `account`, and nothing renames one. The four constraints, all
20
+ staging-verified 2026-08-25:
21
+ - the name is unique per USER across every agent, not per agent;
22
+ - `'default'` is usually taken already by a binding the app made, which this API will not
23
+ adopt — it answers `409 channel.conflict`;
24
+ - the format is `^[a-z0-9][a-z0-9_-]{0,63}$` plus three reserved words, and nothing is
25
+ normalized for you;
26
+ - this SDK cannot pre-check a name, because `listChannels` is scoped to one agent while the
27
+ constraint spans the whole account.
28
+ - **`startFeishuSetup` warns that a name clash surfaces after the scan.** Approving the QR
29
+ registers a new app in the Feishu workspace before the binding is written, so a clash costs
30
+ a scan and leaves that app behind; retrying under the same name repeats both.
31
+
32
+ No runtime change — comments only.
33
+
6
34
  ## 0.4.0 — 2026-08-25
7
35
 
8
36
  ### Changed (breaking)
package/dist/client.d.ts CHANGED
@@ -275,7 +275,29 @@ export type ChannelPlatform = 'feishu' | 'slack' | 'wecom';
275
275
  export interface AddChannelInput {
276
276
  /** See {@link ChannelPlatform}. Typed loosely so a newly supported platform needs no SDK release. */
277
277
  platform: ChannelPlatform | (string & {});
278
- /** Server default: `'default'`. */
278
+ /**
279
+ * Names this binding. It is part of the record's identity, not a setting: `updateChannel`
280
+ * and `removeChannel` find a binding by `platform` + `account`, and nothing renames one
281
+ * afterwards — you would remove it and bind again.
282
+ *
283
+ * Four things to know before you pick a value, all staging-verified 2026-08-25:
284
+ *
285
+ * - **The name is unique per USER, across every agent** — one active row per
286
+ * (owner, platform, account). Binding `feishu`/`default` on one agent takes that name away
287
+ * from all your other agents.
288
+ * - **`'default'` is very likely taken already** if the same login ever bound this platform in
289
+ * the app. That binding was not made through this API, so the server refuses to adopt it and
290
+ * answers `409 channel.conflict`.
291
+ * - **Format is `^[a-z0-9][a-z0-9_-]{0,63}$`**, plus three reserved words (`__proto__`,
292
+ * `prototype`, `constructor`). Anything else is `400`. Nothing is normalized for you, so a
293
+ * display name with capitals, spaces or non-ASCII characters is rejected rather than fixed.
294
+ * - **This SDK cannot pre-check the name for you.** {@link ZooworkClient.listChannels} is scoped
295
+ * to one agent while the constraint spans your whole account, so a name another agent holds is
296
+ * invisible here. Track your own names.
297
+ *
298
+ * Use whatever naming scheme your system already has. With no scheme of your own, the agent id
299
+ * works as-is: it matches the pattern and is unique per agent by construction.
300
+ */
279
301
  account?: string;
280
302
  display_name?: string;
281
303
  /** Server default: `'open'`. `'pairing'` is rejected with `400 channel.pairing_unsupported`. */
@@ -296,7 +318,7 @@ export interface AddChannelInput {
296
318
  config?: Record<string, unknown>;
297
319
  }
298
320
  export interface UpdateChannelInput {
299
- /** Which platform account to touch. Server default: `'default'`. */
321
+ /** Which binding to touch — see {@link AddChannelInput.account}. Server default: `'default'`. */
300
322
  account?: string;
301
323
  dm_policy?: string;
302
324
  group_policy?: string;
@@ -305,7 +327,29 @@ export interface UpdateChannelInput {
305
327
  export interface FeishuSetupInput {
306
328
  /** `'feishu'` (default) or `'lark'` — the international brand of the same platform. */
307
329
  brand?: 'feishu' | 'lark';
308
- /** Server default: `'default'`. */
330
+ /**
331
+ * Names this binding. It is part of the record's identity, not a setting: `updateChannel`
332
+ * and `removeChannel` find a binding by `platform` + `account`, and nothing renames one
333
+ * afterwards — you would remove it and bind again.
334
+ *
335
+ * Four things to know before you pick a value, all staging-verified 2026-08-25:
336
+ *
337
+ * - **The name is unique per USER, across every agent** — one active row per
338
+ * (owner, platform, account). Binding `feishu`/`default` on one agent takes that name away
339
+ * from all your other agents.
340
+ * - **`'default'` is very likely taken already** if the same login ever bound this platform in
341
+ * the app. That binding was not made through this API, so the server refuses to adopt it and
342
+ * answers `409 channel.conflict`.
343
+ * - **Format is `^[a-z0-9][a-z0-9_-]{0,63}$`**, plus three reserved words (`__proto__`,
344
+ * `prototype`, `constructor`). Anything else is `400`. Nothing is normalized for you, so a
345
+ * display name with capitals, spaces or non-ASCII characters is rejected rather than fixed.
346
+ * - **This SDK cannot pre-check the name for you.** {@link ZooworkClient.listChannels} is scoped
347
+ * to one agent while the constraint spans your whole account, so a name another agent holds is
348
+ * invisible here. Track your own names.
349
+ *
350
+ * Use whatever naming scheme your system already has. With no scheme of your own, the agent id
351
+ * works as-is: it matches the pattern and is unique per agent by construction.
352
+ */
309
353
  account?: string;
310
354
  /** Server default: `'open'`. */
311
355
  dm_policy?: string;
@@ -1049,6 +1093,12 @@ export interface ZooworkClient {
1049
1093
  * Observed defaults: `expires_in: 600`, `poll_interval: 5`. `brand` picks the real host —
1050
1094
  * `'feishu'` answers an `open.feishu.cn` URI, `'lark'` an `open.larksuite.com` one, so the
1051
1095
  * brand has to match the workspace the person will approve it in.
1096
+ *
1097
+ * Pick `account` before you show the QR. Approving the scan registers a NEW app in that
1098
+ * Feishu workspace, and only then does the binding get written — so a name clash surfaces as
1099
+ * `409 channel.conflict` AFTER someone has scanned, with the freshly registered app left
1100
+ * behind in their workspace. Retrying under the same name repeats both. See
1101
+ * {@link AddChannelInput.account} for how names are scoped.
1052
1102
  */
1053
1103
  startFeishuSetup(agentId: string, input?: FeishuSetupInput): Promise<FeishuSetupSession>;
1054
1104
  /**
package/dist/client.js CHANGED
@@ -27,6 +27,18 @@ function readEnv(name) {
27
27
  const value = proc?.env?.[name];
28
28
  return value === undefined || value === '' ? undefined : value;
29
29
  }
30
+ /**
31
+ * Trailing `/` removed by scanning, not by `/\/+$/`. That regex retries at every start
32
+ * position on a long run of slashes, which is quadratic and which CodeQL flags. Nothing
33
+ * hostile can reach it — the input is the caller's own base URL — but one pass is simpler
34
+ * than arguing with the scanner about it.
35
+ */
36
+ function stripTrailingSlashes(url) {
37
+ let end = url.length;
38
+ while (end > 0 && url.charCodeAt(end - 1) === 47 /* '/' */)
39
+ end--;
40
+ return end === url.length ? url : url.slice(0, end);
41
+ }
30
42
  export class ZooworkError extends Error {
31
43
  status;
32
44
  /**
@@ -64,7 +76,7 @@ export class ZooworkError extends Error {
64
76
  */
65
77
  export function createZooworkClient(cfg = {}) {
66
78
  const doFetch = cfg.fetch ?? ((input, init) => fetch(input, init));
67
- const base = (cfg.baseUrl ?? readEnv('ZOOWORK_BASE_URL') ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
79
+ const base = stripTrailingSlashes(cfg.baseUrl ?? readEnv('ZOOWORK_BASE_URL') ?? DEFAULT_BASE_URL);
68
80
  const auth = cfg.auth ??
69
81
  (cfg.apiKey !== undefined
70
82
  ? { apiKey: cfg.apiKey }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zoowork-ai/sdk",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "TypeScript SDK for the ZooWork Managed Agents API (Developer Preview)",
5
5
  "keywords": [
6
6
  "zoowork",