chatccc 0.2.72 → 0.2.74
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/package.json +1 -1
- package/src/__tests__/im-skills.test.ts +26 -1
- package/src/__tests__/platform-startup.test.ts +19 -0
- package/src/im-skills.ts +31 -0
- package/src/index.ts +34 -15
- package/src/platform-startup.ts +16 -0
package/package.json
CHANGED
|
@@ -3,7 +3,12 @@ import { join } from "node:path";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
buildImSkillsPrompt,
|
|
8
|
+
buildImSkillsPromptCached,
|
|
9
|
+
clearImSkillsPromptCache,
|
|
10
|
+
exportSkillSubDocs,
|
|
11
|
+
} from "../im-skills.ts";
|
|
7
12
|
|
|
8
13
|
let tempRoot: string | null = null;
|
|
9
14
|
|
|
@@ -97,4 +102,24 @@ describe("IM skills prompt rendering", () => {
|
|
|
97
102
|
expect(prompt).toContain("[ChatCCC IM skill: wechat-skill]");
|
|
98
103
|
expect(exported.length).toBeGreaterThanOrEqual(2);
|
|
99
104
|
});
|
|
105
|
+
|
|
106
|
+
it("exports cached prompt helpers used by session startup", async () => {
|
|
107
|
+
tempRoot = await mkdtemp(join(tmpdir(), "chatccc-im-skills-cache-"));
|
|
108
|
+
const skillDir = join(tempRoot, "feishu-skill");
|
|
109
|
+
await mkdir(skillDir);
|
|
110
|
+
await writeFile(join(skillDir, "skill.md"), "cwd={{cwd}}", "utf-8");
|
|
111
|
+
|
|
112
|
+
const input = {
|
|
113
|
+
skillsDir: tempRoot,
|
|
114
|
+
variables: { cwd: "C:/first", session_id: "sid_cache" },
|
|
115
|
+
};
|
|
116
|
+
const first = await buildImSkillsPromptCached(input);
|
|
117
|
+
await writeFile(join(skillDir, "skill.md"), "cwd={{cwd}} changed", "utf-8");
|
|
118
|
+
const cached = await buildImSkillsPromptCached(input);
|
|
119
|
+
clearImSkillsPromptCache("sid_cache");
|
|
120
|
+
const refreshed = await buildImSkillsPromptCached(input);
|
|
121
|
+
|
|
122
|
+
expect(cached).toBe(first);
|
|
123
|
+
expect(refreshed).toContain("changed");
|
|
124
|
+
});
|
|
100
125
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { buildPlatformStartupPlan } from "../platform-startup.ts";
|
|
4
|
+
|
|
5
|
+
describe("buildPlatformStartupPlan", () => {
|
|
6
|
+
it("starts WeChat iLink without requiring Feishu when Feishu is disabled", () => {
|
|
7
|
+
expect(buildPlatformStartupPlan({ feishuEnabled: false, ilinkEnabled: true })).toEqual({
|
|
8
|
+
startFeishu: false,
|
|
9
|
+
startIlink: true,
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("can start both platforms when both are enabled", () => {
|
|
14
|
+
expect(buildPlatformStartupPlan({ feishuEnabled: true, ilinkEnabled: true })).toEqual({
|
|
15
|
+
startFeishu: true,
|
|
16
|
+
startIlink: true,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
package/src/im-skills.ts
CHANGED
|
@@ -70,6 +70,37 @@ export async function buildImSkillsPrompt(input: BuildImSkillsPromptInput): Prom
|
|
|
70
70
|
.join("\n\n");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// 会话级缓存:相同 session_id + cwd 的渲染结果完全相同,避免每轮重复读文件+渲染
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
const promptCache = new Map<string, string>();
|
|
78
|
+
|
|
79
|
+
function promptCacheKey(input: BuildImSkillsPromptInput): string {
|
|
80
|
+
const names = input.enabledSkillNames
|
|
81
|
+
? [...input.enabledSkillNames].sort().join(",")
|
|
82
|
+
: "*";
|
|
83
|
+
const { session_id, cwd } = input.variables;
|
|
84
|
+
return `${input.skillsDir ?? DEFAULT_IM_SKILLS_DIR}|${names}|${session_id}|${cwd}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 带会话级缓存的 buildImSkillsPrompt。同 session + 同 cwd 时直接返回缓存的渲染结果。 */
|
|
88
|
+
export async function buildImSkillsPromptCached(input: BuildImSkillsPromptInput): Promise<string> {
|
|
89
|
+
const key = promptCacheKey(input);
|
|
90
|
+
const cached = promptCache.get(key);
|
|
91
|
+
if (cached !== undefined) return cached;
|
|
92
|
+
const result = await buildImSkillsPrompt(input);
|
|
93
|
+
promptCache.set(key, result);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 清除指定会话的缓存(如 /cd 后 cwd 变了,旧 key 不再需要) */
|
|
98
|
+
export function clearImSkillsPromptCache(sessionId: string): void {
|
|
99
|
+
for (const key of promptCache.keys()) {
|
|
100
|
+
if (key.includes(`|${sessionId}|`)) promptCache.delete(key);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
73
104
|
/**
|
|
74
105
|
* 渲染技能目录下的子文档(skill.md 除外)并写入 outputDir。
|
|
75
106
|
* 返回写入的文件路径列表。通常在会话初始化时调用,写入的文档供 Agent 按需读取。
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ import WebSocket from "ws";
|
|
|
29
29
|
|
|
30
30
|
import { appendStartupTrace, attachRelayWebSocket, ensureSingleInstance, freeRelayListenPort, installCrashLogging, waitForPortFree } from "./shared.ts";
|
|
31
31
|
import { createUiRouter, setExtraApiHandler, setReloadConfigHook, startSetupMode } from "./web-ui.ts";
|
|
32
|
+
import { buildPlatformStartupPlan } from "./platform-startup.ts";
|
|
32
33
|
import { makeTraceId, logTrace } from "./trace.ts";
|
|
33
34
|
import {
|
|
34
35
|
CHATCCC_PORT,
|
|
@@ -668,6 +669,36 @@ async function startWechatSupervisor(): Promise<void> {
|
|
|
668
669
|
console.log("[WX] 微信 iLink 平台已停止。");
|
|
669
670
|
}
|
|
670
671
|
|
|
672
|
+
async function startConfiguredPlatforms(
|
|
673
|
+
httpServer: Server,
|
|
674
|
+
options: { failOnFeishuError: boolean },
|
|
675
|
+
): Promise<void> {
|
|
676
|
+
const plan = buildPlatformStartupPlan({
|
|
677
|
+
feishuEnabled: FEISHU_ENABLED,
|
|
678
|
+
ilinkEnabled: ILINK_ENABLED,
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
if (plan.startFeishu) {
|
|
682
|
+
try {
|
|
683
|
+
await startBotService({ httpServer, port: CHATCCC_PORT });
|
|
684
|
+
} catch (err) {
|
|
685
|
+
if (options.failOnFeishuError) throw err;
|
|
686
|
+
console.error(`\n[飞书] 启动失败: ${(err as Error).message}`);
|
|
687
|
+
console.error("[飞书] 微信等其他平台不受影响,将继续启动。\n");
|
|
688
|
+
}
|
|
689
|
+
} else {
|
|
690
|
+
console.log("[飞书] 平台未启用,跳过飞书启动。");
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (plan.startIlink) {
|
|
694
|
+
startWechatSupervisor().catch((err) =>
|
|
695
|
+
console.error(`[WX] 微信 supervisor 异常退出: ${(err as Error).message}`),
|
|
696
|
+
);
|
|
697
|
+
} else {
|
|
698
|
+
console.log("[WX] 微信 iLink 未启用,跳过微信启动。");
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
671
702
|
// ---------------------------------------------------------------------------
|
|
672
703
|
// Main
|
|
673
704
|
// ---------------------------------------------------------------------------
|
|
@@ -784,11 +815,11 @@ async function main(): Promise<void> {
|
|
|
784
815
|
appIdMaskAfterReload: maskAppId(APP_ID),
|
|
785
816
|
});
|
|
786
817
|
try {
|
|
787
|
-
await
|
|
818
|
+
await startConfiguredPlatforms(httpServer, { failOnFeishuError: true });
|
|
788
819
|
installShutdownHandlers(httpServer);
|
|
789
820
|
return { ok: true };
|
|
790
821
|
} catch (err) {
|
|
791
|
-
appendStartupTrace("setup-activate:
|
|
822
|
+
appendStartupTrace("setup-activate: startConfiguredPlatforms failed", {
|
|
792
823
|
message: (err as Error).message,
|
|
793
824
|
});
|
|
794
825
|
return { ok: false, error: (err as Error).message };
|
|
@@ -822,19 +853,7 @@ async function main(): Promise<void> {
|
|
|
822
853
|
process.exit(1);
|
|
823
854
|
});
|
|
824
855
|
|
|
825
|
-
|
|
826
|
-
try {
|
|
827
|
-
await startBotService({ httpServer, port: CHATCCC_PORT });
|
|
828
|
-
} catch (err) {
|
|
829
|
-
console.error(`\n[飞书] 启动失败: ${(err as Error).message}`);
|
|
830
|
-
console.error("[飞书] 微信等其他平台不受影响,将继续启动。\n");
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// 启动微信 iLink 平台(后台运行,不阻塞飞书)
|
|
835
|
-
startWechatSupervisor().catch((err) =>
|
|
836
|
-
console.error(`[WX] 微信 supervisor 异常退出: ${(err as Error).message}`),
|
|
837
|
-
);
|
|
856
|
+
await startConfiguredPlatforms(httpServer, { failOnFeishuError: false });
|
|
838
857
|
|
|
839
858
|
installShutdownHandlers(httpServer);
|
|
840
859
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface PlatformStartupPlanInput {
|
|
2
|
+
feishuEnabled: boolean;
|
|
3
|
+
ilinkEnabled: boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface PlatformStartupPlan {
|
|
7
|
+
startFeishu: boolean;
|
|
8
|
+
startIlink: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildPlatformStartupPlan(input: PlatformStartupPlanInput): PlatformStartupPlan {
|
|
12
|
+
return {
|
|
13
|
+
startFeishu: input.feishuEnabled,
|
|
14
|
+
startIlink: input.ilinkEnabled,
|
|
15
|
+
};
|
|
16
|
+
}
|