chatccc 0.2.268 → 0.2.269
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/README.md +13 -13
- 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/agent-team/web/agent-team-page.js +243 -243
- 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 +18 -4
- package/package.json +76 -76
package/README.md
CHANGED
|
@@ -155,11 +155,11 @@ chatccc
|
|
|
155
155
|
|
|
156
156
|
#### 本地任务看板(Agent Team)
|
|
157
157
|
|
|
158
|
-
Agent Team 提供本地任务看板和项目主 Agent 入口。每个规范化后的本机目录对应一个独立项目和看板,固定包含“头脑风暴、Todo、Doing、Done、搁置”五列,支持新增、单击卡片编辑、拖动排序和删除任务。页面会自动保存,不需要手动提交。当前按路径识别项目,不检查 Git 仓库或 worktree 关系,任意子目录都可以作为独立项目。
|
|
158
|
+
Agent Team 提供本地任务看板和项目主 Agent 入口。每个规范化后的本机目录对应一个独立项目和看板,固定包含“头脑风暴、Todo、Doing、Done、搁置”五列,支持新增、单击卡片编辑、拖动排序和删除任务。页面会自动保存,不需要手动提交。当前按路径识别项目,不检查 Git 仓库或 worktree 关系,任意子目录都可以作为独立项目。
|
|
159
159
|
|
|
160
|
-
打开项目后可以选择 CCC、Claude、Cursor 或 Codex 作为主 Agent。首次设置会创建固定命名为 `主Agent-<目录短名>` 的飞书群和空 Agent Session;后续切换 Agent 或重新关联目录会复用原群,运行中的主 Agent 不允许切换。建群成员取自机器人最近一次收到的飞书私聊;如果尚无私聊记录,网页会提示先给机器人发送任意私聊消息并自动检测。项目群名不会随第一句话或 `/forget` 改变。
|
|
161
|
-
|
|
162
|
-
任务卡片可通过“交给主 Agent”启动真实执行:任务会从 Todo 移到 Doing,成功后自动移到 Done,失败则保留在 Doing 并显示错误,支持停止与重试。同一项目同一时间只运行一个看板任务;运行记录持久化在本地 JSON 中,服务异常退出后会把未完成执行标记为中断,避免错误显示为仍在运行。
|
|
160
|
+
打开项目后可以选择 CCC、Claude、Cursor 或 Codex 作为主 Agent。首次设置会创建固定命名为 `主Agent-<目录短名>` 的飞书群和空 Agent Session;后续切换 Agent 或重新关联目录会复用原群,运行中的主 Agent 不允许切换。建群成员取自机器人最近一次收到的飞书私聊;如果尚无私聊记录,网页会提示先给机器人发送任意私聊消息并自动检测。项目群名不会随第一句话或 `/forget` 改变。
|
|
161
|
+
|
|
162
|
+
任务卡片可通过“交给主 Agent”启动真实执行:任务会从 Todo 移到 Doing,成功后自动移到 Done,失败则保留在 Doing 并显示错误,支持停止与重试。同一项目同一时间只运行一个看板任务;运行记录持久化在本地 JSON 中,服务异常退出后会把未完成执行标记为中断,避免错误显示为仍在运行。
|
|
163
163
|
|
|
164
164
|
看板数据默认保存在 `~/.chatccc/agent-team/`,其中 `workspaces.json` 保存最近工作目录索引,`boards/` 保存按稳定 ID 分隔的看板 JSON,`main-agent-bindings/` 单独保存本机飞书群与 Session 绑定。目录移动或重命名后,可从最近目录列表重新关联。数据访问通过仓储接口隔离,后续接入飞书多维表格时可增加双向同步适配器,无需把本机群聊和 Session 状态混入任务同步模型。选择目录、创建群聊和启动 Agent 等操作都由本机 Node 后端执行;当前实现不依赖 Electron。
|
|
165
165
|
|
|
@@ -426,17 +426,17 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
426
426
|
| `/abd<内容>` | 去掉 `/abd` 前缀后把内容发给 Agent,并在消息末尾追加第一性原理需求澄清提示 |
|
|
427
427
|
| `/plan <内容>` | 只读计划模式:仅允许读文件和 stop-stuck-loop 请求,不执行任何写操作 |
|
|
428
428
|
| `/ask <内容>` | 只读问答模式:与 /plan 相同,仅允许读文件和 stop-stuck-loop 请求 |
|
|
429
|
-
| `/restart` | 重启机器人进程 |
|
|
430
|
-
| `/restart safe` | 停止接受新任务,等待现有会话、缓存消息和依赖安装完成后重启 |
|
|
431
|
-
| `/update` | 更新 npm 全局包并重启(仅限 `npm install -g chatccc` 安装的全局进程;同一飞书事件跨重启去重) |
|
|
432
|
-
| `/update safe` | 停止接受新任务,排空现有工作后更新并重启 |
|
|
433
|
-
| `/safestatus` | 查看安全重启/更新的等待状态 |
|
|
434
|
-
| `/cancelsf` | 取消尚未开始执行的安全重启/更新预约 |
|
|
429
|
+
| `/restart` | 重启机器人进程 |
|
|
430
|
+
| `/restart safe` | 停止接受新任务,等待现有会话、缓存消息和依赖安装完成后重启 |
|
|
431
|
+
| `/update` | 更新 npm 全局包并重启(仅限 `npm install -g chatccc` 安装的全局进程;同一飞书事件跨重启去重) |
|
|
432
|
+
| `/update safe` | 停止接受新任务,排空现有工作后更新并重启 |
|
|
433
|
+
| `/safestatus` | 查看安全重启/更新的等待状态 |
|
|
434
|
+
| `/cancelsf` | 取消尚未开始执行的安全重启/更新预约 |
|
|
435
435
|
| `/deleteg` | 解散当前飞书会话群;Agent 会话记录保留 |
|
|
436
436
|
|
|
437
|
-
`/update` 与 `/update safe` 会把飞书消息或按钮事件 ID 原子写入 `~/.chatccc/state/update-command-guard.json`。同一 ID 跨重启重投时会静默忽略;用户主动发送的新更新指令因事件 ID 不同,仍可执行。该保护不改变普通消息与重启指令的处理方式。
|
|
438
|
-
|
|
439
|
-
`/restart safe` 与 `/update safe` 会先建立全局准入门禁:指令到达前已经运行或进入单会话缓存队列的工作会继续完成,之后到达的新普通任务会被提示在维护完成后重发。维护任务持久化到 `~/.chatccc/state/safe-maintenance.json`,进程意外退出后可继续排空;依赖安装、会话收尾、自动恢复和 Agent Teams 执行也计入等待条件。内存缓存随重启自然重建,磁盘会话、看板、图片等持久数据不会被清理。
|
|
437
|
+
`/update` 与 `/update safe` 会把飞书消息或按钮事件 ID 原子写入 `~/.chatccc/state/update-command-guard.json`。同一 ID 跨重启重投时会静默忽略;用户主动发送的新更新指令因事件 ID 不同,仍可执行。该保护不改变普通消息与重启指令的处理方式。
|
|
438
|
+
|
|
439
|
+
`/restart safe` 与 `/update safe` 会先建立全局准入门禁:指令到达前已经运行或进入单会话缓存队列的工作会继续完成,之后到达的新普通任务会被提示在维护完成后重发。维护任务持久化到 `~/.chatccc/state/safe-maintenance.json`,进程意外退出后可继续排空;依赖安装、会话收尾、自动恢复和 Agent Teams 执行也计入等待条件。内存缓存随重启自然重建,磁盘会话、看板、图片等持久数据不会被清理。
|
|
440
440
|
|
|
441
441
|
> **模型切换**:`/model` 查看当前会话 Agent 的可选模型清单,`/model <名称>` 模糊匹配切换,`/model clear` 恢复默认。可选模型来自当前 Agent 的配置:Claude 使用 `claude.model` / `claude.subagentModel`;Cursor、Codex、CCC Agent 和 DSH 使用各自的 `model` / `alternativeModel`。
|
|
442
442
|
|
|
@@ -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
|
+
}
|
|
@@ -1,244 +1,244 @@
|
|
|
1
|
-
export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
6
|
-
<title>Agent Team</title>
|
|
7
|
-
<style>
|
|
8
|
-
:root{color-scheme:light;--bg:#f5f7fb;--panel:#fff;--panel2:#fff;--soft:#f8f9fc;--line:#e3e7ef;--text:#182033;--muted:#737d91;--accent:#6758e8;--accent-soft:#eeeaff;--danger:#c2415a;--success:#23825d;--shadow:0 8px 28px rgba(26,35,58,.07)}
|
|
9
|
-
*{box-sizing:border-box}body{margin:0;min-height:100vh;font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei",sans-serif;background:var(--bg);color:var(--text)}button,input,textarea,select{font:inherit}.app-shell{display:grid;grid-template-columns:210px minmax(0,1fr);min-height:100vh}
|
|
10
|
-
.sidebar{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;gap:24px;padding:24px 18px;background:var(--panel);border-right:1px solid var(--line)}.brand{display:flex;align-items:center;gap:11px;color:var(--text);text-decoration:none;font-size:17px;font-weight:800}.brand-icon{width:34px;height:34px;display:grid;place-items:center;border-radius:11px;background:var(--accent);color:#fff;box-shadow:0 8px 18px rgba(103,88,232,.25)}.nav{display:grid;gap:7px}.nav-item{width:100%;display:flex;align-items:center;gap:11px;padding:10px 12px;border:0;border-radius:10px;background:transparent;color:var(--muted);font-size:13px;font-weight:650;text-align:left}.nav-item:hover{border:0;background:var(--soft)}.nav-item.active{background:var(--accent-soft);color:var(--accent)}.nav-icon{width:18px;text-align:center}.side-note{margin-top:auto;padding:13px;border:1px solid var(--line);background:var(--soft);border-radius:12px;font-size:11px;line-height:1.55;color:var(--muted)}.side-note strong{color:var(--success)}
|
|
11
|
-
.shell{min-width:320px;padding:24px}.top{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;margin-bottom:18px}.eyebrow{font-size:11px;font-weight:800;letter-spacing:.12em;color:var(--accent);margin-bottom:6px}.top h1{font-size:27px;line-height:1.2;letter-spacing:-.025em;margin:0}.top p{font-size:12px;color:var(--muted);margin:6px 0 0}.top a{height:38px;display:inline-flex;align-items:center;padding:0 13px;border:1px solid var(--line);border-radius:10px;background:var(--panel);color:var(--text);text-decoration:none;font-size:12px;font-weight:700}
|
|
12
|
-
.workspace-bar{display:grid;grid-template-columns:minmax(180px,220px) minmax(280px,1fr) auto auto;gap:9px;padding:11px;background:var(--panel);border:1px solid var(--line);border-radius:15px;margin-bottom:12px;box-shadow:var(--shadow)}input,textarea,select{border:1px solid var(--line);background:var(--soft);color:var(--text);border-radius:9px;padding:10px 12px;outline:none}input:focus,textarea:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(103,88,232,.13)}button{cursor:pointer;border:1px solid var(--line);border-radius:9px;padding:10px 14px;background:var(--panel);color:var(--text);font-weight:700}button:hover{border-color:#c8cde0;background:#fbfbfe}button.primary{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 7px 17px rgba(103,88,232,.22)}button.primary:hover{background:#5949d6;border-color:#5949d6}button.ghost{background:var(--soft)}button.danger{background:#fff1f3;border-color:#f3c4ce;color:var(--danger)}.hidden{display:none!important}
|
|
13
|
-
.main-agent-bar{display:grid;grid-template-columns:minmax(180px,1fr) auto;align-items:center;gap:12px;padding:13px 15px;background:var(--panel);border:1px solid var(--line);border-radius:15px;margin-bottom:12px;box-shadow:var(--shadow)}.main-agent-copy{min-width:0}.main-agent-copy strong{display:block;font-size:13px;margin-bottom:3px}.main-agent-copy span{display:block;color:var(--muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.main-agent-controls{display:flex;align-items:center;gap:8px}.main-agent-controls select{min-width:130px}.main-agent-controls button{min-width:120px;white-space:nowrap}.agent-status{display:inline-flex;align-items:center;gap:6px;color:var(--muted)}.agent-status::before{content:"●";font-size:9px;color:#a3aabd}.agent-status.ready::before{color:var(--success)}.agent-status.error::before{color:var(--danger)}.agent-status.provisioning::before{color:#b7791f}
|
|
14
|
-
.status-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 3px 10px;color:var(--muted);font-size:11px}.workspace-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.save-state{min-width:78px;text-align:right;color:var(--success);font-weight:700}.save-state::before{content:"● ";font-size:9px}.save-state.saving{color:#b7791f}.save-state.error{color:var(--danger)}
|
|
15
|
-
.board-scroll{overflow-x:auto;padding-bottom:12px}.board{display:grid;grid-template-columns:repeat(5,minmax(230px,1fr));gap:11px;min-width:1200px}.column{display:flex;flex-direction:column;min-height:calc(100vh - 230px);background:var(--soft);border:1px solid var(--line);border-radius:15px;overflow:hidden}.column.drag-over{border-color:var(--accent);box-shadow:0 0 0 3px rgba(103,88,232,.13)}.column-head{display:flex;align-items:center;gap:8px;padding:11px 10px 7px 13px}.column-dot{width:7px;height:7px;border-radius:50%;background:var(--accent)}.column-title{font-size:13px;font-weight:800}.count{margin-left:auto;display:inline-grid;place-items:center;min-width:23px;height:23px;padding:0 7px;border-radius:999px;background:var(--panel);border:1px solid var(--line);color:var(--muted);font-size:10px}.column-add{width:27px;height:27px;display:grid;place-items:center;padding:0;border-color:transparent;border-radius:8px;background:transparent;color:var(--muted);font-size:19px;font-weight:500;line-height:1}.column-add:hover{border-color:var(--line);background:var(--panel);color:var(--accent)}.task-list{flex:1;min-height:100px;padding:4px 10px 10px}.task{position:relative;background:var(--panel2);border:1px solid var(--line);border-radius:11px;padding:12px;margin:8px 0;box-shadow:0 4px 15px rgba(34,42,65,.045);cursor:pointer}.task:hover{border-color:#d1d5e1;box-shadow:0 8px 22px rgba(34,42,65,.08)}.task.dragging{opacity:.35;cursor:grabbing}.task h3{font-size:13px;line-height:1.45;margin:0 42px 6px 0}.task p{font-size:11px;line-height:1.55;color:var(--muted);margin:0;white-space:pre-wrap;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}.task-actions{display:flex;gap:4px;position:absolute;right:7px;top:7px;opacity:0;transition:opacity .15s}.task:hover .task-actions,.task:focus-within .task-actions{opacity:1}.icon-btn{padding:3px 6px;border-radius:6px;background:var(--soft);color:var(--muted);font-size:10px}.empty{padding:20px 8px;text-align:center;color:#9aa3b4;font-size:11px}
|
|
16
|
-
.task.running{cursor:default;border-color:#cfc8ff}.task-run{margin-top:10px;padding-top:9px;border-top:1px solid var(--line)}.task-run-head{display:flex;align-items:center;justify-content:space-between;gap:7px}.run-status{font-size:10px;font-weight:800;color:var(--muted)}.run-status.running,.run-status.queued{color:var(--accent)}.run-status.succeeded{color:var(--success)}.run-status.failed,.run-status.interrupted{color:var(--danger)}.run-result{margin-top:6px!important;padding:7px 8px;border-radius:7px;background:var(--soft);display:-webkit-box!important;-webkit-line-clamp:3!important}.run-button{padding:5px 8px;font-size:10px;border-radius:7px}.run-button.primary{box-shadow:none}
|
|
17
|
-
.modal-backdrop{position:fixed;inset:0;z-index:20;background:rgba(24,32,51,.36);display:grid;place-items:center;padding:20px;backdrop-filter:blur(3px)}.modal{width:min(560px,100%);background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:20px;box-shadow:0 28px 90px rgba(35,43,65,.24)}.modal h2{margin:0 0 16px}.field{display:grid;gap:7px;margin-bottom:14px}.field label{font-size:13px;color:var(--text);font-weight:650}.field textarea{min-height:150px;resize:vertical}.modal-actions{display:flex;justify-content:flex-end;gap:9px}.task-detail-modal{width:min(980px,100%);max-height:calc(100vh - 40px);padding:0;display:flex;flex-direction:column;overflow:hidden}.task-detail-scroll{min-height:0;overflow-y:auto;padding:22px 24px}.task-detail-modal>.modal-actions{flex:none;padding:14px 24px;border-top:1px solid var(--line);background:var(--panel)}.task-execution-details{margin-top:22px;padding-top:20px;border-top:1px solid var(--line)}.task-execution-title{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.task-execution-title h3{margin:0;font-size:16px}.run-meta{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:14px}.run-meta-item{padding:9px 10px;border:1px solid var(--line);border-radius:9px;background:var(--soft);min-width:0}.run-meta-item span{display:block;color:var(--muted);font-size:10px;margin-bottom:4px}.run-meta-item strong{display:block;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.execution-timeline{display:grid;gap:9px}.timeline-entry{border:1px solid var(--line);border-radius:10px;background:var(--soft);overflow:hidden}.timeline-entry.tool_use{border-left:3px solid var(--accent)}.timeline-entry.tool_result{border-left:3px solid var(--success)}.timeline-entry.error{border-left-color:var(--danger)}.timeline-head{display:flex;justify-content:space-between;gap:12px;padding:8px 10px;border-bottom:1px solid var(--line);color:var(--muted);font-size:10px;font-weight:800}.timeline-entry pre{margin:0;padding:11px 12px;max-height:none;overflow:visible;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--text)}.timeline-empty{padding:22px;border:1px dashed var(--line);border-radius:10px;text-align:center;color:var(--muted);font-size:12px}.directory-modal{width:min(820px,100%)}.directory-layout{display:grid;grid-template-columns:180px minmax(0,1fr);min-height:430px;border:1px solid var(--line);border-radius:12px;overflow:hidden;margin-bottom:16px}.directory-sidebar{padding:12px;background:var(--soft);border-right:1px solid var(--line)}.directory-label{margin:2px 6px 9px;color:var(--muted);font-size:11px;font-weight:800;letter-spacing:.05em}.directory-locations{display:grid;gap:4px}.directory-location{width:100%;padding:8px 9px;border:0;background:transparent;text-align:left;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.directory-location:hover{border:0;background:var(--accent-soft);color:var(--accent)}.directory-main{min-width:0;display:flex;flex-direction:column;padding:12px}.directory-toolbar{display:grid;grid-template-columns:auto auto minmax(0,1fr);gap:7px}.directory-toolbar button{padding:8px 11px}.directory-toolbar input{min-width:0;padding:8px 10px}.directory-list{flex:1;height:310px;overflow:auto;margin-top:10px;border:1px solid var(--line);border-radius:10px;background:var(--soft);padding:6px}.directory-row{width:100%;display:flex;align-items:center;gap:9px;padding:9px 10px;border:0;background:transparent;text-align:left;font-size:12px}.directory-row:hover{border:0;background:#fff}.directory-row.selected{border:0;background:var(--accent-soft);color:var(--accent)}.directory-folder-icon{color:#b7791f;font-size:15px}.directory-empty{padding:46px 16px;text-align:center;color:var(--muted);font-size:12px}.directory-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:10px;color:var(--muted);font-size:11px}.directory-footer label{display:flex;align-items:center;gap:6px;white-space:nowrap}.directory-footer input{width:15px;height:15px;margin:0}.directory-selected{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right}.toast{position:fixed;right:22px;bottom:22px;z-index:30;max-width:min(420px,calc(100vw - 44px));padding:12px 15px;border-radius:10px;background:#253047;color:#fff;border:1px solid #35415a;box-shadow:0 16px 50px rgba(35,43,65,.25)}.toast.error{border-color:#ef9bad;background:#9f2942}
|
|
18
|
-
@media(max-width:1100px){.app-shell{grid-template-columns:78px minmax(0,1fr)}.sidebar{padding:22px 12px;align-items:center}.brand span:last-child,.nav-item span:last-child,.side-note{display:none}.nav-item{width:45px;height:45px;justify-content:center;padding:0}.workspace-bar{grid-template-columns:1fr 1fr}.workspace-bar input{grid-column:1/-1}.main-agent-bar{grid-template-columns:1fr}.main-agent-controls{justify-content:flex-end}.board{min-width:1200px}}
|
|
19
|
-
@media(max-width:700px){.app-shell{display:block}.sidebar{display:none}.shell{padding:14px}.top{align-items:flex-start}.workspace-bar{grid-template-columns:1fr}.workspace-bar input{grid-column:auto}.main-agent-controls{display:grid;grid-template-columns:minmax(0,1fr) auto;justify-content:stretch}.main-agent-controls select{min-width:0}.board{min-width:1180px}.task-detail-scroll{padding:18px}.task-detail-modal>.modal-actions{padding:12px 18px}.run-meta{grid-template-columns:repeat(2,minmax(0,1fr))}.directory-layout{grid-template-columns:1fr}.directory-sidebar{border-right:0;border-bottom:1px solid var(--line)}.directory-locations{display:flex;overflow-x:auto}.directory-location{width:auto;min-width:max-content}.directory-toolbar{grid-template-columns:auto auto 1fr}.directory-list{height:270px}}
|
|
20
|
-
</style>
|
|
21
|
-
</head>
|
|
22
|
-
<body>
|
|
23
|
-
<div class="app-shell">
|
|
24
|
-
<aside class="sidebar">
|
|
25
|
-
<a class="brand" href="/agent-team"><span class="brand-icon">✦</span><span>Agent Team</span></a>
|
|
26
|
-
<nav class="nav" aria-label="Agent Team 导航">
|
|
27
|
-
<span class="nav-item active"><span class="nav-icon">▦</span><span>任务看板</span></span>
|
|
28
|
-
<button type="button" class="nav-item" data-feature="工作目录"><span class="nav-icon">⌕</span><span>工作目录</span></button>
|
|
29
|
-
<button type="button" class="nav-item" data-feature="同步设置"><span class="nav-icon">↻</span><span>同步设置</span></button>
|
|
30
|
-
</nav>
|
|
31
|
-
<div class="side-note">本地优先<br>数据保存在 ~/.chatccc<br><strong>● Node 后端已连接</strong></div>
|
|
32
|
-
</aside>
|
|
33
|
-
<main class="shell">
|
|
34
|
-
<div class="top"><div><div class="eyebrow">LOCAL WORKSPACE</div><h1>Agent Team</h1><p>每个工作目录拥有独立的本地任务看板</p></div><a href="/">返回设置</a></div>
|
|
35
|
-
<section class="workspace-bar">
|
|
36
|
-
<select id="recent" aria-label="最近工作目录"><option value="">最近工作目录</option></select>
|
|
37
|
-
<input id="workspace-path" type="text" spellcheck="false" placeholder="输入本机工作目录绝对路径">
|
|
38
|
-
<button id="pick" class="ghost">选择文件夹</button>
|
|
39
|
-
<button id="relink" class="hidden">重新关联</button>
|
|
40
|
-
</section>
|
|
41
|
-
<section class="main-agent-bar" aria-label="项目主 Agent">
|
|
42
|
-
<div class="main-agent-copy"><strong>项目主 Agent</strong><span id="main-agent-status" class="agent-status">请先打开一个项目</span></div>
|
|
43
|
-
<div class="main-agent-controls"><select id="main-agent" disabled aria-label="选择主 Agent"><option value="">选择主 Agent</option></select><button id="save-main-agent" class="primary" disabled>设置为主 Agent</button></div>
|
|
44
|
-
</section>
|
|
45
|
-
<div class="status-row"><span id="workspace-name" class="workspace-name">尚未打开工作目录</span><span id="save-state" class="save-state">本地 JSON</span></div>
|
|
46
|
-
<div class="board-scroll"><section class="board" id="board">
|
|
47
|
-
<article class="column" data-column="brainstorm"><header class="column-head"><span class="column-dot"></span><span class="column-title">头脑风暴</span><span class="count">0</span><button type="button" class="column-add" data-add="brainstorm" aria-label="在头脑风暴中添加任务" title="添加任务">+</button></header><div class="task-list"></div></article>
|
|
48
|
-
<article class="column" data-column="todo"><header class="column-head"><span class="column-dot"></span><span class="column-title">Todo</span><span class="count">0</span><button type="button" class="column-add" data-add="todo" aria-label="在 Todo 中添加任务" title="添加任务">+</button></header><div class="task-list"></div></article>
|
|
49
|
-
<article class="column" data-column="doing"><header class="column-head"><span class="column-dot"></span><span class="column-title">Doing</span><span class="count">0</span><button type="button" class="column-add" data-add="doing" aria-label="在 Doing 中添加任务" title="添加任务">+</button></header><div class="task-list"></div></article>
|
|
50
|
-
<article class="column" data-column="done"><header class="column-head"><span class="column-dot"></span><span class="column-title">Done</span><span class="count">0</span><button type="button" class="column-add" data-add="done" aria-label="在 Done 中添加任务" title="添加任务">+</button></header><div class="task-list"></div></article>
|
|
51
|
-
<article class="column" data-column="on_hold"><header class="column-head"><span class="column-dot"></span><span class="column-title">搁置</span><span class="count">0</span><button type="button" class="column-add" data-add="on_hold" aria-label="在搁置中添加任务" title="添加任务">+</button></header><div class="task-list"></div></article>
|
|
52
|
-
</section></div>
|
|
53
|
-
</main>
|
|
54
|
-
</div>
|
|
55
|
-
<div id="modal" class="modal-backdrop hidden" role="dialog" aria-modal="true">
|
|
56
|
-
<form class="modal task-detail-modal" id="task-form">
|
|
57
|
-
<div class="task-detail-scroll">
|
|
58
|
-
<h2 id="modal-title">添加任务</h2>
|
|
59
|
-
<div class="field"><label for="task-title">标题</label><input id="task-title" maxlength="200" required></div>
|
|
60
|
-
<div class="field"><label for="task-description">描述</label><textarea id="task-description" maxlength="20000"></textarea></div>
|
|
61
|
-
<section id="task-execution-details" class="task-execution-details hidden">
|
|
62
|
-
<div class="task-execution-title"><h3>完整执行过程</h3><span id="task-run-status" class="run-status"></span></div>
|
|
63
|
-
<div id="task-run-meta" class="run-meta"></div>
|
|
64
|
-
<div id="task-transcript" class="execution-timeline"></div>
|
|
65
|
-
</section>
|
|
66
|
-
</div>
|
|
67
|
-
<div class="modal-actions"><button type="button" id="cancel" class="ghost">取消</button><button type="submit" id="task-save" class="primary">保存</button></div>
|
|
68
|
-
</form>
|
|
69
|
-
</div>
|
|
70
|
-
<div id="dm-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="dm-modal-title">
|
|
71
|
-
<div class="modal">
|
|
72
|
-
<h2 id="dm-modal-title">需要识别飞书用户</h2>
|
|
73
|
-
<p style="color:var(--muted);line-height:1.7;margin:0 0 16px">请先给飞书机器人私聊发送任意消息。检测到私聊后,网页会自动继续创建主 Agent 群。</p>
|
|
74
|
-
<div id="dm-detect-status" class="agent-status provisioning" style="margin-bottom:18px">正在等待私聊消息…</div>
|
|
75
|
-
<div class="modal-actions"><button type="button" id="dm-cancel" class="ghost">取消</button><button type="button" id="dm-check" class="primary">立即检测</button></div>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
<div id="directory-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="directory-modal-title">
|
|
79
|
-
<div class="modal directory-modal">
|
|
80
|
-
<h2 id="directory-modal-title">选择工作目录</h2>
|
|
81
|
-
<div class="directory-layout">
|
|
82
|
-
<aside class="directory-sidebar"><div class="directory-label">快捷位置</div><div id="directory-locations" class="directory-locations"></div></aside>
|
|
83
|
-
<section class="directory-main">
|
|
84
|
-
<div class="directory-toolbar"><button type="button" id="directory-up" class="ghost" title="返回上级">↑</button><button type="button" id="directory-refresh" class="ghost" title="刷新">↻</button><input id="directory-path" aria-label="目录路径" spellcheck="false"></div>
|
|
85
|
-
<div id="directory-list" class="directory-list"><div class="directory-empty">正在读取目录…</div></div>
|
|
86
|
-
<div class="directory-footer"><label><input type="checkbox" id="directory-show-hidden">显示隐藏目录</label><span id="directory-selected" class="directory-selected">请选择当前目录或一个子目录</span></div>
|
|
87
|
-
</section>
|
|
88
|
-
</div>
|
|
89
|
-
<div class="modal-actions"><button type="button" id="directory-cancel" class="ghost">取消</button><button type="button" id="directory-confirm" class="primary">选择当前文件夹</button></div>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
<div id="feature-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="feature-modal-title">
|
|
93
|
-
<div class="modal">
|
|
94
|
-
<h2 id="feature-modal-title">功能尚未实现</h2>
|
|
95
|
-
<p id="feature-modal-message" style="color:var(--muted);line-height:1.7;margin:0 0 16px"></p>
|
|
96
|
-
<div class="modal-actions"><button type="button" id="feature-modal-ok" class="primary">知道了</button></div>
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
<div id="create-board-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="create-board-modal-title">
|
|
100
|
-
<div class="modal">
|
|
101
|
-
<h2 id="create-board-modal-title">新建看板</h2>
|
|
102
|
-
<p style="color:var(--muted);line-height:1.7;margin:0 0 8px">这个路径还没有创建过看板,是否为这个路径新建看板?</p>
|
|
103
|
-
<p id="create-board-path" style="word-break:break-all;background:var(--soft);border:1px solid var(--line);border-radius:9px;padding:10px 12px;margin:0 0 18px"></p>
|
|
104
|
-
<div class="modal-actions"><button type="button" id="create-board-cancel" class="ghost">取消</button><button type="button" id="create-board-confirm" class="primary">新建看板</button></div>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
<div id="toast" class="toast hidden"></div>
|
|
108
|
-
<script>
|
|
109
|
-
(function(){
|
|
110
|
-
var state={board:null,binding:null,runs:[],runPollTimer:null,runPollBusy:false,workspaces:[],agentOptions:[],dragTaskId:null,modalTaskId:null,modalColumnId:null,modalDetailVersion:0,relinkBoardId:null,pendingAgentId:null,pendingWorkspacePath:null,workspaceSelectionVersion:0,directoryLocations:[],directoryPath:null,directoryParentPath:null,directorySelectedPath:null,directoryBrowseVersion:0,contactTimer:null,contactChecking:false,mutationQueue:Promise.resolve()};
|
|
111
|
-
var columns=['brainstorm','todo','doing','done','on_hold'];
|
|
112
|
-
var recent=document.getElementById('recent');
|
|
113
|
-
var pathInput=document.getElementById('workspace-path');
|
|
114
|
-
var relinkButton=document.getElementById('relink');
|
|
115
|
-
var saveState=document.getElementById('save-state');
|
|
116
|
-
var mainAgentSelect=document.getElementById('main-agent');
|
|
117
|
-
var mainAgentButton=document.getElementById('save-main-agent');
|
|
118
|
-
|
|
119
|
-
async function api(path,options){
|
|
120
|
-
var response=await fetch(path,Object.assign({headers:{'content-type':'application/json; charset=utf-8'}},options||{}));
|
|
121
|
-
var data=await response.json().catch(function(){return {error:'响应不是有效 JSON'};});
|
|
122
|
-
if(!response.ok){var error=new Error(data.error||('HTTP '+response.status));error.code=data.code;throw error;}
|
|
123
|
-
return data;
|
|
124
|
-
}
|
|
125
|
-
function request(method,path,body){return api(path,{method:method,body:body===undefined?undefined:JSON.stringify(body)});}
|
|
126
|
-
function setSaving(label,kind){saveState.textContent=label;saveState.className='save-state '+(kind||'');}
|
|
127
|
-
function toast(message,error){var el=document.getElementById('toast');el.textContent=message;el.className='toast'+(error?' error':'');clearTimeout(toast.timer);toast.timer=setTimeout(function(){el.className='toast hidden';},3500);}
|
|
128
|
-
function mutate(operation){
|
|
129
|
-
if(!state.board)return Promise.resolve(false);
|
|
130
|
-
state.mutationQueue=state.mutationQueue.then(async function(){
|
|
131
|
-
setSaving('保存中…','saving');
|
|
132
|
-
try{var result=await operation();state.board=result.board;if(Object.prototype.hasOwnProperty.call(result,'binding'))state.binding=result.binding;renderBoard();setSaving('已保存','');return true;}
|
|
133
|
-
catch(error){setSaving('保存失败','error');if(error.code==='revision_conflict'){try{await reloadBoard();toast('其他页面修改了看板,已载入最新版本',true);}catch(reloadError){toast(reloadError.message,true);}}else toast(error.message,true);return false;}
|
|
134
|
-
});
|
|
135
|
-
return state.mutationQueue;
|
|
136
|
-
}
|
|
137
|
-
async function refreshWorkspaces(selectBoardId){
|
|
138
|
-
var data=await api('/api/agent-team/workspaces');state.workspaces=data.workspaces||[];state.agentOptions=data.agentOptions||[];recent.innerHTML='<option value="">最近工作目录</option>';
|
|
139
|
-
state.workspaces.forEach(function(item){var option=document.createElement('option');option.value=item.boardId;option.textContent=(item.exists?'':'⚠ ')+item.workspacePath;recent.appendChild(option);});
|
|
140
|
-
if(selectBoardId)recent.value=selectBoardId;
|
|
141
|
-
return data.defaultWorkspace;
|
|
142
|
-
}
|
|
143
|
-
async function openWorkspace(path,selectionVersion){
|
|
144
|
-
if(!path||!path.trim())return toast('请输入工作目录',true);
|
|
145
|
-
var version=selectionVersion===undefined?++state.workspaceSelectionVersion:selectionVersion;
|
|
146
|
-
await state.mutationQueue;
|
|
147
|
-
setSaving('加载中…','saving');
|
|
148
|
-
try{var result=await request('POST','/api/agent-team/open',{workspacePath:path.trim()});if(version!==state.workspaceSelectionVersion)return;state.board=result.board;state.binding=result.binding||null;state.runs=[];state.relinkBoardId=null;relinkButton.classList.add('hidden');pathInput.value=state.board.workspacePath;await Promise.all([refreshWorkspaces(state.board.boardId),loadRuns(false)]);if(version!==state.workspaceSelectionVersion)return;renderBoard();scheduleRunPoll();setSaving('已保存','');}
|
|
149
|
-
catch(error){if(version!==state.workspaceSelectionVersion)return;setSaving('打开失败','error');toast(error.message,true);}
|
|
150
|
-
}
|
|
151
|
-
async function selectWorkspacePath(path){
|
|
152
|
-
if(!path||!path.trim())return toast('请输入工作目录',true);
|
|
153
|
-
var workspacePath=path.trim();var version=++state.workspaceSelectionVersion;
|
|
154
|
-
await state.mutationQueue;
|
|
155
|
-
setSaving('检查中…','saving');
|
|
156
|
-
try{var lookup=await request('POST','/api/agent-team/lookup',{workspacePath:workspacePath});if(version!==state.workspaceSelectionVersion)return;if(lookup.exists)return openWorkspace(workspacePath,version);state.pendingWorkspacePath=workspacePath;pathInput.value=workspacePath;document.getElementById('create-board-path').textContent=workspacePath;document.getElementById('create-board-modal').classList.remove('hidden');setSaving(state.board?'已保存':'本地 JSON','');}
|
|
157
|
-
catch(error){if(version!==state.workspaceSelectionVersion)return;setSaving('打开失败','error');toast(error.message,true);}
|
|
158
|
-
}
|
|
159
|
-
function closeCreateBoardModal(keepPath){document.getElementById('create-board-modal').classList.add('hidden');state.pendingWorkspacePath=null;if(!keepPath&&state.board)pathInput.value=state.board.workspacePath;}
|
|
160
|
-
function showFeatureModal(feature){document.getElementById('feature-modal-message').textContent='“'+feature+'”功能还未实现,后续版本会开放。';document.getElementById('feature-modal').classList.remove('hidden');}
|
|
161
|
-
function closeFeatureModal(){document.getElementById('feature-modal').classList.add('hidden');}
|
|
162
|
-
function renderDirectoryLocations(){var root=document.getElementById('directory-locations');root.innerHTML='';state.directoryLocations.forEach(function(location){var button=document.createElement('button');button.type='button';button.className='directory-location';button.textContent=(location.kind==='drive'?'▣ ':'⌂ ')+location.label;button.title=location.path;button.addEventListener('click',function(){browseDirectory(location.path);});root.appendChild(button);});}
|
|
163
|
-
function renderDirectoryEntries(entries){var root=document.getElementById('directory-list');root.innerHTML='';if(!entries.length){var empty=document.createElement('div');empty.className='directory-empty';empty.textContent='这个目录中没有可显示的子目录';root.appendChild(empty);return;}entries.forEach(function(entry){var row=document.createElement('button');row.type='button';row.className='directory-row';row.title='双击进入 '+entry.path;var icon=document.createElement('span');icon.className='directory-folder-icon';icon.textContent='▰';var name=document.createElement('span');name.textContent=entry.name;row.append(icon,name);row.addEventListener('click',function(){state.directorySelectedPath=entry.path;root.querySelectorAll('.directory-row').forEach(function(item){item.classList.remove('selected');});row.classList.add('selected');document.getElementById('directory-selected').textContent=entry.path;document.getElementById('directory-confirm').textContent='选择此文件夹';});row.addEventListener('dblclick',function(){browseDirectory(entry.path);});root.appendChild(row);});}
|
|
164
|
-
async function browseDirectory(path){if(!path)return false;var version=++state.directoryBrowseVersion;var root=document.getElementById('directory-list');root.innerHTML='<div class="directory-empty">正在读取目录…</div>';document.getElementById('directory-path').value=path;try{var result=await request('POST','/api/agent-team/filesystem/directories',{path:path,showHidden:document.getElementById('directory-show-hidden').checked});if(version!==state.directoryBrowseVersion)return false;state.directoryPath=result.directory.path;state.directoryParentPath=result.directory.parentPath;state.directorySelectedPath=null;document.getElementById('directory-path').value=result.directory.path;document.getElementById('directory-up').disabled=!result.directory.parentPath;document.getElementById('directory-selected').textContent=result.directory.path;document.getElementById('directory-confirm').textContent='选择当前文件夹';renderDirectoryEntries(result.directory.entries||[]);return true;}catch(error){if(version!==state.directoryBrowseVersion)return false;root.innerHTML='';var empty=document.createElement('div');empty.className='directory-empty';empty.textContent=error.message;root.appendChild(empty);return false;}}
|
|
165
|
-
async function openDirectoryModal(){state.directoryPath=null;state.directoryParentPath=null;state.directorySelectedPath=null;document.getElementById('directory-modal').classList.remove('hidden');document.getElementById('directory-locations').innerHTML='';document.getElementById('directory-list').innerHTML='<div class="directory-empty">正在读取目录…</div>';try{var result=await api('/api/agent-team/filesystem/locations');state.directoryLocations=result.locations||[];renderDirectoryLocations();var candidate=pathInput.value.trim()||(state.board&&state.board.workspacePath)||(state.directoryLocations[0]&&state.directoryLocations[0].path);var opened=await browseDirectory(candidate);if(!opened&&state.directoryLocations[0]&&state.directoryLocations[0].path!==candidate)await browseDirectory(state.directoryLocations[0].path);}catch(error){var root=document.getElementById('directory-list');root.innerHTML='';var empty=document.createElement('div');empty.className='directory-empty';empty.textContent=error.message;root.appendChild(empty);}}
|
|
166
|
-
function closeDirectoryModal(){state.directoryBrowseVersion++;document.getElementById('directory-modal').classList.add('hidden');}
|
|
167
|
-
async function confirmDirectorySelection(){var target=state.directorySelectedPath||state.directoryPath;if(!target)return;var button=document.getElementById('directory-confirm');button.disabled=true;try{var result=await request('POST','/api/agent-team/filesystem/validate-directory',{path:target});closeDirectoryModal();pathInput.value=result.path;if(!state.relinkBoardId)selectWorkspacePath(result.path);}catch(error){toast(error.message,true);}finally{button.disabled=false;}}
|
|
168
|
-
async function reloadBoard(){if(!state.board)return;var result=await api('/api/agent-team/boards/'+encodeURIComponent(state.board.boardId));state.board=result.board;state.binding=result.binding||null;renderBoard();}
|
|
169
|
-
function workspaceShortName(value){var path=String(value||'');var slash=String.fromCharCode(92);while(path.endsWith('/')||path.endsWith(slash))path=path.slice(0,-1);var index=Math.max(path.lastIndexOf('/'),path.lastIndexOf(slash));return path.slice(index+1)||path;}
|
|
170
|
-
async function loadRuns(render){if(!state.board)return;var boardId=state.board.boardId;var result=await api('/api/agent-team/boards/'+encodeURIComponent(boardId)+'/runs');if(!state.board||state.board.boardId!==boardId)return;state.runs=result.runs||[];if(render!==false)renderBoard();}
|
|
171
|
-
function latestRun(taskId){return state.runs.find(function(run){return run.taskId===taskId;})||null;}
|
|
172
|
-
function isActiveRun(run){return !!run&&(run.state==='queued'||run.state==='running');}
|
|
173
|
-
function runLabel(run){var labels={queued:'等待执行',running:run.stopRequestedAt?'正在停止':'主 Agent 执行中',succeeded:'执行成功',failed:'执行失败',canceled:'已停止',interrupted:'重启时中断'};return labels[run.state]||run.state;}
|
|
174
|
-
function scheduleRunPoll(){if(state.runPollTimer){clearTimeout(state.runPollTimer);state.runPollTimer=null;}if(!state.runs.some(isActiveRun))return;state.runPollTimer=setTimeout(pollRuns,1500);}
|
|
175
|
-
async function pollRuns(){if(state.runPollBusy||!state.board)return scheduleRunPoll();state.runPollBusy=true;var boardId=state.board.boardId;try{var results=await Promise.all([api('/api/agent-team/boards/'+encodeURIComponent(boardId)),api('/api/agent-team/boards/'+encodeURIComponent(boardId)+'/runs')]);if(state.board&&state.board.boardId===boardId){state.board=results[0].board;state.binding=results[0].binding||null;state.runs=results[1].runs||[];renderBoard();if(state.modalTaskId)await loadOpenTaskRunDetails(false);if(!state.runs.some(isActiveRun))setSaving('已保存','');}}catch(error){toast(error.message,true);}finally{state.runPollBusy=false;scheduleRunPoll();}}
|
|
176
|
-
async function startTaskRun(task){if(!state.board)return;if(!state.binding||state.binding.status!=='ready')return toast('请先设置可用的主 Agent',true);try{setSaving('正在交给主 Agent…','saving');var result=await request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks/'+encodeURIComponent(task.id)+'/run',{expectedRevision:state.board.revision});state.board=result.board;state.runs=[result.run].concat(state.runs.filter(function(run){return run.runId!==result.run.runId;}));renderBoard();scheduleRunPoll();setSaving('任务执行中','saving');}catch(error){if(error.code==='revision_conflict')await reloadBoard();toast(error.message,true);setSaving('执行失败','error');}}
|
|
177
|
-
async function stopTaskRun(run){if(!state.board)return;try{var result=await request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/runs/'+encodeURIComponent(run.runId)+'/stop',{});state.runs=state.runs.map(function(item){return item.runId===result.run.runId?result.run:item;});renderBoard();scheduleRunPoll();toast('已请求主 Agent 停止任务');}catch(error){toast(error.message,true);}}
|
|
178
|
-
function activeTasks(columnId){return state.board?state.board.tasks.filter(function(task){return !task.deletedAt&&task.columnId===columnId;}).sort(function(a,b){return a.order-b.order;}):[];}
|
|
179
|
-
function renderBoard(){
|
|
180
|
-
document.getElementById('workspace-name').textContent=state.board?state.board.workspacePath:'尚未打开工作目录';
|
|
181
|
-
renderMainAgent();
|
|
182
|
-
columns.forEach(function(columnId){var column=document.querySelector('[data-column="'+columnId+'"]');var list=column.querySelector('.task-list');var tasks=activeTasks(columnId);column.querySelector('.count').textContent=String(tasks.length);list.innerHTML='';
|
|
183
|
-
if(!tasks.length){var empty=document.createElement('div');empty.className='empty';empty.textContent='暂无任务';list.appendChild(empty);}
|
|
184
|
-
tasks.forEach(function(task){var run=latestRun(task.id);var active=isActiveRun(run);var dragged=false;var card=document.createElement('article');card.className='task'+(active?' running':'');card.draggable=!active;card.dataset.taskId=task.id;
|
|
185
|
-
var title=document.createElement('h3');title.textContent=task.title;card.appendChild(title);if(task.description){var desc=document.createElement('p');desc.textContent=task.description;card.appendChild(desc);}
|
|
186
|
-
var actions=document.createElement('div');actions.className='task-actions';var edit=document.createElement('button');edit.type='button';edit.className='icon-btn';edit.textContent='编辑';edit.addEventListener('click',function(event){event.stopPropagation();showEdit(task);});var del=document.createElement('button');del.type='button';del.className='icon-btn';del.textContent='删除';del.disabled=active;del.addEventListener('click',function(event){event.stopPropagation();removeTask(task);});actions.append(edit,del);card.appendChild(actions);
|
|
187
|
-
var runBox=document.createElement('div');runBox.className='task-run';var runHead=document.createElement('div');runHead.className='task-run-head';var status=document.createElement('span');status.className='run-status '+(run?run.state:'');status.textContent=run?runLabel(run)+(run.attempt>1?' · 第 '+run.attempt+' 次':''):'尚未执行';var runButton=document.createElement('button');runButton.type='button';runButton.className='run-button '+(!run||!active?'primary':'danger');runButton.textContent=active?'停止':(run?'重试':'交给主 Agent');runButton.addEventListener('click',function(event){event.stopPropagation();if(active)stopTaskRun(run);else startTaskRun(task);});runHead.append(status,runButton);runBox.appendChild(runHead);if(run&&(run.result||run.error)){var resultText=document.createElement('p');resultText.className='run-result';resultText.textContent=run.error||run.result;runBox.appendChild(resultText);}card.appendChild(runBox);
|
|
188
|
-
card.addEventListener('click',function(){if(!dragged)showEdit(task);});card.addEventListener('dragstart',function(event){if(active){event.preventDefault();return;}dragged=true;state.dragTaskId=task.id;card.classList.add('dragging');event.dataTransfer.effectAllowed='move';});card.addEventListener('dragend',function(){state.dragTaskId=null;card.classList.remove('dragging');document.querySelectorAll('.column').forEach(function(el){el.classList.remove('drag-over');});setTimeout(function(){dragged=false;},0);});list.appendChild(card);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
function renderMainAgent(){
|
|
193
|
-
var selected=state.board?(state.board.primaryAgentId||(state.binding&&state.binding.agentId)||''):'';mainAgentSelect.innerHTML='<option value="">选择主 Agent</option>';
|
|
194
|
-
state.agentOptions.forEach(function(agent){var option=document.createElement('option');option.value=agent.id;option.textContent=agent.label;mainAgentSelect.appendChild(option);});
|
|
195
|
-
mainAgentSelect.value=selected;mainAgentSelect.disabled=!state.board;mainAgentButton.disabled=!state.board||!mainAgentSelect.value;
|
|
196
|
-
var status=document.getElementById('main-agent-status');var binding=state.binding;
|
|
197
|
-
if(!state.board){status.textContent='请先打开一个项目';status.className='agent-status';mainAgentButton.textContent='设置为主 Agent';return;}
|
|
198
|
-
if(!selected){status.textContent='尚未设置;设置后将创建“主Agent-'+workspaceShortName(state.board.workspacePath)+'”群聊';status.className='agent-status';mainAgentButton.textContent='设置为主 Agent';return;}
|
|
199
|
-
if(binding&&binding.status==='ready'){status.textContent='主 Agent 群已就绪';status.className='agent-status ready';}
|
|
200
|
-
else if(binding&&binding.status==='error'){status.textContent='创建失败,可重新设置后重试';status.className='agent-status error';}
|
|
201
|
-
else{status.textContent='正在准备主 Agent 群';status.className='agent-status provisioning';}
|
|
202
|
-
mainAgentButton.textContent='设置为主 Agent';
|
|
203
|
-
}
|
|
204
|
-
function closeDmModal(){document.getElementById('dm-modal').classList.add('hidden');if(state.contactTimer){clearInterval(state.contactTimer);state.contactTimer=null;}state.contactChecking=false;}
|
|
205
|
-
function showDmModal(){document.getElementById('dm-modal').classList.remove('hidden');document.getElementById('dm-detect-status').textContent='正在等待私聊消息…';if(state.contactTimer)clearInterval(state.contactTimer);state.contactTimer=setInterval(checkFeishuContact,1500);}
|
|
206
|
-
async function checkFeishuContact(){if(state.contactChecking)return;state.contactChecking=true;try{var result=await api('/api/agent-team/feishu-contact');if(result.contact){var pending=state.pendingAgentId;closeDmModal();toast('已识别飞书私聊用户,继续创建主 Agent 群');if(pending)saveMainAgent(pending);}}catch(error){document.getElementById('dm-detect-status').textContent='检测失败:'+error.message;}finally{state.contactChecking=false;}}
|
|
207
|
-
function saveMainAgent(agentId){
|
|
208
|
-
if(!state.board||!agentId)return;state.pendingAgentId=agentId;
|
|
209
|
-
state.mutationQueue=state.mutationQueue.then(async function(){setSaving('创建主 Agent…','saving');mainAgentButton.disabled=true;try{var result=await request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/main-agent',{expectedRevision:state.board.revision,agentId:agentId});state.board=result.board;state.binding=result.binding;state.pendingAgentId=null;renderBoard();setSaving('已保存','');toast('主 Agent 群已就绪');return true;}catch(error){setSaving('设置失败','error');renderBoard();if(error.code==='feishu_dm_required')showDmModal();else if(error.code==='revision_conflict'){try{await reloadBoard();toast('其他页面修改了项目,已载入最新版本',true);}catch(reloadError){toast(reloadError.message,true);}}else toast(error.message,true);return false;}});return state.mutationQueue;
|
|
210
|
-
}
|
|
211
|
-
function formatRunTime(value){if(!value)return '—';var date=new Date(value);return Number.isNaN(date.getTime())?String(value):date.toLocaleString();}
|
|
212
|
-
function addRunMeta(root,label,value){var item=document.createElement('div');item.className='run-meta-item';var key=document.createElement('span');key.textContent=label;var content=document.createElement('strong');content.textContent=value||'—';content.title=value||'—';item.append(key,content);root.appendChild(item);}
|
|
213
|
-
function transcriptLabel(entry){var labels={prompt:'任务指令',thinking:'思考',text:'主 Agent 回复',tool_use:'调用工具',tool_result:'工具结果',search:'联网搜索',compact:'上下文压缩',status:'状态变化',notice:'提示'};var label=labels[entry.type]||entry.type;if(entry.name)label+=' · '+entry.name;return label;}
|
|
214
|
-
function transcriptContent(entry){if(entry.type==='tool_use')return entry.input||'';if(entry.type==='tool_result')return entry.output||'';return entry.text||'';}
|
|
215
|
-
function addTranscriptEntry(root,entry){var row=document.createElement('article');row.className='timeline-entry '+entry.type+(entry.isError?' error':'');var head=document.createElement('div');head.className='timeline-head';var label=document.createElement('span');label.textContent=transcriptLabel(entry);var time=document.createElement('span');time.textContent=formatRunTime(entry.at);head.append(label,time);var body=document.createElement('pre');body.textContent=transcriptContent(entry)||'(无文本内容)';row.append(head,body);root.appendChild(row);}
|
|
216
|
-
function renderTaskExecution(task,run){var details=document.getElementById('task-execution-details');var title=document.getElementById('task-title');var description=document.getElementById('task-description');var save=document.getElementById('task-save');var cancel=document.getElementById('cancel');var active=isActiveRun(run);title.disabled=active;description.disabled=active;save.classList.toggle('hidden',active);cancel.textContent=active?'关闭':'取消';details.classList.remove('hidden');var status=document.getElementById('task-run-status');status.className='run-status '+(run?run.state:'');status.textContent=run?runLabel(run):'尚未执行';var meta=document.getElementById('task-run-meta');meta.innerHTML='';if(run){addRunMeta(meta,'执行次数','第 '+run.attempt+' 次');addRunMeta(meta,'执行 Agent',run.agentId);addRunMeta(meta,'开始时间',formatRunTime(run.startedAt||run.createdAt));addRunMeta(meta,'结束时间',formatRunTime(run.finishedAt));}else{addRunMeta(meta,'任务状态','尚未交给主 Agent');addRunMeta(meta,'创建时间',formatRunTime(task.createdAt));addRunMeta(meta,'更新时间',formatRunTime(task.updatedAt));}var transcript=document.getElementById('task-transcript');transcript.innerHTML='';var entries=run&&Array.isArray(run.transcript)?run.transcript.slice():[];var hasText=entries.some(function(entry){return entry.type==='text';});if(run&&run.result&&!hasText)entries.push({type:'text',at:run.finishedAt||run.updatedAt,text:run.result});if(run&&run.error)entries.push({type:'notice',at:run.finishedAt||run.updatedAt,text:run.error,isError:true});if(!entries.length){var empty=document.createElement('div');empty.className='timeline-empty';empty.textContent=run?'暂时还没有可显示的执行事件':'任务尚未执行,交给主 Agent 后会在这里记录完整过程。';transcript.appendChild(empty);return;}entries.forEach(function(entry){addTranscriptEntry(transcript,entry);});}
|
|
217
|
-
async function loadOpenTaskRunDetails(showError){if(!state.board||!state.modalTaskId)return;var task=state.board.tasks.find(function(candidate){return candidate.id===state.modalTaskId&&!candidate.deletedAt;});if(!task)return;var summary=latestRun(task.id);if(!summary){renderTaskExecution(task,null);return;}var version=++state.modalDetailVersion;try{var result=await api('/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/runs/'+encodeURIComponent(summary.runId));if(version!==state.modalDetailVersion||state.modalTaskId!==task.id)return;renderTaskExecution(task,result.run);}catch(error){if(showError!==false)toast(error.message,true);}}
|
|
218
|
-
function showCreate(columnId){if(!state.board)return toast('请先打开一个工作目录',true);state.modalTaskId=null;state.modalColumnId=columnId;document.getElementById('modal-title').textContent='添加任务';document.getElementById('task-title').value='';document.getElementById('task-description').value='';document.getElementById('task-title').disabled=false;document.getElementById('task-description').disabled=false;document.getElementById('task-save').classList.remove('hidden');document.getElementById('cancel').textContent='取消';document.getElementById('task-execution-details').classList.add('hidden');document.getElementById('modal').classList.remove('hidden');setTimeout(function(){document.getElementById('task-title').focus();},0);}
|
|
219
|
-
function showEdit(task){state.modalTaskId=task.id;state.modalColumnId=task.columnId;document.getElementById('modal-title').textContent='任务详情';document.getElementById('task-title').value=task.title;document.getElementById('task-description').value=task.description;renderTaskExecution(task,latestRun(task.id));document.getElementById('modal').classList.remove('hidden');loadOpenTaskRunDetails(true);}
|
|
220
|
-
function closeModal(){state.modalDetailVersion++;document.getElementById('modal').classList.add('hidden');state.modalTaskId=null;}
|
|
221
|
-
function removeTask(task){if(!confirm('删除任务“'+task.title+'”?'))return;mutate(function(){return request('DELETE','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks/'+encodeURIComponent(task.id),{expectedRevision:state.board.revision});});}
|
|
222
|
-
|
|
223
|
-
document.querySelectorAll('[data-add]').forEach(function(button){button.addEventListener('click',function(){showCreate(button.dataset.add);});});
|
|
224
|
-
document.querySelectorAll('.column').forEach(function(column){
|
|
225
|
-
column.addEventListener('dragover',function(event){if(!state.dragTaskId)return;event.preventDefault();column.classList.add('drag-over');event.dataTransfer.dropEffect='move';});column.addEventListener('dragleave',function(event){if(!column.contains(event.relatedTarget))column.classList.remove('drag-over');});
|
|
226
|
-
column.addEventListener('drop',function(event){event.preventDefault();column.classList.remove('drag-over');if(!state.dragTaskId||!state.board)return;var columnId=column.dataset.column;var cards=Array.from(column.querySelectorAll('.task:not(.dragging)'));var index=cards.findIndex(function(card){return event.clientY<card.getBoundingClientRect().top+card.getBoundingClientRect().height/2;});if(index<0)index=cards.length;var taskId=state.dragTaskId;mutate(function(){return request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks/'+encodeURIComponent(taskId)+'/move',{expectedRevision:state.board.revision,columnId:columnId,index:index});});});
|
|
227
|
-
});
|
|
228
|
-
document.getElementById('task-form').addEventListener('submit',function(event){event.preventDefault();var title=document.getElementById('task-title').value;var description=document.getElementById('task-description').value;var taskId=state.modalTaskId;var columnId=state.modalColumnId;closeModal();mutate(function(){var root='/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks';return taskId?request('PATCH',root+'/'+encodeURIComponent(taskId),{expectedRevision:state.board.revision,title:title,description:description}):request('POST',root,{expectedRevision:state.board.revision,title:title,description:description,columnId:columnId});});});
|
|
229
|
-
document.getElementById('cancel').addEventListener('click',closeModal);document.getElementById('modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeModal();});
|
|
230
|
-
document.querySelectorAll('[data-feature]').forEach(function(button){button.addEventListener('click',function(){showFeatureModal(button.dataset.feature);});});
|
|
231
|
-
document.getElementById('feature-modal-ok').addEventListener('click',closeFeatureModal);document.getElementById('feature-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeFeatureModal();});
|
|
232
|
-
document.getElementById('directory-cancel').addEventListener('click',closeDirectoryModal);document.getElementById('directory-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeDirectoryModal();});document.getElementById('directory-confirm').addEventListener('click',confirmDirectorySelection);document.getElementById('directory-up').addEventListener('click',function(){if(state.directoryParentPath)browseDirectory(state.directoryParentPath);});document.getElementById('directory-refresh').addEventListener('click',function(){if(state.directoryPath)browseDirectory(state.directoryPath);});document.getElementById('directory-show-hidden').addEventListener('change',function(){if(state.directoryPath)browseDirectory(state.directoryPath);});document.getElementById('directory-path').addEventListener('keydown',function(event){if(event.key==='Enter'){event.preventDefault();browseDirectory(event.currentTarget.value);}});
|
|
233
|
-
document.getElementById('create-board-cancel').addEventListener('click',function(){closeCreateBoardModal(false);});document.getElementById('create-board-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeCreateBoardModal(false);});document.getElementById('create-board-confirm').addEventListener('click',function(){var path=state.pendingWorkspacePath;if(!path)return;closeCreateBoardModal(true);openWorkspace(path);});
|
|
234
|
-
mainAgentSelect.addEventListener('change',function(){mainAgentButton.disabled=!state.board||!mainAgentSelect.value;});mainAgentButton.addEventListener('click',function(){saveMainAgent(mainAgentSelect.value);});
|
|
235
|
-
document.getElementById('dm-cancel').addEventListener('click',function(){state.pendingAgentId=null;closeDmModal();});document.getElementById('dm-check').addEventListener('click',checkFeishuContact);
|
|
236
|
-
pathInput.addEventListener('change',function(){if(!state.relinkBoardId)selectWorkspacePath(pathInput.value);});pathInput.addEventListener('keydown',function(event){if(event.key==='Enter'){event.preventDefault();if(!state.relinkBoardId)selectWorkspacePath(pathInput.value);}});
|
|
237
|
-
document.getElementById('pick').addEventListener('click',openDirectoryModal);
|
|
238
|
-
recent.addEventListener('change',function(){var item=state.workspaces.find(function(candidate){return candidate.boardId===recent.value;});if(!item)return;pathInput.value=item.workspacePath;if(item.exists){state.relinkBoardId=null;relinkButton.classList.add('hidden');openWorkspace(item.workspacePath);}else{state.relinkBoardId=item.boardId;relinkButton.classList.remove('hidden');api('/api/agent-team/boards/'+encodeURIComponent(item.boardId)).then(async function(result){state.board=result.board;state.binding=result.binding||null;state.runs=[];await loadRuns(false);renderBoard();scheduleRunPoll();toast('原目录不存在,请选择新目录后重新关联',true);}).catch(function(error){toast(error.message,true);});}});
|
|
239
|
-
relinkButton.addEventListener('click',function(){if(!state.relinkBoardId||!state.board)return toast('请先打开或选择需要关联的看板',true);mutate(function(){return request('POST','/api/agent-team/boards/'+encodeURIComponent(state.relinkBoardId)+'/relink',{expectedRevision:state.board.revision,workspacePath:pathInput.value});}).then(function(saved){if(!saved)return;refreshWorkspaces(state.board.boardId);state.relinkBoardId=null;relinkButton.classList.add('hidden');});});
|
|
240
|
-
refreshWorkspaces().then(function(defaultWorkspace){pathInput.value=defaultWorkspace;return selectWorkspacePath(defaultWorkspace);}).catch(function(error){toast(error.message,true);});
|
|
241
|
-
})();
|
|
242
|
-
</script>
|
|
243
|
-
</body>
|
|
1
|
+
export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
6
|
+
<title>Agent Team</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{color-scheme:light;--bg:#f5f7fb;--panel:#fff;--panel2:#fff;--soft:#f8f9fc;--line:#e3e7ef;--text:#182033;--muted:#737d91;--accent:#6758e8;--accent-soft:#eeeaff;--danger:#c2415a;--success:#23825d;--shadow:0 8px 28px rgba(26,35,58,.07)}
|
|
9
|
+
*{box-sizing:border-box}body{margin:0;min-height:100vh;font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei",sans-serif;background:var(--bg);color:var(--text)}button,input,textarea,select{font:inherit}.app-shell{display:grid;grid-template-columns:210px minmax(0,1fr);min-height:100vh}
|
|
10
|
+
.sidebar{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;gap:24px;padding:24px 18px;background:var(--panel);border-right:1px solid var(--line)}.brand{display:flex;align-items:center;gap:11px;color:var(--text);text-decoration:none;font-size:17px;font-weight:800}.brand-icon{width:34px;height:34px;display:grid;place-items:center;border-radius:11px;background:var(--accent);color:#fff;box-shadow:0 8px 18px rgba(103,88,232,.25)}.nav{display:grid;gap:7px}.nav-item{width:100%;display:flex;align-items:center;gap:11px;padding:10px 12px;border:0;border-radius:10px;background:transparent;color:var(--muted);font-size:13px;font-weight:650;text-align:left}.nav-item:hover{border:0;background:var(--soft)}.nav-item.active{background:var(--accent-soft);color:var(--accent)}.nav-icon{width:18px;text-align:center}.side-note{margin-top:auto;padding:13px;border:1px solid var(--line);background:var(--soft);border-radius:12px;font-size:11px;line-height:1.55;color:var(--muted)}.side-note strong{color:var(--success)}
|
|
11
|
+
.shell{min-width:320px;padding:24px}.top{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;margin-bottom:18px}.eyebrow{font-size:11px;font-weight:800;letter-spacing:.12em;color:var(--accent);margin-bottom:6px}.top h1{font-size:27px;line-height:1.2;letter-spacing:-.025em;margin:0}.top p{font-size:12px;color:var(--muted);margin:6px 0 0}.top a{height:38px;display:inline-flex;align-items:center;padding:0 13px;border:1px solid var(--line);border-radius:10px;background:var(--panel);color:var(--text);text-decoration:none;font-size:12px;font-weight:700}
|
|
12
|
+
.workspace-bar{display:grid;grid-template-columns:minmax(180px,220px) minmax(280px,1fr) auto auto;gap:9px;padding:11px;background:var(--panel);border:1px solid var(--line);border-radius:15px;margin-bottom:12px;box-shadow:var(--shadow)}input,textarea,select{border:1px solid var(--line);background:var(--soft);color:var(--text);border-radius:9px;padding:10px 12px;outline:none}input:focus,textarea:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(103,88,232,.13)}button{cursor:pointer;border:1px solid var(--line);border-radius:9px;padding:10px 14px;background:var(--panel);color:var(--text);font-weight:700}button:hover{border-color:#c8cde0;background:#fbfbfe}button.primary{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 7px 17px rgba(103,88,232,.22)}button.primary:hover{background:#5949d6;border-color:#5949d6}button.ghost{background:var(--soft)}button.danger{background:#fff1f3;border-color:#f3c4ce;color:var(--danger)}.hidden{display:none!important}
|
|
13
|
+
.main-agent-bar{display:grid;grid-template-columns:minmax(180px,1fr) auto;align-items:center;gap:12px;padding:13px 15px;background:var(--panel);border:1px solid var(--line);border-radius:15px;margin-bottom:12px;box-shadow:var(--shadow)}.main-agent-copy{min-width:0}.main-agent-copy strong{display:block;font-size:13px;margin-bottom:3px}.main-agent-copy span{display:block;color:var(--muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.main-agent-controls{display:flex;align-items:center;gap:8px}.main-agent-controls select{min-width:130px}.main-agent-controls button{min-width:120px;white-space:nowrap}.agent-status{display:inline-flex;align-items:center;gap:6px;color:var(--muted)}.agent-status::before{content:"●";font-size:9px;color:#a3aabd}.agent-status.ready::before{color:var(--success)}.agent-status.error::before{color:var(--danger)}.agent-status.provisioning::before{color:#b7791f}
|
|
14
|
+
.status-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 3px 10px;color:var(--muted);font-size:11px}.workspace-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.save-state{min-width:78px;text-align:right;color:var(--success);font-weight:700}.save-state::before{content:"● ";font-size:9px}.save-state.saving{color:#b7791f}.save-state.error{color:var(--danger)}
|
|
15
|
+
.board-scroll{overflow-x:auto;padding-bottom:12px}.board{display:grid;grid-template-columns:repeat(5,minmax(230px,1fr));gap:11px;min-width:1200px}.column{display:flex;flex-direction:column;min-height:calc(100vh - 230px);background:var(--soft);border:1px solid var(--line);border-radius:15px;overflow:hidden}.column.drag-over{border-color:var(--accent);box-shadow:0 0 0 3px rgba(103,88,232,.13)}.column-head{display:flex;align-items:center;gap:8px;padding:11px 10px 7px 13px}.column-dot{width:7px;height:7px;border-radius:50%;background:var(--accent)}.column-title{font-size:13px;font-weight:800}.count{margin-left:auto;display:inline-grid;place-items:center;min-width:23px;height:23px;padding:0 7px;border-radius:999px;background:var(--panel);border:1px solid var(--line);color:var(--muted);font-size:10px}.column-add{width:27px;height:27px;display:grid;place-items:center;padding:0;border-color:transparent;border-radius:8px;background:transparent;color:var(--muted);font-size:19px;font-weight:500;line-height:1}.column-add:hover{border-color:var(--line);background:var(--panel);color:var(--accent)}.task-list{flex:1;min-height:100px;padding:4px 10px 10px}.task{position:relative;background:var(--panel2);border:1px solid var(--line);border-radius:11px;padding:12px;margin:8px 0;box-shadow:0 4px 15px rgba(34,42,65,.045);cursor:pointer}.task:hover{border-color:#d1d5e1;box-shadow:0 8px 22px rgba(34,42,65,.08)}.task.dragging{opacity:.35;cursor:grabbing}.task h3{font-size:13px;line-height:1.45;margin:0 42px 6px 0}.task p{font-size:11px;line-height:1.55;color:var(--muted);margin:0;white-space:pre-wrap;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}.task-actions{display:flex;gap:4px;position:absolute;right:7px;top:7px;opacity:0;transition:opacity .15s}.task:hover .task-actions,.task:focus-within .task-actions{opacity:1}.icon-btn{padding:3px 6px;border-radius:6px;background:var(--soft);color:var(--muted);font-size:10px}.empty{padding:20px 8px;text-align:center;color:#9aa3b4;font-size:11px}
|
|
16
|
+
.task.running{cursor:default;border-color:#cfc8ff}.task-run{margin-top:10px;padding-top:9px;border-top:1px solid var(--line)}.task-run-head{display:flex;align-items:center;justify-content:space-between;gap:7px}.run-status{font-size:10px;font-weight:800;color:var(--muted)}.run-status.running,.run-status.queued{color:var(--accent)}.run-status.succeeded{color:var(--success)}.run-status.failed,.run-status.interrupted{color:var(--danger)}.run-result{margin-top:6px!important;padding:7px 8px;border-radius:7px;background:var(--soft);display:-webkit-box!important;-webkit-line-clamp:3!important}.run-button{padding:5px 8px;font-size:10px;border-radius:7px}.run-button.primary{box-shadow:none}
|
|
17
|
+
.modal-backdrop{position:fixed;inset:0;z-index:20;background:rgba(24,32,51,.36);display:grid;place-items:center;padding:20px;backdrop-filter:blur(3px)}.modal{width:min(560px,100%);background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:20px;box-shadow:0 28px 90px rgba(35,43,65,.24)}.modal h2{margin:0 0 16px}.field{display:grid;gap:7px;margin-bottom:14px}.field label{font-size:13px;color:var(--text);font-weight:650}.field textarea{min-height:150px;resize:vertical}.modal-actions{display:flex;justify-content:flex-end;gap:9px}.task-detail-modal{width:min(980px,100%);max-height:calc(100vh - 40px);padding:0;display:flex;flex-direction:column;overflow:hidden}.task-detail-scroll{min-height:0;overflow-y:auto;padding:22px 24px}.task-detail-modal>.modal-actions{flex:none;padding:14px 24px;border-top:1px solid var(--line);background:var(--panel)}.task-execution-details{margin-top:22px;padding-top:20px;border-top:1px solid var(--line)}.task-execution-title{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.task-execution-title h3{margin:0;font-size:16px}.run-meta{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:14px}.run-meta-item{padding:9px 10px;border:1px solid var(--line);border-radius:9px;background:var(--soft);min-width:0}.run-meta-item span{display:block;color:var(--muted);font-size:10px;margin-bottom:4px}.run-meta-item strong{display:block;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.execution-timeline{display:grid;gap:9px}.timeline-entry{border:1px solid var(--line);border-radius:10px;background:var(--soft);overflow:hidden}.timeline-entry.tool_use{border-left:3px solid var(--accent)}.timeline-entry.tool_result{border-left:3px solid var(--success)}.timeline-entry.error{border-left-color:var(--danger)}.timeline-head{display:flex;justify-content:space-between;gap:12px;padding:8px 10px;border-bottom:1px solid var(--line);color:var(--muted);font-size:10px;font-weight:800}.timeline-entry pre{margin:0;padding:11px 12px;max-height:none;overflow:visible;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--text)}.timeline-empty{padding:22px;border:1px dashed var(--line);border-radius:10px;text-align:center;color:var(--muted);font-size:12px}.directory-modal{width:min(820px,100%)}.directory-layout{display:grid;grid-template-columns:180px minmax(0,1fr);min-height:430px;border:1px solid var(--line);border-radius:12px;overflow:hidden;margin-bottom:16px}.directory-sidebar{padding:12px;background:var(--soft);border-right:1px solid var(--line)}.directory-label{margin:2px 6px 9px;color:var(--muted);font-size:11px;font-weight:800;letter-spacing:.05em}.directory-locations{display:grid;gap:4px}.directory-location{width:100%;padding:8px 9px;border:0;background:transparent;text-align:left;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.directory-location:hover{border:0;background:var(--accent-soft);color:var(--accent)}.directory-main{min-width:0;display:flex;flex-direction:column;padding:12px}.directory-toolbar{display:grid;grid-template-columns:auto auto minmax(0,1fr);gap:7px}.directory-toolbar button{padding:8px 11px}.directory-toolbar input{min-width:0;padding:8px 10px}.directory-list{flex:1;height:310px;overflow:auto;margin-top:10px;border:1px solid var(--line);border-radius:10px;background:var(--soft);padding:6px}.directory-row{width:100%;display:flex;align-items:center;gap:9px;padding:9px 10px;border:0;background:transparent;text-align:left;font-size:12px}.directory-row:hover{border:0;background:#fff}.directory-row.selected{border:0;background:var(--accent-soft);color:var(--accent)}.directory-folder-icon{color:#b7791f;font-size:15px}.directory-empty{padding:46px 16px;text-align:center;color:var(--muted);font-size:12px}.directory-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:10px;color:var(--muted);font-size:11px}.directory-footer label{display:flex;align-items:center;gap:6px;white-space:nowrap}.directory-footer input{width:15px;height:15px;margin:0}.directory-selected{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right}.toast{position:fixed;right:22px;bottom:22px;z-index:30;max-width:min(420px,calc(100vw - 44px));padding:12px 15px;border-radius:10px;background:#253047;color:#fff;border:1px solid #35415a;box-shadow:0 16px 50px rgba(35,43,65,.25)}.toast.error{border-color:#ef9bad;background:#9f2942}
|
|
18
|
+
@media(max-width:1100px){.app-shell{grid-template-columns:78px minmax(0,1fr)}.sidebar{padding:22px 12px;align-items:center}.brand span:last-child,.nav-item span:last-child,.side-note{display:none}.nav-item{width:45px;height:45px;justify-content:center;padding:0}.workspace-bar{grid-template-columns:1fr 1fr}.workspace-bar input{grid-column:1/-1}.main-agent-bar{grid-template-columns:1fr}.main-agent-controls{justify-content:flex-end}.board{min-width:1200px}}
|
|
19
|
+
@media(max-width:700px){.app-shell{display:block}.sidebar{display:none}.shell{padding:14px}.top{align-items:flex-start}.workspace-bar{grid-template-columns:1fr}.workspace-bar input{grid-column:auto}.main-agent-controls{display:grid;grid-template-columns:minmax(0,1fr) auto;justify-content:stretch}.main-agent-controls select{min-width:0}.board{min-width:1180px}.task-detail-scroll{padding:18px}.task-detail-modal>.modal-actions{padding:12px 18px}.run-meta{grid-template-columns:repeat(2,minmax(0,1fr))}.directory-layout{grid-template-columns:1fr}.directory-sidebar{border-right:0;border-bottom:1px solid var(--line)}.directory-locations{display:flex;overflow-x:auto}.directory-location{width:auto;min-width:max-content}.directory-toolbar{grid-template-columns:auto auto 1fr}.directory-list{height:270px}}
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<div class="app-shell">
|
|
24
|
+
<aside class="sidebar">
|
|
25
|
+
<a class="brand" href="/agent-team"><span class="brand-icon">✦</span><span>Agent Team</span></a>
|
|
26
|
+
<nav class="nav" aria-label="Agent Team 导航">
|
|
27
|
+
<span class="nav-item active"><span class="nav-icon">▦</span><span>任务看板</span></span>
|
|
28
|
+
<button type="button" class="nav-item" data-feature="工作目录"><span class="nav-icon">⌕</span><span>工作目录</span></button>
|
|
29
|
+
<button type="button" class="nav-item" data-feature="同步设置"><span class="nav-icon">↻</span><span>同步设置</span></button>
|
|
30
|
+
</nav>
|
|
31
|
+
<div class="side-note">本地优先<br>数据保存在 ~/.chatccc<br><strong>● Node 后端已连接</strong></div>
|
|
32
|
+
</aside>
|
|
33
|
+
<main class="shell">
|
|
34
|
+
<div class="top"><div><div class="eyebrow">LOCAL WORKSPACE</div><h1>Agent Team</h1><p>每个工作目录拥有独立的本地任务看板</p></div><a href="/">返回设置</a></div>
|
|
35
|
+
<section class="workspace-bar">
|
|
36
|
+
<select id="recent" aria-label="最近工作目录"><option value="">最近工作目录</option></select>
|
|
37
|
+
<input id="workspace-path" type="text" spellcheck="false" placeholder="输入本机工作目录绝对路径">
|
|
38
|
+
<button id="pick" class="ghost">选择文件夹</button>
|
|
39
|
+
<button id="relink" class="hidden">重新关联</button>
|
|
40
|
+
</section>
|
|
41
|
+
<section class="main-agent-bar" aria-label="项目主 Agent">
|
|
42
|
+
<div class="main-agent-copy"><strong>项目主 Agent</strong><span id="main-agent-status" class="agent-status">请先打开一个项目</span></div>
|
|
43
|
+
<div class="main-agent-controls"><select id="main-agent" disabled aria-label="选择主 Agent"><option value="">选择主 Agent</option></select><button id="save-main-agent" class="primary" disabled>设置为主 Agent</button></div>
|
|
44
|
+
</section>
|
|
45
|
+
<div class="status-row"><span id="workspace-name" class="workspace-name">尚未打开工作目录</span><span id="save-state" class="save-state">本地 JSON</span></div>
|
|
46
|
+
<div class="board-scroll"><section class="board" id="board">
|
|
47
|
+
<article class="column" data-column="brainstorm"><header class="column-head"><span class="column-dot"></span><span class="column-title">头脑风暴</span><span class="count">0</span><button type="button" class="column-add" data-add="brainstorm" aria-label="在头脑风暴中添加任务" title="添加任务">+</button></header><div class="task-list"></div></article>
|
|
48
|
+
<article class="column" data-column="todo"><header class="column-head"><span class="column-dot"></span><span class="column-title">Todo</span><span class="count">0</span><button type="button" class="column-add" data-add="todo" aria-label="在 Todo 中添加任务" title="添加任务">+</button></header><div class="task-list"></div></article>
|
|
49
|
+
<article class="column" data-column="doing"><header class="column-head"><span class="column-dot"></span><span class="column-title">Doing</span><span class="count">0</span><button type="button" class="column-add" data-add="doing" aria-label="在 Doing 中添加任务" title="添加任务">+</button></header><div class="task-list"></div></article>
|
|
50
|
+
<article class="column" data-column="done"><header class="column-head"><span class="column-dot"></span><span class="column-title">Done</span><span class="count">0</span><button type="button" class="column-add" data-add="done" aria-label="在 Done 中添加任务" title="添加任务">+</button></header><div class="task-list"></div></article>
|
|
51
|
+
<article class="column" data-column="on_hold"><header class="column-head"><span class="column-dot"></span><span class="column-title">搁置</span><span class="count">0</span><button type="button" class="column-add" data-add="on_hold" aria-label="在搁置中添加任务" title="添加任务">+</button></header><div class="task-list"></div></article>
|
|
52
|
+
</section></div>
|
|
53
|
+
</main>
|
|
54
|
+
</div>
|
|
55
|
+
<div id="modal" class="modal-backdrop hidden" role="dialog" aria-modal="true">
|
|
56
|
+
<form class="modal task-detail-modal" id="task-form">
|
|
57
|
+
<div class="task-detail-scroll">
|
|
58
|
+
<h2 id="modal-title">添加任务</h2>
|
|
59
|
+
<div class="field"><label for="task-title">标题</label><input id="task-title" maxlength="200" required></div>
|
|
60
|
+
<div class="field"><label for="task-description">描述</label><textarea id="task-description" maxlength="20000"></textarea></div>
|
|
61
|
+
<section id="task-execution-details" class="task-execution-details hidden">
|
|
62
|
+
<div class="task-execution-title"><h3>完整执行过程</h3><span id="task-run-status" class="run-status"></span></div>
|
|
63
|
+
<div id="task-run-meta" class="run-meta"></div>
|
|
64
|
+
<div id="task-transcript" class="execution-timeline"></div>
|
|
65
|
+
</section>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="modal-actions"><button type="button" id="cancel" class="ghost">取消</button><button type="submit" id="task-save" class="primary">保存</button></div>
|
|
68
|
+
</form>
|
|
69
|
+
</div>
|
|
70
|
+
<div id="dm-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="dm-modal-title">
|
|
71
|
+
<div class="modal">
|
|
72
|
+
<h2 id="dm-modal-title">需要识别飞书用户</h2>
|
|
73
|
+
<p style="color:var(--muted);line-height:1.7;margin:0 0 16px">请先给飞书机器人私聊发送任意消息。检测到私聊后,网页会自动继续创建主 Agent 群。</p>
|
|
74
|
+
<div id="dm-detect-status" class="agent-status provisioning" style="margin-bottom:18px">正在等待私聊消息…</div>
|
|
75
|
+
<div class="modal-actions"><button type="button" id="dm-cancel" class="ghost">取消</button><button type="button" id="dm-check" class="primary">立即检测</button></div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div id="directory-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="directory-modal-title">
|
|
79
|
+
<div class="modal directory-modal">
|
|
80
|
+
<h2 id="directory-modal-title">选择工作目录</h2>
|
|
81
|
+
<div class="directory-layout">
|
|
82
|
+
<aside class="directory-sidebar"><div class="directory-label">快捷位置</div><div id="directory-locations" class="directory-locations"></div></aside>
|
|
83
|
+
<section class="directory-main">
|
|
84
|
+
<div class="directory-toolbar"><button type="button" id="directory-up" class="ghost" title="返回上级">↑</button><button type="button" id="directory-refresh" class="ghost" title="刷新">↻</button><input id="directory-path" aria-label="目录路径" spellcheck="false"></div>
|
|
85
|
+
<div id="directory-list" class="directory-list"><div class="directory-empty">正在读取目录…</div></div>
|
|
86
|
+
<div class="directory-footer"><label><input type="checkbox" id="directory-show-hidden">显示隐藏目录</label><span id="directory-selected" class="directory-selected">请选择当前目录或一个子目录</span></div>
|
|
87
|
+
</section>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="modal-actions"><button type="button" id="directory-cancel" class="ghost">取消</button><button type="button" id="directory-confirm" class="primary">选择当前文件夹</button></div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div id="feature-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="feature-modal-title">
|
|
93
|
+
<div class="modal">
|
|
94
|
+
<h2 id="feature-modal-title">功能尚未实现</h2>
|
|
95
|
+
<p id="feature-modal-message" style="color:var(--muted);line-height:1.7;margin:0 0 16px"></p>
|
|
96
|
+
<div class="modal-actions"><button type="button" id="feature-modal-ok" class="primary">知道了</button></div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
<div id="create-board-modal" class="modal-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="create-board-modal-title">
|
|
100
|
+
<div class="modal">
|
|
101
|
+
<h2 id="create-board-modal-title">新建看板</h2>
|
|
102
|
+
<p style="color:var(--muted);line-height:1.7;margin:0 0 8px">这个路径还没有创建过看板,是否为这个路径新建看板?</p>
|
|
103
|
+
<p id="create-board-path" style="word-break:break-all;background:var(--soft);border:1px solid var(--line);border-radius:9px;padding:10px 12px;margin:0 0 18px"></p>
|
|
104
|
+
<div class="modal-actions"><button type="button" id="create-board-cancel" class="ghost">取消</button><button type="button" id="create-board-confirm" class="primary">新建看板</button></div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<div id="toast" class="toast hidden"></div>
|
|
108
|
+
<script>
|
|
109
|
+
(function(){
|
|
110
|
+
var state={board:null,binding:null,runs:[],runPollTimer:null,runPollBusy:false,workspaces:[],agentOptions:[],dragTaskId:null,modalTaskId:null,modalColumnId:null,modalDetailVersion:0,relinkBoardId:null,pendingAgentId:null,pendingWorkspacePath:null,workspaceSelectionVersion:0,directoryLocations:[],directoryPath:null,directoryParentPath:null,directorySelectedPath:null,directoryBrowseVersion:0,contactTimer:null,contactChecking:false,mutationQueue:Promise.resolve()};
|
|
111
|
+
var columns=['brainstorm','todo','doing','done','on_hold'];
|
|
112
|
+
var recent=document.getElementById('recent');
|
|
113
|
+
var pathInput=document.getElementById('workspace-path');
|
|
114
|
+
var relinkButton=document.getElementById('relink');
|
|
115
|
+
var saveState=document.getElementById('save-state');
|
|
116
|
+
var mainAgentSelect=document.getElementById('main-agent');
|
|
117
|
+
var mainAgentButton=document.getElementById('save-main-agent');
|
|
118
|
+
|
|
119
|
+
async function api(path,options){
|
|
120
|
+
var response=await fetch(path,Object.assign({headers:{'content-type':'application/json; charset=utf-8'}},options||{}));
|
|
121
|
+
var data=await response.json().catch(function(){return {error:'响应不是有效 JSON'};});
|
|
122
|
+
if(!response.ok){var error=new Error(data.error||('HTTP '+response.status));error.code=data.code;throw error;}
|
|
123
|
+
return data;
|
|
124
|
+
}
|
|
125
|
+
function request(method,path,body){return api(path,{method:method,body:body===undefined?undefined:JSON.stringify(body)});}
|
|
126
|
+
function setSaving(label,kind){saveState.textContent=label;saveState.className='save-state '+(kind||'');}
|
|
127
|
+
function toast(message,error){var el=document.getElementById('toast');el.textContent=message;el.className='toast'+(error?' error':'');clearTimeout(toast.timer);toast.timer=setTimeout(function(){el.className='toast hidden';},3500);}
|
|
128
|
+
function mutate(operation){
|
|
129
|
+
if(!state.board)return Promise.resolve(false);
|
|
130
|
+
state.mutationQueue=state.mutationQueue.then(async function(){
|
|
131
|
+
setSaving('保存中…','saving');
|
|
132
|
+
try{var result=await operation();state.board=result.board;if(Object.prototype.hasOwnProperty.call(result,'binding'))state.binding=result.binding;renderBoard();setSaving('已保存','');return true;}
|
|
133
|
+
catch(error){setSaving('保存失败','error');if(error.code==='revision_conflict'){try{await reloadBoard();toast('其他页面修改了看板,已载入最新版本',true);}catch(reloadError){toast(reloadError.message,true);}}else toast(error.message,true);return false;}
|
|
134
|
+
});
|
|
135
|
+
return state.mutationQueue;
|
|
136
|
+
}
|
|
137
|
+
async function refreshWorkspaces(selectBoardId){
|
|
138
|
+
var data=await api('/api/agent-team/workspaces');state.workspaces=data.workspaces||[];state.agentOptions=data.agentOptions||[];recent.innerHTML='<option value="">最近工作目录</option>';
|
|
139
|
+
state.workspaces.forEach(function(item){var option=document.createElement('option');option.value=item.boardId;option.textContent=(item.exists?'':'⚠ ')+item.workspacePath;recent.appendChild(option);});
|
|
140
|
+
if(selectBoardId)recent.value=selectBoardId;
|
|
141
|
+
return data.defaultWorkspace;
|
|
142
|
+
}
|
|
143
|
+
async function openWorkspace(path,selectionVersion){
|
|
144
|
+
if(!path||!path.trim())return toast('请输入工作目录',true);
|
|
145
|
+
var version=selectionVersion===undefined?++state.workspaceSelectionVersion:selectionVersion;
|
|
146
|
+
await state.mutationQueue;
|
|
147
|
+
setSaving('加载中…','saving');
|
|
148
|
+
try{var result=await request('POST','/api/agent-team/open',{workspacePath:path.trim()});if(version!==state.workspaceSelectionVersion)return;state.board=result.board;state.binding=result.binding||null;state.runs=[];state.relinkBoardId=null;relinkButton.classList.add('hidden');pathInput.value=state.board.workspacePath;await Promise.all([refreshWorkspaces(state.board.boardId),loadRuns(false)]);if(version!==state.workspaceSelectionVersion)return;renderBoard();scheduleRunPoll();setSaving('已保存','');}
|
|
149
|
+
catch(error){if(version!==state.workspaceSelectionVersion)return;setSaving('打开失败','error');toast(error.message,true);}
|
|
150
|
+
}
|
|
151
|
+
async function selectWorkspacePath(path){
|
|
152
|
+
if(!path||!path.trim())return toast('请输入工作目录',true);
|
|
153
|
+
var workspacePath=path.trim();var version=++state.workspaceSelectionVersion;
|
|
154
|
+
await state.mutationQueue;
|
|
155
|
+
setSaving('检查中…','saving');
|
|
156
|
+
try{var lookup=await request('POST','/api/agent-team/lookup',{workspacePath:workspacePath});if(version!==state.workspaceSelectionVersion)return;if(lookup.exists)return openWorkspace(workspacePath,version);state.pendingWorkspacePath=workspacePath;pathInput.value=workspacePath;document.getElementById('create-board-path').textContent=workspacePath;document.getElementById('create-board-modal').classList.remove('hidden');setSaving(state.board?'已保存':'本地 JSON','');}
|
|
157
|
+
catch(error){if(version!==state.workspaceSelectionVersion)return;setSaving('打开失败','error');toast(error.message,true);}
|
|
158
|
+
}
|
|
159
|
+
function closeCreateBoardModal(keepPath){document.getElementById('create-board-modal').classList.add('hidden');state.pendingWorkspacePath=null;if(!keepPath&&state.board)pathInput.value=state.board.workspacePath;}
|
|
160
|
+
function showFeatureModal(feature){document.getElementById('feature-modal-message').textContent='“'+feature+'”功能还未实现,后续版本会开放。';document.getElementById('feature-modal').classList.remove('hidden');}
|
|
161
|
+
function closeFeatureModal(){document.getElementById('feature-modal').classList.add('hidden');}
|
|
162
|
+
function renderDirectoryLocations(){var root=document.getElementById('directory-locations');root.innerHTML='';state.directoryLocations.forEach(function(location){var button=document.createElement('button');button.type='button';button.className='directory-location';button.textContent=(location.kind==='drive'?'▣ ':'⌂ ')+location.label;button.title=location.path;button.addEventListener('click',function(){browseDirectory(location.path);});root.appendChild(button);});}
|
|
163
|
+
function renderDirectoryEntries(entries){var root=document.getElementById('directory-list');root.innerHTML='';if(!entries.length){var empty=document.createElement('div');empty.className='directory-empty';empty.textContent='这个目录中没有可显示的子目录';root.appendChild(empty);return;}entries.forEach(function(entry){var row=document.createElement('button');row.type='button';row.className='directory-row';row.title='双击进入 '+entry.path;var icon=document.createElement('span');icon.className='directory-folder-icon';icon.textContent='▰';var name=document.createElement('span');name.textContent=entry.name;row.append(icon,name);row.addEventListener('click',function(){state.directorySelectedPath=entry.path;root.querySelectorAll('.directory-row').forEach(function(item){item.classList.remove('selected');});row.classList.add('selected');document.getElementById('directory-selected').textContent=entry.path;document.getElementById('directory-confirm').textContent='选择此文件夹';});row.addEventListener('dblclick',function(){browseDirectory(entry.path);});root.appendChild(row);});}
|
|
164
|
+
async function browseDirectory(path){if(!path)return false;var version=++state.directoryBrowseVersion;var root=document.getElementById('directory-list');root.innerHTML='<div class="directory-empty">正在读取目录…</div>';document.getElementById('directory-path').value=path;try{var result=await request('POST','/api/agent-team/filesystem/directories',{path:path,showHidden:document.getElementById('directory-show-hidden').checked});if(version!==state.directoryBrowseVersion)return false;state.directoryPath=result.directory.path;state.directoryParentPath=result.directory.parentPath;state.directorySelectedPath=null;document.getElementById('directory-path').value=result.directory.path;document.getElementById('directory-up').disabled=!result.directory.parentPath;document.getElementById('directory-selected').textContent=result.directory.path;document.getElementById('directory-confirm').textContent='选择当前文件夹';renderDirectoryEntries(result.directory.entries||[]);return true;}catch(error){if(version!==state.directoryBrowseVersion)return false;root.innerHTML='';var empty=document.createElement('div');empty.className='directory-empty';empty.textContent=error.message;root.appendChild(empty);return false;}}
|
|
165
|
+
async function openDirectoryModal(){state.directoryPath=null;state.directoryParentPath=null;state.directorySelectedPath=null;document.getElementById('directory-modal').classList.remove('hidden');document.getElementById('directory-locations').innerHTML='';document.getElementById('directory-list').innerHTML='<div class="directory-empty">正在读取目录…</div>';try{var result=await api('/api/agent-team/filesystem/locations');state.directoryLocations=result.locations||[];renderDirectoryLocations();var candidate=pathInput.value.trim()||(state.board&&state.board.workspacePath)||(state.directoryLocations[0]&&state.directoryLocations[0].path);var opened=await browseDirectory(candidate);if(!opened&&state.directoryLocations[0]&&state.directoryLocations[0].path!==candidate)await browseDirectory(state.directoryLocations[0].path);}catch(error){var root=document.getElementById('directory-list');root.innerHTML='';var empty=document.createElement('div');empty.className='directory-empty';empty.textContent=error.message;root.appendChild(empty);}}
|
|
166
|
+
function closeDirectoryModal(){state.directoryBrowseVersion++;document.getElementById('directory-modal').classList.add('hidden');}
|
|
167
|
+
async function confirmDirectorySelection(){var target=state.directorySelectedPath||state.directoryPath;if(!target)return;var button=document.getElementById('directory-confirm');button.disabled=true;try{var result=await request('POST','/api/agent-team/filesystem/validate-directory',{path:target});closeDirectoryModal();pathInput.value=result.path;if(!state.relinkBoardId)selectWorkspacePath(result.path);}catch(error){toast(error.message,true);}finally{button.disabled=false;}}
|
|
168
|
+
async function reloadBoard(){if(!state.board)return;var result=await api('/api/agent-team/boards/'+encodeURIComponent(state.board.boardId));state.board=result.board;state.binding=result.binding||null;renderBoard();}
|
|
169
|
+
function workspaceShortName(value){var path=String(value||'');var slash=String.fromCharCode(92);while(path.endsWith('/')||path.endsWith(slash))path=path.slice(0,-1);var index=Math.max(path.lastIndexOf('/'),path.lastIndexOf(slash));return path.slice(index+1)||path;}
|
|
170
|
+
async function loadRuns(render){if(!state.board)return;var boardId=state.board.boardId;var result=await api('/api/agent-team/boards/'+encodeURIComponent(boardId)+'/runs');if(!state.board||state.board.boardId!==boardId)return;state.runs=result.runs||[];if(render!==false)renderBoard();}
|
|
171
|
+
function latestRun(taskId){return state.runs.find(function(run){return run.taskId===taskId;})||null;}
|
|
172
|
+
function isActiveRun(run){return !!run&&(run.state==='queued'||run.state==='running');}
|
|
173
|
+
function runLabel(run){var labels={queued:'等待执行',running:run.stopRequestedAt?'正在停止':'主 Agent 执行中',succeeded:'执行成功',failed:'执行失败',canceled:'已停止',interrupted:'重启时中断'};return labels[run.state]||run.state;}
|
|
174
|
+
function scheduleRunPoll(){if(state.runPollTimer){clearTimeout(state.runPollTimer);state.runPollTimer=null;}if(!state.runs.some(isActiveRun))return;state.runPollTimer=setTimeout(pollRuns,1500);}
|
|
175
|
+
async function pollRuns(){if(state.runPollBusy||!state.board)return scheduleRunPoll();state.runPollBusy=true;var boardId=state.board.boardId;try{var results=await Promise.all([api('/api/agent-team/boards/'+encodeURIComponent(boardId)),api('/api/agent-team/boards/'+encodeURIComponent(boardId)+'/runs')]);if(state.board&&state.board.boardId===boardId){state.board=results[0].board;state.binding=results[0].binding||null;state.runs=results[1].runs||[];renderBoard();if(state.modalTaskId)await loadOpenTaskRunDetails(false);if(!state.runs.some(isActiveRun))setSaving('已保存','');}}catch(error){toast(error.message,true);}finally{state.runPollBusy=false;scheduleRunPoll();}}
|
|
176
|
+
async function startTaskRun(task){if(!state.board)return;if(!state.binding||state.binding.status!=='ready')return toast('请先设置可用的主 Agent',true);try{setSaving('正在交给主 Agent…','saving');var result=await request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks/'+encodeURIComponent(task.id)+'/run',{expectedRevision:state.board.revision});state.board=result.board;state.runs=[result.run].concat(state.runs.filter(function(run){return run.runId!==result.run.runId;}));renderBoard();scheduleRunPoll();setSaving('任务执行中','saving');}catch(error){if(error.code==='revision_conflict')await reloadBoard();toast(error.message,true);setSaving('执行失败','error');}}
|
|
177
|
+
async function stopTaskRun(run){if(!state.board)return;try{var result=await request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/runs/'+encodeURIComponent(run.runId)+'/stop',{});state.runs=state.runs.map(function(item){return item.runId===result.run.runId?result.run:item;});renderBoard();scheduleRunPoll();toast('已请求主 Agent 停止任务');}catch(error){toast(error.message,true);}}
|
|
178
|
+
function activeTasks(columnId){return state.board?state.board.tasks.filter(function(task){return !task.deletedAt&&task.columnId===columnId;}).sort(function(a,b){return a.order-b.order;}):[];}
|
|
179
|
+
function renderBoard(){
|
|
180
|
+
document.getElementById('workspace-name').textContent=state.board?state.board.workspacePath:'尚未打开工作目录';
|
|
181
|
+
renderMainAgent();
|
|
182
|
+
columns.forEach(function(columnId){var column=document.querySelector('[data-column="'+columnId+'"]');var list=column.querySelector('.task-list');var tasks=activeTasks(columnId);column.querySelector('.count').textContent=String(tasks.length);list.innerHTML='';
|
|
183
|
+
if(!tasks.length){var empty=document.createElement('div');empty.className='empty';empty.textContent='暂无任务';list.appendChild(empty);}
|
|
184
|
+
tasks.forEach(function(task){var run=latestRun(task.id);var active=isActiveRun(run);var dragged=false;var card=document.createElement('article');card.className='task'+(active?' running':'');card.draggable=!active;card.dataset.taskId=task.id;
|
|
185
|
+
var title=document.createElement('h3');title.textContent=task.title;card.appendChild(title);if(task.description){var desc=document.createElement('p');desc.textContent=task.description;card.appendChild(desc);}
|
|
186
|
+
var actions=document.createElement('div');actions.className='task-actions';var edit=document.createElement('button');edit.type='button';edit.className='icon-btn';edit.textContent='编辑';edit.addEventListener('click',function(event){event.stopPropagation();showEdit(task);});var del=document.createElement('button');del.type='button';del.className='icon-btn';del.textContent='删除';del.disabled=active;del.addEventListener('click',function(event){event.stopPropagation();removeTask(task);});actions.append(edit,del);card.appendChild(actions);
|
|
187
|
+
var runBox=document.createElement('div');runBox.className='task-run';var runHead=document.createElement('div');runHead.className='task-run-head';var status=document.createElement('span');status.className='run-status '+(run?run.state:'');status.textContent=run?runLabel(run)+(run.attempt>1?' · 第 '+run.attempt+' 次':''):'尚未执行';var runButton=document.createElement('button');runButton.type='button';runButton.className='run-button '+(!run||!active?'primary':'danger');runButton.textContent=active?'停止':(run?'重试':'交给主 Agent');runButton.addEventListener('click',function(event){event.stopPropagation();if(active)stopTaskRun(run);else startTaskRun(task);});runHead.append(status,runButton);runBox.appendChild(runHead);if(run&&(run.result||run.error)){var resultText=document.createElement('p');resultText.className='run-result';resultText.textContent=run.error||run.result;runBox.appendChild(resultText);}card.appendChild(runBox);
|
|
188
|
+
card.addEventListener('click',function(){if(!dragged)showEdit(task);});card.addEventListener('dragstart',function(event){if(active){event.preventDefault();return;}dragged=true;state.dragTaskId=task.id;card.classList.add('dragging');event.dataTransfer.effectAllowed='move';});card.addEventListener('dragend',function(){state.dragTaskId=null;card.classList.remove('dragging');document.querySelectorAll('.column').forEach(function(el){el.classList.remove('drag-over');});setTimeout(function(){dragged=false;},0);});list.appendChild(card);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function renderMainAgent(){
|
|
193
|
+
var selected=state.board?(state.board.primaryAgentId||(state.binding&&state.binding.agentId)||''):'';mainAgentSelect.innerHTML='<option value="">选择主 Agent</option>';
|
|
194
|
+
state.agentOptions.forEach(function(agent){var option=document.createElement('option');option.value=agent.id;option.textContent=agent.label;mainAgentSelect.appendChild(option);});
|
|
195
|
+
mainAgentSelect.value=selected;mainAgentSelect.disabled=!state.board;mainAgentButton.disabled=!state.board||!mainAgentSelect.value;
|
|
196
|
+
var status=document.getElementById('main-agent-status');var binding=state.binding;
|
|
197
|
+
if(!state.board){status.textContent='请先打开一个项目';status.className='agent-status';mainAgentButton.textContent='设置为主 Agent';return;}
|
|
198
|
+
if(!selected){status.textContent='尚未设置;设置后将创建“主Agent-'+workspaceShortName(state.board.workspacePath)+'”群聊';status.className='agent-status';mainAgentButton.textContent='设置为主 Agent';return;}
|
|
199
|
+
if(binding&&binding.status==='ready'){status.textContent='主 Agent 群已就绪';status.className='agent-status ready';}
|
|
200
|
+
else if(binding&&binding.status==='error'){status.textContent='创建失败,可重新设置后重试';status.className='agent-status error';}
|
|
201
|
+
else{status.textContent='正在准备主 Agent 群';status.className='agent-status provisioning';}
|
|
202
|
+
mainAgentButton.textContent='设置为主 Agent';
|
|
203
|
+
}
|
|
204
|
+
function closeDmModal(){document.getElementById('dm-modal').classList.add('hidden');if(state.contactTimer){clearInterval(state.contactTimer);state.contactTimer=null;}state.contactChecking=false;}
|
|
205
|
+
function showDmModal(){document.getElementById('dm-modal').classList.remove('hidden');document.getElementById('dm-detect-status').textContent='正在等待私聊消息…';if(state.contactTimer)clearInterval(state.contactTimer);state.contactTimer=setInterval(checkFeishuContact,1500);}
|
|
206
|
+
async function checkFeishuContact(){if(state.contactChecking)return;state.contactChecking=true;try{var result=await api('/api/agent-team/feishu-contact');if(result.contact){var pending=state.pendingAgentId;closeDmModal();toast('已识别飞书私聊用户,继续创建主 Agent 群');if(pending)saveMainAgent(pending);}}catch(error){document.getElementById('dm-detect-status').textContent='检测失败:'+error.message;}finally{state.contactChecking=false;}}
|
|
207
|
+
function saveMainAgent(agentId){
|
|
208
|
+
if(!state.board||!agentId)return;state.pendingAgentId=agentId;
|
|
209
|
+
state.mutationQueue=state.mutationQueue.then(async function(){setSaving('创建主 Agent…','saving');mainAgentButton.disabled=true;try{var result=await request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/main-agent',{expectedRevision:state.board.revision,agentId:agentId});state.board=result.board;state.binding=result.binding;state.pendingAgentId=null;renderBoard();setSaving('已保存','');toast('主 Agent 群已就绪');return true;}catch(error){setSaving('设置失败','error');renderBoard();if(error.code==='feishu_dm_required')showDmModal();else if(error.code==='revision_conflict'){try{await reloadBoard();toast('其他页面修改了项目,已载入最新版本',true);}catch(reloadError){toast(reloadError.message,true);}}else toast(error.message,true);return false;}});return state.mutationQueue;
|
|
210
|
+
}
|
|
211
|
+
function formatRunTime(value){if(!value)return '—';var date=new Date(value);return Number.isNaN(date.getTime())?String(value):date.toLocaleString();}
|
|
212
|
+
function addRunMeta(root,label,value){var item=document.createElement('div');item.className='run-meta-item';var key=document.createElement('span');key.textContent=label;var content=document.createElement('strong');content.textContent=value||'—';content.title=value||'—';item.append(key,content);root.appendChild(item);}
|
|
213
|
+
function transcriptLabel(entry){var labels={prompt:'任务指令',thinking:'思考',text:'主 Agent 回复',tool_use:'调用工具',tool_result:'工具结果',search:'联网搜索',compact:'上下文压缩',status:'状态变化',notice:'提示'};var label=labels[entry.type]||entry.type;if(entry.name)label+=' · '+entry.name;return label;}
|
|
214
|
+
function transcriptContent(entry){if(entry.type==='tool_use')return entry.input||'';if(entry.type==='tool_result')return entry.output||'';return entry.text||'';}
|
|
215
|
+
function addTranscriptEntry(root,entry){var row=document.createElement('article');row.className='timeline-entry '+entry.type+(entry.isError?' error':'');var head=document.createElement('div');head.className='timeline-head';var label=document.createElement('span');label.textContent=transcriptLabel(entry);var time=document.createElement('span');time.textContent=formatRunTime(entry.at);head.append(label,time);var body=document.createElement('pre');body.textContent=transcriptContent(entry)||'(无文本内容)';row.append(head,body);root.appendChild(row);}
|
|
216
|
+
function renderTaskExecution(task,run){var details=document.getElementById('task-execution-details');var title=document.getElementById('task-title');var description=document.getElementById('task-description');var save=document.getElementById('task-save');var cancel=document.getElementById('cancel');var active=isActiveRun(run);title.disabled=active;description.disabled=active;save.classList.toggle('hidden',active);cancel.textContent=active?'关闭':'取消';details.classList.remove('hidden');var status=document.getElementById('task-run-status');status.className='run-status '+(run?run.state:'');status.textContent=run?runLabel(run):'尚未执行';var meta=document.getElementById('task-run-meta');meta.innerHTML='';if(run){addRunMeta(meta,'执行次数','第 '+run.attempt+' 次');addRunMeta(meta,'执行 Agent',run.agentId);addRunMeta(meta,'开始时间',formatRunTime(run.startedAt||run.createdAt));addRunMeta(meta,'结束时间',formatRunTime(run.finishedAt));}else{addRunMeta(meta,'任务状态','尚未交给主 Agent');addRunMeta(meta,'创建时间',formatRunTime(task.createdAt));addRunMeta(meta,'更新时间',formatRunTime(task.updatedAt));}var transcript=document.getElementById('task-transcript');transcript.innerHTML='';var entries=run&&Array.isArray(run.transcript)?run.transcript.slice():[];var hasText=entries.some(function(entry){return entry.type==='text';});if(run&&run.result&&!hasText)entries.push({type:'text',at:run.finishedAt||run.updatedAt,text:run.result});if(run&&run.error)entries.push({type:'notice',at:run.finishedAt||run.updatedAt,text:run.error,isError:true});if(!entries.length){var empty=document.createElement('div');empty.className='timeline-empty';empty.textContent=run?'暂时还没有可显示的执行事件':'任务尚未执行,交给主 Agent 后会在这里记录完整过程。';transcript.appendChild(empty);return;}entries.forEach(function(entry){addTranscriptEntry(transcript,entry);});}
|
|
217
|
+
async function loadOpenTaskRunDetails(showError){if(!state.board||!state.modalTaskId)return;var task=state.board.tasks.find(function(candidate){return candidate.id===state.modalTaskId&&!candidate.deletedAt;});if(!task)return;var summary=latestRun(task.id);if(!summary){renderTaskExecution(task,null);return;}var version=++state.modalDetailVersion;try{var result=await api('/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/runs/'+encodeURIComponent(summary.runId));if(version!==state.modalDetailVersion||state.modalTaskId!==task.id)return;renderTaskExecution(task,result.run);}catch(error){if(showError!==false)toast(error.message,true);}}
|
|
218
|
+
function showCreate(columnId){if(!state.board)return toast('请先打开一个工作目录',true);state.modalTaskId=null;state.modalColumnId=columnId;document.getElementById('modal-title').textContent='添加任务';document.getElementById('task-title').value='';document.getElementById('task-description').value='';document.getElementById('task-title').disabled=false;document.getElementById('task-description').disabled=false;document.getElementById('task-save').classList.remove('hidden');document.getElementById('cancel').textContent='取消';document.getElementById('task-execution-details').classList.add('hidden');document.getElementById('modal').classList.remove('hidden');setTimeout(function(){document.getElementById('task-title').focus();},0);}
|
|
219
|
+
function showEdit(task){state.modalTaskId=task.id;state.modalColumnId=task.columnId;document.getElementById('modal-title').textContent='任务详情';document.getElementById('task-title').value=task.title;document.getElementById('task-description').value=task.description;renderTaskExecution(task,latestRun(task.id));document.getElementById('modal').classList.remove('hidden');loadOpenTaskRunDetails(true);}
|
|
220
|
+
function closeModal(){state.modalDetailVersion++;document.getElementById('modal').classList.add('hidden');state.modalTaskId=null;}
|
|
221
|
+
function removeTask(task){if(!confirm('删除任务“'+task.title+'”?'))return;mutate(function(){return request('DELETE','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks/'+encodeURIComponent(task.id),{expectedRevision:state.board.revision});});}
|
|
222
|
+
|
|
223
|
+
document.querySelectorAll('[data-add]').forEach(function(button){button.addEventListener('click',function(){showCreate(button.dataset.add);});});
|
|
224
|
+
document.querySelectorAll('.column').forEach(function(column){
|
|
225
|
+
column.addEventListener('dragover',function(event){if(!state.dragTaskId)return;event.preventDefault();column.classList.add('drag-over');event.dataTransfer.dropEffect='move';});column.addEventListener('dragleave',function(event){if(!column.contains(event.relatedTarget))column.classList.remove('drag-over');});
|
|
226
|
+
column.addEventListener('drop',function(event){event.preventDefault();column.classList.remove('drag-over');if(!state.dragTaskId||!state.board)return;var columnId=column.dataset.column;var cards=Array.from(column.querySelectorAll('.task:not(.dragging)'));var index=cards.findIndex(function(card){return event.clientY<card.getBoundingClientRect().top+card.getBoundingClientRect().height/2;});if(index<0)index=cards.length;var taskId=state.dragTaskId;mutate(function(){return request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks/'+encodeURIComponent(taskId)+'/move',{expectedRevision:state.board.revision,columnId:columnId,index:index});});});
|
|
227
|
+
});
|
|
228
|
+
document.getElementById('task-form').addEventListener('submit',function(event){event.preventDefault();var title=document.getElementById('task-title').value;var description=document.getElementById('task-description').value;var taskId=state.modalTaskId;var columnId=state.modalColumnId;closeModal();mutate(function(){var root='/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks';return taskId?request('PATCH',root+'/'+encodeURIComponent(taskId),{expectedRevision:state.board.revision,title:title,description:description}):request('POST',root,{expectedRevision:state.board.revision,title:title,description:description,columnId:columnId});});});
|
|
229
|
+
document.getElementById('cancel').addEventListener('click',closeModal);document.getElementById('modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeModal();});
|
|
230
|
+
document.querySelectorAll('[data-feature]').forEach(function(button){button.addEventListener('click',function(){showFeatureModal(button.dataset.feature);});});
|
|
231
|
+
document.getElementById('feature-modal-ok').addEventListener('click',closeFeatureModal);document.getElementById('feature-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeFeatureModal();});
|
|
232
|
+
document.getElementById('directory-cancel').addEventListener('click',closeDirectoryModal);document.getElementById('directory-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeDirectoryModal();});document.getElementById('directory-confirm').addEventListener('click',confirmDirectorySelection);document.getElementById('directory-up').addEventListener('click',function(){if(state.directoryParentPath)browseDirectory(state.directoryParentPath);});document.getElementById('directory-refresh').addEventListener('click',function(){if(state.directoryPath)browseDirectory(state.directoryPath);});document.getElementById('directory-show-hidden').addEventListener('change',function(){if(state.directoryPath)browseDirectory(state.directoryPath);});document.getElementById('directory-path').addEventListener('keydown',function(event){if(event.key==='Enter'){event.preventDefault();browseDirectory(event.currentTarget.value);}});
|
|
233
|
+
document.getElementById('create-board-cancel').addEventListener('click',function(){closeCreateBoardModal(false);});document.getElementById('create-board-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeCreateBoardModal(false);});document.getElementById('create-board-confirm').addEventListener('click',function(){var path=state.pendingWorkspacePath;if(!path)return;closeCreateBoardModal(true);openWorkspace(path);});
|
|
234
|
+
mainAgentSelect.addEventListener('change',function(){mainAgentButton.disabled=!state.board||!mainAgentSelect.value;});mainAgentButton.addEventListener('click',function(){saveMainAgent(mainAgentSelect.value);});
|
|
235
|
+
document.getElementById('dm-cancel').addEventListener('click',function(){state.pendingAgentId=null;closeDmModal();});document.getElementById('dm-check').addEventListener('click',checkFeishuContact);
|
|
236
|
+
pathInput.addEventListener('change',function(){if(!state.relinkBoardId)selectWorkspacePath(pathInput.value);});pathInput.addEventListener('keydown',function(event){if(event.key==='Enter'){event.preventDefault();if(!state.relinkBoardId)selectWorkspacePath(pathInput.value);}});
|
|
237
|
+
document.getElementById('pick').addEventListener('click',openDirectoryModal);
|
|
238
|
+
recent.addEventListener('change',function(){var item=state.workspaces.find(function(candidate){return candidate.boardId===recent.value;});if(!item)return;pathInput.value=item.workspacePath;if(item.exists){state.relinkBoardId=null;relinkButton.classList.add('hidden');openWorkspace(item.workspacePath);}else{state.relinkBoardId=item.boardId;relinkButton.classList.remove('hidden');api('/api/agent-team/boards/'+encodeURIComponent(item.boardId)).then(async function(result){state.board=result.board;state.binding=result.binding||null;state.runs=[];await loadRuns(false);renderBoard();scheduleRunPoll();toast('原目录不存在,请选择新目录后重新关联',true);}).catch(function(error){toast(error.message,true);});}});
|
|
239
|
+
relinkButton.addEventListener('click',function(){if(!state.relinkBoardId||!state.board)return toast('请先打开或选择需要关联的看板',true);mutate(function(){return request('POST','/api/agent-team/boards/'+encodeURIComponent(state.relinkBoardId)+'/relink',{expectedRevision:state.board.revision,workspacePath:pathInput.value});}).then(function(saved){if(!saved)return;refreshWorkspaces(state.board.boardId);state.relinkBoardId=null;relinkButton.classList.add('hidden');});});
|
|
240
|
+
refreshWorkspaces().then(function(defaultWorkspace){pathInput.value=defaultWorkspace;return selectWorkspacePath(defaultWorkspace);}).catch(function(error){toast(error.message,true);});
|
|
241
|
+
})();
|
|
242
|
+
</script>
|
|
243
|
+
</body>
|
|
244
244
|
</html>`;
|
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
1
|
---
|
|
2
2
|
name: feishu-skill
|
|
3
|
-
description: Feishu IM local skills for sending
|
|
3
|
+
description: Feishu IM local skills for sending images, files, videos, and for creating new sessions or switching working directories.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Current working directory: {{cwd}}
|
|
7
|
+
Your session id: {{session_id}}
|
|
8
|
+
Your Feishu open_id: {{open_id}}
|
|
7
9
|
|
|
8
10
|
Use local endpoints instead of calling Feishu Open Platform directly.
|
|
9
11
|
|
|
10
|
-
- **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
|
-
- **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
|
-
- **
|
|
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`
|
|
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`
|
|
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.
|
package/package.json
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "chatccc",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Feishu bot bridge for Claude Code",
|
|
5
|
-
"license": "Apache-2.0",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "./dist/src/index.js",
|
|
8
|
-
"bin": {
|
|
9
|
-
"chatccc": "bin/chatccc.mjs",
|
|
10
|
-
"cccagent": "bin/cccagent.mjs"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist/",
|
|
14
|
-
"deepccc-agent/os-prompts/",
|
|
15
|
-
"bin/",
|
|
16
|
-
"scripts/postinstall-sharp-check.mjs",
|
|
17
|
-
"demo/ilink_echo_probe.ts",
|
|
18
|
-
"agent-prompts/",
|
|
19
|
-
"im-skills/",
|
|
20
|
-
".agents/skills/create-chatccc-feishu-app/",
|
|
21
|
-
".claude/skills/create-chatccc-feishu-app/",
|
|
22
|
-
".cursor/skills/create-chatccc-feishu-app/",
|
|
23
|
-
"images/img_readme_*.jpg",
|
|
24
|
-
"images/img_readme_*.png",
|
|
25
|
-
"images/avatars/status_*.png",
|
|
26
|
-
"images/avatars/badges/",
|
|
27
|
-
"images/avatars/combinations/",
|
|
28
|
-
"package.json",
|
|
29
|
-
"README.md",
|
|
30
|
-
"config.sample.json"
|
|
31
|
-
],
|
|
32
|
-
"scripts": {
|
|
33
|
-
"build": "node scripts/build.mjs",
|
|
34
|
-
"prepack": "npm run build",
|
|
35
|
-
"dev": "tsx src/index.ts",
|
|
36
|
-
"chatccc": "tsx src/index.ts",
|
|
37
|
-
"start": "tsx src/index.ts",
|
|
38
|
-
"demo:bot-test": "tsx demo/bot_test.ts",
|
|
39
|
-
"demo:bot-test:local": "tsx demo/bot_test.ts --local",
|
|
40
|
-
"demo:create-group": "tsx src/index.ts",
|
|
41
|
-
"demo:create-group:local": "tsx src/index.ts --local",
|
|
42
|
-
"demo:permission-check": "tsx demo/permission_check.ts",
|
|
43
|
-
"demo:claude-hi": "tsx demo/claude_say_hi.ts",
|
|
44
|
-
"demo:codex-hi": "tsx demo/codex_say_hi.ts",
|
|
45
|
-
"demo:codex-app-server-approval": "tsx demo/codex-app-server-approval/approval_demo.ts",
|
|
46
|
-
"demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
|
|
47
|
-
"claude-proxy": "tsx src/litellm-proxy.ts",
|
|
48
|
-
"test": "vitest run",
|
|
49
|
-
"test:deepccc": "vitest run --root deepccc-agent",
|
|
50
|
-
"test:watch": "vitest",
|
|
51
|
-
"postinstall": "node scripts/postinstall-sharp-check.mjs"
|
|
52
|
-
},
|
|
53
|
-
"dependencies": {
|
|
54
|
-
"@ai-sdk/anthropic": "^3.0.105",
|
|
55
|
-
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
56
|
-
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
57
|
-
"@openilink/openilink-sdk-node": "^0.6.0",
|
|
58
|
-
"@vscode/ripgrep": "^1.18.0",
|
|
59
|
-
"ai": "^6.0.184",
|
|
60
|
-
"nodemailer": "^8.0.7",
|
|
61
|
-
"qrcode-terminal": "^0.12.0",
|
|
62
|
-
"sharp": "^0.34.5",
|
|
63
|
-
"ws": "^8.18.0"
|
|
64
|
-
},
|
|
65
|
-
"devDependencies": {
|
|
66
|
-
"@types/node": "^20.0.0",
|
|
67
|
-
"@types/qrcode-terminal": "^0.12.2",
|
|
68
|
-
"@types/ws": "^8.18.1",
|
|
69
|
-
"tsx": "^4.0.0",
|
|
70
|
-
"typescript": "^5.0.0",
|
|
71
|
-
"vitest": "^3.2.4"
|
|
72
|
-
},
|
|
73
|
-
"engines": {
|
|
74
|
-
"node": ">=20"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "chatccc",
|
|
3
|
+
"version": "0.2.269",
|
|
4
|
+
"description": "Feishu bot bridge for Claude Code",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/src/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"chatccc": "bin/chatccc.mjs",
|
|
10
|
+
"cccagent": "bin/cccagent.mjs"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist/",
|
|
14
|
+
"deepccc-agent/os-prompts/",
|
|
15
|
+
"bin/",
|
|
16
|
+
"scripts/postinstall-sharp-check.mjs",
|
|
17
|
+
"demo/ilink_echo_probe.ts",
|
|
18
|
+
"agent-prompts/",
|
|
19
|
+
"im-skills/",
|
|
20
|
+
".agents/skills/create-chatccc-feishu-app/",
|
|
21
|
+
".claude/skills/create-chatccc-feishu-app/",
|
|
22
|
+
".cursor/skills/create-chatccc-feishu-app/",
|
|
23
|
+
"images/img_readme_*.jpg",
|
|
24
|
+
"images/img_readme_*.png",
|
|
25
|
+
"images/avatars/status_*.png",
|
|
26
|
+
"images/avatars/badges/",
|
|
27
|
+
"images/avatars/combinations/",
|
|
28
|
+
"package.json",
|
|
29
|
+
"README.md",
|
|
30
|
+
"config.sample.json"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "node scripts/build.mjs",
|
|
34
|
+
"prepack": "npm run build",
|
|
35
|
+
"dev": "tsx src/index.ts",
|
|
36
|
+
"chatccc": "tsx src/index.ts",
|
|
37
|
+
"start": "tsx src/index.ts",
|
|
38
|
+
"demo:bot-test": "tsx demo/bot_test.ts",
|
|
39
|
+
"demo:bot-test:local": "tsx demo/bot_test.ts --local",
|
|
40
|
+
"demo:create-group": "tsx src/index.ts",
|
|
41
|
+
"demo:create-group:local": "tsx src/index.ts --local",
|
|
42
|
+
"demo:permission-check": "tsx demo/permission_check.ts",
|
|
43
|
+
"demo:claude-hi": "tsx demo/claude_say_hi.ts",
|
|
44
|
+
"demo:codex-hi": "tsx demo/codex_say_hi.ts",
|
|
45
|
+
"demo:codex-app-server-approval": "tsx demo/codex-app-server-approval/approval_demo.ts",
|
|
46
|
+
"demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
|
|
47
|
+
"claude-proxy": "tsx src/litellm-proxy.ts",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:deepccc": "vitest run --root deepccc-agent",
|
|
50
|
+
"test:watch": "vitest",
|
|
51
|
+
"postinstall": "node scripts/postinstall-sharp-check.mjs"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@ai-sdk/anthropic": "^3.0.105",
|
|
55
|
+
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
56
|
+
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
57
|
+
"@openilink/openilink-sdk-node": "^0.6.0",
|
|
58
|
+
"@vscode/ripgrep": "^1.18.0",
|
|
59
|
+
"ai": "^6.0.184",
|
|
60
|
+
"nodemailer": "^8.0.7",
|
|
61
|
+
"qrcode-terminal": "^0.12.0",
|
|
62
|
+
"sharp": "^0.34.5",
|
|
63
|
+
"ws": "^8.18.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/node": "^20.0.0",
|
|
67
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
68
|
+
"@types/ws": "^8.18.1",
|
|
69
|
+
"tsx": "^4.0.0",
|
|
70
|
+
"typescript": "^5.0.0",
|
|
71
|
+
"vitest": "^3.2.4"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=20"
|
|
75
|
+
}
|
|
76
|
+
}
|