@zoowork-ai/sdk 0.4.0 → 0.4.2
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 +40 -0
- package/dist/client.d.ts +59 -5
- package/dist/client.js +13 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,46 @@
|
|
|
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.2 — 2026-08-25
|
|
7
|
+
|
|
8
|
+
### Documentation
|
|
9
|
+
|
|
10
|
+
- **`addChannel` is idempotent, not an upsert — 0.3.4 said the opposite.** Re-posting an
|
|
11
|
+
identical body for the same `platform` + `account` replays the binding you already have and
|
|
12
|
+
answers `201` again; that same pair with a **different** `config` answers
|
|
13
|
+
`409 channel.conflict`. Rotating credentials therefore means `removeChannel` and then a
|
|
14
|
+
fresh `addChannel` — a plain re-add fails. The earlier note came from re-posting the same
|
|
15
|
+
body, where a replay cannot be told apart from an overwrite; the conflict path was measured
|
|
16
|
+
on staging 2026-08-25. No signature or behaviour change.
|
|
17
|
+
|
|
18
|
+
## 0.4.1 — 2026-08-25
|
|
19
|
+
|
|
20
|
+
### Internal
|
|
21
|
+
|
|
22
|
+
- **Trailing slashes are stripped from the base URL by a scan rather than `/\/+$/`.** Same
|
|
23
|
+
output for every input; the regex retried at every start position on a long run of
|
|
24
|
+
slashes, which CodeQL flags as polynomial. Nothing hostile reaches it — the input is the
|
|
25
|
+
caller's own base URL — so this closes an alert rather than a vulnerability.
|
|
26
|
+
|
|
27
|
+
### Documentation
|
|
28
|
+
|
|
29
|
+
- **`account` on `addChannel` / `startFeishuSetup` now documents what it actually is.** It
|
|
30
|
+
names a binding and is part of its identity — `updateChannel` and `removeChannel` look a
|
|
31
|
+
binding up by `platform` + `account`, and nothing renames one. The four constraints, all
|
|
32
|
+
staging-verified 2026-08-25:
|
|
33
|
+
- the name is unique per USER across every agent, not per agent;
|
|
34
|
+
- `'default'` is usually taken already by a binding the app made, which this API will not
|
|
35
|
+
adopt — it answers `409 channel.conflict`;
|
|
36
|
+
- the format is `^[a-z0-9][a-z0-9_-]{0,63}$` plus three reserved words, and nothing is
|
|
37
|
+
normalized for you;
|
|
38
|
+
- this SDK cannot pre-check a name, because `listChannels` is scoped to one agent while the
|
|
39
|
+
constraint spans the whole account.
|
|
40
|
+
- **`startFeishuSetup` warns that a name clash surfaces after the scan.** Approving the QR
|
|
41
|
+
registers a new app in the Feishu workspace before the binding is written, so a clash costs
|
|
42
|
+
a scan and leaves that app behind; retrying under the same name repeats both.
|
|
43
|
+
|
|
44
|
+
No runtime change — comments only.
|
|
45
|
+
|
|
6
46
|
## 0.4.0 — 2026-08-25
|
|
7
47
|
|
|
8
48
|
### 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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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;
|
|
@@ -1012,8 +1056,12 @@ export interface ZooworkClient {
|
|
|
1012
1056
|
* path for Slack and WeCom; Feishu also has the QR flow. See {@link ChannelPlatform} for what
|
|
1013
1057
|
* binds and what does not.
|
|
1014
1058
|
*
|
|
1015
|
-
* **It is
|
|
1016
|
-
* `201` again and
|
|
1059
|
+
* **It is idempotent, not an upsert.** Re-posting an identical body for the same
|
|
1060
|
+
* `platform` + `account` answers `201` again and replays the binding you already have — it
|
|
1061
|
+
* neither creates a second channel nor overwrites the first. That same pair with a DIFFERENT
|
|
1062
|
+
* `config` answers `409 channel.conflict`, so rotating credentials means
|
|
1063
|
+
* {@link removeChannel} and then a fresh `addChannel`; a plain re-add fails
|
|
1064
|
+
* (staging-verified 2026-08-25).
|
|
1017
1065
|
*
|
|
1018
1066
|
* ⚠️ **201 means STORED, not WORKING.** Credentials are not validated at bind time: a channel
|
|
1019
1067
|
* created from deliberately bogus credentials still answered 201 with `health: 'unknown'`,
|
|
@@ -1049,6 +1097,12 @@ export interface ZooworkClient {
|
|
|
1049
1097
|
* Observed defaults: `expires_in: 600`, `poll_interval: 5`. `brand` picks the real host —
|
|
1050
1098
|
* `'feishu'` answers an `open.feishu.cn` URI, `'lark'` an `open.larksuite.com` one, so the
|
|
1051
1099
|
* brand has to match the workspace the person will approve it in.
|
|
1100
|
+
*
|
|
1101
|
+
* Pick `account` before you show the QR. Approving the scan registers a NEW app in that
|
|
1102
|
+
* Feishu workspace, and only then does the binding get written — so a name clash surfaces as
|
|
1103
|
+
* `409 channel.conflict` AFTER someone has scanned, with the freshly registered app left
|
|
1104
|
+
* behind in their workspace. Retrying under the same name repeats both. See
|
|
1105
|
+
* {@link AddChannelInput.account} for how names are scoped.
|
|
1052
1106
|
*/
|
|
1053
1107
|
startFeishuSetup(agentId: string, input?: FeishuSetupInput): Promise<FeishuSetupSession>;
|
|
1054
1108
|
/**
|
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)
|
|
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 }
|