chatccc 0.2.196 → 0.2.197

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.
Files changed (51) hide show
  1. package/agent-prompts/cursor_specific.md +13 -13
  2. package/bin/cccagent.mjs +17 -17
  3. package/config.sample.json +27 -27
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-reload-config-rpc.test.ts +99 -0
  6. package/src/__tests__/builtin-chat-session.test.ts +277 -181
  7. package/src/__tests__/builtin-cli-json.test.ts +39 -39
  8. package/src/__tests__/builtin-config.test.ts +33 -33
  9. package/src/__tests__/builtin-context.test.ts +163 -163
  10. package/src/__tests__/builtin-file-tools.test.ts +224 -196
  11. package/src/__tests__/builtin-session-select.test.ts +116 -116
  12. package/src/__tests__/builtin-sigint.test.ts +56 -56
  13. package/src/__tests__/cards.test.ts +109 -109
  14. package/src/__tests__/ccc-adapter.test.ts +114 -113
  15. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  16. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  17. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  18. package/src/__tests__/claude-raw-stream-log.test.ts +87 -0
  19. package/src/__tests__/codex-raw-stream-log.test.ts +120 -0
  20. package/src/__tests__/config-reload.test.ts +10 -10
  21. package/src/__tests__/config-sample.test.ts +18 -18
  22. package/src/__tests__/cursor-adapter.test.ts +113 -113
  23. package/src/__tests__/feishu-avatar.test.ts +40 -40
  24. package/src/__tests__/orchestrator.test.ts +181 -154
  25. package/src/__tests__/raw-stream-log.test.ts +106 -106
  26. package/src/__tests__/session.test.ts +40 -40
  27. package/src/__tests__/sim-platform.test.ts +12 -12
  28. package/src/__tests__/web-ui.test.ts +209 -209
  29. package/src/adapters/ccc-adapter.ts +121 -119
  30. package/src/adapters/claude-adapter.ts +603 -566
  31. package/src/adapters/codex-adapter.ts +57 -32
  32. package/src/adapters/cursor-adapter.ts +264 -264
  33. package/src/adapters/raw-stream-log.ts +124 -124
  34. package/src/agent-reload-config-rpc.ts +34 -0
  35. package/src/builtin/cli.ts +473 -461
  36. package/src/builtin/context.ts +323 -323
  37. package/src/builtin/file-tools.ts +1072 -915
  38. package/src/builtin/index.ts +404 -353
  39. package/src/builtin/session-select.ts +48 -48
  40. package/src/builtin/sigint.ts +50 -50
  41. package/src/cards.ts +195 -195
  42. package/src/chatgpt-subscription-rpc.ts +27 -27
  43. package/src/chatgpt-subscription.ts +299 -299
  44. package/src/chrome-devtools-guard.ts +318 -318
  45. package/src/config.ts +125 -125
  46. package/src/feishu-api.ts +49 -49
  47. package/src/index.ts +8 -13
  48. package/src/orchestrator.ts +166 -145
  49. package/src/runtime-reload.ts +34 -0
  50. package/src/session.ts +141 -141
  51. package/src/web-ui.ts +205 -205
@@ -28,7 +28,11 @@ import {
28
28
  type CodexSessionMetaStore,
29
29
  } from "./codex-session-meta-store.ts";
30
30
  import { killProcessTree } from "./proc-tree-kill.ts";
31
- import { config } from "../config.ts";
31
+ import { config, RAW_STREAM_LOGS_DIR } from "../config.ts";
32
+ import {
33
+ createRawStreamLog,
34
+ type RawStreamLogHandle,
35
+ } from "./raw-stream-log.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,6 +235,7 @@ function spawnCodex(
231
235
  async function* readJsonLines(
232
236
  proc: ChildProcess,
233
237
  signal?: AbortSignal,
238
+ rawLog?: RawStreamLogHandle | null,
234
239
  ): AsyncGenerator<CodexEvent> {
235
240
  const rl = createInterface({ input: proc.stdout!, crlfDelay: Infinity });
236
241
  // abort 时主动 close readline,避免等待 Windows 管道自然关闭(可能延迟数分钟)
@@ -241,6 +246,7 @@ async function* readJsonLines(
241
246
  if (signal?.aborted) break;
242
247
  const trimmed = line.trim();
243
248
  if (!trimmed) continue;
249
+ rawLog?.writeLine(trimmed);
244
250
  try {
245
251
  yield JSON.parse(trimmed) as CodexEvent;
246
252
  } catch {
@@ -258,17 +264,17 @@ async function* readJsonLines(
258
264
  // ---------------------------------------------------------------------------
259
265
 
260
266
  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
- }
267
+ readonly displayName = "Codex";
268
+ readonly sessionDescPrefix = "Codex Session:";
269
+ private metaStore: CodexSessionMetaStore;
270
+ private modelOverride: string | undefined;
271
+ private effortOverride: string | undefined;
272
+
273
+ constructor(metaStore: CodexSessionMetaStore, modelOverride?: string, effortOverride?: string) {
274
+ this.metaStore = metaStore;
275
+ this.modelOverride = modelOverride;
276
+ this.effortOverride = effortOverride;
277
+ }
272
278
 
273
279
  // createSession: 生成 sessionId,记录 cwd,不创建 Codex 线程(延迟到首次 prompt)
274
280
  async createSession(cwd: string): Promise<CreateSessionResult> {
@@ -298,19 +304,37 @@ class CodexAdapter implements ToolAdapter {
298
304
  ? [...baseArgs, "-C", cwd, "-"]
299
305
  : [...baseArgs, "resume", threadId, "-"];
300
306
 
301
- const proc = spawnCodex(args, cwd, buildCodexPromptText(userText), this.modelOverride, this.effortOverride);
307
+ const proc = spawnCodex(args, cwd, buildCodexPromptText(userText), this.modelOverride, this.effortOverride);
302
308
  if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
303
309
 
310
+ const rawLogConfig = config.rawStreamLogs.codex;
311
+ let rawLog: RawStreamLogHandle | null = null;
312
+ try {
313
+ rawLog = await createRawStreamLog({
314
+ enabled: rawLogConfig.enabled,
315
+ rootDir: RAW_STREAM_LOGS_DIR,
316
+ tool: "codex",
317
+ sessionId,
318
+ label: "prompt",
319
+ maxBytesPerTurn: rawLogConfig.maxBytesPerTurn,
320
+ retentionDays: rawLogConfig.retentionDays,
321
+ });
322
+ } catch (err) {
323
+ console.error(`[Codex raw stream log] create failed: ${(err as Error).message}`);
324
+ }
325
+
304
326
  // 关键:spawn 用了 shell:true,proc.pid 指向的是壳进程(cmd.exe / sh)。
305
327
  // 真正干活的是壳的孙子 codex.exe。普通 proc.kill() 在 Windows 上只杀第一层,
306
328
  // 会留下幽灵 node + codex.exe 继续烧 token、stream-state 永远停在 running。
307
329
  // 因此 abort 与 finally 都必须用 killProcessTree 整棵进程树一起收尸。
308
330
  const onAbort = () => { void killProcessTree(proc.pid); };
309
331
  signal?.addEventListener("abort", onAbort, { once: true });
332
+ let completed = false;
310
333
 
311
334
  try {
312
- for await (const raw of readJsonLines(proc, signal)) {
335
+ for await (const raw of readJsonLines(proc, signal, rawLog)) {
313
336
  if (signal?.aborted) break;
337
+ if (raw.type === "turn.completed") completed = true;
314
338
 
315
339
  if (
316
340
  isFirstPrompt &&
@@ -328,6 +352,7 @@ class CodexAdapter implements ToolAdapter {
328
352
  } finally {
329
353
  signal?.removeEventListener("abort", onAbort);
330
354
  await killProcessTree(proc.pid);
355
+ await rawLog?.close({ keep: rawLogConfig.keepCompleted || signal?.aborted === true || !completed });
331
356
  if (proc.pid !== undefined) options?.onProcessExit?.({ pid: proc.pid });
332
357
  }
333
358
  }
@@ -352,8 +377,8 @@ class CodexAdapter implements ToolAdapter {
352
377
  export interface CreateCodexAdapterOptions {
353
378
  metaStore?: CodexSessionMetaStore;
354
379
  /** per-session 模型覆盖(/model 命令);传了就用,不传走全局 codex.model */
355
- model?: string;
356
- effort?: string;
380
+ model?: string;
381
+ effort?: string;
357
382
  }
358
383
 
359
384
  export function createCodexAdapter(
@@ -361,7 +386,7 @@ export function createCodexAdapter(
361
386
  ): ToolAdapter {
362
387
  return new CodexAdapter(
363
388
  options.metaStore ?? defaultCodexSessionMetaStore,
364
- options.model,
365
- options.effort,
366
- );
367
- }
389
+ options.model,
390
+ options.effort,
391
+ );
392
+ }