@openclaw-china/shared 2026.3.9-1 → 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 +1 -1
- package/src/cli/china-setup.test.ts +90 -0
- package/src/cli/china-setup.ts +41 -20
package/package.json
CHANGED
|
@@ -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
|
+
});
|
package/src/cli/china-setup.ts
CHANGED
|
@@ -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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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(飞书)");
|