feat(controller): 工具仓库、Skills 录入、多轮追问与终端运行日志
- 插件/MCP 改为通用工具仓库:内置、HTTP MCP、本机命令(stdio)三类工具,可增删改、 按 Agent 分配;定义变更自动提升下发版本并重发,删除即回收分配并通知 Agent 清空。 - 旧 desktop-filesystem 降级为仓库中的一个内置条目;本机预设工具改为总控只展示、 Agent 默认启用,任务执行来源新增 builtin(审计 task.builtin_tool_execution)。 - Skills 补齐录入闭环:新建或按名称覆盖更新,并可从旧版技能目录(SKILL.md + skill.json) 批量导入;同名更新保持标识不变并自动重新应用,旧包入口脚本只提示不执行。 - 单独指派支持基于某条已完成任务的多轮追问:任务内容框仍下发无上下文的一次性任务, 追问时由总控组装历史(仅成功轮次、默认最近 50 轮 / 约 6 万字符),会话按 Agent 隔离; 执行动态把同一会话合并为一条记录,任务详情弹窗底部提供继续对话输入框。 - 任务详情支持运行中观测:打开期间每 2 秒刷新,终端风格输出全部事件并自动滚动; progress 事件不改变任务状态且顺延无进展超时,避免正在执行工具的任务被误判超时。 - 修复 build.ps1 缺 UTF-8 BOM 导致 build.bat 在 PowerShell 5.1 下语法报错。
This commit is contained in:
@@ -97,7 +97,7 @@ func TestPairDispatchAndTaskEvent(t *testing.T) {
|
||||
if statuses, err := controller.AgentLLMStatuses(); err != nil || len(statuses) != 1 || statuses[0].ApplyStatus != "applied" {
|
||||
t.Fatalf("expected applied LLM deployment, got %#v, %v", statuses, err)
|
||||
}
|
||||
if err := controller.EnableDesktopFilesystem([]string{agentID}); err != nil {
|
||||
if err := controller.SetAgentMCPTools(agentID, []string{"desktop-filesystem"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var mcpSnapshot map[string]any
|
||||
@@ -486,6 +486,60 @@ func TestTaskTextRejectsCredentialsAndInternalReasoning(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskProgressIsRecordedWithoutChangingStateAndRenewsDeadline(t *testing.T) {
|
||||
t.Setenv("MULTICLAW_CONTROLLER_DATA_DIR", t.TempDir())
|
||||
controller, err := Open(filepath.Join(t.TempDir(), "controller-config.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer controller.Close()
|
||||
now := time.Now().UTC()
|
||||
if _, err := controller.db.Exec("INSERT INTO managed_agents(agent_id, name, credential_hash, status, last_seen_at) VALUES ('agent-1', '进展测试机', 'test', 'online', ?)", now.Format(time.RFC3339Nano)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := controller.db.Exec("INSERT INTO task_runs(task_id, agent_id, agent_name, instruction, status, created_at, deadline_at, timeout_seconds) VALUES ('tool-task', 'agent-1', '进展测试机', '长任务', 'running', ?, ?, 300)", now.Format(time.RFC3339Nano), now.Add(time.Minute).Format(time.RFC3339Nano)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
controller.applyTaskEvent("agent-1", map[string]any{"task_id": "tool-task", "status": "progress", "message": "调用 computer_time:{} api_key: do-not-show"})
|
||||
|
||||
var status, deadline string
|
||||
if err := controller.db.QueryRow("SELECT status, deadline_at FROM task_runs WHERE task_id = 'tool-task'").Scan(&status, &deadline); err != nil || status != "running" {
|
||||
t.Fatalf("progress must not change the task state: %q / %v", status, err)
|
||||
}
|
||||
parsed, err := time.Parse(time.RFC3339Nano, deadline)
|
||||
if err != nil || parsed.Before(time.Now().UTC().Add(4*time.Minute)) {
|
||||
t.Fatalf("progress did not renew the no-progress deadline: %q / %v", deadline, err)
|
||||
}
|
||||
detail, err := controller.TaskDetails("tool-task")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
progressEvents := 0
|
||||
for _, event := range detail.Events {
|
||||
if event.Type == "progress" {
|
||||
progressEvents++
|
||||
if strings.Contains(event.Message, "do-not-show") || !strings.Contains(event.Message, "[已脱敏]") {
|
||||
t.Fatalf("progress text leaked a credential: %q", event.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
if progressEvents != 1 {
|
||||
t.Fatalf("expected one recorded progress step, got %d in %#v", progressEvents, detail.Events)
|
||||
}
|
||||
|
||||
// Progress for a task that already ended must be ignored entirely.
|
||||
controller.applyTaskEvent("agent-1", map[string]any{"task_id": "tool-task", "status": "completed", "message": "完成"})
|
||||
controller.applyTaskEvent("agent-1", map[string]any{"task_id": "tool-task", "status": "progress", "message": "迟到的进展"})
|
||||
if detail, err = controller.TaskDetails("tool-task"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, event := range detail.Events {
|
||||
if event.Message == "迟到的进展" {
|
||||
t.Fatal("a finished task must not gain new progress steps")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentProgressRenewsNoProgressDeadline(t *testing.T) {
|
||||
t.Setenv("MULTICLAW_CONTROLLER_DATA_DIR", t.TempDir())
|
||||
controller, err := Open(filepath.Join(t.TempDir(), "controller-config.json"))
|
||||
|
||||
Reference in New Issue
Block a user