chatccc 0.2.187 → 0.2.189
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/claude_specific.md +45 -45
- package/agent-prompts/codex_specific.md +2 -2
- package/agent-prompts/cursor_specific.md +13 -13
- package/config.sample.json +5 -0
- package/im-skills/feishu-skill/receive-send-file.md +63 -63
- package/im-skills/feishu-skill/receive-send-image.md +24 -24
- package/im-skills/feishu-skill/skill.md +3 -3
- package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
- package/im-skills/wechat-file-skill/send-file.mjs +83 -83
- package/im-skills/wechat-file-skill/skill.md +10 -10
- package/im-skills/wechat-image-skill/skill.md +10 -10
- package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
- package/im-skills/wechat-video-skill/send-video.mjs +79 -79
- package/im-skills/wechat-video-skill/skill.md +10 -10
- package/package.json +1 -1
- package/scripts/postinstall-sharp-check.mjs +58 -58
- package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
- package/src/__tests__/builtin-config.test.ts +33 -0
- package/src/__tests__/card-plain-text.test.ts +5 -5
- package/src/__tests__/cardkit.test.ts +60 -60
- package/src/__tests__/cards.test.ts +77 -77
- 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 -125
- package/src/__tests__/claude-adapter.test.ts +592 -592
- package/src/__tests__/codex-reset-actions.test.ts +146 -146
- package/src/__tests__/config-reload.test.ts +1 -0
- package/src/__tests__/config-sample.test.ts +11 -0
- package/src/__tests__/feishu-api.test.ts +60 -60
- package/src/__tests__/feishu-avatar.test.ts +180 -115
- package/src/__tests__/feishu-platform.test.ts +22 -22
- package/src/__tests__/format-message.test.ts +47 -47
- package/src/__tests__/orchestrator.test.ts +150 -2
- package/src/__tests__/privacy.test.ts +198 -198
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/session.test.ts +10 -0
- package/src/__tests__/shared-prefix.test.ts +36 -36
- package/src/__tests__/stream-state.test.ts +42 -42
- package/src/__tests__/web-ui.test.ts +209 -130
- package/src/adapters/claude-adapter.ts +566 -566
- package/src/adapters/claude-session-meta-store.ts +120 -120
- package/src/adapters/codex-adapter.ts +10 -6
- package/src/adapters/cursor-adapter.ts +46 -46
- package/src/adapters/raw-stream-log.ts +124 -124
- package/src/adapters/resource-monitor.ts +140 -140
- package/src/agent-delegate-task-rpc.ts +153 -153
- package/src/agent-delegate-task.ts +81 -81
- package/src/agent-stop-stuck.ts +129 -129
- package/src/builtin/cli.ts +189 -197
- package/src/builtin/index.ts +168 -167
- package/src/cards.ts +130 -89
- package/src/chatgpt-subscription-rpc.ts +27 -27
- package/src/chatgpt-subscription.ts +299 -299
- package/src/chrome-devtools-guard.ts +318 -242
- package/src/codex-reset-actions.ts +184 -184
- package/src/config.ts +38 -0
- package/src/feishu-api.ts +219 -190
- package/src/feishu-platform.ts +20 -20
- package/src/format-message.ts +293 -293
- package/src/index.ts +2 -2
- package/src/litellm-proxy.ts +374 -374
- package/src/orchestrator.ts +201 -9
- package/src/platform-adapter.ts +9 -1
- package/src/privacy.ts +118 -118
- package/src/session-chat-binding.ts +6 -6
- package/src/session-name.ts +8 -8
- package/src/session.ts +44 -16
- package/src/shared-prefix.ts +29 -29
- package/src/sim-platform.ts +20 -20
- package/src/turn-cards.ts +117 -117
- package/src/web-ui.ts +142 -24
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
// =============================================================================
|
|
2
|
-
// claude-session-meta-store.ts — Claude 会话 sessionId → meta 持久化映射
|
|
3
|
-
// =============================================================================
|
|
4
|
-
// 背景:Claude Agent SDK 的 getSessionInfo 可能因 cwd / 本地记录缺失而查不到。
|
|
5
|
-
// ChatCCC 额外维护 sessionId → { cwd, model } 映射作为展示与恢复兜底。
|
|
6
|
-
//
|
|
7
|
-
// 存储:
|
|
8
|
-
// 文件 state/claude-session-meta.json,结构:
|
|
9
|
-
// { "<sessionId>": { "cwd": "...", "model": "..." } }
|
|
10
|
-
//
|
|
11
|
-
// API 设计:
|
|
12
|
-
// set(sid, partial) → 部分合并写入;只更新非空字段,不会清空其他字段
|
|
13
|
-
// =============================================================================
|
|
14
|
-
|
|
15
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16
|
-
import { dirname, join } from "node:path";
|
|
17
|
-
|
|
18
|
-
import { USER_DATA_DIR } from "../config.ts";
|
|
19
|
-
|
|
20
|
-
export const CLAUDE_SESSION_META_FILE = join(
|
|
21
|
-
USER_DATA_DIR,
|
|
22
|
-
"state",
|
|
23
|
-
"claude-session-meta.json",
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
export interface ClaudeSessionMeta {
|
|
27
|
-
cwd: string;
|
|
28
|
-
model?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ClaudeSessionMetaStore {
|
|
32
|
-
get(sessionId: string): Promise<ClaudeSessionMeta | undefined>;
|
|
33
|
-
set(sessionId: string, partial: Partial<ClaudeSessionMeta>): Promise<void>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface RawEntry {
|
|
37
|
-
cwd?: string;
|
|
38
|
-
model?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function isNonEmptyString(v: unknown): v is string {
|
|
42
|
-
return typeof v === "string" && v.length > 0;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function parseEntry(raw: unknown): RawEntry | null {
|
|
46
|
-
if (typeof raw === "string" && raw.length > 0) {
|
|
47
|
-
return { cwd: raw };
|
|
48
|
-
}
|
|
49
|
-
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
50
|
-
const obj = raw as Record<string, unknown>;
|
|
51
|
-
const out: RawEntry = {};
|
|
52
|
-
if (isNonEmptyString(obj.cwd)) out.cwd = obj.cwd;
|
|
53
|
-
if (isNonEmptyString(obj.model)) out.model = obj.model;
|
|
54
|
-
return out;
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function createClaudeSessionMetaStore(
|
|
60
|
-
filePath: string = CLAUDE_SESSION_META_FILE,
|
|
61
|
-
): ClaudeSessionMetaStore {
|
|
62
|
-
let cache: Record<string, RawEntry> | null = null;
|
|
63
|
-
|
|
64
|
-
async function load(): Promise<Record<string, RawEntry>> {
|
|
65
|
-
if (cache) return cache;
|
|
66
|
-
try {
|
|
67
|
-
const raw = await readFile(filePath, "utf-8");
|
|
68
|
-
const parsed = JSON.parse(raw);
|
|
69
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
70
|
-
const out: Record<string, RawEntry> = {};
|
|
71
|
-
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
|
|
72
|
-
const entry = parseEntry(v);
|
|
73
|
-
if (entry) out[k] = entry;
|
|
74
|
-
}
|
|
75
|
-
cache = out;
|
|
76
|
-
return out;
|
|
77
|
-
}
|
|
78
|
-
} catch {
|
|
79
|
-
// 文件不存在 / JSON 损坏 → 视为空映射
|
|
80
|
-
}
|
|
81
|
-
cache = {};
|
|
82
|
-
return cache;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
async get(sessionId: string): Promise<ClaudeSessionMeta | undefined> {
|
|
87
|
-
const map = await load();
|
|
88
|
-
const entry = map[sessionId];
|
|
89
|
-
if (!entry || !isNonEmptyString(entry.cwd)) return undefined;
|
|
90
|
-
return entry.model
|
|
91
|
-
? { cwd: entry.cwd, model: entry.model }
|
|
92
|
-
: { cwd: entry.cwd };
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
async set(
|
|
96
|
-
sessionId: string,
|
|
97
|
-
partial: Partial<ClaudeSessionMeta>,
|
|
98
|
-
): Promise<void> {
|
|
99
|
-
const map = await load();
|
|
100
|
-
const existing = map[sessionId] ?? {};
|
|
101
|
-
const merged: RawEntry = { ...existing };
|
|
102
|
-
if (isNonEmptyString(partial.cwd)) merged.cwd = partial.cwd;
|
|
103
|
-
if (isNonEmptyString(partial.model)) merged.model = partial.model;
|
|
104
|
-
|
|
105
|
-
if (existing.cwd === merged.cwd && existing.model === merged.model) return;
|
|
106
|
-
|
|
107
|
-
map[sessionId] = merged;
|
|
108
|
-
try {
|
|
109
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
110
|
-
await writeFile(filePath, JSON.stringify(map, null, 2), "utf-8");
|
|
111
|
-
} catch (err) {
|
|
112
|
-
console.error(
|
|
113
|
-
`[claude-session-meta] failed to persist ${filePath}: ${(err as Error).message}`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export const defaultClaudeSessionMetaStore = createClaudeSessionMetaStore();
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// claude-session-meta-store.ts — Claude 会话 sessionId → meta 持久化映射
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// 背景:Claude Agent SDK 的 getSessionInfo 可能因 cwd / 本地记录缺失而查不到。
|
|
5
|
+
// ChatCCC 额外维护 sessionId → { cwd, model } 映射作为展示与恢复兜底。
|
|
6
|
+
//
|
|
7
|
+
// 存储:
|
|
8
|
+
// 文件 state/claude-session-meta.json,结构:
|
|
9
|
+
// { "<sessionId>": { "cwd": "...", "model": "..." } }
|
|
10
|
+
//
|
|
11
|
+
// API 设计:
|
|
12
|
+
// set(sid, partial) → 部分合并写入;只更新非空字段,不会清空其他字段
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
|
|
18
|
+
import { USER_DATA_DIR } from "../config.ts";
|
|
19
|
+
|
|
20
|
+
export const CLAUDE_SESSION_META_FILE = join(
|
|
21
|
+
USER_DATA_DIR,
|
|
22
|
+
"state",
|
|
23
|
+
"claude-session-meta.json",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export interface ClaudeSessionMeta {
|
|
27
|
+
cwd: string;
|
|
28
|
+
model?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ClaudeSessionMetaStore {
|
|
32
|
+
get(sessionId: string): Promise<ClaudeSessionMeta | undefined>;
|
|
33
|
+
set(sessionId: string, partial: Partial<ClaudeSessionMeta>): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface RawEntry {
|
|
37
|
+
cwd?: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isNonEmptyString(v: unknown): v is string {
|
|
42
|
+
return typeof v === "string" && v.length > 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseEntry(raw: unknown): RawEntry | null {
|
|
46
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
47
|
+
return { cwd: raw };
|
|
48
|
+
}
|
|
49
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
50
|
+
const obj = raw as Record<string, unknown>;
|
|
51
|
+
const out: RawEntry = {};
|
|
52
|
+
if (isNonEmptyString(obj.cwd)) out.cwd = obj.cwd;
|
|
53
|
+
if (isNonEmptyString(obj.model)) out.model = obj.model;
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createClaudeSessionMetaStore(
|
|
60
|
+
filePath: string = CLAUDE_SESSION_META_FILE,
|
|
61
|
+
): ClaudeSessionMetaStore {
|
|
62
|
+
let cache: Record<string, RawEntry> | null = null;
|
|
63
|
+
|
|
64
|
+
async function load(): Promise<Record<string, RawEntry>> {
|
|
65
|
+
if (cache) return cache;
|
|
66
|
+
try {
|
|
67
|
+
const raw = await readFile(filePath, "utf-8");
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
70
|
+
const out: Record<string, RawEntry> = {};
|
|
71
|
+
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
|
|
72
|
+
const entry = parseEntry(v);
|
|
73
|
+
if (entry) out[k] = entry;
|
|
74
|
+
}
|
|
75
|
+
cache = out;
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// 文件不存在 / JSON 损坏 → 视为空映射
|
|
80
|
+
}
|
|
81
|
+
cache = {};
|
|
82
|
+
return cache;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
async get(sessionId: string): Promise<ClaudeSessionMeta | undefined> {
|
|
87
|
+
const map = await load();
|
|
88
|
+
const entry = map[sessionId];
|
|
89
|
+
if (!entry || !isNonEmptyString(entry.cwd)) return undefined;
|
|
90
|
+
return entry.model
|
|
91
|
+
? { cwd: entry.cwd, model: entry.model }
|
|
92
|
+
: { cwd: entry.cwd };
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async set(
|
|
96
|
+
sessionId: string,
|
|
97
|
+
partial: Partial<ClaudeSessionMeta>,
|
|
98
|
+
): Promise<void> {
|
|
99
|
+
const map = await load();
|
|
100
|
+
const existing = map[sessionId] ?? {};
|
|
101
|
+
const merged: RawEntry = { ...existing };
|
|
102
|
+
if (isNonEmptyString(partial.cwd)) merged.cwd = partial.cwd;
|
|
103
|
+
if (isNonEmptyString(partial.model)) merged.model = partial.model;
|
|
104
|
+
|
|
105
|
+
if (existing.cwd === merged.cwd && existing.model === merged.model) return;
|
|
106
|
+
|
|
107
|
+
map[sessionId] = merged;
|
|
108
|
+
try {
|
|
109
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
110
|
+
await writeFile(filePath, JSON.stringify(map, null, 2), "utf-8");
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error(
|
|
113
|
+
`[claude-session-meta] failed to persist ${filePath}: ${(err as Error).message}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const defaultClaudeSessionMetaStore = createClaudeSessionMetaStore();
|
|
@@ -72,7 +72,6 @@ function buildCodexPromptText(userText: string): string {
|
|
|
72
72
|
function detectCodexCommand(): string {
|
|
73
73
|
return config.codex.path || "codex";
|
|
74
74
|
}
|
|
75
|
-
const CODEX_COMMAND = detectCodexCommand();
|
|
76
75
|
|
|
77
76
|
/** exec 模式共用参数:JSONL 输出、绕过沙盒和确认、跳过 git 仓库检查 */
|
|
78
77
|
const CODEX_BASE_ARGS = [
|
|
@@ -189,6 +188,7 @@ function spawnCodex(
|
|
|
189
188
|
cwd?: string,
|
|
190
189
|
stdinText?: string,
|
|
191
190
|
modelOverride?: string,
|
|
191
|
+
effortOverride?: string,
|
|
192
192
|
): ChildProcess {
|
|
193
193
|
const allArgs = [...args];
|
|
194
194
|
const model = modelOverride ?? resolveCodexModel();
|
|
@@ -197,12 +197,12 @@ function spawnCodex(
|
|
|
197
197
|
const execIdx = allArgs.indexOf("exec");
|
|
198
198
|
allArgs.splice(execIdx + 1, 0, "-m", model);
|
|
199
199
|
}
|
|
200
|
-
const effort = resolveCodexEffort();
|
|
200
|
+
const effort = effortOverride ?? resolveCodexEffort();
|
|
201
201
|
if (effort) {
|
|
202
202
|
allArgs.push("-c", `model_reasoning_effort="${effort}"`);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
const proc = spawn(
|
|
205
|
+
const proc = spawn(detectCodexCommand(), allArgs, {
|
|
206
206
|
cwd,
|
|
207
207
|
stdio: [stdinText !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
|
|
208
208
|
windowsHide: true,
|
|
@@ -262,10 +262,12 @@ class CodexAdapter implements ToolAdapter {
|
|
|
262
262
|
readonly sessionDescPrefix = "Codex Session:";
|
|
263
263
|
private metaStore: CodexSessionMetaStore;
|
|
264
264
|
private modelOverride: string | undefined;
|
|
265
|
+
private effortOverride: string | undefined;
|
|
265
266
|
|
|
266
|
-
constructor(metaStore: CodexSessionMetaStore, modelOverride?: string) {
|
|
267
|
+
constructor(metaStore: CodexSessionMetaStore, modelOverride?: string, effortOverride?: string) {
|
|
267
268
|
this.metaStore = metaStore;
|
|
268
269
|
this.modelOverride = modelOverride;
|
|
270
|
+
this.effortOverride = effortOverride;
|
|
269
271
|
}
|
|
270
272
|
|
|
271
273
|
// createSession: 生成 sessionId,记录 cwd,不创建 Codex 线程(延迟到首次 prompt)
|
|
@@ -296,7 +298,7 @@ class CodexAdapter implements ToolAdapter {
|
|
|
296
298
|
? [...baseArgs, "-C", cwd, "-"]
|
|
297
299
|
: [...baseArgs, "resume", threadId, "-"];
|
|
298
300
|
|
|
299
|
-
const proc = spawnCodex(args, cwd, buildCodexPromptText(userText), this.modelOverride);
|
|
301
|
+
const proc = spawnCodex(args, cwd, buildCodexPromptText(userText), this.modelOverride, this.effortOverride);
|
|
300
302
|
if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
|
|
301
303
|
|
|
302
304
|
// 关键:spawn 用了 shell:true,proc.pid 指向的是壳进程(cmd.exe / sh)。
|
|
@@ -351,6 +353,7 @@ export interface CreateCodexAdapterOptions {
|
|
|
351
353
|
metaStore?: CodexSessionMetaStore;
|
|
352
354
|
/** per-session 模型覆盖(/model 命令);传了就用,不传走全局 codex.model */
|
|
353
355
|
model?: string;
|
|
356
|
+
effort?: string;
|
|
354
357
|
}
|
|
355
358
|
|
|
356
359
|
export function createCodexAdapter(
|
|
@@ -359,5 +362,6 @@ export function createCodexAdapter(
|
|
|
359
362
|
return new CodexAdapter(
|
|
360
363
|
options.metaStore ?? defaultCodexSessionMetaStore,
|
|
361
364
|
options.model,
|
|
365
|
+
options.effort,
|
|
362
366
|
);
|
|
363
|
-
}
|
|
367
|
+
}
|
|
@@ -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
|
// 特殊注入提示
|
|
@@ -348,12 +348,12 @@ function spawnAgent(
|
|
|
348
348
|
return proc;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
async function* readJsonLines(
|
|
352
|
-
proc: ChildProcess,
|
|
353
|
-
signal?: AbortSignal,
|
|
354
|
-
debugTag?: string,
|
|
355
|
-
rawLog?: RawStreamLogHandle | null,
|
|
356
|
-
): AsyncGenerator<CursorMessageLine> {
|
|
351
|
+
async function* readJsonLines(
|
|
352
|
+
proc: ChildProcess,
|
|
353
|
+
signal?: AbortSignal,
|
|
354
|
+
debugTag?: string,
|
|
355
|
+
rawLog?: RawStreamLogHandle | null,
|
|
356
|
+
): AsyncGenerator<CursorMessageLine> {
|
|
357
357
|
const tag = debugTag ?? "cursor";
|
|
358
358
|
const rl = createInterface({ input: proc.stdout!, crlfDelay: Infinity });
|
|
359
359
|
// abort 时主动 close readline,避免等待 Windows 管道自然关闭(可能延迟数分钟)
|
|
@@ -364,10 +364,10 @@ async function* readJsonLines(
|
|
|
364
364
|
for await (const line of rl) {
|
|
365
365
|
if (signal?.aborted) break;
|
|
366
366
|
lineCount++;
|
|
367
|
-
const trimmed = line.trim();
|
|
368
|
-
if (!trimmed) continue;
|
|
369
|
-
rawLog?.writeLine(trimmed);
|
|
370
|
-
try {
|
|
367
|
+
const trimmed = line.trim();
|
|
368
|
+
if (!trimmed) continue;
|
|
369
|
+
rawLog?.writeLine(trimmed);
|
|
370
|
+
try {
|
|
371
371
|
yield JSON.parse(trimmed) as CursorMessageLine;
|
|
372
372
|
} catch { /* 非 JSON 行静默跳过 */ }
|
|
373
373
|
}
|
|
@@ -432,32 +432,32 @@ class CursorAdapter implements ToolAdapter {
|
|
|
432
432
|
cmd.mode ?? undefined,
|
|
433
433
|
);
|
|
434
434
|
this.activeProcs.add(proc);
|
|
435
|
-
if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
|
|
436
|
-
|
|
437
|
-
const rawLogConfig = config.rawStreamLogs.cursor;
|
|
438
|
-
let rawLog: RawStreamLogHandle | null = null;
|
|
439
|
-
try {
|
|
440
|
-
rawLog = await createRawStreamLog({
|
|
441
|
-
enabled: rawLogConfig.enabled,
|
|
442
|
-
rootDir: RAW_STREAM_LOGS_DIR,
|
|
443
|
-
tool: "cursor",
|
|
444
|
-
sessionId,
|
|
445
|
-
label: "prompt",
|
|
446
|
-
maxBytesPerTurn: rawLogConfig.maxBytesPerTurn,
|
|
447
|
-
retentionDays: rawLogConfig.retentionDays,
|
|
448
|
-
});
|
|
449
|
-
} catch (err) {
|
|
450
|
-
console.error(`[Cursor raw stream log] create failed: ${(err as Error).message}`);
|
|
451
|
-
}
|
|
435
|
+
if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
|
|
436
|
+
|
|
437
|
+
const rawLogConfig = config.rawStreamLogs.cursor;
|
|
438
|
+
let rawLog: RawStreamLogHandle | null = null;
|
|
439
|
+
try {
|
|
440
|
+
rawLog = await createRawStreamLog({
|
|
441
|
+
enabled: rawLogConfig.enabled,
|
|
442
|
+
rootDir: RAW_STREAM_LOGS_DIR,
|
|
443
|
+
tool: "cursor",
|
|
444
|
+
sessionId,
|
|
445
|
+
label: "prompt",
|
|
446
|
+
maxBytesPerTurn: rawLogConfig.maxBytesPerTurn,
|
|
447
|
+
retentionDays: rawLogConfig.retentionDays,
|
|
448
|
+
});
|
|
449
|
+
} catch (err) {
|
|
450
|
+
console.error(`[Cursor raw stream log] create failed: ${(err as Error).message}`);
|
|
451
|
+
}
|
|
452
452
|
|
|
453
453
|
// 见 codex-adapter.ts 同位置注释:spawn 用了 shell:true,必须杀整棵树,
|
|
454
454
|
// 否则 abort 后真正在跑的孙进程 cursor-agent 还会继续输出 & 占用资源。
|
|
455
|
-
const onAbort = () => { void killProcessTree(proc.pid); };
|
|
456
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
457
|
-
let sawResult = false;
|
|
458
|
-
|
|
459
|
-
try {
|
|
460
|
-
for await (const raw of readJsonLines(proc, signal, sessionId, rawLog)) {
|
|
455
|
+
const onAbort = () => { void killProcessTree(proc.pid); };
|
|
456
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
457
|
+
let sawResult = false;
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
for await (const raw of readJsonLines(proc, signal, sessionId, rawLog)) {
|
|
461
461
|
if (signal?.aborted) break;
|
|
462
462
|
if (
|
|
463
463
|
raw.type === "system" &&
|
|
@@ -473,17 +473,17 @@ class CursorAdapter implements ToolAdapter {
|
|
|
473
473
|
if (normalized) yield normalized;
|
|
474
474
|
|
|
475
475
|
// result 是流末事件,收到后立即结束进程,防止 CLI 僵死导致 readline 挂起。
|
|
476
|
-
if (raw.type === "result") {
|
|
477
|
-
sawResult = true;
|
|
478
|
-
void killProcessTree(proc.pid);
|
|
476
|
+
if (raw.type === "result") {
|
|
477
|
+
sawResult = true;
|
|
478
|
+
void killProcessTree(proc.pid);
|
|
479
479
|
break;
|
|
480
480
|
}
|
|
481
481
|
}
|
|
482
482
|
} finally {
|
|
483
483
|
signal?.removeEventListener("abort", onAbort);
|
|
484
|
-
await killProcessTree(proc.pid);
|
|
485
|
-
await rawLog?.close({ keep: rawLogConfig.keepCompleted || signal?.aborted === true || !sawResult });
|
|
486
|
-
this.activeProcs.delete(proc);
|
|
484
|
+
await killProcessTree(proc.pid);
|
|
485
|
+
await rawLog?.close({ keep: rawLogConfig.keepCompleted || signal?.aborted === true || !sawResult });
|
|
486
|
+
this.activeProcs.delete(proc);
|
|
487
487
|
if (proc.pid !== undefined) options?.onProcessExit?.({ pid: proc.pid });
|
|
488
488
|
console.log(`[Cursor debug] prompt end: sessionId=${sessionId}, signalAborted=${signal?.aborted ?? false}`);
|
|
489
489
|
}
|
|
@@ -519,4 +519,4 @@ export function createCursorAdapter(
|
|
|
519
519
|
options: CreateCursorAdapterOptions = {},
|
|
520
520
|
): ToolAdapter {
|
|
521
521
|
return new CursorAdapter(options.metaStore ?? defaultCursorSessionMetaStore, options.model);
|
|
522
|
-
}
|
|
522
|
+
}
|