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.
Files changed (92) hide show
  1. package/README.md +17 -15
  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 +133 -122
  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__/cursor-adapter.test.ts +226 -226
  42. package/src/__tests__/feishu-api.test.ts +60 -60
  43. package/src/__tests__/feishu-avatar.test.ts +90 -90
  44. package/src/__tests__/feishu-platform.test.ts +22 -22
  45. package/src/__tests__/format-message.test.ts +47 -47
  46. package/src/__tests__/jsonl-stream.test.ts +79 -79
  47. package/src/__tests__/orchestrator.test.ts +464 -301
  48. package/src/__tests__/privacy.test.ts +198 -198
  49. package/src/__tests__/raw-stream-log.test.ts +106 -106
  50. package/src/__tests__/session.test.ts +46 -44
  51. package/src/__tests__/shared-prefix.test.ts +36 -36
  52. package/src/__tests__/sim-platform.test.ts +12 -12
  53. package/src/__tests__/stream-state.test.ts +42 -42
  54. package/src/__tests__/web-ui.test.ts +209 -209
  55. package/src/adapters/ccc-adapter.ts +121 -121
  56. package/src/adapters/claude-adapter.ts +603 -603
  57. package/src/adapters/claude-session-meta-store.ts +120 -120
  58. package/src/adapters/codex-adapter.ts +380 -380
  59. package/src/adapters/cursor-adapter.ts +673 -673
  60. package/src/adapters/jsonl-stream.ts +157 -157
  61. package/src/adapters/raw-stream-log.ts +124 -124
  62. package/src/adapters/resource-monitor.ts +140 -140
  63. package/src/agent-delegate-task-rpc.ts +153 -153
  64. package/src/agent-delegate-task.ts +81 -81
  65. package/src/agent-reload-config-rpc.ts +34 -34
  66. package/src/agent-stop-stuck.ts +129 -129
  67. package/src/builtin/cli.ts +473 -473
  68. package/src/builtin/context.ts +323 -323
  69. package/src/builtin/file-tools.ts +1072 -1072
  70. package/src/builtin/index.ts +404 -404
  71. package/src/builtin/session-select.ts +48 -48
  72. package/src/builtin/sigint.ts +50 -50
  73. package/src/cards.ts +222 -210
  74. package/src/chatgpt-subscription-rpc.ts +27 -27
  75. package/src/chatgpt-subscription.ts +299 -299
  76. package/src/chrome-devtools-guard.ts +318 -318
  77. package/src/codex-reset-actions.ts +184 -184
  78. package/src/config.ts +125 -125
  79. package/src/feishu-api.ts +118 -118
  80. package/src/feishu-platform.ts +20 -20
  81. package/src/format-message.ts +293 -293
  82. package/src/litellm-proxy.ts +374 -374
  83. package/src/orchestrator.ts +348 -329
  84. package/src/privacy.ts +118 -118
  85. package/src/runtime-reload.ts +34 -34
  86. package/src/session-chat-binding.ts +6 -6
  87. package/src/session-name.ts +8 -8
  88. package/src/session.ts +177 -169
  89. package/src/shared-prefix.ts +29 -29
  90. package/src/sim-platform.ts +20 -20
  91. package/src/turn-cards.ts +117 -117
  92. package/src/web-ui.ts +205 -205
@@ -91,12 +91,12 @@ import {
91
91
  stopSession,
92
92
  startUnifiedDisplayLoop,
93
93
  stopUnifiedDisplayLoop,
94
- _setProcessAliveForTest,
95
- _resetProcessAliveForTest,
96
- _setProcessMonitorIntervalForTest,
97
- _resetProcessMonitorIntervalForTest,
98
- setSessionEffortOverride,
99
- } from "../session.ts";
94
+ _setProcessAliveForTest,
95
+ _resetProcessAliveForTest,
96
+ _setProcessMonitorIntervalForTest,
97
+ _resetProcessMonitorIntervalForTest,
98
+ setSessionEffortOverride,
99
+ } from "../session.ts";
100
100
  import {
101
101
  activePrompts,
102
102
  bindChatToSession,
@@ -896,17 +896,17 @@ describe("getSessionStatus", () => {
896
896
  expect(status!.effort).not.toBeNull();
897
897
  // model 必为字符串(留空时显示 '(留空)',否则为环境变量值);不应是占位符
898
898
  expect(typeof status!.model).toBe("string");
899
- expect(status!.model.length).toBeGreaterThan(0);
900
- });
901
-
902
- it("Codex session status uses the per-session effort override", async () => {
903
- mockSessionInfo("chat-codex", { sessionId: "sid-codex-effort", tool: "codex" });
904
- setSessionEffortOverride("sid-codex-effort", "xhigh");
905
-
906
- const status = await getSessionStatus("chat-codex");
907
-
908
- expect(status!.effort).toBe("xhigh");
909
- });
899
+ expect(status!.model.length).toBeGreaterThan(0);
900
+ });
901
+
902
+ it("Codex session status uses the per-session effort override", async () => {
903
+ mockSessionInfo("chat-codex", { sessionId: "sid-codex-effort", tool: "codex" });
904
+ setSessionEffortOverride("sid-codex-effort", "xhigh");
905
+
906
+ const status = await getSessionStatus("chat-codex");
907
+
908
+ expect(status!.effort).toBe("xhigh");
909
+ });
910
910
 
911
911
  it("Cursor 会话:effort 恒为 null(卡片渲染时隐藏该行,避免显示无意义的 effort)", async () => {
912
912
  mockSessionInfo("chat-cursor", { sessionId: "sid-cur", tool: "cursor" });
@@ -942,29 +942,29 @@ describe("getSessionStatus", () => {
942
942
  expect(status!.model).toBe(UNKNOWN_MODEL_PLACEHOLDER);
943
943
  });
944
944
 
945
- it("Cursor 会话:adapter.getSessionInfo 抛错时降级为占位符(不阻塞 /state)", async () => {
946
- mockSessionInfo("chat-cursor", { sessionId: "sid-cur", tool: "cursor" });
947
- _setAdapterForToolForTest(
948
- "cursor",
949
- mockAdapter(() => {
950
- throw new Error("simulated adapter failure");
951
- }),
952
- );
953
- const status = await getSessionStatus("chat-cursor");
954
- expect(status!.model).toBe(UNKNOWN_MODEL_PLACEHOLDER);
955
- expect(status!.effort).toBeNull();
956
- });
957
-
958
- it("CCC Agent 会话:model 来自 ccc 配置且 effort 恒为 null", async () => {
959
- mockSessionInfo("chat-ccc", { sessionId: "session-ccc", tool: "ccc" });
960
-
961
- const status = await getSessionStatus("chat-ccc");
962
-
963
- expect(status!.model).toBeTruthy();
964
- expect(status!.model).not.toBe(UNKNOWN_MODEL_PLACEHOLDER);
965
- expect(status!.effort).toBeNull();
966
- });
967
- });
945
+ it("Cursor 会话:adapter.getSessionInfo 抛错时降级为占位符(不阻塞 /state)", async () => {
946
+ mockSessionInfo("chat-cursor", { sessionId: "sid-cur", tool: "cursor" });
947
+ _setAdapterForToolForTest(
948
+ "cursor",
949
+ mockAdapter(() => {
950
+ throw new Error("simulated adapter failure");
951
+ }),
952
+ );
953
+ const status = await getSessionStatus("chat-cursor");
954
+ expect(status!.model).toBe(UNKNOWN_MODEL_PLACEHOLDER);
955
+ expect(status!.effort).toBeNull();
956
+ });
957
+
958
+ it("CCC Agent 会话:model 来自 ccc 配置且 effort 恒为 null", async () => {
959
+ mockSessionInfo("chat-ccc", { sessionId: "session-ccc", tool: "ccc" });
960
+
961
+ const status = await getSessionStatus("chat-ccc");
962
+
963
+ expect(status!.model).toBeTruthy();
964
+ expect(status!.model).not.toBe(UNKNOWN_MODEL_PLACEHOLDER);
965
+ expect(status!.effort).toBeNull();
966
+ });
967
+ });
968
968
 
969
969
  describe("getAllSessionsStatus", () => {
970
970
  let registryFile = "";
@@ -1005,9 +1005,10 @@ describe("getAllSessionsStatus", () => {
1005
1005
 
1006
1006
  it("returns statuses from disk registry", async () => {
1007
1007
  await recordSessionRegistry({
1008
- chatId: "chat1",
1009
- sessionId: "s1",
1010
- tool: "claude",
1008
+ chatId: "chat1",
1009
+ sessionId: "s1",
1010
+ tool: "claude",
1011
+ chatType: "p2p",
1011
1012
  chatName: "test-chat-1",
1012
1013
  turnCount: 2,
1013
1014
  startTime: 1000,
@@ -1027,7 +1028,8 @@ describe("getAllSessionsStatus", () => {
1027
1028
 
1028
1029
  const result = await getAllSessionsStatus();
1029
1030
  expect(result).toHaveLength(2);
1030
- expect(result[0].chatId).toBe("chat1");
1031
+ expect(result[0].chatId).toBe("chat1");
1032
+ expect(result[0].chatType).toBe("p2p");
1031
1033
  // running=true in registry doesn't make it active — must be in activePrompts
1032
1034
  expect(result[0].active).toBe(false);
1033
1035
  expect(result[0].turnCount).toBe(2);
@@ -1,36 +1,36 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- ABD_APPEND_PROMPT,
5
- applySharedPrefix,
6
- } from "../shared-prefix.ts";
7
-
8
- describe("applySharedPrefix", () => {
9
- it("leaves normal messages unchanged", () => {
10
- expect(applySharedPrefix("帮我分析")).toEqual({
11
- matched: false,
12
- text: "帮我分析",
13
- body: "帮我分析",
14
- });
15
- });
16
-
17
- it("removes /abd without requiring a space", () => {
18
- const result = applySharedPrefix("/abd帮我分析");
19
-
20
- expect(result.matched).toBe(true);
21
- expect(result.body).toBe("帮我分析");
22
- expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
23
- });
24
-
25
- it("removes whitespace after /abd", () => {
26
- const result = applySharedPrefix("/abd 帮我分析");
27
-
28
- expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
29
- });
30
-
31
- it("keeps the appendix when the user body is empty", () => {
32
- const result = applySharedPrefix("/abd ");
33
-
34
- expect(result.text).toBe(`---\n${ABD_APPEND_PROMPT}`);
35
- });
36
- });
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ ABD_APPEND_PROMPT,
5
+ applySharedPrefix,
6
+ } from "../shared-prefix.ts";
7
+
8
+ describe("applySharedPrefix", () => {
9
+ it("leaves normal messages unchanged", () => {
10
+ expect(applySharedPrefix("帮我分析")).toEqual({
11
+ matched: false,
12
+ text: "帮我分析",
13
+ body: "帮我分析",
14
+ });
15
+ });
16
+
17
+ it("removes /abd without requiring a space", () => {
18
+ const result = applySharedPrefix("/abd帮我分析");
19
+
20
+ expect(result.matched).toBe(true);
21
+ expect(result.body).toBe("帮我分析");
22
+ expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
23
+ });
24
+
25
+ it("removes whitespace after /abd", () => {
26
+ const result = applySharedPrefix("/abd 帮我分析");
27
+
28
+ expect(result.text).toBe(`帮我分析\n\n---\n${ABD_APPEND_PROMPT}`);
29
+ });
30
+
31
+ it("keeps the appendix when the user body is empty", () => {
32
+ const result = applySharedPrefix("/abd ");
33
+
34
+ expect(result.text).toBe(`---\n${ABD_APPEND_PROMPT}`);
35
+ });
36
+ });
@@ -62,21 +62,21 @@ describe("SimulatedPlatform", () => {
62
62
  await expect(SimulatedPlatform.setChatAvatar("t", "ch1", "claude", "busy")).resolves.toBeUndefined();
63
63
  });
64
64
 
65
- it("纯函数 extractSessionInfo 正常工作", () => {
66
- const result = SimulatedPlatform.extractSessionInfo("Claude Code Session: a1b2c3d4-e5f6-7890-abcd-ef1234567890");
67
- expect(result).toEqual({ sessionId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", tool: "claude" });
68
- });
69
-
70
- it("pure extractSessionInfo recognizes ccc session ids", () => {
71
- const result = SimulatedPlatform.extractSessionInfo("CCC Session: session-20260702-121530-a1b2c3");
72
- expect(result).toEqual({ sessionId: "session-20260702-121530-a1b2c3", tool: "ccc" });
73
- });
74
-
75
- it("纯函数 formatDelayNotice 正常工作", () => {
65
+ it("纯函数 extractSessionInfo 正常工作", () => {
66
+ const result = SimulatedPlatform.extractSessionInfo("Claude Code Session: a1b2c3d4-e5f6-7890-abcd-ef1234567890");
67
+ expect(result).toEqual({ sessionId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", tool: "claude" });
68
+ });
69
+
70
+ it("pure extractSessionInfo recognizes ccc session ids", () => {
71
+ const result = SimulatedPlatform.extractSessionInfo("CCC Session: session-20260702-121530-a1b2c3");
72
+ expect(result).toEqual({ sessionId: "session-20260702-121530-a1b2c3", tool: "ccc" });
73
+ });
74
+
75
+ it("纯函数 formatDelayNotice 正常工作", () => {
76
76
  const notice = SimulatedPlatform.formatDelayNotice(Date.now() - 20 * 60 * 1000, "测试消息");
77
77
  expect(notice).toBeDefined();
78
78
  expect(notice).toContain("延迟送达");
79
79
  // 近期消息不触发
80
80
  expect(SimulatedPlatform.formatDelayNotice(Date.now(), "test")).toBeNull();
81
81
  });
82
- });
82
+ });
@@ -15,12 +15,12 @@ vi.mock("../config.ts", async () => {
15
15
  });
16
16
 
17
17
  const {
18
- writeStreamState,
19
- readStreamState,
20
- createEmptyStreamState,
21
- isFinalReplySentForTurn,
22
- markFinalReplySent,
23
- STREAMS_DIR,
18
+ writeStreamState,
19
+ readStreamState,
20
+ createEmptyStreamState,
21
+ isFinalReplySentForTurn,
22
+ markFinalReplySent,
23
+ STREAMS_DIR,
24
24
  _setRenameForTest,
25
25
  _resetRenameForTest,
26
26
  } = await import("../stream-state.ts");
@@ -119,36 +119,36 @@ describe("writeStreamState — atomic rename", () => {
119
119
  const got = await readStreamState("never-existed");
120
120
  expect(got).toBeNull();
121
121
  });
122
- it("marks final reply delivery for the matching terminal turn only", async () => {
123
- const state = createEmptyStreamState("sid-delivered", "/tmp", "claude", 2);
124
- state.status = "done";
125
- state.finalReply = "done";
126
- await writeStreamState(state);
127
-
128
- await markFinalReplySent("sid-delivered", 2, 12345);
129
-
130
- const got = await readStreamState("sid-delivered");
131
- expect(got).not.toBeNull();
132
- expect(got!.finalReplySentTurn).toBe(2);
133
- expect(got!.finalReplySentAt).toBe(12345);
134
- expect(isFinalReplySentForTurn(got!)).toBe(true);
135
- });
136
-
137
- it("does not mark a running state or a different turn as delivered", async () => {
138
- const state = createEmptyStreamState("sid-running", "/tmp", "claude", 3);
139
- await writeStreamState(state);
140
-
141
- await markFinalReplySent("sid-running", 2, 12345);
142
- await markFinalReplySent("sid-running", 3, 12345);
143
-
144
- const got = await readStreamState("sid-running");
145
- expect(got).not.toBeNull();
146
- expect(got!.finalReplySentTurn).toBeUndefined();
147
- expect(isFinalReplySentForTurn(got!)).toBe(false);
148
- });
149
- });
150
-
151
- describe("createEmptyStreamState", () => {
122
+ it("marks final reply delivery for the matching terminal turn only", async () => {
123
+ const state = createEmptyStreamState("sid-delivered", "/tmp", "claude", 2);
124
+ state.status = "done";
125
+ state.finalReply = "done";
126
+ await writeStreamState(state);
127
+
128
+ await markFinalReplySent("sid-delivered", 2, 12345);
129
+
130
+ const got = await readStreamState("sid-delivered");
131
+ expect(got).not.toBeNull();
132
+ expect(got!.finalReplySentTurn).toBe(2);
133
+ expect(got!.finalReplySentAt).toBe(12345);
134
+ expect(isFinalReplySentForTurn(got!)).toBe(true);
135
+ });
136
+
137
+ it("does not mark a running state or a different turn as delivered", async () => {
138
+ const state = createEmptyStreamState("sid-running", "/tmp", "claude", 3);
139
+ await writeStreamState(state);
140
+
141
+ await markFinalReplySent("sid-running", 2, 12345);
142
+ await markFinalReplySent("sid-running", 3, 12345);
143
+
144
+ const got = await readStreamState("sid-running");
145
+ expect(got).not.toBeNull();
146
+ expect(got!.finalReplySentTurn).toBeUndefined();
147
+ expect(isFinalReplySentForTurn(got!)).toBe(false);
148
+ });
149
+ });
150
+
151
+ describe("createEmptyStreamState", () => {
152
152
  it("生成的 state 字段正确且 status=running", () => {
153
153
  const s = createEmptyStreamState("sid-X", "/cwd", "cursor", 5);
154
154
  expect(s.sessionId).toBe("sid-X");
@@ -156,9 +156,9 @@ describe("createEmptyStreamState", () => {
156
156
  expect(s.tool).toBe("cursor");
157
157
  expect(s.turnCount).toBe(5);
158
158
  expect(s.status).toBe("running");
159
- expect(s.accumulatedContent).toBe("");
160
- expect(s.finalReply).toBe("");
161
- expect(s.finalReplySentTurn).toBeUndefined();
162
- expect(s.chunkCount).toBe(0);
163
- });
164
- });
159
+ expect(s.accumulatedContent).toBe("");
160
+ expect(s.finalReply).toBe("");
161
+ expect(s.finalReplySentTurn).toBeUndefined();
162
+ expect(s.chunkCount).toBe(0);
163
+ });
164
+ });