chatccc 0.2.188 → 0.2.190
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 +116 -112
- 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 -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 +27 -28
- 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 -189
- package/src/builtin/index.ts +170 -168
- 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 +142 -24
package/src/orchestrator.ts
CHANGED
|
@@ -22,11 +22,11 @@ import {
|
|
|
22
22
|
PROJECT_ROOT,
|
|
23
23
|
anthropicConfigDisplay,
|
|
24
24
|
config,
|
|
25
|
-
fileLog,
|
|
26
|
-
getAllEffortsForTool,
|
|
27
|
-
getAllModelsForTool,
|
|
28
|
-
getDefaultEffortForTool,
|
|
29
|
-
getDefaultCwd,
|
|
25
|
+
fileLog,
|
|
26
|
+
getAllEffortsForTool,
|
|
27
|
+
getAllModelsForTool,
|
|
28
|
+
getDefaultEffortForTool,
|
|
29
|
+
getDefaultCwd,
|
|
30
30
|
setDefaultCwd,
|
|
31
31
|
getRecentDirs,
|
|
32
32
|
addRecentDir,
|
|
@@ -38,9 +38,9 @@ import {
|
|
|
38
38
|
type AgentTool,
|
|
39
39
|
} from "./config.ts";
|
|
40
40
|
import {
|
|
41
|
-
buildHelpCard,
|
|
42
|
-
buildEffortCard,
|
|
43
|
-
buildModelCard,
|
|
41
|
+
buildHelpCard,
|
|
42
|
+
buildEffortCard,
|
|
43
|
+
buildModelCard,
|
|
44
44
|
buildStatusCard,
|
|
45
45
|
buildCdContent,
|
|
46
46
|
buildCdCard,
|
|
@@ -55,21 +55,21 @@ import {
|
|
|
55
55
|
runGitCommand,
|
|
56
56
|
} from "./git-command.ts";
|
|
57
57
|
import {
|
|
58
|
-
clearSessionModelOverride,
|
|
59
|
-
clearSessionEffortOverride,
|
|
58
|
+
clearSessionModelOverride,
|
|
59
|
+
clearSessionEffortOverride,
|
|
60
60
|
getSessionStatus,
|
|
61
61
|
getAllSessionsStatus,
|
|
62
62
|
initClaudeSession,
|
|
63
63
|
lastMsgTimestamps,
|
|
64
64
|
resumeAndPrompt,
|
|
65
65
|
sessionInfoMap,
|
|
66
|
-
setSessionModelOverride,
|
|
67
|
-
setSessionEffortOverride,
|
|
66
|
+
setSessionModelOverride,
|
|
67
|
+
setSessionEffortOverride,
|
|
68
68
|
switchChatBinding,
|
|
69
69
|
recordSessionRegistry,
|
|
70
70
|
getAdapterForTool,
|
|
71
|
-
getEffectiveModelForTool,
|
|
72
|
-
getEffectiveEffortForTool,
|
|
71
|
+
getEffectiveModelForTool,
|
|
72
|
+
getEffectiveEffortForTool,
|
|
73
73
|
stopSession,
|
|
74
74
|
loadSessionRegistryForBinding,
|
|
75
75
|
removeSessionRegistryRecord,
|
|
@@ -1534,96 +1534,96 @@ export async function handleCommand(
|
|
|
1534
1534
|
}
|
|
1535
1535
|
|
|
1536
1536
|
// /git <args>:在「当前会话工作目录」执行 git 命令
|
|
1537
|
-
if (isCommandText && textLower === "/effort clear") {
|
|
1538
|
-
logTrace(tid, "BRANCH", { cmd: "/effort clear", sessionId, tool: descriptionTool });
|
|
1539
|
-
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1540
|
-
const toolLabel = toolDisplayName(descriptionTool);
|
|
1541
|
-
if (efforts.length === 0) {
|
|
1542
|
-
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1543
|
-
await (platform.kind === "wechat"
|
|
1544
|
-
? platform.sendText(chatId, msg)
|
|
1545
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1546
|
-
).catch(() => {});
|
|
1547
|
-
logTrace(tid, "DONE", { outcome: "effort_unsupported", tool: descriptionTool });
|
|
1548
|
-
return;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
clearSessionEffortOverride(sessionId);
|
|
1552
|
-
const defaultEffort = getDefaultEffortForTool(descriptionTool as AgentTool);
|
|
1553
|
-
const msg = `已清除当前 ${toolLabel} 会话的 effort 覆盖,恢复使用: \`${defaultEffort || "(未指定)"}\``;
|
|
1554
|
-
await (platform.kind === "wechat"
|
|
1555
|
-
? platform.sendText(chatId, msg)
|
|
1556
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "green")
|
|
1557
|
-
).catch(() => {});
|
|
1558
|
-
logTrace(tid, "DONE", { outcome: "effort_cleared", sessionId, tool: descriptionTool });
|
|
1559
|
-
return;
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
if (isCommandText && textLower.startsWith("/effort ")) {
|
|
1563
|
-
const effortArg = text.slice(8).trim();
|
|
1564
|
-
if (!effortArg) return;
|
|
1565
|
-
logTrace(tid, "BRANCH", { cmd: "/effort", arg: effortArg, sessionId, tool: descriptionTool });
|
|
1566
|
-
|
|
1567
|
-
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1568
|
-
const toolLabel = toolDisplayName(descriptionTool);
|
|
1569
|
-
if (efforts.length === 0) {
|
|
1570
|
-
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1571
|
-
await (platform.kind === "wechat"
|
|
1572
|
-
? platform.sendText(chatId, msg)
|
|
1573
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1574
|
-
).catch(() => {});
|
|
1575
|
-
logTrace(tid, "DONE", { outcome: "effort_unsupported", tool: descriptionTool });
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
const target = findModelMatch(effortArg, efforts);
|
|
1580
|
-
if (!target) {
|
|
1581
|
-
const msg = `未找到匹配 "${effortArg}" 的 effort。当前 ${toolLabel} 可选 effort:\n${efforts.map(e => ` \`${e}\``).join("\n")}`;
|
|
1582
|
-
await (platform.kind === "wechat"
|
|
1583
|
-
? platform.sendText(chatId, msg)
|
|
1584
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1585
|
-
).catch(() => {});
|
|
1586
|
-
logTrace(tid, "DONE", { outcome: "effort_not_found", arg: effortArg, tool: descriptionTool });
|
|
1587
|
-
return;
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
setSessionEffortOverride(sessionId, target);
|
|
1591
|
-
const msg = `已切换当前 ${toolLabel} 会话 effort 为: \`${target}\``;
|
|
1592
|
-
await (platform.kind === "wechat"
|
|
1593
|
-
? platform.sendText(chatId, msg)
|
|
1594
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "green")
|
|
1595
|
-
).catch(() => {});
|
|
1596
|
-
logTrace(tid, "DONE", { outcome: "effort_switched", arg: effortArg, target, sessionId, tool: descriptionTool });
|
|
1597
|
-
return;
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
if (isCommandText && textLower === "/effort") {
|
|
1601
|
-
logTrace(tid, "BRANCH", { cmd: "/effort", sessionId, tool: descriptionTool });
|
|
1602
|
-
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1603
|
-
const currentEffort = getEffectiveEffortForTool(descriptionTool, sessionId);
|
|
1604
|
-
const toolLabel = toolDisplayName(descriptionTool);
|
|
1605
|
-
|
|
1606
|
-
if (efforts.length === 0) {
|
|
1607
|
-
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1608
|
-
await (platform.kind === "wechat"
|
|
1609
|
-
? platform.sendText(chatId, msg)
|
|
1610
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1611
|
-
).catch(() => {});
|
|
1612
|
-
} else if (platform.kind === "wechat") {
|
|
1613
|
-
const lines = [currentEffort ? `当前 effort (${toolLabel}): ${currentEffort}` : `当前 effort (${toolLabel}): 未指定`];
|
|
1614
|
-
lines.push("", "可切换 effort:");
|
|
1615
|
-
for (const e of efforts) lines.push(` ${e}`);
|
|
1616
|
-
lines.push("", "输入 /effort <effort> 切换 effort");
|
|
1617
|
-
await platform.sendText(chatId, lines.join("\n")).catch(() => {});
|
|
1618
|
-
} else {
|
|
1619
|
-
const card = buildEffortCard(currentEffort, efforts, descriptionTool);
|
|
1620
|
-
await platform.sendRawCard(chatId, card);
|
|
1621
|
-
}
|
|
1622
|
-
logTrace(tid, "DONE", { outcome: "effort_query", tool: descriptionTool });
|
|
1623
|
-
return;
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
if (isCommandText && (textLower.startsWith("/git ") || textLower === "/git")) {
|
|
1537
|
+
if (isCommandText && textLower === "/effort clear") {
|
|
1538
|
+
logTrace(tid, "BRANCH", { cmd: "/effort clear", sessionId, tool: descriptionTool });
|
|
1539
|
+
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1540
|
+
const toolLabel = toolDisplayName(descriptionTool);
|
|
1541
|
+
if (efforts.length === 0) {
|
|
1542
|
+
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1543
|
+
await (platform.kind === "wechat"
|
|
1544
|
+
? platform.sendText(chatId, msg)
|
|
1545
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1546
|
+
).catch(() => {});
|
|
1547
|
+
logTrace(tid, "DONE", { outcome: "effort_unsupported", tool: descriptionTool });
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
clearSessionEffortOverride(sessionId);
|
|
1552
|
+
const defaultEffort = getDefaultEffortForTool(descriptionTool as AgentTool);
|
|
1553
|
+
const msg = `已清除当前 ${toolLabel} 会话的 effort 覆盖,恢复使用: \`${defaultEffort || "(未指定)"}\``;
|
|
1554
|
+
await (platform.kind === "wechat"
|
|
1555
|
+
? platform.sendText(chatId, msg)
|
|
1556
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "green")
|
|
1557
|
+
).catch(() => {});
|
|
1558
|
+
logTrace(tid, "DONE", { outcome: "effort_cleared", sessionId, tool: descriptionTool });
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
if (isCommandText && textLower.startsWith("/effort ")) {
|
|
1563
|
+
const effortArg = text.slice(8).trim();
|
|
1564
|
+
if (!effortArg) return;
|
|
1565
|
+
logTrace(tid, "BRANCH", { cmd: "/effort", arg: effortArg, sessionId, tool: descriptionTool });
|
|
1566
|
+
|
|
1567
|
+
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1568
|
+
const toolLabel = toolDisplayName(descriptionTool);
|
|
1569
|
+
if (efforts.length === 0) {
|
|
1570
|
+
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1571
|
+
await (platform.kind === "wechat"
|
|
1572
|
+
? platform.sendText(chatId, msg)
|
|
1573
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1574
|
+
).catch(() => {});
|
|
1575
|
+
logTrace(tid, "DONE", { outcome: "effort_unsupported", tool: descriptionTool });
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
const target = findModelMatch(effortArg, efforts);
|
|
1580
|
+
if (!target) {
|
|
1581
|
+
const msg = `未找到匹配 "${effortArg}" 的 effort。当前 ${toolLabel} 可选 effort:\n${efforts.map(e => ` \`${e}\``).join("\n")}`;
|
|
1582
|
+
await (platform.kind === "wechat"
|
|
1583
|
+
? platform.sendText(chatId, msg)
|
|
1584
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1585
|
+
).catch(() => {});
|
|
1586
|
+
logTrace(tid, "DONE", { outcome: "effort_not_found", arg: effortArg, tool: descriptionTool });
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
setSessionEffortOverride(sessionId, target);
|
|
1591
|
+
const msg = `已切换当前 ${toolLabel} 会话 effort 为: \`${target}\``;
|
|
1592
|
+
await (platform.kind === "wechat"
|
|
1593
|
+
? platform.sendText(chatId, msg)
|
|
1594
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "green")
|
|
1595
|
+
).catch(() => {});
|
|
1596
|
+
logTrace(tid, "DONE", { outcome: "effort_switched", arg: effortArg, target, sessionId, tool: descriptionTool });
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
if (isCommandText && textLower === "/effort") {
|
|
1601
|
+
logTrace(tid, "BRANCH", { cmd: "/effort", sessionId, tool: descriptionTool });
|
|
1602
|
+
const efforts = getAllEffortsForTool(descriptionTool as AgentTool);
|
|
1603
|
+
const currentEffort = getEffectiveEffortForTool(descriptionTool, sessionId);
|
|
1604
|
+
const toolLabel = toolDisplayName(descriptionTool);
|
|
1605
|
+
|
|
1606
|
+
if (efforts.length === 0) {
|
|
1607
|
+
const msg = `当前 ${toolLabel} 不支持 effort 切换。`;
|
|
1608
|
+
await (platform.kind === "wechat"
|
|
1609
|
+
? platform.sendText(chatId, msg)
|
|
1610
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1611
|
+
).catch(() => {});
|
|
1612
|
+
} else if (platform.kind === "wechat") {
|
|
1613
|
+
const lines = [currentEffort ? `当前 effort (${toolLabel}): ${currentEffort}` : `当前 effort (${toolLabel}): 未指定`];
|
|
1614
|
+
lines.push("", "可切换 effort:");
|
|
1615
|
+
for (const e of efforts) lines.push(` ${e}`);
|
|
1616
|
+
lines.push("", "输入 /effort <effort> 切换 effort");
|
|
1617
|
+
await platform.sendText(chatId, lines.join("\n")).catch(() => {});
|
|
1618
|
+
} else {
|
|
1619
|
+
const card = buildEffortCard(currentEffort, efforts, descriptionTool);
|
|
1620
|
+
await platform.sendRawCard(chatId, card);
|
|
1621
|
+
}
|
|
1622
|
+
logTrace(tid, "DONE", { outcome: "effort_query", tool: descriptionTool });
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
if (isCommandText && (textLower.startsWith("/git ") || textLower === "/git")) {
|
|
1627
1627
|
const args = text === "/git" ? "" : text.slice(5).trim();
|
|
1628
1628
|
logTrace(tid, "BRANCH", { cmd: "/git", args: args || "(none)" });
|
|
1629
1629
|
if (!args) {
|
|
@@ -1781,45 +1781,45 @@ export async function handleCommand(
|
|
|
1781
1781
|
}
|
|
1782
1782
|
|
|
1783
1783
|
// 无会话上下文 → /sessions 仍是有效指令,不触发飞书私聊自动建群。
|
|
1784
|
-
if (isCommandText && textLower === "/effort") {
|
|
1785
|
-
const defaultTool = resolveDefaultAgentTool();
|
|
1786
|
-
const efforts = getAllEffortsForTool(defaultTool);
|
|
1787
|
-
const currentEffort = getDefaultEffortForTool(defaultTool);
|
|
1788
|
-
const toolLabel = toolDisplayName(defaultTool);
|
|
1789
|
-
|
|
1790
|
-
if (efforts.length === 0) {
|
|
1791
|
-
const msg = `当前默认 agent (${toolLabel}) 不支持 effort 切换。`;
|
|
1792
|
-
await (platform.kind === "wechat"
|
|
1793
|
-
? platform.sendText(chatId, msg)
|
|
1794
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1795
|
-
).catch(() => {});
|
|
1796
|
-
} else if (platform.kind === "wechat") {
|
|
1797
|
-
const lines = [currentEffort ? `当前默认 effort (${toolLabel}): ${currentEffort}` : `当前默认 effort (${toolLabel}): 未指定`];
|
|
1798
|
-
lines.push("", "可切换 effort:");
|
|
1799
|
-
for (const e of efforts) lines.push(` ${e}`);
|
|
1800
|
-
lines.push("", "在会话中输入 /effort <effort> 切换 effort");
|
|
1801
|
-
await platform.sendText(chatId, lines.join("\n")).catch(() => {});
|
|
1802
|
-
} else {
|
|
1803
|
-
const card = buildEffortCard(currentEffort, efforts, defaultTool);
|
|
1804
|
-
await platform.sendRawCard(chatId, card);
|
|
1805
|
-
}
|
|
1806
|
-
logTrace(tid, "DONE", { outcome: "effort_query", defaultTool });
|
|
1807
|
-
return;
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
if (isCommandText && textLower.startsWith("/effort ")) {
|
|
1811
|
-
const defaultTool = resolveDefaultAgentTool();
|
|
1812
|
-
const toolLabel = toolDisplayName(defaultTool);
|
|
1813
|
-
const msg = `当前没有绑定会话。请先进入 Claude/Codex 会话,再输入 /effort <effort> 切换当前会话的 effort。当前默认 agent: ${toolLabel}`;
|
|
1814
|
-
await (platform.kind === "wechat"
|
|
1815
|
-
? platform.sendText(chatId, msg)
|
|
1816
|
-
: platform.sendCard(chatId, "Effort 切换", msg, "yellow")
|
|
1817
|
-
).catch(() => {});
|
|
1818
|
-
logTrace(tid, "DONE", { outcome: "effort_no_session", defaultTool });
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
if (isCommandText && textLower === "/sessions") {
|
|
1784
|
+
if (isCommandText && textLower === "/effort") {
|
|
1785
|
+
const defaultTool = resolveDefaultAgentTool();
|
|
1786
|
+
const efforts = getAllEffortsForTool(defaultTool);
|
|
1787
|
+
const currentEffort = getDefaultEffortForTool(defaultTool);
|
|
1788
|
+
const toolLabel = toolDisplayName(defaultTool);
|
|
1789
|
+
|
|
1790
|
+
if (efforts.length === 0) {
|
|
1791
|
+
const msg = `当前默认 agent (${toolLabel}) 不支持 effort 切换。`;
|
|
1792
|
+
await (platform.kind === "wechat"
|
|
1793
|
+
? platform.sendText(chatId, msg)
|
|
1794
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "red")
|
|
1795
|
+
).catch(() => {});
|
|
1796
|
+
} else if (platform.kind === "wechat") {
|
|
1797
|
+
const lines = [currentEffort ? `当前默认 effort (${toolLabel}): ${currentEffort}` : `当前默认 effort (${toolLabel}): 未指定`];
|
|
1798
|
+
lines.push("", "可切换 effort:");
|
|
1799
|
+
for (const e of efforts) lines.push(` ${e}`);
|
|
1800
|
+
lines.push("", "在会话中输入 /effort <effort> 切换 effort");
|
|
1801
|
+
await platform.sendText(chatId, lines.join("\n")).catch(() => {});
|
|
1802
|
+
} else {
|
|
1803
|
+
const card = buildEffortCard(currentEffort, efforts, defaultTool);
|
|
1804
|
+
await platform.sendRawCard(chatId, card);
|
|
1805
|
+
}
|
|
1806
|
+
logTrace(tid, "DONE", { outcome: "effort_query", defaultTool });
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
if (isCommandText && textLower.startsWith("/effort ")) {
|
|
1811
|
+
const defaultTool = resolveDefaultAgentTool();
|
|
1812
|
+
const toolLabel = toolDisplayName(defaultTool);
|
|
1813
|
+
const msg = `当前没有绑定会话。请先进入 Claude/Codex 会话,再输入 /effort <effort> 切换当前会话的 effort。当前默认 agent: ${toolLabel}`;
|
|
1814
|
+
await (platform.kind === "wechat"
|
|
1815
|
+
? platform.sendText(chatId, msg)
|
|
1816
|
+
: platform.sendCard(chatId, "Effort 切换", msg, "yellow")
|
|
1817
|
+
).catch(() => {});
|
|
1818
|
+
logTrace(tid, "DONE", { outcome: "effort_no_session", defaultTool });
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
if (isCommandText && textLower === "/sessions") {
|
|
1823
1823
|
logTrace(tid, "BRANCH", { cmd: "/sessions", scope: "global" });
|
|
1824
1824
|
const allSessions = await getAllSessionsStatus();
|
|
1825
1825
|
const now = Date.now();
|
package/src/privacy.ts
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { USER_DATA_DIR } from "./config.ts";
|
|
4
|
-
|
|
5
|
-
interface PrivacyRules {
|
|
6
|
-
[key: string]: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface PrivacyConfig {
|
|
10
|
-
enabled: boolean;
|
|
11
|
-
rules: PrivacyRules;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
let config: PrivacyConfig | null = null;
|
|
15
|
-
let loadedStamp: string | null | undefined;
|
|
16
|
-
|
|
17
|
-
function privacyFilePath(): string {
|
|
18
|
-
return join(USER_DATA_DIR, "privacy.json");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function privacyFileStamp(): string | null {
|
|
22
|
-
const filePath = privacyFilePath();
|
|
23
|
-
if (!existsSync(filePath)) return null;
|
|
24
|
-
try {
|
|
25
|
-
const s = statSync(filePath);
|
|
26
|
-
return `${s.mtimeMs}:${s.size}`;
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function sanitizeRules(raw: Record<string, unknown>): PrivacyRules {
|
|
33
|
-
const result: PrivacyRules = {};
|
|
34
|
-
for (const [from, to] of Object.entries(raw)) {
|
|
35
|
-
if (!from) {
|
|
36
|
-
console.error("[PRIVACY] privacy.json rule key cannot be empty, skipped");
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (typeof to !== "string") {
|
|
40
|
-
console.error(`[PRIVACY] privacy.json value must be a string, skipped "${from}"`);
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
result[from] = to;
|
|
44
|
-
}
|
|
45
|
-
return result;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function normalizeConfig(parsed: Record<string, unknown>): PrivacyConfig {
|
|
49
|
-
const hasNewSchema = Object.prototype.hasOwnProperty.call(parsed, "enabled") ||
|
|
50
|
-
Object.prototype.hasOwnProperty.call(parsed, "rules");
|
|
51
|
-
|
|
52
|
-
if (!hasNewSchema) {
|
|
53
|
-
return { enabled: true, rules: sanitizeRules(parsed) };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const enabledRaw = parsed.enabled;
|
|
57
|
-
const enabled = enabledRaw === undefined ? true : enabledRaw !== false;
|
|
58
|
-
if (enabledRaw !== undefined && typeof enabledRaw !== "boolean") {
|
|
59
|
-
console.error("[PRIVACY] privacy.json enabled must be a boolean, using true");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const rulesRaw = parsed.rules;
|
|
63
|
-
if (rulesRaw === undefined) return { enabled, rules: {} };
|
|
64
|
-
if (typeof rulesRaw !== "object" || rulesRaw === null || Array.isArray(rulesRaw)) {
|
|
65
|
-
console.error("[PRIVACY] privacy.json rules must be an object");
|
|
66
|
-
return { enabled, rules: {} };
|
|
67
|
-
}
|
|
68
|
-
return { enabled, rules: sanitizeRules(rulesRaw as Record<string, unknown>) };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function loadConfig(): PrivacyConfig {
|
|
72
|
-
const filePath = privacyFilePath();
|
|
73
|
-
if (!existsSync(filePath)) {
|
|
74
|
-
return { enabled: true, rules: {} };
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
const raw = readFileSync(filePath, "utf-8").replace(/^\uFEFF/, "");
|
|
78
|
-
const parsed = JSON.parse(raw);
|
|
79
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
80
|
-
console.error("[PRIVACY] privacy.json must be an object");
|
|
81
|
-
return { enabled: true, rules: {} };
|
|
82
|
-
}
|
|
83
|
-
return normalizeConfig(parsed as Record<string, unknown>);
|
|
84
|
-
} catch (err) {
|
|
85
|
-
console.error(`[PRIVACY] failed to read privacy.json: ${(err as Error).message}`);
|
|
86
|
-
return { enabled: true, rules: {} };
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function getPrivacyConfig(): PrivacyConfig {
|
|
91
|
-
const stamp = privacyFileStamp();
|
|
92
|
-
if (!config || stamp !== loadedStamp) {
|
|
93
|
-
config = loadConfig();
|
|
94
|
-
loadedStamp = stamp;
|
|
95
|
-
}
|
|
96
|
-
return config;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function getPrivacyRules(): PrivacyRules {
|
|
100
|
-
return getPrivacyConfig().rules;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function reloadPrivacyRules(): void {
|
|
104
|
-
config = null;
|
|
105
|
-
loadedStamp = undefined;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function applyPrivacy(text: string): string {
|
|
109
|
-
const { enabled, rules } = getPrivacyConfig();
|
|
110
|
-
if (!enabled || Object.keys(rules).length === 0 || !text) return text;
|
|
111
|
-
|
|
112
|
-
let result = text;
|
|
113
|
-
for (const [from, to] of Object.entries(rules)) {
|
|
114
|
-
// Use split+join instead of regex replacement so rule keys stay literal.
|
|
115
|
-
result = result.split(from).join(to);
|
|
116
|
-
}
|
|
117
|
-
return result;
|
|
118
|
-
}
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { USER_DATA_DIR } from "./config.ts";
|
|
4
|
+
|
|
5
|
+
interface PrivacyRules {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface PrivacyConfig {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
rules: PrivacyRules;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let config: PrivacyConfig | null = null;
|
|
15
|
+
let loadedStamp: string | null | undefined;
|
|
16
|
+
|
|
17
|
+
function privacyFilePath(): string {
|
|
18
|
+
return join(USER_DATA_DIR, "privacy.json");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function privacyFileStamp(): string | null {
|
|
22
|
+
const filePath = privacyFilePath();
|
|
23
|
+
if (!existsSync(filePath)) return null;
|
|
24
|
+
try {
|
|
25
|
+
const s = statSync(filePath);
|
|
26
|
+
return `${s.mtimeMs}:${s.size}`;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function sanitizeRules(raw: Record<string, unknown>): PrivacyRules {
|
|
33
|
+
const result: PrivacyRules = {};
|
|
34
|
+
for (const [from, to] of Object.entries(raw)) {
|
|
35
|
+
if (!from) {
|
|
36
|
+
console.error("[PRIVACY] privacy.json rule key cannot be empty, skipped");
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (typeof to !== "string") {
|
|
40
|
+
console.error(`[PRIVACY] privacy.json value must be a string, skipped "${from}"`);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
result[from] = to;
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeConfig(parsed: Record<string, unknown>): PrivacyConfig {
|
|
49
|
+
const hasNewSchema = Object.prototype.hasOwnProperty.call(parsed, "enabled") ||
|
|
50
|
+
Object.prototype.hasOwnProperty.call(parsed, "rules");
|
|
51
|
+
|
|
52
|
+
if (!hasNewSchema) {
|
|
53
|
+
return { enabled: true, rules: sanitizeRules(parsed) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const enabledRaw = parsed.enabled;
|
|
57
|
+
const enabled = enabledRaw === undefined ? true : enabledRaw !== false;
|
|
58
|
+
if (enabledRaw !== undefined && typeof enabledRaw !== "boolean") {
|
|
59
|
+
console.error("[PRIVACY] privacy.json enabled must be a boolean, using true");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const rulesRaw = parsed.rules;
|
|
63
|
+
if (rulesRaw === undefined) return { enabled, rules: {} };
|
|
64
|
+
if (typeof rulesRaw !== "object" || rulesRaw === null || Array.isArray(rulesRaw)) {
|
|
65
|
+
console.error("[PRIVACY] privacy.json rules must be an object");
|
|
66
|
+
return { enabled, rules: {} };
|
|
67
|
+
}
|
|
68
|
+
return { enabled, rules: sanitizeRules(rulesRaw as Record<string, unknown>) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function loadConfig(): PrivacyConfig {
|
|
72
|
+
const filePath = privacyFilePath();
|
|
73
|
+
if (!existsSync(filePath)) {
|
|
74
|
+
return { enabled: true, rules: {} };
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const raw = readFileSync(filePath, "utf-8").replace(/^\uFEFF/, "");
|
|
78
|
+
const parsed = JSON.parse(raw);
|
|
79
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
80
|
+
console.error("[PRIVACY] privacy.json must be an object");
|
|
81
|
+
return { enabled: true, rules: {} };
|
|
82
|
+
}
|
|
83
|
+
return normalizeConfig(parsed as Record<string, unknown>);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error(`[PRIVACY] failed to read privacy.json: ${(err as Error).message}`);
|
|
86
|
+
return { enabled: true, rules: {} };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getPrivacyConfig(): PrivacyConfig {
|
|
91
|
+
const stamp = privacyFileStamp();
|
|
92
|
+
if (!config || stamp !== loadedStamp) {
|
|
93
|
+
config = loadConfig();
|
|
94
|
+
loadedStamp = stamp;
|
|
95
|
+
}
|
|
96
|
+
return config;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getPrivacyRules(): PrivacyRules {
|
|
100
|
+
return getPrivacyConfig().rules;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function reloadPrivacyRules(): void {
|
|
104
|
+
config = null;
|
|
105
|
+
loadedStamp = undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function applyPrivacy(text: string): string {
|
|
109
|
+
const { enabled, rules } = getPrivacyConfig();
|
|
110
|
+
if (!enabled || Object.keys(rules).length === 0 || !text) return text;
|
|
111
|
+
|
|
112
|
+
let result = text;
|
|
113
|
+
for (const [from, to] of Object.entries(rules)) {
|
|
114
|
+
// Use split+join instead of regex replacement so rule keys stay literal.
|
|
115
|
+
result = result.split(from).join(to);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
@@ -71,11 +71,11 @@ export interface ActivePrompt {
|
|
|
71
71
|
/** Set when the watchdog detects that the CLI process disappeared before stream finalization. */
|
|
72
72
|
abnormalExit?: boolean;
|
|
73
73
|
abnormalExitNotified?: boolean;
|
|
74
|
-
/** Set when the resource monitor detects CPU + memory unchanged for 3 minutes. */
|
|
75
|
-
resourceStuck?: boolean;
|
|
76
|
-
/** Adapter-provided callback to close the underlying SDK session / subprocess.
|
|
77
|
-
* Called by stop-stuck-loop before controller.abort() to terminate the CLI
|
|
78
|
-
* process immediately, rather than waiting for the async generator to unblock. */
|
|
74
|
+
/** Set when the resource monitor detects CPU + memory unchanged for 3 minutes. */
|
|
75
|
+
resourceStuck?: boolean;
|
|
76
|
+
/** Adapter-provided callback to close the underlying SDK session / subprocess.
|
|
77
|
+
* Called by stop-stuck-loop before controller.abort() to terminate the CLI
|
|
78
|
+
* process immediately, rather than waiting for the async generator to unblock. */
|
|
79
79
|
closeSession?: () => void;
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -228,4 +228,4 @@ export function resetBindingState(): void {
|
|
|
228
228
|
clearInterval(unifiedDisplayLoopHandle);
|
|
229
229
|
unifiedDisplayLoopHandle = null;
|
|
230
230
|
}
|
|
231
|
-
}
|
|
231
|
+
}
|
package/src/session-name.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export function cwdDisplayName(cwd: string): string {
|
|
2
|
-
const trimmed = cwd.trim().replace(/[\\/]+$/, "");
|
|
3
|
-
return trimmed.split(/[\\/]/).filter(Boolean).pop() || trimmed || "cwd";
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function sessionChatName(left: string, cwd: string): string {
|
|
7
|
-
return `${left}-${cwdDisplayName(cwd)}`;
|
|
8
|
-
}
|
|
1
|
+
export function cwdDisplayName(cwd: string): string {
|
|
2
|
+
const trimmed = cwd.trim().replace(/[\\/]+$/, "");
|
|
3
|
+
return trimmed.split(/[\\/]/).filter(Boolean).pop() || trimmed || "cwd";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function sessionChatName(left: string, cwd: string): string {
|
|
7
|
+
return `${left}-${cwdDisplayName(cwd)}`;
|
|
8
|
+
}
|