chatccc 0.2.190 → 0.2.192
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 +14 -14
- 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-chat-session.test.ts +76 -0
- package/src/__tests__/builtin-config.test.ts +33 -33
- package/src/__tests__/builtin-context.test.ts +126 -0
- package/src/__tests__/builtin-sigint.test.ts +56 -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 -165
- 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 +4 -4
- package/src/__tests__/config-sample.test.ts +17 -17
- package/src/__tests__/feishu-api.test.ts +60 -60
- package/src/__tests__/feishu-platform.test.ts +22 -22
- package/src/__tests__/format-message.test.ts +47 -47
- package/src/__tests__/orchestrator.test.ts +120 -120
- package/src/__tests__/privacy.test.ts +198 -198
- package/src/__tests__/raw-stream-log.test.ts +106 -106
- package/src/__tests__/session.test.ts +17 -17
- 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 -209
- package/src/adapters/claude-adapter.ts +566 -566
- package/src/adapters/claude-session-meta-store.ts +120 -120
- package/src/adapters/codex-adapter.ts +30 -30
- 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 +211 -189
- package/src/builtin/context.ts +225 -0
- package/src/builtin/index.ts +232 -170
- package/src/builtin/sigint.ts +50 -0
- package/src/cards.ts +137 -137
- 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/codex-reset-actions.ts +184 -184
- package/src/config.ts +86 -86
- package/src/feishu-platform.ts +20 -20
- package/src/format-message.ts +293 -293
- package/src/litellm-proxy.ts +374 -374
- package/src/orchestrator.ts +143 -143
- 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 +98 -98
- 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 +205 -205
|
@@ -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();
|
|
@@ -69,9 +69,9 @@ function buildCodexPromptText(userText: string): string {
|
|
|
69
69
|
// ---------------------------------------------------------------------------
|
|
70
70
|
|
|
71
71
|
/** 可通过 config.json codex.path 自定义 Codex 可执行文件路径 */
|
|
72
|
-
function detectCodexCommand(): string {
|
|
73
|
-
return config.codex.path || "codex";
|
|
74
|
-
}
|
|
72
|
+
function detectCodexCommand(): string {
|
|
73
|
+
return config.codex.path || "codex";
|
|
74
|
+
}
|
|
75
75
|
|
|
76
76
|
/** exec 模式共用参数:JSONL 输出、绕过沙盒和确认、跳过 git 仓库检查 */
|
|
77
77
|
const CODEX_BASE_ARGS = [
|
|
@@ -183,13 +183,13 @@ export function normalizeCodexMessage(
|
|
|
183
183
|
// 子进程辅助函数
|
|
184
184
|
// ---------------------------------------------------------------------------
|
|
185
185
|
|
|
186
|
-
function spawnCodex(
|
|
187
|
-
args: string[],
|
|
188
|
-
cwd?: string,
|
|
189
|
-
stdinText?: string,
|
|
190
|
-
modelOverride?: string,
|
|
191
|
-
effortOverride?: string,
|
|
192
|
-
): ChildProcess {
|
|
186
|
+
function spawnCodex(
|
|
187
|
+
args: string[],
|
|
188
|
+
cwd?: string,
|
|
189
|
+
stdinText?: string,
|
|
190
|
+
modelOverride?: string,
|
|
191
|
+
effortOverride?: string,
|
|
192
|
+
): ChildProcess {
|
|
193
193
|
const allArgs = [...args];
|
|
194
194
|
const model = modelOverride ?? resolveCodexModel();
|
|
195
195
|
if (model) {
|
|
@@ -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 = effortOverride ?? 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(detectCodexCommand(), allArgs, {
|
|
205
|
+
const proc = spawn(detectCodexCommand(), allArgs, {
|
|
206
206
|
cwd,
|
|
207
207
|
stdio: [stdinText !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
|
|
208
208
|
windowsHide: true,
|
|
@@ -258,17 +258,17 @@ async function* readJsonLines(
|
|
|
258
258
|
// ---------------------------------------------------------------------------
|
|
259
259
|
|
|
260
260
|
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
|
-
}
|
|
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
|
+
}
|
|
272
272
|
|
|
273
273
|
// createSession: 生成 sessionId,记录 cwd,不创建 Codex 线程(延迟到首次 prompt)
|
|
274
274
|
async createSession(cwd: string): Promise<CreateSessionResult> {
|
|
@@ -298,7 +298,7 @@ class CodexAdapter implements ToolAdapter {
|
|
|
298
298
|
? [...baseArgs, "-C", cwd, "-"]
|
|
299
299
|
: [...baseArgs, "resume", threadId, "-"];
|
|
300
300
|
|
|
301
|
-
const proc = spawnCodex(args, cwd, buildCodexPromptText(userText), this.modelOverride, this.effortOverride);
|
|
301
|
+
const proc = spawnCodex(args, cwd, buildCodexPromptText(userText), this.modelOverride, this.effortOverride);
|
|
302
302
|
if (proc.pid !== undefined) options?.onProcessStart?.({ pid: proc.pid });
|
|
303
303
|
|
|
304
304
|
// 关键:spawn 用了 shell:true,proc.pid 指向的是壳进程(cmd.exe / sh)。
|
|
@@ -352,8 +352,8 @@ class CodexAdapter implements ToolAdapter {
|
|
|
352
352
|
export interface CreateCodexAdapterOptions {
|
|
353
353
|
metaStore?: CodexSessionMetaStore;
|
|
354
354
|
/** per-session 模型覆盖(/model 命令);传了就用,不传走全局 codex.model */
|
|
355
|
-
model?: string;
|
|
356
|
-
effort?: string;
|
|
355
|
+
model?: string;
|
|
356
|
+
effort?: string;
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
export function createCodexAdapter(
|
|
@@ -361,7 +361,7 @@ export function createCodexAdapter(
|
|
|
361
361
|
): ToolAdapter {
|
|
362
362
|
return new CodexAdapter(
|
|
363
363
|
options.metaStore ?? defaultCodexSessionMetaStore,
|
|
364
|
-
options.model,
|
|
365
|
-
options.effort,
|
|
366
|
-
);
|
|
367
|
-
}
|
|
364
|
+
options.model,
|
|
365
|
+
options.effort,
|
|
366
|
+
);
|
|
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
|
+
}
|