deepccc 0.1.10 → 0.1.12
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 +2 -2
- package/bin/deepccc.mjs +26 -26
- package/dist/cli.js +5 -0
- package/dist/context.d.ts +29 -0
- package/dist/context.js +47 -1
- package/dist/file-tools.d.ts +5 -0
- package/dist/file-tools.js +23 -0
- package/dist/index.js +38 -16
- package/dist/session-search.d.ts +59 -0
- package/dist/session-search.js +276 -0
- package/package.json +62 -62
package/README.md
CHANGED
|
@@ -219,7 +219,7 @@ echo "运行测试并解释失败原因" | deepccc --stream-json
|
|
|
219
219
|
|
|
220
220
|
## 在 ChatCCC 中使用
|
|
221
221
|
|
|
222
|
-
**ChatCCC 已内置 deepccc**:ChatCCC 的 "CCC Agent" 工具直接内嵌 deepccc
|
|
222
|
+
**ChatCCC 已内置 deepccc**:ChatCCC 的 "CCC Agent" 工具直接内嵌 deepccc 的代码(仓库内 `deepccc-agent/` 子目录),以 `permissionMode: "bypass"` 全自动运行,无需单独安装或配置本仓库。
|
|
223
223
|
|
|
224
224
|
ChatCCC 是一个把 Claude Code / Codex / Cursor / CCC Agent 聚合到飞书/企微等 IM 消息通道的本地机器人框架,提供会话管理、过程卡片、用量统计与隐私替换等能力。
|
|
225
225
|
|
|
@@ -234,7 +234,7 @@ ChatCCC 是一个把 Claude Code / Codex / Cursor / CCC Agent 聚合到飞书/
|
|
|
234
234
|
|
|
235
235
|
这种方式适合已经在 ChatCCC 里协作的场景:ChatCCC 负责会话入口和消息通道,`deepccc` 负责本地编程 Agent 能力,包括读取项目提示词、运行命令、编辑文件和输出流式结果。
|
|
236
236
|
|
|
237
|
-
|
|
237
|
+
deepccc 的内核主战场在 ChatCCC 仓库的 `deepccc-agent/` 子目录;本仓库(deepccc-agent)是发布镜像,由 ChatCCC 仓库的 `sync-deepccc.mjs` 目录级同步(多的删、少的补、不同的改),之后 `npm run build && npm publish` 发布独立 `deepccc` 包。
|
|
238
238
|
|
|
239
239
|
## 项目提示词自动注入
|
|
240
240
|
|
package/bin/deepccc.mjs
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
6
|
-
|
|
7
|
-
const require = createRequire(import.meta.url);
|
|
8
|
-
const pkgRoot = dirname(require.resolve("../package.json"));
|
|
9
|
-
const distCli = join(pkgRoot, "dist", "cli.js");
|
|
10
|
-
|
|
11
|
-
let args;
|
|
12
|
-
if (existsSync(distCli)) {
|
|
13
|
-
args = [distCli, ...process.argv.slice(2)];
|
|
14
|
-
} else {
|
|
15
|
-
const tsxCli = require.resolve("tsx/cli");
|
|
16
|
-
const sourceCli = join(pkgRoot, "src", "cli.ts");
|
|
17
|
-
args = [tsxCli, sourceCli, ...process.argv.slice(2)];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const result = spawnSync(process.execPath, args, {
|
|
21
|
-
stdio: "inherit",
|
|
22
|
-
cwd: process.cwd(),
|
|
23
|
-
env: process.env,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const pkgRoot = dirname(require.resolve("../package.json"));
|
|
9
|
+
const distCli = join(pkgRoot, "dist", "cli.js");
|
|
10
|
+
|
|
11
|
+
let args;
|
|
12
|
+
if (existsSync(distCli)) {
|
|
13
|
+
args = [distCli, ...process.argv.slice(2)];
|
|
14
|
+
} else {
|
|
15
|
+
const tsxCli = require.resolve("tsx/cli");
|
|
16
|
+
const sourceCli = join(pkgRoot, "src", "cli.ts");
|
|
17
|
+
args = [tsxCli, sourceCli, ...process.argv.slice(2)];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const result = spawnSync(process.execPath, args, {
|
|
21
|
+
stdio: "inherit",
|
|
22
|
+
cwd: process.cwd(),
|
|
23
|
+
env: process.env,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
process.exit(result.status === null ? 1 : result.status);
|
package/dist/cli.js
CHANGED
|
@@ -429,6 +429,11 @@ async function runRepl(args) {
|
|
|
429
429
|
rl.prompt();
|
|
430
430
|
return;
|
|
431
431
|
}
|
|
432
|
+
if (input === "/sessions") {
|
|
433
|
+
printSessions();
|
|
434
|
+
rl.prompt();
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
432
437
|
currentAbort = new AbortController();
|
|
433
438
|
const signal = currentAbort.signal;
|
|
434
439
|
// TTY 下用"过程区块"(飞书过程卡片的终端形态):固定区域原地更新、
|
package/dist/context.d.ts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
export type BuiltinContextRole = "user" | "assistant";
|
|
2
|
+
/**
|
|
3
|
+
* 结构化工具调用存档(与 content 里的 [Tool transcript] 文本互为冗余视图):
|
|
4
|
+
* content 文本保持不变供模型消费;toolCalls 数组提供可检索、可回放的结构化数据。
|
|
5
|
+
*/
|
|
6
|
+
export interface BuiltinContextToolCall {
|
|
7
|
+
name: string;
|
|
8
|
+
/** 工具入参(JSON 文本,已截断到安全上限) */
|
|
9
|
+
input?: string;
|
|
10
|
+
/** 工具出参(JSON 文本或错误消息,已截断到安全上限) */
|
|
11
|
+
output?: string;
|
|
12
|
+
is_error?: boolean;
|
|
13
|
+
}
|
|
2
14
|
export interface BuiltinContextMessage {
|
|
3
15
|
role: BuiltinContextRole;
|
|
4
16
|
content: string;
|
|
17
|
+
/** 可选结构化工具调用记录(assistant 消息专用) */
|
|
18
|
+
toolCalls?: BuiltinContextToolCall[];
|
|
5
19
|
}
|
|
6
20
|
export interface BuiltinContextState {
|
|
7
21
|
version: 1;
|
|
@@ -53,6 +67,21 @@ export declare function latestBuiltinSessionForCwd(cwd: string, contextDir?: str
|
|
|
53
67
|
*/
|
|
54
68
|
export declare function estimateBuiltinContextTokens(summary: string, messages: readonly BuiltinContextMessage[]): number;
|
|
55
69
|
export declare function serializeMessagesForSummary(messages: readonly BuiltinContextMessage[]): string;
|
|
70
|
+
export declare const DEFAULT_PERSISTED_ASSISTANT_TEXT_CHARS = 32000;
|
|
71
|
+
export declare const DEFAULT_PERSISTED_TOOL_TRANSCRIPT_CHARS = 24000;
|
|
72
|
+
/**
|
|
73
|
+
* 构造持久化的 assistant 消息:content 保持既有"正文 + [Tool transcript]"文本格式
|
|
74
|
+
* (模型上下文行为不变),同时附加结构化 toolCalls 存档(供 session-search 等检索)。
|
|
75
|
+
*/
|
|
76
|
+
export declare function buildPersistedAssistantMessage(params: {
|
|
77
|
+
fullText: string;
|
|
78
|
+
/** [Tool transcript] 的文本行(按原始流顺序,含 tool_call / tool_result / tool_error) */
|
|
79
|
+
transcriptLines: readonly string[];
|
|
80
|
+
/** 结构化工具调用(按调用顺序);有则写入消息的 toolCalls 字段 */
|
|
81
|
+
toolCalls?: readonly BuiltinContextToolCall[];
|
|
82
|
+
maxAssistantChars?: number;
|
|
83
|
+
maxTranscriptChars?: number;
|
|
84
|
+
}): BuiltinContextMessage;
|
|
56
85
|
export declare function buildSummaryPrompt(plan: BuiltinCompactionPlan): string;
|
|
57
86
|
export declare class BuiltinContextManager {
|
|
58
87
|
readonly persist: boolean;
|
package/dist/context.js
CHANGED
|
@@ -39,7 +39,32 @@ function normalizeMessage(value) {
|
|
|
39
39
|
return null;
|
|
40
40
|
if (typeof raw.content !== "string")
|
|
41
41
|
return null;
|
|
42
|
-
|
|
42
|
+
const message = { role: raw.role, content: raw.content };
|
|
43
|
+
const toolCalls = normalizeToolCalls(raw.toolCalls);
|
|
44
|
+
if (toolCalls.length > 0)
|
|
45
|
+
message.toolCalls = toolCalls;
|
|
46
|
+
return message;
|
|
47
|
+
}
|
|
48
|
+
function normalizeToolCalls(value) {
|
|
49
|
+
if (!Array.isArray(value))
|
|
50
|
+
return [];
|
|
51
|
+
const calls = [];
|
|
52
|
+
for (const entry of value) {
|
|
53
|
+
if (!entry || typeof entry !== "object")
|
|
54
|
+
continue;
|
|
55
|
+
const raw = entry;
|
|
56
|
+
if (typeof raw.name !== "string" || raw.name.length === 0)
|
|
57
|
+
continue;
|
|
58
|
+
const call = { name: raw.name };
|
|
59
|
+
if (typeof raw.input === "string")
|
|
60
|
+
call.input = raw.input;
|
|
61
|
+
if (typeof raw.output === "string")
|
|
62
|
+
call.output = raw.output;
|
|
63
|
+
if (typeof raw.is_error === "boolean")
|
|
64
|
+
call.is_error = raw.is_error;
|
|
65
|
+
calls.push(call);
|
|
66
|
+
}
|
|
67
|
+
return calls;
|
|
43
68
|
}
|
|
44
69
|
function emptyState(sessionId, cwd) {
|
|
45
70
|
const now = Date.now();
|
|
@@ -153,6 +178,27 @@ function truncateMiddle(value, maxChars, marker) {
|
|
|
153
178
|
const tailChars = Math.floor(available / 2);
|
|
154
179
|
return `${value.slice(0, headChars)}\n${marker}\n${value.slice(-tailChars)}`;
|
|
155
180
|
}
|
|
181
|
+
export const DEFAULT_PERSISTED_ASSISTANT_TEXT_CHARS = 32_000;
|
|
182
|
+
export const DEFAULT_PERSISTED_TOOL_TRANSCRIPT_CHARS = 24_000;
|
|
183
|
+
const ASSISTANT_TRUNCATED_MARKER = "...[assistant response truncated in context]...";
|
|
184
|
+
const TOOL_TRANSCRIPT_TRUNCATED_MARKER = "...[tool transcript truncated]...";
|
|
185
|
+
/**
|
|
186
|
+
* 构造持久化的 assistant 消息:content 保持既有"正文 + [Tool transcript]"文本格式
|
|
187
|
+
* (模型上下文行为不变),同时附加结构化 toolCalls 存档(供 session-search 等检索)。
|
|
188
|
+
*/
|
|
189
|
+
export function buildPersistedAssistantMessage(params) {
|
|
190
|
+
const maxAssistantChars = params.maxAssistantChars ?? DEFAULT_PERSISTED_ASSISTANT_TEXT_CHARS;
|
|
191
|
+
const maxTranscriptChars = params.maxTranscriptChars ?? DEFAULT_PERSISTED_TOOL_TRANSCRIPT_CHARS;
|
|
192
|
+
const persistedAssistantText = truncateMiddle(params.fullText, maxAssistantChars, ASSISTANT_TRUNCATED_MARKER);
|
|
193
|
+
const content = params.transcriptLines.length > 0
|
|
194
|
+
? `${persistedAssistantText}\n\n[Tool transcript]\n${truncateMiddle(params.transcriptLines.join("\n"), maxTranscriptChars, TOOL_TRANSCRIPT_TRUNCATED_MARKER)}`
|
|
195
|
+
: persistedAssistantText;
|
|
196
|
+
const message = { role: "assistant", content };
|
|
197
|
+
const toolCalls = normalizeToolCalls(params.toolCalls);
|
|
198
|
+
if (toolCalls.length > 0)
|
|
199
|
+
message.toolCalls = toolCalls;
|
|
200
|
+
return message;
|
|
201
|
+
}
|
|
156
202
|
function serializeMessagesForCompaction(messages) {
|
|
157
203
|
const sections = [];
|
|
158
204
|
let remaining = MAX_COMPACTION_SOURCE_CHARS;
|
package/dist/file-tools.d.ts
CHANGED
|
@@ -150,5 +150,10 @@ export declare function applyPatchForTool(cwd: string, input: ApplyPatchInput):
|
|
|
150
150
|
export interface BuiltinFileToolsOptions {
|
|
151
151
|
/** 权限门控:副作用工具(run_command/文件写操作)执行前会先经过 gate.check */
|
|
152
152
|
permissionGate?: PermissionGate;
|
|
153
|
+
/** session_search 工具的会话/原始日志目录(默认 ~/.deepccc/sessions 与 raw-stream-logs;测试可注入) */
|
|
154
|
+
sessionSearch?: {
|
|
155
|
+
contextDir?: string;
|
|
156
|
+
rawLogsDir?: string;
|
|
157
|
+
};
|
|
153
158
|
}
|
|
154
159
|
export declare function createBuiltinFileTools(cwd: string, options?: BuiltinFileToolsOptions): ToolSet;
|
package/dist/file-tools.js
CHANGED
|
@@ -9,6 +9,7 @@ import { createInterface } from "node:readline";
|
|
|
9
9
|
import { jsonSchema, tool } from "ai";
|
|
10
10
|
import { isDangerousCommand } from "./permissions.js";
|
|
11
11
|
import { killProcessTree } from "./proc-tree-kill.js";
|
|
12
|
+
import { searchBuiltinSessions, } from "./session-search.js";
|
|
12
13
|
import { webFetchForTool, webSearchForTool, } from "./web-tools.js";
|
|
13
14
|
const MAX_READ_BYTES = 1024 * 1024;
|
|
14
15
|
const MAX_LIST_ENTRIES = 200;
|
|
@@ -1181,5 +1182,27 @@ export function createBuiltinFileTools(cwd, options = {}) {
|
|
|
1181
1182
|
}),
|
|
1182
1183
|
execute: (input, options) => webFetchForTool(input, { abortSignal: options.abortSignal }),
|
|
1183
1184
|
}),
|
|
1185
|
+
// 会话历史检索:只读 ~/.deepccc 存档,无需权限询问
|
|
1186
|
+
session_search: tool({
|
|
1187
|
+
description: "Search DeepCCC session archives by keyword and return matching snippets. Use when you need to recall old user messages, assistant replies, or tool calls from previous sessions. Multiple terms must all appear in the same message (AND, case-insensitive). Set include_raw_logs to also scan gzipped raw stream logs (slower).",
|
|
1188
|
+
inputSchema: jsonSchema({
|
|
1189
|
+
type: "object",
|
|
1190
|
+
additionalProperties: false,
|
|
1191
|
+
properties: {
|
|
1192
|
+
query: { type: "string", description: "Keywords to search for (space-separated, all must match)." },
|
|
1193
|
+
session_id: { type: "string", description: "Optional session id to restrict the search to." },
|
|
1194
|
+
include_raw_logs: { type: "boolean", description: "Also scan gzipped raw stream logs under ~/.deepccc/raw-stream-logs. Default false." },
|
|
1195
|
+
max_results: { type: "number", description: "Maximum number of matches, capped at 50." },
|
|
1196
|
+
},
|
|
1197
|
+
required: ["query"],
|
|
1198
|
+
}),
|
|
1199
|
+
execute: (input) => searchBuiltinSessions(input.query, {
|
|
1200
|
+
contextDir: options.sessionSearch?.contextDir,
|
|
1201
|
+
rawLogsDir: options.sessionSearch?.rawLogsDir,
|
|
1202
|
+
includeRawLogs: input.include_raw_logs ?? false,
|
|
1203
|
+
sessionId: input.session_id,
|
|
1204
|
+
maxResults: input.max_results,
|
|
1205
|
+
}),
|
|
1206
|
+
}),
|
|
1184
1207
|
};
|
|
1185
1208
|
}
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { readFileSync } from "node:fs";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { config as appConfig, RAW_STREAM_LOGS_DIR } from "./config.js";
|
|
11
11
|
import { createRawStreamLog, } from "./raw-stream-log.js";
|
|
12
|
-
import {
|
|
12
|
+
import { buildPersistedAssistantMessage, buildSummaryPrompt, BuiltinContextManager, defaultBuiltinSessionId, } from "./context.js";
|
|
13
13
|
import { createBuiltinFileTools } from "./file-tools.js";
|
|
14
14
|
import { PermissionGate } from "./permissions.js";
|
|
15
15
|
import { buildDefaultSkillDirs, buildSkillsIndexPrompt, scanSkillsDirs, } from "./skills.js";
|
|
@@ -34,6 +34,20 @@ const SYSTEM_PROMPT = [
|
|
|
34
34
|
"- 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.",
|
|
35
35
|
"- Use definitive language only after the evidence closes the loop. Otherwise state uncertainty, identify the missing evidence, and name the next check.",
|
|
36
36
|
"- Do not repeat checks once decisive evidence exists.",
|
|
37
|
+
"",
|
|
38
|
+
"## Survey Before Acting",
|
|
39
|
+
"- Before diving into any task, first map the landscape at low cost: project instructions, directory layout, routes/APIs, existing tests, and git state.",
|
|
40
|
+
"- Produce a brief execution plan that includes how you will verify the result, then execute.",
|
|
41
|
+
"- When evidence contradicts an early assumption, revisit the plan instead of tunneling on one direction.",
|
|
42
|
+
"",
|
|
43
|
+
"## Delegated Authority",
|
|
44
|
+
"- When the user delegates decisions (\"you decide\", \"do it elegantly\", \"up to you\"), act autonomously on implementation details.",
|
|
45
|
+
"- Only ask about genuine blockers: irreversible actions, security boundaries, credentials, or scope changes.",
|
|
46
|
+
"- Do not bounce implementation-level multiple-choice questions back to the user after they delegated.",
|
|
47
|
+
"",
|
|
48
|
+
"## Pre-Delivery Self-Check",
|
|
49
|
+
"- Before reporting completion, verify: the change runs, edge cases are covered, assumptions are listed, and anything unverified is explicitly labeled as such.",
|
|
50
|
+
"- State what was done, how it was verified, and what remains unverified or risky.",
|
|
37
51
|
].join("\n");
|
|
38
52
|
const SUMMARY_SYSTEM_PROMPT = [
|
|
39
53
|
"You are DeepCCC's context compactor.",
|
|
@@ -42,8 +56,6 @@ const SUMMARY_SYSTEM_PROMPT = [
|
|
|
42
56
|
].join("\n");
|
|
43
57
|
export const DEFAULT_COMPACTION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
44
58
|
const MAX_COMPACTION_PASSES = 8;
|
|
45
|
-
const MAX_PERSISTED_ASSISTANT_TEXT_CHARS = 32_000;
|
|
46
|
-
const MAX_PERSISTED_TOOL_TRANSCRIPT_CHARS = 24_000;
|
|
47
59
|
// ---------------------------------------------------------------------------
|
|
48
60
|
// 类型定义
|
|
49
61
|
// ---------------------------------------------------------------------------
|
|
@@ -165,6 +177,10 @@ export class ChatSession {
|
|
|
165
177
|
let safeAccumulated = "";
|
|
166
178
|
let rawLog = null;
|
|
167
179
|
let completed = false;
|
|
180
|
+
// 结构化工具调用存档:按 toolCallId 关联入参/出参/错误,落盘到 context.json 的
|
|
181
|
+
// assistant 消息 toolCalls 字段;[Tool transcript] 文本视图仍按原格式生成。
|
|
182
|
+
const toolCallsById = new Map();
|
|
183
|
+
const toolCallOrder = [];
|
|
168
184
|
try {
|
|
169
185
|
if (this.context.planCompaction()) {
|
|
170
186
|
yield { type: "status", phase: "compacting" };
|
|
@@ -220,6 +236,8 @@ export class ChatSession {
|
|
|
220
236
|
}
|
|
221
237
|
else if (part.type === "tool-call") {
|
|
222
238
|
toolContext.push(`tool_call ${part.toolName}: ${safeJson(part.input)}`);
|
|
239
|
+
toolCallsById.set(part.toolCallId, { name: part.toolName, input: safeJson(part.input) });
|
|
240
|
+
toolCallOrder.push(part.toolCallId);
|
|
223
241
|
yield {
|
|
224
242
|
type: "tool_use",
|
|
225
243
|
id: part.toolCallId,
|
|
@@ -229,6 +247,10 @@ export class ChatSession {
|
|
|
229
247
|
}
|
|
230
248
|
else if (part.type === "tool-result") {
|
|
231
249
|
toolContext.push(`tool_result ${part.toolName}: ${truncateToolContext(safeJson(part.output))}`);
|
|
250
|
+
const call = toolCallsById.get(part.toolCallId);
|
|
251
|
+
if (call) {
|
|
252
|
+
call.output = truncateToolContext(safeJson(part.output));
|
|
253
|
+
}
|
|
232
254
|
yield {
|
|
233
255
|
type: "tool_result",
|
|
234
256
|
tool_use_id: part.toolCallId,
|
|
@@ -240,6 +262,11 @@ export class ChatSession {
|
|
|
240
262
|
else if (part.type === "tool-error") {
|
|
241
263
|
const message = errorMessage(part.error);
|
|
242
264
|
toolContext.push(`tool_error ${part.toolName}: ${message}`);
|
|
265
|
+
const call = toolCallsById.get(part.toolCallId);
|
|
266
|
+
if (call) {
|
|
267
|
+
call.output = message;
|
|
268
|
+
call.is_error = true;
|
|
269
|
+
}
|
|
243
270
|
yield {
|
|
244
271
|
type: "tool_result",
|
|
245
272
|
tool_use_id: part.toolCallId,
|
|
@@ -255,11 +282,14 @@ export class ChatSession {
|
|
|
255
282
|
}
|
|
256
283
|
}
|
|
257
284
|
completed = true;
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
285
|
+
const collectedToolCalls = toolCallOrder
|
|
286
|
+
.map((id) => toolCallsById.get(id))
|
|
287
|
+
.filter((call) => call !== undefined);
|
|
288
|
+
this.context.appendMessage(buildPersistedAssistantMessage({
|
|
289
|
+
fullText,
|
|
290
|
+
transcriptLines: toolContext,
|
|
291
|
+
toolCalls: collectedToolCalls,
|
|
292
|
+
}));
|
|
263
293
|
yield { type: "done", text: safeAccumulated };
|
|
264
294
|
}
|
|
265
295
|
catch (err) {
|
|
@@ -386,14 +416,6 @@ function safeRawStreamJson(value) {
|
|
|
386
416
|
function truncateToolContext(value) {
|
|
387
417
|
return value.length > 8000 ? `${value.slice(0, 8000)}...[truncated]` : value;
|
|
388
418
|
}
|
|
389
|
-
function truncateMiddle(value, maxChars, marker) {
|
|
390
|
-
if (value.length <= maxChars)
|
|
391
|
-
return value;
|
|
392
|
-
const available = Math.max(0, maxChars - marker.length - 2);
|
|
393
|
-
const headChars = Math.ceil(available / 2);
|
|
394
|
-
const tailChars = Math.floor(available / 2);
|
|
395
|
-
return `${value.slice(0, headChars)}\n${marker}\n${value.slice(-tailChars)}`;
|
|
396
|
-
}
|
|
397
419
|
function formatDuration(ms) {
|
|
398
420
|
if (ms % 60_000 === 0)
|
|
399
421
|
return `${ms / 60_000} minutes`;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-search.ts — DeepCCC 历史会话关键词检索
|
|
3
|
+
*
|
|
4
|
+
* 供 agent 通过 session_search 工具按需查找很久以前的原始消息/工具调用:
|
|
5
|
+
* - 主数据源:~/.deepccc/sessions/<sessionId>/context.json(明文 JSON,含 summary、
|
|
6
|
+
* messages.content 与结构化 toolCalls)
|
|
7
|
+
* - 可选数据源:~/.deepccc/raw-stream-logs/deepccc/<sessionId>/*.jsonl.gz(gzip 原始流,
|
|
8
|
+
* 逐行解压检索,默认关闭)
|
|
9
|
+
*
|
|
10
|
+
* 纯关键词匹配(多词 AND、大小写不敏感),不依赖向量索引。
|
|
11
|
+
*/
|
|
12
|
+
import { type BuiltinContextRole } from "./context.js";
|
|
13
|
+
export interface SessionSearchOptions {
|
|
14
|
+
/** 会话目录,默认 ~/.deepccc/sessions */
|
|
15
|
+
contextDir?: string;
|
|
16
|
+
/** raw-stream-logs 根目录,默认 ~/.deepccc/raw-stream-logs */
|
|
17
|
+
rawLogsDir?: string;
|
|
18
|
+
/** 是否搜索 gzip 原始流日志(默认 false,较慢) */
|
|
19
|
+
includeRawLogs?: boolean;
|
|
20
|
+
/** 只搜索指定 sessionId(目录名或 state.sessionId 匹配) */
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
/** 结果上限(默认 20,上限 50) */
|
|
23
|
+
maxResults?: number;
|
|
24
|
+
/** 单条命中片段最大字符数(默认 400) */
|
|
25
|
+
maxSnippetChars?: number;
|
|
26
|
+
/** 单个 raw log 文件最多解压检索的字节数(默认 4MB) */
|
|
27
|
+
maxRawLogBytesPerFile?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface SessionSearchInput {
|
|
30
|
+
query: string;
|
|
31
|
+
session_id?: string;
|
|
32
|
+
include_raw_logs?: boolean;
|
|
33
|
+
max_results?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface SessionSearchMatch {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
source: "context" | "summary" | "raw-log";
|
|
38
|
+
role?: BuiltinContextRole;
|
|
39
|
+
/** 在 messages 数组中的下标 */
|
|
40
|
+
messageIndex?: number;
|
|
41
|
+
/** 在 message.toolCalls 数组中的下标(仅结构化工具调用命中时) */
|
|
42
|
+
toolCallIndex?: number;
|
|
43
|
+
toolCallName?: string;
|
|
44
|
+
snippet: string;
|
|
45
|
+
filePath: string;
|
|
46
|
+
}
|
|
47
|
+
export interface SessionSearchOutput {
|
|
48
|
+
query: string;
|
|
49
|
+
terms: string[];
|
|
50
|
+
matches: SessionSearchMatch[];
|
|
51
|
+
truncated: boolean;
|
|
52
|
+
scannedSessions: number;
|
|
53
|
+
scannedRawLogFiles: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 关键词检索历史会话存档。context.json 为同步扫描;raw-stream-logs 为
|
|
57
|
+
* gzip 逐行解压(异步,仅 options.includeRawLogs 时启用)。
|
|
58
|
+
*/
|
|
59
|
+
export declare function searchBuiltinSessions(query: string, options?: SessionSearchOptions): Promise<SessionSearchOutput>;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-search.ts — DeepCCC 历史会话关键词检索
|
|
3
|
+
*
|
|
4
|
+
* 供 agent 通过 session_search 工具按需查找很久以前的原始消息/工具调用:
|
|
5
|
+
* - 主数据源:~/.deepccc/sessions/<sessionId>/context.json(明文 JSON,含 summary、
|
|
6
|
+
* messages.content 与结构化 toolCalls)
|
|
7
|
+
* - 可选数据源:~/.deepccc/raw-stream-logs/deepccc/<sessionId>/*.jsonl.gz(gzip 原始流,
|
|
8
|
+
* 逐行解压检索,默认关闭)
|
|
9
|
+
*
|
|
10
|
+
* 纯关键词匹配(多词 AND、大小写不敏感),不依赖向量索引。
|
|
11
|
+
*/
|
|
12
|
+
import { createReadStream, existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { createInterface } from "node:readline";
|
|
15
|
+
import { createGunzip } from "node:zlib";
|
|
16
|
+
import { RAW_STREAM_LOGS_DIR } from "./config.js";
|
|
17
|
+
import { DEFAULT_BUILTIN_CONTEXT_DIR } from "./context.js";
|
|
18
|
+
const DEFAULT_MAX_RESULTS = 20;
|
|
19
|
+
const MAX_RESULTS_CAP = 50;
|
|
20
|
+
const DEFAULT_MAX_SNIPPET_CHARS = 400;
|
|
21
|
+
const MAX_SNIPPET_CHARS_CAP = 2_000;
|
|
22
|
+
const DEFAULT_MAX_RAW_LOG_BYTES_PER_FILE = 4 * 1024 * 1024;
|
|
23
|
+
function clamp(value, min, max) {
|
|
24
|
+
return Math.min(max, Math.max(min, value));
|
|
25
|
+
}
|
|
26
|
+
function tokenize(query) {
|
|
27
|
+
return query
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.split(/\s+/)
|
|
30
|
+
.map((term) => term.trim())
|
|
31
|
+
.filter((term) => term.length > 0);
|
|
32
|
+
}
|
|
33
|
+
/** 多词 AND、大小写不敏感:候选文本必须包含全部关键词 */
|
|
34
|
+
function matchesTerms(text, terms) {
|
|
35
|
+
const lower = text.toLowerCase();
|
|
36
|
+
return terms.every((term) => lower.includes(term));
|
|
37
|
+
}
|
|
38
|
+
function makeSnippet(text, terms, maxChars) {
|
|
39
|
+
if (text.length <= maxChars)
|
|
40
|
+
return text;
|
|
41
|
+
const lower = text.toLowerCase();
|
|
42
|
+
let hitIndex = -1;
|
|
43
|
+
for (const term of terms) {
|
|
44
|
+
const index = lower.indexOf(term);
|
|
45
|
+
if (index !== -1) {
|
|
46
|
+
hitIndex = index;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const half = Math.floor(maxChars / 2);
|
|
51
|
+
if (hitIndex === -1) {
|
|
52
|
+
return `${text.slice(0, half)}…${text.slice(-half)}`;
|
|
53
|
+
}
|
|
54
|
+
const start = Math.max(0, hitIndex - half);
|
|
55
|
+
const end = Math.min(text.length, hitIndex + half);
|
|
56
|
+
const prefix = start > 0 ? "…" : "";
|
|
57
|
+
const suffix = end < text.length ? "…" : "";
|
|
58
|
+
return `${prefix}${text.slice(start, end)}${suffix}`;
|
|
59
|
+
}
|
|
60
|
+
function searchContextDir(query, options, maxResults, maxSnippetChars) {
|
|
61
|
+
const dir = options.contextDir ?? DEFAULT_BUILTIN_CONTEXT_DIR;
|
|
62
|
+
const terms = tokenize(query);
|
|
63
|
+
const matches = [];
|
|
64
|
+
if (terms.length === 0 || !existsSync(dir)) {
|
|
65
|
+
return { matches, scannedSessions: 0, totalHits: 0 };
|
|
66
|
+
}
|
|
67
|
+
const restrictTo = options.sessionId?.trim();
|
|
68
|
+
let scannedSessions = 0;
|
|
69
|
+
let totalHits = 0;
|
|
70
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
71
|
+
if (!entry.isDirectory())
|
|
72
|
+
continue;
|
|
73
|
+
if (restrictTo && entry.name !== restrictTo)
|
|
74
|
+
continue;
|
|
75
|
+
const filePath = join(dir, entry.name, "context.json");
|
|
76
|
+
if (!existsSync(filePath))
|
|
77
|
+
continue;
|
|
78
|
+
let state;
|
|
79
|
+
try {
|
|
80
|
+
state = JSON.parse(readFileSync(filePath, "utf8"));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue; // 损坏或非 JSON 的会话文件跳过
|
|
84
|
+
}
|
|
85
|
+
if (!state || typeof state !== "object")
|
|
86
|
+
continue;
|
|
87
|
+
if (restrictTo && state.sessionId !== restrictTo && entry.name !== restrictTo)
|
|
88
|
+
continue;
|
|
89
|
+
scannedSessions += 1;
|
|
90
|
+
const sessionId = typeof state.sessionId === "string" ? state.sessionId : entry.name;
|
|
91
|
+
if (typeof state.summary === "string" && matchesTerms(state.summary, terms)) {
|
|
92
|
+
totalHits += 1;
|
|
93
|
+
matches.push({
|
|
94
|
+
sessionId,
|
|
95
|
+
source: "summary",
|
|
96
|
+
snippet: makeSnippet(state.summary, terms, maxSnippetChars),
|
|
97
|
+
filePath,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(state.messages)) {
|
|
101
|
+
state.messages.forEach((message, messageIndex) => {
|
|
102
|
+
if (!message || typeof message !== "object")
|
|
103
|
+
return;
|
|
104
|
+
const raw = message;
|
|
105
|
+
const role = raw.role === "user" || raw.role === "assistant" ? raw.role : undefined;
|
|
106
|
+
if (typeof raw.content === "string" && matchesTerms(raw.content, terms)) {
|
|
107
|
+
totalHits += 1;
|
|
108
|
+
matches.push({
|
|
109
|
+
sessionId,
|
|
110
|
+
source: "context",
|
|
111
|
+
role,
|
|
112
|
+
messageIndex,
|
|
113
|
+
snippet: makeSnippet(raw.content, terms, maxSnippetChars),
|
|
114
|
+
filePath,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(raw.toolCalls)) {
|
|
118
|
+
raw.toolCalls.forEach((call, toolCallIndex) => {
|
|
119
|
+
if (!call || typeof call !== "object")
|
|
120
|
+
return;
|
|
121
|
+
const callRaw = call;
|
|
122
|
+
const name = typeof callRaw.name === "string" ? callRaw.name : "";
|
|
123
|
+
const input = typeof callRaw.input === "string" ? callRaw.input : "";
|
|
124
|
+
const output = typeof callRaw.output === "string" ? callRaw.output : "";
|
|
125
|
+
if (name.length === 0 && input.length === 0 && output.length === 0)
|
|
126
|
+
return;
|
|
127
|
+
const haystack = [name, input, output].join("\n");
|
|
128
|
+
if (!matchesTerms(haystack, terms))
|
|
129
|
+
return;
|
|
130
|
+
totalHits += 1;
|
|
131
|
+
const source = output || input || name;
|
|
132
|
+
matches.push({
|
|
133
|
+
sessionId,
|
|
134
|
+
source: "context",
|
|
135
|
+
role,
|
|
136
|
+
messageIndex,
|
|
137
|
+
toolCallIndex,
|
|
138
|
+
toolCallName: name,
|
|
139
|
+
snippet: makeSnippet(source, terms, maxSnippetChars),
|
|
140
|
+
filePath,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { matches: matches.slice(0, maxResults), scannedSessions, totalHits };
|
|
148
|
+
}
|
|
149
|
+
async function searchRawLogs(query, options, maxResults, maxSnippetChars) {
|
|
150
|
+
const terms = tokenize(query);
|
|
151
|
+
const rootDir = options.rawLogsDir ?? RAW_STREAM_LOGS_DIR;
|
|
152
|
+
const matches = [];
|
|
153
|
+
if (terms.length === 0 || !existsSync(rootDir)) {
|
|
154
|
+
return { matches, scannedFiles: 0, truncated: false };
|
|
155
|
+
}
|
|
156
|
+
const toolRoot = join(rootDir, "deepccc");
|
|
157
|
+
if (!existsSync(toolRoot))
|
|
158
|
+
return { matches, scannedFiles: 0, truncated: false };
|
|
159
|
+
const restrictTo = options.sessionId?.trim();
|
|
160
|
+
const maxBytesPerFile = Math.max(0, options.maxRawLogBytesPerFile ?? DEFAULT_MAX_RAW_LOG_BYTES_PER_FILE);
|
|
161
|
+
// 收集所有 .jsonl.gz 文件,按 mtime 新→旧排序(最新轮次优先)
|
|
162
|
+
const files = [];
|
|
163
|
+
for (const sessionEntry of readdirSync(toolRoot, { withFileTypes: true })) {
|
|
164
|
+
if (!sessionEntry.isDirectory())
|
|
165
|
+
continue;
|
|
166
|
+
if (restrictTo && sessionEntry.name !== restrictTo)
|
|
167
|
+
continue;
|
|
168
|
+
const sessionDir = join(toolRoot, sessionEntry.name);
|
|
169
|
+
let entries;
|
|
170
|
+
try {
|
|
171
|
+
entries = readdirSync(sessionDir, { withFileTypes: true });
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
for (const fileEntry of entries) {
|
|
177
|
+
if (!fileEntry.isFile() || !fileEntry.name.endsWith(".jsonl.gz"))
|
|
178
|
+
continue;
|
|
179
|
+
const filePath = join(sessionDir, fileEntry.name);
|
|
180
|
+
try {
|
|
181
|
+
const info = statSync(filePath);
|
|
182
|
+
files.push({ path: filePath, sessionId: sessionEntry.name, mtimeMs: info.mtimeMs });
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// 无法 stat 的文件跳过
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
files.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
190
|
+
let scannedFiles = 0;
|
|
191
|
+
let truncated = false;
|
|
192
|
+
for (const file of files) {
|
|
193
|
+
if (matches.length >= maxResults) {
|
|
194
|
+
truncated = true;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
scannedFiles += 1;
|
|
198
|
+
const snippets = await searchGzipFileLines(file.path, terms, maxBytesPerFile, maxSnippetChars);
|
|
199
|
+
for (const snippet of snippets) {
|
|
200
|
+
matches.push({
|
|
201
|
+
sessionId: file.sessionId,
|
|
202
|
+
source: "raw-log",
|
|
203
|
+
snippet,
|
|
204
|
+
filePath: file.path,
|
|
205
|
+
});
|
|
206
|
+
if (matches.length >= maxResults)
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { matches: matches.slice(0, maxResults), scannedFiles, truncated };
|
|
211
|
+
}
|
|
212
|
+
async function searchGzipFileLines(filePath, terms, maxBytes, maxSnippetChars) {
|
|
213
|
+
const hits = [];
|
|
214
|
+
let bytes = 0;
|
|
215
|
+
try {
|
|
216
|
+
const source = createReadStream(filePath);
|
|
217
|
+
const gunzip = createGunzip();
|
|
218
|
+
// 防崩溃(线上事故):截断/损坏的 gzip 会在 zlib 层 emit 'error'(如
|
|
219
|
+
// "unexpected end of file")。若无人监听,错误事件会升级为 uncaughtException
|
|
220
|
+
// 直接杀死整个服务(崩溃黑匣子 exit(1))。显式挂监听把错误降级为
|
|
221
|
+
// “跳过该文件”:destroy 流让 for await 正常结束,不再冒泡。
|
|
222
|
+
source.on("error", () => gunzip.destroy());
|
|
223
|
+
gunzip.on("error", () => gunzip.destroy());
|
|
224
|
+
const reader = createInterface({ input: source.pipe(gunzip), crlfDelay: Infinity });
|
|
225
|
+
// readline 会把 input 流的 error 转发到自己身上,同样需要监听避免冒泡
|
|
226
|
+
reader.on("error", () => { });
|
|
227
|
+
for await (const line of reader) {
|
|
228
|
+
bytes += Buffer.byteLength(line, "utf-8");
|
|
229
|
+
if (bytes > maxBytes)
|
|
230
|
+
break;
|
|
231
|
+
if (!matchesTerms(line, terms))
|
|
232
|
+
continue;
|
|
233
|
+
hits.push(makeSnippet(line, terms, maxSnippetChars));
|
|
234
|
+
if (hits.length >= 50)
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// 损坏的 gzip 文件按 best-effort 跳过
|
|
240
|
+
}
|
|
241
|
+
return hits;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* 关键词检索历史会话存档。context.json 为同步扫描;raw-stream-logs 为
|
|
245
|
+
* gzip 逐行解压(异步,仅 options.includeRawLogs 时启用)。
|
|
246
|
+
*/
|
|
247
|
+
export async function searchBuiltinSessions(query, options = {}) {
|
|
248
|
+
const terms = tokenize(query);
|
|
249
|
+
const maxResults = clamp(options.maxResults ?? DEFAULT_MAX_RESULTS, 1, MAX_RESULTS_CAP);
|
|
250
|
+
const maxSnippetChars = clamp(options.maxSnippetChars ?? DEFAULT_MAX_SNIPPET_CHARS, 80, MAX_SNIPPET_CHARS_CAP);
|
|
251
|
+
if (terms.length === 0) {
|
|
252
|
+
return {
|
|
253
|
+
query,
|
|
254
|
+
terms,
|
|
255
|
+
matches: [],
|
|
256
|
+
truncated: false,
|
|
257
|
+
scannedSessions: 0,
|
|
258
|
+
scannedRawLogFiles: 0,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const contextResult = searchContextDir(query, options, maxResults, maxSnippetChars);
|
|
262
|
+
let rawResult = { matches: [], scannedFiles: 0, truncated: false };
|
|
263
|
+
if (options.includeRawLogs) {
|
|
264
|
+
rawResult = await searchRawLogs(query, options, maxResults, maxSnippetChars);
|
|
265
|
+
}
|
|
266
|
+
const matches = [...contextResult.matches, ...rawResult.matches].slice(0, maxResults);
|
|
267
|
+
const truncated = contextResult.totalHits > maxResults || rawResult.truncated || rawResult.matches.length > maxResults;
|
|
268
|
+
return {
|
|
269
|
+
query,
|
|
270
|
+
terms,
|
|
271
|
+
matches,
|
|
272
|
+
truncated,
|
|
273
|
+
scannedSessions: contextResult.scannedSessions,
|
|
274
|
+
scannedRawLogFiles: rawResult.scannedFiles,
|
|
275
|
+
};
|
|
276
|
+
}
|
package/package.json
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "deepccc",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A lightweight DeepSeek-optimized coding agent with OpenAI-compatible model support.",
|
|
5
|
-
"license": "Apache-2.0",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"agent",
|
|
8
|
-
"deepseek",
|
|
9
|
-
"coding-agent",
|
|
10
|
-
"openai-compatible",
|
|
11
|
-
"cli"
|
|
12
|
-
],
|
|
13
|
-
"repository": {
|
|
14
|
-
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/wzj998/deepccc-agent.git"
|
|
16
|
-
},
|
|
17
|
-
"bugs": {
|
|
18
|
-
"url": "https://github.com/wzj998/deepccc-agent/issues"
|
|
19
|
-
},
|
|
20
|
-
"homepage": "https://github.com/wzj998/deepccc-agent#readme",
|
|
21
|
-
"type": "module",
|
|
22
|
-
"main": "./dist/index.js",
|
|
23
|
-
"types": "./dist/index.d.ts",
|
|
24
|
-
"exports": {
|
|
25
|
-
".": {
|
|
26
|
-
"types": "./dist/index.d.ts",
|
|
27
|
-
"import": "./dist/index.js"
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"bin": {
|
|
31
|
-
"deepccc": "bin/deepccc.mjs"
|
|
32
|
-
},
|
|
33
|
-
"files": [
|
|
34
|
-
"dist/",
|
|
35
|
-
"bin/",
|
|
36
|
-
"docs/",
|
|
37
|
-
"package.json",
|
|
38
|
-
"README.md",
|
|
39
|
-
"LICENSE"
|
|
40
|
-
],
|
|
41
|
-
"scripts": {
|
|
42
|
-
"build": "node node_modules/typescript/bin/tsc -p tsconfig.build.json",
|
|
43
|
-
"prepack": "node node_modules/typescript/bin/tsc -p tsconfig.build.json",
|
|
44
|
-
"test": "vitest run",
|
|
45
|
-
"test:watch": "vitest",
|
|
46
|
-
"typecheck": "node node_modules/typescript/bin/tsc --noEmit"
|
|
47
|
-
},
|
|
48
|
-
"dependencies": {
|
|
49
|
-
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
50
|
-
"@vscode/ripgrep": "^1.18.0",
|
|
51
|
-
"ai": "^6.0.184"
|
|
52
|
-
},
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"@types/node": "^20.0.0",
|
|
55
|
-
"tsx": "^4.0.0",
|
|
56
|
-
"typescript": "^5.0.0",
|
|
57
|
-
"vitest": "^3.2.4"
|
|
58
|
-
},
|
|
59
|
-
"engines": {
|
|
60
|
-
"node": ">=20"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "deepccc",
|
|
3
|
+
"version": "0.1.12",
|
|
4
|
+
"description": "A lightweight DeepSeek-optimized coding agent with OpenAI-compatible model support.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"agent",
|
|
8
|
+
"deepseek",
|
|
9
|
+
"coding-agent",
|
|
10
|
+
"openai-compatible",
|
|
11
|
+
"cli"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/wzj998/deepccc-agent.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/wzj998/deepccc-agent/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/wzj998/deepccc-agent#readme",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"deepccc": "bin/deepccc.mjs"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/",
|
|
35
|
+
"bin/",
|
|
36
|
+
"docs/",
|
|
37
|
+
"package.json",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "node node_modules/typescript/bin/tsc -p tsconfig.build.json",
|
|
43
|
+
"prepack": "node node_modules/typescript/bin/tsc -p tsconfig.build.json",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"typecheck": "node node_modules/typescript/bin/tsc --noEmit"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
50
|
+
"@vscode/ripgrep": "^1.18.0",
|
|
51
|
+
"ai": "^6.0.184"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^20.0.0",
|
|
55
|
+
"tsx": "^4.0.0",
|
|
56
|
+
"typescript": "^5.0.0",
|
|
57
|
+
"vitest": "^3.2.4"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=20"
|
|
61
|
+
}
|
|
62
|
+
}
|