deepccc 0.1.1

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/dist/cli.js ADDED
@@ -0,0 +1,513 @@
1
+ /**
2
+ * DeepCCC terminal REPL and JSONL streaming entrypoint — 同步自 ChatCCC
3
+ *
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"
8
+ *
9
+ * 交互模式(TTY)下,单轮回复渲染为固定"过程区块":状态行 + 折叠工具行 +
10
+ * 原地更新正文,不再滚屏刷 JSON;完成/停止/异常后定型留在屏幕上。
11
+ * 非 TTY(管道/CI)或 --plain 回退为纯文本流式输出;--stream-json 机器接口不变。
12
+ */
13
+ import * as readline from "node:readline";
14
+ import * as process from "node:process";
15
+ import { appendFileSync } from "node:fs";
16
+ import { resolve as resolvePath } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import { listBuiltinContextSessions } from "./context.js";
19
+ import { resolveBuiltinSession } from "./session-select.js";
20
+ import { createCtrlCState } from "./sigint.js";
21
+ import { reduceProgress } from "./progress/reducer.js";
22
+ import { TerminalProgressRenderer } from "./progress/terminal-renderer.js";
23
+ import { progressView } from "./progress/view.js";
24
+ import { defaultLogDir, setupFileLogging } from "./file-log.js";
25
+ function parsePositiveIntegerOption(name, value) {
26
+ const parsed = Number(value);
27
+ if (!Number.isInteger(parsed) || parsed <= 0) {
28
+ throw new Error(`${name} must be a positive integer`);
29
+ }
30
+ return parsed;
31
+ }
32
+ function parseArgs(argv = process.argv.slice(2)) {
33
+ const config = {};
34
+ const options = {};
35
+ let listSessions = false;
36
+ let resume;
37
+ let help = false;
38
+ let streamJson = false;
39
+ let prompt = null;
40
+ let plain = false;
41
+ for (let i = 0; i < argv.length; i++) {
42
+ const arg = argv[i];
43
+ const next = argv[i + 1];
44
+ if (arg === "--model" && next !== undefined) {
45
+ config.model = next;
46
+ i++;
47
+ }
48
+ else if (arg === "--effort" && next !== undefined) {
49
+ config.effort = next;
50
+ i++;
51
+ }
52
+ else if (arg === "--base-url" && next !== undefined) {
53
+ config.baseURL = next;
54
+ i++;
55
+ }
56
+ else if (arg === "--api-key" && next !== undefined) {
57
+ config.apiKey = next;
58
+ i++;
59
+ }
60
+ else if (arg === "--cwd" && next !== undefined) {
61
+ options.cwd = next;
62
+ i++;
63
+ }
64
+ else if (arg === "--max-steps" && next !== undefined) {
65
+ options.maxSteps = parsePositiveIntegerOption("--max-steps", next);
66
+ i++;
67
+ }
68
+ else if (arg === "--resume") {
69
+ if (next !== undefined && !next.startsWith("--")) {
70
+ resume = next;
71
+ i++;
72
+ }
73
+ else {
74
+ resume = true;
75
+ }
76
+ }
77
+ else if (arg === "--list-sessions") {
78
+ listSessions = true;
79
+ }
80
+ else if (arg === "--stream-json") {
81
+ streamJson = true;
82
+ }
83
+ else if (arg === "--prompt" && next !== undefined) {
84
+ prompt = next;
85
+ i++;
86
+ }
87
+ else if (arg === "--plain") {
88
+ plain = true;
89
+ }
90
+ else if (arg === "--help" || arg === "-h") {
91
+ help = true;
92
+ }
93
+ }
94
+ return { config, options, listSessions, resume, help, streamJson, prompt, plain };
95
+ }
96
+ async function loadRuntime() {
97
+ const [{ ChatSession }, { config: appConfig }] = await Promise.all([
98
+ import("./index.js"),
99
+ import("./config.js"),
100
+ ]);
101
+ return { ChatSession, appConfig };
102
+ }
103
+ function printHelp(appConfig) {
104
+ console.log([
105
+ "DeepCCC terminal agent",
106
+ "",
107
+ "Usage: deepccc [options]",
108
+ "",
109
+ "Options:",
110
+ ` --model <name> Model name (current default ${appConfig.model})`,
111
+ ` --effort <level> Reasoning effort: none/minimal/low/medium/high/xhigh/max (overrides config.effort)`,
112
+ ` --base-url <url> OpenAI-compatible API base URL (current default ${appConfig.baseURL})`,
113
+ " --api-key <key> API key",
114
+ " --cwd <path> Working directory",
115
+ " --max-steps <n> Optional tool-step limit. Omit for no step limit",
116
+ " --resume [id] Resume latest cwd session, or the explicit session id",
117
+ " --list-sessions List saved sessions and exit",
118
+ " --stream-json One-shot mode: write JSONL events to stdout",
119
+ " --prompt <text> Prompt text for --stream-json",
120
+ " --plain Force plain streaming output (no progress block renderer)",
121
+ " --help, -h Show help",
122
+ "",
123
+ "Default config source:",
124
+ " ~/.deepccc/config.json, DEEPCCC_* environment variables, or DEEPSEEK_* aliases",
125
+ "",
126
+ ].join("\n"));
127
+ }
128
+ function formatTime(ms) {
129
+ return new Date(ms).toLocaleString();
130
+ }
131
+ function printSessions(streamJson = false) {
132
+ const sessions = listBuiltinContextSessions();
133
+ if (streamJson) {
134
+ writeJsonLine({
135
+ type: "sessions",
136
+ sessions: sessions.map((session) => ({
137
+ session_id: session.sessionId,
138
+ turns: session.totalMessages,
139
+ compacted_messages: session.compactedMessages,
140
+ has_summary: session.hasSummary,
141
+ updated_at: session.updatedAt,
142
+ cwd: session.cwd,
143
+ })),
144
+ });
145
+ return;
146
+ }
147
+ if (sessions.length === 0) {
148
+ console.log("No saved DeepCCC sessions");
149
+ return;
150
+ }
151
+ for (const session of sessions) {
152
+ const summary = session.hasSummary ? " summary=yes" : "";
153
+ const cwd = session.cwd ? ` cwd=${session.cwd}` : "";
154
+ console.log(`${session.sessionId} turns=${session.totalMessages} compacted=${session.compactedMessages}${summary} updated=${formatTime(session.updatedAt)}${cwd}`);
155
+ }
156
+ }
157
+ function stringifyConsoleArg(value) {
158
+ if (typeof value === "string")
159
+ return value;
160
+ if (value instanceof Error)
161
+ return value.stack ?? value.message;
162
+ try {
163
+ return JSON.stringify(value);
164
+ }
165
+ catch {
166
+ return String(value);
167
+ }
168
+ }
169
+ function redirectConsoleLogsToStderr() {
170
+ const write = (...args) => {
171
+ process.stderr.write(`${args.map(stringifyConsoleArg).join(" ")}\n`);
172
+ };
173
+ console.log = write;
174
+ console.info = write;
175
+ console.warn = write;
176
+ }
177
+ function writeJsonLine(event) {
178
+ process.stdout.write(`${JSON.stringify(event)}\n`);
179
+ }
180
+ function streamJsonEvent(event) {
181
+ if (event.type === "text") {
182
+ writeJsonLine({
183
+ type: "text_delta",
184
+ text: event.text,
185
+ accumulated: event.accumulated,
186
+ });
187
+ }
188
+ else if (event.type === "compact") {
189
+ writeJsonLine({
190
+ type: "compact",
191
+ compacted_messages: event.compactedMessages,
192
+ });
193
+ }
194
+ else if (event.type === "tool_use") {
195
+ writeJsonLine({
196
+ type: "tool_call",
197
+ id: event.id,
198
+ name: event.name,
199
+ input: event.input,
200
+ });
201
+ }
202
+ else if (event.type === "tool_result") {
203
+ writeJsonLine({
204
+ type: "tool_result",
205
+ tool_call_id: event.tool_use_id,
206
+ name: event.name,
207
+ content: event.content,
208
+ is_error: event.is_error,
209
+ });
210
+ }
211
+ else if (event.type === "done") {
212
+ writeJsonLine({
213
+ type: "done",
214
+ text: event.text,
215
+ });
216
+ }
217
+ else if (event.type === "error") {
218
+ writeJsonLine({
219
+ type: "error",
220
+ message: event.message,
221
+ });
222
+ }
223
+ }
224
+ async function readPromptFromStdin() {
225
+ const chunks = [];
226
+ for await (const chunk of process.stdin) {
227
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
228
+ }
229
+ return Buffer.concat(chunks).toString("utf8");
230
+ }
231
+ async function runStreamJson(args) {
232
+ redirectConsoleLogsToStderr();
233
+ if (args.listSessions) {
234
+ printSessions(true);
235
+ return 0;
236
+ }
237
+ const prompt = args.prompt ?? (!process.stdin.isTTY ? await readPromptFromStdin() : "");
238
+ if (!prompt.trim()) {
239
+ writeJsonLine({ type: "error", message: "--stream-json requires --prompt <text> or stdin input" });
240
+ return 1;
241
+ }
242
+ let runtime;
243
+ try {
244
+ runtime = await loadRuntime();
245
+ }
246
+ catch (err) {
247
+ writeJsonLine({ type: "error", message: err.message });
248
+ return 1;
249
+ }
250
+ const cwd = resolvePath(args.options.cwd ?? process.cwd());
251
+ let resolvedSession;
252
+ try {
253
+ resolvedSession = resolveBuiltinSession({ cwd, resume: args.resume });
254
+ }
255
+ catch (err) {
256
+ writeJsonLine({ type: "error", message: err.message });
257
+ return 1;
258
+ }
259
+ let session;
260
+ try {
261
+ session = new runtime.ChatSession(args.config, {
262
+ ...args.options,
263
+ cwd,
264
+ persist: true,
265
+ sessionId: resolvedSession.sessionId,
266
+ });
267
+ }
268
+ catch (err) {
269
+ writeJsonLine({ type: "error", message: err.message });
270
+ return 1;
271
+ }
272
+ writeJsonLine({
273
+ type: "start",
274
+ session_id: resolvedSession.sessionId,
275
+ mode: resolvedSession.mode,
276
+ cwd,
277
+ model: args.config.model ?? runtime.appConfig.model,
278
+ });
279
+ try {
280
+ for await (const event of session.chat(prompt)) {
281
+ streamJsonEvent(event);
282
+ }
283
+ return 0;
284
+ }
285
+ catch (err) {
286
+ writeJsonLine({ type: "error", message: err.message });
287
+ return 1;
288
+ }
289
+ }
290
+ const C = {
291
+ reset: "\x1b[0m",
292
+ dim: "\x1b[2m",
293
+ green: "\x1b[32m",
294
+ yellow: "\x1b[33m",
295
+ };
296
+ /**
297
+ * 交互模式下渲染器独占终端 stdout:普通日志只写日志文件、不回显到终端,
298
+ * 避免生成过程中任何 console 输出混入过程区块、破坏行数计数(导致重绘
299
+ * 上移不足、把上方历史内容"吃掉")。错误提示(console.error)保留回显。
300
+ */
301
+ function muteConsoleLogToFile(logPath) {
302
+ const writeFile = (level, args) => {
303
+ try {
304
+ const text = args
305
+ .map((a) => typeof a === "string" ? a
306
+ : a instanceof Error ? (a.stack ?? a.message)
307
+ : JSON.stringify(a))
308
+ .join(" ");
309
+ appendFileSync(logPath, `[${new Date().toISOString()}] [${level}] ${text}\n`, "utf8");
310
+ }
311
+ catch {
312
+ // 日志系统自身失败不影响主流程
313
+ }
314
+ };
315
+ console.log = (...args) => writeFile("LOG", args);
316
+ console.info = (...args) => writeFile("INFO", args);
317
+ console.warn = (...args) => writeFile("WARN", args);
318
+ }
319
+ async function runRepl(args) {
320
+ const { ChatSession, appConfig } = await loadRuntime();
321
+ const cwd = resolvePath(args.options.cwd ?? process.cwd());
322
+ let resolvedSession;
323
+ try {
324
+ resolvedSession = resolveBuiltinSession({ cwd, resume: args.resume });
325
+ }
326
+ catch (err) {
327
+ console.error(`${C.yellow}${err.message}${C.reset}`);
328
+ process.exit(1);
329
+ }
330
+ console.log(`${C.dim}DeepCCC agent${C.reset}`);
331
+ console.log(`${C.dim}Model: ${args.config.model ?? appConfig.model}${C.reset}`);
332
+ console.log(`${C.dim}Directory: ${cwd}${C.reset}`);
333
+ console.log(`${C.dim}Session: ${resolvedSession.sessionId} (${resolvedSession.mode === "new" ? "new" : "resumed"})${C.reset}`);
334
+ console.log(`${C.dim}Type a message to chat. Double Ctrl+C interrupts generation or exits. Type exit to quit.${C.reset}`);
335
+ console.log("");
336
+ // 交互渲染模式下 console 输出只写日志文件、不回显到终端(渲染器独占 stdout,
337
+ // 避免生成中日志混入区块破坏行数)。--plain 无渲染器,不需要静音。
338
+ if (process.stdout.isTTY === true && !args.plain) {
339
+ muteConsoleLogToFile(setupFileLogging(defaultLogDir(), "index").logPath);
340
+ }
341
+ let session;
342
+ try {
343
+ session = new ChatSession(args.config, {
344
+ ...args.options,
345
+ cwd,
346
+ persist: true,
347
+ sessionId: resolvedSession.sessionId,
348
+ });
349
+ }
350
+ catch (err) {
351
+ console.error(`${C.yellow}${err.message}${C.reset}`);
352
+ process.exit(1);
353
+ }
354
+ const rl = readline.createInterface({
355
+ input: process.stdin,
356
+ output: process.stdout,
357
+ prompt: `${C.green}>${C.reset} `,
358
+ });
359
+ let currentAbort = null;
360
+ const ctrlCState = createCtrlCState();
361
+ rl.prompt();
362
+ rl.on("line", async (line) => {
363
+ ctrlCState.reset();
364
+ const input = line.trim();
365
+ if (!input) {
366
+ rl.prompt();
367
+ return;
368
+ }
369
+ if (input === "exit") {
370
+ process.stdout.write(`${C.dim}bye${C.reset}\n`);
371
+ rl.close();
372
+ return;
373
+ }
374
+ if (input === "/clear") {
375
+ session.reset();
376
+ process.stdout.write(`${C.dim}session cleared${C.reset}\n`);
377
+ rl.prompt();
378
+ return;
379
+ }
380
+ if (input === "/history") {
381
+ process.stdout.write(`${C.dim}${session.turnCount} conversation turns${C.reset}\n`);
382
+ rl.prompt();
383
+ return;
384
+ }
385
+ currentAbort = new AbortController();
386
+ const signal = currentAbort.signal;
387
+ // TTY 下用"过程区块"(飞书过程卡片的终端形态):固定区域原地更新、
388
+ // 工具调用折叠为单行;非 TTY(管道/CI)或 --plain 回退为纯文本流式输出。
389
+ const useTerminalBlock = process.stdout.isTTY === true && !args.plain;
390
+ const renderer = useTerminalBlock ? new TerminalProgressRenderer() : null;
391
+ let view = null;
392
+ if (renderer) {
393
+ view = progressView({ headerTitle: "生成中..." });
394
+ // 先回行首换行再 begin:让过程区块从输入行下方开始,避免首帧 \r\x1b[2K
395
+ // 清掉用户刚输入的问题行(历史文本不被刷掉)。\r\n 兼容 readline
396
+ // 行提交后光标仍停在输入行行尾的情况。
397
+ process.stdout.write("\r\n");
398
+ renderer.begin(view);
399
+ }
400
+ let rendererEnded = false;
401
+ try {
402
+ let lastAccumulated = "";
403
+ for await (const event of session.chat(input, signal)) {
404
+ if (renderer && view) {
405
+ view = reduceProgress(view, event);
406
+ if (event.type === "text" || event.type === "compact") {
407
+ renderer.render(view);
408
+ }
409
+ else {
410
+ renderer.flush();
411
+ }
412
+ }
413
+ else if (event.type === "text") {
414
+ const newText = event.accumulated.slice(lastAccumulated.length);
415
+ process.stdout.write(newText);
416
+ lastAccumulated = event.accumulated;
417
+ }
418
+ else if (event.type === "done") {
419
+ if (lastAccumulated)
420
+ console.log("");
421
+ console.log(`${C.dim}[done]${C.reset}`);
422
+ }
423
+ else if (event.type === "compact") {
424
+ console.log(`${C.dim}[context compacted: ${event.compactedMessages} old messages]${C.reset}`);
425
+ }
426
+ else if (event.type === "tool_use") {
427
+ console.log(`\n${C.dim}[tool] ${event.name} ${stringifyConsoleArg(event.input)}${C.reset}`);
428
+ }
429
+ else if (event.type === "tool_result") {
430
+ const status = event.is_error ? "error" : "ok";
431
+ console.log(`${C.dim}[tool result] ${event.name ?? event.tool_use_id} ${status}${C.reset}`);
432
+ }
433
+ else if (event.type === "error") {
434
+ console.log(`\n${C.yellow}[error] ${event.message}${C.reset}`);
435
+ }
436
+ }
437
+ }
438
+ catch (err) {
439
+ if (renderer && view) {
440
+ const aborted = err instanceof Error && err.name === "AbortError";
441
+ view = progressView({ ...view, status: aborted ? "stopped" : "error", showStop: false });
442
+ renderer.end(view);
443
+ rendererEnded = true;
444
+ process.stdout.write("\n");
445
+ }
446
+ console.error(`\n${C.yellow}[error] ${err.message}${C.reset}`);
447
+ }
448
+ finally {
449
+ if (renderer && view && !rendererEnded) {
450
+ // 定型终态区块(完成/已停止/异常结束)留在屏幕上,恢复光标
451
+ renderer.end(view);
452
+ process.stdout.write("\n");
453
+ }
454
+ currentAbort = null;
455
+ ctrlCState.reset();
456
+ }
457
+ rl.prompt();
458
+ });
459
+ rl.on("SIGINT", () => {
460
+ const action = ctrlCState.press(currentAbort !== null);
461
+ if (action === "exit") {
462
+ console.error(`\n${C.dim}bye${C.reset}`);
463
+ rl.close();
464
+ return;
465
+ }
466
+ if (action === "interrupt") {
467
+ console.error(`\n${C.yellow}[interrupting...]${C.reset}`);
468
+ currentAbort?.abort();
469
+ currentAbort = null;
470
+ return;
471
+ }
472
+ if (action === "arm-interrupt") {
473
+ console.error(`\n${C.dim}Press Ctrl+C again to interrupt current response${C.reset}`);
474
+ return;
475
+ }
476
+ if (action === "arm-exit") {
477
+ console.error(`\n${C.dim}Press Ctrl+C again to exit, or type exit${C.reset}`);
478
+ rl.prompt();
479
+ }
480
+ });
481
+ rl.on("close", () => {
482
+ process.stdout.write("\n");
483
+ process.exit(0);
484
+ });
485
+ }
486
+ async function main() {
487
+ const args = parseArgs();
488
+ if (args.streamJson) {
489
+ const code = await runStreamJson(args);
490
+ process.exit(code);
491
+ }
492
+ if (args.help) {
493
+ const { appConfig } = await loadRuntime();
494
+ printHelp(appConfig);
495
+ return;
496
+ }
497
+ if (args.listSessions) {
498
+ printSessions();
499
+ return;
500
+ }
501
+ await runRepl(args);
502
+ }
503
+ function isDirectCliInvocation() {
504
+ const current = resolvePath(fileURLToPath(import.meta.url));
505
+ const invoked = process.argv[1] ? resolvePath(process.argv[1]) : "";
506
+ return current === invoked;
507
+ }
508
+ if (isDirectCliInvocation()) {
509
+ main().catch((err) => {
510
+ console.error(`${C.yellow}startup failed: ${err.message}${C.reset}`);
511
+ process.exit(1);
512
+ });
513
+ }
@@ -0,0 +1,17 @@
1
+ export interface DeepCccConfig {
2
+ apiKey: string;
3
+ baseURL: string;
4
+ model: string;
5
+ /** Reasoning effort(none/minimal/low/medium/high/xhigh/max),留空不传 reasoning_effort */
6
+ effort: string;
7
+ rawStreamLogs: {
8
+ enabled: boolean;
9
+ maxBytesPerTurn: number;
10
+ retentionDays: number;
11
+ keepCompleted: boolean;
12
+ };
13
+ }
14
+ export declare const DEEPCCC_HOME: string;
15
+ export declare const RAW_STREAM_LOGS_DIR: string;
16
+ export declare function ensureConfigDir(): void;
17
+ export declare const config: DeepCccConfig;
package/dist/config.js ADDED
@@ -0,0 +1,64 @@
1
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ export const DEEPCCC_HOME = join(homedir(), ".deepccc");
5
+ export const RAW_STREAM_LOGS_DIR = join(DEEPCCC_HOME, "raw-stream-logs");
6
+ const CONFIG_PATH = join(DEEPCCC_HOME, "config.json");
7
+ const DEFAULT_CONFIG = {
8
+ apiKey: "",
9
+ baseURL: "https://api.deepseek.com/v1",
10
+ model: "deepseek-v4-pro",
11
+ effort: "",
12
+ rawStreamLogs: {
13
+ enabled: false,
14
+ maxBytesPerTurn: 1024 * 1024,
15
+ retentionDays: 7,
16
+ keepCompleted: false,
17
+ },
18
+ };
19
+ function readConfigFile() {
20
+ if (!existsSync(CONFIG_PATH))
21
+ return {};
22
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
23
+ return raw && typeof raw === "object" ? raw : {};
24
+ }
25
+ function env(name) {
26
+ const value = process.env[name]?.trim();
27
+ return value ? value : undefined;
28
+ }
29
+ function boolEnv(name) {
30
+ const value = env(name)?.toLowerCase();
31
+ if (value === undefined)
32
+ return undefined;
33
+ if (["1", "true", "yes", "on"].includes(value))
34
+ return true;
35
+ if (["0", "false", "no", "off"].includes(value))
36
+ return false;
37
+ return undefined;
38
+ }
39
+ function numberEnv(name) {
40
+ const value = Number(env(name));
41
+ return Number.isFinite(value) && value >= 0 ? value : undefined;
42
+ }
43
+ function loadConfig() {
44
+ const file = readConfigFile();
45
+ const rawLogs = file.rawStreamLogs && typeof file.rawStreamLogs === "object"
46
+ ? file.rawStreamLogs
47
+ : {};
48
+ return {
49
+ apiKey: env("DEEPCCC_API_KEY") ?? env("DEEPSEEK_API_KEY") ?? file.apiKey ?? DEFAULT_CONFIG.apiKey,
50
+ baseURL: env("DEEPCCC_BASE_URL") ?? env("DEEPSEEK_BASE_URL") ?? file.baseURL ?? DEFAULT_CONFIG.baseURL,
51
+ model: env("DEEPCCC_MODEL") ?? env("DEEPSEEK_MODEL") ?? file.model ?? DEFAULT_CONFIG.model,
52
+ effort: env("DEEPCCC_EFFORT") ?? env("DEEPSEEK_EFFORT") ?? file.effort ?? DEFAULT_CONFIG.effort,
53
+ rawStreamLogs: {
54
+ enabled: boolEnv("DEEPCCC_RAW_STREAM_LOGS") ?? rawLogs.enabled ?? DEFAULT_CONFIG.rawStreamLogs.enabled,
55
+ maxBytesPerTurn: numberEnv("DEEPCCC_RAW_STREAM_MAX_BYTES") ?? rawLogs.maxBytesPerTurn ?? DEFAULT_CONFIG.rawStreamLogs.maxBytesPerTurn,
56
+ retentionDays: numberEnv("DEEPCCC_RAW_STREAM_RETENTION_DAYS") ?? rawLogs.retentionDays ?? DEFAULT_CONFIG.rawStreamLogs.retentionDays,
57
+ keepCompleted: boolEnv("DEEPCCC_RAW_STREAM_KEEP_COMPLETED") ?? rawLogs.keepCompleted ?? DEFAULT_CONFIG.rawStreamLogs.keepCompleted,
58
+ },
59
+ };
60
+ }
61
+ export function ensureConfigDir() {
62
+ mkdirSync(dirname(CONFIG_PATH), { recursive: true });
63
+ }
64
+ export const config = loadConfig();
@@ -0,0 +1,73 @@
1
+ export type BuiltinContextRole = "user" | "assistant";
2
+ export interface BuiltinContextMessage {
3
+ role: BuiltinContextRole;
4
+ content: string;
5
+ }
6
+ export interface BuiltinContextState {
7
+ version: 1;
8
+ createdAt: number;
9
+ updatedAt: number;
10
+ sessionId: string;
11
+ cwd?: string;
12
+ summary: string;
13
+ messages: BuiltinContextMessage[];
14
+ totalMessages: number;
15
+ compactedMessages: number;
16
+ }
17
+ export interface BuiltinContextSessionInfo {
18
+ sessionId: string;
19
+ createdAt: number;
20
+ updatedAt: number;
21
+ cwd?: string;
22
+ totalMessages: number;
23
+ compactedMessages: number;
24
+ hasSummary: boolean;
25
+ contextFilePath: string;
26
+ }
27
+ export interface BuiltinCompactionPlan {
28
+ previousSummary: string;
29
+ oldMessages: BuiltinContextMessage[];
30
+ recentMessages: BuiltinContextMessage[];
31
+ }
32
+ export interface BuiltinContextOptions {
33
+ persist?: boolean;
34
+ contextDir?: string;
35
+ sessionId?: string;
36
+ cwd?: string;
37
+ compactAtTokens?: number;
38
+ keepRecentMessages?: number;
39
+ }
40
+ export declare const DEFAULT_BUILTIN_CONTEXT_DIR: string;
41
+ export declare const DEFAULT_COMPACT_AT_TOKENS = 48000;
42
+ export declare const DEFAULT_KEEP_RECENT_MESSAGES = 16;
43
+ export declare function normalizeBuiltinSessionId(value: string): string;
44
+ export declare function defaultBuiltinSessionId(cwd?: string): string;
45
+ export declare function newBuiltinSessionId(now?: Date, suffix?: string): string;
46
+ export declare function getBuiltinContextSession(sessionId: string, contextDir?: string): BuiltinContextSessionInfo | null;
47
+ export declare function listBuiltinContextSessions(contextDir?: string): BuiltinContextSessionInfo[];
48
+ export declare function latestBuiltinSessionForCwd(cwd: string, contextDir?: string): BuiltinContextSessionInfo | null;
49
+ export declare function estimateBuiltinContextTokens(summary: string, messages: readonly BuiltinContextMessage[]): number;
50
+ export declare function serializeMessagesForSummary(messages: readonly BuiltinContextMessage[]): string;
51
+ export declare function buildSummaryPrompt(plan: BuiltinCompactionPlan): string;
52
+ export declare class BuiltinContextManager {
53
+ readonly persist: boolean;
54
+ readonly contextDir: string;
55
+ readonly sessionId: string;
56
+ readonly compactAtTokens: number;
57
+ readonly keepRecentMessages: number;
58
+ private readonly cwd?;
59
+ private state;
60
+ constructor(options?: BuiltinContextOptions);
61
+ get summary(): string;
62
+ get messages(): BuiltinContextMessage[];
63
+ get totalMessages(): number;
64
+ get contextFilePath(): string;
65
+ appendMessage(message: BuiltinContextMessage): void;
66
+ setSummary(summary: string): void;
67
+ buildModelMessages(): BuiltinContextMessage[];
68
+ planCompaction(): BuiltinCompactionPlan | null;
69
+ applyCompaction(summary: string, plan: BuiltinCompactionPlan): void;
70
+ reset(): void;
71
+ save(): void;
72
+ private load;
73
+ }