chatccc 0.2.199 → 0.2.200

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.
Files changed (90) hide show
  1. package/README.md +1 -1
  2. package/agent-prompts/claude_specific.md +45 -45
  3. package/agent-prompts/codex_specific.md +2 -2
  4. package/agent-prompts/cursor_specific.md +13 -13
  5. package/bin/cccagent.mjs +17 -17
  6. package/config.sample.json +27 -27
  7. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  8. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  9. package/im-skills/feishu-skill/skill.md +3 -3
  10. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  11. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  12. package/im-skills/wechat-file-skill/skill.md +10 -10
  13. package/im-skills/wechat-image-skill/skill.md +10 -10
  14. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  15. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  16. package/im-skills/wechat-video-skill/skill.md +10 -10
  17. package/package.json +1 -1
  18. package/scripts/postinstall-sharp-check.mjs +58 -58
  19. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  20. package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
  21. package/src/__tests__/builtin-chat-session.test.ts +277 -277
  22. package/src/__tests__/builtin-cli-json.test.ts +39 -39
  23. package/src/__tests__/builtin-config.test.ts +33 -33
  24. package/src/__tests__/builtin-context.test.ts +163 -163
  25. package/src/__tests__/builtin-file-tools.test.ts +224 -224
  26. package/src/__tests__/builtin-session-select.test.ts +116 -116
  27. package/src/__tests__/builtin-sigint.test.ts +56 -56
  28. package/src/__tests__/card-plain-text.test.ts +5 -5
  29. package/src/__tests__/cardkit.test.ts +60 -60
  30. package/src/__tests__/cards.test.ts +109 -109
  31. package/src/__tests__/ccc-adapter.test.ts +114 -114
  32. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  33. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  34. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  35. package/src/__tests__/claude-adapter.test.ts +592 -592
  36. package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
  37. package/src/__tests__/codex-raw-stream-log.test.ts +163 -163
  38. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  39. package/src/__tests__/config-reload.test.ts +10 -10
  40. package/src/__tests__/config-sample.test.ts +18 -18
  41. package/src/__tests__/feishu-api.test.ts +60 -60
  42. package/src/__tests__/feishu-avatar.test.ts +87 -45
  43. package/src/__tests__/feishu-platform.test.ts +22 -22
  44. package/src/__tests__/format-message.test.ts +47 -47
  45. package/src/__tests__/jsonl-stream.test.ts +79 -79
  46. package/src/__tests__/orchestrator.test.ts +227 -202
  47. package/src/__tests__/privacy.test.ts +198 -198
  48. package/src/__tests__/raw-stream-log.test.ts +106 -106
  49. package/src/__tests__/session.test.ts +40 -40
  50. package/src/__tests__/shared-prefix.test.ts +36 -36
  51. package/src/__tests__/sim-platform.test.ts +12 -12
  52. package/src/__tests__/stream-state.test.ts +42 -42
  53. package/src/__tests__/web-ui.test.ts +209 -209
  54. package/src/adapters/ccc-adapter.ts +121 -121
  55. package/src/adapters/claude-adapter.ts +603 -603
  56. package/src/adapters/claude-session-meta-store.ts +120 -120
  57. package/src/adapters/codex-adapter.ts +380 -380
  58. package/src/adapters/jsonl-stream.ts +157 -157
  59. package/src/adapters/raw-stream-log.ts +124 -124
  60. package/src/adapters/resource-monitor.ts +140 -140
  61. package/src/agent-delegate-task-rpc.ts +153 -153
  62. package/src/agent-delegate-task.ts +81 -81
  63. package/src/agent-reload-config-rpc.ts +34 -34
  64. package/src/agent-stop-stuck.ts +129 -129
  65. package/src/builtin/cli.ts +473 -473
  66. package/src/builtin/context.ts +323 -323
  67. package/src/builtin/file-tools.ts +1072 -1072
  68. package/src/builtin/index.ts +404 -404
  69. package/src/builtin/session-select.ts +48 -48
  70. package/src/builtin/sigint.ts +50 -50
  71. package/src/cards.ts +195 -195
  72. package/src/chatgpt-subscription-rpc.ts +27 -27
  73. package/src/chatgpt-subscription.ts +299 -299
  74. package/src/chrome-devtools-guard.ts +318 -318
  75. package/src/codex-reset-actions.ts +184 -184
  76. package/src/config.ts +125 -125
  77. package/src/feishu-api.ts +86 -63
  78. package/src/feishu-platform.ts +20 -20
  79. package/src/format-message.ts +293 -293
  80. package/src/litellm-proxy.ts +374 -374
  81. package/src/orchestrator.ts +183 -183
  82. package/src/privacy.ts +118 -118
  83. package/src/runtime-reload.ts +34 -34
  84. package/src/session-chat-binding.ts +6 -6
  85. package/src/session-name.ts +8 -8
  86. package/src/session.ts +141 -141
  87. package/src/shared-prefix.ts +29 -29
  88. package/src/sim-platform.ts +20 -20
  89. package/src/turn-cards.ts +117 -117
  90. package/src/web-ui.ts +205 -205
@@ -7,10 +7,10 @@ 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();
@@ -378,11 +378,11 @@ describe("handleCommand WeChat processing ack", () => {
378
378
  expect(userText).not.toContain("/abd");
379
379
  });
380
380
 
381
- it("cleans stale Feishu p2p binding but keeps valid commands from auto-creating a group", async () => {
382
- const platform = mockPlatform("feishu");
383
- await recordSessionRegistry({
384
- chatId: "feishu-p2p",
385
- sessionId: "stale-sid",
381
+ it("cleans stale Feishu p2p binding but keeps valid commands from auto-creating a group", async () => {
382
+ const platform = mockPlatform("feishu");
383
+ await recordSessionRegistry({
384
+ chatId: "feishu-p2p",
385
+ sessionId: "stale-sid",
386
386
  tool: "claude",
387
387
  chatName: "旧私聊绑定",
388
388
  running: false,
@@ -392,108 +392,108 @@ describe("handleCommand WeChat processing ack", () => {
392
392
 
393
393
  expect(platform.createGroup).not.toHaveBeenCalled();
394
394
  expect(platform.sendRawCard).toHaveBeenCalled();
395
- const registry = await loadSessionRegistryForBinding();
396
- expect(registry["feishu-p2p"]).toBeUndefined();
397
- });
398
-
399
- it("shows Claude effort switch card in an active Feishu session", async () => {
400
- const platform = mockPlatform("feishu");
401
- vi.mocked(platform.getChatInfo).mockResolvedValue({ name: "claude-session", description: "Claude Session: sid-claude-effort" });
402
- vi.mocked(platform.extractSessionInfo).mockReturnValue({ sessionId: "sid-claude-effort", tool: "claude" });
403
- await recordSessionRegistry({
404
- chatId: "feishu-chat",
405
- sessionId: "sid-claude-effort",
406
- tool: "claude",
407
- chatName: "claude-session",
408
- running: false,
409
- });
410
- sessionInfoMap.set("feishu-chat", {
411
- sessionId: "sid-claude-effort",
412
- tool: "claude",
413
- turnCount: 0,
414
- lastContextTokens: 0,
415
- startTime: Date.now(),
416
- });
417
-
418
- await handleCommand(platform, "/effort", "feishu-chat", "ou-user", Date.now(), "group");
419
-
420
- expect(platform.sendRawCard).toHaveBeenCalled();
421
- const card = JSON.parse(vi.mocked(platform.sendRawCard).mock.calls[0][1]);
422
- const raw = JSON.stringify(card);
423
- expect(raw).toContain("/effort low");
424
- expect(raw).toContain("/effort xhigh");
425
- expect(raw).toContain("/effort max");
426
- });
427
-
428
- it("switches Codex effort for the current session and reflects it in /state", async () => {
429
- const platform = mockPlatform("feishu");
430
- vi.mocked(platform.getChatInfo).mockResolvedValue({ name: "codex-session", description: "Codex Session: sid-codex-effort" });
431
- vi.mocked(platform.extractSessionInfo).mockReturnValue({ sessionId: "sid-codex-effort", tool: "codex" });
432
- _setAdapterForToolForTest("codex", mockAdapter("sid-codex-effort"));
433
- await recordSessionRegistry({
434
- chatId: "codex-chat",
435
- sessionId: "sid-codex-effort",
436
- tool: "codex",
437
- chatName: "codex-session",
438
- running: false,
439
- });
440
- sessionInfoMap.set("codex-chat", {
441
- sessionId: "sid-codex-effort",
442
- tool: "codex",
443
- turnCount: 0,
444
- lastContextTokens: 0,
445
- startTime: Date.now(),
446
- });
447
-
448
- await handleCommand(platform, "/effort xhigh", "codex-chat", "ou-user", Date.now(), "group");
449
- expect(platform.sendCard).toHaveBeenCalledWith(
450
- "codex-chat",
451
- "Effort 切换",
452
- expect.stringContaining("xhigh"),
453
- "green",
454
- );
455
-
456
- vi.mocked(platform.sendCard).mockClear();
457
- vi.mocked(platform.sendRawCard).mockClear();
458
- await handleCommand(platform, "/state", "codex-chat", "ou-user", Date.now() + 1, "group");
459
-
460
- expect(platform.sendRawCard).toHaveBeenCalled();
461
- const card = JSON.parse(vi.mocked(platform.sendRawCard).mock.calls[0][1]);
462
- expect(JSON.stringify(card)).toContain("xhigh");
463
- });
464
-
465
- it("rejects /effort in Cursor sessions", async () => {
466
- const platform = mockPlatform("feishu");
467
- vi.mocked(platform.getChatInfo).mockResolvedValue({ name: "cursor-session", description: "Cursor Session: sid-cursor-effort" });
468
- vi.mocked(platform.extractSessionInfo).mockReturnValue({ sessionId: "sid-cursor-effort", tool: "cursor" });
469
- _setAdapterForToolForTest("cursor", mockAdapter("sid-cursor-effort"));
470
- await recordSessionRegistry({
471
- chatId: "cursor-chat",
472
- sessionId: "sid-cursor-effort",
473
- tool: "cursor",
474
- chatName: "cursor-session",
475
- running: false,
476
- });
477
- sessionInfoMap.set("cursor-chat", {
478
- sessionId: "sid-cursor-effort",
479
- tool: "cursor",
480
- turnCount: 0,
481
- lastContextTokens: 0,
482
- startTime: Date.now(),
483
- });
484
-
485
- await handleCommand(platform, "/effort high", "cursor-chat", "ou-user", Date.now(), "group");
486
-
487
- expect(platform.sendCard).toHaveBeenCalledWith(
488
- "cursor-chat",
489
- "Effort 切换",
490
- expect.stringContaining("不支持 effort"),
491
- "red",
492
- );
493
- });
494
-
495
- it("handles /usage without creating a new Feishu group", async () => {
496
- const platform = mockPlatform("feishu");
395
+ const registry = await loadSessionRegistryForBinding();
396
+ expect(registry["feishu-p2p"]).toBeUndefined();
397
+ });
398
+
399
+ it("shows Claude effort switch card in an active Feishu session", async () => {
400
+ const platform = mockPlatform("feishu");
401
+ vi.mocked(platform.getChatInfo).mockResolvedValue({ name: "claude-session", description: "Claude Session: sid-claude-effort" });
402
+ vi.mocked(platform.extractSessionInfo).mockReturnValue({ sessionId: "sid-claude-effort", tool: "claude" });
403
+ await recordSessionRegistry({
404
+ chatId: "feishu-chat",
405
+ sessionId: "sid-claude-effort",
406
+ tool: "claude",
407
+ chatName: "claude-session",
408
+ running: false,
409
+ });
410
+ sessionInfoMap.set("feishu-chat", {
411
+ sessionId: "sid-claude-effort",
412
+ tool: "claude",
413
+ turnCount: 0,
414
+ lastContextTokens: 0,
415
+ startTime: Date.now(),
416
+ });
417
+
418
+ await handleCommand(platform, "/effort", "feishu-chat", "ou-user", Date.now(), "group");
419
+
420
+ expect(platform.sendRawCard).toHaveBeenCalled();
421
+ const card = JSON.parse(vi.mocked(platform.sendRawCard).mock.calls[0][1]);
422
+ const raw = JSON.stringify(card);
423
+ expect(raw).toContain("/effort low");
424
+ expect(raw).toContain("/effort xhigh");
425
+ expect(raw).toContain("/effort max");
426
+ });
427
+
428
+ it("switches Codex effort for the current session and reflects it in /state", async () => {
429
+ const platform = mockPlatform("feishu");
430
+ vi.mocked(platform.getChatInfo).mockResolvedValue({ name: "codex-session", description: "Codex Session: sid-codex-effort" });
431
+ vi.mocked(platform.extractSessionInfo).mockReturnValue({ sessionId: "sid-codex-effort", tool: "codex" });
432
+ _setAdapterForToolForTest("codex", mockAdapter("sid-codex-effort"));
433
+ await recordSessionRegistry({
434
+ chatId: "codex-chat",
435
+ sessionId: "sid-codex-effort",
436
+ tool: "codex",
437
+ chatName: "codex-session",
438
+ running: false,
439
+ });
440
+ sessionInfoMap.set("codex-chat", {
441
+ sessionId: "sid-codex-effort",
442
+ tool: "codex",
443
+ turnCount: 0,
444
+ lastContextTokens: 0,
445
+ startTime: Date.now(),
446
+ });
447
+
448
+ await handleCommand(platform, "/effort xhigh", "codex-chat", "ou-user", Date.now(), "group");
449
+ expect(platform.sendCard).toHaveBeenCalledWith(
450
+ "codex-chat",
451
+ "Effort 切换",
452
+ expect.stringContaining("xhigh"),
453
+ "green",
454
+ );
455
+
456
+ vi.mocked(platform.sendCard).mockClear();
457
+ vi.mocked(platform.sendRawCard).mockClear();
458
+ await handleCommand(platform, "/state", "codex-chat", "ou-user", Date.now() + 1, "group");
459
+
460
+ expect(platform.sendRawCard).toHaveBeenCalled();
461
+ const card = JSON.parse(vi.mocked(platform.sendRawCard).mock.calls[0][1]);
462
+ expect(JSON.stringify(card)).toContain("xhigh");
463
+ });
464
+
465
+ it("rejects /effort in Cursor sessions", async () => {
466
+ const platform = mockPlatform("feishu");
467
+ vi.mocked(platform.getChatInfo).mockResolvedValue({ name: "cursor-session", description: "Cursor Session: sid-cursor-effort" });
468
+ vi.mocked(platform.extractSessionInfo).mockReturnValue({ sessionId: "sid-cursor-effort", tool: "cursor" });
469
+ _setAdapterForToolForTest("cursor", mockAdapter("sid-cursor-effort"));
470
+ await recordSessionRegistry({
471
+ chatId: "cursor-chat",
472
+ sessionId: "sid-cursor-effort",
473
+ tool: "cursor",
474
+ chatName: "cursor-session",
475
+ running: false,
476
+ });
477
+ sessionInfoMap.set("cursor-chat", {
478
+ sessionId: "sid-cursor-effort",
479
+ tool: "cursor",
480
+ turnCount: 0,
481
+ lastContextTokens: 0,
482
+ startTime: Date.now(),
483
+ });
484
+
485
+ await handleCommand(platform, "/effort high", "cursor-chat", "ou-user", Date.now(), "group");
486
+
487
+ expect(platform.sendCard).toHaveBeenCalledWith(
488
+ "cursor-chat",
489
+ "Effort 切换",
490
+ expect.stringContaining("不支持 effort"),
491
+ "red",
492
+ );
493
+ });
494
+
495
+ it("handles /usage without creating a new Feishu group", async () => {
496
+ const platform = mockPlatform("feishu");
497
497
  const usage = {
498
498
  fiveHour: { usedPercent: 37, remainingPercent: 63, resetAtEpochSeconds: 1781528212, resetAfterSeconds: 10349 },
499
499
  weekly: { usedPercent: 12, remainingPercent: 88, resetAtEpochSeconds: 1781842926, resetAfterSeconds: 325063 },
@@ -519,7 +519,7 @@ describe("handleCommand WeChat processing ack", () => {
519
519
  expect(card.elements[0].text.content).toContain("**5h:** 已用 37%,剩余 63%,重置:");
520
520
  expect(card.elements[0].text.content).toContain("约 2小时52分钟后");
521
521
  expect(card.elements[0].text.content).toContain("[███████░░░░░░░░░░░░░]");
522
- expect(card.elements[0].text.content).toContain("**周:** 已用 12%,剩余 88%,重置:");
522
+ expect(card.elements[0].text.content).toContain("**7天:** 已用 12%,剩余 88%,重置:");
523
523
  expect(card.elements[0].text.content).toContain("约 3天18小时17分钟后");
524
524
  expect(card.elements[0].text.content).toContain("[██░░░░░░░░░░░░░░░░░░]");
525
525
  expect(card.elements[2].actions[0].text.content).toBe("发起重置");
@@ -527,6 +527,31 @@ describe("handleCommand WeChat processing ack", () => {
527
527
  expect(platform.setChatAvatar).toHaveBeenCalledWith("feishu-p2p", "codex", "idle", { codexUsage: usage });
528
528
  });
529
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
+ });
554
+
530
555
  it("adds ChatGPT subscription expiry to Codex /usage when CDP lookup succeeds", async () => {
531
556
  const platform = mockPlatform("feishu");
532
557
  mockGetCodexUsageSummary.mockResolvedValue({
@@ -637,16 +662,16 @@ describe("handleCommand WeChat processing ack", () => {
637
662
  );
638
663
  });
639
664
 
640
- it("advertises /usage in new Codex and Cursor session ready messages", async () => {
641
- const codexPlatform = mockPlatform("feishu");
642
- _setAdapterForToolForTest("codex", mockAdapter("sid-codex"));
665
+ it("advertises /usage in new Codex and Cursor session ready messages", async () => {
666
+ const codexPlatform = mockPlatform("feishu");
667
+ _setAdapterForToolForTest("codex", mockAdapter("sid-codex"));
643
668
 
644
669
  await handleCommand(codexPlatform, "/new codex", "feishu-p2p", "ou-user", Date.now(), "p2p");
645
670
 
646
671
  expect(codexPlatform.sendCard).toHaveBeenCalledWith(
647
672
  "feishu-group",
648
673
  "Codex Session Ready",
649
- expect.stringContaining("发送 **/usage** 查看 Codex 5h/周用量,以及查询/使用主动重置卡。"),
674
+ expect.stringContaining("发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡。"),
650
675
  "green",
651
676
  );
652
677
 
@@ -663,50 +688,50 @@ describe("handleCommand WeChat processing ack", () => {
663
688
  const claudePlatform = mockPlatform("feishu");
664
689
  await handleCommand(claudePlatform, "/new claude", "feishu-p2p-2", "ou-user", Date.now(), "p2p");
665
690
 
666
- const claudeReadyCall = vi.mocked(claudePlatform.sendCard).mock.calls.find(
667
- ([chatId, title]) => chatId === "feishu-group" && title === "Claude Code Session Ready",
668
- );
669
- expect(claudeReadyCall?.[2]).not.toContain("/usage");
670
- });
671
-
672
- it("reloads runtime config for the exact /reload command", async () => {
673
- const platform = mockPlatform("feishu");
674
-
675
- await handleCommand(platform, "/reload", "feishu-chat", "open-1", Date.now(), "group");
676
-
677
- expect(mockReloadRuntimeConfig).toHaveBeenCalledWith("chat-command");
678
- expect(platform.sendText).toHaveBeenCalledWith(
679
- "feishu-chat",
680
- expect.stringContaining("配置已重新加载"),
681
- );
682
- expect(platform.sendText).toHaveBeenCalledWith(
683
- "feishu-chat",
684
- expect.stringContaining("默认 Agent: Codex"),
685
- );
686
- });
687
-
688
- it("allows the hidden /new ccc entry without advertising it in normal help", async () => {
689
- const platform = mockPlatform("feishu");
690
- _setAdapterForToolForTest("ccc", mockAdapter("session-20260702-121530-a1b2c3"));
691
-
692
- await handleCommand(platform, "/new ccc", "feishu-p2p-ccc", "ou-user", Date.now(), "p2p");
693
-
694
- expect(platform.updateChatInfo).toHaveBeenCalledWith(
695
- "feishu-group",
696
- expect.any(String),
697
- "CCC Session: session-20260702-121530-a1b2c3",
698
- );
699
- expect(platform.sendCard).toHaveBeenCalledWith(
700
- "feishu-group",
701
- "CCC Agent Session Ready",
702
- expect.stringContaining("CCC Agent"),
703
- "green",
704
- );
705
-
706
- const helpPlatform = mockPlatform("feishu");
707
- await handleCommand(helpPlatform, "hello", "feishu-group-help", "ou-user", Date.now(), "group");
708
- const helpCard = vi.mocked(helpPlatform.sendRawCard).mock.calls.at(-1)?.[1] as string;
709
- expect(helpCard).not.toContain("/new ccc");
710
- expect(helpCard).not.toContain("CCC Agent");
711
- });
712
- });
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
+ });