@xcanwin/manyoyo 8.6.7 → 8.6.8
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/lib/web/AGENTS.md +1 -0
- package/lib/web/index.html +86 -85
- package/lib/web/server.js +71 -9
- package/package.json +2 -1
package/lib/web/AGENTS.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- **追加提示词(排队)**:任务进行中用户可以追加提示词,队列存在 `agentSession.queue`(`lib/web/agent-queue.js`,最多 10 条,`when` 为 `now` / `step` / `turn`;`normalizeWebAgentSession` 的字段白名单里要有它,否则落盘后丢失)。接口 `POST /api/sessions/:name/agent/queue`(clientMessageId 幂等;容器被别的会话占着返回 409)、`DELETE .../agent/queue/:id`、`POST .../agent/queue/continue`;`/messages` 带 `queue`。一轮对话的完整流程抽成 `runWebAgentStreamTurn(..., res)`:前端不在线时传 `createHeadlessResponse()` 顶替 `res`,事件照常落盘。每一轮结束(完成 / 失败 / 被追加打断,**用户点停止除外**,由 `stopWebAgentRun` 置 `runState.userStopped`,并把 `agentSession.queuePaused` **落盘**——只记在内存里的话,serve 重启或用户下一次正常发送、那一轮结束后,停止前的排队会被自动发出去;暂停只有 `POST .../agent/queue/continue` 能解除,队列清空时自动解除,`/messages` 带 `queuePaused`)后若队列可以自动发(`agentQueue.shouldAutoDispatch`),先给连着的流发一个 `queue_next` 事件(前端据此结束这条流、改走恢复轮询),再 `setImmediate` 调 `dispatchNextQueued`(同步取出队首并占锁,开不起来就放回队首)。`now` 立即走进程树信号;`step` 在 `isStepBoundary`(一次工具 / 子 Agent / 说明完成)处打断,`agentStepFallbackMs`(默认 120 秒)等不到就退化成 `now`。serve 重启后 `startContainerServices` 末尾调 `resumeQueuedAgentTurns`,把留在队列里(暂停的除外)又没有任务在跑的内容当作 `turn` 发出;发之前用 `buildLiveRunsCommand`(只读,校验规则同停止脚本)看容器里还有没有活着的 Agent 进程,有就不发、排队留给用户——那可能是上一个 serve 的孤儿任务,也可能是同一 HOME 下另一个 serve 实例正在跑的任务,所以只检测不杀。
|
|
20
20
|
- **停止 / 打断必须进到容器里发信号**(`lib/web/agent-run-control.js`):只杀宿主机上的 `docker exec` 客户端,容器里的 Agent 进程会继续跑(实测)。命令被包成 `echo $$ > /tmp/manyoyo-run-<id>.pid; <命令>; exit $?`,**末尾的 `exit $?` 不能省**——命令是最后一条简单命令时 bash 会 exec 替换自己,`/proc/<pid>/cmdline` 里的 `manyoyo-run-<id>.pid` 标记就没了,停止时会因“认不出进程”而不杀(真实容器测出来的,单测里用 `sleep 61; true` 测不到)。PID 文件容器里可写,按不可信输入处理(纯数字、普通文件、命令行带标记才发信号),再按子孙进程树逐级 INT → TERM → KILL。真实的 claude 收到 SIGINT 会正常收尾、**退出码为 0**,所以 `interrupted` 看“发过信号”(`runState.signaled`)而不只是退出码。已验证 Claude 打断后 `-r <id>` 能接上上下文;Codex / Gemini / OpenCode 没有环境验证,拿不到引擎会话 id 时走既有的历史注入降级。
|
|
21
21
|
- **子 Agent**:Claude 的 `Agent`(旧名 `Task`)工具生成 `kind: "subagent"` 事件,子 Agent 里的事件带 `parentToolId`(取自 `parent_tool_use_id`);带 `parent_tool_use_id` 的 assistant 文本不当作主 Agent 的回复。`--forward-subagent-text` 只在 `claude --help` 里有该参数时才加(命令里就地探测,约 50ms,不缓存)。样本 fixture:`test/fixtures/claude-subagent.ndjson`。
|
|
22
|
+
- **最终回复的取值**(`resolveAgentFinalOutput`):原始 stdout 只保留开头 `AGENT_RAW_OUTPUT_MAX_CHARS`(32MB,会话 id 在开头),另外用 `createTailBuffer` 保留结尾(8MB)。最终回复和用量都在结尾:超过上限后只解析开头,会把截断点之前的某条中间说明当成最终回复(每个 token 一行约 260 字节的增量,加上思考 token、子 Agent、大段工具结果,长任务很容易超过);所以截断时先从结尾提取,提取不到再看开头,用量也优先看结尾(`stdoutTail`)。Claude 以最后一个 `result` 事件的 `result` 为准(后台任务场景结尾有两个 `result`,第一个是中间结论)。提取到的最终回复**不截断**(与流式落盘一致),提取不到才退回原始输出并按 `WEB_OUTPUT_MAX_CHARS` 截断。子进程的 stdout / stderr 必须 `setEncoding('utf8')` 按流解码:逐块 `toString` 会把跨块的中文拆成乱码。测试可用 `agentRawOutputMaxChars` 调小上限;真实样本 `test/fixtures/claude-background-task.ndjson`、`claude-subagent-parallel.ndjson`(claude 2.1.290,已精简)。
|
|
22
23
|
- **历史读写在 `lib/web/history-store.js`**:进程内缓存 + 原子写(`writeConfigFileSecure`,0600)+ 合并写盘。读靠 `stat` 的 `ino + mtimeMs + size` 判断缓存是否还有效(别的进程原子替换也能识别);`loadWebSessionHistory` 返回的是**缓存里的同一个对象**,改了必须 `saveWebSessionHistory`,只读路由不要改它(`getWebAgentSession(..., { create: true })` 会在缓存里留一个未保存的空 agent,只在马上要写的路径里用)。流式过程中的 trace patch 和控制事件走 `{ defer: true }`(只改内存,最迟 `AGENT_STREAM_PARTIAL_PERSIST_INTERVAL_MS` 后落盘);`process.exit` / serve 关闭会 flush;`result` / `error` 类事件(`WEB_STREAM_FLUSH_EVENT_TYPES`)和所有非 defer 写入立即写盘并带上攒着的内容。`kill -9` 最多丢约 1 秒的 trace。文件解析失败会留一份 `.corrupt-<时间>` 备份再按空历史处理,不要绕过它。磁盘格式只有 `agents.<id>.messages`(不再有顶层 `messages`,紧凑 JSON),老版本(含 `update --rollback`)读取时优先用 `agents`,能读新文件。
|
|
23
24
|
- `GET /api/sessions` 以前是同步 IO 大户(逐容器读 + 解析整份历史),现在命中缓存基本是 0 次解析,但新增的同步操作仍要先实测;`buildSessionSummary()` 支持传入调用方加载好的 history,不要重复读。(`docker ps -a` + 批量 `inspect` 实测仅约 90ms,不是瓶颈)
|
|
24
25
|
- **`/messages` 是分页 + 增量**(`lib/web/messages-view.js`):`?limit=`(1~200,默认 60,起点往前对齐到 user 消息)/ `?after=<id>&epoch=<e>`(tail,锚点之后的消息;epoch 不符、锚点不存在或超过 120 条时返回最近一页并标 `mode: "reset"`)/ `?before=<id>`(更早一页);`GET /messages/<id>/trace` 取完整执行过程。查询参数全部白名单校验,非法返回 400。**稳定前缀约定**:第一条 pending 消息之前的消息不再被修改——违反约定的写操作(删除非 pending 消息、克隆)必须给该 agent 换新的 `epoch`(`removeWebSessionMessage` 与 clone 已处理;新增修改历史消息的写操作要自己判断)。500 条上限从头裁掉**不换** epoch(tail 响应带 `firstId`,前端据此裁掉本地被丢弃的开头)。列表里的 trace 消息会被精简(长字符串截成 200 字符,带 `traceTruncated`),`/audit` 与 trace 接口输出完整内容。
|