chatccc 0.2.270 → 0.2.276

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.
Files changed (42) hide show
  1. package/README.md +16 -10
  2. package/config.sample.json +4 -3
  3. package/deepccc-agent/README.md +147 -61
  4. package/deepccc-agent/package.json +5 -2
  5. package/dist/deepccc-agent/src/attachments.js +192 -0
  6. package/dist/deepccc-agent/src/cli.js +59 -13
  7. package/dist/deepccc-agent/src/config.js +57 -4
  8. package/dist/deepccc-agent/src/context.js +299 -16
  9. package/dist/deepccc-agent/src/file-tools.js +33 -0
  10. package/dist/deepccc-agent/src/index.js +68 -21
  11. package/dist/deepccc-agent/src/tool-protocol.js +14 -3
  12. package/dist/deepccc-agent/src/web-entry.js +72 -0
  13. package/dist/deepccc-agent/src/web-page.js +414 -0
  14. package/dist/deepccc-agent/src/web-runtime.js +331 -0
  15. package/dist/deepccc-agent/src/web-server.js +476 -0
  16. package/dist/deepccc-agent/src/web-session-store.js +162 -0
  17. package/dist/deepccc-agent/src/web-tool-presentation.js +123 -0
  18. package/dist/src/adapters/ccc-adapter.js +5 -1
  19. package/dist/src/agent-capability-grants.js +26 -0
  20. package/dist/src/agent-delegate-task.js +5 -2
  21. package/dist/src/agent-file-rpc.js +6 -1
  22. package/dist/src/agent-image-rpc.js +6 -1
  23. package/dist/src/agent-team/application/task-execution-service.js +330 -97
  24. package/dist/src/agent-team/domain/task-run.js +14 -1
  25. package/dist/src/agent-team/infrastructure/task-execution-runtime.js +7 -2
  26. package/dist/src/agent-team/main-agent-bootstrap.js +24 -1
  27. package/dist/src/agent-team/repositories/json-task-run-repository.js +22 -4
  28. package/dist/src/agent-team/web/agent-team-page.js +14 -7
  29. package/dist/src/cards.js +7 -4
  30. package/dist/src/config.js +12 -0
  31. package/dist/src/im-skills.js +9 -2
  32. package/dist/src/orchestrator.js +117 -29
  33. package/dist/src/safe-maintenance.js +4 -1
  34. package/dist/src/session-name.js +15 -0
  35. package/dist/src/session.js +54 -9
  36. package/dist/src/web-ui.js +76 -32
  37. package/im-skills/feishu-skill/receive-send-file.md +3 -2
  38. package/im-skills/feishu-skill/receive-send-image.md +3 -2
  39. package/im-skills/feishu-skill/send-file.mjs +6 -5
  40. package/im-skills/feishu-skill/send-image.mjs +6 -5
  41. package/im-skills/feishu-skill/skill.md +4 -2
  42. package/package.json +1 -1
@@ -0,0 +1,123 @@
1
+ export const WEB_TOOL_SUMMARY_RULES = {
2
+ read_file: { emoji: "📖", inputFields: ["path"] },
3
+ list_dir: { emoji: "📂", inputFields: ["path"] },
4
+ search_code: { emoji: "🔎", inputFields: ["query", "path"] },
5
+ run_command: { emoji: "🖥️", inputFields: ["command"] },
6
+ edit_file: { emoji: "✏️", inputFields: ["path"] },
7
+ create_file: { emoji: "✍️", inputFields: ["path"] },
8
+ delete_file: { emoji: "🗑️", inputFields: ["path"] },
9
+ move_file: { emoji: "📦", inputFields: ["sourcePath", "destinationPath"] },
10
+ apply_patch: { emoji: "📋", inputFields: [] },
11
+ task: { emoji: "🤖", inputFields: ["description", "cwd"] },
12
+ websearch: { emoji: "🌐", inputFields: ["query"] },
13
+ webfetch: { emoji: "📥", inputFields: ["url"] },
14
+ session_search: { emoji: "🗂️", inputFields: ["query", "session_id"] },
15
+ present_file: { emoji: "🖼️", inputFields: ["path"] },
16
+ };
17
+ export function buildWebToolSummary(call) {
18
+ const rule = WEB_TOOL_SUMMARY_RULES[call.name] ?? { emoji: "🔧", inputFields: [] };
19
+ const mark = call.pending ? "…" : call.isError ? "✗" : "✓";
20
+ const input = asRecord(call.input);
21
+ const details = rule.inputFields
22
+ .map((field) => oneLine(input[field]))
23
+ .filter(Boolean);
24
+ const result = call.pending ? "" : resultSummary(call.name, call.output, !!call.isError);
25
+ return [rule.emoji, call.name || "tool", mark, [...details, result].filter(Boolean).join(" · ")]
26
+ .filter(Boolean)
27
+ .join(" ")
28
+ .slice(0, 420);
29
+ }
30
+ export function truncateToolPayload(value, headLines, tailLines, maxLineChars = 240) {
31
+ const full = formatToolPayload(value);
32
+ const lines = full.split("\n");
33
+ const keep = Math.max(0, headLines) + Math.max(0, tailLines);
34
+ const omittedLines = Math.max(0, lines.length - keep);
35
+ const selected = omittedLines > 0
36
+ ? [
37
+ ...lines.slice(0, Math.max(0, headLines)),
38
+ `… 已省略 ${omittedLines} 行`,
39
+ ...lines.slice(-Math.max(0, tailLines)),
40
+ ]
41
+ : lines;
42
+ let clipped = false;
43
+ const preview = selected.map((line) => {
44
+ if (line.length <= maxLineChars)
45
+ return line;
46
+ clipped = true;
47
+ return `${line.slice(0, Math.max(0, maxLineChars - 1))}…`;
48
+ }).join("\n");
49
+ return {
50
+ full,
51
+ preview,
52
+ omittedLines,
53
+ truncated: omittedLines > 0 || clipped,
54
+ };
55
+ }
56
+ export function formatToolPayload(value) {
57
+ if (value === undefined)
58
+ return "";
59
+ if (value === null)
60
+ return "null";
61
+ if (typeof value === "string") {
62
+ const parsed = tryJson(value);
63
+ if (parsed !== undefined)
64
+ return JSON.stringify(parsed, null, 2);
65
+ return value;
66
+ }
67
+ try {
68
+ return JSON.stringify(value, null, 2);
69
+ }
70
+ catch {
71
+ return String(value);
72
+ }
73
+ }
74
+ function resultSummary(name, raw, isError) {
75
+ const output = asRecord(raw);
76
+ if (isError)
77
+ return oneLine(output.message ?? raw) || "失败";
78
+ if (typeof output.exitCode === "number")
79
+ return `exit ${output.exitCode}`;
80
+ if (Array.isArray(output.entries))
81
+ return `${output.entries.length} 项`;
82
+ if (Array.isArray(output.matches))
83
+ return `${output.matches.length} 条匹配`;
84
+ if (Array.isArray(output.changedFiles))
85
+ return `${output.changedFiles.length} 个文件`;
86
+ if (Array.isArray(output.results))
87
+ return `${output.results.length} 条结果`;
88
+ if (typeof output.size === "number")
89
+ return formatBytes(output.size);
90
+ if (name === "task" && typeof output.result === "string")
91
+ return "已返回结果";
92
+ return "完成";
93
+ }
94
+ function asRecord(value) {
95
+ const parsed = typeof value === "string" ? tryJson(value) : value;
96
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
97
+ ? parsed
98
+ : {};
99
+ }
100
+ function tryJson(value) {
101
+ const trimmed = value.trim();
102
+ if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("[")))
103
+ return undefined;
104
+ try {
105
+ return JSON.parse(trimmed);
106
+ }
107
+ catch {
108
+ return undefined;
109
+ }
110
+ }
111
+ function oneLine(value) {
112
+ if (value === undefined || value === null)
113
+ return "";
114
+ const text = typeof value === "string" ? value : JSON.stringify(value);
115
+ return text.replace(/\s+/g, " ").trim().slice(0, 220);
116
+ }
117
+ function formatBytes(value) {
118
+ if (value < 1024)
119
+ return `${value} B`;
120
+ if (value < 1024 * 1024)
121
+ return `${(value / 1024).toFixed(1)} KB`;
122
+ return `${(value / 1024 / 1024).toFixed(1)} MB`;
123
+ }
@@ -10,6 +10,7 @@ function toChatSessionOptions(sessionId, cwd, options) {
10
10
  contextDir: options.contextDir,
11
11
  contextWindow: options.contextWindow,
12
12
  compactAtTokens: options.compactAtTokens,
13
+ maxToolContextTokens: options.maxToolContextTokens,
13
14
  keepRecentMessages: options.keepRecentMessages,
14
15
  compactionTimeoutMs: options.compactionTimeoutMs,
15
16
  maxSteps: options.maxSteps,
@@ -29,7 +30,10 @@ export function createCccAdapter(options = {}) {
29
30
  ...(options.baseURL !== undefined ? { baseURL: options.baseURL } : {}),
30
31
  ...(options.model !== undefined ? { model: options.model } : {}),
31
32
  ...(options.subModel !== undefined ? { subModel: options.subModel } : {}),
32
- ...(options.effort !== undefined ? { effort: options.effort } : {}),
33
+ ...(options.effort?.trim() ? { effort: options.effort.trim() } : {}),
34
+ ...(options.maxOutputTokens !== undefined
35
+ ? { maxOutputTokens: options.maxOutputTokens }
36
+ : {}),
33
37
  };
34
38
  return {
35
39
  displayName: "CCC Agent",
@@ -0,0 +1,26 @@
1
+ import { randomBytes, timingSafeEqual } from "node:crypto";
2
+ const grantsBySession = new Map();
3
+ export function issueAgentCapabilityGrant(sessionId) {
4
+ if (!sessionId)
5
+ throw new Error("sessionId is required for an agent capability grant");
6
+ const existing = grantsBySession.get(sessionId);
7
+ if (existing !== undefined)
8
+ return existing;
9
+ const grant = randomBytes(32).toString("base64url");
10
+ grantsBySession.set(sessionId, grant);
11
+ return grant;
12
+ }
13
+ export function validateAgentCapabilityGrant(sessionId, candidate) {
14
+ if (!sessionId || typeof candidate !== "string" || candidate.length === 0)
15
+ return false;
16
+ const expected = grantsBySession.get(sessionId);
17
+ if (expected === undefined)
18
+ return false;
19
+ const expectedBytes = Buffer.from(expected, "utf8");
20
+ const candidateBytes = Buffer.from(candidate, "utf8");
21
+ return expectedBytes.length === candidateBytes.length
22
+ && timingSafeEqual(expectedBytes, candidateBytes);
23
+ }
24
+ export function clearAgentCapabilityGrants() {
25
+ grantsBySession.clear();
26
+ }
@@ -1,9 +1,9 @@
1
1
  import { resolve } from "node:path";
2
2
  import { sessionPrefixForTool, toolDisplayName, ts } from "./config.js";
3
3
  import { setDefaultCwd } from "./config.js";
4
- import { getEffectiveFastModeForTool, initClaudeSession, recordSessionRegistry, resumeAndPrompt, saveSessionTool, } from "./session.js";
4
+ import { getEffectiveFastModeForTool, initClaudeSession, recordSessionRegistry, resumeAndPrompt, saveSessionTool, saveSessionPresentation, } from "./session.js";
5
5
  import { bindChatToSession } from "./session-chat-binding.js";
6
- import { sessionChatName } from "./session-name.js";
6
+ import { sessionChatName, sessionDisplayTitleFromPrompt } 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);
@@ -12,6 +12,7 @@ export async function delegateAgentTask(input) {
12
12
  const sessionId = init.sessionId;
13
13
  const chatNamePrefix = input.chatNamePrefix?.trim() || (hasPrompt ? input.promptText.slice(0, 10) : "新会话");
14
14
  const chatName = sessionChatName(chatNamePrefix, cwd);
15
+ const displayTitle = hasPrompt ? sessionDisplayTitleFromPrompt(input.promptText) : "新会话";
15
16
  let chatId;
16
17
  try {
17
18
  chatId = await input.platform.createGroup(chatName, input.openIds);
@@ -24,11 +25,13 @@ export async function delegateAgentTask(input) {
24
25
  tool: input.tool,
25
26
  chatType: "group",
26
27
  chatName,
28
+ displayTitle,
27
29
  turnCount: 0,
28
30
  startTime: Date.now(),
29
31
  running: false,
30
32
  });
31
33
  await saveSessionTool(sessionId, input.tool, chatName);
34
+ await saveSessionPresentation(sessionId, { displayTitle });
32
35
  }
33
36
  catch (err) {
34
37
  console.error(`[${ts()}] [AGENT-DELEGATE-TASK] create group failed: ${err.message}`);
@@ -6,6 +6,7 @@ import { readUtf8JsonBody } from "./agent-rpc-body.js";
6
6
  import { getAdapterForTool } from "./session.js";
7
7
  import { getChatsForSession } from "./session-chat-binding.js";
8
8
  import { splitFeishuTargetChats } from "./agent-platform-routing.js";
9
+ import { validateAgentCapabilityGrant } from "./agent-capability-grants.js";
9
10
  export const AGENT_SEND_FILE_PATH = "/api/agent/send-file";
10
11
  const MAX_REQUEST_BYTES = 64 * 1024;
11
12
  const MAX_FILE_BYTES = 100 * 1024 * 1024;
@@ -61,6 +62,10 @@ export async function handleAgentFileRequest(req, res) {
61
62
  jsonReply(res, 400, { ok: false, error: "Missing session_id" });
62
63
  return true;
63
64
  }
65
+ if (!validateAgentCapabilityGrant(sessionId, payload.grant)) {
66
+ jsonReply(res, 403, { ok: false, error: "Invalid or expired agent capability grant" });
67
+ return true;
68
+ }
64
69
  let cwd;
65
70
  try {
66
71
  const { getSessionTool } = await import("./session.js");
@@ -135,7 +140,7 @@ export function buildAgentFileCapabilityPrompt(input) {
135
140
  `POST ${input.url}`,
136
141
  "Content-Type: application/json; charset=utf-8",
137
142
  "",
138
- `Body: {"session_id":"${input.sessionId ?? "YOUR_SESSION_ID"}","path":"absolute file path","caption":"optional caption"}`,
143
+ `Body: {"session_id":"${input.sessionId ?? "YOUR_SESSION_ID"}","grant":"${input.grant ?? "YOUR_SESSION_GRANT"}","path":"absolute file path","caption":"optional caption"}`,
139
144
  "",
140
145
  "Rules:",
141
146
  "- Save or choose a local file first, then call the endpoint.",
@@ -6,6 +6,7 @@ import { readUtf8JsonBody } from "./agent-rpc-body.js";
6
6
  import { getAdapterForTool } from "./session.js";
7
7
  import { getChatsForSession } from "./session-chat-binding.js";
8
8
  import { splitFeishuTargetChats } from "./agent-platform-routing.js";
9
+ import { validateAgentCapabilityGrant } from "./agent-capability-grants.js";
9
10
  export const AGENT_SEND_IMAGE_PATH = "/api/agent/send-image";
10
11
  const MAX_REQUEST_BYTES = 64 * 1024;
11
12
  const MAX_IMAGE_BYTES = 10 * 1024 * 1024;
@@ -56,6 +57,10 @@ export async function handleAgentImageRequest(req, res) {
56
57
  jsonReply(res, 400, { ok: false, error: "Missing session_id" });
57
58
  return true;
58
59
  }
60
+ if (!validateAgentCapabilityGrant(sessionId, payload.grant)) {
61
+ jsonReply(res, 403, { ok: false, error: "Invalid or expired agent capability grant" });
62
+ return true;
63
+ }
59
64
  // 获取 cwd 以校验路径
60
65
  let cwd;
61
66
  try {
@@ -132,7 +137,7 @@ export function buildAgentImageCapabilityPrompt(input) {
132
137
  `POST ${input.url}`,
133
138
  "Content-Type: application/json; charset=utf-8",
134
139
  "",
135
- `Body: {"session_id":"${input.sessionId ?? "YOUR_SESSION_ID"}","path":"absolute image file path","caption":"optional caption"}`,
140
+ `Body: {"session_id":"${input.sessionId ?? "YOUR_SESSION_ID"}","grant":"${input.grant ?? "YOUR_SESSION_GRANT"}","path":"absolute image file path","caption":"optional caption"}`,
136
141
  "",
137
142
  "Rules:",
138
143
  "- Save or choose a local image file first, then call the endpoint.",