chatccc 0.2.196 → 0.2.198
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/agent-prompts/cursor_specific.md +13 -13
- package/bin/cccagent.mjs +17 -17
- package/config.sample.json +27 -27
- package/package.json +2 -1
- package/src/__tests__/agent-reload-config-rpc.test.ts +99 -0
- package/src/__tests__/builtin-chat-session.test.ts +277 -181
- package/src/__tests__/builtin-cli-json.test.ts +39 -39
- package/src/__tests__/builtin-config.test.ts +33 -33
- package/src/__tests__/builtin-context.test.ts +163 -163
- package/src/__tests__/builtin-file-tools.test.ts +224 -196
- package/src/__tests__/builtin-session-select.test.ts +116 -116
- package/src/__tests__/builtin-sigint.test.ts +56 -56
- package/src/__tests__/cards.test.ts +109 -109
- package/src/__tests__/ccc-adapter.test.ts +114 -113
- package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
- package/src/__tests__/chatgpt-subscription.test.ts +135 -135
- package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
- package/src/__tests__/claude-raw-stream-log.test.ts +87 -0
- package/src/__tests__/codex-raw-stream-log.test.ts +163 -0
- package/src/__tests__/config-reload.test.ts +10 -10
- package/src/__tests__/config-sample.test.ts +18 -18
- package/src/__tests__/cursor-adapter.test.ts +167 -113
- package/src/__tests__/feishu-avatar.test.ts +40 -40
- package/src/__tests__/jsonl-stream.test.ts +79 -0
- package/src/__tests__/orchestrator.test.ts +181 -154
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/session.test.ts +40 -40
- package/src/__tests__/sim-platform.test.ts +12 -12
- package/src/__tests__/web-ui.test.ts +209 -209
- package/src/adapters/ccc-adapter.ts +121 -119
- package/src/adapters/claude-adapter.ts +603 -566
- package/src/adapters/codex-adapter.ts +65 -52
- package/src/adapters/cursor-adapter.ts +269 -276
- package/src/adapters/jsonl-stream.ts +157 -0
- package/src/adapters/raw-stream-log.ts +124 -124
- package/src/agent-reload-config-rpc.ts +34 -0
- package/src/builtin/cli.ts +473 -461
- package/src/builtin/context.ts +323 -323
- package/src/builtin/file-tools.ts +1072 -915
- package/src/builtin/index.ts +404 -353
- package/src/builtin/session-select.ts +48 -48
- package/src/builtin/sigint.ts +50 -50
- package/src/cards.ts +195 -195
- package/src/chatgpt-subscription-rpc.ts +27 -27
- package/src/chatgpt-subscription.ts +299 -299
- package/src/chrome-devtools-guard.ts +318 -318
- package/src/config.ts +125 -125
- package/src/feishu-api.ts +49 -49
- package/src/index.ts +8 -13
- package/src/orchestrator.ts +166 -145
- package/src/runtime-reload.ts +34 -0
- package/src/session.ts +141 -141
- package/src/web-ui.ts +205 -205
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
11
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import { dirname, join } from "node:path";
|
|
13
|
-
import { createInterface } from "node:readline";
|
|
14
13
|
import { randomUUID } from "node:crypto";
|
|
15
14
|
import { fileURLToPath } from "node:url";
|
|
16
15
|
|
|
@@ -28,7 +27,12 @@ import {
|
|
|
28
27
|
type CodexSessionMetaStore,
|
|
29
28
|
} from "./codex-session-meta-store.ts";
|
|
30
29
|
import { killProcessTree } from "./proc-tree-kill.ts";
|
|
31
|
-
import { config } from "../config.ts";
|
|
30
|
+
import { config, RAW_STREAM_LOGS_DIR } from "../config.ts";
|
|
31
|
+
import {
|
|
32
|
+
createRawStreamLog,
|
|
33
|
+
type RawStreamLogHandle,
|
|
34
|
+
} from "./raw-stream-log.ts";
|
|
35
|
+
import { readJsonLinesWithBadJsonIdleWatchdog } from "./jsonl-stream.ts";
|
|
32
36
|
|
|
33
37
|
// ---------------------------------------------------------------------------
|
|
34
38
|
// 特殊注入提示
|
|
@@ -69,9 +73,9 @@ function buildCodexPromptText(userText: string): string {
|
|
|
69
73
|
// ---------------------------------------------------------------------------
|
|
70
74
|
|
|
71
75
|
/** 可通过 config.json codex.path 自定义 Codex 可执行文件路径 */
|
|
72
|
-
function detectCodexCommand(): string {
|
|
73
|
-
return config.codex.path || "codex";
|
|
74
|
-
}
|
|
76
|
+
function detectCodexCommand(): string {
|
|
77
|
+
return config.codex.path || "codex";
|
|
78
|
+
}
|
|
75
79
|
|
|
76
80
|
/** exec 模式共用参数:JSONL 输出、绕过沙盒和确认、跳过 git 仓库检查 */
|
|
77
81
|
const CODEX_BASE_ARGS = [
|
|
@@ -183,13 +187,13 @@ export function normalizeCodexMessage(
|
|
|
183
187
|
// 子进程辅助函数
|
|
184
188
|
// ---------------------------------------------------------------------------
|
|
185
189
|
|
|
186
|
-
function spawnCodex(
|
|
187
|
-
args: string[],
|
|
188
|
-
cwd?: string,
|
|
189
|
-
stdinText?: string,
|
|
190
|
-
modelOverride?: string,
|
|
191
|
-
effortOverride?: string,
|
|
192
|
-
): ChildProcess {
|
|
190
|
+
function spawnCodex(
|
|
191
|
+
args: string[],
|
|
192
|
+
cwd?: string,
|
|
193
|
+
stdinText?: string,
|
|
194
|
+
modelOverride?: string,
|
|
195
|
+
effortOverride?: string,
|
|
196
|
+
): ChildProcess {
|
|
193
197
|
const allArgs = [...args];
|
|
194
198
|
const model = modelOverride ?? resolveCodexModel();
|
|
195
199
|
if (model) {
|
|
@@ -197,12 +201,12 @@ function spawnCodex(
|
|
|
197
201
|
const execIdx = allArgs.indexOf("exec");
|
|
198
202
|
allArgs.splice(execIdx + 1, 0, "-m", model);
|
|
199
203
|
}
|
|
200
|
-
const effort = effortOverride ?? resolveCodexEffort();
|
|
204
|
+
const effort = effortOverride ?? resolveCodexEffort();
|
|
201
205
|
if (effort) {
|
|
202
206
|
allArgs.push("-c", `model_reasoning_effort="${effort}"`);
|
|
203
207
|
}
|
|
204
208
|
|
|
205
|
-
const proc = spawn(detectCodexCommand(), allArgs, {
|
|
209
|
+
const proc = spawn(detectCodexCommand(), allArgs, {
|
|
206
210
|
cwd,
|
|
207
211
|
stdio: [stdinText !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
|
|
208
212
|
windowsHide: true,
|
|
@@ -231,26 +235,16 @@ function spawnCodex(
|
|
|
231
235
|
async function* readJsonLines(
|
|
232
236
|
proc: ChildProcess,
|
|
233
237
|
signal?: AbortSignal,
|
|
238
|
+
rawLog?: RawStreamLogHandle | null,
|
|
234
239
|
): AsyncGenerator<CodexEvent> {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (!trimmed) continue;
|
|
244
|
-
try {
|
|
245
|
-
yield JSON.parse(trimmed) as CodexEvent;
|
|
246
|
-
} catch {
|
|
247
|
-
// 非 JSON 行静默跳过(如 "Reading prompt from stdin...")
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
} finally {
|
|
251
|
-
signal?.removeEventListener("abort", onAbort);
|
|
252
|
-
rl.close();
|
|
253
|
-
}
|
|
240
|
+
yield* readJsonLinesWithBadJsonIdleWatchdog<CodexEvent>({
|
|
241
|
+
input: proc.stdout!,
|
|
242
|
+
tool: "codex",
|
|
243
|
+
tag: "codex",
|
|
244
|
+
signal,
|
|
245
|
+
rawLog,
|
|
246
|
+
parse: (line) => JSON.parse(line) as CodexEvent,
|
|
247
|
+
});
|
|
254
248
|
}
|
|
255
249
|
|
|
256
250
|
// ---------------------------------------------------------------------------
|
|
@@ -258,17 +252,17 @@ async function* readJsonLines(
|
|
|
258
252
|
// ---------------------------------------------------------------------------
|
|
259
253
|
|
|
260
254
|
class CodexAdapter implements ToolAdapter {
|
|
261
|
-
readonly displayName = "Codex";
|
|
262
|
-
readonly sessionDescPrefix = "Codex Session:";
|
|
263
|
-
private metaStore: CodexSessionMetaStore;
|
|
264
|
-
private modelOverride: string | undefined;
|
|
265
|
-
private effortOverride: string | undefined;
|
|
266
|
-
|
|
267
|
-
constructor(metaStore: CodexSessionMetaStore, modelOverride?: string, effortOverride?: string) {
|
|
268
|
-
this.metaStore = metaStore;
|
|
269
|
-
this.modelOverride = modelOverride;
|
|
270
|
-
this.effortOverride = effortOverride;
|
|
271
|
-
}
|
|
255
|
+
readonly displayName = "Codex";
|
|
256
|
+
readonly sessionDescPrefix = "Codex Session:";
|
|
257
|
+
private metaStore: CodexSessionMetaStore;
|
|
258
|
+
private modelOverride: string | undefined;
|
|
259
|
+
private effortOverride: string | undefined;
|
|
260
|
+
|
|
261
|
+
constructor(metaStore: CodexSessionMetaStore, modelOverride?: string, effortOverride?: string) {
|
|
262
|
+
this.metaStore = metaStore;
|
|
263
|
+
this.modelOverride = modelOverride;
|
|
264
|
+
this.effortOverride = effortOverride;
|
|
265
|
+
}
|
|
272
266
|
|
|
273
267
|
// createSession: 生成 sessionId,记录 cwd,不创建 Codex 线程(延迟到首次 prompt)
|
|
274
268
|
async createSession(cwd: string): Promise<CreateSessionResult> {
|
|
@@ -298,19 +292,37 @@ class CodexAdapter implements ToolAdapter {
|
|
|
298
292
|
? [...baseArgs, "-C", cwd, "-"]
|
|
299
293
|
: [...baseArgs, "resume", threadId, "-"];
|
|
300
294
|
|
|
301
|
-
const proc = spawnCodex(args, cwd, buildCodexPromptText(userText), this.modelOverride, this.effortOverride);
|
|
295
|
+
const proc = spawnCodex(args, cwd, buildCodexPromptText(userText), this.modelOverride, this.effortOverride);
|
|
302
296
|
if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
|
|
303
297
|
|
|
298
|
+
const rawLogConfig = config.rawStreamLogs.codex;
|
|
299
|
+
let rawLog: RawStreamLogHandle | null = null;
|
|
300
|
+
try {
|
|
301
|
+
rawLog = await createRawStreamLog({
|
|
302
|
+
enabled: rawLogConfig.enabled,
|
|
303
|
+
rootDir: RAW_STREAM_LOGS_DIR,
|
|
304
|
+
tool: "codex",
|
|
305
|
+
sessionId,
|
|
306
|
+
label: "prompt",
|
|
307
|
+
maxBytesPerTurn: rawLogConfig.maxBytesPerTurn,
|
|
308
|
+
retentionDays: rawLogConfig.retentionDays,
|
|
309
|
+
});
|
|
310
|
+
} catch (err) {
|
|
311
|
+
console.error(`[Codex raw stream log] create failed: ${(err as Error).message}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
304
314
|
// 关键:spawn 用了 shell:true,proc.pid 指向的是壳进程(cmd.exe / sh)。
|
|
305
315
|
// 真正干活的是壳的孙子 codex.exe。普通 proc.kill() 在 Windows 上只杀第一层,
|
|
306
316
|
// 会留下幽灵 node + codex.exe 继续烧 token、stream-state 永远停在 running。
|
|
307
317
|
// 因此 abort 与 finally 都必须用 killProcessTree 整棵进程树一起收尸。
|
|
308
318
|
const onAbort = () => { void killProcessTree(proc.pid); };
|
|
309
319
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
320
|
+
let completed = false;
|
|
310
321
|
|
|
311
322
|
try {
|
|
312
|
-
for await (const raw of readJsonLines(proc, signal)) {
|
|
323
|
+
for await (const raw of readJsonLines(proc, signal, rawLog)) {
|
|
313
324
|
if (signal?.aborted) break;
|
|
325
|
+
if (raw.type === "turn.completed") completed = true;
|
|
314
326
|
|
|
315
327
|
if (
|
|
316
328
|
isFirstPrompt &&
|
|
@@ -328,6 +340,7 @@ class CodexAdapter implements ToolAdapter {
|
|
|
328
340
|
} finally {
|
|
329
341
|
signal?.removeEventListener("abort", onAbort);
|
|
330
342
|
await killProcessTree(proc.pid);
|
|
343
|
+
await rawLog?.close({ keep: rawLogConfig.keepCompleted || signal?.aborted === true || !completed });
|
|
331
344
|
if (proc.pid !== undefined) options?.onProcessExit?.({ pid: proc.pid });
|
|
332
345
|
}
|
|
333
346
|
}
|
|
@@ -352,8 +365,8 @@ class CodexAdapter implements ToolAdapter {
|
|
|
352
365
|
export interface CreateCodexAdapterOptions {
|
|
353
366
|
metaStore?: CodexSessionMetaStore;
|
|
354
367
|
/** per-session 模型覆盖(/model 命令);传了就用,不传走全局 codex.model */
|
|
355
|
-
model?: string;
|
|
356
|
-
effort?: string;
|
|
368
|
+
model?: string;
|
|
369
|
+
effort?: string;
|
|
357
370
|
}
|
|
358
371
|
|
|
359
372
|
export function createCodexAdapter(
|
|
@@ -361,7 +374,7 @@ export function createCodexAdapter(
|
|
|
361
374
|
): ToolAdapter {
|
|
362
375
|
return new CodexAdapter(
|
|
363
376
|
options.metaStore ?? defaultCodexSessionMetaStore,
|
|
364
|
-
options.model,
|
|
365
|
-
options.effort,
|
|
366
|
-
);
|
|
367
|
-
}
|
|
377
|
+
options.model,
|
|
378
|
+
options.effort,
|
|
379
|
+
);
|
|
380
|
+
}
|