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
@@ -20,16 +20,16 @@ import type {
20
20
  SessionInfo,
21
21
  } from "./adapter-interface.ts";
22
22
  import { parseUserCommand } from "./adapter-interface.ts";
23
- import { config, CURSOR_AGENT_COMMAND, CURSOR_AGENT_ARGS, RAW_STREAM_LOGS_DIR } from "../config.ts";
23
+ import { config, CURSOR_AGENT_COMMAND, CURSOR_AGENT_ARGS, RAW_STREAM_LOGS_DIR } from "../config.ts";
24
24
  import {
25
25
  defaultCursorSessionMetaStore,
26
26
  type CursorSessionMetaStore,
27
27
  } from "./cursor-session-meta-store.ts";
28
- import { killProcessTree } from "./proc-tree-kill.ts";
29
- import {
30
- createRawStreamLog,
31
- type RawStreamLogHandle,
32
- } from "./raw-stream-log.ts";
28
+ import { killProcessTree } from "./proc-tree-kill.ts";
29
+ import {
30
+ createRawStreamLog,
31
+ type RawStreamLogHandle,
32
+ } from "./raw-stream-log.ts";
33
33
 
34
34
  // ---------------------------------------------------------------------------
35
35
  // 特殊注入提示
@@ -105,9 +105,9 @@ interface CursorMessageLine {
105
105
  tool_call?: Record<string, unknown>;
106
106
  }
107
107
 
108
- interface CursorContentBlock {
109
- type?: string;
110
- text?: string;
108
+ interface CursorContentBlock {
109
+ type?: string;
110
+ text?: string;
111
111
  thinking?: string;
112
112
  name?: string;
113
113
  input?: unknown;
@@ -115,73 +115,73 @@ interface CursorContentBlock {
115
115
  content?: unknown;
116
116
  is_error?: boolean;
117
117
  query?: string;
118
- [key: string]: unknown;
119
- }
120
-
121
- interface CursorProcessCloseInfo {
122
- code: number | null;
123
- signal: NodeJS.Signals | null;
124
- stderr: string;
125
- }
126
-
127
- interface CursorProcessHandle {
128
- proc: ChildProcess;
129
- getStderr(): string;
130
- waitForClose(): Promise<CursorProcessCloseInfo>;
131
- }
132
-
133
- interface CursorStreamStats {
134
- stdoutLength: number;
135
- rawLineCount: number;
136
- parsedLineCount: number;
137
- }
138
-
139
- type CursorSpawn = typeof spawn;
140
-
141
- function createCursorStreamStats(): CursorStreamStats {
142
- return { stdoutLength: 0, rawLineCount: 0, parsedLineCount: 0 };
143
- }
144
-
145
- function isCursorAuthRelatedError(stderr: string): boolean {
146
- const text = stderr.toLowerCase();
147
- return (
148
- text.includes("authentication required") ||
149
- text.includes("not logged in") ||
150
- text.includes("login") ||
151
- text.includes("sign in") ||
152
- text.includes("unauthorized") ||
153
- text.includes("401") ||
154
- text.includes("cursor_api_key") ||
155
- text.includes("api key")
156
- );
157
- }
158
-
159
- export function formatCursorAgentEmptyOutputMessage(args: {
160
- exitCode: number | null;
161
- stdoutLength: number;
162
- stderr: string;
163
- }): string | null {
164
- if (args.exitCode === 0) return null;
165
- if (args.stdoutLength !== 0) return null;
166
- if (args.stderr.trim().length === 0) return null;
167
-
168
- if (isCursorAuthRelatedError(args.stderr)) {
169
- return "Cursor Agent 没有返回内容。检测到认证相关错误,可能需要重新登录 Cursor Agent,或配置 CURSOR_API_KEY。请在本机运行 agent status 检查状态;如未登录,请运行 agent login 后重试。";
170
- }
171
-
172
- return "Cursor Agent 没有返回内容。底层命令异常退出,请检查本机 Cursor Agent 状态后重试。";
173
- }
174
-
175
- function createCursorAgentFailureError(info: CursorProcessCloseInfo): Error {
176
- const stderr = info.stderr.trim().slice(0, 500);
177
- return new Error(
178
- `Cursor Agent exited without stream-json output (exit=${info.code ?? "unknown"}, stderr=${stderr})`,
179
- );
180
- }
181
-
182
- // ---------------------------------------------------------------------------
183
- // normalizeCursorMessage — Cursor 消息 → UnifiedStreamMessage | null
184
- // ---------------------------------------------------------------------------
118
+ [key: string]: unknown;
119
+ }
120
+
121
+ interface CursorProcessCloseInfo {
122
+ code: number | null;
123
+ signal: NodeJS.Signals | null;
124
+ stderr: string;
125
+ }
126
+
127
+ interface CursorProcessHandle {
128
+ proc: ChildProcess;
129
+ getStderr(): string;
130
+ waitForClose(): Promise<CursorProcessCloseInfo>;
131
+ }
132
+
133
+ interface CursorStreamStats {
134
+ stdoutLength: number;
135
+ rawLineCount: number;
136
+ parsedLineCount: number;
137
+ }
138
+
139
+ type CursorSpawn = typeof spawn;
140
+
141
+ function createCursorStreamStats(): CursorStreamStats {
142
+ return { stdoutLength: 0, rawLineCount: 0, parsedLineCount: 0 };
143
+ }
144
+
145
+ function isCursorAuthRelatedError(stderr: string): boolean {
146
+ const text = stderr.toLowerCase();
147
+ return (
148
+ text.includes("authentication required") ||
149
+ text.includes("not logged in") ||
150
+ text.includes("login") ||
151
+ text.includes("sign in") ||
152
+ text.includes("unauthorized") ||
153
+ text.includes("401") ||
154
+ text.includes("cursor_api_key") ||
155
+ text.includes("api key")
156
+ );
157
+ }
158
+
159
+ export function formatCursorAgentEmptyOutputMessage(args: {
160
+ exitCode: number | null;
161
+ stdoutLength: number;
162
+ stderr: string;
163
+ }): string | null {
164
+ if (args.exitCode === 0) return null;
165
+ if (args.stdoutLength !== 0) return null;
166
+ if (args.stderr.trim().length === 0) return null;
167
+
168
+ if (isCursorAuthRelatedError(args.stderr)) {
169
+ return "Cursor Agent 没有返回内容。检测到认证相关错误,可能需要重新登录 Cursor Agent,或配置 CURSOR_API_KEY。请在本机运行 agent status 检查状态;如未登录,请运行 agent login 后重试。";
170
+ }
171
+
172
+ return "Cursor Agent 没有返回内容。底层命令异常退出,请检查本机 Cursor Agent 状态后重试。";
173
+ }
174
+
175
+ function createCursorAgentFailureError(info: CursorProcessCloseInfo): Error {
176
+ const stderr = info.stderr.trim().slice(0, 500);
177
+ return new Error(
178
+ `Cursor Agent exited without stream-json output (exit=${info.code ?? "unknown"}, stderr=${stderr})`,
179
+ );
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // normalizeCursorMessage — Cursor 消息 → UnifiedStreamMessage | null
184
+ // ---------------------------------------------------------------------------
185
185
 
186
186
  /** Cursor tool_call 内部 key → 统一工具名 */
187
187
  function mapToolCallKey(key: string): string {
@@ -359,14 +359,14 @@ export function normalizeCursorMessage(
359
359
  // 子进程辅助函数
360
360
  // ---------------------------------------------------------------------------
361
361
 
362
- function spawnAgent(
363
- extraArgs: string[],
364
- cwd?: string,
365
- stdinText?: string,
366
- modelOverride?: string,
367
- mode?: "plan" | "ask",
368
- spawnImpl: CursorSpawn = spawn,
369
- ): CursorProcessHandle {
362
+ function spawnAgent(
363
+ extraArgs: string[],
364
+ cwd?: string,
365
+ stdinText?: string,
366
+ modelOverride?: string,
367
+ mode?: "plan" | "ask",
368
+ spawnImpl: CursorSpawn = spawn,
369
+ ): CursorProcessHandle {
370
370
  let allArgs: string[];
371
371
  if (mode) {
372
372
  // plan/ask 模式:移除 --force/--yolo,添加 --mode plan/ask
@@ -385,59 +385,59 @@ function spawnAgent(
385
385
  allArgs.push("--model", modelOverride);
386
386
  }
387
387
  }
388
- const proc = spawnImpl(CURSOR_AGENT_COMMAND, allArgs, {
389
- cwd,
390
- stdio: [stdinText !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
391
- windowsHide: true,
392
- shell: true,
393
- });
388
+ const proc = spawnImpl(CURSOR_AGENT_COMMAND, allArgs, {
389
+ cwd,
390
+ stdio: [stdinText !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
391
+ windowsHide: true,
392
+ shell: true,
393
+ });
394
394
 
395
395
  console.log(`[Cursor debug] spawn: cmd=${CURSOR_AGENT_COMMAND}, args=[${allArgs.join(", ")}], cwd=${cwd ?? "(none)"}, stdinLen=${stdinText?.length ?? 0}, pid=${proc.pid}`);
396
396
 
397
- // 收集 stderr,子进程异常退出时输出到日志,方便排查静默失败
398
- let stderr = "";
399
- let closeInfo: CursorProcessCloseInfo | null = null;
400
- let resolveClose: (info: CursorProcessCloseInfo) => void = () => {};
401
- const closePromise = new Promise<CursorProcessCloseInfo>((resolve) => {
402
- resolveClose = resolve;
403
- });
404
- const settleClose = (code: number | null, signal: NodeJS.Signals | null) => {
405
- if (closeInfo) return;
406
- closeInfo = { code, signal, stderr };
407
- if (stderr.trim()) {
408
- console.error(`[Cursor stderr] exit=${code}: ${stderr.trim().slice(0, 2000)}`);
409
- }
410
- resolveClose(closeInfo);
411
- };
412
- proc.stderr!.on("data", (chunk: Buffer) => { stderr += chunk.toString(); });
413
- proc.once("error", (err) => {
414
- stderr += `${stderr ? "\n" : ""}${(err as Error).message}`;
415
- settleClose(null, null);
416
- });
417
- proc.once("close", (code, signal) => { settleClose(code, signal); });
418
-
419
- if (stdinText !== undefined) {
420
- proc.stdin!.write(stdinText);
421
- proc.stdin!.end();
422
- }
423
- return {
424
- proc,
425
- getStderr: () => stderr,
426
- waitForClose: async () => {
427
- if (closeInfo) return { ...closeInfo, stderr };
428
- const info = await closePromise;
429
- return { ...info, stderr };
430
- },
431
- };
432
- }
433
-
434
- async function* readJsonLines(
435
- proc: ChildProcess,
436
- signal?: AbortSignal,
437
- debugTag?: string,
438
- rawLog?: RawStreamLogHandle | null,
439
- stats?: CursorStreamStats,
440
- ): AsyncGenerator<CursorMessageLine> {
397
+ // 收集 stderr,子进程异常退出时输出到日志,方便排查静默失败
398
+ let stderr = "";
399
+ let closeInfo: CursorProcessCloseInfo | null = null;
400
+ let resolveClose: (info: CursorProcessCloseInfo) => void = () => {};
401
+ const closePromise = new Promise<CursorProcessCloseInfo>((resolve) => {
402
+ resolveClose = resolve;
403
+ });
404
+ const settleClose = (code: number | null, signal: NodeJS.Signals | null) => {
405
+ if (closeInfo) return;
406
+ closeInfo = { code, signal, stderr };
407
+ if (stderr.trim()) {
408
+ console.error(`[Cursor stderr] exit=${code}: ${stderr.trim().slice(0, 2000)}`);
409
+ }
410
+ resolveClose(closeInfo);
411
+ };
412
+ proc.stderr!.on("data", (chunk: Buffer) => { stderr += chunk.toString(); });
413
+ proc.once("error", (err) => {
414
+ stderr += `${stderr ? "\n" : ""}${(err as Error).message}`;
415
+ settleClose(null, null);
416
+ });
417
+ proc.once("close", (code, signal) => { settleClose(code, signal); });
418
+
419
+ if (stdinText !== undefined) {
420
+ proc.stdin!.write(stdinText);
421
+ proc.stdin!.end();
422
+ }
423
+ return {
424
+ proc,
425
+ getStderr: () => stderr,
426
+ waitForClose: async () => {
427
+ if (closeInfo) return { ...closeInfo, stderr };
428
+ const info = await closePromise;
429
+ return { ...info, stderr };
430
+ },
431
+ };
432
+ }
433
+
434
+ async function* readJsonLines(
435
+ proc: ChildProcess,
436
+ signal?: AbortSignal,
437
+ debugTag?: string,
438
+ rawLog?: RawStreamLogHandle | null,
439
+ stats?: CursorStreamStats,
440
+ ): AsyncGenerator<CursorMessageLine> {
441
441
  const tag = debugTag ?? "cursor";
442
442
  const rl = createInterface({ input: proc.stdout!, crlfDelay: Infinity });
443
443
  // abort 时主动 close readline,避免等待 Windows 管道自然关闭(可能延迟数分钟)
@@ -445,21 +445,21 @@ async function* readJsonLines(
445
445
  signal?.addEventListener("abort", onAbort, { once: true });
446
446
  let lineCount = 0;
447
447
  try {
448
- for await (const line of rl) {
449
- if (signal?.aborted) break;
450
- lineCount++;
451
- if (stats) {
452
- stats.rawLineCount++;
453
- stats.stdoutLength += Buffer.byteLength(line, "utf-8") + 1;
454
- }
455
- const trimmed = line.trim();
456
- if (!trimmed) continue;
457
- rawLog?.writeLine(trimmed);
458
- try {
459
- const parsed = JSON.parse(trimmed) as CursorMessageLine;
460
- if (stats) stats.parsedLineCount++;
461
- yield parsed;
462
- } catch { /* 非 JSON 行静默跳过 */ }
448
+ for await (const line of rl) {
449
+ if (signal?.aborted) break;
450
+ lineCount++;
451
+ if (stats) {
452
+ stats.rawLineCount++;
453
+ stats.stdoutLength += Buffer.byteLength(line, "utf-8") + 1;
454
+ }
455
+ const trimmed = line.trim();
456
+ if (!trimmed) continue;
457
+ rawLog?.writeLine(trimmed);
458
+ try {
459
+ const parsed = JSON.parse(trimmed) as CursorMessageLine;
460
+ if (stats) stats.parsedLineCount++;
461
+ yield parsed;
462
+ } catch { /* 非 JSON 行静默跳过 */ }
463
463
  }
464
464
  } finally {
465
465
  signal?.removeEventListener("abort", onAbort);
@@ -474,28 +474,28 @@ async function* readJsonLines(
474
474
 
475
475
  class CursorAdapter implements ToolAdapter {
476
476
  readonly displayName = "Cursor";
477
- readonly sessionDescPrefix = "Cursor Session:";
478
- private activeProcs = new Set<ChildProcess>();
479
- private metaStore: CursorSessionMetaStore;
480
- private modelOverride: string | undefined;
481
- private spawnImpl: CursorSpawn;
482
-
483
- constructor(metaStore: CursorSessionMetaStore, modelOverride?: string, spawnImpl: CursorSpawn = spawn) {
484
- this.metaStore = metaStore;
485
- this.modelOverride = modelOverride;
486
- this.spawnImpl = spawnImpl;
487
- }
488
-
489
- async createSession(cwd: string): Promise<CreateSessionResult> {
490
- const handle = spawnAgent(["ok"], cwd, undefined, this.modelOverride, undefined, this.spawnImpl);
491
- const proc = handle.proc;
492
- const stats = createCursorStreamStats();
493
- this.activeProcs.add(proc);
494
-
495
- for await (const msg of readJsonLines(proc, undefined, "createSession", null, stats)) {
496
- if (msg.type === "system" && msg.subtype === "init" && msg.session_id) {
497
- const sessionId = msg.session_id;
498
- await this.metaStore
477
+ readonly sessionDescPrefix = "Cursor Session:";
478
+ private activeProcs = new Set<ChildProcess>();
479
+ private metaStore: CursorSessionMetaStore;
480
+ private modelOverride: string | undefined;
481
+ private spawnImpl: CursorSpawn;
482
+
483
+ constructor(metaStore: CursorSessionMetaStore, modelOverride?: string, spawnImpl: CursorSpawn = spawn) {
484
+ this.metaStore = metaStore;
485
+ this.modelOverride = modelOverride;
486
+ this.spawnImpl = spawnImpl;
487
+ }
488
+
489
+ async createSession(cwd: string): Promise<CreateSessionResult> {
490
+ const handle = spawnAgent(["ok"], cwd, undefined, this.modelOverride, undefined, this.spawnImpl);
491
+ const proc = handle.proc;
492
+ const stats = createCursorStreamStats();
493
+ this.activeProcs.add(proc);
494
+
495
+ for await (const msg of readJsonLines(proc, undefined, "createSession", null, stats)) {
496
+ if (msg.type === "system" && msg.subtype === "init" && msg.session_id) {
497
+ const sessionId = msg.session_id;
498
+ await this.metaStore
499
499
  .set(sessionId, { cwd: msg.cwd ?? cwd, model: msg.model })
500
500
  .catch(() => {});
501
501
  this.activeProcs.delete(proc);
@@ -503,18 +503,18 @@ class CursorAdapter implements ToolAdapter {
503
503
  return { sessionId };
504
504
  }
505
505
  }
506
-
507
- await killProcessTree(proc.pid);
508
- this.activeProcs.delete(proc);
509
- const closeInfo = await handle.waitForClose();
510
- const visibleMessage = formatCursorAgentEmptyOutputMessage({
511
- exitCode: closeInfo.code,
512
- stdoutLength: stats.stdoutLength,
513
- stderr: closeInfo.stderr,
514
- });
515
- if (visibleMessage) throw new Error(visibleMessage);
516
- throw new Error("No session ID in Cursor init event");
517
- }
506
+
507
+ await killProcessTree(proc.pid);
508
+ this.activeProcs.delete(proc);
509
+ const closeInfo = await handle.waitForClose();
510
+ const visibleMessage = formatCursorAgentEmptyOutputMessage({
511
+ exitCode: closeInfo.code,
512
+ stdoutLength: stats.stdoutLength,
513
+ stderr: closeInfo.stderr,
514
+ });
515
+ if (visibleMessage) throw new Error(visibleMessage);
516
+ throw new Error("No session ID in Cursor init event");
517
+ }
518
518
 
519
519
  async *prompt(
520
520
  sessionId: string,
@@ -525,46 +525,46 @@ class CursorAdapter implements ToolAdapter {
525
525
  ): AsyncIterable<UnifiedStreamMessage> {
526
526
  console.log(`[Cursor debug] prompt start: sessionId=${sessionId}, cwd=${cwd}, userTextLen=${userText.length}`);
527
527
  const cmd = parseUserCommand(userText);
528
- const handle = spawnAgent(
529
- ["--resume", sessionId],
530
- cwd,
531
- buildCursorPromptText(userText),
532
- this.modelOverride,
533
- cmd.mode ?? undefined,
534
- this.spawnImpl,
535
- );
536
- const proc = handle.proc;
537
- this.activeProcs.add(proc);
538
- if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
539
-
540
- const rawLogConfig = config.rawStreamLogs.cursor;
541
- let rawLog: RawStreamLogHandle | null = null;
542
- try {
543
- rawLog = await createRawStreamLog({
544
- enabled: rawLogConfig.enabled,
545
- rootDir: RAW_STREAM_LOGS_DIR,
546
- tool: "cursor",
547
- sessionId,
548
- label: "prompt",
549
- maxBytesPerTurn: rawLogConfig.maxBytesPerTurn,
550
- retentionDays: rawLogConfig.retentionDays,
551
- });
552
- } catch (err) {
553
- console.error(`[Cursor raw stream log] create failed: ${(err as Error).message}`);
554
- }
528
+ const handle = spawnAgent(
529
+ ["--resume", sessionId],
530
+ cwd,
531
+ buildCursorPromptText(userText),
532
+ this.modelOverride,
533
+ cmd.mode ?? undefined,
534
+ this.spawnImpl,
535
+ );
536
+ const proc = handle.proc;
537
+ this.activeProcs.add(proc);
538
+ if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
539
+
540
+ const rawLogConfig = config.rawStreamLogs.cursor;
541
+ let rawLog: RawStreamLogHandle | null = null;
542
+ try {
543
+ rawLog = await createRawStreamLog({
544
+ enabled: rawLogConfig.enabled,
545
+ rootDir: RAW_STREAM_LOGS_DIR,
546
+ tool: "cursor",
547
+ sessionId,
548
+ label: "prompt",
549
+ maxBytesPerTurn: rawLogConfig.maxBytesPerTurn,
550
+ retentionDays: rawLogConfig.retentionDays,
551
+ });
552
+ } catch (err) {
553
+ console.error(`[Cursor raw stream log] create failed: ${(err as Error).message}`);
554
+ }
555
555
 
556
556
  // 见 codex-adapter.ts 同位置注释:spawn 用了 shell:true,必须杀整棵树,
557
557
  // 否则 abort 后真正在跑的孙进程 cursor-agent 还会继续输出 & 占用资源。
558
- const onAbort = () => { void killProcessTree(proc.pid); };
559
- signal?.addEventListener("abort", onAbort, { once: true });
560
- let sawResult = false;
561
- const stats = createCursorStreamStats();
562
-
563
- try {
564
- for await (const raw of readJsonLines(proc, signal, sessionId, rawLog, stats)) {
565
- if (signal?.aborted) break;
566
- if (
567
- raw.type === "system" &&
558
+ const onAbort = () => { void killProcessTree(proc.pid); };
559
+ signal?.addEventListener("abort", onAbort, { once: true });
560
+ let sawResult = false;
561
+ const stats = createCursorStreamStats();
562
+
563
+ try {
564
+ for await (const raw of readJsonLines(proc, signal, sessionId, rawLog, stats)) {
565
+ if (signal?.aborted) break;
566
+ if (
567
+ raw.type === "system" &&
568
568
  raw.subtype === "init" &&
569
569
  raw.session_id &&
570
570
  (raw.cwd || raw.model)
@@ -577,32 +577,32 @@ class CursorAdapter implements ToolAdapter {
577
577
  if (normalized) yield normalized;
578
578
 
579
579
  // result 是流末事件,收到后立即结束进程,防止 CLI 僵死导致 readline 挂起。
580
- if (raw.type === "result") {
581
- sawResult = true;
582
- void killProcessTree(proc.pid);
583
- break;
584
- }
585
- }
586
- if (!signal?.aborted && !sawResult) {
587
- const closeInfo = await handle.waitForClose();
588
- const visibleMessage = formatCursorAgentEmptyOutputMessage({
589
- exitCode: closeInfo.code,
590
- stdoutLength: stats.stdoutLength,
591
- stderr: closeInfo.stderr,
592
- });
593
- if (visibleMessage) {
594
- yield {
595
- type: "assistant",
596
- blocks: [{ type: "text_final", text: visibleMessage }],
597
- };
598
- throw createCursorAgentFailureError(closeInfo);
599
- }
600
- }
601
- } finally {
602
- signal?.removeEventListener("abort", onAbort);
603
- await killProcessTree(proc.pid);
604
- await rawLog?.close({ keep: rawLogConfig.keepCompleted || signal?.aborted === true || !sawResult });
605
- this.activeProcs.delete(proc);
580
+ if (raw.type === "result") {
581
+ sawResult = true;
582
+ void killProcessTree(proc.pid);
583
+ break;
584
+ }
585
+ }
586
+ if (!signal?.aborted && !sawResult) {
587
+ const closeInfo = await handle.waitForClose();
588
+ const visibleMessage = formatCursorAgentEmptyOutputMessage({
589
+ exitCode: closeInfo.code,
590
+ stdoutLength: stats.stdoutLength,
591
+ stderr: closeInfo.stderr,
592
+ });
593
+ if (visibleMessage) {
594
+ yield {
595
+ type: "assistant",
596
+ blocks: [{ type: "text_final", text: visibleMessage }],
597
+ };
598
+ throw createCursorAgentFailureError(closeInfo);
599
+ }
600
+ }
601
+ } finally {
602
+ signal?.removeEventListener("abort", onAbort);
603
+ await killProcessTree(proc.pid);
604
+ await rawLog?.close({ keep: rawLogConfig.keepCompleted || signal?.aborted === true || !sawResult });
605
+ this.activeProcs.delete(proc);
606
606
  if (proc.pid !== undefined) options?.onProcessExit?.({ pid: proc.pid });
607
607
  console.log(`[Cursor debug] prompt end: sessionId=${sessionId}, signalAborted=${signal?.aborted ?? false}`);
608
608
  }
@@ -627,21 +627,21 @@ class CursorAdapter implements ToolAdapter {
627
627
  // 工厂函数
628
628
  // ---------------------------------------------------------------------------
629
629
 
630
- export interface CreateCursorAdapterOptions {
631
- /** 注入自定义 meta 持久化 store(测试用);未提供时使用全局默认实例。 */
632
- metaStore?: CursorSessionMetaStore;
633
- /** per-session 模型覆盖(/model 命令);传了就用,不传走全局 cursor.model */
634
- model?: string;
635
- /** 注入自定义 spawn 实现(测试用)。 */
636
- spawn?: CursorSpawn;
637
- }
638
-
639
- export function createCursorAdapter(
640
- options: CreateCursorAdapterOptions = {},
641
- ): ToolAdapter {
642
- return new CursorAdapter(
643
- options.metaStore ?? defaultCursorSessionMetaStore,
644
- options.model,
645
- options.spawn,
646
- );
647
- }
630
+ export interface CreateCursorAdapterOptions {
631
+ /** 注入自定义 meta 持久化 store(测试用);未提供时使用全局默认实例。 */
632
+ metaStore?: CursorSessionMetaStore;
633
+ /** per-session 模型覆盖(/model 命令);传了就用,不传走全局 cursor.model */
634
+ model?: string;
635
+ /** 注入自定义 spawn 实现(测试用)。 */
636
+ spawn?: CursorSpawn;
637
+ }
638
+
639
+ export function createCursorAdapter(
640
+ options: CreateCursorAdapterOptions = {},
641
+ ): ToolAdapter {
642
+ return new CursorAdapter(
643
+ options.metaStore ?? defaultCursorSessionMetaStore,
644
+ options.model,
645
+ options.spawn,
646
+ );
647
+ }