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.
- package/README.md +1 -1
- 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 +109 -109
- 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__/feishu-api.test.ts +60 -60
- package/src/__tests__/feishu-avatar.test.ts +87 -45
- 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 +227 -202
- package/src/__tests__/privacy.test.ts +198 -198
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/session.test.ts +40 -40
- 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/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 +195 -195
- 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 +86 -63
- 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 +183 -183
- 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 +141 -141
- 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
|
@@ -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 = "";
|
|
@@ -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
|
+
});
|