@openclaw-china/shared 2026.3.9 → 2026.3.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw-china/shared",
3
- "version": "2026.3.9",
3
+ "version": "2026.3.10",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -64,6 +64,7 @@ function createCommandNode(): CommandNode {
64
64
 
65
65
  async function runSetup(initialConfig: ConfigRoot): Promise<{
66
66
  writeConfigFile: ReturnType<typeof vi.fn>;
67
+ channels?: string[];
67
68
  }> {
68
69
  let registrar:
69
70
  | ((ctx: { program: unknown; config?: unknown; logger?: LoggerLike }) => void | Promise<void>)
@@ -178,3 +179,92 @@ describe("china setup wecom", () => {
178
179
  expect(selectOptions.some((option) => option.label === "WeCom(企业微信-智能机器人)(已配置)")).toBe(true);
179
180
  });
180
181
  });
182
+
183
+ describe("china setup dingtalk", () => {
184
+ const stdinDescriptor = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");
185
+ const stdoutDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
186
+
187
+ beforeEach(() => {
188
+ vi.clearAllMocks();
189
+ delete (globalThis as Record<PropertyKey, unknown>)[CLI_STATE_KEY];
190
+ Object.defineProperty(process.stdin, "isTTY", {
191
+ configurable: true,
192
+ value: true,
193
+ });
194
+ Object.defineProperty(process.stdout, "isTTY", {
195
+ configurable: true,
196
+ value: true,
197
+ });
198
+ });
199
+
200
+ afterEach(() => {
201
+ if (stdinDescriptor) {
202
+ Object.defineProperty(process.stdin, "isTTY", stdinDescriptor);
203
+ }
204
+ if (stdoutDescriptor) {
205
+ Object.defineProperty(process.stdout, "isTTY", stdoutDescriptor);
206
+ }
207
+ });
208
+
209
+ it("stores gateway token when dingtalk AI Card streaming is enabled", async () => {
210
+ let registrar:
211
+ | ((ctx: { program: unknown; config?: unknown; logger?: LoggerLike }) => void | Promise<void>)
212
+ | undefined;
213
+ const writeConfigFile = vi.fn(async (_cfg: ConfigRoot) => {});
214
+
215
+ registerChinaSetupCli(
216
+ {
217
+ runtime: {
218
+ config: {
219
+ writeConfigFile,
220
+ },
221
+ },
222
+ registerCli: (nextRegistrar) => {
223
+ registrar = nextRegistrar;
224
+ },
225
+ },
226
+ { channels: ["dingtalk"] }
227
+ );
228
+
229
+ selectMock.mockResolvedValueOnce("dingtalk");
230
+ textMock.mockResolvedValueOnce("ding-app-key");
231
+ textMock.mockResolvedValueOnce("ding-app-secret");
232
+ confirmMock.mockResolvedValueOnce(true);
233
+ textMock.mockResolvedValueOnce("gateway-token-123");
234
+
235
+ const program = createCommandNode();
236
+ await registrar?.({
237
+ program,
238
+ config: {
239
+ gateway: {
240
+ auth: {
241
+ token: "global-token",
242
+ },
243
+ },
244
+ },
245
+ logger: {},
246
+ });
247
+
248
+ const setupCommand = program.children.get("china")?.children.get("setup");
249
+ expect(setupCommand?.actionHandler).toBeTypeOf("function");
250
+ await setupCommand?.actionHandler?.();
251
+
252
+ expect(writeConfigFile).toHaveBeenCalledTimes(1);
253
+ const savedConfig = writeConfigFile.mock.calls[0]?.[0] as ConfigRoot;
254
+ const dingtalkConfig = savedConfig.channels?.dingtalk;
255
+
256
+ expect(dingtalkConfig?.enabled).toBe(true);
257
+ expect(dingtalkConfig?.clientId).toBe("ding-app-key");
258
+ expect(dingtalkConfig?.clientSecret).toBe("ding-app-secret");
259
+ expect(dingtalkConfig?.enableAICard).toBe(true);
260
+ expect(dingtalkConfig?.gatewayToken).toBe("gateway-token-123");
261
+
262
+ const promptMessages = textMock.mock.calls.map((call) => {
263
+ const firstArg = call[0] as { message?: string } | undefined;
264
+ return firstArg?.message ?? "";
265
+ });
266
+ expect(promptMessages).toContain(
267
+ "OpenClaw Gateway Token(流式输出必需;留空则使用全局 gateway.auth.token)"
268
+ );
269
+ });
270
+ });
@@ -266,11 +266,19 @@ function cloneConfig(cfg: ConfigRoot): ConfigRoot {
266
266
  }
267
267
  }
268
268
 
269
- function getChannelConfig(cfg: ConfigRoot, channelId: ChannelId): ConfigRecord {
270
- const channels = isRecord(cfg.channels) ? cfg.channels : {};
271
- const existing = channels[channelId];
272
- return isRecord(existing) ? existing : {};
273
- }
269
+ function getChannelConfig(cfg: ConfigRoot, channelId: ChannelId): ConfigRecord {
270
+ const channels = isRecord(cfg.channels) ? cfg.channels : {};
271
+ const existing = channels[channelId];
272
+ return isRecord(existing) ? existing : {};
273
+ }
274
+
275
+ function getGatewayAuthToken(cfg: ConfigRoot): string | undefined {
276
+ if (!isRecord(cfg.gateway)) {
277
+ return undefined;
278
+ }
279
+ const auth = isRecord(cfg.gateway.auth) ? cfg.gateway.auth : undefined;
280
+ return toTrimmedString(auth?.token);
281
+ }
274
282
 
275
283
  function getPreferredAccountConfig(channelCfg: ConfigRecord): ConfigRecord | undefined {
276
284
  const accounts = channelCfg.accounts;
@@ -456,10 +464,10 @@ class SetupPrompter {
456
464
  }
457
465
  }
458
466
 
459
- async function configureDingtalk(prompter: SetupPrompter, cfg: ConfigRoot): Promise<ConfigRoot> {
460
- section("配置 DingTalk(钉钉)");
461
- showGuideLink("dingtalk");
462
- const existing = getChannelConfig(cfg, "dingtalk");
467
+ async function configureDingtalk(prompter: SetupPrompter, cfg: ConfigRoot): Promise<ConfigRoot> {
468
+ section("配置 DingTalk(钉钉)");
469
+ showGuideLink("dingtalk");
470
+ const existing = getChannelConfig(cfg, "dingtalk");
463
471
 
464
472
  const clientId = await prompter.askText({
465
473
  label: "DingTalk clientId(AppKey)",
@@ -471,17 +479,30 @@ async function configureDingtalk(prompter: SetupPrompter, cfg: ConfigRoot): Prom
471
479
  existingValue: toTrimmedString(existing.clientSecret),
472
480
  required: true,
473
481
  });
474
- const enableAICard = await prompter.askConfirm(
475
- "启用 AI Card 流式回复(推荐关闭,使用非流式)",
476
- toBoolean(existing.enableAICard, false)
477
- );
478
-
479
- return mergeChannelConfig(cfg, "dingtalk", {
480
- clientId,
481
- clientSecret,
482
- enableAICard,
483
- });
484
- }
482
+ const enableAICard = await prompter.askConfirm(
483
+ "启用 AI Card 流式回复(推荐关闭,使用非流式)",
484
+ toBoolean(existing.enableAICard, false)
485
+ );
486
+ const patch: ConfigRecord = {
487
+ clientId,
488
+ clientSecret,
489
+ enableAICard,
490
+ };
491
+
492
+ if (enableAICard) {
493
+ const gatewayToken = await prompter.askSecret({
494
+ label: "OpenClaw Gateway Token(流式输出必需;留空则使用全局 gateway.auth.token)",
495
+ existingValue: toTrimmedString(existing.gatewayToken) ?? getGatewayAuthToken(cfg),
496
+ required: false,
497
+ });
498
+
499
+ if (gatewayToken.trim()) {
500
+ patch.gatewayToken = gatewayToken;
501
+ }
502
+ }
503
+
504
+ return mergeChannelConfig(cfg, "dingtalk", patch);
505
+ }
485
506
 
486
507
  async function configureFeishu(prompter: SetupPrompter, cfg: ConfigRoot): Promise<ConfigRoot> {
487
508
  section("配置 Feishu(飞书)");