Commit Graph
100 Commits
Author SHA1 Message Date
Autumn-27 6e40f41051 docs(changelog): 发布 0.3.4 2026-08-24 12:41:47 -04:00
Autumn 54af3a54b9 Merge pull request #54 from Autumn-27/feat/goalless-intent-execution
feat(tasks): 目标完成后支持主 agent 直投意图执行(goalless)
2026-08-25 00:22:28 +08:00
Autumn-27 01018a2161 feat(tasks): 目标完成后支持主 agent 直投意图执行(goalless)
所有目标达成后,主 agent 经 add_intent 下发的意图可被 worker 直接执行、跑完
即止;planner 在任务无 open 目标时不再运行,避免其重判目标达成而 cancelExec
杀掉直投意图;frontier 抽干后任务回到 done。

- db: 新增 HasOpenGoal 谓词(零迁移,读现有 goal 状态)
- engine: plannerLoop 加 goalless 门(无 open 目标则不进 planner,抽干则收尾)
- tools: add_intent 成功后调 resumeTask 复活任务(仅主 agent 生效,planner 为 nil)
- mainagent: 提示加入「目标达成后反问是否登记为正式目标」引导
- orchestration: reseedMainAgentPrompt 让旧库收到新提示(一次性,同构 reseedGoalsPrompt)

已知边角问题(paused/exhausted 意图的收官判据、零 goal 窗口、TOCTOU 等)
待后续讨论后再修。
2026-08-24 11:58:02 -04:00
Autumn-27 43edb86720 revert(tasks): 从 main 移除误并入 4cf36c7 的 goalless 直投意图改动
这几处 goalless(目标完成后主 agent 直投意图执行)改动被打包进了「任务列表
按 id 降序」提交(4cf36c7),与该提交主题无关,且尚在评审阶段不应留在 main。
此处仅做正向移除,不改写历史;功能改由独立分支 feat/goalless-intent-execution
承载,评审通过后再合入。
2026-08-24 11:57:28 -04:00
Autumn-27 7299150cfa fix(chat): 会话已选 LLM 时不再被「全局未激活」误拦,提示按状态细分
发送消息的前置检查只看全局激活配置(chatAgentRef),而真正执行的
runConversationSync 会优先用会话所选/Agent 绑定的配置——两处解析分叉,导致
「会话已选一个有效配置、但全局没有激活配置」时,消息在预检查阶段就被 503
「LLM 未配置」拦下,根本走不到能用所选配置的执行阶段。

- 抽出 resolveChatAgent(conversation) 作为唯一解析入口(会话/Agent 绑定优先、
  全局兜底),send 预检查与后台执行都用它,消除分叉。
- 提示按实际状态细分(chatUnavailableReason):无任何配置→提示添加;有配置但
  未激活→提示激活或为本会话指定一个;而非一律「未配置」。

相邻的 StartTriggeredRun 仍用全局 gate(触发器场景,本次不动)。

验证: 新增两个测试——三态提示区分、会话所选配置在全局无激活时仍能解析出
ChatAgent(回归防护);连本地 PG 实跑通过,server 全量无回归。
2026-08-24 11:55:50 -04:00
Autumn-27 412a993c25 feat(agent): get_worker_trace 的 step_ids 超限改为降级返回而非报错
原先 step_ids 超过 5 个直接报错,逼 Agent 重新组织调用、白跑一轮。改为优雅
降级:先去重、剔除非法 id,超出 5 个则返回前 5 步的完整内容,并在结果里用
returned_step_ids / omitted_step_ids / notice 告知本次取了哪些、还有哪些没取,
并提示「若已足够则无需再取剩余」。Agent 立刻拿到数据,是否再取由它自行判断。

保留数量上限的原因不变:detail 是完整原文、不截断,上限用于兜住单个 tool
result 的大小。get_task_worker_trace 经 delegateToTask 复用同一实现,一并生效;
两处工具描述同步更新。

验证: 新增 TestGetWorkerTraceStepIDsDegradeGracefully(含去重/非法 id/前5/
omitted/notice/在限内不加提示),连本地 PG 实跑通过;agent 全量无回归。
2026-08-24 11:35:49 -04:00
Autumn-27 6a8e13fcbd feat(tasks): 新建任务的分类选择器改为可搜索、可即时新建
原先是普通下拉,只能从已有分类里选。改成可搜索的输入框(复用 base-ui
Combobox):输入即过滤已有分类,库里没有的名称回车(或点下拉里的「创建」)会
即时调 createTaskCategory 新建并选中,选中项以可移除的 tag 展示。仍限单个
分类——multiple 仅用于 chip 视觉,onValueChange 只保留最新选中的一个。

- 新建的分类要等父层重新拉取才回流到 categories,组件内先本地留一份
  (localExtra),避免选中 chip 和下拉在这段窗口显示成「未知分类」。
- 回车创建仅在完全无匹配时触发,有匹配项时保留 base-ui 的「回车选中高亮项」;
  精确同名已存在则直接选中不重复创建。
- 新建成功通过 onCategoriesChanged 通知父层刷新,分类在筛选器等处随即可见。
- 任务列表筛选和批量改分类仍用下拉(选已有分类,无需创建),保持不变。

验证: tsc / biome / next build 均通过。
2026-08-24 11:24:17 -04:00
Autumn-27 4cf36c7896 fix(tasks): 任务列表改按 id 降序,消除同刻创建任务的抖动
原先按 created_at 降序排,但没有二级排序键。同一时刻创建的多个任务时间戳
相同,排序不稳定;叠加前端每 10 秒轮询、以及 Manager 用内存 map 存任务
(遍历顺序随机、每次 List 都要重排),这些任务就会在列表里频繁互换位置。

改用 id 降序:id 由 BIGSERIAL 单调递增分配,即插入顺序,唯一且稳定,最新
创建的在最前。db.ListTasks 的 ORDER BY 与 Manager.List 的内存排序同步改。

验证: 新增 TestListTasksOrderByIDDesc 连本地 PG 实跑通过;db/server 全量
无新增失败(仅剩既有的 TestCompanyScopeInvalid)。
2026-08-24 11:08:10 -04:00
Autumn-27 dfbbb33069 fix(assets): ip 字段拒收主机名,并告诉调用方该怎么改
上一次修复让非法 ip 不再打挂企业归属重算,但脏数据仍在持续产生。这次从源头
拦住:assets.ip 会被转成 inet 参与网段归属,存主机名的资产对所有 IP/CIDR
范围规则都是隐形的。

- db 层新增 ValidateAssetIP + ErrAssetIPInvalid,在 UpsertIP /
  UpsertHTTPService / UpsertOtherService / UpsertEndpoint 四个入口校验。
  放在 db 层是因为 Agent 工具和资产 API 都经由它们,不必写两遍;内部调用方
  (enrich 的解析结果、task_assets 的 ParseCIDR 输出、RecordValue 已 ParseIP
  过滤的分支)传的本就是合法地址,不受影响。
- 错误信息给的是改法而不只是「非法」:带上收到的原值,并指出主机名应改用
  type=subdomain 填 domain,或先解析 A/AAAA 记录再填 ip。insert_assets 的
  错误是逐条返回的({index, error}),同批其余资产照常入库,Agent 拿到后能
  定位到具体那条并在下一轮自行修正。
- 同步收紧 insert_assets 的工具描述与 ip 字段说明,在提示层先挡一道。
- 空 ip 仍然合法:service / endpoint 的 ip 是可选字段。

验证: 新增 db 层四类型拒收测试与 agent 层「一批混入一条非法、其余照常入库」
测试,均连 PG 实跑通过;干净库 go test -p 1 ./... 无新增失败(仅剩既有的
TestCompanyScopeInvalid)。
2026-08-24 10:32:10 -04:00
Autumn-27 9e98407528 fix(assets): 企业归属重算不再被单行非法 ip 打挂,并明确告知是哪些资产
assets.ip 是自由文本(Agent 的 insert_assets 和资产 API 都可能写进主机名),
而 db/companies.go 的 IP/CIDR 归属匹配裸转 a.ip::inet。只要表里有一行不是
合法 IP,PostgreSQL 就抛 22P02 中断整条语句,连带同事务的新增/修改企业资产
范围、删除企业全部失败回滚。这个裸转从项目初始化提交就存在。

- schema.sql 新增 try_inet(text):非法值返回 NULL 而不是抛错。没用
  pg_input_is_valid 是因为那要 PG16+,这里要兼容更老的存量库。
- 四处 IP/CIDR 匹配统一改用 try_inet。原先 task_scope.go 和
  task_assets_context.go 用的正则 a.ip !~ '[^0-9a-fA-F:.]' 只检查字符集,
  abc.def 这类全 hex 字母的主机名照样漏过去后炸,一并换掉。
- 跳过不能是静默的:重算会统计并抽样列出这些资产(id + 取值),说明它们不会
  被网段规则归属。信息通过企业范围接口新增的 warnings 字段返回并在前端以
  长停留 toast 展示,同时无条件写入服务端日志,覆盖没有前端响应的路径
  (删除企业、scopesentry 同步、Agent 写入)。
- warnings 与 errors 分开:errors 仍只表示本次提交里不合法的行,既有数据
  问题不该被记到用户这次输入头上。

验证: 干净库上 go test -p 1 ./... 仅剩 1 个既有失败(TestCompanyScopeInvalid,
与本改动无关,stash 后同样失败);改动前在含真实数据的库上有 14 个失败,现已
全部通过。go build / vet / gofmt / tsc / biome 均通过。
2026-08-24 10:16:17 -04:00
Autumn-27 fea78b2d6f feat(tasks): 任务列表支持批量修改分类
勾选多个任务后可一次性移动到目标分类,或选「未分类」把它们移出当前分类。

- db: 新增 SetTasksCategory,在单个事务里校验分类存在性并 UPDATE ... WHERE
  id=ANY(...),返回真正被更新的 id 和刷新后的分类(含 task_count)。分类检查
  放在事务内,避免并发删除把 UPDATE 变成外键冲突。单批上限 100。
- server: 新增 POST /api/tasks/category/batch。写入原子,响应里的逐任务失败
  只可能是勾选后又被删掉的任务;顺带把 category_id 的解析(必填但可为 null)
  抽成 parseCategoryIDField 与单任务接口共用。
- server: 把批量 id 的 trim/归一/去重逻辑从 controlTasksBatch 抽成
  normalizeBatchTaskIDs,两个批量端点共用同一套重复判定。
- manager: SetTasksCategory 在 taskStateMu 内完成 DB 写入与运行中任务 DTO
  刷新,防止与单任务更新交错后留下过期的 category_name。
- web: 选中工具栏新增「修改分类」对话框(需确认目标,避免误点批量改动);
  「未分类」哨兵值统一提取为 UNCATEGORIZED_VALUE。

验证: go build / vet / gofmt、db 与 server 新增测试连本地 PG 实跑通过、
tsc --noEmit、biome check、next build 均通过。
2026-08-24 09:16:44 -04:00
Autumn-27 3eca390089 style(db): 对齐 ICP 范围匹配 SQL 缩进,并注明手工挂载资产的语句顺序依赖
#53 引入 icp 范围匹配时,三处覆盖度 CTE 里新增的 OR 分支混进了 tab 缩进,
与同一 JOIN 条件内其它分支(纯空格)不一致。统一改回空格,SQL 语义未变
(git diff -w 无差异)。

AttachAssetsToTask 里 UPDATE assets 会先触发 trg_assets_task_links 写入
source='system',随后的显式 INSERT 才把来源改成 'manual'。加注释说明这层
顺序依赖,避免后续调换两条语句导致人工挂载的来源静默退化成 system。
2026-08-24 08:55:55 -04:00
Autumn-27 6b44a94ec7 Merge pull request #53 from neouks/main
feat: 同步 0.3.3 并增强任务、资产与 LLM 运行时

冲突解决说明:
main 此前已合入同源分支(#42/#31)的大部分内容,与本 PR 形成大量重复历史,
git 报出 18 个文件 104 处冲突。逐块核对后确认 PR 树已完整包含 main 的全部
内容(net diff 无任何纯删除,adc8e35/476a445/9fad543/9d8e0ba 等 main 侧修复
均已在内),故统一取 PR 侧。

另修正 db/assets.go 的静默错误自动合并: git 未报冲突但把 CountByTask /
CountsByTypeForTask / DeleteByTaskID 等 166 行重复插入两遍,会导致方法重复
定义编译失败。

相对 main 的净新增功能:
- 任务分类: task_categories 表 + tasks.category_id,分类增删改查与筛选,
  改名时同步刷新运行中任务的 DTO
- 任务测试资产: task_asset_links 表记录关联来源(agent/manual/api/task/
  anchor/system/legacy),支持实时挂载与移出;移出仅解绑,保留全局资产
- 测试范围新增 icp / keyword 两种 kind,ICP 按去空格小写匹配 icp/app_icp
- Worker 会话标题旁展示当前测试资产及来源摘要,继承任务仅暴露终态意图
- 前端抽出公共 ScopeTextEditor,补齐上述接口的 mock 契约

验证: go build / go vet / go test / tsc --noEmit 均通过。
2026-08-24 08:36:34 -04:00
Autumn-27 9ab9fe2327 docs(changelog): 定版 0.3.3(补齐 v0.3.2 之后的新增/修改/修复) 2026-08-23 13:32:44 -04:00
Autumn 734c7b21cf Merge pull request #49 from Autumn-27/fix/prompt-var-duplicate-now
fix(prompt-vars): 消除变量目录与全局变量撞名导致的前端 key 重复
2026-08-24 01:16:29 +08:00
Autumn-27 9fad543168 fix(prompt-vars): 消除变量目录里与全局变量撞名导致的前端 key 重复
goals 内置 agent 的变量目录里定义了 Now,seed 进 agent_prompt_vars 后,
withGlobalVars 又追加了同名的全局 runtime 变量,getAgent 返回两条 Now →
前端 variables.map 的 key={v.name} 重复,React 报错并可能丢/重渲子节点。

- withGlobalVars: 追加全局变量前剔除同名的库内条目,全局(权威 runtime 定义)
  胜出,保证返回列表名字唯一。对所有现存库立即生效,无需迁移。
- db.go: 从 goals 内置目录删掉冗余的 Now(已是全局变量);扩展既有清理 DELETE
  一并清掉旧库残留的 'Now' 行(已发版→DB 改动带迁移)。
- 新增 withGlobalVars 去重回归测试。

go build / go vet / go test ./server 全绿。
2026-08-23 13:14:28 -04:00
Autumn 1de7a383cc Merge pull request #48 from Autumn-27/fix/llm-precommit-safe-retry
fix(llm): 提交前安全窗口内对同一 provider 重试,减少 model_error
2026-08-24 01:06:00 +08:00
Autumn-27 9d8e0ba988 fix(llm): 提交前安全窗口内对同一 provider 重试,减少 model_error
SDK 的 doStream 只重试建连阶段(拿到 200 之前);流一旦开始,中途断流 /
overloaded / 流内 429 等瞬时故障会直接冒泡成 model_error,零重试。表现为
worker 常出现「无总结,终态 model_error」,甚至只跑了一两个工具就中断。

在 streamTaskLLM(所有任务 agent 的统一出口)里补一层同 provider 退避重试:
- 仅在 committed 之前(还没向调用方交付任何输出)触发,重放相同请求不会
  重复模型输出或工具副作用,绝对安全
- 最多 2 次,退避 0.5s→1s(封顶 4s),ctx 取消立即让位
- 只重试瞬时故障(传输 reset/EOF/timeout、408/429/5xx、流内 overloaded);
  额度耗尽交给 profile 故障转移、上下文过长交给 harness reactive 压缩、
  4xx 确定性拒绝一律不重试
- 给不上时原样落到既有透传/故障转移逻辑,行为不变

新增 3 个回归测试(重试到成功 / 重试耗尽如实上报 / 4xx 不重试),退避以
变量暴露便于测试置零。go test -race 全绿。
2026-08-23 13:02:54 -04:00
Autumn 5574624984 Merge pull request #47 from Autumn-27/fix/capture-usage-yield
fix(test): capture_usage 遵守迭代器协议,消除 yield 告警
2026-08-24 00:53:38 +08:00
Autumn-27 5f39391594 fix(test): capture_usage 遵守迭代器协议,yield 返回 false 即停止
消除 vet 的 "yield may be called again after returning false" 告警:
每次 yield 后判断返回值,false 则 return,不再继续调用 yield。
2026-08-23 12:52:32 -04:00
Autumn 2a07893f64 Merge pull request #46 from Autumn-27/feat/operation-constraints
feat(constraints): 操作约束(抽取→注入 planner/worker 提示→总览管理)
2026-08-24 00:49:09 +08:00
Autumn-27 27c3248844 feat(constraints): 操作约束抽取→注入 planner/worker 提示→总览管理
- 新表 task_constraints(allow/deny),CREATE TABLE IF NOT EXISTS 无需迁移
- 新工具 set_constraints:目标拆解器 round-0 先抽约束再拆目标、主 agent 运行时可补;
  拆解器提示词要求约束自包含(把「当前目标」等指代替换成具体域名/端口)
- constraintBlock 按最高优先级注入 planner+worker 系统提示;去偏:多样性/拓面显式服从约束
- 注入范围可配置(constraints_inject_planner/worker,默认都开,每轮读、即时生效)
- 总览「约束管理」卡片 + CRUD 接口(增删改不通知 planner,下轮规划自然读到)
- goals 提示词把抽约束步并入可编辑正文;旧库经 flag 守卫的一次性 reseed 追加新默认版本
2026-08-23 12:48:01 -04:00
Autumn d8f34cc79d Merge pull request #45 from Autumn-27/feat/goal-management
feat(goals): 总览目标管理(增/删/改) + 通知 planner 并复活任务
2026-08-23 23:42:43 +08:00
Autumn-27 d28ecd67fe feat(goals): 总览新增目标管理(增/删/改),同步通知 planner 并复活任务
- 总览页新增「目标管理」卡片,支持人工查看/新增/修改/删除目标
- 新增/修改:通知 planner(新增了目标/由 old 变为 new)并复活任务
- 删除:硬删除 goal 节点(级联删边/锚点),通知 planner,不复活任务
- 无 schema 变更:目标仍存 exploration_nodes(kind='goal'),仅加查询方法
2026-08-23 11:41:53 -04:00
Autumn-27 025e1ce445 feat(intents): 删除 work 改为「停止+记录原因」,不再销毁数据
删除不再删意图与其产出,而是:停到 stopped、要求填写删除原因、把原因作为
事实挂到该意图上、并写进意图 payload,规划者收到 cancelled 触发(意图内容+原因)。

- db: StopIntentWithReason —— 标 stopped + 建原因事实 + 连边 + 合并 payload
  (cancelled_by_user/cancel_reason),单事务;旧 CancelIntent 保留不再被调用
- agent/server: renderTriggers 加 cancelled 分支;Task.NotifyCancelled;
  controlIntent/applyIntentControl 接收并要求 reason
- web: 删除弹窗改为必填原因的 Textarea;列表用红色垃圾桶+删除线+「已删除」徽标
  区分;选中会话顶部横幅展示删除原因;controlIntent 传 reason

Claude-Session: https://claude.ai/code/session_019QXSCDmzimwnjntBNCgUD7
2026-08-23 11:14:04 -04:00
Autumn-27 3297ca1821 change(llm): 出站代理留空=直连,不再回退 HTTP_PROXY 环境变量
- quotaAwareHTTPClient: proxy 为空时 transport.Proxy=nil(直连),
  不再用 http.ProxyFromEnvironment 吃 HTTP_PROXY/HTTPS_PROXY
- 前端代理输入:提示可带账号密码(socks5://user:pass@host:port),
  文案改为「留空表示不使用代理(直连)」

显式的 ARTEX_LLM_PROXY 环境变量不受影响(仍写入 Config.Proxy)。

Claude-Session: https://claude.ai/code/session_019QXSCDmzimwnjntBNCgUD7
2026-08-23 10:41:16 -04:00
Autumn-27 52aba07846 feat(mainagent): 默认给主 agent 加上 steer_work 工具
主 agent 之前只能 add_hint/add_intent,无法对已在运行的 work 实时纠偏。
现将 planner 的 steer_work(不打断、不丢进展地注入纠偏指令)默认下放给主 agent。

- MainAgentTools 加入 steerWorkTool
- MainAgent 加 steerWork 字段与 SetSteerWork,Chat 里接进 ToolSet
- 全局与 per-task router 两处主 agent 构建均 SetSteerWork(engine.SteerWork)
- 主 agent 提示词「操舵」段补充 steer_work 用法引导(仅影响新 seed)

作用域安全:steer_work 仍校验 intent_id 属于本任务(关联任务只读)。

Claude-Session: https://claude.ai/code/session_019QXSCDmzimwnjntBNCgUD7
2026-08-23 10:37:24 -04:00
Autumn-27 052cf63338 fix(intercept): 加固裁判输出解析,避免正确 DENY 被当成放行
模型若照抄提示词里的中文标签(如「拦截:DENY:...」),旧解析要求关键词在行首,
会判为无法解析 → 按 fail_action(默认 allow)兜底,导致真实拦截被放行。

- ParseVerdict 改为扫描行内最早出现的 ALLOW/ASK/DENY 关键词,容忍前缀标签
- 提示词输出段改用箭头并明确禁止输出中文标签,附合法输出示例
- 新增 prompt_test.go 覆盖标签回显、全角冒号、大小写、超长理由截断等场景

Claude-Session: https://claude.ai/code/session_019QXSCDmzimwnjntBNCgUD7
2026-08-23 10:05:10 -04:00
Autumn-27 95d2309935 feat(intercept): 未命中规则的命令接入 LLM 兜底审批
在正则/字符串规则之外新增一套独立的模型兜底层:规则命中直接按规则走;
一条规则都没命中时,才由模型做语义判定(ALLOW/ASK/DENY),人工审批兜底。

- intercept: DefaultJudgePrompt(三态判定提示词)+ ParseVerdict;Reviewer 回调注入;
  Judge() 读全局 llm_judge_* 配置、超时/失败回退、ask 填审批超时、模型判定加 [模型] 前缀
- guard: applyIntercept 未命中分支调 Judge;模型 allow 不落库,deny/ask 带 reason 入库
- db: intercept_pending 加 reason 列(带 ALTER IF NOT EXISTS 补旧库);
  create 函数支持 reason 与 ruleID 0→NULL;历史查询带出 reason
- server: 用 providerForProfile + 单次 Stream 收集注入裁判;GET/PUT /api/intercept/judge
- web: 拦截页拆「拦截规则 / 模型配置」两 tab;拦截范围提为共用信息条;
  模型配置双栏布局(提示词直接展开编辑);审批/任务拦截页标注「模型判定」+ 理由

配置默认值:失败回退 allow、审批超时动作 deny、模型 allow 不落库、
提示词默认即内置模板(清空回退模板)。无需 DB 结构性迁移(judge 走 settings KV)。

Claude-Session: https://claude.ai/code/session_019QXSCDmzimwnjntBNCgUD7
2026-08-23 10:02:43 -04:00
Autumn-27 f63a451b87 feat(tasks): 任务增加可选名称字段
tasks 表新增 name 列(空=未命名,前端回退到描述),端到端打通:
- DB: schema.sql 加列并补旧库 ALTER;tasks.go 的 Task/CreateOptions/CRUD;
  findings.go 分组与任务下拉带出 t.name
- 后端: TaskDTO、内存 Task、createTaskReq 透传 name
- 前端: 类型/API/mock 加 name;任务列表新增名称列与搜索、创建表单加名称输入;
  详情页与漏洞页标题回退 name || description

Claude-Session: https://claude.ai/code/session_019QXSCDmzimwnjntBNCgUD7
2026-08-23 09:08:05 -04:00
Autumn-27 070784d43c revert: 恢复版本号动态注入,本地构建回退为 dev
本地运行不再硬编码 0.3.3,改回 dev;build.sh 通过 git describe
注入真实 tag 并经 ldflags 覆盖 main.version。

Claude-Session: https://claude.ai/code/session_01HMRoSjvobCcuH5gbKkStFF
2026-08-23 03:55:10 -04:00
Autumn-27 ca1d22b227 docs(insert_assets): 明确 related 判定,CDN/静态资源类应设 false
补充工具描述:与任务无关的资产(如仅存静态资源的 CDN)必须设
related=false 或不插入,避免污染资产覆盖度分母。

Claude-Session: https://claude.ai/code/session_01RtPQYJ8W1mcaace3sHWGDm
2026-08-22 12:33:26 -04:00
Autumn-27 a3fe9b767b feat(ui): 系统设置接入会话发送键位与 Web 搜索代理
- 会话发送键位改用 chat-send-mode 统一配置(CHAT_SEND_MODE_OPTIONS/
  useChatSendMode),替换页面内自维护的模式常量。
- Web 搜索区新增代理输入框(http/socks5,留空为直连)。

Claude-Session: https://claude.ai/code/session_01RtPQYJ8W1mcaace3sHWGDm
2026-08-22 11:46:12 -04:00
Autumn be90d520a3 Merge pull request #43 from Autumn-27/feat/traffic-sqlite-store
feat(traffic): 正文入库 + 全文搜索,删除改为 SQL 事务
2026-08-22 23:43:55 +08:00
Autumn-27 fb666c03cc feat(traffic): 正文入库 + 全文搜索,删除改为 SQL 事务
流量录制不再镜像 URL 路径写文件树,改为整条 exchange 落 SQLite:

- record: 元数据/头/正文写库;>256KB 的 body 才溢出到 _blobs(单层
  桶、按 hash 去重),库里留 8KB 预览 + 指针。文本正文全量喂 trigram
  FTS 索引(二进制按 content-type 跳过),支持任意子串与中文全文搜索。
- 删除退化为一个 SQL 事务级联删四张表,host 树后台异步回收,不再在
  写锁内 RemoveAll + 全树扫描——删除从数小时降到毫秒级,期间不再停摆录制。
- gcBlobs 改走 blob_refs 反查,顺带清理空桶目录;保留对旧文件树的
  引用扫描与 Get 回落,历史数据免迁移仍可读、可搜(元数据)、可删。
- 新增 traffic_blob 工具与 GET /api/traffic/blob 流式下载大 body;
  traffic_search 增加 body_contains 全文参数。

新表均为 CREATE TABLE IF NOT EXISTS,旧库自动补建,无需 ALTER 迁移。

Claude-Session: https://claude.ai/code/session_01RtPQYJ8W1mcaace3sHWGDm
2026-08-22 11:36:58 -04:00
Autumn-27 22ae280584 feat(insert_assets): 资产可标记是否与当前任务相关(related)
insert_assets 每条资产新增 related 入参(默认 true):仅在任务开启资产
覆盖度功能时生效——true 才自动纳入覆盖度(测试范围分母),false 则只入
共享资产库、不计入本任务覆盖度(如顺带发现的旁站/无关资产)。

- Call 内部按 related + coverageDisabled 双重 gate 决定是否 AddAutoScope
- 覆盖度关闭时 related 无意义,经 StripCoverageParams 从最终工具 schema
  剔除(必须在 ToolResolve 的 DB schema 覆盖之后做,故在装配链末端处理)
- 旧库经 refreshBuiltinToolSchemas(flag v6) 刷新 insert_assets schema,
  否则首插入only 的 seed 收不到新参数
- 补 StripCoverageParams 回归测试(开启保留/关闭隐藏/不改原始 schema)

Claude-Session: https://claude.ai/code/session_01Jm87Gr5fq5aso6SfLyguhx
2026-08-22 10:26:11 -04:00
Autumn-27 eb3f278711 feat(task): 资产覆盖度功能可按任务开关
创建任务时新增「资产覆盖度功能」开关(默认开)。关闭后:
- 不计算/展示测试覆盖度,总览卡片隐藏、覆盖度 API 返回 enabled:false
- 态势图仅展示资产不显示测试进度(B1:抹平 tested、去已测统计与图例)
- 不再自动累积 task_scope(source=auto)
- planner/mainagent 的 add_task_scope、list_untested_assets 从工具列表剔除
- graph_overview 丢弃 coverage 度量块、保留 scope 字段(防上下文污染)

company 关联(task_scope kind=company)与此开关无关,永不受影响:
insertTaskCompanies 不 gate,TaskCompanyIDs 照常读回,scope 字段照常喂给 agent。

DB:tasks 加 coverage_enabled 列(schema.sql + ALTER ADD COLUMN IF NOT EXISTS
补旧库,默认 true 保持存量任务原行为)。

Claude-Session: https://claude.ai/code/session_01Jm87Gr5fq5aso6SfLyguhx
2026-08-22 10:03:18 -04:00
Autumn-27 5126ac4592 feat(ui): 会话输入框发送键位可配置
0.3.2 把 Ctrl+Enter 发送改成了 Enter 发送,习惯旧键位的用户没有退路。
系统设置新增「会话输入框发送键位」,两种模式二选一:

- Enter 发送,Shift+Enter 换行(默认,即 0.3.2 现行为)
- Ctrl+Enter 发送,Enter 换行(0.3.2 之前的键位)

实现:

- 新增 lib/chat-send-mode.ts,复用已有的 local-storage.client 读写封装;
  纯前端偏好,不入库、不随账号同步
- 对话页与任务详情主 Agent 会话原本各自内联了一份相同判断,统一收敛到
  shouldSubmitOnKey(),加模式时不必两处各改一遍
- enter 模式保留原判断(只排除 Shift),没顺手把 Ctrl+Enter 也变成换行——
  原本它是会发送的,改掉会让没动设置的用户感到意外
- 中文输入法保护照旧:isComposing / keyCode 229 时放行,回车选词不误发送
- useSyncExternalStore + 自维护订阅集合:storage 事件只在其他标签页触发,
  本页改完需手动 emit 才能立即生效;getServerSnapshot 给默认值避免 SSR 不一致
- ctrl-enter 模式同时接受 Ctrl 与 Cmd(macOS)

按需求放在系统设置页,未采用 issue 中建议的输入框内控件。

Closes #39

Claude-Session: https://claude.ai/code/session_01Q8nhYZfS4H8fsSCBJXxbSy
2026-08-22 07:18:21 -04:00
Autumn-27 ca7300ad52 feat(ui): 任务列表操作列增加详情与暂停/继续按钮
操作列此前只有删除,暂停/继续必须先勾选再点批量控制,进详情页只能点标题。

- 详情按钮用 Button asChild 包 Link,渲染为真正的 <a>,中键新开标签、
  右键复制链接都可用
- 暂停/继续按钮按状态自动切换形态,调已有的 controlTask 接口,无需改后端;
  终态任务渲染为 disabled 而非隐藏,各行操作列宽度才不会随状态跳动
- 状态门控与后端 applyTaskControlWithCause 对齐:非终态且未暂停可暂停
  (即 created/queued/running),paused 可继续,不会给出后端会拒绝的动作
- 控制后清空 lastRef 再 load:列表轮询按负载去重,不清会把状态回写挡掉,
  看起来像点了没反应;放在 finally 里是因为失败多为状态已变化,更需要重拉
- 批量控制原本自己写了一遍状态列表,改为与行内按钮共用 taskControlAction(),
  避免将来新增状态时两边判断分歧

Claude-Session: https://claude.ai/code/session_01Q8nhYZfS4H8fsSCBJXxbSy
2026-08-22 06:51:39 -04:00
Autumn-27 76c648d36d fix(ui): 任务总览的测试范围显示企业名称而非 ID
/api/tasks/{id}/scope 只返回 company_id,前端无从解析名称,测试范围里的
企业行只能渲染成 "company #4"。

- TaskScope 增加 company_name,ListTaskScope 与 ListTaskScopeWithSources
  都 LEFT JOIN companies 带出名称(LEFT 而非 INNER:域名/IP/网段行的
  company_id 为 NULL,内连接会把它们全过滤掉)
- 两个查询函数都改,避免结构体字段"一个填一个不填"的不一致
- 前端优先显示名称,公司被删后的残留行仍以「企业 #N」兜底

覆盖图谱的企业节点本就查名显示(task_scope.go 的 BuildCoverageGraph),
不受影响。

Claude-Session: https://claude.ai/code/session_01Q8nhYZfS4H8fsSCBJXxbSy
2026-08-22 06:43:08 -04:00
Autumn-27 f0c2b43802 Merge PR #42: 0.3.3 Worker/LLM/Agent/UI 增强
来自 @neouks 的 PR #42,外加本地的三个后续修改:

- fix(llm): Agent 绑定优先于任务 LLM 配置链
- feat(ui): 会话徽标改显示 LLM 配置名
- fix(scope): 含点号的文本不再被误判为 ICP 备案号

已知遗留(code review 确认,评估影响可控,后续单独处理):
- db/exploration.go CancelIntent 先数后删的并发竞态,会回滚并向用户报
  "intent cleanup changed concurrently",不损坏数据
- db/assets.go company_source 优先级 CASE 在 8 处手写重复,改动易漏
- server/goals.go reconcileConcurrency 第二个 if 是死代码

Claude-Session: https://claude.ai/code/session_01Q8nhYZfS4H8fsSCBJXxbSy
2026-08-22 06:26:43 -04:00
Autumn-27 fbddf1cfb5 fix(scope): 含点号的文本不再被误判为 ICP 备案号
单文本框自动分类只要文本里出现 "icp" 或 "备案" 子串就判定为 ICP 规则。
备案号本身不含点号(京ICP备12345678号-1),而 ICP 归属走的是精确相等比较
(companies.go 的 kind='icp' 归属查询),所以掺了域名或版本号的描述性文字
一旦被判成 ICP,就会存成一条永远匹配不上任何资产的死规则,且无任何提示。

分类条件增加"不含点号",半角 . 之外一并覆盖全角 .和中文句号 。——粘贴
中文内容时容易出现这两个,真实备案号里同样不会有。

已有的 "京 ICP备 123号"、"沪网备案 9988" 仍判定为 ICP,不受影响。

Claude-Session: https://claude.ai/code/session_01Q8nhYZfS4H8fsSCBJXxbSy
2026-08-22 06:26:07 -04:00
Autumn-27 9b75afe9f1 feat(ui): 会话徽标改显示 LLM 配置名
会话标题右侧的徽标原本显示模型 ID,改为显示配置名称;模型 ID 移到 hover
的 tooltip 里,并去掉其中的协议名(openai/anthropic 等)。

- 抽出 resolutionLabel(),徽标、aria-label、tooltip 共用一处取名逻辑;
  environment 来源的配置可能没有 name,按 name → model → 未命名配置 降级,
  避免渲染出空徽标
- 去掉 font-mono:等宽字体是为模型 ID 准备的,配置名多为中文
- tooltip 用 filter(Boolean).join(" / ") 拼接,模型名缺失时不留孤立斜杠

Claude-Session: https://claude.ai/code/session_01Q8nhYZfS4H8fsSCBJXxbSy
2026-08-22 06:18:07 -04:00
Autumn-27 adc8e3521d fix(llm): Agent 绑定优先于任务 LLM 配置链
任务内各角色解析 LLM 的顺序由「任务链 → Agent 绑定 → 全局」改为
「Agent 绑定 → 任务链 → 全局」:显式绑定了模型的角色始终跑在该模型上,
未绑定的角色才落到任务链,任务链为空时再落到全局配置。

- 拆分 fallbackProviderForAgent 为 agentBindingProvider / globalProvider,
  让三个层级可按新顺序自由组合
- current()、CompactionWindow()、taskRuntimeAvailable() 统一按新顺序解析;
  taskRuntimeAvailable 改为逐角色判断,有可用绑定的角色在任务链耗尽时仍可运行
- resolveTaskRoleLLM(UI 展示)与前端 mock 同步对调,避免显示与实际调用不一致

保留两处既有语义:绑定/全局路径仍返回 profileID=0,不推进任务链故障转移
(绑定自身的转移仍由 poolForBinding + llm_pool_bind_fallback 负责);未绑定
角色遇到任务链耗尽仍是硬停止,不静默降级到全局。

Claude-Session: https://claude.ai/code/session_01Q8nhYZfS4H8fsSCBJXxbSy
2026-08-22 06:14:18 -04:00
Autumn-27 63859faf3d docs(changelog): 修正 0.3.2 贡献者为 @neouks
上一条把贡献者误写成 git 本地作者名 pphua;该提交经 PR 合入,
GitHub 作者实为 @neouks,更正之。

Claude-Session: https://claude.ai/code/session_01RVrPtpngE9T9DRJHosf6MB
2026-08-20 13:22:42 -04:00
Autumn-27 12de72354e docs(changelog): 定版 0.3.2 + 贡献者
Claude-Session: https://claude.ai/code/session_01RVrPtpngE9T9DRJHosf6MB
2026-08-20 13:21:15 -04:00
Autumn-27 0a55c144ea fix(skills): 补提交漏 add 的 server/skill_usage.go
834e945 在 assembly.go 引入了 meterSkillTool 调用,却漏 add 定义它的新文件,
导致此后 origin/main 从干净 checkout 编译失败(undefined: meterSkillTool)。
补上该文件修复构建。

Claude-Session: https://claude.ai/code/session_01RVrPtpngE9T9DRJHosf6MB
2026-08-20 13:17:38 -04:00
Autumn-27 39c540edfc fix(chat): 打开主 Agent 会话默认贴底并完整显示最后一条
最后一条 final answer 的完整内容是懒加载(AnswerBlock useInView+fetchDetail),
在 activity 数组稳定后才撑开高度,而滚动 layout effect 只依赖 activity 引用、
不会因高度变化重触发,导致打开后最后一条被顶到屏幕外。新增 ResizeObserver 观测
transcript 容器:用户仍贴底时,任何高度变化(transcript 挂载/详情展开/markdown 撑高)
都重新贴底;手动上滚则不再拽回。planner/worker 会话同样受益。

Claude-Session: https://claude.ai/code/session_01RVrPtpngE9T9DRJHosf6MB
2026-08-20 13:17:38 -04:00
Autumn-27 a98081eea9 feat(agent): worker 单次 run 墙钟默认 600→1200
改默认不动存量行:CREATE 列默认 + 幂等 ALTER COLUMN SET DEFAULT
补旧库列默认(仅影响将来新插入)、seedBuiltins 种子默认、运行时兜底同步 1200。
旧库存量 agent 行保持原值不变。

Claude-Session: https://claude.ai/code/session_01Qx86izpr6f5R53DW864bPB
2026-08-20 13:17:24 -04:00
Autumn-27 0fc63ad94e fix(chat): 任务暂停不再拦截主 Agent 对话 + 移除乐观回显消除消息错乱
- 后端 chat 处理器去掉 IsPaused 的 409 拒绝(保留 IsDeleting):主 Agent
  编排会话独立于任务暂停,暂停中仍可继续对话;暂停只终止当前进行的那一轮。
- 前端 sessions-tab 删除 chatExtra 乐观回显:主 Agent 控制台改为纯服务端
  数据渲染。根治伪造 seq 撞全局 DB id 导致的 React key 冲突/跨会话内容渗漏,
  以及发送失败不回滚的幽灵气泡。发送失败改为回滚输入框内容 + toast 报错。

Claude-Session: https://claude.ai/code/session_01RVrPtpngE9T9DRJHosf6MB
2026-08-20 13:09:29 -04:00
Autumn-27 28c1be735b fix(chat): 会话输入框改为 Enter 发送、Shift+Enter 换行
原先 Enter 换行、Ctrl+Enter 发送,不符合直觉。改为单独 Enter 发送、Shift+Enter
换行,并加 isComposing 保护避免中文输入法选字时误发(与 sessions-tab 一致)。

Claude-Session: https://claude.ai/code/session_01Bs2rrsVHUBTxRusm9espwB
2026-08-20 12:40:01 -04:00
Autumn-27 9103ff838a feat(llmrec): 录制请求体补记实际发出的思考参数
Recorder 携带配置级 thinking_type/reasoning_effort(这些在 norma buildBody 里从
provider 配置注入 HTTP body,不在 CompletionRequest 上,故录制原先完全看不到)。
序列化时写入 thinking:{type,effort},type 取有效值——每请求 req.Thinking 覆盖优先
于配置级(如 compaction 摘要强制 disabled),与 buildBody 判定一致。前端录制查看页
原样美化 JSON,自动展示该字段。

Claude-Session: https://claude.ai/code/session_01Bs2rrsVHUBTxRusm9espwB
2026-08-20 12:35:12 -04:00
Autumn-27 863e44ec24 feat(llm): 思考开关与思考强度拆成两个独立可配置参数
- thinking_type(''/disabled/enabled) 与 reasoning_effort(''/low/medium/high/xhigh/max)
  彻底解耦,可任意组合:支持「不发 thinking 字段、只靠强度激活思考」的接口
- agent.Config 拆为 ThinkingType + ReasoningEffort 两字段,NewProvider 直接透传
- db/schema.sql 加 thinking_type 列 + 一次性 DO 块迁移(仅列不存在时回填旧语义)
- 前端 LLM 配置页改为两个独立下拉,强度新增 xhigh 档

Claude-Session: https://claude.ai/code/session_01Bs2rrsVHUBTxRusm9espwB
2026-08-20 12:31:20 -04:00
Autumn-27 834e94535a feat(skills): 新增 Skill 调用统计(账本 + 页面总览/详情)
后端:
- 新表 skill_usage(无外键,任务/会话删除后统计保留),一次 Skill()
  调用记一行,只记维度不记 args 正文;含 found 标记未命中调用
- 用 RunInfo 经 ctx 把 task/exploration/intent/session 传到工具装配层,
  在 Skill meta-tool 外包一层做埋点(可记 args 长度与不存在的 skill)
- fsListSkills 返回附带调用次数/覆盖任务/最近调用;新增
  GET /api/skills/missing 与 GET /api/skills/{name}/usage

前端(/system/skills):
- 树节点显示调用次数 badge;目录优先排序 + 子项缩进错开一级
- 空态改为技能库总览(指标卡 + 调用排行 + 最近/未使用)
- 选中 skill 的详情面板改为响应式左右分栏(配置在左、统计在右,
  窄屏回落上下),含最近调用明细与未命中缺口清单

Claude-Session: https://claude.ai/code/session_01BNN7npEJs4X6LfTtgniMd8
2026-08-20 12:30:01 -04:00
Autumn-27 43f40dd74f feat(findings): 发现页支持导出(Markdown 单文件/分文件zip、CSV、JSON,按筛选/全部/勾选)
- 后端新增 GET /exploration/findings/export:按 scope(filtered|all|selected) ×
  format(md-single|md-zip|csv|json) 导出,md-zip 用 archive/zip 后端打包
- db.ListFindingsForExport:带完整 report、不分页,按严重等级+时间排序
- report/findings.go:汇总/单条 Markdown、安全文件名、带 BOM 的 CSV 渲染
- 前端发现页加行勾选 + 导出弹窗(范围×格式);复用 lib/utils.copyText
- 顺带修复报告复制在 HTTP 非安全上下文失效(execCommand 降级)+ 复用 CopyButton

Claude-Session: https://claude.ai/code/session_01Bs2rrsVHUBTxRusm9espwB
2026-08-20 12:06:46 -04:00
Autumn-27 ed96f256a4 fix(ui): 报告复制在 HTTP 非安全上下文下失效,增加 execCommand 降级
Claude-Session: https://claude.ai/code/session_01Bs2rrsVHUBTxRusm9espwB
2026-08-20 11:21:07 -04:00
Autumn-27 d14ce4d9f4 feat(ui): skill 树修复长文件名与删除确认,任务列表支持多选删除
Skill 管理:
- 长文件名把行撑出侧栏导致操作按钮不可见,约束 ScrollArea viewport
  内层并把按钮组改为绝对定位固定在右侧
- Skill / 文件夹 / 文件三处删除补上二次确认对话框

任务列表:
- 表格新增多选列(本页全选 + 跨页保留选择)
- 批量删除复用单任务的关联数据清理选项,串行执行并显示进度,
  结果按成功/失败聚合提示

Claude-Session: https://claude.ai/code/session_01BNN7npEJs4X6LfTtgniMd8
2026-08-20 11:18:24 -04:00
Autumn-27 def7649baa feat(tokens): 新增 llm_usage 计量账本,按模型统计 token + 仪表盘新旧版切换
- llmrec: 拆分轻量计量与重量 body 录制——token+model+task+worker 永远写入
  新表 llm_usage(不含 body,不受 llm_record 开关控制),body 仍受开关控制
- 修复 token 累加 bug:usage 从覆盖改为 Add(Anthropic 把 input/output 拆到
  message_start/message_delta 两个事件,覆盖会把 input 冲成 0;OpenAI 不受影响)
- 任务总览新增「LLM Token 用量」卡片:按模型统计 + 缓存命中率(缓存读/输入)
- 仪表盘 Token 卡片加「新版/旧版」开关:新版走 llm_usage(逐次精确、含中断消耗、
  真实每日消耗),旧版沿用 activity 统计
- llm_usage 随任务删除保留(历史计量账本)

Claude-Session: https://claude.ai/code/session_014TfZWeVGhjXqZD3vcj44mz
2026-08-20 11:16:48 -04:00
Autumn-27 6b79043d82 feat(tasks): 总览页支持任务测试范围的查看/增删
- db: 新增 DeleteTaskScope;AddAgentScope 加 source 参数区分 agent/manual
- server: 新增 POST/DELETE /api/tasks/{id}/scope 端点
- web: 总览页新增「测试范围」卡片,可按类型手动增删,继承范围只读

Claude-Session: https://claude.ai/code/session_014TfZWeVGhjXqZD3vcj44mz
2026-08-20 09:36:28 -04:00
Autumn-27 56101b46e8 feat(worker): 暂停/重跑续接先前 LLM 对话,不再从头开始
Worker 的 Execute 在创建 session 后尝试 Resume 先前的 transcript,
如果存在则续接对话而非重建全新会话,避免浪费已积累的 LLM 上下文。

Claude-Session: https://claude.ai/code/session_014TfZWeVGhjXqZD3vcj44mz
2026-08-20 09:15:53 -04:00
Autumn-27 06a8f64b5e fix(ui): 任务列表并发设置按钮紧靠新建任务按钮 2026-08-20 08:42:12 -04:00
Autumn 7b673f2e4b Merge pull request #31 from neouks/codex/task-context-llm-worker-control
feat(tasks): add inherited context and resilient LLM execution
2026-08-20 20:30:30 +08:00
Autumn-27 7090b034f8 refactor(ui): LLM 页改为配置卡片列表 + 抽屉编辑,新增轮询配置入口
原来是「上边轮询卡片 + 下边左列表右详情」的分栏布局,详情区常驻占掉三分之二
版面。改为:

- 页面主体 = 模型配置卡片网格,点卡片打开右侧抽屉编辑/测试/保存
- 卡片右上角显示该配置是否正常:未配置 Key / 正常 / 异常·失败 N 次 /
  已熔断·冷却 xx,悬停出具体错误
- 工具栏「新建」左边加「轮询配置」入口,抽屉里放开关、兜底开关、实际链路
  顺序、熔断状态与恢复操作;开启时按钮上带「已开启」徽章

新建与编辑合用同一个抽屉(profile === null 即新建)。原先新建是 Dialog、编辑
是常驻表单,两份几乎完全相同的表单代码约 150 行,加字段得改两处——现在只有
一份 ProfileSheet。

抽屉的开关状态与内容状态分开存(editOpen / editing):否则关闭动画那 200ms
里标题会从「编辑 XX」闪成「新建模型配置」。

卡片上的优先级/不参与轮询只在轮询开启时才显示——关着时这两个字段没有意义,
不占版面。

注:卡片的「正常」是排除法得出的弱信号(填了 Key 且轮询没记录到它失败过),
不是主动探测——从未被调用过的错误配置也会显示正常。
2026-08-19 11:25:45 -04:00
Autumn-27 c436b9d7d8 fix(update): Docker 更新只拉 artex 镜像,不动 postgres
postgres 固定 16-alpine 无需跟着升级,pull/up 带上服务名即可;artex 声明了
depends_on postgres,pg 没起会自动拉起,已在跑的不重建。
2026-08-19 11:12:26 -04:00
Autumn-27 ca2c5e45a9 feat(ui): 对话输入框 Enter 换行,Ctrl/Cmd+Enter 发送
会话界面共享 Composer 与任务详情主 Agent 输入框统一改为回车换行,避免长消息
误发。任务详情原本是单行 Input(根本无法换行),换成 Textarea 并对齐 Composer
的自动增高样式(rows=1 + max-h-40)。
2026-08-19 11:12:21 -04:00
Autumn-27 0c653a2135 feat(llm): LLM 配置轮询(故障转移),某个配置没额度/失效时自动切下一个
默认关闭。开启后「未指定模型」的 agent 在当前配置不可用(402 余额不足 /
401 key 失效 / 429 限流 / 5xx)时自动切到下一个配置;agent 绑定或任务 pin
了配置的默认不轮询(绑定即独占,可由 llm_pool_bind_fallback 打开兜底)。

链序 = 激活配置 + 其余按 priority DESC,排除 pool_exclude 的。只给
llm_profiles 加两列,不引入关联表,所以 agents/tasks/conversations 对
llm_profile_id 的四处引用一行未改。

核心约束:只在本次调用尚未 yield 出任何事件时切换成员。已经吐出
text_delta / tool_use_start 后中途断流一律原样上抛(交给 agentcore 的
resume),否则重发同一 request 会重复输出、污染会话历史。SDK 的 doStream
保证 4xx/5xx 在读 body 前返回,所以目标场景都落在这个安全窗口内。

context.Canceled / DeadlineExceeded 绝不触发切换——那是用户取消或任务
超时,误判会白烧备用 key 并污染 a6bd180 的终止原因诊断。400 也不切
(请求本身有问题,换模型结果一样)。

熔断:402/401/403/404 一次即熔断,429/5xx/网络错误连续 3 次;冷却
60s→5min→30min 退避,到期半开试探,成功清零。Registry 是进程级的,独立
于 provider 缓存,重建链路不丢状态,且全进程共享(A 任务撞见的 402,B 任务
立刻知道)。状态旁路落库,重启只恢复尚未到期的冷却窗口。

上下文窗口装不下当前请求的成员直接跳过(必然 400),不把主力模型的窗口
压到全链最小值。全员熔断时仍探链首,不让引擎停摆。

Pool 包在 llmrec 外面、每个成员自带 recorder,失败调用会以 status=error
+ 正确的 profile 名进 llm_records;切换/熔断走 log.Printf,自动出现在
系统日志页。

顺带修两个既有问题:
- DecomposeGoals 原本自己 NewProvider(),绕过限速器/录制/轮询,且每个
  任务新建一个实例——改为由 server 传入共享 provider
- saveLLMConfig 会把 priority/pool_exclude 重置为零值

前端:LLM 页新增轮询卡片(总开关/兜底开关/实际链路顺序/健康徽章/冷却
倒计时/立即恢复),配置编辑区加优先级与「不参与轮询」。

只有一个配置时链长度为 1,poolChain 返回 nil 走裸 provider,行为与开关
打开前完全一致。设计见 docs/LLM轮询设计.md。
2026-08-19 11:07:53 -04:00
Autumn-27 89c5e9b26f fix(mock): intentsPage 返回分页格式,修复 worker 列表为空
handler 对 /exploration/intents 只返回裸数组,intentsPage 带
?page= 参数时期望 {items, has_more} 形状,r.items 取到 undefined
导致 mock 模式下 worker 会话列表始终为空。
2026-08-19 11:05:09 -04:00
Autumn-27 4f7c1a199d feat(tasks): 删除任务可选连带删除资产/漏洞/流量/文件(默认不选)
删除弹窗新增四个默认不勾的复选框:
- 删除关联的资产:仅删本任务独有资产,与其他任务共享的仅解除关联(保留)。
- 删除发现的漏洞:删本任务全部 findings。
- 删除测试的流量:按本任务资产的 host 清录制流量(host 无任务标签,会连带清
  其他任务命中同一 host 的流量)。
- 删除测试过程中写的文件:删 <workDir>/tasks/<id> 目录(worker 产物 + 上传)。

- db: AssetStore.DeleteByTaskID(独有删+共享解关联)/HostsByTask;DeleteFindingsByTask。
- server: pgDeleteTask 读可选 JSON body {assets,findings,traffic,files},先按 host
  清流量(需在删资产前取 host)→删漏洞→删资产→删任务→删文件目录;空 body=只删任务。
- web: api.deleteTask(id,opts);删除弹窗四复选框(默认不选)+说明。

go build/vet、tsc 通过;新增 TestDeleteByTaskID(独有删/共享解关联/host 反查)。
2026-08-19 10:40:19 -04:00
Autumn-27 2aeb2e49da fix(ui): 删除确认弹窗长文本溢出(break-words + break-all)
任务/漏洞名称含长 URL、路径等无空格断点的长串时,弹窗描述不换行撑出边界。
给 AlertDialogDescription 加 break-words,用户内容包 span break-all 强制断行。
覆盖任务删除 + 三处漏洞删除弹窗。
2026-08-19 10:30:56 -04:00
Autumn-27 90030051ce feat(findings): 漏洞删除功能(二次确认)
- db: DeleteFinding(id) 删 findings 行 + 来源探索节点(kind='finding'),
  级联清理 edges/node_assets、置空 activity 引用;返回受影响行数。
- server: DELETE /api/exploration/findings/{id}。
- web: api.deleteFinding;三处均可删且带 AlertDialog 二次确认——
  发现列表页独立「操作」列(每行垃圾桶按钮)、漏洞详情页头部删除按钮、
  任务详情·发现 Tab 展开区删除按钮。两处列表同源,删一次全部消失。
- 测试 TestDeleteFinding:findings 行 + 来源节点均被删,重复删返回(0,nil)。

go build/vet、tsc 通过。
2026-08-19 10:28:06 -04:00
Autumn-27 8047bb7aca feat(tasks): 任务并发上限(可选,超限排队自动补位)
任务页新建任务左侧加「并发设置」齿轮:开关(默认关) + 并发数量(开启默认5)。
开启后新建任务若「运行中」数已达上限,进入「排队中」不启动;有空位时后台
每 5s 自动把最早排队的任务补位启动。

- db: tasks 加持久化 queued 列(schema + ALTER 补旧库);Task.Queued/SetQueued。
- manager: ConcurrencyLimit/SetConcurrency(settings KV);SetTaskQueued(落库+同步内存)。
- 准入与促进器(goals.go):launchTask 统一准入,满则挂起排队;抽出 startTaskEngine
  复用;occupiesSlot/runningTaskCount/queueIfAtCapacity;reconcileConcurrency 每
  scheduler tick 补位(关闭则放行全部排队),concMu 串行化防超发;启动重载/setActive/
  reviveTask 三处'排队则不启动'门。
- settings: /api/settings 增 task_concurrency_enabled/limit;PUT 后立即协调一次。
- web: 齿轮 Dialog(Switch+数量);新增 queued/「排队中」状态(类型/状态色/筛选)。
- 超限只排队新任务,不打断在跑的;排队任务不消耗 LLM(目标拆解推迟到出队)。

go build/vet、tsc 通过;DB queued 往返测试通过。
2026-08-19 10:05:01 -04:00
Autumn-27 a6bd18050c feat(agent): 诊断 run 取消/终止原因,取代笼统的中断提示
- 新增 AbortCause(cancelcause.go):各取消点用 WithCancelCause/WithTimeoutCause
  附上原因(暂停/删除/kill_work/目标达成/drain 兜底/聊天停止/进程关闭/硬超时…),
  captureRun 用 context.Cause 还原出到底是谁、为何取消。
- 新增 terminalreason.go:把终端状态转成可读的一行摘要 + 多行详情(取消原因、
  终止 reason、轮数、卡住的工具),替换原来固定那句猜测式提示。
- engine/worker/planner/capture 及 conversations/orchestration/server 接入;
  cmd/artex/main.go、前端 llm/main-content 相应调整。
- 配套测试 terminalreason_test.go / engine_cancelcause_test.go。
2026-08-19 09:20:10 -04:00
Autumn-27 8e9b126972 feat(assets): 任务/企业资产列表改服务端分页,去掉 50 条上限
- db: QueryByTask/QueryByCompany 加 offset 参数,limit<=0 不再兜底成
  50/200 而是不限量;新增 CountByTask/CountByCompany/CountsByTypeForTask;
  抽出共享 pageClause 与 scanTypeCounts。
- server: /assets 的 task_id/company_id 分支改用真实 total(不再是
  len(assets));/assets/counts 支持 ?task_id= 返回按类型计数。
- web: taskAssets(taskId,type,limit,offset) 返回 {assets,total};
  assetCounts(taskId?);assets-tab 改为只取当前分类当前页,分类徽章走
  counts 接口,分页器 100/200 真正可用,轮询变轻。
- 补 TestQueryByTaskPaging/TestQueryByCompanyPaging 回归(60 条 25/页翻页
  不重不漏)。
2026-08-19 09:17:13 -04:00
Autumn-27 5244500fee fix(scheduler): 事件水位每 tick 都推进,避免新建触发器回放历史
原来 fire{Findings,Goals,TaskTimeouts,TaskCreates,ToolCalls} 在没有
对应触发器时直接 return,不推进水位。于是水位会一直停在旧值;等用户
新建/启用触发器(如预置的 reporter on_tool_call),下一个 tick 就把
水位之后的全部历史事件当成新事件触发一遍(逐条唤起 reporter 写报告)。

改为:即便当前没有对应触发器,也照常把水位推进到最新(静默消费),
触发动作只在存在触发器时执行。这样触发器只对启用之后出现的事件生效,
不再回放历史。goal 用 fired-set 同理:无触发器时也标记为已消费。
2026-08-19 09:17:04 -04:00
Autumn-27 e67ff26e90 fix(mock): 补全发现页今日改动的 demo 数据
- findingStats 返回 tasks(按任务筛选下拉);findingsPage 支持 task_id 过滤
- PATCH finding 支持改 name/vulnclass(行内编辑)
- 给 f-1/f-17 补详细报告 Markdown,行内看报告在 demo 里可见(3 条)
2026-08-18 13:33:04 -04:00
Autumn-27 d9474814b5 feat: 发现页支持按任务筛选/行内看报告与改名称类别严重度;任务列表输入去卡顿
- 发现列表新增「按任务」下拉(有漏洞的任务+条数),task_id 下推 SQL 筛选
- 展开行懒加载并渲染详细报告(Markdown),修 table 溢出:table-fixed +
  whitespace-normal + break-words + pre 换行
- 展开行前置「名称/类别/严重等级」可编辑并保存:patchFinding 增 name/vulnclass,
  DB 抽 setFindingCol 同步节点 payload
- 详情页状态区加「漏洞 ID」,列表证据行去掉裸 id
- 任务新建表单拆出独立组件消除输入卡顿;轮询 3s→10s+去重,时长 tick 仅 running 时跑
2026-08-18 13:27:46 -04:00
Autumn-27 24a94cf8e5 perf: 探索图减少卡顿(数据未变不重建 + 只渲染可见节点)
- graph-tab:轮询按签名判重,图数据无变化时跳过 setState,避免整图重建;
  拖动不再被 20s 轮询打断
- ExplorationGraph:ReactFlow 加 onlyRenderVisibleElements,屏幕外节点不渲染
2026-08-18 13:18:32 -04:00
Autumn-27 08cdac2362 tweak: 任务图谱轮询间隔 3s → 20s 2026-08-18 13:16:32 -04:00
Autumn-27 fcc1e75a85 refactor: 抽出共享 ExplorationGraph 画布,漏洞链路图改用 React Flow
- 新增 components/exploration-graph.tsx:把任务图谱 Tab 的 React Flow 渲染
  (节点卡片/自动分层布局/连线/小地图/图例/点击抽屉)整体抽成 <ExplorationGraph
  nodes edges> 复用组件,取 {nodes,edges} 作为 props
- graph-tab.tsx 精简为纯取数(轮询 explorationGraph)+ 复用该画布
- 漏洞详情「链路图」Tab 改用同一画布渲染 lineage 子图(从初始节点到漏洞节点),
  与任务攻击链路图完全一致的观感;之前的纵向卡片版删除

纯前端重构,后端 lineage 接口不变。tsc 通过(tasks/page.tsx 的既有报错与本次无关)。
2026-08-18 13:13:48 -04:00
Autumn-27 b741ee176c feat: 漏洞详情页删除证据/元数据 Tab,新增「链路图」
链路图展示该漏洞所属任务探索图里,从初始节点(任务根)追溯到本漏洞节点的
那条链路:漏洞节点 + 其全部祖先(沿边反向可达)+ 之间的关系边。

- db.ExplorationStore.FindingLineage(nodeID):递归 CTE 取祖先子图(节点+边),
  下游(如 finding→proves→goal)与无关节点不纳入
- GET /api/exploration/findings/{id}/lineage:解析 finding 的 node_id/task_id →
  返回 {nodes,edges};无节点/任务已删 → 空图
- 前端 api.findingLineage;详情页 Tab 改为「概览 / 链路图」
- lineage.tsx:按深度拓扑排序,自根到漏洞纵向展示节点卡片 + 入边关系标注,
  漏洞节点高亮
- db 测试补 FindingLineage 断言(含祖先/排除下游 goal/排除无关意图)

go build / db / server 测试通过。
2026-08-18 13:08:03 -04:00
Autumn-27 33714a5ff0 feat: 任务发现 Tab 的漏洞行支持跳转到漏洞详情页
每条发现行(finding_id 存在时)加「详情」链接 → /function/findings/detail?id=<finding_id>。
finding_id 由 per-task DTO 经 node_id 关联独立 findings 行填充。
2026-08-18 12:50:36 -04:00
Autumn-27 57114a2994 feat: 触发器支持编辑(前端补齐,后端早已支持 PATCH)
触发器 Tab 之前只能新增/删除/启停。改造「新增」表单为「新增/编辑」双模式:
- 每条触发器加「编辑」按钮 → 灌入表单进入编辑态(高亮该行),改完点「保存修改」
  调 api.updateTrigger;可「取消编辑」
- 编辑时保留该触发器原有启用状态;删除正在编辑的触发器会自动退出编辑态
后端 PATCH /api/triggers/{id} 与 api.updateTrigger 早已存在,本次仅补前端 UI。
2026-08-18 12:38:14 -04:00
Autumn-27 87221be586 tweak: reporter 触发运行策略默认改为 parallel + merge=none
一漏洞一报告:多个 finding 并发各写各的,避免串行排队积压。
merge 设 none(而非默认 all)——否则一波 finding 会被合并成一次运行,并行失效;
maxParallel=5 限制同时的报告会话数。
2026-08-18 12:27:26 -04:00
Autumn-27 05874da129 tweak: reporter 报告模板将「证据」与「PoC」拆成两节
- 证据:证明漏洞真实存在的请求/响应/命令输出/回显,贴原文
- PoC:可运行/复用的利用代码或 payload,通常以代码块给出完整代码 + 运行说明
2026-08-18 12:20:53 -04:00
Autumn-27 6cad678699 feat: 预置「报告撰写」自定义 agent,发现漏洞时自动写详细报告
- 新增 host 工具 update_finding_report(finding_id, report):按 node_id 写
  findings.report(任务无关,无需 task 上下文);get_task_node_detail(task_id, id)
  读取漏洞完整证据/PoC。均注册进 orchestrationTools。
- promptcatalog 新增 ReporterDefaultPrompt:详述报告应含的结构(概述/影响/
  受影响范围/复现步骤/证据PoC/根因/修复建议)与只据实证撰写的纪律。
- seedReporterAgent(一次性,flag 守卫):预置 builtin=false 的自定义 agent
  「报告撰写」(reporter),UI 可编辑/删除;绑定 update_finding_report + 任务
  查询工具(node_detail/worker_trace/findings/graph);建 on_tool_call 触发器
  (tool_names=[report_finding]) —— 每登记漏洞即唤起它写报告并回写。
- 移除上一提交里绑到 worker/planner/mainagent 的 in-task update_finding_report
  工具及 ExplorationStore 包装(触发态是会话上下文,须用 host 工具);保留
  db.SetFindingReportByNodeID;新增 ToolSet.NodeDetailTool 访问器。

go build / agent / server 测试通过。reporter 为自定义 agent,不影响内置 agent 计数。
2026-08-18 12:19:14 -04:00
Autumn-27 c0ac501c4b feat: 新增 update_finding_report 工具,写入漏洞 report 字段
- 新增 agent 写工具 update_finding_report(finding_id + report[Markdown]),
  整段覆盖该漏洞的详细报告;finding_id 传 report_finding 返回的 id
- 绑定 worker / planner / mainagent 三个 agent,经 BuiltinToolSeeds 自动入
  tools 目录(首插不覆盖,旧库升级重启即出现,可在 UI 管理/勾选)
- db.SetFindingReportByNodeID / ExplorationStore.SetFindingReport:按 finding
  节点 id 更新独立 findings 行的 report
- 详情页概览已渲染 report,写入后即可展示

go build / agent / server 测试通过。
2026-08-18 11:56:43 -04:00
Autumn-27 8092167ae5 feat: finding 增加详细报告字段 report(Markdown),详情页证据下方渲染
- findings 表加 report 列(TEXT 默认空,schema + ALTER 补旧库)
- 仅 GetFinding 查 report,列表/分页不带以免 payload 膨胀;FindingDTO.report omitempty
- 详情页「概览」证据卡下方新增「详细报告」卡,复用 <Markdown> 组件渲染,空时占位提示
- 前端 Finding.report? 类型;mock 单条 GET 返回 report、列表剥掉,并给 f-12 加示例报告
- 字段先留空,后续由写入方填充;db 测试补 report 往返断言

next build 通过。
2026-08-18 11:52:34 -04:00
Autumn-27 64d9c66f6a feat: 新增发现详情页(多 Tab + 状态区可改严重度/处置状态)
详情页 /function/findings/detail?id=<finding_id>:
- 概览 Tab:左栏摘要 + 证据/PoC,右栏状态区(严重等级、漏洞类型、涉及资产、处理状态、所属任务、发现时间)
- 严重等级与处理状态用下拉,选择后直接调后端接口即时修正
- 另有「证据/PoC」「元数据」两个 Tab
- 发现列表页漏洞名称改为链接进入详情

后端:
- GET  /api/exploration/findings/{id}  取单条(含资产解析)
- PATCH /api/exploration/findings/{id} 扩展为部分更新 {status?, severity?}
- db.GetFinding / db.SetFindingSeverity(同步探索节点 payload,保持任务内视图一致)
- db.ValidSeverity 白名单;api 增加 getFinding / setFindingSeverity
- mock 支持单条 GET / PATCH,并回填 finding_id 供 demo 链接

db 测试补充 GetFinding / SetFindingSeverity 往返断言;next build 通过。
2026-08-18 11:43:50 -04:00
Autumn-27 c69534e09b tweak: 引擎停滞判定阈值 60s → 120s
stalled 判据(在跑、无在执行意图、planner/worker 静默)的静默时长从 60 秒放宽到 120 秒。
2026-08-18 11:31:42 -04:00
Autumn-27 82c6d4a06b feat: 顶栏显示后端应用版本号
后端 /api/health 返回 BuildVersion(由 ldflags 注入的 main.version),
前端顶栏设置按钮左侧展示 v<版本>。
2026-08-18 11:27:01 -04:00
Autumn-27 ff3d41ea35 feat: 漏洞新增 severity=critical(严重) 与 name(漏洞名称)字段
severity 严重级:
- report_finding 工具 severity 说明改为 critical|high|medium|low
- DB 排序 CASE、report 报告 sevRank、FindingStats 增加 critical
- 前端 Severity 类型、status.ts 新增 rose 色调(实心高强调,视觉高于高危)、
  发现页严重度筛选/统计卡、任务发现 Tab 排序、dashboard 汇总均加入严重

name 漏洞名称:
- findings 表加 name 列(schema + ALTER TABLE ADD COLUMN IF NOT EXISTS 补旧库)
- report_finding 工具新增 name 入参(非必填),写入 node payload 与独立 findings 行
- DBFinding/AddFinding/AddStandaloneFinding、FindingDTO、findingPayload、report 全链路透传
- 发现列表页与任务发现 Tab 展示 name,为空时回退 vulnclass;vulnclass 移到展开详情「类型」
- report 报告标题优先 name,回退 vulnclass

无 CHECK 约束,旧库仅需补 name 列;critical/name 均向后兼容。
db 测试补充 critical 排序/统计与 name 往返断言。
2026-08-18 11:21:08 -04:00
Autumn-27 7b3eec2166 feat: 发现处置状态扩展为 8 个
在 待处理/误报/忽略/已处理 基础上新增:
- in_progress 处理中(已受理、修复/验证中)
- confirmed 已确认(真实漏洞未修复)
- duplicate 重复
- risk_accepted 风险接受(确认存在但业务接受)

改动:db 常量 + ValidFindingStatus 白名单、schema 注释、status.ts 色板/文案、
types.ts 联合类型、两处下拉的 FINDING_STATUSES(下拉与状态筛选自动带出)。
状态无 CHECK 约束,旧库无需迁移。
2026-08-18 10:37:13 -04:00
Autumn-27 58a277e2d1 fix: 发现状态下拉点不动(item-aligned 快速点击即闭合)
行内状态 Select 用组件默认 position="item-aligned",会把当前选中项铺在触发器上,
在 h-7 小触发器上按下即打开、抬起时正好落在当前项 → 瞬间又选中同值并关闭,表现为点不动。
改用 position="popper" align="end",下拉浮在触发器下方,不覆盖触发器。
2026-08-18 10:11:23 -04:00
Autumn-27 30dc09ae08 feat: 发现列表改为服务端分页 + 全表聚合(去掉 500 上限)
- db.ListFindingsPage:WHERE(严重度/状态/类型)+ ORDER BY(严重度 CASE 或时间)+ LIMIT/OFFSET,返回当前页与匹配总数
- db.FindingStats:全表 COUNT FILTER 聚合(总数/待处理/高/中/低)+ DISTINCT 漏洞类型,供统计卡与类型下拉,不受分页影响
- GET /api/exploration/findings 带 page/limit → 分页对象{items,total,page,page_size};不带 → 保留裸数组(dashboard 汇总,与 intents 端点兼容策略一致)
- 新增 GET /api/exploration/findings/stats
- 筛选/排序全部下推到 SQL,前端不再一次拉全量再本地切片
- 资产按当前页所需批量解析;per-task 视图经 node_id 关联独立行,复用同一状态并补齐绑定资产
- 前端:发现页改为服务端驱动分页/筛选/排序,统计卡与类型下拉走 stats 接口
- db 层加 findings 分页/筛选/聚合测试(隔离标记 vulnclass,自清理)
2026-08-18 10:05:18 -04:00
Autumn-27 05f0b69469 feat: finding 增加处置状态(待处理/误报/忽略/已处理)+ 发现列表页改版
- findings 表新增 status 列(默认 pending),schema.sql 同步 ALTER TABLE 补旧库
- 新增 PATCH /api/exploration/findings/{id} 更新处置状态,server 侧白名单校验
- FindingDTO 增加 status/finding_id/assets(后端预渲染资产 label)
- 任务内发现节点通过 node_id 关联独立 findings 行,复用同一处置状态
- 发现列表页:漏洞名称(类型主 + 摘要小字)、新增资产列与状态列(可下拉修改)、状态筛选与待处理统计
- 任务详情发现 Tab 同步展示/编辑处置状态与绑定资产
2026-08-18 09:46:46 -04:00
Autumn-27 c588dd67ae feat: 删除任务增加二次确认弹窗
任务列表的删除按钮改为 AlertDialog 二次确认(与对话删除交互一致),
确认后才调用 api.deleteTask。
2026-08-18 09:30:13 -04:00
Autumn-27 d901543aba fix: 修复覆盖图谱卸载竞态导致的 g6 崩溃(getTransformInstance)
d3-force 力导向布局是异步多帧模拟。布局未跑完就切走图谱标签→组件卸载
graph.destroy() 把 context 清空,飞行中的布局落地时 transformDataAfterLayout
读 context.transform.getTransformInstance() → undefined 崩溃。
- applyData 渲染前判 graph.destroyed;render() 挂 .catch 吞掉 teardown 竞态的
  拒绝(已销毁静默,未销毁仍打日志),不再冒泡成运行时错误。
- 卸载先 stopLayout() 再 destroy(),缩短竞态窗口。
2026-08-18 09:15:42 -04:00
Autumn-27 3633ae78f4 feat: 任务总览页顶部展示原始任务描述与目标
创建时填写的描述/目标此前在总览页看不到,加一张置顶卡片左右两栏展示
(whitespace-pre-wrap 保留换行,空值显示 —),数据复用已有 task 状态。
2026-08-18 09:15:42 -04:00
Autumn-27 6a5e7c1b84 feat: 新建任务改为右侧抽屉(45%宽)+ 支持上传文件写入描述
- 表单从居中弹窗(Dialog)换成右侧 Sheet,宽 45vw(窄屏全宽),整屏高度可
  滚动,长表单不再受弹窗高度限制;头/脚固定、中间字段区独立滚动。
- 高级参数(超时/心跳/首个意图)收进「高级设置」Collapsible 折叠,常用路径
  只露 描述/目标/LLM;描述与目标 textarea 加高(min-h-32)。
- 描述下方加「上传文件」组件(可多选):建任务前任务无 ID,新增上传
  scope=staging,文件落到 <workDir>/drafts/<draftId>/uploads/,后端返回绝对
  路径(chatAttachment.Abs),前端把绝对路径以提示词形式追加到描述末尾,供
  worker 用 Read/Bash 打开;再次上传追加到同一标记块。draftId 用
  crypto.randomUUID(非安全上下文降级),创建成功后清空。
2026-08-18 09:01:56 -04:00
Autumn-27 683f5ee684 feat: 独立对话(ChatAgent)也支持文件上传(方式1:落盘 + Read/Bash 读取)
补齐上次只做了任务主 Agent 的遗留:独立对话面此前只共用了 transcript 渲染,
后端注入 + Composer 上传缺失,现补上。

- 后端(pgSendConversationMessage):收 attachments;用 userActivityWithAttachments
  存 {text,attachments} JSON 供卡片渲染(无附件仍存全文 Detail 供懒加载);用
  composeAgentMessage 把【绝对路径】清单拼进发给 ChatAgent 的消息,baseDir=
  <workDir>/sessions/conv-<id>/,与 chatUpload 落盘、chat.go 的 sessionWorkDir 对齐;
  纯附件无文本也可发,首条会话名兜底"附件消息"。
- 前端(chat/page.tsx):Composer 加回形针上传按钮 + 附件 chip 预览(可移除);
  ChatView 上传到 scope=session&id=conv-<id> 并带附件发送;DraftChat 草稿态没有
  会话 id,点回形针先懒创建会话再上传,交接文本+附件给 ChatView 发送。
- api.sendConversationMessage 增加 attachments 参数。
2026-08-18 07:55:18 -04:00