chatccc 0.2.200 → 0.2.202
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/README.md +17 -15
- package/agent-prompts/claude_specific.md +45 -45
- package/agent-prompts/codex_specific.md +2 -2
- package/agent-prompts/cursor_specific.md +13 -13
- package/bin/cccagent.mjs +17 -17
- package/config.sample.json +27 -27
- package/im-skills/feishu-skill/receive-send-file.md +63 -63
- package/im-skills/feishu-skill/receive-send-image.md +24 -24
- package/im-skills/feishu-skill/skill.md +3 -3
- package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
- package/im-skills/wechat-file-skill/send-file.mjs +83 -83
- package/im-skills/wechat-file-skill/skill.md +10 -10
- package/im-skills/wechat-image-skill/skill.md +10 -10
- package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
- package/im-skills/wechat-video-skill/send-video.mjs +79 -79
- package/im-skills/wechat-video-skill/skill.md +10 -10
- package/package.json +1 -1
- package/scripts/postinstall-sharp-check.mjs +58 -58
- package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
- package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
- package/src/__tests__/builtin-chat-session.test.ts +277 -277
- package/src/__tests__/builtin-cli-json.test.ts +39 -39
- package/src/__tests__/builtin-config.test.ts +33 -33
- package/src/__tests__/builtin-context.test.ts +163 -163
- package/src/__tests__/builtin-file-tools.test.ts +224 -224
- package/src/__tests__/builtin-session-select.test.ts +116 -116
- package/src/__tests__/builtin-sigint.test.ts +56 -56
- package/src/__tests__/card-plain-text.test.ts +5 -5
- package/src/__tests__/cardkit.test.ts +60 -60
- package/src/__tests__/cards.test.ts +133 -122
- package/src/__tests__/ccc-adapter.test.ts +114 -114
- package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
- package/src/__tests__/chatgpt-subscription.test.ts +135 -135
- package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
- package/src/__tests__/claude-adapter.test.ts +592 -592
- package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
- package/src/__tests__/codex-raw-stream-log.test.ts +163 -163
- package/src/__tests__/codex-reset-actions.test.ts +146 -146
- package/src/__tests__/config-reload.test.ts +10 -10
- package/src/__tests__/config-sample.test.ts +18 -18
- package/src/__tests__/cursor-adapter.test.ts +226 -226
- package/src/__tests__/feishu-api.test.ts +60 -60
- package/src/__tests__/feishu-avatar.test.ts +90 -90
- package/src/__tests__/feishu-platform.test.ts +22 -22
- package/src/__tests__/format-message.test.ts +47 -47
- package/src/__tests__/jsonl-stream.test.ts +79 -79
- package/src/__tests__/orchestrator.test.ts +464 -301
- package/src/__tests__/privacy.test.ts +198 -198
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/session.test.ts +46 -44
- package/src/__tests__/shared-prefix.test.ts +36 -36
- package/src/__tests__/sim-platform.test.ts +12 -12
- package/src/__tests__/stream-state.test.ts +42 -42
- package/src/__tests__/web-ui.test.ts +209 -209
- package/src/adapters/ccc-adapter.ts +121 -121
- package/src/adapters/claude-adapter.ts +603 -603
- package/src/adapters/claude-session-meta-store.ts +120 -120
- package/src/adapters/codex-adapter.ts +380 -380
- package/src/adapters/cursor-adapter.ts +673 -673
- package/src/adapters/jsonl-stream.ts +157 -157
- package/src/adapters/raw-stream-log.ts +124 -124
- package/src/adapters/resource-monitor.ts +140 -140
- package/src/agent-delegate-task-rpc.ts +153 -153
- package/src/agent-delegate-task.ts +81 -81
- package/src/agent-reload-config-rpc.ts +34 -34
- package/src/agent-stop-stuck.ts +129 -129
- package/src/builtin/cli.ts +473 -473
- package/src/builtin/context.ts +323 -323
- package/src/builtin/file-tools.ts +1072 -1072
- package/src/builtin/index.ts +404 -404
- package/src/builtin/session-select.ts +48 -48
- package/src/builtin/sigint.ts +50 -50
- package/src/cards.ts +222 -210
- package/src/chatgpt-subscription-rpc.ts +27 -27
- package/src/chatgpt-subscription.ts +299 -299
- package/src/chrome-devtools-guard.ts +318 -318
- package/src/codex-reset-actions.ts +184 -184
- package/src/config.ts +125 -125
- package/src/feishu-api.ts +118 -118
- package/src/feishu-platform.ts +20 -20
- package/src/format-message.ts +293 -293
- package/src/litellm-proxy.ts +374 -374
- package/src/orchestrator.ts +348 -329
- package/src/privacy.ts +118 -118
- package/src/runtime-reload.ts +34 -34
- package/src/session-chat-binding.ts +6 -6
- package/src/session-name.ts +8 -8
- package/src/session.ts +177 -169
- package/src/shared-prefix.ts +29 -29
- package/src/sim-platform.ts +20 -20
- package/src/turn-cards.ts +117 -117
- package/src/web-ui.ts +205 -205
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
|
|
6
6
|
import type { PlatformAdapter } from "../platform-adapter.ts";
|
|
7
7
|
import type { SessionInfo, ToolAdapter } from "../adapters/adapter-interface.ts";
|
|
8
8
|
|
|
9
9
|
const mockStreamStates = new Map<string, { status: "running" | "done" | "stopped"; finalReply: string }>();
|
|
10
|
-
const mockGetCodexUsageSummary = vi.hoisted(() => vi.fn());
|
|
11
|
-
const mockGetCursorUsageSummary = vi.hoisted(() => vi.fn());
|
|
12
|
-
const mockGetChatGptSubscriptionStatus = vi.hoisted(() => vi.fn());
|
|
13
|
-
const mockReloadRuntimeConfig = vi.hoisted(() => vi.fn());
|
|
10
|
+
const mockGetCodexUsageSummary = vi.hoisted(() => vi.fn());
|
|
11
|
+
const mockGetCursorUsageSummary = vi.hoisted(() => vi.fn());
|
|
12
|
+
const mockGetChatGptSubscriptionStatus = vi.hoisted(() => vi.fn());
|
|
13
|
+
const mockReloadRuntimeConfig = vi.hoisted(() => vi.fn());
|
|
14
14
|
|
|
15
15
|
vi.mock("../im-skills.ts", () => ({
|
|
16
16
|
buildImSkillsPrompt: async () => "",
|
|
@@ -66,15 +66,15 @@ vi.mock("../cursor-usage.ts", () => ({
|
|
|
66
66
|
getCursorUsageSummary: mockGetCursorUsageSummary,
|
|
67
67
|
}));
|
|
68
68
|
|
|
69
|
-
vi.mock("../chatgpt-subscription.ts", () => ({
|
|
70
|
-
getChatGptSubscriptionStatus: mockGetChatGptSubscriptionStatus,
|
|
71
|
-
}));
|
|
72
|
-
|
|
73
|
-
vi.mock("../runtime-reload.ts", () => ({
|
|
74
|
-
reloadRuntimeConfig: mockReloadRuntimeConfig,
|
|
75
|
-
}));
|
|
76
|
-
|
|
77
|
-
import { handleCommand } from "../orchestrator.ts";
|
|
69
|
+
vi.mock("../chatgpt-subscription.ts", () => ({
|
|
70
|
+
getChatGptSubscriptionStatus: mockGetChatGptSubscriptionStatus,
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
vi.mock("../runtime-reload.ts", () => ({
|
|
74
|
+
reloadRuntimeConfig: mockReloadRuntimeConfig,
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
import { handleCommand } from "../orchestrator.ts";
|
|
78
78
|
import {
|
|
79
79
|
_clearAdapterCacheForTest,
|
|
80
80
|
_resetSessionRegistryFileForTest,
|
|
@@ -82,14 +82,14 @@ import {
|
|
|
82
82
|
_setAdapterForToolForTest,
|
|
83
83
|
_setSessionRegistryFileForTest,
|
|
84
84
|
_setSessionToolsFileForTest,
|
|
85
|
-
loadSessionRegistryForBinding,
|
|
86
|
-
recordSessionRegistry,
|
|
87
|
-
resetState,
|
|
88
|
-
sessionInfoMap,
|
|
89
|
-
} from "../session.ts";
|
|
85
|
+
loadSessionRegistryForBinding,
|
|
86
|
+
recordSessionRegistry,
|
|
87
|
+
resetState,
|
|
88
|
+
sessionInfoMap,
|
|
89
|
+
} from "../session.ts";
|
|
90
90
|
import { activePrompts, resetBindingState } from "../session-chat-binding.ts";
|
|
91
|
-
import { ABD_APPEND_PROMPT } from "../shared-prefix.ts";
|
|
92
|
-
import { config } from "../config.ts";
|
|
91
|
+
import { ABD_APPEND_PROMPT } from "../shared-prefix.ts";
|
|
92
|
+
import { config } from "../config.ts";
|
|
93
93
|
|
|
94
94
|
function mockPlatform(kind: "wechat" | "feishu" = "wechat"): PlatformAdapter {
|
|
95
95
|
return {
|
|
@@ -136,16 +136,16 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
136
136
|
tempDir = await mkdtemp(join(tmpdir(), "chatccc-orchestrator-"));
|
|
137
137
|
_setSessionRegistryFileForTest(join(tempDir, "session-registry.json"));
|
|
138
138
|
_setSessionToolsFileForTest(join(tempDir, "sessions.json"));
|
|
139
|
-
resetState();
|
|
140
|
-
resetBindingState();
|
|
141
|
-
config.claude.defaultAgent = true;
|
|
142
|
-
config.cursor.defaultAgent = false;
|
|
143
|
-
config.codex.defaultAgent = false;
|
|
144
|
-
mockStreamStates.clear();
|
|
145
|
-
mockGetCodexUsageSummary.mockReset();
|
|
146
|
-
mockGetCursorUsageSummary.mockReset();
|
|
147
|
-
mockGetChatGptSubscriptionStatus.mockReset();
|
|
148
|
-
mockReloadRuntimeConfig.mockReset();
|
|
139
|
+
resetState();
|
|
140
|
+
resetBindingState();
|
|
141
|
+
config.claude.defaultAgent = true;
|
|
142
|
+
config.cursor.defaultAgent = false;
|
|
143
|
+
config.codex.defaultAgent = false;
|
|
144
|
+
mockStreamStates.clear();
|
|
145
|
+
mockGetCodexUsageSummary.mockReset();
|
|
146
|
+
mockGetCursorUsageSummary.mockReset();
|
|
147
|
+
mockGetChatGptSubscriptionStatus.mockReset();
|
|
148
|
+
mockReloadRuntimeConfig.mockReset();
|
|
149
149
|
mockGetCodexUsageSummary.mockResolvedValue({
|
|
150
150
|
fiveHour: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
|
|
151
151
|
weekly: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
|
|
@@ -177,19 +177,19 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
177
177
|
displayMessage: "You've hit your usage limit",
|
|
178
178
|
autoBucketModels: ["default"],
|
|
179
179
|
});
|
|
180
|
-
mockGetChatGptSubscriptionStatus.mockResolvedValue({
|
|
181
|
-
ok: false,
|
|
182
|
-
code: "chrome_cdp_disabled",
|
|
183
|
-
reason: "Chrome CDP guard is disabled in ChatCCC config.",
|
|
184
|
-
chromeCdp: { enabled: false, port: 15166, status: "skipped" },
|
|
185
|
-
});
|
|
186
|
-
mockReloadRuntimeConfig.mockResolvedValue({
|
|
187
|
-
configPath: "C:\\Users\\me\\.chatccc\\config.json",
|
|
188
|
-
defaultAgent: "codex",
|
|
189
|
-
reloadedAt: "2026-07-02T05:00:00.000Z",
|
|
190
|
-
});
|
|
191
|
-
_setAdapterForToolForTest("claude", mockAdapter());
|
|
192
|
-
});
|
|
180
|
+
mockGetChatGptSubscriptionStatus.mockResolvedValue({
|
|
181
|
+
ok: false,
|
|
182
|
+
code: "chrome_cdp_disabled",
|
|
183
|
+
reason: "Chrome CDP guard is disabled in ChatCCC config.",
|
|
184
|
+
chromeCdp: { enabled: false, port: 15166, status: "skipped" },
|
|
185
|
+
});
|
|
186
|
+
mockReloadRuntimeConfig.mockResolvedValue({
|
|
187
|
+
configPath: "C:\\Users\\me\\.chatccc\\config.json",
|
|
188
|
+
defaultAgent: "codex",
|
|
189
|
+
reloadedAt: "2026-07-02T05:00:00.000Z",
|
|
190
|
+
});
|
|
191
|
+
_setAdapterForToolForTest("claude", mockAdapter());
|
|
192
|
+
});
|
|
193
193
|
|
|
194
194
|
afterEach(async () => {
|
|
195
195
|
resetState();
|
|
@@ -254,12 +254,12 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
254
254
|
_setAdapterForToolForTest("claude", {
|
|
255
255
|
displayName: "Claude",
|
|
256
256
|
sessionDescPrefix: "Claude Session:",
|
|
257
|
-
createSession: vi.fn(async () => ({ sessionId: "sid-wechat" })),
|
|
258
|
-
prompt,
|
|
259
|
-
getSessionInfo: async (sessionId: string): Promise<SessionInfo> => ({
|
|
260
|
-
sessionId,
|
|
261
|
-
cwd: "F:\\repo",
|
|
262
|
-
}),
|
|
257
|
+
createSession: vi.fn(async () => ({ sessionId: "sid-wechat" })),
|
|
258
|
+
prompt,
|
|
259
|
+
getSessionInfo: async (sessionId: string): Promise<SessionInfo> => ({
|
|
260
|
+
sessionId,
|
|
261
|
+
cwd: "F:\\repo",
|
|
262
|
+
}),
|
|
263
263
|
closeSession: async () => {},
|
|
264
264
|
});
|
|
265
265
|
await recordSessionRegistry({
|
|
@@ -298,9 +298,9 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
298
298
|
expect(platform.sendText).not.toHaveBeenCalledWith("wx-chat", "会话已停止。");
|
|
299
299
|
});
|
|
300
300
|
|
|
301
|
-
it("
|
|
302
|
-
const platform = mockPlatform("feishu");
|
|
303
|
-
const prompt = vi.fn(async function* (_sessionId: string, userText: string) {
|
|
301
|
+
it("resumes the Feishu p2p session in place instead of creating a group", async () => {
|
|
302
|
+
const platform = mockPlatform("feishu");
|
|
303
|
+
const prompt = vi.fn(async function* (_sessionId: string, userText: string) {
|
|
304
304
|
yield {
|
|
305
305
|
type: "assistant" as const,
|
|
306
306
|
blocks: [{ type: "text" as const, text: `收到: ${userText}` }],
|
|
@@ -309,191 +309,354 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
309
309
|
_setAdapterForToolForTest("claude", {
|
|
310
310
|
displayName: "Claude",
|
|
311
311
|
sessionDescPrefix: "Claude Session:",
|
|
312
|
-
createSession: vi.fn(async () => ({ sessionId: "sid-feishu-new" })),
|
|
313
|
-
prompt,
|
|
314
|
-
getSessionInfo: async (sessionId: string): Promise<SessionInfo> => ({
|
|
315
|
-
sessionId,
|
|
316
|
-
cwd:
|
|
317
|
-
}),
|
|
312
|
+
createSession: vi.fn(async () => ({ sessionId: "sid-feishu-new" })),
|
|
313
|
+
prompt,
|
|
314
|
+
getSessionInfo: async (sessionId: string): Promise<SessionInfo> => ({
|
|
315
|
+
sessionId,
|
|
316
|
+
cwd: homedir(),
|
|
317
|
+
}),
|
|
318
318
|
closeSession: async () => {},
|
|
319
319
|
});
|
|
320
|
-
await recordSessionRegistry({
|
|
321
|
-
chatId: "feishu-p2p",
|
|
322
|
-
sessionId: "
|
|
323
|
-
tool: "claude",
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
expect(platform.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
expect.stringContaining("帮我看一下日志"),
|
|
339
|
-
"F:\\repo",
|
|
340
|
-
expect.any(AbortSignal),
|
|
320
|
+
await recordSessionRegistry({
|
|
321
|
+
chatId: "feishu-p2p",
|
|
322
|
+
sessionId: "sid-feishu-private",
|
|
323
|
+
tool: "claude",
|
|
324
|
+
chatType: "p2p",
|
|
325
|
+
chatName: "飞书私聊",
|
|
326
|
+
running: false,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
await handleCommand(platform, "帮我看一下日志", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
330
|
+
|
|
331
|
+
expect(platform.createGroup).not.toHaveBeenCalled();
|
|
332
|
+
expect(platform.updateChatInfo).not.toHaveBeenCalled();
|
|
333
|
+
expect(prompt).toHaveBeenCalledWith(
|
|
334
|
+
"sid-feishu-private",
|
|
335
|
+
expect.stringContaining("帮我看一下日志"),
|
|
336
|
+
homedir(),
|
|
337
|
+
expect.any(AbortSignal),
|
|
341
338
|
expect.any(Object),
|
|
342
339
|
);
|
|
343
|
-
|
|
344
|
-
const registry = await loadSessionRegistryForBinding();
|
|
345
|
-
expect(registry["feishu-p2p"]).
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
const prompt = vi.fn(async function* (_sessionId: string, userText: string) {
|
|
340
|
+
|
|
341
|
+
const registry = await loadSessionRegistryForBinding();
|
|
342
|
+
expect(registry["feishu-p2p"]?.sessionId).toBe("sid-feishu-private");
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("creates the first Feishu p2p session in the OS user directory and sends the first prompt in place", async () => {
|
|
346
|
+
const platform = mockPlatform("feishu");
|
|
347
|
+
const createSession = vi.fn(async () => ({ sessionId: "sid-feishu-private" }));
|
|
348
|
+
const prompt = vi.fn(async function* (_sessionId: string, userText: string) {
|
|
349
|
+
yield {
|
|
350
|
+
type: "assistant" as const,
|
|
351
|
+
blocks: [{ type: "text" as const, text: `收到: ${userText}` }],
|
|
352
|
+
};
|
|
353
|
+
});
|
|
354
|
+
_setAdapterForToolForTest("claude", {
|
|
355
|
+
displayName: "Claude",
|
|
356
|
+
sessionDescPrefix: "Claude Session:",
|
|
357
|
+
createSession,
|
|
358
|
+
prompt,
|
|
359
|
+
getSessionInfo: async (sessionId: string): Promise<SessionInfo> => ({
|
|
360
|
+
sessionId,
|
|
361
|
+
cwd: homedir(),
|
|
362
|
+
}),
|
|
363
|
+
closeSession: async () => {},
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
await handleCommand(platform, "帮我看一下日志", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
367
|
+
|
|
368
|
+
expect(createSession).toHaveBeenCalledWith(homedir());
|
|
369
|
+
expect(platform.createGroup).not.toHaveBeenCalled();
|
|
370
|
+
expect(platform.updateChatInfo).not.toHaveBeenCalled();
|
|
371
|
+
expect(prompt).toHaveBeenCalledWith(
|
|
372
|
+
"sid-feishu-private",
|
|
373
|
+
expect.stringContaining("帮我看一下日志"),
|
|
374
|
+
homedir(),
|
|
375
|
+
expect.any(AbortSignal),
|
|
376
|
+
expect.any(Object),
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const registry = await loadSessionRegistryForBinding();
|
|
380
|
+
expect(registry["feishu-p2p"]?.sessionId).toBe("sid-feishu-private");
|
|
381
|
+
expect(registry["feishu-p2p"]?.tool).toBe("claude");
|
|
382
|
+
expect(registry["feishu-p2p"]?.chatType).toBe("p2p");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("replaces an unmarked legacy Feishu p2p binding with a home-directory session", async () => {
|
|
386
|
+
const platform = mockPlatform("feishu");
|
|
387
|
+
const createSession = vi.fn(async () => ({ sessionId: "sid-feishu-migrated" }));
|
|
388
|
+
const prompt = vi.fn(async function* () {
|
|
389
|
+
yield {
|
|
390
|
+
type: "assistant" as const,
|
|
391
|
+
blocks: [{ type: "text" as const, text: "done" }],
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
_setAdapterForToolForTest("claude", {
|
|
395
|
+
displayName: "Claude",
|
|
396
|
+
sessionDescPrefix: "Claude Session:",
|
|
397
|
+
createSession,
|
|
398
|
+
prompt,
|
|
399
|
+
getSessionInfo: async (sessionId: string): Promise<SessionInfo> => ({
|
|
400
|
+
sessionId,
|
|
401
|
+
cwd: homedir(),
|
|
402
|
+
}),
|
|
403
|
+
closeSession: async () => {},
|
|
404
|
+
});
|
|
405
|
+
await recordSessionRegistry({
|
|
406
|
+
chatId: "feishu-p2p",
|
|
407
|
+
sessionId: "sid-feishu-legacy",
|
|
408
|
+
tool: "claude",
|
|
409
|
+
chatName: "旧私聊绑定",
|
|
410
|
+
running: false,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
await handleCommand(platform, "继续", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
414
|
+
|
|
415
|
+
expect(createSession).toHaveBeenCalledWith(homedir());
|
|
416
|
+
expect(prompt).toHaveBeenCalledWith(
|
|
417
|
+
"sid-feishu-migrated",
|
|
418
|
+
expect.stringContaining("继续"),
|
|
419
|
+
homedir(),
|
|
420
|
+
expect.any(AbortSignal),
|
|
421
|
+
expect.any(Object),
|
|
422
|
+
);
|
|
423
|
+
expect(platform.createGroup).not.toHaveBeenCalled();
|
|
424
|
+
const registry = await loadSessionRegistryForBinding();
|
|
425
|
+
expect(registry["feishu-p2p"]?.sessionId).toBe("sid-feishu-migrated");
|
|
426
|
+
expect(registry["feishu-p2p"]?.chatType).toBe("p2p");
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it("creates a Feishu p2p session for /abd and sends the transformed prompt without creating a group", async () => {
|
|
430
|
+
const platform = mockPlatform("feishu");
|
|
431
|
+
const createSession = vi.fn(async () => ({ sessionId: "sid-feishu-abd" }));
|
|
432
|
+
const prompt = vi.fn(async function* (_sessionId: string, userText: string) {
|
|
352
433
|
yield {
|
|
353
434
|
type: "assistant" as const,
|
|
354
435
|
blocks: [{ type: "text" as const, text: "done" }],
|
|
355
436
|
};
|
|
356
437
|
});
|
|
357
438
|
_setAdapterForToolForTest("claude", {
|
|
358
|
-
displayName: "Claude",
|
|
359
|
-
sessionDescPrefix: "Claude Session:",
|
|
360
|
-
createSession
|
|
439
|
+
displayName: "Claude",
|
|
440
|
+
sessionDescPrefix: "Claude Session:",
|
|
441
|
+
createSession,
|
|
361
442
|
prompt,
|
|
362
443
|
getSessionInfo: async (sessionId: string): Promise<SessionInfo> => ({
|
|
363
444
|
sessionId,
|
|
364
|
-
cwd:
|
|
445
|
+
cwd: homedir(),
|
|
365
446
|
}),
|
|
366
447
|
closeSession: async () => {},
|
|
367
448
|
});
|
|
368
|
-
|
|
369
|
-
await handleCommand(platform, "/abd帮我看一下日志", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
370
|
-
|
|
371
|
-
expect(
|
|
372
|
-
expect(platform.createGroup).not.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
expect(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
expect(platform.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
await recordSessionRegistry({
|
|
434
|
-
chatId: "
|
|
435
|
-
sessionId: "sid-
|
|
436
|
-
tool: "
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
expect(platform.sendCard).toHaveBeenCalledWith(
|
|
450
|
-
"
|
|
451
|
-
"
|
|
452
|
-
expect.stringContaining(
|
|
453
|
-
"green",
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
await
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
"
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
449
|
+
|
|
450
|
+
await handleCommand(platform, "/abd帮我看一下日志", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
451
|
+
|
|
452
|
+
expect(createSession).toHaveBeenCalledWith(homedir());
|
|
453
|
+
expect(platform.createGroup).not.toHaveBeenCalled();
|
|
454
|
+
expect(platform.updateChatInfo).not.toHaveBeenCalled();
|
|
455
|
+
const userText = prompt.mock.calls[0][1];
|
|
456
|
+
expect(userText).toContain(`[User message]\n帮我看一下日志\n\n---\n${ABD_APPEND_PROMPT}\n[/User message]`);
|
|
457
|
+
expect(userText).not.toContain("/abd");
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it("keeps the Feishu p2p binding when handling commands", async () => {
|
|
461
|
+
const platform = mockPlatform("feishu");
|
|
462
|
+
await recordSessionRegistry({
|
|
463
|
+
chatId: "feishu-p2p",
|
|
464
|
+
sessionId: "sid-feishu-private",
|
|
465
|
+
tool: "claude",
|
|
466
|
+
chatType: "p2p",
|
|
467
|
+
chatName: "飞书私聊",
|
|
468
|
+
running: false,
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
await handleCommand(platform, "/model", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
472
|
+
|
|
473
|
+
expect(platform.createGroup).not.toHaveBeenCalled();
|
|
474
|
+
expect(platform.sendRawCard).toHaveBeenCalled();
|
|
475
|
+
const registry = await loadSessionRegistryForBinding();
|
|
476
|
+
expect(registry["feishu-p2p"]?.sessionId).toBe("sid-feishu-private");
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it("keeps the Feishu p2p session bound when /new creates a separate group", async () => {
|
|
480
|
+
const platform = mockPlatform("feishu");
|
|
481
|
+
const createSession = vi.fn(async () => ({ sessionId: "sid-feishu-group" }));
|
|
482
|
+
_setAdapterForToolForTest("claude", {
|
|
483
|
+
...mockAdapter("sid-feishu-group"),
|
|
484
|
+
createSession,
|
|
485
|
+
});
|
|
486
|
+
await recordSessionRegistry({
|
|
487
|
+
chatId: "feishu-p2p",
|
|
488
|
+
sessionId: "sid-feishu-private",
|
|
489
|
+
tool: "claude",
|
|
490
|
+
chatType: "p2p",
|
|
491
|
+
chatName: "飞书私聊",
|
|
492
|
+
running: false,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
await handleCommand(platform, "/new", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
496
|
+
|
|
497
|
+
expect(platform.createGroup).toHaveBeenCalledWith(expect.any(String), ["ou-user"]);
|
|
498
|
+
const registry = await loadSessionRegistryForBinding();
|
|
499
|
+
expect(registry["feishu-p2p"]?.sessionId).toBe("sid-feishu-private");
|
|
500
|
+
expect(registry["feishu-group"]?.sessionId).toBe("sid-feishu-group");
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("resets a Feishu p2p session in place with /newh and forces the OS user directory", async () => {
|
|
504
|
+
const platform = mockPlatform("feishu");
|
|
505
|
+
const createSession = vi.fn(async () => ({ sessionId: "sid-feishu-private-reset" }));
|
|
506
|
+
_setAdapterForToolForTest("claude", {
|
|
507
|
+
...mockAdapter("sid-feishu-private-reset"),
|
|
508
|
+
createSession,
|
|
509
|
+
getSessionInfo: async (sessionId: string): Promise<SessionInfo> => ({
|
|
510
|
+
sessionId,
|
|
511
|
+
cwd: "F:\\some-project",
|
|
512
|
+
}),
|
|
513
|
+
});
|
|
514
|
+
await recordSessionRegistry({
|
|
515
|
+
chatId: "feishu-p2p",
|
|
516
|
+
sessionId: "sid-feishu-private",
|
|
517
|
+
tool: "claude",
|
|
518
|
+
chatType: "p2p",
|
|
519
|
+
chatName: "飞书私聊",
|
|
520
|
+
running: false,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
await handleCommand(platform, "/newh", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
524
|
+
|
|
525
|
+
expect(createSession).toHaveBeenCalledWith(homedir());
|
|
526
|
+
expect(platform.createGroup).not.toHaveBeenCalled();
|
|
527
|
+
expect(platform.updateChatInfo).not.toHaveBeenCalled();
|
|
528
|
+
const registry = await loadSessionRegistryForBinding();
|
|
529
|
+
expect(registry["feishu-p2p"]?.sessionId).toBe("sid-feishu-private-reset");
|
|
530
|
+
expect(platform.sendCard).toHaveBeenCalledWith(
|
|
531
|
+
"feishu-p2p",
|
|
532
|
+
"Claude Code Session Reset",
|
|
533
|
+
expect.stringContaining(`${homedir()}\`(飞书私聊固定使用系统用户目录)`),
|
|
534
|
+
"green",
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it("does not allow /session to replace the dedicated Feishu p2p session", async () => {
|
|
539
|
+
const platform = mockPlatform("feishu");
|
|
540
|
+
await recordSessionRegistry({
|
|
541
|
+
chatId: "feishu-p2p",
|
|
542
|
+
sessionId: "sid-feishu-private",
|
|
543
|
+
tool: "claude",
|
|
544
|
+
chatType: "p2p",
|
|
545
|
+
chatName: "飞书私聊",
|
|
546
|
+
running: false,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
await handleCommand(platform, "/session 1", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
550
|
+
|
|
551
|
+
expect(platform.sendCard).toHaveBeenCalledWith(
|
|
552
|
+
"feishu-p2p",
|
|
553
|
+
"/session",
|
|
554
|
+
expect.stringContaining("飞书私聊使用固定的专属会话"),
|
|
555
|
+
"yellow",
|
|
556
|
+
);
|
|
557
|
+
expect(platform.updateChatInfo).not.toHaveBeenCalled();
|
|
558
|
+
const registry = await loadSessionRegistryForBinding();
|
|
559
|
+
expect(registry["feishu-p2p"]?.sessionId).toBe("sid-feishu-private");
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it("shows Claude effort switch card in an active Feishu session", async () => {
|
|
563
|
+
const platform = mockPlatform("feishu");
|
|
564
|
+
vi.mocked(platform.getChatInfo).mockResolvedValue({ name: "claude-session", description: "Claude Session: sid-claude-effort" });
|
|
565
|
+
vi.mocked(platform.extractSessionInfo).mockReturnValue({ sessionId: "sid-claude-effort", tool: "claude" });
|
|
566
|
+
await recordSessionRegistry({
|
|
567
|
+
chatId: "feishu-chat",
|
|
568
|
+
sessionId: "sid-claude-effort",
|
|
569
|
+
tool: "claude",
|
|
570
|
+
chatName: "claude-session",
|
|
571
|
+
running: false,
|
|
572
|
+
});
|
|
573
|
+
sessionInfoMap.set("feishu-chat", {
|
|
574
|
+
sessionId: "sid-claude-effort",
|
|
575
|
+
tool: "claude",
|
|
576
|
+
turnCount: 0,
|
|
577
|
+
lastContextTokens: 0,
|
|
578
|
+
startTime: Date.now(),
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
await handleCommand(platform, "/effort", "feishu-chat", "ou-user", Date.now(), "group");
|
|
582
|
+
|
|
583
|
+
expect(platform.sendRawCard).toHaveBeenCalled();
|
|
584
|
+
const card = JSON.parse(vi.mocked(platform.sendRawCard).mock.calls[0][1]);
|
|
585
|
+
const raw = JSON.stringify(card);
|
|
586
|
+
expect(raw).toContain("/effort low");
|
|
587
|
+
expect(raw).toContain("/effort xhigh");
|
|
588
|
+
expect(raw).toContain("/effort max");
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it("switches Codex effort for the current session and reflects it in /state", async () => {
|
|
592
|
+
const platform = mockPlatform("feishu");
|
|
593
|
+
vi.mocked(platform.getChatInfo).mockResolvedValue({ name: "codex-session", description: "Codex Session: sid-codex-effort" });
|
|
594
|
+
vi.mocked(platform.extractSessionInfo).mockReturnValue({ sessionId: "sid-codex-effort", tool: "codex" });
|
|
595
|
+
_setAdapterForToolForTest("codex", mockAdapter("sid-codex-effort"));
|
|
596
|
+
await recordSessionRegistry({
|
|
597
|
+
chatId: "codex-chat",
|
|
598
|
+
sessionId: "sid-codex-effort",
|
|
599
|
+
tool: "codex",
|
|
600
|
+
chatName: "codex-session",
|
|
601
|
+
running: false,
|
|
602
|
+
});
|
|
603
|
+
sessionInfoMap.set("codex-chat", {
|
|
604
|
+
sessionId: "sid-codex-effort",
|
|
605
|
+
tool: "codex",
|
|
606
|
+
turnCount: 0,
|
|
607
|
+
lastContextTokens: 0,
|
|
608
|
+
startTime: Date.now(),
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
await handleCommand(platform, "/effort xhigh", "codex-chat", "ou-user", Date.now(), "group");
|
|
612
|
+
expect(platform.sendCard).toHaveBeenCalledWith(
|
|
613
|
+
"codex-chat",
|
|
614
|
+
"Effort 切换",
|
|
615
|
+
expect.stringContaining("xhigh"),
|
|
616
|
+
"green",
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
vi.mocked(platform.sendCard).mockClear();
|
|
620
|
+
vi.mocked(platform.sendRawCard).mockClear();
|
|
621
|
+
await handleCommand(platform, "/state", "codex-chat", "ou-user", Date.now() + 1, "group");
|
|
622
|
+
|
|
623
|
+
expect(platform.sendRawCard).toHaveBeenCalled();
|
|
624
|
+
const card = JSON.parse(vi.mocked(platform.sendRawCard).mock.calls[0][1]);
|
|
625
|
+
expect(JSON.stringify(card)).toContain("xhigh");
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it("rejects /effort in Cursor sessions", async () => {
|
|
629
|
+
const platform = mockPlatform("feishu");
|
|
630
|
+
vi.mocked(platform.getChatInfo).mockResolvedValue({ name: "cursor-session", description: "Cursor Session: sid-cursor-effort" });
|
|
631
|
+
vi.mocked(platform.extractSessionInfo).mockReturnValue({ sessionId: "sid-cursor-effort", tool: "cursor" });
|
|
632
|
+
_setAdapterForToolForTest("cursor", mockAdapter("sid-cursor-effort"));
|
|
633
|
+
await recordSessionRegistry({
|
|
634
|
+
chatId: "cursor-chat",
|
|
635
|
+
sessionId: "sid-cursor-effort",
|
|
636
|
+
tool: "cursor",
|
|
637
|
+
chatName: "cursor-session",
|
|
638
|
+
running: false,
|
|
639
|
+
});
|
|
640
|
+
sessionInfoMap.set("cursor-chat", {
|
|
641
|
+
sessionId: "sid-cursor-effort",
|
|
642
|
+
tool: "cursor",
|
|
643
|
+
turnCount: 0,
|
|
644
|
+
lastContextTokens: 0,
|
|
645
|
+
startTime: Date.now(),
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
await handleCommand(platform, "/effort high", "cursor-chat", "ou-user", Date.now(), "group");
|
|
649
|
+
|
|
650
|
+
expect(platform.sendCard).toHaveBeenCalledWith(
|
|
651
|
+
"cursor-chat",
|
|
652
|
+
"Effort 切换",
|
|
653
|
+
expect.stringContaining("不支持 effort"),
|
|
654
|
+
"red",
|
|
655
|
+
);
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it("handles /usage without creating a new Feishu group", async () => {
|
|
659
|
+
const platform = mockPlatform("feishu");
|
|
497
660
|
const usage = {
|
|
498
661
|
fiveHour: { usedPercent: 37, remainingPercent: 63, resetAtEpochSeconds: 1781528212, resetAfterSeconds: 10349 },
|
|
499
662
|
weekly: { usedPercent: 12, remainingPercent: 88, resetAtEpochSeconds: 1781842926, resetAfterSeconds: 325063 },
|
|
@@ -519,38 +682,38 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
519
682
|
expect(card.elements[0].text.content).toContain("**5h:** 已用 37%,剩余 63%,重置:");
|
|
520
683
|
expect(card.elements[0].text.content).toContain("约 2小时52分钟后");
|
|
521
684
|
expect(card.elements[0].text.content).toContain("[███████░░░░░░░░░░░░░]");
|
|
522
|
-
expect(card.elements[0].text.content).toContain("**7天:** 已用 12%,剩余 88%,重置:");
|
|
685
|
+
expect(card.elements[0].text.content).toContain("**7天:** 已用 12%,剩余 88%,重置:");
|
|
523
686
|
expect(card.elements[0].text.content).toContain("约 3天18小时17分钟后");
|
|
524
687
|
expect(card.elements[0].text.content).toContain("[██░░░░░░░░░░░░░░░░░░]");
|
|
525
688
|
expect(card.elements[2].actions[0].text.content).toBe("发起重置");
|
|
526
689
|
expect(card.elements[2].actions[0].value).toEqual({ action: "codex_reset_request", availableCount: 2 });
|
|
527
|
-
expect(platform.setChatAvatar).toHaveBeenCalledWith("feishu-p2p", "codex", "idle", { codexUsage: usage });
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it("shows only the 7-day window when the 5h window is absent", async () => {
|
|
531
|
-
const platform = mockPlatform("feishu");
|
|
532
|
-
const usage = {
|
|
533
|
-
fiveHour: null,
|
|
534
|
-
weekly: {
|
|
535
|
-
usedPercent: 23,
|
|
536
|
-
remainingPercent: 77,
|
|
537
|
-
resetAtEpochSeconds: 1784510226,
|
|
538
|
-
resetAfterSeconds: 500000,
|
|
539
|
-
limitWindowSeconds: 604800,
|
|
540
|
-
},
|
|
541
|
-
rateLimitResetCreditsAvailable: 0,
|
|
542
|
-
rateLimitResetCredits: [],
|
|
543
|
-
};
|
|
544
|
-
mockGetCodexUsageSummary.mockResolvedValue(usage);
|
|
545
|
-
|
|
546
|
-
await handleCommand(platform, "/usage", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
547
|
-
|
|
548
|
-
const card = JSON.parse(vi.mocked(platform.sendRawCard).mock.calls[0][1]);
|
|
549
|
-
const content = card.elements[0].text.content as string;
|
|
550
|
-
expect(content).not.toContain("**5h:**");
|
|
551
|
-
expect(content).toContain("**7天:** 已用 23%,剩余 77%,重置:");
|
|
552
|
-
expect(platform.setChatAvatar).toHaveBeenCalledWith("feishu-p2p", "codex", "idle", { codexUsage: usage });
|
|
553
|
-
});
|
|
690
|
+
expect(platform.setChatAvatar).toHaveBeenCalledWith("feishu-p2p", "codex", "idle", { codexUsage: usage });
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it("shows only the 7-day window when the 5h window is absent", async () => {
|
|
694
|
+
const platform = mockPlatform("feishu");
|
|
695
|
+
const usage = {
|
|
696
|
+
fiveHour: null,
|
|
697
|
+
weekly: {
|
|
698
|
+
usedPercent: 23,
|
|
699
|
+
remainingPercent: 77,
|
|
700
|
+
resetAtEpochSeconds: 1784510226,
|
|
701
|
+
resetAfterSeconds: 500000,
|
|
702
|
+
limitWindowSeconds: 604800,
|
|
703
|
+
},
|
|
704
|
+
rateLimitResetCreditsAvailable: 0,
|
|
705
|
+
rateLimitResetCredits: [],
|
|
706
|
+
};
|
|
707
|
+
mockGetCodexUsageSummary.mockResolvedValue(usage);
|
|
708
|
+
|
|
709
|
+
await handleCommand(platform, "/usage", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
710
|
+
|
|
711
|
+
const card = JSON.parse(vi.mocked(platform.sendRawCard).mock.calls[0][1]);
|
|
712
|
+
const content = card.elements[0].text.content as string;
|
|
713
|
+
expect(content).not.toContain("**5h:**");
|
|
714
|
+
expect(content).toContain("**7天:** 已用 23%,剩余 77%,重置:");
|
|
715
|
+
expect(platform.setChatAvatar).toHaveBeenCalledWith("feishu-p2p", "codex", "idle", { codexUsage: usage });
|
|
716
|
+
});
|
|
554
717
|
|
|
555
718
|
it("adds ChatGPT subscription expiry to Codex /usage when CDP lookup succeeds", async () => {
|
|
556
719
|
const platform = mockPlatform("feishu");
|
|
@@ -662,16 +825,16 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
662
825
|
);
|
|
663
826
|
});
|
|
664
827
|
|
|
665
|
-
it("advertises /usage in new Codex and Cursor session ready messages", async () => {
|
|
666
|
-
const codexPlatform = mockPlatform("feishu");
|
|
667
|
-
_setAdapterForToolForTest("codex", mockAdapter("sid-codex"));
|
|
828
|
+
it("advertises /usage in new Codex and Cursor session ready messages", async () => {
|
|
829
|
+
const codexPlatform = mockPlatform("feishu");
|
|
830
|
+
_setAdapterForToolForTest("codex", mockAdapter("sid-codex"));
|
|
668
831
|
|
|
669
832
|
await handleCommand(codexPlatform, "/new codex", "feishu-p2p", "ou-user", Date.now(), "p2p");
|
|
670
833
|
|
|
671
834
|
expect(codexPlatform.sendCard).toHaveBeenCalledWith(
|
|
672
835
|
"feishu-group",
|
|
673
836
|
"Codex Session Ready",
|
|
674
|
-
expect.stringContaining("发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡。"),
|
|
837
|
+
expect.stringContaining("发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡。"),
|
|
675
838
|
"green",
|
|
676
839
|
);
|
|
677
840
|
|
|
@@ -688,50 +851,50 @@ describe("handleCommand WeChat processing ack", () => {
|
|
|
688
851
|
const claudePlatform = mockPlatform("feishu");
|
|
689
852
|
await handleCommand(claudePlatform, "/new claude", "feishu-p2p-2", "ou-user", Date.now(), "p2p");
|
|
690
853
|
|
|
691
|
-
const claudeReadyCall = vi.mocked(claudePlatform.sendCard).mock.calls.find(
|
|
692
|
-
([chatId, title]) => chatId === "feishu-group" && title === "Claude Code Session Ready",
|
|
693
|
-
);
|
|
694
|
-
expect(claudeReadyCall?.[2]).not.toContain("/usage");
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
it("reloads runtime config for the exact /reload command", async () => {
|
|
698
|
-
const platform = mockPlatform("feishu");
|
|
699
|
-
|
|
700
|
-
await handleCommand(platform, "/reload", "feishu-chat", "open-1", Date.now(), "group");
|
|
701
|
-
|
|
702
|
-
expect(mockReloadRuntimeConfig).toHaveBeenCalledWith("chat-command");
|
|
703
|
-
expect(platform.sendText).toHaveBeenCalledWith(
|
|
704
|
-
"feishu-chat",
|
|
705
|
-
expect.stringContaining("配置已重新加载"),
|
|
706
|
-
);
|
|
707
|
-
expect(platform.sendText).toHaveBeenCalledWith(
|
|
708
|
-
"feishu-chat",
|
|
709
|
-
expect.stringContaining("默认 Agent: Codex"),
|
|
710
|
-
);
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
it("allows the hidden /new ccc entry without advertising it in normal help", async () => {
|
|
714
|
-
const platform = mockPlatform("feishu");
|
|
715
|
-
_setAdapterForToolForTest("ccc", mockAdapter("session-20260702-121530-a1b2c3"));
|
|
716
|
-
|
|
717
|
-
await handleCommand(platform, "/new ccc", "feishu-p2p-ccc", "ou-user", Date.now(), "p2p");
|
|
718
|
-
|
|
719
|
-
expect(platform.updateChatInfo).toHaveBeenCalledWith(
|
|
720
|
-
"feishu-group",
|
|
721
|
-
expect.any(String),
|
|
722
|
-
"CCC Session: session-20260702-121530-a1b2c3",
|
|
723
|
-
);
|
|
724
|
-
expect(platform.sendCard).toHaveBeenCalledWith(
|
|
725
|
-
"feishu-group",
|
|
726
|
-
"CCC Agent Session Ready",
|
|
727
|
-
expect.stringContaining("CCC Agent"),
|
|
728
|
-
"green",
|
|
729
|
-
);
|
|
730
|
-
|
|
731
|
-
const helpPlatform = mockPlatform("feishu");
|
|
732
|
-
await handleCommand(helpPlatform, "hello", "feishu-group-help", "ou-user", Date.now(), "group");
|
|
733
|
-
const helpCard = vi.mocked(helpPlatform.sendRawCard).mock.calls.at(-1)?.[1] as string;
|
|
734
|
-
expect(helpCard).not.toContain("/new ccc");
|
|
735
|
-
expect(helpCard).not.toContain("CCC Agent");
|
|
736
|
-
});
|
|
737
|
-
});
|
|
854
|
+
const claudeReadyCall = vi.mocked(claudePlatform.sendCard).mock.calls.find(
|
|
855
|
+
([chatId, title]) => chatId === "feishu-group" && title === "Claude Code Session Ready",
|
|
856
|
+
);
|
|
857
|
+
expect(claudeReadyCall?.[2]).not.toContain("/usage");
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
it("reloads runtime config for the exact /reload command", async () => {
|
|
861
|
+
const platform = mockPlatform("feishu");
|
|
862
|
+
|
|
863
|
+
await handleCommand(platform, "/reload", "feishu-chat", "open-1", Date.now(), "group");
|
|
864
|
+
|
|
865
|
+
expect(mockReloadRuntimeConfig).toHaveBeenCalledWith("chat-command");
|
|
866
|
+
expect(platform.sendText).toHaveBeenCalledWith(
|
|
867
|
+
"feishu-chat",
|
|
868
|
+
expect.stringContaining("配置已重新加载"),
|
|
869
|
+
);
|
|
870
|
+
expect(platform.sendText).toHaveBeenCalledWith(
|
|
871
|
+
"feishu-chat",
|
|
872
|
+
expect.stringContaining("默认 Agent: Codex"),
|
|
873
|
+
);
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it("allows the hidden /new ccc entry without advertising it in normal help", async () => {
|
|
877
|
+
const platform = mockPlatform("feishu");
|
|
878
|
+
_setAdapterForToolForTest("ccc", mockAdapter("session-20260702-121530-a1b2c3"));
|
|
879
|
+
|
|
880
|
+
await handleCommand(platform, "/new ccc", "feishu-p2p-ccc", "ou-user", Date.now(), "p2p");
|
|
881
|
+
|
|
882
|
+
expect(platform.updateChatInfo).toHaveBeenCalledWith(
|
|
883
|
+
"feishu-group",
|
|
884
|
+
expect.any(String),
|
|
885
|
+
"CCC Session: session-20260702-121530-a1b2c3",
|
|
886
|
+
);
|
|
887
|
+
expect(platform.sendCard).toHaveBeenCalledWith(
|
|
888
|
+
"feishu-group",
|
|
889
|
+
"CCC Agent Session Ready",
|
|
890
|
+
expect.stringContaining("CCC Agent"),
|
|
891
|
+
"green",
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
const helpPlatform = mockPlatform("feishu");
|
|
895
|
+
await handleCommand(helpPlatform, "hello", "feishu-group-help", "ou-user", Date.now(), "group");
|
|
896
|
+
const helpCard = vi.mocked(helpPlatform.sendRawCard).mock.calls.at(-1)?.[1] as string;
|
|
897
|
+
expect(helpCard).not.toContain("/new ccc");
|
|
898
|
+
expect(helpCard).not.toContain("CCC Agent");
|
|
899
|
+
});
|
|
900
|
+
});
|