chatccc 0.2.238 → 0.2.240
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 +5 -3
- package/package.json +1 -1
- package/src/__tests__/builtin-chat-session.test.ts +36 -0
- package/src/__tests__/config.test.ts +17 -2
- package/src/__tests__/session-ccc-config.test.ts +45 -0
- package/src/adapters/ccc-adapter.ts +5 -1
- package/src/builtin/index.ts +11 -3
- package/src/config-utils.ts +13 -3
- package/src/config.ts +13 -14
- package/src/session.ts +7 -5
package/README.md
CHANGED
|
@@ -210,9 +210,11 @@ chatccc
|
|
|
210
210
|
|
|
211
211
|
Claude Code、Cursor 和 Codex 需要对应的本地工具;CCC Agent 内置于 ChatCCC,开箱即用,**模型接入不限于 DeepSeek**——它走 OpenAI 兼容协议,任意兼容端点都可以直接替换(详见下文 CCC Agent)。
|
|
212
212
|
|
|
213
|
-
#### CCC Agent
|
|
214
|
-
|
|
215
|
-
CCC Agent 是 ChatCCC 内置的编程 Agent,不需要额外安装 CLI,开箱即用。在首次配置向导或 Web 管理页中启用后,填写 API Key、Base URL 和模型即可使用;它可以设为 `/new` 的默认 Agent,也可以通过 `/new ccc` 显式创建会话。
|
|
213
|
+
#### CCC Agent
|
|
214
|
+
|
|
215
|
+
CCC Agent 是 ChatCCC 内置的编程 Agent,不需要额外安装 CLI,开箱即用。在首次配置向导或 Web 管理页中启用后,填写 API Key、Base URL 和模型即可使用;它可以设为 `/new` 的默认 Agent,也可以通过 `/new ccc` 显式创建会话。
|
|
216
|
+
|
|
217
|
+
ChatCCC 会把 `ccc.DEEPSEEK_API_KEY`、`ccc.DEEPSEEK_BASE_URL`、模型和 effort 显式传给内置 Agent;这些配置不会回退读取 `~/.deepccc/config.json`,因此用户无需安装或配置独立的 `deepccc` 包。API Key 为空时 CCC Agent 会自动保持禁用。
|
|
216
218
|
|
|
217
219
|
**API 支持不限于 DeepSeek。** CCC Agent 底层使用 OpenAI 兼容协议(`@ai-sdk/openai-compatible`),DeepSeek 只是出厂默认端点。`ccc.DEEPSEEK_API_KEY` / `ccc.DEEPSEEK_BASE_URL` 可以指向**任意 OpenAI 兼容服务**,例如:
|
|
218
220
|
|
package/package.json
CHANGED
|
@@ -56,6 +56,42 @@ afterEach(() => {
|
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
describe("ChatSession context management", () => {
|
|
59
|
+
it("keeps the generalized evidence gate in the stable system prompt prefix", async () => {
|
|
60
|
+
const { ChatSession } = await import("../builtin/index.ts");
|
|
61
|
+
const dir = await mkdtemp(join(tmpdir(), "deepccc-session-evidence-gate-"));
|
|
62
|
+
await writeFile(join(dir, "AGENTS.md"), "PROJECT GUIDANCE MARKER", "utf-8");
|
|
63
|
+
streamTextMock.mockReturnValueOnce({ textStream: textStream("done") });
|
|
64
|
+
|
|
65
|
+
const session = new ChatSession(
|
|
66
|
+
{ apiKey: "sk-test" },
|
|
67
|
+
{
|
|
68
|
+
cwd: dir,
|
|
69
|
+
sessionId: "evidence-gate",
|
|
70
|
+
systemPrompt: "CUSTOM PROMPT MARKER",
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
await collect(session.chat("diagnose a consequential problem"));
|
|
74
|
+
|
|
75
|
+
const system = streamTextMock.mock.calls.at(-1)?.[0].system as string;
|
|
76
|
+
expect(system).toContain("## Evidence-Gated Conclusions");
|
|
77
|
+
expect(system).toContain("source of truth");
|
|
78
|
+
expect(system).toContain("direct observations from inferences");
|
|
79
|
+
expect(system).toContain("plausible alternative explanations");
|
|
80
|
+
expect(system).toContain("runtime behavior for runtime claims");
|
|
81
|
+
expect(system).toContain("state uncertainty");
|
|
82
|
+
expect(system).toContain("Do not repeat checks once decisive evidence exists");
|
|
83
|
+
expect(system).not.toContain("CodesForUnity");
|
|
84
|
+
expect(system.indexOf("## Evidence-Gated Conclusions")).toBeLessThan(
|
|
85
|
+
system.indexOf("PROJECT GUIDANCE MARKER"),
|
|
86
|
+
);
|
|
87
|
+
expect(system.indexOf("## Evidence-Gated Conclusions")).toBeLessThan(
|
|
88
|
+
system.indexOf("Current working directory"),
|
|
89
|
+
);
|
|
90
|
+
expect(system.indexOf("## Evidence-Gated Conclusions")).toBeLessThan(
|
|
91
|
+
system.indexOf("CUSTOM PROMPT MARKER"),
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
59
95
|
it("injects cwd project instruction files before runtime workspace details", async () => {
|
|
60
96
|
const { ChatSession } = await import("../builtin/index.ts");
|
|
61
97
|
const dir = await mkdtemp(join(tmpdir(), "deepccc-session-instructions-"));
|
|
@@ -15,8 +15,23 @@ import {
|
|
|
15
15
|
autoDetectCursorPath,
|
|
16
16
|
normalizeOptionalConfigField,
|
|
17
17
|
parseGitTimeoutSeconds,
|
|
18
|
-
readToolCliPath,
|
|
19
|
-
|
|
18
|
+
readToolCliPath,
|
|
19
|
+
resolveCccEnabled,
|
|
20
|
+
} from "../config-utils.ts";
|
|
21
|
+
|
|
22
|
+
describe("resolveCccEnabled", () => {
|
|
23
|
+
it("never enables CCC Agent without a ChatCCC API key", () => {
|
|
24
|
+
expect(resolveCccEnabled(true, "")).toBe(false);
|
|
25
|
+
expect(resolveCccEnabled(true, " ")).toBe(false);
|
|
26
|
+
expect(resolveCccEnabled(undefined, undefined)).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("respects the explicit enabled flag when a ChatCCC API key exists", () => {
|
|
30
|
+
expect(resolveCccEnabled(undefined, "sk-chatccc")).toBe(true);
|
|
31
|
+
expect(resolveCccEnabled(true, "sk-chatccc")).toBe(true);
|
|
32
|
+
expect(resolveCccEnabled(false, "sk-chatccc")).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
20
35
|
|
|
21
36
|
describe("parseGitTimeoutSeconds", () => {
|
|
22
37
|
it("returns default when raw is undefined", () => {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const createCccAdapterMock = vi.hoisted(() => vi.fn(() => ({
|
|
4
|
+
displayName: "CCC Agent",
|
|
5
|
+
sessionDescPrefix: "CCC Session:",
|
|
6
|
+
createSession: vi.fn(),
|
|
7
|
+
prompt: vi.fn(),
|
|
8
|
+
getSessionInfo: vi.fn(),
|
|
9
|
+
closeSession: vi.fn(),
|
|
10
|
+
})));
|
|
11
|
+
|
|
12
|
+
vi.mock("../adapters/ccc-adapter.ts", () => ({
|
|
13
|
+
createCccAdapter: createCccAdapterMock,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
import { config } from "../config.ts";
|
|
17
|
+
import { _clearAdapterCacheForTest, getAdapterForTool } from "../session.ts";
|
|
18
|
+
|
|
19
|
+
describe("CCC Agent ChatCCC configuration", () => {
|
|
20
|
+
const original = { ...config.ccc };
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
Object.assign(config.ccc, original);
|
|
24
|
+
_clearAdapterCacheForTest();
|
|
25
|
+
createCccAdapterMock.mockClear();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("injects ChatCCC credentials and endpoint instead of relying on ~/.deepccc", () => {
|
|
29
|
+
Object.assign(config.ccc, {
|
|
30
|
+
DEEPSEEK_API_KEY: "chatccc-api-key",
|
|
31
|
+
DEEPSEEK_BASE_URL: "https://chatccc.example.com/v1",
|
|
32
|
+
model: "chatccc-model",
|
|
33
|
+
effort: "high",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
getAdapterForTool("ccc");
|
|
37
|
+
|
|
38
|
+
expect(createCccAdapterMock).toHaveBeenCalledWith({
|
|
39
|
+
apiKey: "chatccc-api-key",
|
|
40
|
+
baseURL: "https://chatccc.example.com/v1",
|
|
41
|
+
model: "chatccc-model",
|
|
42
|
+
effort: "high",
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -42,8 +42,12 @@ function toChatSessionOptions(
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export function createCccAdapter(options: CccAdapterOptions = {}): ToolAdapter {
|
|
45
|
+
if (!options.apiKey?.trim()) {
|
|
46
|
+
throw new Error("ChatCCC 未配置 CCC Agent API Key。请先填写 ccc.DEEPSEEK_API_KEY 后再启用 CCC Agent。");
|
|
47
|
+
}
|
|
48
|
+
|
|
45
49
|
const chatConfig: ChatSessionConfig = {
|
|
46
|
-
|
|
50
|
+
apiKey: options.apiKey,
|
|
47
51
|
...(options.baseURL !== undefined ? { baseURL: options.baseURL } : {}),
|
|
48
52
|
...(options.model !== undefined ? { model: options.model } : {}),
|
|
49
53
|
...(options.effort !== undefined ? { effort: options.effort } : {}),
|
package/src/builtin/index.ts
CHANGED
|
@@ -42,9 +42,17 @@ const SYSTEM_PROMPT = [
|
|
|
42
42
|
"- Respond in the user's language unless they ask otherwise.",
|
|
43
43
|
"- Prefer direct, usable answers and concrete actions over long explanations.",
|
|
44
44
|
"- For code tasks, inspect the relevant files before editing and verify with tests or checks when practical.",
|
|
45
|
-
"- Preserve user work. Do not overwrite concurrent changes unless the user explicitly asks.",
|
|
46
|
-
"- Keep immutable platform rules above project guidance and runtime details.",
|
|
47
|
-
|
|
45
|
+
"- Preserve user work. Do not overwrite concurrent changes unless the user explicitly asks.",
|
|
46
|
+
"- Keep immutable platform rules above project guidance and runtime details.",
|
|
47
|
+
"",
|
|
48
|
+
"## Evidence-Gated Conclusions",
|
|
49
|
+
"- Apply this gate before consequential claims or actions that could affect code, data, deployments, or user decisions, and whenever the available evidence is indirect.",
|
|
50
|
+
"- Identify the claim and its authoritative source of truth. Separate direct observations from inferences, and test plausible alternative explanations before choosing one.",
|
|
51
|
+
"- Use the strongest practical decisive check at the same semantic level as the claim: runtime behavior for runtime claims, effective configuration for configuration claims, deployed state for deployment claims, and transformed output for transformation claims.",
|
|
52
|
+
"- Do not treat proxy signals such as names, timestamps, file sizes, line counts, partial samples, or a clean command exit as decisive when a direct check is practical.",
|
|
53
|
+
"- Use definitive language only after the evidence closes the loop. Otherwise state uncertainty, identify the missing evidence, and name the next check.",
|
|
54
|
+
"- Do not repeat checks once decisive evidence exists.",
|
|
55
|
+
].join("\n");
|
|
48
56
|
|
|
49
57
|
const SUMMARY_SYSTEM_PROMPT = [
|
|
50
58
|
"You are DeepCCC's context compactor.",
|
package/src/config-utils.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { join } from "node:path";
|
|
|
20
20
|
* - `value` 去除空白后等于 `"default"`(不区分大小写)→ 视作 `""` 并 warn。
|
|
21
21
|
* - 其余情况原样返回(不裁剪两端空白,留给具体调用方决定)。
|
|
22
22
|
*/
|
|
23
|
-
export function normalizeOptionalConfigField(
|
|
23
|
+
export function normalizeOptionalConfigField(
|
|
24
24
|
value: unknown,
|
|
25
25
|
options: { label: string; fallback?: string },
|
|
26
26
|
): string {
|
|
@@ -33,8 +33,18 @@ export function normalizeOptionalConfigField(
|
|
|
33
33
|
);
|
|
34
34
|
return "";
|
|
35
35
|
}
|
|
36
|
-
return value;
|
|
37
|
-
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* CCC Agent requires credentials owned by ChatCCC. An explicit enabled=true
|
|
41
|
+
* must not make the agent selectable when that credential is absent.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveCccEnabled(rawEnabled: unknown, apiKey: unknown): boolean {
|
|
44
|
+
const hasApiKey = typeof apiKey === "string" && apiKey.trim().length > 0;
|
|
45
|
+
if (!hasApiKey) return false;
|
|
46
|
+
return typeof rawEnabled === "boolean" ? rawEnabled : true;
|
|
47
|
+
}
|
|
38
48
|
|
|
39
49
|
// ---------------------------------------------------------------------------
|
|
40
50
|
// /git 超时配置相关
|
package/src/config.ts
CHANGED
|
@@ -11,9 +11,10 @@ import {
|
|
|
11
11
|
anthropicConfigDisplay,
|
|
12
12
|
autoDetectCodexPath,
|
|
13
13
|
autoDetectCursorPath,
|
|
14
|
-
normalizeOptionalConfigField,
|
|
15
|
-
readToolCliPath,
|
|
16
|
-
|
|
14
|
+
normalizeOptionalConfigField,
|
|
15
|
+
readToolCliPath,
|
|
16
|
+
resolveCccEnabled,
|
|
17
|
+
} from "./config-utils.ts";
|
|
17
18
|
|
|
18
19
|
// 重新导出 config-utils 中的纯函数/常量,保持对外 API 不变
|
|
19
20
|
// (历史上这些符号都从 ./config.ts 导入;新代码可直接从 ./config-utils.ts 导入以避免触发本文件的副作用)
|
|
@@ -22,10 +23,11 @@ export {
|
|
|
22
23
|
MIN_GIT_TIMEOUT_SECONDS,
|
|
23
24
|
MAX_GIT_TIMEOUT_SECONDS,
|
|
24
25
|
parseGitTimeoutSeconds,
|
|
25
|
-
normalizeOptionalConfigField,
|
|
26
|
-
isAnthropicConfigEmpty,
|
|
27
|
-
anthropicConfigDisplay,
|
|
28
|
-
|
|
26
|
+
normalizeOptionalConfigField,
|
|
27
|
+
isAnthropicConfigEmpty,
|
|
28
|
+
anthropicConfigDisplay,
|
|
29
|
+
resolveCccEnabled,
|
|
30
|
+
} from "./config-utils.ts";
|
|
29
31
|
export type { ParsedGitTimeout } from "./config-utils.ts";
|
|
30
32
|
|
|
31
33
|
// ---------------------------------------------------------------------------
|
|
@@ -595,13 +597,10 @@ function loadConfig(): AppConfig {
|
|
|
595
597
|
);
|
|
596
598
|
// 旧版 ccc 配置没有 enabled。只用 API Key 推断启用,避免 sample 中自带的
|
|
597
599
|
// 默认 Base URL / model 让升级用户在未配置凭证时意外启用 CCC Agent。
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const
|
|
602
|
-
const cursorEnabled = resolveEnabled(cursorRaw.enabled, cursorNonEmpty);
|
|
603
|
-
const codexEnabled = resolveEnabled(codexRaw.enabled, codexNonEmpty);
|
|
604
|
-
const cccEnabled = resolveEnabled(cccRaw.enabled, cccNonEmpty);
|
|
600
|
+
const claudeEnabled = resolveEnabled(claude.enabled, claudeNonEmpty);
|
|
601
|
+
const cursorEnabled = resolveEnabled(cursorRaw.enabled, cursorNonEmpty);
|
|
602
|
+
const codexEnabled = resolveEnabled(codexRaw.enabled, codexNonEmpty);
|
|
603
|
+
const cccEnabled = resolveCccEnabled(cccRaw.enabled, cccRaw.DEEPSEEK_API_KEY);
|
|
605
604
|
const chromeDevtoolsPort = Number(chromeDevtoolsRaw.port);
|
|
606
605
|
const explicitDefaultTool: AgentTool | null =
|
|
607
606
|
typeof claude.defaultAgent === "boolean" && claude.defaultAgent && claudeEnabled ? "claude" :
|
package/src/session.ts
CHANGED
|
@@ -707,11 +707,13 @@ export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter
|
|
|
707
707
|
effort: effectiveEffort || undefined,
|
|
708
708
|
fastMode: effectiveFastMode,
|
|
709
709
|
});
|
|
710
|
-
} else if (tool === "ccc") {
|
|
711
|
-
adapter = createCccAdapter({
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
710
|
+
} else if (tool === "ccc") {
|
|
711
|
+
adapter = createCccAdapter({
|
|
712
|
+
apiKey: config.ccc.DEEPSEEK_API_KEY,
|
|
713
|
+
baseURL: config.ccc.DEEPSEEK_BASE_URL,
|
|
714
|
+
model: effectiveModel || undefined,
|
|
715
|
+
effort: effectiveEffort || undefined,
|
|
716
|
+
});
|
|
715
717
|
} else {
|
|
716
718
|
adapter = createClaudeAdapter({
|
|
717
719
|
model: effectiveModel,
|