chatccc 0.2.268 → 0.2.270
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/dist/src/agent-delegate-task-rpc.js +14 -12
- package/dist/src/agent-delegate-task.js +8 -3
- package/dist/src/agent-set-cwd-rpc.js +73 -0
- package/dist/src/cards.js +0 -3
- package/dist/src/im-skills.js +2 -2
- package/dist/src/index.js +3 -0
- package/dist/src/orchestrator.js +2 -2
- package/dist/src/session.js +6 -2
- package/im-skills/feishu-skill/skill.md +24 -10
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { resolveDefaultAgentTool } from "./config.js";
|
|
3
4
|
import { readUtf8JsonBody } from "./agent-rpc-body.js";
|
|
4
5
|
import { delegateAgentTask } from "./agent-delegate-task.js";
|
|
@@ -6,7 +7,7 @@ import { applySharedPrefix } from "./shared-prefix.js";
|
|
|
6
7
|
import { beginSafeMaintenanceTrackedWork, isSafeMaintenanceAdmissionClosed, } from "./safe-maintenance.js";
|
|
7
8
|
export const AGENT_DELEGATE_TASK_PATH = "/api/agent/delegate-task";
|
|
8
9
|
const MAX_REQUEST_BYTES = 128 * 1024;
|
|
9
|
-
const VALID_TOOLS = new Set(["claude", "cursor", "codex"]);
|
|
10
|
+
const VALID_TOOLS = new Set(["claude", "cursor", "codex", "ccc", "dsh"]);
|
|
10
11
|
function jsonReply(res, status, data) {
|
|
11
12
|
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
12
13
|
res.end(JSON.stringify(data));
|
|
@@ -38,7 +39,7 @@ function validateTool(rawTool) {
|
|
|
38
39
|
function validateCwd(rawCwd) {
|
|
39
40
|
const cwd = stringValue(rawCwd);
|
|
40
41
|
if (!cwd)
|
|
41
|
-
|
|
42
|
+
return homedir();
|
|
42
43
|
return resolve(cwd);
|
|
43
44
|
}
|
|
44
45
|
export async function handleAgentDelegateTaskRequest(req, res, platform) {
|
|
@@ -70,8 +71,6 @@ export async function handleAgentDelegateTaskRequest(req, res, platform) {
|
|
|
70
71
|
tool = validateTool(payload.tool);
|
|
71
72
|
cwd = validateCwd(payload.cwd);
|
|
72
73
|
const rawPrompt = promptFromPayload(payload);
|
|
73
|
-
if (!rawPrompt)
|
|
74
|
-
throw new Error("prompt must be a non-empty string");
|
|
75
74
|
const sharedPrefix = applySharedPrefix(rawPrompt);
|
|
76
75
|
promptText = sharedPrefix.text;
|
|
77
76
|
promptNamePrefix = sharedPrefix.body || rawPrompt;
|
|
@@ -117,24 +116,27 @@ export async function handleAgentDelegateTaskRequest(req, res, platform) {
|
|
|
117
116
|
}
|
|
118
117
|
export function buildAgentDelegateTaskCapabilityPrompt(input) {
|
|
119
118
|
const lines = [
|
|
120
|
-
"[ChatCCC local capability:
|
|
121
|
-
"You can create a separate Feishu ChatCCC agent session and assign its first task by calling this local endpoint.",
|
|
119
|
+
"[ChatCCC local capability: new session]",
|
|
120
|
+
"You can create a separate Feishu ChatCCC agent session (a new group) and optionally assign its first task by calling this local endpoint.",
|
|
122
121
|
"",
|
|
123
122
|
`POST ${input.url}`,
|
|
124
123
|
"Content-Type: application/json; charset=utf-8",
|
|
125
124
|
"",
|
|
126
|
-
'Body: {"tool":"codex|
|
|
125
|
+
'Body: {"tool":"claude|cursor|codex|ccc|dsh","cwd":"absolute working directory","open_id":"initiator open_id","prompt":"optional first task"}',
|
|
127
126
|
"",
|
|
128
127
|
"Rules:",
|
|
129
|
-
"- Use this
|
|
130
|
-
"- Pass
|
|
131
|
-
"- Pass
|
|
132
|
-
"-
|
|
128
|
+
"- Use this when the user asks to start a new conversation/session, optionally in a specific directory.",
|
|
129
|
+
"- Pass open_id exactly as provided to you; the new group will only include that requester.",
|
|
130
|
+
"- Pass cwd as an absolute local path. When the user does not name a directory, use your current working directory.",
|
|
131
|
+
"- tool is optional; omit it to use the default agent, or pass one of claude/cursor/codex/ccc/dsh when the user names a tool.",
|
|
132
|
+
"- prompt is optional; omit it to just create the session without sending a first task.",
|
|
133
133
|
"- The prompt is sent through the normal ChatCCC prompt path, so project prompt injection and IM skills still apply.",
|
|
134
134
|
"- Request body must be UTF-8 encoded JSON bytes. Do not call Feishu Open Platform directly.",
|
|
135
|
-
"[/ChatCCC local capability:
|
|
135
|
+
"[/ChatCCC local capability: new session]",
|
|
136
136
|
];
|
|
137
137
|
if (input.cwd)
|
|
138
138
|
lines.splice(2, 0, `Current working directory: ${input.cwd}`);
|
|
139
|
+
if (input.openId)
|
|
140
|
+
lines.splice(2, 0, `Initiator open_id: ${input.openId}`);
|
|
139
141
|
return lines.join("\n");
|
|
140
142
|
}
|
|
@@ -7,9 +7,10 @@ import { sessionChatName } from "./session-name.js";
|
|
|
7
7
|
export async function delegateAgentTask(input) {
|
|
8
8
|
const cwd = resolve(input.cwd);
|
|
9
9
|
const toolLabel = toolDisplayName(input.tool);
|
|
10
|
+
const hasPrompt = input.promptText.trim().length > 0;
|
|
10
11
|
const init = await initClaudeSession(input.tool, cwd);
|
|
11
12
|
const sessionId = init.sessionId;
|
|
12
|
-
const chatNamePrefix = input.chatNamePrefix?.trim() || input.promptText.slice(0, 10)
|
|
13
|
+
const chatNamePrefix = input.chatNamePrefix?.trim() || (hasPrompt ? input.promptText.slice(0, 10) : "新会话");
|
|
13
14
|
const chatName = sessionChatName(chatNamePrefix, cwd);
|
|
14
15
|
let chatId;
|
|
15
16
|
try {
|
|
@@ -36,13 +37,17 @@ export async function delegateAgentTask(input) {
|
|
|
36
37
|
await input.platform.sendCard(chatId, `${toolLabel} Session Ready`, `已创建 **${toolLabel}** 会话群。\n\n` +
|
|
37
38
|
`**Session ID:** ${sessionId}\n` +
|
|
38
39
|
`**工作目录:** \`${cwd}\`\n\n` +
|
|
39
|
-
|
|
40
|
+
(hasPrompt
|
|
41
|
+
? `下面会自动把任务作为第一句话发送给 ${toolLabel}。`
|
|
42
|
+
: `直接在这里发消息即可与 ${toolLabel} 对话。`), "green").catch(() => { });
|
|
40
43
|
const fastMode = getEffectiveFastModeForTool(input.tool, sessionId);
|
|
41
44
|
const avatarUpdate = fastMode
|
|
42
45
|
? input.platform.setChatAvatar(chatId, input.tool, "new", { fastMode: true })
|
|
43
46
|
: input.platform.setChatAvatar(chatId, input.tool, "new");
|
|
44
47
|
avatarUpdate.catch(() => { });
|
|
45
|
-
|
|
48
|
+
if (hasPrompt) {
|
|
49
|
+
await resumeAndPrompt(sessionId, input.promptText, input.platform, chatId, input.msgTimestamp ?? Date.now(), input.tool, input.traceId, input.openIds?.[0]);
|
|
50
|
+
}
|
|
46
51
|
console.log(`[${ts()}] [AGENT-DELEGATE-TASK] created ${toolLabel} session=${sessionId} chat=${chatId} cwd=${cwd}`);
|
|
47
52
|
return { chatId, sessionId, tool: input.tool, cwd };
|
|
48
53
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { addRecentDir, setDefaultCwd } from "./config.js";
|
|
4
|
+
import { readUtf8JsonBody } from "./agent-rpc-body.js";
|
|
5
|
+
import { getChatsForSession } from "./session-chat-binding.js";
|
|
6
|
+
export const AGENT_SET_CWD_PATH = "/api/agent/set-cwd";
|
|
7
|
+
const MAX_REQUEST_BYTES = 64 * 1024;
|
|
8
|
+
function jsonReply(res, status, data) {
|
|
9
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
10
|
+
res.end(JSON.stringify(data));
|
|
11
|
+
}
|
|
12
|
+
function stringValue(value) {
|
|
13
|
+
return typeof value === "string" ? value.trim() : "";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Agent 本地能力:设置后续新建会话的默认工作目录(等价于 /cd,不改变当前会话)。
|
|
17
|
+
* 请求携带发起者当前 session_id,主进程据此反查 chatId,并持久化到该 chat 的
|
|
18
|
+
* working_dir 文件,同时写入最近目录记录。
|
|
19
|
+
*/
|
|
20
|
+
export async function handleAgentSetCwdRequest(req, res) {
|
|
21
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
22
|
+
if (url.pathname !== AGENT_SET_CWD_PATH)
|
|
23
|
+
return false;
|
|
24
|
+
if (req.method !== "POST") {
|
|
25
|
+
jsonReply(res, 405, { ok: false, error: "Method not allowed" });
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
let payload;
|
|
29
|
+
try {
|
|
30
|
+
payload = await readUtf8JsonBody(req, MAX_REQUEST_BYTES);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
jsonReply(res, 400, { ok: false, error: err.message || "Invalid JSON" });
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const sessionId = stringValue(payload.session_id);
|
|
37
|
+
if (!sessionId) {
|
|
38
|
+
jsonReply(res, 400, { ok: false, error: "Missing session_id" });
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
const rawDir = stringValue(payload.dir) || stringValue(payload.path);
|
|
42
|
+
if (!rawDir) {
|
|
43
|
+
jsonReply(res, 400, { ok: false, error: "dir must be a non-empty string" });
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const dir = resolve(rawDir);
|
|
47
|
+
try {
|
|
48
|
+
const st = await stat(dir);
|
|
49
|
+
if (!st.isDirectory()) {
|
|
50
|
+
jsonReply(res, 400, { ok: false, error: `path is not a directory: ${dir}` });
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
jsonReply(res, 400, { ok: false, error: `directory does not exist: ${dir}` });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const chatIds = getChatsForSession(sessionId);
|
|
59
|
+
const chatId = chatIds[0];
|
|
60
|
+
if (!chatId) {
|
|
61
|
+
jsonReply(res, 404, { ok: false, error: "No chat bound to this session; cannot set working directory." });
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await setDefaultCwd(dir, chatId);
|
|
66
|
+
await addRecentDir(dir);
|
|
67
|
+
jsonReply(res, 200, { ok: true, dir, chat_id: chatId });
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
jsonReply(res, 500, { ok: false, error: err.message });
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
package/dist/src/cards.js
CHANGED
|
@@ -143,10 +143,7 @@ export function buildHelpCard(userText, opts = {}) {
|
|
|
143
143
|
"发送 **/ask** 以问答模式提问(只读,不执行写操作)",
|
|
144
144
|
"发送 **/usage** 查看当前 Agent 的用量或余额",
|
|
145
145
|
"发送 **/restart** 重启 ChatCCC 进程",
|
|
146
|
-
"发送 **/restart safe** 等待现有任务完成后安全重启",
|
|
147
146
|
"发送 **/update** 更新并重启(仅 npm 全局安装可用)",
|
|
148
|
-
"发送 **/update safe** 等待现有任务完成后安全更新",
|
|
149
|
-
"发送 **/safestatus** 查看安全维护状态,**/cancelsf** 取消等待中的预约",
|
|
150
147
|
ABD_HELP_LINE,
|
|
151
148
|
].join("\n");
|
|
152
149
|
return JSON.stringify({
|
package/dist/src/im-skills.js
CHANGED
|
@@ -56,8 +56,8 @@ function promptCacheKey(input) {
|
|
|
56
56
|
const names = input.enabledSkillNames
|
|
57
57
|
? [...input.enabledSkillNames].sort().join(",")
|
|
58
58
|
: "*";
|
|
59
|
-
const { session_id, cwd } = input.variables;
|
|
60
|
-
return `${input.skillsDir ?? DEFAULT_IM_SKILLS_DIR}|${names}|${session_id}|${cwd}`;
|
|
59
|
+
const { session_id, cwd, open_id } = input.variables;
|
|
60
|
+
return `${input.skillsDir ?? DEFAULT_IM_SKILLS_DIR}|${names}|${session_id}|${cwd}|${open_id}`;
|
|
61
61
|
}
|
|
62
62
|
/** 带会话级缓存的 buildImSkillsPrompt。同 session + 同 cwd 时直接返回缓存的渲染结果。 */
|
|
63
63
|
export async function buildImSkillsPromptCached(input) {
|
package/dist/src/index.js
CHANGED
|
@@ -40,6 +40,7 @@ import { handleAgentFileRequest } from "./agent-file-rpc.js";
|
|
|
40
40
|
import { handleAgentDelegateTaskRequest } from "./agent-delegate-task-rpc.js";
|
|
41
41
|
import { handleAgentStopStuckRequest } from "./agent-stop-stuck.js";
|
|
42
42
|
import { handleAgentReloadConfigRequest } from "./agent-reload-config-rpc.js";
|
|
43
|
+
import { handleAgentSetCwdRequest } from "./agent-set-cwd-rpc.js";
|
|
43
44
|
import { handleChatGptSubscriptionRequest } from "./chatgpt-subscription-rpc.js";
|
|
44
45
|
import { applyPrivacy } from "./privacy.js";
|
|
45
46
|
import { createCardKitCard, sendCardKitMessage, updateCardKitCard, } from "./cardkit.js";
|
|
@@ -561,6 +562,7 @@ async function main() {
|
|
|
561
562
|
if (injected)
|
|
562
563
|
return true;
|
|
563
564
|
return (await handleAgentReloadConfigRequest(req, res))
|
|
565
|
+
|| (await handleAgentSetCwdRequest(req, res))
|
|
564
566
|
|| (await handleAgentImageRequest(req, res))
|
|
565
567
|
|| (await handleAgentFileRequest(req, res))
|
|
566
568
|
|| (await handleAgentDelegateTaskRequest(req, res, feishuPlatform))
|
|
@@ -622,6 +624,7 @@ async function main() {
|
|
|
622
624
|
});
|
|
623
625
|
setExtraApiHandler(async (req, res) => {
|
|
624
626
|
return (await handleAgentReloadConfigRequest(req, res))
|
|
627
|
+
|| (await handleAgentSetCwdRequest(req, res))
|
|
625
628
|
|| (await handleAgentImageRequest(req, res))
|
|
626
629
|
|| (await handleAgentFileRequest(req, res))
|
|
627
630
|
|| (await handleAgentDelegateTaskRequest(req, res, feishuPlatform))
|
package/dist/src/orchestrator.js
CHANGED
|
@@ -2090,7 +2090,7 @@ async function handleCommandInternal(platform, text, chatId, openId, msgTimestam
|
|
|
2090
2090
|
}
|
|
2091
2091
|
try {
|
|
2092
2092
|
logTrace(tid, "RESUME", { sessionId, tool: descriptionTool });
|
|
2093
|
-
const resumeOutcome = await resumeAndPrompt(sessionId, promptText, platform, chatId, msgTimestamp, descriptionTool, tid);
|
|
2093
|
+
const resumeOutcome = await resumeAndPrompt(sessionId, promptText, platform, chatId, msgTimestamp, descriptionTool, tid, openId);
|
|
2094
2094
|
if (resumeOutcome === "error") {
|
|
2095
2095
|
logTrace(tid, "DONE", { outcome: "resume_error", sessionId });
|
|
2096
2096
|
console.error(`[${ts()}] [RESUME] Session ${sessionId} ended with an Agent error`);
|
|
@@ -2269,7 +2269,7 @@ async function handleCommandInternal(platform, text, chatId, openId, msgTimestam
|
|
|
2269
2269
|
if (!switchResult.ok) {
|
|
2270
2270
|
throw switchResult.error ?? new Error("Failed to bind Feishu private session");
|
|
2271
2271
|
}
|
|
2272
|
-
await resumeAndPrompt(sessionId, promptText, platform, chatId, msgTimestamp, tool, tid);
|
|
2272
|
+
await resumeAndPrompt(sessionId, promptText, platform, chatId, msgTimestamp, tool, tid, openId);
|
|
2273
2273
|
logTrace(tid, "DONE", {
|
|
2274
2274
|
outcome: "auto_new_feishu_p2p_prompt_done",
|
|
2275
2275
|
chatId,
|
package/dist/src/session.js
CHANGED
|
@@ -913,8 +913,10 @@ export async function initClaudeSession(tool, overrideCwd, chatId) {
|
|
|
913
913
|
await addRecentDir(cwd);
|
|
914
914
|
return { sessionId, cwd };
|
|
915
915
|
}
|
|
916
|
-
export async function resumeAndPrompt(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId) {
|
|
917
|
-
return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId
|
|
916
|
+
export async function resumeAndPrompt(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId, initiatorOpenId) {
|
|
917
|
+
return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId, {
|
|
918
|
+
initiatorOpenId,
|
|
919
|
+
});
|
|
918
920
|
}
|
|
919
921
|
export async function runAgentSession(sessionId, userText, platform, _chatId, msgTimestamp, tool, traceId, options = {}) {
|
|
920
922
|
const tid = traceId ?? "";
|
|
@@ -990,8 +992,10 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
|
|
|
990
992
|
const skillVariables = {
|
|
991
993
|
cwd,
|
|
992
994
|
session_id: sessionId,
|
|
995
|
+
open_id: options.initiatorOpenId,
|
|
993
996
|
im_skills_cache_dir: imSkillsCacheDir,
|
|
994
997
|
delegate_task_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/delegate-task`,
|
|
998
|
+
set_cwd_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/set-cwd`,
|
|
995
999
|
send_image_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-image`,
|
|
996
1000
|
send_file_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-file`,
|
|
997
1001
|
send_image_script: join(feishuSkillDir, "send-image.mjs"),
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: feishu-skill
|
|
3
|
-
description: Feishu IM local skills for sending
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Current working directory: {{cwd}}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
---
|
|
2
|
+
name: feishu-skill
|
|
3
|
+
description: Feishu IM local skills for sending images, files, videos, and for creating new sessions or switching working directories.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Current working directory: {{cwd}}
|
|
7
|
+
Your session id: {{session_id}}
|
|
8
|
+
Your Feishu open_id: {{open_id}}
|
|
9
|
+
|
|
10
|
+
Use local endpoints instead of calling Feishu Open Platform directly.
|
|
11
|
+
|
|
10
12
|
- **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
|
|
11
13
|
- **Send files/videos**: POST `{{send_file_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-file.md`
|
|
12
|
-
- **
|
|
14
|
+
- **Create a new session (新建会话)**: POST `{{delegate_task_url}}` with `{"tool":"claude|cursor|codex|ccc|dsh","cwd":"<absolute path>","open_id":"{{open_id}}","prompt":"<optional first task>"}`. This creates a new Feishu group and session, and only adds you (the requester). `tool` and `prompt` are optional; omit `prompt` to just create the session without a first task.
|
|
15
|
+
- **Set default working directory (cd / 切换目录)**: POST `{{set_cwd_url}}` with `{"session_id":"{{session_id}}","dir":"<absolute path>"}`. This sets the default directory for future new sessions only; it does not change the current session.
|
|
16
|
+
|
|
17
|
+
How to map user requests to these endpoints:
|
|
18
|
+
|
|
19
|
+
- "新建会话 / 开个新会话 / 换个新会话" → create a new session (no prompt). Use `cwd` = your current working directory ({{cwd}}) unless the user names a directory.
|
|
20
|
+
- "在 <目录> 新建会话(做 <任务>)" → create a new session with `cwd` = that directory, and set `prompt` to the task if one was given.
|
|
21
|
+
- "cd 到 <目录> / 切换到 <目录> / 去 <目录> 干活" → judge intent:
|
|
22
|
+
- if the user wants to start working there now (a fresh conversation in that directory) → create a new session with `cwd` = that directory.
|
|
23
|
+
- if the user only wants to change the default directory for future sessions → call set-cwd.
|
|
24
|
+
- when ambiguous, prefer creating a new session (the more common intent for "切换到").
|
|
25
|
+
- Directory names may be fuzzy or relative; resolve them to an absolute local path (using your file tools) before calling either endpoint.
|
|
26
|
+
- `open_id` must always be passed as exactly {{open_id}}; do not invent it.
|