chatccc 0.2.190 → 0.2.191
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-config.test.ts +33 -33
- 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 +204 -189
- package/src/builtin/index.ts +170 -170
- 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
package/src/session.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
CLAUDE_API_KEY,
|
|
6
|
-
CLAUDE_BASE_URL,
|
|
7
|
-
CLAUDE_MAX_TURN,
|
|
5
|
+
CLAUDE_API_KEY,
|
|
6
|
+
CLAUDE_BASE_URL,
|
|
7
|
+
CLAUDE_MAX_TURN,
|
|
8
8
|
CLAUDE_MODEL,
|
|
9
9
|
CLAUDE_SUBAGENT_MODEL,
|
|
10
10
|
CHATCCC_PORT,
|
|
@@ -14,13 +14,13 @@ import {
|
|
|
14
14
|
addRecentDir,
|
|
15
15
|
anthropicConfigDisplay,
|
|
16
16
|
config,
|
|
17
|
-
fileLog,
|
|
18
|
-
getDefaultCwd,
|
|
19
|
-
getDefaultEffortForTool,
|
|
20
|
-
isAnthropicConfigEmpty,
|
|
21
|
-
toolDisplayName,
|
|
22
|
-
ts,
|
|
23
|
-
} from "./config.ts";
|
|
17
|
+
fileLog,
|
|
18
|
+
getDefaultCwd,
|
|
19
|
+
getDefaultEffortForTool,
|
|
20
|
+
isAnthropicConfigEmpty,
|
|
21
|
+
toolDisplayName,
|
|
22
|
+
ts,
|
|
23
|
+
} from "./config.ts";
|
|
24
24
|
import { buildProgressCard, getToolEmoji, isCodeBlockOpen, truncateContent } from "./cards.ts";
|
|
25
25
|
import { simplifyToolUse, simplifyToolResult } from "./simplify.ts";
|
|
26
26
|
import { logTrace } from "./trace.ts";
|
|
@@ -333,14 +333,14 @@ export function resetState(): void {
|
|
|
333
333
|
for (const prompt of activePrompts.values()) {
|
|
334
334
|
if (prompt.processMonitor) clearInterval(prompt.processMonitor);
|
|
335
335
|
}
|
|
336
|
-
activePrompts.clear();
|
|
337
|
-
displayCards.clear();
|
|
338
|
-
sessionModelOverrides.clear();
|
|
339
|
-
sessionEffortOverrides.clear();
|
|
340
|
-
adapterCache.clear();
|
|
341
|
-
stopUnifiedDisplayLoop();
|
|
342
|
-
console.log(`[${ts()}] [RESET] State cleared (dedup + active sessions + bindings)`);
|
|
343
|
-
}
|
|
336
|
+
activePrompts.clear();
|
|
337
|
+
displayCards.clear();
|
|
338
|
+
sessionModelOverrides.clear();
|
|
339
|
+
sessionEffortOverrides.clear();
|
|
340
|
+
adapterCache.clear();
|
|
341
|
+
stopUnifiedDisplayLoop();
|
|
342
|
+
console.log(`[${ts()}] [RESET] State cleared (dedup + active sessions + bindings)`);
|
|
343
|
+
}
|
|
344
344
|
|
|
345
345
|
// 注:`rebuildBindingsFromRegistry` 定义在下方与 loadSessionRegistry 同区域,
|
|
346
346
|
// 是 onReady/onReconnected 取代 resetState 的正确入口。
|
|
@@ -352,8 +352,8 @@ export function resetState(): void {
|
|
|
352
352
|
const adapterCache = new Map<string, ToolAdapter>();
|
|
353
353
|
|
|
354
354
|
// Per-session 模型覆盖(/model 命令设置,不持久化)
|
|
355
|
-
const sessionModelOverrides = new Map<string, string>();
|
|
356
|
-
const sessionEffortOverrides = new Map<string, string>();
|
|
355
|
+
const sessionModelOverrides = new Map<string, string>();
|
|
356
|
+
const sessionEffortOverrides = new Map<string, string>();
|
|
357
357
|
|
|
358
358
|
/** 返回 session 的生效模型:优先 per-session 覆盖,其次全局配置(Claude) */
|
|
359
359
|
function getModelForSession(sessionId?: string): string {
|
|
@@ -365,26 +365,26 @@ function getModelForSession(sessionId?: string): string {
|
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
/** 返回指定 tool 的生效模型:优先 per-session 覆盖,其次 tool 默认配置 */
|
|
368
|
-
export function getEffectiveModelForTool(tool: string, sessionId?: string): string {
|
|
369
|
-
if (sessionId) {
|
|
370
|
-
const override = sessionModelOverrides.get(sessionId);
|
|
371
|
-
if (override) return override;
|
|
372
|
-
}
|
|
368
|
+
export function getEffectiveModelForTool(tool: string, sessionId?: string): string {
|
|
369
|
+
if (sessionId) {
|
|
370
|
+
const override = sessionModelOverrides.get(sessionId);
|
|
371
|
+
if (override) return override;
|
|
372
|
+
}
|
|
373
373
|
if (tool === "cursor") return config.cursor.model;
|
|
374
374
|
if (tool === "codex") return config.codex.model;
|
|
375
|
-
return CLAUDE_MODEL;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
export function getEffectiveEffortForTool(tool: string, sessionId?: string): string {
|
|
379
|
-
if (sessionId) {
|
|
380
|
-
const override = sessionEffortOverrides.get(sessionId);
|
|
381
|
-
if (override) return override;
|
|
382
|
-
}
|
|
383
|
-
if (tool === "claude" || tool === "codex") {
|
|
384
|
-
return getDefaultEffortForTool(tool);
|
|
385
|
-
}
|
|
386
|
-
return "";
|
|
387
|
-
}
|
|
375
|
+
return CLAUDE_MODEL;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function getEffectiveEffortForTool(tool: string, sessionId?: string): string {
|
|
379
|
+
if (sessionId) {
|
|
380
|
+
const override = sessionEffortOverrides.get(sessionId);
|
|
381
|
+
if (override) return override;
|
|
382
|
+
}
|
|
383
|
+
if (tool === "claude" || tool === "codex") {
|
|
384
|
+
return getDefaultEffortForTool(tool);
|
|
385
|
+
}
|
|
386
|
+
return "";
|
|
387
|
+
}
|
|
388
388
|
|
|
389
389
|
/** 为指定 session 设置模型覆盖(/model <name>) */
|
|
390
390
|
export function setSessionModelOverride(sessionId: string, model: string): void {
|
|
@@ -393,41 +393,41 @@ export function setSessionModelOverride(sessionId: string, model: string): void
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
/** 清除指定 session 的模型覆盖(/model clear) */
|
|
396
|
-
export function clearSessionModelOverride(sessionId: string): void {
|
|
397
|
-
sessionModelOverrides.delete(sessionId);
|
|
398
|
-
adapterCache.clear();
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
export function setSessionEffortOverride(sessionId: string, effort: string): void {
|
|
402
|
-
sessionEffortOverrides.set(sessionId, effort);
|
|
403
|
-
adapterCache.clear();
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
export function clearSessionEffortOverride(sessionId: string): void {
|
|
407
|
-
sessionEffortOverrides.delete(sessionId);
|
|
408
|
-
adapterCache.clear();
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter {
|
|
412
|
-
const effectiveModel = getEffectiveModelForTool(tool, sessionId);
|
|
413
|
-
const effectiveEffort = getEffectiveEffortForTool(tool, sessionId);
|
|
414
|
-
const cacheKey = `${tool}:${effectiveModel || ""}:${effectiveEffort || ""}`;
|
|
415
|
-
const cached = adapterCache.get(cacheKey);
|
|
416
|
-
if (cached) return cached;
|
|
417
|
-
|
|
418
|
-
let adapter: ToolAdapter;
|
|
419
|
-
if (tool === "cursor") {
|
|
420
|
-
adapter = createCursorAdapter({ model: effectiveModel || undefined });
|
|
421
|
-
} else if (tool === "codex") {
|
|
422
|
-
adapter = createCodexAdapter({ model: effectiveModel || undefined, effort: effectiveEffort || undefined });
|
|
423
|
-
} else {
|
|
424
|
-
adapter = createClaudeAdapter({
|
|
425
|
-
model: effectiveModel,
|
|
426
|
-
subagentModel: CLAUDE_SUBAGENT_MODEL,
|
|
427
|
-
effort: effectiveEffort,
|
|
428
|
-
apiKey: CLAUDE_API_KEY,
|
|
429
|
-
baseUrl: CLAUDE_BASE_URL,
|
|
430
|
-
isEmpty: isAnthropicConfigEmpty,
|
|
396
|
+
export function clearSessionModelOverride(sessionId: string): void {
|
|
397
|
+
sessionModelOverrides.delete(sessionId);
|
|
398
|
+
adapterCache.clear();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function setSessionEffortOverride(sessionId: string, effort: string): void {
|
|
402
|
+
sessionEffortOverrides.set(sessionId, effort);
|
|
403
|
+
adapterCache.clear();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function clearSessionEffortOverride(sessionId: string): void {
|
|
407
|
+
sessionEffortOverrides.delete(sessionId);
|
|
408
|
+
adapterCache.clear();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter {
|
|
412
|
+
const effectiveModel = getEffectiveModelForTool(tool, sessionId);
|
|
413
|
+
const effectiveEffort = getEffectiveEffortForTool(tool, sessionId);
|
|
414
|
+
const cacheKey = `${tool}:${effectiveModel || ""}:${effectiveEffort || ""}`;
|
|
415
|
+
const cached = adapterCache.get(cacheKey);
|
|
416
|
+
if (cached) return cached;
|
|
417
|
+
|
|
418
|
+
let adapter: ToolAdapter;
|
|
419
|
+
if (tool === "cursor") {
|
|
420
|
+
adapter = createCursorAdapter({ model: effectiveModel || undefined });
|
|
421
|
+
} else if (tool === "codex") {
|
|
422
|
+
adapter = createCodexAdapter({ model: effectiveModel || undefined, effort: effectiveEffort || undefined });
|
|
423
|
+
} else {
|
|
424
|
+
adapter = createClaudeAdapter({
|
|
425
|
+
model: effectiveModel,
|
|
426
|
+
subagentModel: CLAUDE_SUBAGENT_MODEL,
|
|
427
|
+
effort: effectiveEffort,
|
|
428
|
+
apiKey: CLAUDE_API_KEY,
|
|
429
|
+
baseUrl: CLAUDE_BASE_URL,
|
|
430
|
+
isEmpty: isAnthropicConfigEmpty,
|
|
431
431
|
maxTurn: CLAUDE_MAX_TURN,
|
|
432
432
|
});
|
|
433
433
|
}
|
|
@@ -855,18 +855,18 @@ export async function switchChatBinding(args: SwitchChatBindingArgs): Promise<Sw
|
|
|
855
855
|
function formatToolConfigForLog(tool: string, sessionModel?: string, sessionId?: string): string {
|
|
856
856
|
if (tool === "cursor") {
|
|
857
857
|
return `model=${sessionModel ?? "(由 cursor-agent 决定,init 事件后学习)"}`;
|
|
858
|
-
}
|
|
859
|
-
if (tool === "codex") {
|
|
860
|
-
const m = getEffectiveModelForTool(tool, sessionId);
|
|
861
|
-
const e = getEffectiveEffortForTool(tool, sessionId);
|
|
858
|
+
}
|
|
859
|
+
if (tool === "codex") {
|
|
860
|
+
const m = getEffectiveModelForTool(tool, sessionId);
|
|
861
|
+
const e = getEffectiveEffortForTool(tool, sessionId);
|
|
862
862
|
const modelStr = m.trim() !== "" ? m : "(由 codex config.toml 决定)";
|
|
863
863
|
const effortStr = e.trim() !== ""
|
|
864
864
|
? `effort=${e}`
|
|
865
865
|
: "effort=(由 codex config.toml 决定)";
|
|
866
866
|
return `model=${modelStr}, ${effortStr}`;
|
|
867
867
|
}
|
|
868
|
-
return `model=${anthropicConfigDisplay(getModelForSession(sessionId))}, subagentModel=${anthropicConfigDisplay(CLAUDE_SUBAGENT_MODEL)}, effort=${anthropicConfigDisplay(getEffectiveEffortForTool(tool, sessionId))}`;
|
|
869
|
-
}
|
|
868
|
+
return `model=${anthropicConfigDisplay(getModelForSession(sessionId))}, subagentModel=${anthropicConfigDisplay(CLAUDE_SUBAGENT_MODEL)}, effort=${anthropicConfigDisplay(getEffectiveEffortForTool(tool, sessionId))}`;
|
|
869
|
+
}
|
|
870
870
|
|
|
871
871
|
export async function initClaudeSession(tool: string, overrideCwd?: string, chatId?: string): Promise<{ sessionId: string; cwd: string }> {
|
|
872
872
|
const cwd = overrideCwd ?? (await getDefaultCwd(chatId));
|
|
@@ -981,12 +981,12 @@ export async function runAgentSession(
|
|
|
981
981
|
const imSkillsCacheDir = join(USER_DATA_DIR, "im-skills");
|
|
982
982
|
const skillVariables = {
|
|
983
983
|
cwd,
|
|
984
|
-
session_id: sessionId,
|
|
985
|
-
im_skills_cache_dir: imSkillsCacheDir,
|
|
986
|
-
delegate_task_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/delegate-task`,
|
|
987
|
-
send_image_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-image`,
|
|
988
|
-
send_file_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-file`,
|
|
989
|
-
send_image_script: join(feishuSkillDir, "send-image.mjs"),
|
|
984
|
+
session_id: sessionId,
|
|
985
|
+
im_skills_cache_dir: imSkillsCacheDir,
|
|
986
|
+
delegate_task_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/delegate-task`,
|
|
987
|
+
send_image_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-image`,
|
|
988
|
+
send_file_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-file`,
|
|
989
|
+
send_image_script: join(feishuSkillDir, "send-image.mjs"),
|
|
990
990
|
send_file_script: join(feishuSkillDir, "send-file.mjs"),
|
|
991
991
|
download_video_script: join(feishuSkillDir, "download-video.mjs"),
|
|
992
992
|
wechat_send_image_script: join(wechatImageSkillDir, "send-image.mjs"),
|
|
@@ -1780,20 +1780,20 @@ async function resolveModelEffort(
|
|
|
1780
1780
|
// adapter 异常时降级为占位符(不阻塞 /state 卡片)
|
|
1781
1781
|
}
|
|
1782
1782
|
return { model, effort: null };
|
|
1783
|
-
}
|
|
1784
|
-
if (tool === "codex") {
|
|
1785
|
-
const m = getEffectiveModelForTool(tool, sessionId);
|
|
1786
|
-
const e = getEffectiveEffortForTool(tool, sessionId);
|
|
1783
|
+
}
|
|
1784
|
+
if (tool === "codex") {
|
|
1785
|
+
const m = getEffectiveModelForTool(tool, sessionId);
|
|
1786
|
+
const e = getEffectiveEffortForTool(tool, sessionId);
|
|
1787
1787
|
return {
|
|
1788
1788
|
model: m.trim() !== "" ? m : UNKNOWN_MODEL_PLACEHOLDER,
|
|
1789
1789
|
effort: e.trim() !== "" ? e : UNKNOWN_MODEL_PLACEHOLDER,
|
|
1790
1790
|
};
|
|
1791
|
-
}
|
|
1792
|
-
return {
|
|
1793
|
-
model: anthropicConfigDisplay(getModelForSession(sessionId)),
|
|
1794
|
-
effort: anthropicConfigDisplay(getEffectiveEffortForTool(tool, sessionId)),
|
|
1795
|
-
};
|
|
1796
|
-
}
|
|
1791
|
+
}
|
|
1792
|
+
return {
|
|
1793
|
+
model: anthropicConfigDisplay(getModelForSession(sessionId)),
|
|
1794
|
+
effort: anthropicConfigDisplay(getEffectiveEffortForTool(tool, sessionId)),
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
1797
|
|
|
1798
1798
|
export async function getSessionStatus(chatId: string): Promise<SessionStatus | null> {
|
|
1799
1799
|
const info = sessionInfoMap.get(chatId);
|
|
@@ -1899,9 +1899,9 @@ export function _setAdapterForToolForTest(tool: string, adapter: ToolAdapter): v
|
|
|
1899
1899
|
adapterCache.set(tool, adapter);
|
|
1900
1900
|
// 同时设置当前配置模型对应的 key(getAdapterForTool 会优先 lookup 含 model 的 key)
|
|
1901
1901
|
const effective = getEffectiveModelForTool(tool);
|
|
1902
|
-
const effort = getEffectiveEffortForTool(tool);
|
|
1903
|
-
adapterCache.set(`${tool}:${effective || ""}:${effort || ""}`, adapter);
|
|
1904
|
-
if (effective) adapterCache.set(`${tool}:${effective}`, adapter);
|
|
1902
|
+
const effort = getEffectiveEffortForTool(tool);
|
|
1903
|
+
adapterCache.set(`${tool}:${effective || ""}:${effort || ""}`, adapter);
|
|
1904
|
+
if (effective) adapterCache.set(`${tool}:${effective}`, adapter);
|
|
1905
1905
|
}
|
|
1906
1906
|
|
|
1907
1907
|
export function clearAdapterCache(): void {
|
package/src/shared-prefix.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
export const ABD_PREFIX = "/abd";
|
|
2
|
-
|
|
3
|
-
export const ABD_APPEND_PROMPT =
|
|
4
|
-
"请从第一性原理出发挖掘我的真实需求。你觉得我的需求合理吗?开始实现之前有什么问题要问我的?";
|
|
5
|
-
|
|
6
|
-
export const ABD_HELP_LINE =
|
|
7
|
-
`发送 **${ABD_PREFIX}** 前缀消息,将以第一性原理追问真实需求、合理性和实现前问题`;
|
|
8
|
-
|
|
9
|
-
export interface SharedPrefixResult {
|
|
10
|
-
matched: boolean;
|
|
11
|
-
text: string;
|
|
12
|
-
body: string;
|
|
13
|
-
prefix?: typeof ABD_PREFIX;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function applySharedPrefix(text: string): SharedPrefixResult {
|
|
17
|
-
if (!text.toLowerCase().startsWith(ABD_PREFIX)) {
|
|
18
|
-
return { matched: false, text, body: text };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const body = text.slice(ABD_PREFIX.length).replace(/^\s+/, "");
|
|
22
|
-
const appendix = ["---", ABD_APPEND_PROMPT].join("\n");
|
|
23
|
-
return {
|
|
24
|
-
matched: true,
|
|
25
|
-
prefix: ABD_PREFIX,
|
|
26
|
-
body,
|
|
27
|
-
text: body ? [body, appendix].join("\n\n") : appendix,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
1
|
+
export const ABD_PREFIX = "/abd";
|
|
2
|
+
|
|
3
|
+
export const ABD_APPEND_PROMPT =
|
|
4
|
+
"请从第一性原理出发挖掘我的真实需求。你觉得我的需求合理吗?开始实现之前有什么问题要问我的?";
|
|
5
|
+
|
|
6
|
+
export const ABD_HELP_LINE =
|
|
7
|
+
`发送 **${ABD_PREFIX}** 前缀消息,将以第一性原理追问真实需求、合理性和实现前问题`;
|
|
8
|
+
|
|
9
|
+
export interface SharedPrefixResult {
|
|
10
|
+
matched: boolean;
|
|
11
|
+
text: string;
|
|
12
|
+
body: string;
|
|
13
|
+
prefix?: typeof ABD_PREFIX;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function applySharedPrefix(text: string): SharedPrefixResult {
|
|
17
|
+
if (!text.toLowerCase().startsWith(ABD_PREFIX)) {
|
|
18
|
+
return { matched: false, text, body: text };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const body = text.slice(ABD_PREFIX.length).replace(/^\s+/, "");
|
|
22
|
+
const appendix = ["---", ABD_APPEND_PROMPT].join("\n");
|
|
23
|
+
return {
|
|
24
|
+
matched: true,
|
|
25
|
+
prefix: ABD_PREFIX,
|
|
26
|
+
body,
|
|
27
|
+
text: body ? [body, appendix].join("\n\n") : appendix,
|
|
28
|
+
};
|
|
29
|
+
}
|
package/src/sim-platform.ts
CHANGED
|
@@ -123,25 +123,25 @@ export const SimulatedPlatform: FeishuPlatform = {
|
|
|
123
123
|
},
|
|
124
124
|
|
|
125
125
|
// ---- 头像 ----
|
|
126
|
-
async setChatAvatar(_token, _chatId, _tool, _status) {
|
|
127
|
-
// 模拟模式不需要头像
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
async getCodexUsageSummary() {
|
|
131
|
-
return {
|
|
132
|
-
fiveHour: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
|
|
133
|
-
weekly: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
|
|
134
|
-
rateLimitResetCreditsAvailable: null,
|
|
135
|
-
rateLimitResetCredits: null,
|
|
136
|
-
};
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
async consumeCodexRateLimitResetCredit(_redeemRequestId) {
|
|
140
|
-
return { code: "no_credit" as const, windowsReset: 0 };
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
// ---- 图片下载 ----
|
|
144
|
-
async getOrDownloadImage(_token, _messageId, fileKey) {
|
|
126
|
+
async setChatAvatar(_token, _chatId, _tool, _status) {
|
|
127
|
+
// 模拟模式不需要头像
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async getCodexUsageSummary() {
|
|
131
|
+
return {
|
|
132
|
+
fiveHour: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
|
|
133
|
+
weekly: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
|
|
134
|
+
rateLimitResetCreditsAvailable: null,
|
|
135
|
+
rateLimitResetCredits: null,
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
async consumeCodexRateLimitResetCredit(_redeemRequestId) {
|
|
140
|
+
return { code: "no_credit" as const, windowsReset: 0 };
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// ---- 图片下载 ----
|
|
144
|
+
async getOrDownloadImage(_token, _messageId, fileKey) {
|
|
145
145
|
return join(SIM_DIR, "images", fileKey);
|
|
146
146
|
},
|
|
147
147
|
|
|
@@ -174,4 +174,4 @@ export const SimulatedPlatform: FeishuPlatform = {
|
|
|
174
174
|
};
|
|
175
175
|
|
|
176
176
|
/** 模拟模式下的默认 chat_id(重新导出以保持向后兼容) */
|
|
177
|
-
export { SIM_DEFAULT_CHAT_ID } from "./sim-store.ts";
|
|
177
|
+
export { SIM_DEFAULT_CHAT_ID } from "./sim-store.ts";
|
package/src/turn-cards.ts
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
|
|
4
|
-
import { USER_DATA_DIR, ts } from "./config.ts";
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// turn-cards.json — 每个 session 的卡片归属持久化
|
|
8
|
-
// 记录每个 turn 有哪些卡片(轮转会产生多张),以及每张卡片的状态
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
export const TURNS_DIR = join(USER_DATA_DIR, "state", "turns");
|
|
12
|
-
|
|
13
|
-
export interface TurnCardEntry {
|
|
14
|
-
cardId: string;
|
|
15
|
-
status: "active" | "done" | "stopped";
|
|
16
|
-
createdAt: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface TurnCardsFile {
|
|
20
|
-
sessionId: string;
|
|
21
|
-
turns: { turnCount: number; cards: TurnCardEntry[] }[];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function getTurnCardsPath(sessionId: string): string {
|
|
25
|
-
return join(TURNS_DIR, `${sessionId}.json`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function createEmptyTurnCards(sessionId: string): TurnCardsFile {
|
|
29
|
-
return { sessionId, turns: [] };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function readTurnCards(sessionId: string): Promise<TurnCardsFile | null> {
|
|
33
|
-
try {
|
|
34
|
-
const raw = await readFile(getTurnCardsPath(sessionId), "utf-8");
|
|
35
|
-
const parsed = JSON.parse(raw) as TurnCardsFile;
|
|
36
|
-
if (parsed && typeof parsed.sessionId === "string" && Array.isArray(parsed.turns)) {
|
|
37
|
-
return parsed;
|
|
38
|
-
}
|
|
39
|
-
return null;
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function writeTurnCards(sessionId: string, data: TurnCardsFile): Promise<void> {
|
|
46
|
-
try {
|
|
47
|
-
const filePath = getTurnCardsPath(sessionId);
|
|
48
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
49
|
-
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
50
|
-
} catch (err) {
|
|
51
|
-
console.error(`[${ts()}] Failed to write turn-cards for ${sessionId}: ${(err as Error).message}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** 向指定 turn 追加一张卡片并持久化。若 turn 不存在则创建。 */
|
|
56
|
-
export async function addCardToTurn(
|
|
57
|
-
sessionId: string,
|
|
58
|
-
turnCount: number,
|
|
59
|
-
cardId: string,
|
|
60
|
-
): Promise<void> {
|
|
61
|
-
const data = (await readTurnCards(sessionId)) ?? createEmptyTurnCards(sessionId);
|
|
62
|
-
let turn = data.turns.find(t => t.turnCount === turnCount);
|
|
63
|
-
if (!turn) {
|
|
64
|
-
turn = { turnCount, cards: [] };
|
|
65
|
-
data.turns.push(turn);
|
|
66
|
-
}
|
|
67
|
-
turn.cards.push({ cardId, status: "active", createdAt: Date.now() });
|
|
68
|
-
await writeTurnCards(sessionId, data);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** 将指定 turn 的所有 active 卡片标记为终态并持久化。返回被标记的 cardId 列表。 */
|
|
72
|
-
export async function finalizeTurnCards(
|
|
73
|
-
sessionId: string,
|
|
74
|
-
turnCount: number,
|
|
75
|
-
finalStatus: "done" | "stopped",
|
|
76
|
-
): Promise<string[]> {
|
|
77
|
-
const data = await readTurnCards(sessionId);
|
|
78
|
-
if (!data) return [];
|
|
79
|
-
const turn = data.turns.find(t => t.turnCount === turnCount);
|
|
80
|
-
if (!turn) return [];
|
|
81
|
-
const finalized: string[] = [];
|
|
82
|
-
for (const card of turn.cards) {
|
|
83
|
-
if (card.status === "active") {
|
|
84
|
-
card.status = finalStatus;
|
|
85
|
-
finalized.push(card.cardId);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (finalized.length > 0) {
|
|
89
|
-
await writeTurnCards(sessionId, data);
|
|
90
|
-
}
|
|
91
|
-
return finalized;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** 将指定 turn 中的一张 active 卡片标记为 done(轮转时用),并持久化。 */
|
|
95
|
-
export async function markCardDone(
|
|
96
|
-
sessionId: string,
|
|
97
|
-
turnCount: number,
|
|
98
|
-
cardId: string,
|
|
99
|
-
): Promise<void> {
|
|
100
|
-
const data = await readTurnCards(sessionId);
|
|
101
|
-
if (!data) return;
|
|
102
|
-
const turn = data.turns.find(t => t.turnCount === turnCount);
|
|
103
|
-
if (!turn) return;
|
|
104
|
-
const card = turn.cards.find(c => c.cardId === cardId && c.status === "active");
|
|
105
|
-
if (!card) return;
|
|
106
|
-
card.status = "done";
|
|
107
|
-
await writeTurnCards(sessionId, data);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** 清理 session 的 turn-cards 文件 */
|
|
111
|
-
export async function removeTurnCards(sessionId: string): Promise<void> {
|
|
112
|
-
try {
|
|
113
|
-
const { unlink } = await import("node:fs/promises");
|
|
114
|
-
await unlink(getTurnCardsPath(sessionId));
|
|
115
|
-
} catch {
|
|
116
|
-
// 文件不存在,忽略
|
|
117
|
-
}
|
|
1
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { USER_DATA_DIR, ts } from "./config.ts";
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// turn-cards.json — 每个 session 的卡片归属持久化
|
|
8
|
+
// 记录每个 turn 有哪些卡片(轮转会产生多张),以及每张卡片的状态
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export const TURNS_DIR = join(USER_DATA_DIR, "state", "turns");
|
|
12
|
+
|
|
13
|
+
export interface TurnCardEntry {
|
|
14
|
+
cardId: string;
|
|
15
|
+
status: "active" | "done" | "stopped";
|
|
16
|
+
createdAt: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TurnCardsFile {
|
|
20
|
+
sessionId: string;
|
|
21
|
+
turns: { turnCount: number; cards: TurnCardEntry[] }[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getTurnCardsPath(sessionId: string): string {
|
|
25
|
+
return join(TURNS_DIR, `${sessionId}.json`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createEmptyTurnCards(sessionId: string): TurnCardsFile {
|
|
29
|
+
return { sessionId, turns: [] };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function readTurnCards(sessionId: string): Promise<TurnCardsFile | null> {
|
|
33
|
+
try {
|
|
34
|
+
const raw = await readFile(getTurnCardsPath(sessionId), "utf-8");
|
|
35
|
+
const parsed = JSON.parse(raw) as TurnCardsFile;
|
|
36
|
+
if (parsed && typeof parsed.sessionId === "string" && Array.isArray(parsed.turns)) {
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function writeTurnCards(sessionId: string, data: TurnCardsFile): Promise<void> {
|
|
46
|
+
try {
|
|
47
|
+
const filePath = getTurnCardsPath(sessionId);
|
|
48
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
49
|
+
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error(`[${ts()}] Failed to write turn-cards for ${sessionId}: ${(err as Error).message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 向指定 turn 追加一张卡片并持久化。若 turn 不存在则创建。 */
|
|
56
|
+
export async function addCardToTurn(
|
|
57
|
+
sessionId: string,
|
|
58
|
+
turnCount: number,
|
|
59
|
+
cardId: string,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
const data = (await readTurnCards(sessionId)) ?? createEmptyTurnCards(sessionId);
|
|
62
|
+
let turn = data.turns.find(t => t.turnCount === turnCount);
|
|
63
|
+
if (!turn) {
|
|
64
|
+
turn = { turnCount, cards: [] };
|
|
65
|
+
data.turns.push(turn);
|
|
66
|
+
}
|
|
67
|
+
turn.cards.push({ cardId, status: "active", createdAt: Date.now() });
|
|
68
|
+
await writeTurnCards(sessionId, data);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 将指定 turn 的所有 active 卡片标记为终态并持久化。返回被标记的 cardId 列表。 */
|
|
72
|
+
export async function finalizeTurnCards(
|
|
73
|
+
sessionId: string,
|
|
74
|
+
turnCount: number,
|
|
75
|
+
finalStatus: "done" | "stopped",
|
|
76
|
+
): Promise<string[]> {
|
|
77
|
+
const data = await readTurnCards(sessionId);
|
|
78
|
+
if (!data) return [];
|
|
79
|
+
const turn = data.turns.find(t => t.turnCount === turnCount);
|
|
80
|
+
if (!turn) return [];
|
|
81
|
+
const finalized: string[] = [];
|
|
82
|
+
for (const card of turn.cards) {
|
|
83
|
+
if (card.status === "active") {
|
|
84
|
+
card.status = finalStatus;
|
|
85
|
+
finalized.push(card.cardId);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (finalized.length > 0) {
|
|
89
|
+
await writeTurnCards(sessionId, data);
|
|
90
|
+
}
|
|
91
|
+
return finalized;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** 将指定 turn 中的一张 active 卡片标记为 done(轮转时用),并持久化。 */
|
|
95
|
+
export async function markCardDone(
|
|
96
|
+
sessionId: string,
|
|
97
|
+
turnCount: number,
|
|
98
|
+
cardId: string,
|
|
99
|
+
): Promise<void> {
|
|
100
|
+
const data = await readTurnCards(sessionId);
|
|
101
|
+
if (!data) return;
|
|
102
|
+
const turn = data.turns.find(t => t.turnCount === turnCount);
|
|
103
|
+
if (!turn) return;
|
|
104
|
+
const card = turn.cards.find(c => c.cardId === cardId && c.status === "active");
|
|
105
|
+
if (!card) return;
|
|
106
|
+
card.status = "done";
|
|
107
|
+
await writeTurnCards(sessionId, data);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 清理 session 的 turn-cards 文件 */
|
|
111
|
+
export async function removeTurnCards(sessionId: string): Promise<void> {
|
|
112
|
+
try {
|
|
113
|
+
const { unlink } = await import("node:fs/promises");
|
|
114
|
+
await unlink(getTurnCardsPath(sessionId));
|
|
115
|
+
} catch {
|
|
116
|
+
// 文件不存在,忽略
|
|
117
|
+
}
|
|
118
118
|
}
|