chatccc 0.2.270 → 0.2.276
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 +16 -10
- package/config.sample.json +4 -3
- package/deepccc-agent/README.md +147 -61
- package/deepccc-agent/package.json +5 -2
- package/dist/deepccc-agent/src/attachments.js +192 -0
- package/dist/deepccc-agent/src/cli.js +59 -13
- package/dist/deepccc-agent/src/config.js +57 -4
- package/dist/deepccc-agent/src/context.js +299 -16
- package/dist/deepccc-agent/src/file-tools.js +33 -0
- package/dist/deepccc-agent/src/index.js +68 -21
- package/dist/deepccc-agent/src/tool-protocol.js +14 -3
- package/dist/deepccc-agent/src/web-entry.js +72 -0
- package/dist/deepccc-agent/src/web-page.js +414 -0
- package/dist/deepccc-agent/src/web-runtime.js +331 -0
- package/dist/deepccc-agent/src/web-server.js +476 -0
- package/dist/deepccc-agent/src/web-session-store.js +162 -0
- package/dist/deepccc-agent/src/web-tool-presentation.js +123 -0
- package/dist/src/adapters/ccc-adapter.js +5 -1
- package/dist/src/agent-capability-grants.js +26 -0
- package/dist/src/agent-delegate-task.js +5 -2
- package/dist/src/agent-file-rpc.js +6 -1
- package/dist/src/agent-image-rpc.js +6 -1
- package/dist/src/agent-team/application/task-execution-service.js +330 -97
- package/dist/src/agent-team/domain/task-run.js +14 -1
- package/dist/src/agent-team/infrastructure/task-execution-runtime.js +7 -2
- package/dist/src/agent-team/main-agent-bootstrap.js +24 -1
- package/dist/src/agent-team/repositories/json-task-run-repository.js +22 -4
- package/dist/src/agent-team/web/agent-team-page.js +14 -7
- package/dist/src/cards.js +7 -4
- package/dist/src/config.js +12 -0
- package/dist/src/im-skills.js +9 -2
- package/dist/src/orchestrator.js +117 -29
- package/dist/src/safe-maintenance.js +4 -1
- package/dist/src/session-name.js +15 -0
- package/dist/src/session.js +54 -9
- package/dist/src/web-ui.js +76 -32
- package/im-skills/feishu-skill/receive-send-file.md +3 -2
- package/im-skills/feishu-skill/receive-send-image.md +3 -2
- package/im-skills/feishu-skill/send-file.mjs +6 -5
- package/im-skills/feishu-skill/send-image.mjs +6 -5
- package/im-skills/feishu-skill/skill.md +4 -2
- package/package.json +1 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* DeepCCC terminal REPL and JSONL streaming entrypoint — 同步自 ChatCCC
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* node bin/deepccc.mjs
|
|
6
|
-
* node bin/deepccc.mjs --model deepseek-v4-pro
|
|
7
|
-
* node bin/deepccc.mjs --stream-json --prompt "hello"
|
|
5
|
+
* node bin/deepccc-cli.mjs
|
|
6
|
+
* node bin/deepccc-cli.mjs --model deepseek-v4-pro
|
|
7
|
+
* node bin/deepccc-cli.mjs --stream-json --prompt "hello"
|
|
8
8
|
*
|
|
9
9
|
* 交互模式(TTY)下,单轮回复渲染为固定"过程区块":状态行 + 折叠工具行 +
|
|
10
10
|
* 原地更新正文,不再滚屏刷 JSON;完成/停止/异常后定型留在屏幕上。
|
|
@@ -24,6 +24,7 @@ import { reduceProgress } from "./progress/reducer.js";
|
|
|
24
24
|
import { TerminalProgressRenderer } from "./progress/terminal-renderer.js";
|
|
25
25
|
import { progressView } from "./progress/view.js";
|
|
26
26
|
import { defaultLogDir, setupFileLogging } from "./file-log.js";
|
|
27
|
+
import { AttachmentStore, buildAttachmentPrompt } from "./attachments.js";
|
|
27
28
|
function parsePositiveIntegerOption(name, value) {
|
|
28
29
|
const parsed = Number(value);
|
|
29
30
|
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
@@ -46,6 +47,7 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
46
47
|
let streamJson = false;
|
|
47
48
|
let prompt = null;
|
|
48
49
|
let plain = false;
|
|
50
|
+
const images = [];
|
|
49
51
|
for (let i = 0; i < argv.length; i++) {
|
|
50
52
|
const arg = argv[i];
|
|
51
53
|
const next = argv[i + 1];
|
|
@@ -65,6 +67,10 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
65
67
|
config.effort = next;
|
|
66
68
|
i++;
|
|
67
69
|
}
|
|
70
|
+
else if (arg === "--max-output-tokens" && next !== undefined) {
|
|
71
|
+
config.maxOutputTokens = parsePositiveIntegerOption("--max-output-tokens", next);
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
68
74
|
else if (arg === "--base-url" && next !== undefined) {
|
|
69
75
|
config.baseURL = next;
|
|
70
76
|
i++;
|
|
@@ -100,6 +106,10 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
100
106
|
prompt = next;
|
|
101
107
|
i++;
|
|
102
108
|
}
|
|
109
|
+
else if (arg === "--image" && next !== undefined) {
|
|
110
|
+
images.push(next);
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
103
113
|
else if (arg === "--plain") {
|
|
104
114
|
plain = true;
|
|
105
115
|
}
|
|
@@ -111,7 +121,7 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
111
121
|
help = true;
|
|
112
122
|
}
|
|
113
123
|
}
|
|
114
|
-
return { config, options, listSessions, resume, help, streamJson, prompt, plain };
|
|
124
|
+
return { config, options, listSessions, resume, help, streamJson, prompt, images, plain };
|
|
115
125
|
}
|
|
116
126
|
async function loadRuntime() {
|
|
117
127
|
const [{ ChatSession }, { config: appConfig }] = await Promise.all([
|
|
@@ -124,13 +134,14 @@ function printHelp(appConfig) {
|
|
|
124
134
|
console.log([
|
|
125
135
|
"DeepCCC terminal agent",
|
|
126
136
|
"",
|
|
127
|
-
"Usage: deepccc [options]",
|
|
137
|
+
"Usage: deepccc-cli [options]",
|
|
128
138
|
"",
|
|
129
139
|
"Options:",
|
|
130
140
|
` --provider <name> API protocol: openai or anthropic (current default ${appConfig.provider})`,
|
|
131
141
|
` --model <name> Model name (current default ${appConfig.model})`,
|
|
132
142
|
` --sub-model <name> Sub-model for lightweight steps (compaction/task; empty = follow main model)`,
|
|
133
143
|
` --effort <level> Reasoning effort: none/minimal/low/medium/high/xhigh/max (overrides config.effort)`,
|
|
144
|
+
` --max-output-tokens <n> Maximum output tokens (unset = Provider default)`,
|
|
134
145
|
` --base-url <url> Provider API base URL (current default ${appConfig.baseURL})`,
|
|
135
146
|
" --api-key <key> API key",
|
|
136
147
|
" --cwd <path> Working directory",
|
|
@@ -139,6 +150,7 @@ function printHelp(appConfig) {
|
|
|
139
150
|
" --list-sessions List saved sessions and exit",
|
|
140
151
|
" --stream-json One-shot mode: write JSONL events to stdout",
|
|
141
152
|
" --prompt <text> Prompt text for --stream-json",
|
|
153
|
+
" --image <path> Attach a PNG/JPEG/WebP file (repeatable; copied to the session attachment store)",
|
|
142
154
|
" --plain Force plain streaming output (no progress block renderer)",
|
|
143
155
|
" --dangerously-bypass-permissions Skip all permission prompts (aligns with chatccc's bypass mode)",
|
|
144
156
|
" --help, -h Show help",
|
|
@@ -271,8 +283,8 @@ async function runStreamJson(args) {
|
|
|
271
283
|
return 0;
|
|
272
284
|
}
|
|
273
285
|
const prompt = args.prompt ?? (!process.stdin.isTTY ? await readPromptFromStdin() : "");
|
|
274
|
-
if (!prompt.trim()) {
|
|
275
|
-
writeJsonLine({ type: "error", message: "--stream-json requires --prompt <text
|
|
286
|
+
if (!prompt.trim() && !args.images.length) {
|
|
287
|
+
writeJsonLine({ type: "error", message: "--stream-json requires --prompt <text>, stdin input, or --image <path>" });
|
|
276
288
|
return 1;
|
|
277
289
|
}
|
|
278
290
|
let runtime;
|
|
@@ -305,15 +317,29 @@ async function runStreamJson(args) {
|
|
|
305
317
|
writeJsonLine({ type: "error", message: err.message });
|
|
306
318
|
return 1;
|
|
307
319
|
}
|
|
320
|
+
let attachments;
|
|
321
|
+
try {
|
|
322
|
+
attachments = await importCliAttachments(resolvedSession.sessionId, args.images);
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
writeJsonLine({ type: "error", message: err.message });
|
|
326
|
+
return 1;
|
|
327
|
+
}
|
|
328
|
+
const promptWithAttachments = buildAttachmentPrompt(prompt, attachments);
|
|
308
329
|
writeJsonLine({
|
|
309
330
|
type: "start",
|
|
310
331
|
session_id: resolvedSession.sessionId,
|
|
311
332
|
mode: resolvedSession.mode,
|
|
312
333
|
cwd,
|
|
313
334
|
model: args.config.model ?? runtime.appConfig.model,
|
|
335
|
+
attachments: attachments.map((attachment) => ({
|
|
336
|
+
attachment_id: attachment.attachmentId,
|
|
337
|
+
name: attachment.originalName,
|
|
338
|
+
path: attachment.absolutePath,
|
|
339
|
+
})),
|
|
314
340
|
});
|
|
315
341
|
try {
|
|
316
|
-
for await (const event of session.chat(
|
|
342
|
+
for await (const event of session.chat(promptWithAttachments)) {
|
|
317
343
|
streamJsonEvent(event);
|
|
318
344
|
}
|
|
319
345
|
return 0;
|
|
@@ -420,13 +446,24 @@ async function runRepl(args) {
|
|
|
420
446
|
console.error(`${C.yellow}${err.message}${C.reset}`);
|
|
421
447
|
process.exit(1);
|
|
422
448
|
}
|
|
449
|
+
let pendingAttachments;
|
|
450
|
+
try {
|
|
451
|
+
pendingAttachments = await importCliAttachments(resolvedSession.sessionId, args.images);
|
|
452
|
+
}
|
|
453
|
+
catch (err) {
|
|
454
|
+
console.error(`${C.yellow}${err.message}${C.reset}`);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
if (pendingAttachments.length) {
|
|
458
|
+
console.log(`${C.dim}Attachments queued: ${pendingAttachments.map((attachment) => attachment.originalName).join(", ")}${C.reset}`);
|
|
459
|
+
}
|
|
423
460
|
let currentAbort = null;
|
|
424
461
|
const ctrlCState = createCtrlCState();
|
|
425
462
|
rl.prompt();
|
|
426
463
|
rl.on("line", async (line) => {
|
|
427
464
|
ctrlCState.reset();
|
|
428
465
|
const input = line.trim();
|
|
429
|
-
if (!input) {
|
|
466
|
+
if (!input && !pendingAttachments.length) {
|
|
430
467
|
rl.prompt();
|
|
431
468
|
return;
|
|
432
469
|
}
|
|
@@ -470,8 +507,10 @@ async function runRepl(args) {
|
|
|
470
507
|
}
|
|
471
508
|
let rendererEnded = false;
|
|
472
509
|
try {
|
|
510
|
+
const chatInput = buildAttachmentPrompt(input, pendingAttachments);
|
|
511
|
+
pendingAttachments = [];
|
|
473
512
|
let lastAccumulated = "";
|
|
474
|
-
for await (const event of session.chat(
|
|
513
|
+
for await (const event of session.chat(chatInput, signal)) {
|
|
475
514
|
if (renderer && view) {
|
|
476
515
|
view = reduceProgress(view, event);
|
|
477
516
|
if (event.type === "text" || event.type === "compact" || event.type === "status") {
|
|
@@ -559,8 +598,15 @@ async function runRepl(args) {
|
|
|
559
598
|
process.exit(0);
|
|
560
599
|
});
|
|
561
600
|
}
|
|
601
|
+
async function importCliAttachments(sessionId, paths) {
|
|
602
|
+
const store = new AttachmentStore();
|
|
603
|
+
const attachments = [];
|
|
604
|
+
for (const path of paths)
|
|
605
|
+
attachments.push(await store.importFile(sessionId, path));
|
|
606
|
+
return attachments;
|
|
607
|
+
}
|
|
562
608
|
/**
|
|
563
|
-
* skill create 子命令:deepccc skill create <name> [--scope global|project] [--description "..."]
|
|
609
|
+
* skill create 子命令:deepccc-cli skill create <name> [--scope global|project] [--description "..."]
|
|
564
610
|
* 默认创建为全局技能(~/.deepccc/skills/<name>/SKILL.md,Codex 结构);
|
|
565
611
|
* --scope project 创建为项目技能(<cwd>/.deepccc/skills/<name>/SKILL.md)。
|
|
566
612
|
* 新技能在下一次对话自动生效(技能索引每次 chat() 前重扫)。
|
|
@@ -569,7 +615,7 @@ function runSkillCreate(argv) {
|
|
|
569
615
|
const positional = argv.filter((a) => !a.startsWith("--"));
|
|
570
616
|
const name = positional[0];
|
|
571
617
|
if (!name) {
|
|
572
|
-
console.error("usage: deepccc skill create <name> [--scope global|project] [--description \"...\"]");
|
|
618
|
+
console.error("usage: deepccc-cli skill create <name> [--scope global|project] [--description \"...\"]");
|
|
573
619
|
process.exit(1);
|
|
574
620
|
}
|
|
575
621
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) {
|
|
@@ -603,7 +649,7 @@ function runSkillCreate(argv) {
|
|
|
603
649
|
console.log("hot reload: 下一次对话自动生效,无需重启");
|
|
604
650
|
}
|
|
605
651
|
async function main() {
|
|
606
|
-
// skill create 子命令:deepccc skill create <name> [--scope global|project] [--description "..."]
|
|
652
|
+
// skill create 子命令:deepccc-cli skill create <name> [--scope global|project] [--description "..."]
|
|
607
653
|
// 默认创建在全局 ~/.deepccc/skills(Codex 目录结构),--scope project 创建到 <cwd>/.deepccc/skills。
|
|
608
654
|
if (process.argv[2] === "skill" && process.argv[3] === "create") {
|
|
609
655
|
runSkillCreate(process.argv.slice(4));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
export const DEEPCCC_HOME = join(homedir(), ".deepccc");
|
|
5
5
|
export const RAW_STREAM_LOGS_DIR = join(DEEPCCC_HOME, "raw-stream-logs");
|
|
6
|
-
const CONFIG_PATH = join(DEEPCCC_HOME, "config.json");
|
|
6
|
+
export const CONFIG_PATH = join(DEEPCCC_HOME, "config.json");
|
|
7
7
|
/**
|
|
8
8
|
* 默认配置(不读环境/文件)。导出供测试断言默认值;运行时请用 loadConfig 结果。
|
|
9
9
|
*/
|
|
@@ -31,8 +31,12 @@ export const DEFAULT_CONFIG = {
|
|
|
31
31
|
retentionDays: 7,
|
|
32
32
|
keepCompleted: false,
|
|
33
33
|
},
|
|
34
|
+
web: {
|
|
35
|
+
port: 28_080,
|
|
36
|
+
openOnStart: true,
|
|
37
|
+
},
|
|
34
38
|
};
|
|
35
|
-
function readConfigFile() {
|
|
39
|
+
export function readConfigFile() {
|
|
36
40
|
if (!existsSync(CONFIG_PATH))
|
|
37
41
|
return {};
|
|
38
42
|
const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
@@ -56,6 +60,12 @@ function numberEnv(name) {
|
|
|
56
60
|
const value = Number(env(name));
|
|
57
61
|
return Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
58
62
|
}
|
|
63
|
+
function optionalPositiveInteger(value) {
|
|
64
|
+
if (value === undefined || value === null || value === "")
|
|
65
|
+
return undefined;
|
|
66
|
+
const parsed = Number(value);
|
|
67
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
68
|
+
}
|
|
59
69
|
export function normalizeDeepCccProvider(value) {
|
|
60
70
|
if (value === undefined || value === null || String(value).trim() === "")
|
|
61
71
|
return "openai";
|
|
@@ -64,13 +74,14 @@ export function normalizeDeepCccProvider(value) {
|
|
|
64
74
|
return normalized;
|
|
65
75
|
throw new Error(`DEEPCCC_PROVIDER/provider must be "openai" or "anthropic", received: ${String(value)}`);
|
|
66
76
|
}
|
|
67
|
-
function loadConfig() {
|
|
77
|
+
export function loadConfig() {
|
|
68
78
|
const file = readConfigFile();
|
|
69
79
|
const rawLogs = file.rawStreamLogs && typeof file.rawStreamLogs === "object"
|
|
70
80
|
? file.rawStreamLogs
|
|
71
81
|
: {};
|
|
72
82
|
const git = file.git && typeof file.git === "object" ? file.git : {};
|
|
73
83
|
const coAuthor = git.coAuthor && typeof git.coAuthor === "object" ? git.coAuthor : {};
|
|
84
|
+
const web = file.web && typeof file.web === "object" ? file.web : {};
|
|
74
85
|
return {
|
|
75
86
|
provider: normalizeDeepCccProvider(env("DEEPCCC_PROVIDER") ?? file.provider ?? DEFAULT_CONFIG.provider),
|
|
76
87
|
apiKey: env("DEEPCCC_API_KEY") ?? env("DEEPSEEK_API_KEY") ?? file.apiKey ?? DEFAULT_CONFIG.apiKey,
|
|
@@ -78,6 +89,8 @@ function loadConfig() {
|
|
|
78
89
|
model: env("DEEPCCC_MODEL") ?? env("DEEPSEEK_MODEL") ?? file.model ?? DEFAULT_CONFIG.model,
|
|
79
90
|
subModel: env("DEEPCCC_SUB_MODEL") ?? file.subModel ?? DEFAULT_CONFIG.subModel,
|
|
80
91
|
effort: env("DEEPCCC_EFFORT") ?? env("DEEPSEEK_EFFORT") ?? file.effort ?? DEFAULT_CONFIG.effort,
|
|
92
|
+
maxOutputTokens: optionalPositiveInteger(env("DEEPCCC_MAX_OUTPUT_TOKENS"))
|
|
93
|
+
?? optionalPositiveInteger(file.maxOutputTokens),
|
|
81
94
|
streaming: boolEnv("DEEPCCC_STREAMING") ?? file.streaming ?? DEFAULT_CONFIG.streaming,
|
|
82
95
|
contextWindow: numberEnv("DEEPCCC_CONTEXT_WINDOW") ?? file.contextWindow ?? DEFAULT_CONFIG.contextWindow,
|
|
83
96
|
git: {
|
|
@@ -93,9 +106,49 @@ function loadConfig() {
|
|
|
93
106
|
retentionDays: numberEnv("DEEPCCC_RAW_STREAM_RETENTION_DAYS") ?? rawLogs.retentionDays ?? DEFAULT_CONFIG.rawStreamLogs.retentionDays,
|
|
94
107
|
keepCompleted: boolEnv("DEEPCCC_RAW_STREAM_KEEP_COMPLETED") ?? rawLogs.keepCompleted ?? DEFAULT_CONFIG.rawStreamLogs.keepCompleted,
|
|
95
108
|
},
|
|
109
|
+
web: {
|
|
110
|
+
port: normalizeWebPort(web.port),
|
|
111
|
+
openOnStart: web.openOnStart ?? DEFAULT_CONFIG.web.openOnStart,
|
|
112
|
+
},
|
|
96
113
|
};
|
|
97
114
|
}
|
|
98
115
|
export function ensureConfigDir() {
|
|
99
116
|
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
100
117
|
}
|
|
118
|
+
/** Persist only user-editable Web fields while preserving advanced configuration. */
|
|
119
|
+
export function saveConfigPatch(patch) {
|
|
120
|
+
const existing = readConfigFile();
|
|
121
|
+
const { web: webPatch, ...flatPatch } = patch;
|
|
122
|
+
const next = {
|
|
123
|
+
...existing,
|
|
124
|
+
...flatPatch,
|
|
125
|
+
...(webPatch ? { web: { ...DEFAULT_CONFIG.web, ...(existing.web ?? {}), ...webPatch } } : {}),
|
|
126
|
+
};
|
|
127
|
+
if (patch.provider !== undefined)
|
|
128
|
+
next.provider = normalizeDeepCccProvider(patch.provider);
|
|
129
|
+
if (patch.baseURL !== undefined && !patch.baseURL.trim())
|
|
130
|
+
throw new Error("baseURL must not be empty");
|
|
131
|
+
if (patch.model !== undefined && !patch.model.trim())
|
|
132
|
+
throw new Error("model must not be empty");
|
|
133
|
+
if (patch.effort !== undefined && !["", "none", "minimal", "low", "medium", "high", "xhigh", "max"].includes(patch.effort)) {
|
|
134
|
+
throw new Error(`Unsupported effort: ${patch.effort}`);
|
|
135
|
+
}
|
|
136
|
+
if (patch.contextWindow !== undefined && (!Number.isInteger(patch.contextWindow) || patch.contextWindow <= 0)) {
|
|
137
|
+
throw new Error("contextWindow must be a positive integer");
|
|
138
|
+
}
|
|
139
|
+
if (webPatch?.port !== undefined) {
|
|
140
|
+
next.web = { ...(next.web ?? DEFAULT_CONFIG.web), port: normalizeWebPort(webPatch.port) };
|
|
141
|
+
}
|
|
142
|
+
ensureConfigDir();
|
|
143
|
+
const tempPath = `${CONFIG_PATH}.${process.pid}.tmp`;
|
|
144
|
+
writeFileSync(tempPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
145
|
+
renameSync(tempPath, CONFIG_PATH);
|
|
146
|
+
return loadConfig();
|
|
147
|
+
}
|
|
148
|
+
function normalizeWebPort(value) {
|
|
149
|
+
const parsed = Number(value ?? DEFAULT_CONFIG.web.port);
|
|
150
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65_535)
|
|
151
|
+
return DEFAULT_CONFIG.web.port;
|
|
152
|
+
return parsed;
|
|
153
|
+
}
|
|
101
154
|
export const config = loadConfig();
|