qlogicagent 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +402 -45
  2. package/package.json +2 -2
  3. package/dist/agent.js +0 -1
  4. package/dist/cli.js +0 -9
  5. package/dist/contracts.js +0 -1
  6. package/dist/index.js +0 -5
  7. package/dist/orchestration.js +0 -118
  8. package/dist/types/agent/agent.d.ts +0 -43
  9. package/dist/types/agent/tool-loop.d.ts +0 -64
  10. package/dist/types/agent/types.d.ts +0 -175
  11. package/dist/types/cli/main.d.ts +0 -11
  12. package/dist/types/cli/stdio-server.d.ts +0 -45
  13. package/dist/types/config/config.d.ts +0 -17
  14. package/dist/types/contracts/hooks.d.ts +0 -120
  15. package/dist/types/contracts/index.d.ts +0 -10
  16. package/dist/types/contracts/planner.d.ts +0 -35
  17. package/dist/types/contracts/skill-candidate.d.ts +0 -63
  18. package/dist/types/contracts/todo.d.ts +0 -14
  19. package/dist/types/index.d.ts +0 -13
  20. package/dist/types/llm/builtin-providers.d.ts +0 -10
  21. package/dist/types/llm/index.d.ts +0 -15
  22. package/dist/types/llm/llm-client.d.ts +0 -43
  23. package/dist/types/llm/model-catalog.d.ts +0 -53
  24. package/dist/types/llm/provider-def.d.ts +0 -59
  25. package/dist/types/llm/provider-registry.d.ts +0 -54
  26. package/dist/types/llm/transport.d.ts +0 -62
  27. package/dist/types/llm/transports/anthropic-messages.d.ts +0 -31
  28. package/dist/types/llm/transports/openai-chat.d.ts +0 -36
  29. package/dist/types/orchestration/agent-registry.d.ts +0 -41
  30. package/dist/types/orchestration/approval-aware-tool-plan.d.ts +0 -32
  31. package/dist/types/orchestration/context-compression.d.ts +0 -220
  32. package/dist/types/orchestration/conversation-repair.d.ts +0 -61
  33. package/dist/types/orchestration/curator-scheduler.d.ts +0 -119
  34. package/dist/types/orchestration/embedded-failover-policy.d.ts +0 -110
  35. package/dist/types/orchestration/error-classification.d.ts +0 -12
  36. package/dist/types/orchestration/failover-classification.d.ts +0 -8
  37. package/dist/types/orchestration/failover-error.d.ts +0 -33
  38. package/dist/types/orchestration/fork-subagent.d.ts +0 -100
  39. package/dist/types/orchestration/index.d.ts +0 -120
  40. package/dist/types/orchestration/memory-flush-policy.d.ts +0 -57
  41. package/dist/types/orchestration/memory-provider.d.ts +0 -14
  42. package/dist/types/orchestration/parallel-tool-calls.d.ts +0 -41
  43. package/dist/types/orchestration/prompt-cache-strategy.d.ts +0 -126
  44. package/dist/types/orchestration/reactive-compact.d.ts +0 -73
  45. package/dist/types/orchestration/retry-loop.d.ts +0 -22
  46. package/dist/types/orchestration/skill-candidate.d.ts +0 -52
  47. package/dist/types/orchestration/skill-consolidation.d.ts +0 -123
  48. package/dist/types/orchestration/skill-improvement.d.ts +0 -59
  49. package/dist/types/orchestration/skill-similarity.d.ts +0 -98
  50. package/dist/types/orchestration/streaming-tool-executor.d.ts +0 -73
  51. package/dist/types/orchestration/team-orchestration.d.ts +0 -195
  52. package/dist/types/orchestration/team-tool-loop-wiring.d.ts +0 -92
  53. package/dist/types/orchestration/tool-choice-policy.d.ts +0 -54
  54. package/dist/types/orchestration/tool-loop-state.d.ts +0 -50
  55. package/dist/types/orchestration/tool-schema.d.ts +0 -39
  56. package/dist/types/orchestration/transcript-repair.d.ts +0 -42
  57. package/dist/types/orchestration/turn-loop-guard.d.ts +0 -86
  58. package/dist/types/orchestration/web-browser-policy.d.ts +0 -17
  59. package/dist/types/runtime/context-compression.d.ts +0 -61
  60. package/dist/types/runtime/hook-registry.d.ts +0 -12
  61. package/dist/types/runtime/memory-hooks.d.ts +0 -23
  62. package/dist/types/runtime/tool-eligibility.d.ts +0 -59
  63. package/dist/types/skills/index.d.ts +0 -108
  64. package/dist/types/skills/memory-extractor.d.ts +0 -64
  65. package/dist/types/skills/memory-query-tool.d.ts +0 -43
  66. package/dist/types/skills/memory-store.d.ts +0 -66
  67. package/dist/types/skills/memory-tool.d.ts +0 -67
  68. package/dist/types/skills/portable-tool.d.ts +0 -71
  69. package/dist/types/skills/qmemory-adapter.d.ts +0 -52
  70. package/dist/types/skills/skill-frontmatter.d.ts +0 -19
  71. package/dist/types/skills/skill-guard.d.ts +0 -23
  72. package/dist/types/skills/skill-loader.d.ts +0 -16
  73. package/dist/types/skills/skill-source.d.ts +0 -119
  74. package/dist/types/skills/skill-types.d.ts +0 -199
  75. package/dist/types/skills/think-tool.d.ts +0 -16
  76. package/dist/types/skills/todo-tool.d.ts +0 -63
  77. package/dist/types/skills/tools/agent-tool.d.ts +0 -91
  78. package/dist/types/skills/tools/apply-patch-tool.d.ts +0 -29
  79. package/dist/types/skills/tools/ask-user-tool.d.ts +0 -80
  80. package/dist/types/skills/tools/brief-tool.d.ts +0 -74
  81. package/dist/types/skills/tools/browser-tool.d.ts +0 -114
  82. package/dist/types/skills/tools/checkpoint-tool.d.ts +0 -66
  83. package/dist/types/skills/tools/config-tool.d.ts +0 -63
  84. package/dist/types/skills/tools/cron-tool.d.ts +0 -116
  85. package/dist/types/skills/tools/edit-tool.d.ts +0 -43
  86. package/dist/types/skills/tools/exec-tool.d.ts +0 -102
  87. package/dist/types/skills/tools/image-generate-tool.d.ts +0 -62
  88. package/dist/types/skills/tools/instructions-tool.d.ts +0 -103
  89. package/dist/types/skills/tools/lsp-tool.d.ts +0 -153
  90. package/dist/types/skills/tools/mcp-client-types.d.ts +0 -269
  91. package/dist/types/skills/tools/mcp-tool.d.ts +0 -249
  92. package/dist/types/skills/tools/memory-tool.d.ts +0 -74
  93. package/dist/types/skills/tools/monitor-tool.d.ts +0 -113
  94. package/dist/types/skills/tools/music-generate-tool.d.ts +0 -55
  95. package/dist/types/skills/tools/notify-tool.d.ts +0 -53
  96. package/dist/types/skills/tools/patch-tool.d.ts +0 -45
  97. package/dist/types/skills/tools/pdf-tool.d.ts +0 -66
  98. package/dist/types/skills/tools/plan-mode-tool.d.ts +0 -59
  99. package/dist/types/skills/tools/read-tool.d.ts +0 -51
  100. package/dist/types/skills/tools/repl-tool.d.ts +0 -70
  101. package/dist/types/skills/tools/search-tool.d.ts +0 -112
  102. package/dist/types/skills/tools/send-message-tool.d.ts +0 -51
  103. package/dist/types/skills/tools/skill-list-tool.d.ts +0 -33
  104. package/dist/types/skills/tools/skill-manage-tool.d.ts +0 -73
  105. package/dist/types/skills/tools/skill-view-tool.d.ts +0 -37
  106. package/dist/types/skills/tools/sleep-tool.d.ts +0 -49
  107. package/dist/types/skills/tools/structured-output-tool.d.ts +0 -116
  108. package/dist/types/skills/tools/task-tool.d.ts +0 -104
  109. package/dist/types/skills/tools/team-tool.d.ts +0 -89
  110. package/dist/types/skills/tools/tool-search-tool.d.ts +0 -51
  111. package/dist/types/skills/tools/tts-tool.d.ts +0 -38
  112. package/dist/types/skills/tools/video-edit-tool.d.ts +0 -69
  113. package/dist/types/skills/tools/video-generate-tool.d.ts +0 -62
  114. package/dist/types/skills/tools/video-merge-tool.d.ts +0 -105
  115. package/dist/types/skills/tools/video-upscale-tool.d.ts +0 -45
  116. package/dist/types/skills/tools/web-fetch-tool.d.ts +0 -78
  117. package/dist/types/skills/tools/web-search-tool.d.ts +0 -57
  118. package/dist/types/skills/tools/worktree-tool.d.ts +0 -69
  119. package/dist/types/skills/tools/write-tool.d.ts +0 -45
package/README.md CHANGED
@@ -1,45 +1,402 @@
1
- # qlogicagent
2
-
3
- CLI 子进程 Agent。小智 Claw 的本地 AI Agent 运行时,作为 openclaw Gateway 的子进程通过 **JSON-RPC over stdio** 通信。
4
-
5
- ## 职责
6
-
7
- 1. 接收 `agent.turn` JSON-RPC 请求,执行 Agent Loop(prompt LLM → tool calls → iterate)。
8
- 2. 通过 stdout 流式输出 `turn.chunk`、`turn.tool_call`、`turn.done` 等 JSON-RPC 事件。
9
- 3. 工具执行委托回 Gateway(`turn.tool_call` Gateway 执行 → `tool.result` 返回)。
10
- 4. 支持 failover、parallel tools、context compression、team orchestration 等编排策略。
11
- 5. 可选 qmemory 集成(memory hooks、skill learning、dream mechanism)。
12
-
13
- ## 不负责
14
-
15
- 1. HTTP/WebSocket 服务 — 由 qlogicagent-hub 负责。
16
- 2. 持久化存储 Agent 是无状态子进程,Session/Skill/Plan 由 Gateway 管理。
17
- 3. LLM API Key 管理 — 配置通过 `agent.turn` 请求的 `config.llm` 字段传入。
18
- 4. 平台 API、订阅、计费 — 由 qlogicagent-hub + openclaw-admin 负责。
19
-
20
- ## 模块结构
21
-
22
- ```
23
- src/
24
- ├── cli/ # JSON-RPC stdio 入口 (main.ts, stdio-server.ts)
25
- ├── agent/ # Agent 核心 (agent.ts, tool-loop.ts, types.ts)
26
- ├── llm/ # LLM provider 注册、传输层 (OpenAI, Anthropic)
27
- ├── orchestration/ # 编排策略 (failover, retry, parallel, compression, team)
28
- ├── skills/ # PortableTool 实现 + 外部 skill 加载
29
- ├── runtime/ # Hook registry, memory hooks, context compression
30
- ├── contracts/ # Hook / planner / skill candidate / todo 契约
31
- └── config/ # 配置管理
32
- ```
33
-
34
- ## 运行命令
35
-
36
- ```bash
37
- pnpm build # TypeScript 编译
38
- pnpm test # vitest 测试
39
- pnpm dev # tsx watch 开发
40
- ```
41
-
42
- ## 测试
43
-
44
- - 单元测试:`test/` 目录,覆盖 LLM provider、orchestration、semantic turn tools。
45
- - E2E 测试:`test/e2e/` 目录,spawn CLI 进程验证 JSON-RPC 协议行为。
1
+ # qlogicagent
2
+
3
+ 小智 Claw 的本地 AI Agent 运行时。作为 openclaw Gateway CLI 子进程,通过 **JSON-RPC over stdio** 通信,执行完整的 Agent Loop。
4
+
5
+ ## 功能概览
6
+
7
+ - **44 个内置工具**: 文件操作、Shell 执行、代码编辑、Web 搜索/抓取、图片/视频/音乐生成、MCP 桥接等
8
+ - **多 LLM Provider**: OpenAI、Anthropic、DeepSeek 等,通过统一 `LLMTransport` 接口切换
9
+ - **编排策略**: failover 降级、指数退避重试、并行工具执行、14+ 上下文压缩策略、多代理团队协作
10
+ - **权限系统**: 命令安全分类、规则引擎、用户审批、破坏性命令警告
11
+ - **记忆系统**: 本地 MD 记忆 + QMemory 语义长期记忆 + 会话记忆自动提取 + Dream 整合
12
+ - **会话持久化**: JSONL 对话转录 + 状态快照 + 会话恢复
13
+ - **MCP 协议**: stdio/HTTP 传输、动态工具注入、资源发现
14
+ - **插件系统**: 用户级/项目级插件加载、插件 API
15
+ - **技能系统**: SKILL.md 发现/调用/管理、自主技能学习
16
+ - **Hook 系统**: 30+ 生命周期钩子点,可扩展不侵入核心
17
+
18
+ ## 快速开始
19
+
20
+ ### 1. 环境准备
21
+
22
+ ```bash
23
+ # Node.js 22+
24
+ node --version # v22.x.x+
25
+
26
+ # 安装依赖
27
+ pnpm install
28
+
29
+ # 构建
30
+ pnpm build
31
+ ```
32
+
33
+ ### 2. 配置环境变量
34
+
35
+ ```bash
36
+ cp .env.example .env
37
+ ```
38
+
39
+ 编辑 `.env`:
40
+
41
+ ```bash
42
+ # 日志级别
43
+ LOG_LEVEL=info
44
+
45
+ # QMemory 服务地址(启用长期记忆)
46
+ QMEMORY_BASE_URL=http://127.0.0.1:18800
47
+
48
+ # QMemory API Key(可选)
49
+ # QMEMORY_API_KEY=your-key
50
+ ```
51
+
52
+ ### 3. 启动 QMemory(推荐)
53
+
54
+ qlogicagent 的完整功能依赖本地 QMemory 服务提供语义记忆:
55
+
56
+ ```bash
57
+ # 方式一:直接运行(需 Python 3.11+)
58
+ cd ../qmemory
59
+ pip install -e .
60
+ python -m qmemory --port 18800
61
+
62
+ # 方式二:Docker
63
+ docker run -d -p 18800:18800 qmemory:latest
64
+ ```
65
+
66
+ 验证 QMemory 运行:
67
+
68
+ ```bash
69
+ curl http://127.0.0.1:18800/health
70
+ # {"status":"ok","version":"..."}
71
+ ```
72
+
73
+ ### 4. 作为子进程运行
74
+
75
+ qlogicagent 不独立运行,需要由 openclaw Gateway 或其他 JSON-RPC 宿主 spawn:
76
+
77
+ ```bash
78
+ # Gateway 自动 spawn(标准方式)
79
+ cd ../openclaw && node scripts/run-node.mjs gateway --port 18789
80
+
81
+ # 手动测试(开发调试用)
82
+ node dist/cli/main.js
83
+ ```
84
+
85
+ 启动后 agent 等待 stdin 接收 JSON-RPC 请求。
86
+
87
+ ---
88
+
89
+ ## JSON-RPC 协议
90
+
91
+ ### 请求方法
92
+
93
+ | 方法 | 说明 | 参数 |
94
+ |------|------|------|
95
+ | `agent.hello` | 协议握手 | `{ protocolVersion, hostName, hostVersion }` |
96
+ | `agent.ping` | 心跳检测 | — |
97
+ | `agent.turn` | 执行一轮对话 | 见下方详细说明 |
98
+ | `agent.abort` | 中止当前 turn | `{ turnId }` |
99
+ | `session.list` | 列出历史会话 | `{ limit? }` |
100
+ | `session.resume` | 恢复会话 | `{ sessionId }` |
101
+ | `memory.dream` | 触发记忆整合 | — |
102
+ | `tool.approval.response` | 工具审批响应 | `{ approvalId, decision }` |
103
+
104
+ ### agent.turn 参数
105
+
106
+ ```json
107
+ {
108
+ "jsonrpc": "2.0",
109
+ "id": 1,
110
+ "method": "agent.turn",
111
+ "params": {
112
+ "turnId": "turn-001",
113
+ "sessionId": "sess-001",
114
+ "messages": [
115
+ { "role": "user", "content": "帮我分析 src/ 目录结构" }
116
+ ],
117
+ "tools": [],
118
+ "config": {
119
+ "provider": "deepseek",
120
+ "model": "deepseek-chat",
121
+ "apiKey": "sk-...",
122
+ "baseUrl": "https://api.deepseek.com",
123
+ "maxRounds": 25,
124
+ "temperature": 0.7,
125
+ "workdir": "/path/to/project",
126
+ "mcpServers": {
127
+ "filesystem": {
128
+ "command": "npx",
129
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
130
+ }
131
+ },
132
+ "skillPaths": ["/path/to/skills"],
133
+ "systemPrompt": "You are a helpful assistant."
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### 事件流(Agent → Host)
140
+
141
+ | 事件 | 说明 |
142
+ |------|------|
143
+ | `turn.start` | 轮次开始 |
144
+ | `turn.delta` | 文本流式增量 `{ text }` |
145
+ | `turn.end` | 轮次结束 `{ content, usage, model, provider }` |
146
+ | `turn.error` | 执行错误 `{ error, code }` |
147
+ | `turn.tool_call` | 工具调用 `{ callId, name }` |
148
+ | `turn.tool_result` | 工具结果 `{ callId, name, ok }` |
149
+ | `turn.tool_blocked` | 权限拦截 `{ callId, name, reason }` |
150
+ | `turn.skill_instruction` | 技能指令 `{ instruction }` |
151
+ | `turn.sidechain_started` | 子代理启动 `{ depth, role }` |
152
+ | `turn.sidechain_completed` | 子代理完成 `{ depth, toolCallCount }` |
153
+ | `turn.recovery` | 错误恢复 `{ action }` |
154
+ | `turn.plan_update` | 计划更新 `{ slug, content }` |
155
+ | `tool.approval.request` | 请求审批 `{ approvalId, toolName, arguments, message }` |
156
+
157
+ ---
158
+
159
+ ## 模块结构
160
+
161
+ ```
162
+ src/
163
+ ├── cli/ # 组合根:JSON-RPC 协议 + 全模块装配
164
+ │ ├── main.ts # 进程入口
165
+ │ ├── stdio-server.ts # StdioServer (JSON-RPC handler, 工具注册, 配置解析)
166
+ │ └── tool-bootstrap.ts# 工具注册初始化
167
+
168
+ ├── agent/ # Agent 核心:推理 + 工具循环
169
+ │ ├── agent.ts # Agent 类 (async generator run())
170
+ │ ├── tool-loop.ts # 工具调用循环
171
+ │ ├── types.ts # 核心类型 (ChatMessage, TurnEvent, ToolInvoker...)
172
+ │ └── constants.ts # 运行参数常量
173
+
174
+ ├── llm/ # LLM 通信层(完全隔离)
175
+ │ ├── transport.ts # LLMTransport 接口
176
+ │ ├── transports/ # OpenAI / Anthropic 实现
177
+ │ ├── provider-registry.ts # Provider 注册表
178
+ │ ├── model-catalog.ts # 模型能力目录
179
+ │ └── llm-client.ts # 一站式客户端工厂
180
+
181
+ ├── orchestration/ # 编排策略层(纯函数,零外部依赖)
182
+ │ ├── tool-loop-state.ts # 工具循环 FSM
183
+ │ ├── context-compression.ts # 14+ 压缩策略
184
+ │ ├── error-classification.ts # 错误分类
185
+ │ ├── retry-loop.ts # 重试策略
186
+ │ ├── agent-registry.ts # 内置代理类型
187
+ │ ├── fork-subagent.ts # 子代理 fork
188
+ │ ├── team-orchestration.ts # 多代理团队
189
+ │ └── ... # 23 个策略文件
190
+
191
+ ├── runtime/ # 运行时服务层
192
+ │ ├── session-state.ts # 会话成本追踪
193
+ │ ├── session-persistence.ts # JSONL 对话持久化
194
+ │ ├── session-memory.ts # 会话记忆提取
195
+ │ ├── hook-registry.ts # Hook 系统实现
196
+ │ ├── forked-agent.ts # 子代理执行
197
+ │ ├── dream-agent.ts # 记忆整合 (Dream)
198
+ │ ├── secure-storage.ts # 凭据安全存储
199
+ │ ├── token-budget.ts # Token 预算管理
200
+ │ ├── instruction-loader.ts # .instructions.md 加载
201
+ │ └── ... # 21 个运行时文件
202
+
203
+ ├── skills/ # 工具与技能层
204
+ │ ├── portable-tool.ts # PortableTool 契约接口
205
+ │ ├── tool-registry.ts # 工具注册表
206
+ │ ├── tools/ # 44 个内置工具
207
+ │ │ ├── shell/ # 命令执行子系统 (11 文件)
208
+ │ │ ├── exec-tool.ts # Shell 执行
209
+ │ │ ├── read-tool.ts # 文件读取
210
+ │ │ ├── write-tool.ts # 文件写入
211
+ │ │ ├── edit-tool.ts # 文件编辑
212
+ │ │ ├── search-tool.ts # 文件搜索
213
+ │ │ ├── web-search-tool.ts # Web 搜索
214
+ │ │ ├── agent-tool.ts # 子代理调用
215
+ │ │ ├── team-tool.ts # 团队管理
216
+ │ │ ├── mcp-tool.ts # MCP 服务器管理
217
+ │ │ └── ... # 其他工具
218
+ │ ├── mcp/ # MCP 桥接 (4 文件)
219
+ │ ├── permissions/ # 权限系统 (9 文件)
220
+ │ ├── plugins/ # 插件系统 (4 文件)
221
+ │ ├── memory-tool.ts # 记忆工具 (MD + QMemory)
222
+ │ ├── qmemory-adapter.ts # QMemory HTTP 适配器
223
+ │ └── skill-loader.ts # 技能发现/加载
224
+
225
+ ├── contracts/ # 纯类型契约(零依赖)
226
+ │ ├── hooks.ts # 30+ Hook 点定义
227
+ │ ├── planner.ts # 规划器类型
228
+ │ ├── todo.ts # 待办类型
229
+ │ └── skill-candidate.ts # 技能候选类型
230
+
231
+ └── config/ # 配置管理
232
+ └── config.ts # CLI 参数解析
233
+ ```
234
+
235
+ ---
236
+
237
+ ## QMemory 集成
238
+
239
+ qlogicagent 通过 HTTP 连接本地 QMemory 服务实现语义长期记忆。
240
+
241
+ ### 记忆层次
242
+
243
+ | 层 | 说明 | 存储 |
244
+ |----|------|------|
245
+ | **MD 记忆** | Agent 主动管理的笔记 (add/replace/remove) | `~/.openclaw/session-memory.md` |
246
+ | **QMemory** | 语义向量记忆,自动提取 + 语义召回 | QMemory 服务 (SQLite + embedding) |
247
+ | **会话转录** | JSONL 对话记录,用于会话恢复 | `~/.openclaw/sessions/<id>/` |
248
+
249
+ ### 启用步骤
250
+
251
+ 1. 启动 QMemory 服务(见上方"快速开始")
252
+ 2. 设置环境变量:`QMEMORY_BASE_URL=http://127.0.0.1:18800`
253
+ 3. Agent 自动:
254
+ - 在 `memory.before_recall` hook 预取相关记忆
255
+ - 通过 `memory` 工具的 `search` action 查询语义记忆
256
+ - 会话结束时自动提取关键信息写入 QMemory
257
+ - 达到阈值后触发 Dream 整合(4 阶段:Orient → Gather → Consolidate → Prune)
258
+
259
+ ### 不启用 QMemory
260
+
261
+ 如果不设置 `QMEMORY_BASE_URL`,Agent 仍可运行,但:
262
+ - ❌ 无语义记忆召回
263
+ - ❌ 无自动记忆提取
264
+ - ❌ 无 Dream 整合
265
+ - ✅ MD 本地记忆仍可用
266
+ - ✅ 会话转录仍正常
267
+
268
+ ---
269
+
270
+ ## 配置文件
271
+
272
+ ### 环境变量
273
+
274
+ | 变量 | 说明 | 默认值 |
275
+ |------|------|--------|
276
+ | `LOG_LEVEL` | 日志级别 (debug/info/warn/error) | `info` |
277
+ | `QMEMORY_BASE_URL` | QMemory 服务地址 | 不设则禁用 |
278
+ | `QMEMORY_API_KEY` | QMemory 认证 Key | — |
279
+ | `QLOGICAGENT_QMEMORY_BASE_URL` | QMemory 地址 (备选变量名) | — |
280
+
281
+ ### 用户级配置(`~/.openclaw/`)
282
+
283
+ | 路径 | 说明 |
284
+ |------|------|
285
+ | `~/.openclaw/settings.json` | 权限规则、行为模式 |
286
+ | `~/.openclaw/mcp.json` | 全局 MCP 服务器配置 |
287
+ | `~/.openclaw/plugins/` | 用户级插件目录 |
288
+ | `~/.openclaw/INSTRUCTIONS.md` | 用户级指令 |
289
+ | `~/.openclaw/session-memory.md` | MD 记忆文件 |
290
+ | `~/.openclaw/sessions/` | 会话持久化目录 |
291
+ | `~/.openclaw/.credentials.json` | 安全凭据存储 (0o600) |
292
+
293
+ ### 项目级配置
294
+
295
+ | 路径 | 说明 |
296
+ |------|------|
297
+ | `.openclaw/settings.json` | 项目权限规则 |
298
+ | `.openclaw/plugins/` | 项目级插件 |
299
+ | `.openclaw/INSTRUCTIONS.md` | 项目指令 |
300
+ | `INSTRUCTIONS.md` | 项目指令 (根目录) |
301
+ | `INSTRUCTIONS.local.md` | 本地私有指令 |
302
+
303
+ ---
304
+
305
+ ## MCP 配置
306
+
307
+ ### 全局 MCP(`~/.openclaw/mcp.json`)
308
+
309
+ ```json
310
+ {
311
+ "filesystem": {
312
+ "command": "npx",
313
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user"]
314
+ },
315
+ "github": {
316
+ "command": "npx",
317
+ "args": ["-y", "@modelcontextprotocol/server-github"],
318
+ "env": { "GITHUB_TOKEN": "ghp_..." }
319
+ }
320
+ }
321
+ ```
322
+
323
+ ### Per-Turn MCP(通过 `agent.turn` config)
324
+
325
+ ```json
326
+ {
327
+ "config": {
328
+ "mcpServers": {
329
+ "project-db": {
330
+ "type": "http",
331
+ "url": "http://localhost:3100/mcp"
332
+ }
333
+ }
334
+ }
335
+ }
336
+ ```
337
+
338
+ MCP 工具自动注入到 Agent 工具列表,前缀格式:`mcp__servername__toolname`。
339
+
340
+ ---
341
+
342
+ ## 权限配置
343
+
344
+ ### `~/.openclaw/settings.json`
345
+
346
+ ```json
347
+ {
348
+ "permissionMode": "default",
349
+ "defaultBehavior": "ask",
350
+ "permissionRules": [
351
+ {
352
+ "pattern": "read",
353
+ "behavior": "allow"
354
+ },
355
+ {
356
+ "pattern": "exec",
357
+ "behavior": "ask"
358
+ },
359
+ {
360
+ "pattern": "write:**/node_modules/**",
361
+ "behavior": "deny"
362
+ }
363
+ ]
364
+ }
365
+ ```
366
+
367
+ `behavior` 可选值:`allow`(自动允许)、`ask`(请求审批)、`deny`(拒绝)。
368
+
369
+ ---
370
+
371
+ ## 开发命令
372
+
373
+ ```bash
374
+ pnpm install # 安装依赖
375
+ pnpm build # esbuild 构建 (scripts/build.mjs)
376
+ pnpm dev # tsx watch 开发模式
377
+ pnpm test # vitest 运行测试
378
+ pnpm test:watch # vitest watch 模式
379
+ pnpm lint # oxlint 代码检查
380
+ pnpm start # 启动 CLI (node dist/cli/main.js)
381
+ ```
382
+
383
+ ## 测试
384
+
385
+ ```bash
386
+ pnpm test # 全部测试
387
+ pnpm test -- test/e2e # E2E 协议测试
388
+ pnpm test -- --coverage # 覆盖率
389
+ ```
390
+
391
+ - 单元测试:`test/` — LLM provider、orchestration、tool、permission
392
+ - E2E 测试:`test/e2e/` — spawn CLI 进程验证 JSON-RPC 协议
393
+
394
+ ## 发布
395
+
396
+ ```bash
397
+ # 版本已在 package.json 中更新后:
398
+ git tag v0.4.0
399
+ git push origin master
400
+ git push origin v0.4.0
401
+ # Gitee 流水线自动: build → test → npm publish → Gitee Release
402
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qlogicagent",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "XiaozhiClaw Agent CLI — subprocess architecture (JSON-RPC over stdio)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,4 +54,4 @@
54
54
  "typescript": "^5.9.0",
55
55
  "vitest": "^3.1.0"
56
56
  }
57
- }
57
+ }
package/dist/agent.js DELETED
@@ -1 +0,0 @@
1
- var et=Object.defineProperty;var tt=(e,t)=>()=>(e&&(t=e(e=0)),t);var nt=(e,t)=>{for(var n in t)et(e,n,{get:t[n],enumerable:!0})};var Ge={};nt(Ge,{resolveToolEligibility:()=>Jt});function Kt(e,t){if(Yt.some(n=>n.test(e)))return!0;if(t)for(let n of t)try{if(new RegExp(n,"i").test(e))return!0}catch{}return!1}function Vt(e,t){let n=e.function.name,r=e.meta,o=[];return t.blockedToolNames?.includes(n)?(o.push("policy_blocked"),{level:5,reasons:o}):r?.isReadOnly?(o.push("always_allowed"),{level:1,reasons:o}):r?.requiresApproval?(o.push("approval_required"),{level:4,reasons:o}):r?.isDangerous||Kt(n,t.dangerousPatterns)?(o.push("dangerous_tool"),{level:3,reasons:o}):{level:2,reasons:o}}function Xt(e){switch(e){case 1:return"eligible";case 2:return"eligible";case 3:return"dangerous-notify";case 4:return"approval-required";case 5:return"blocked-by-policy"}}function Jt(e,t={}){let n=new Map,r=[],o=[],c=[];for(let s of e){let i=s.function.name,{level:a,reasons:u}=Vt(s,t),d=Xt(a),h={toolName:i,status:d,permissionLevel:a,approvalRequired:a===4,reasonCodes:u};n.set(i,h),a===5?o.push(h):(r.push(s),a===4&&c.push(h))}return{eligibleTools:r,blockedTools:o,approvalRequiredTools:c,eligibilityByName:n}}var Yt,Ye=tt(()=>{"use strict";Yt=[/^(?:bash|shell|exec|terminal|run_command)$/i,/^(?:write_file|delete_file|move_file|create_directory)$/i,/^(?:git_push|git_reset|git_force)$/i]});function se(e){return{role:"assistant",content:"",tool_calls:e}}function G(e,t){return{role:"tool",tool_call_id:e,content:JSON.stringify(t.ok?{ok:!0,payload:t.payload}:{ok:!1,error:t.error??"Tool invoke failed"})}}var ot=/\b(?:daily|weekly|monthly)(?:\/(?:daily|weekly|monthly))* (?:usage )?limit(?:s)?(?: (?:exhausted|reached|exceeded))?\b/i,F={rateLimit:[/rate[_ ]limit|too many requests|429/,"model_cooldown","exceeded your current quota","resource has been exhausted","quota exceeded","resource_exhausted","usage limit",/\btpm\b/i,"tokens per minute","tokens per day"],overloaded:[/overloaded_error|"type"\s*:\s*"overloaded_error"/i,"overloaded",/service[_ ]unavailable.*(?:overload|capacity|high[_ ]demand)|(?:overload|capacity|high[_ ]demand).*service[_ ]unavailable/i,"high demand"],timeout:["timeout","timed out","service unavailable","deadline exceeded","context deadline exceeded","connection error","network error","network request failed","fetch failed","socket hang up",/\beconn(?:refused|reset|aborted)\b/i,/\benotfound\b/i,/\beai_again\b/i,/without sending (?:any )?chunks?/i,/\bstop reason:\s*(?:abort|error|network_error)\b/i,/\breason:\s*(?:abort|error|network_error)\b/i,/\bunhandled stop reason:\s*(?:abort|error|network_error)\b/i],billing:[/["']?(?:status|code)["']?\s*[:=]\s*402\b|\bhttp\s*402\b|\berror(?:\s+code)?\s*[:=]?\s*402\b|\b(?:got|returned|received)\s+(?:a\s+)?402\b|^\s*402\s+payment/i,"payment required","insufficient credits",/insufficient[_ ]quota/i,"credit balance","plans & billing","insufficient balance"],authPermanent:[/api[_ ]?key[_ ]?(?:revoked|invalid|deactivated|deleted)/i,"invalid_api_key","key has been disabled","key has been revoked","account has been deactivated",/could not (?:authenticate|validate).*(?:api[_ ]?key|credentials)/i,"permission_error","not allowed for this organization"],auth:[/invalid[_ ]?api[_ ]?key/,"incorrect api key","invalid token","authentication","re-authenticate","oauth token refresh failed","unauthorized","forbidden","access denied","insufficient permissions","insufficient permission",/missing scopes?:/i,"expired","token has expired",/\b401\b/,/\b403\b/,"no credentials found","no api key found"],format:["string should match pattern","tool_use.id","tool_use_id","messages.1.content.1.tool_use.id","invalid request format",/tool call id was.*must be/i]},rt=/^(?:error[:\s-]+)?billing(?:\s+error)?(?:[:\s-]+|$)|^(?:error[:\s-]+)?(?:credit balance|insufficient credits?|payment required|http\s*402\b)/i,it=/["']?(?:status|code)["']?\s*[:=]\s*402\b|\bhttp\s*402\b|\berror(?:\s+code)?\s*[:=]?\s*402\b|^\s*402\s+payment/i,st=512,at=/^(?:http\s*)?(\d{3})(?:\s+([\s\S]+))?$/i;var lt=new Set([500,502,503,504,521,522,523,524,529]),ct=["insufficient credits","insufficient quota","credit balance","insufficient balance","plans & billing","add more credits","top up"],ut=["upgrade your plan","upgrade plan","current plan","subscription"],dt=["daily","weekly","monthly"],pt=["try again","retry","temporary","cooldown"],mt=["usage limit","rate limit","organization usage"],ft=["organization","workspace"],gt=["billing period","exceeded","reached","exhausted"],yt=/["']?(?:status|code)["']?\s*[:=]\s*402\b|\bhttp\s*402\b|\berror(?:\s+code)?\s*[:=]?\s*402\b|\b(?:got|returned|received)\s+(?:a\s+)?402\b|^\s*402\s+payment required\b/i,ht=/^(?:error[:\s-]+)?(?:(?:http\s*)?402(?:\s+payment required)?|payment required)(?:[:\s-]+|$)/i;function O(e,t){if(!e)return!1;let n=e.toLowerCase();return t.some(r=>r instanceof RegExp?r.test(n):n.includes(r))}function Tt(e){return O(e,F.format)}function Ae(e){return O(e,F.rateLimit)}function le(e){return O(e,F.timeout)}function Ct(e){return ot.test(e)}function ae(e){let t=e.toLowerCase();return t?e.length>st?it.test(t):O(t,F.billing)?!0:rt.test(e)?t.includes("upgrade")||t.includes("credits")||t.includes("payment")||t.includes("plan"):!1:!1}function Me(e){return O(e,F.authPermanent)}function bt(e){return O(e,F.auth)}function Ee(e){return O(e,F.overloaded)}function L(e,t){return t.some(n=>e.includes(n))}function kt(e){return L(e,ct)||L(e,ut)&&e.includes("limit")||e.includes("billing hard limit")||e.includes("hard limit reached")||e.includes("maximum allowed")&&e.includes("limit")}function xt(e){let t=L(e,dt),n=e.includes("spend limit")||e.includes("spending limit"),r=L(e,ft);return L(e,pt)&&L(e,mt)||t&&(e.includes("usage limit")||n)||t&&e.includes("limit")&&e.includes("reset")||r&&e.includes("limit")&&(n||L(e,gt))}function Rt(e){return e.trim().toLowerCase().replace(ht,"").trim()}function _e(e){let t=Rt(e);return!t||kt(t)?"billing":Ae(t)||xt(t)?"rate_limit":"billing"}function vt(e){return yt.test(e)?_e(e):null}function we(e){let t=e.match(at);if(!t)return null;let n=Number(t[1]);return Number.isFinite(n)?{code:n,rest:(t[2]??"").trim()}:null}function St(e){if(!e)return!1;let t=e.toLowerCase();return t.includes('"type":"api_error"')&&t.includes("internal server error")}function Ie(e){let t=e.trim();if(!t)return!1;let n=we(t);return n?lt.has(n.code):!1}function Y(e,t){return typeof e!="number"||!Number.isFinite(e)?null:e===402?t?_e(t):"billing":e===429?"rate_limit":e===401||e===403?t&&Me(t)?"auth_permanent":"auth":e===408?"timeout":e===503?t&&Ee(t)?"overloaded":"timeout":e===502||e===504?"timeout":e===529?"overloaded":e===400?t&&ae(t)?"billing":"format":null}function Pe(e){if(!e)return!1;let t=e.toLowerCase();return!!(t.includes("unknown model")||t.includes("model not found")||t.includes("model_not_found")||t.includes("not_found_error")||t.includes("does not exist")&&t.includes("model")||t.includes("invalid model")&&!t.includes("invalid model reference")||/models\/[^\s]+ is not found/i.test(e)||/\b404\b/.test(e)&&/not[-_ ]?found/i.test(e))}function At(e){if(!e)return!1;let t=e.toLowerCase();return t.includes("session not found")||t.includes("session does not exist")||t.includes("session expired")||t.includes("session invalid")||t.includes("conversation not found")||t.includes("conversation does not exist")||t.includes("conversation expired")||t.includes("conversation invalid")||t.includes("no such session")||t.includes("invalid session")||t.includes("session id not found")||t.includes("conversation id not found")}function K(e){if(At(e))return"session_expired";if(Pe(e))return"model_not_found";let t=vt(e);return t||(Ct(e)?ae(e)?"billing":"rate_limit":Ae(e)?"rate_limit":Ee(e)?"overloaded":Ie(e)?we(e.trim())?.code===529?"overloaded":"timeout":St(e)?"timeout":Tt(e)?"format":ae(e)?"billing":le(e)?"timeout":Me(e)?"auth_permanent":bt(e)?"auth":null)}var Mt={timeout:"RETRYABLE_TRANSIENT",overloaded:"RETRYABLE_TRANSIENT",rate_limit:"RETRYABLE_DEGRADED",auth:"NON_RETRYABLE_AUTH",auth_permanent:"NON_RETRYABLE_AUTH",billing:"NON_RETRYABLE_QUOTA",format:"NON_RETRYABLE_CONTENT",model_not_found:"NON_RETRYABLE_CONTENT",session_expired:"NON_RETRYABLE_CONTENT",unknown:"RETRYABLE_TRANSIENT"},Et={RETRYABLE_TRANSIENT:{retryable:!0,maxRetries:3,baseDelayMs:1e3,backoffMultiplier:2,switchProvider:!0},RETRYABLE_DEGRADED:{retryable:!0,maxRetries:2,baseDelayMs:5e3,backoffMultiplier:3,switchProvider:!0},NON_RETRYABLE_AUTH:{retryable:!1,maxRetries:0,baseDelayMs:0,backoffMultiplier:1,switchProvider:!0},NON_RETRYABLE_CONTENT:{retryable:!1,maxRetries:0,baseDelayMs:0,backoffMultiplier:1,switchProvider:!1},NON_RETRYABLE_QUOTA:{retryable:!1,maxRetries:0,baseDelayMs:0,backoffMultiplier:1,switchProvider:!0},TOOL_EXECUTION_FAILED:{retryable:!0,maxRetries:1,baseDelayMs:500,backoffMultiplier:1,switchProvider:!1}};function j(e,t){let n=Y(e,t)??(t?K(t):null);return n?Mt[n]:typeof e=="number"&&e>=400&&e<500?"NON_RETRYABLE_CONTENT":"RETRYABLE_TRANSIENT"}function ce(e){return Et[e]}function Le(e){return!(!e.ok||e.existingSkillName||!e.multiStep||e.toolCallCount<3||e.distinctToolCount<2)}function Fe(e){return e.existingSkillName?e.feedback==="negative":!1}function V(e,t){return Fe(e)?{type:"skill.improve",skillName:e.existingSkillName,reason:"negative user feedback on existing skill execution"}:Le(e)?{type:"skill.create",suggestedName:t.suggestedName??`auto-skill-${t.tools.slice(0,3).join("-")}`,description:`Multi-step orchestration using ${t.tools.join(", ")}`,tools:t.tools,stepCount:e.toolCallCount}:null}function Oe(e){return e.function&&typeof e.function=="object"&&typeof e.function.name=="string"?e.function.name.trim():typeof e.name=="string"?e.name.trim():""}function _t(e){return e==="enabled-eligible"||e==="installed-awaiting-approval"}function wt(e){return new Map((e??[]).map(t=>[t.toolName,t]))}function Ne(e){if(!e.eligibility?.length)return[...e.tools];let t=wt(e.eligibility);return e.tools.filter(n=>{let r=Oe(n);if(!r)return!1;let o=t.get(r);return!o||_t(o.status)})}function De(e){let t=[],n=e.compatibility??{},r=e.toolChoice;if(e.thinkingEnabled&&n.requireAutoWhenThinking){let o=typeof r=="object"&&r&&!Array.isArray(r)?String(r.type??""):r;o&&o!=="auto"&&o!=="none"&&(t.push("tool_choice downgraded to auto because thinking mode requires auto/none compatibility."),r="auto")}if(r==="required"&&n.allowRequiredToolChoice===!1){let o=n.requiredFallback??"auto";t.push(`tool_choice=required is not supported by this provider; downgraded to ${o}.`),r=o}if(r&&typeof r=="object"&&!Array.isArray(r)&&r.type==="function"&&n.allowNamedToolChoice===!1){let o=n.namedFallback??"required";t.push(`named tool_choice is not supported by this provider; downgraded to ${o}.`),r=o}return{normalizedToolChoice:r,warnings:t}}function ue(e){let t=De({toolChoice:e.toolChoice,thinkingEnabled:e.thinkingEnabled,compatibility:e.compatibility}),n=t.normalizedToolChoice,r=[...t.warnings],o=Ne({tools:e.tools,eligibility:e.eligibility});if(!n||n==="auto")return{tools:o,normalizedToolChoice:n,warnings:r};if(n==="none")return{tools:[],normalizedToolChoice:"none",warnings:r};if(n==="required"){if(o.length===0)throw new Error("tool_choice=required but no tools were provided");return{tools:o,normalizedToolChoice:"required",extraSystemPrompt:"You must call one of the available tools before responding.",warnings:r}}if(typeof n=="object"&&!Array.isArray(n)&&n.type==="function"){let c=n.function??void 0,s=typeof c?.name=="string"?c.name.trim():"";if(!s)throw new Error("tool_choice.function.name is required");let i=o.filter(a=>Oe(a)===s);if(i.length===0)throw new Error(`tool_choice requested unknown tool: ${s}`);return{tools:i,normalizedToolChoice:{type:"function",function:{name:s}},extraSystemPrompt:`You must call the ${s} tool before responding.`,warnings:r}}return{tools:o,normalizedToolChoice:n,warnings:r}}var It=["stop","aborted","timeout","cancelled","interrupted","error"],Pt=["tool_calls","toolCalls","function_call","functionCall","raw_tool_calls","rawToolCalls"];function Be(e){return e==null?[]:typeof e=="string"?e.length>0?[{type:"text",text:e}]:[]:Array.isArray(e)?e:[{type:"text",text:String(e)}]}function Lt(e,t){return{...e,content:[...Be(e.content),...Be(t.content)]}}function Ft(e){if(!e||typeof e!="object")return!1;if(e.function&&typeof e.function=="object"){let t=e.function.name;if(typeof t=="string"&&t.length>0)return!0}return typeof e.name=="string"&&e.name.length>0}function Ot(e){return new Set((e??It).map(t=>t.trim().toLowerCase()))}function Nt(e,t){return e?Ot(t).has(e.trim().toLowerCase()):!1}function X(e){if(!Array.isArray(e)||e.length===0)return[...e];let t=e.map(i=>{if(i.role==="assistant"&&Array.isArray(i.tool_calls)){let a=i.tool_calls.filter(u=>Ft(u));return{...i,...a.length>0?{tool_calls:a}:{tool_calls:void 0}}}return{...i}}),n=new Set;for(let i of t)if(!(i.role!=="assistant"||!Array.isArray(i.tool_calls)))for(let a of i.tool_calls)typeof a.id=="string"&&a.id&&n.add(a.id);let r=t.filter(i=>i.role!=="tool"?!0:!!(i.tool_call_id&&n.has(i.tool_call_id))),o=new Set;for(let i of r)i.role==="tool"&&typeof i.tool_call_id=="string"&&i.tool_call_id&&o.add(i.tool_call_id);let c=[];for(let i of r){if(i.role==="assistant"&&Array.isArray(i.tool_calls)&&i.tool_calls.length>0){let a=i.tool_calls.filter(u=>typeof u.id=="string"&&o.has(u.id));if(a.length===0){let{tool_calls:u,...d}=i;d.content!=null&&d.content!==""&&c.push(d);continue}if(a.length<i.tool_calls.length){c.push({...i,tool_calls:a});continue}}c.push(i)}let s=[];for(let i of c){let a=s.length>0?s[s.length-1]:void 0;if(i.role==="user"&&a?.role==="user"){s[s.length-1]=Lt(a,i);continue}s.push(i)}return s}function J(e,t){return Nt(t?.stopReason,t?.forcedStopReasons)?e.map(n=>{if(n.role!=="assistant")return{...n};let r={...n};for(let o of Pt)delete r[o];return r}):[...e]}function Q(e,t){let n=t?.placeholderToolResult??JSON.stringify({ok:!1,error:"Tool loop interrupted before the tool result was replayed."}),r=new Set;for(let c of e)c.role==="tool"&&typeof c.tool_call_id=="string"&&c.tool_call_id&&r.add(c.tool_call_id);let o=[];for(let c of e)if(o.push({...c}),!(c.role!=="assistant"||!Array.isArray(c.tool_calls)||c.tool_calls.length===0))for(let s of c.tool_calls)typeof s.id!="string"||!s.id||r.has(s.id)||(r.add(s.id),o.push({role:"tool",tool_call_id:s.id,content:n}));return o}function de(e,t){let n=X(e),r=J(n,t);return Q(r,t)}function $e(e){if(e!==void 0)return typeof e=="boolean"?{kind:"enabled",enabled:e}:e===null?{kind:"suppressed"}:{kind:"invalid",summary:typeof e=="string"?e:typeof e}}function pe(e){let t=[],n=$e(e.requestedPreference),r=new Map((e.toolCapabilities??[]).map(a=>[a.name,a])),o=e.providerSupportsParallel!==!1;n?.kind==="enabled"?o=n.enabled&&e.providerSupportsParallel!==!1:n?.kind==="suppressed"?o=!1:n?.kind==="invalid"&&t.push(`ignoring invalid parallel_tool_calls param: ${n.summary}`),o&&e.providerSupportsParallel===!1&&t.push("provider does not support parallel tool calls; downgraded to serial execution.");let c=[],s=[],i=()=>{s.length!==0&&(c.push({mode:"parallel",calls:s}),s=[])};for(let a of e.toolCalls){let u=r.get(a.function.name);if(!o||u?.requiresApproval===!0||u?.serialOnly===!0||u?.parallelSafe===!1||u?.approvalMode==="user-confirm"){i(),c.push({mode:"serial",calls:[a]});continue}s.push(a)}return i(),{enabled:o,mode:c.some(a=>a.mode==="parallel"&&a.calls.length>1)?"parallel":"serial",batches:c,warnings:t}}function Bt(e){let t=new Set,n=new Set;for(let r of e){if(r.role==="assistant"&&Array.isArray(r.tool_calls))for(let o of r.tool_calls)typeof o.id=="string"&&o.id&&t.add(o.id);r.role==="tool"&&typeof r.tool_call_id=="string"&&r.tool_call_id&&n.add(r.tool_call_id)}return[...t].filter(r=>!n.has(r))}function $t(e){let t=new Set;for(let n of e)n.role==="tool"&&typeof n.tool_call_id=="string"&&n.tool_call_id&&t.add(n.tool_call_id);return[...t]}function je(e){return{round:e.round??0,maxRounds:e.maxRounds,pendingToolCallIds:[...e.pendingToolCallIds??[]],completedToolCallIds:[...e.completedToolCallIds??[]],lastStopReason:e.lastStopReason,replayMessages:[...e.replayMessages??[]]}}function me(e,t){return{round:e.round+1,maxRounds:e.maxRounds,pendingToolCallIds:[...t.pendingToolCallIds??e.pendingToolCallIds],completedToolCallIds:[...t.completedToolCallIds??e.completedToolCallIds],lastStopReason:t.lastStopReason??e.lastStopReason,replayMessages:[...t.replayMessages??e.replayMessages]}}function Z(e,t){return{round:e.round,maxRounds:e.maxRounds,pendingToolCallIds:[],completedToolCallIds:[...t.completedToolCallIds??e.completedToolCallIds],lastStopReason:t.lastStopReason??e.lastStopReason,replayMessages:[...t.replayMessages??e.replayMessages]}}function ee(e){let t=[],n=X(e.replayMessages);n.length!==e.replayMessages.length&&t.push({kind:"drop-orphan-tool-result",detail:"Removed orphan tool results or invalid assistant tool calls."});let r=J(n,e.options);r.some((c,s)=>c!==n[s])&&t.push({kind:"strip-forced-stop-tool-metadata",detail:"Removed assistant tool-call metadata after forced stop."});let o=Q(r,e.options);return o.length>r.length&&t.push({kind:"inject-placeholder-tool-result",detail:"Injected placeholder tool result for pending tool calls."}),{state:je({maxRounds:e.maxRounds,round:e.round,lastStopReason:e.lastStopReason,replayMessages:o,pendingToolCallIds:Bt(o),completedToolCallIds:$t(o)}),recoveryActions:t}}import{MEMORY_OBSERVATION_HOOK_VALUES as jt,MEMORY_OBSERVATION_SOURCE_VALUES as zt,MEMORY_WRITE_ACCESS_VALUES as Ht}from"qlogicagent-runtime-contracts";function fe(e){return{promptTokens:0,hasAttemptedReactiveCompact:!1,currentMaxOutputTokens:e.maxOutputTokens,consecutiveTruncations:0,aborted:e.abortSignal?.aborted??!1}}function ge(e,t){if(e.aborted)return{level:"blocking",usagePercent:100,reason:"budget_exhausted"};let n=t.contextWindowTokens-t.responseBufferTokens-e.currentMaxOutputTokens,r=n>0?e.promptTokens/n*100:100;return e.promptTokens>=n?e.hasAttemptedReactiveCompact||!t.reactiveCompactEnabled?{level:"blocking",usagePercent:r,reason:"prompt_too_long"}:{level:"blocking",usagePercent:r,reason:"prompt_too_long"}:r>=85?{level:"warning",usagePercent:r,remainingTokens:n-e.promptTokens}:{level:"ok"}}function ye(e,t,n){let r=e.message?.toLowerCase()??"",o=e.status??0;return o===413||r.includes("prompt_too_long")||r.includes("context_length_exceeded")?!t.hasAttemptedReactiveCompact&&n.reactiveCompactEnabled?{action:"reactive_compact"}:{action:"abort",reason:"prompt_too_long_unrecoverable"}:o>=500&&o<600?{action:"retry",reason:`server_error_${o}`}:o===429?{action:"retry",reason:"rate_limited"}:{action:"abort",reason:r||"unknown_error"}}function he(e,t,n){if(!t.outputEscalationEnabled)return{shouldEscalate:!1,newMax:e.currentMaxOutputTokens};if(e.consecutiveTruncations>=3)return{shouldEscalate:!1,newMax:e.currentMaxOutputTokens};let r=Math.min(e.currentMaxOutputTokens*2,n);return r<=e.currentMaxOutputTokens?{shouldEscalate:!1,newMax:e.currentMaxOutputTokens}:{shouldEscalate:!0,newMax:r}}function Te(e,t){return e.aborted?!0:t.abortSignal?.aborted?(e.aborted=!0,!0):!1}var ze={maxConsecutiveFailures:3,minMessagesAfterCompact:4,targetUsagePercent:50};function Ce(){return{consecutiveFailures:0,attemptedThisTurn:!1,lastCompactAt:null,toolsAtLastCompact:[]}}function te(e,t=ze){return!(e.attemptedThisTurn||e.consecutiveFailures>=t.maxConsecutiveFailures)}var Ut={research:{type:"research",label:"Research",description:"Independent retrieval or evidence gathering branches.",mergePolicy:"summary-only",budgetTier:"medium",budgetWeight:2,preferFullContext:!1,preserveConversationHistory:!0,toolAccessMode:"read-only"},planner:{type:"planner",label:"Planner",description:"Planning and tool orchestration branches that may return structured context.",mergePolicy:"append-messages",budgetTier:"high",budgetWeight:3,preferFullContext:!0,preserveConversationHistory:!0,toolAccessMode:"none"},"plan-repair":{type:"plan-repair",label:"Plan Repair",description:"Failure repair or fallback branches that re-plan tool choice, ordering, or approval strategy.",mergePolicy:"replace-context",budgetTier:"medium",budgetWeight:2,preferFullContext:!0,preserveConversationHistory:!1,toolAccessMode:"none"},"code-repair":{type:"code-repair",label:"Code Repair",description:"Recovery branches focused on file edits, patch validation, or workspace mutation repair.",mergePolicy:"replace-context",budgetTier:"high",budgetWeight:3,preferFullContext:!0,preserveConversationHistory:!1,toolAccessMode:"full"},"media-prep":{type:"media-prep",label:"Media Prep",description:"Preparation branches for media generation, normalization, or staging.",mergePolicy:"summary-only",budgetTier:"low",budgetWeight:1,preferFullContext:!1,preserveConversationHistory:!1,toolAccessMode:"read-only"}},be=["gateway","agents_list","whatsapp_login","session_status","cron","memory_query","sessions_send"],qt=["sessions_list","sessions_history","sessions_spawn"],He=["file_edit","create_file","replace_string_in_file","multi_replace_string_in_file","create_directory","run_in_terminal","run_command","delete_file","rename_file","move_file","git_commit","git_push","manage_todo_list"];function Wt(e={}){let t=Math.max(1,Math.floor(e.maxDepth??2)),n=typeof e.depth=="number"&&Number.isFinite(e.depth)?Math.max(0,Math.floor(e.depth)):t,r=e.role?e.role==="leaf":n>=t,o=r?[...be,...qt]:[...be],c=new Set(o),s=Array.isArray(e.toolNames)?e.toolNames:e.toolNames?Array.from(e.toolNames):[],i=Array.from(new Set(s.map(a=>a.trim()).filter(Boolean))).map(a=>{let u=c.has(a);return{toolName:a,decision:u?"denied":"allowed",inheritance:u?"shrink":"inherit",reason:u?be.includes(a)?"always-deny":"leaf-deny":"default-allow"}});return{deniedTools:o,canSpawnChildren:!r,defaultDecision:"allowed",defaultInheritance:"inherit",tools:i}}function Ue(e){let t=Gt(e.type),n=Wt({depth:e.depth,maxDepth:e.maxDepth,role:e.role,toolNames:e.toolNames});if(t.toolAccessMode==="full")return n;if(t.toolAccessMode==="none"){let c=Array.isArray(e.toolNames)?e.toolNames:e.toolNames?Array.from(e.toolNames):[],s=[...new Set(c.map(i=>i.trim()).filter(Boolean))];return{deniedTools:s,canSpawnChildren:!1,defaultDecision:"allowed",defaultInheritance:"inherit",tools:s.map(i=>({toolName:i,decision:"denied",inheritance:"shrink",reason:"always-deny"}))}}let r=new Set(He),o=[...new Set([...n.deniedTools,...He])];return{...n,deniedTools:o,tools:n.tools.map(c=>c.decision==="denied"?c:r.has(c.toolName)?{...c,decision:"denied",inheritance:"shrink",reason:"always-deny"}:c)}}function qe(e,t){let n=new Set(t.deniedTools),r=new Map(t.tools.map(s=>[s.toolName,s])),o=[],c=[];for(let s of e){let i=s.function.name,a=r.get(i);a?.decision==="denied"?c.push({toolCall:s,reason:`tool "${i}" denied by sidechain policy: ${a.reason}`}):n.has(i)?c.push({toolCall:s,reason:`tool "${i}" is in sidechain deny list`}):o.push(s)}return{allowed:o,denied:c}}function Gt(e){return Ut[e]}function We(e,t){let n=e.get(t.index);n||(n={id:"",name:"",arguments:""},e.set(t.index,n)),t.id&&(n.id=t.id),t.name&&(n.name+=t.name),n.arguments+=t.arguments}var Ve=100,Qt=Math.min(Math.max(1,Number(process.env.TOOL_LOOP_DEFAULT_BUDGET)||25),Ve),Zt=3,z=2;function en(e){return typeof e=="number"&&Number.isFinite(e)&&e>=1?Math.min(Math.round(e),Ve):Qt}async function*Xe(e,t,n,r){let{turnId:o,sessionId:c,messages:s,tools:i,model:a,apiKey:u,temperature:d,hooks:h,signal:f}=e,m={sessionId:c,turnId:o},{resolveToolEligibility:g}=await Promise.resolve().then(()=>(Ye(),Ge)),I=g(i,e.toolEligibilityContext),E=I.eligibleTools;for(let S of I.blockedTools)yield{type:"tool_blocked",turnId:o,callId:"",name:S.toolName,reason:"blocked-by-policy"};if(!E.length){yield*nn(o,a,s,u,d,f,t,r);return}let _=en(e.maxRounds),v=[...s],k=ee({maxRounds:_,replayMessages:v}).state,$={contextWindowTokens:e.contextWindowTokens??128e3,responseBufferTokens:13e3,maxOutputTokens:e.maxOutputTokens??16384,abortSignal:f,reactiveCompactEnabled:!0,outputEscalationEnabled:!0},C=fe($),H=Ce(),N=!1,ne=null,w=(e.parentDepth??0)+1,Ze,D="",U=0,P={prompt:0,completion:0},B=new Set,x=0;for(let S=0;S<_;S++){if(Te(C,$)){r.info(`turn aborted by guard at round ${S}`);break}let R=ge(C,$);if(R.level==="blocking"){R.reason==="prompt_too_long"&&te(H)&&(H.attemptedThisTurn=!0,C.hasAttemptedReactiveCompact=!0,r.info(`token budget blocking (${R.reason}), reactive compact needed`)),r.info(`token budget blocking (${R.reason}), breaking tool loop`);break}R.level==="warning"&&r.info(`token budget warning: ${R.usagePercent}% used, ${R.remainingTokens} remaining`);let q=ue({tools:E,toolChoice:e.toolChoice??"auto"}),W=ee({maxRounds:_,replayMessages:v,lastStopReason:k.lastStopReason,options:{stopReason:k.lastStopReason}}),ke=q.extraSystemPrompt?[{role:"system",content:q.extraSystemPrompt},...W.state.replayMessages]:W.state.replayMessages;W.recoveryActions.length>0&&r.debug(`tool loop recovery: ${W.recoveryActions.map(l=>l.detail??l.kind).join("; ")}`),r.debug(`round ${S+1}/${_}, messages: ${ke.length}`),h?.invoke("turn.before_inference",{...m,model:a}).catch(()=>{});let oe=!1,xe=[],Re=new Map,re="stop",A,b=null;try{for await(let l of t.stream({model:a,messages:ke,tools:q.tools,toolChoice:q.normalizedToolChoice??"auto",temperature:d,maxTokens:C.currentMaxOutputTokens||void 0},u,f))switch(l.type){case"delta":xe.push(l.text),oe||(yield{type:"delta",turnId:o,text:l.text});break;case"tool_call_delta":oe=!0,We(Re,l);break;case"reasoning_delta":break;case"usage":A={prompt:l.promptTokens,completion:l.completionTokens,reasoning:l.reasoningTokens};break;case"done":re=l.finishReason;break}oe||h?.invoke("turn.after_inference",{...m,model:a}).catch(()=>{})}catch(l){let p=l instanceof Error?l.message:String(l);b={status:typeof l?.status=="number"?l.status:void 0,message:p}}if(b&&h?.invoke("turn.after_inference",{...m,model:a,response:{error:b.message}}).catch(()=>{}),b){let l=ye({status:b.status??500,message:b.message},C,$);if(l.action==="reactive_compact"&&te(H)&&(H.attemptedThisTurn=!0,C.hasAttemptedReactiveCompact=!0,yield{type:"recovery",turnId:o,action:"reactive_compact",detail:`API ${b.status??500}: ${b.message}`}),l.action==="retry"){yield{type:"recovery",turnId:o,action:"retry",detail:l.reason};continue}let p=j(b.status,b.message);yield{type:"error",turnId:o,error:b.message,code:p};return}if(A&&(P.prompt+=A.prompt,P.completion+=A.completion,A.reasoning&&(P.reasoning=(P.reasoning??0)+A.reasoning)),A?.prompt&&(C.promptTokens=A.prompt),re==="length"||re==="max_tokens"){C.consecutiveTruncations+=1;let l=e.modelMaxOutputTokens??65536,p=he(C,$,l);p.shouldEscalate&&(C.currentMaxOutputTokens=p.newMax,yield{type:"recovery",turnId:o,action:"output_escalation",detail:`${p.newMax} tokens`})}else C.consecutiveTruncations=0;Ze=A,D=xe.join("");let M=[...Re.values()].map(l=>({id:l.id||`tc_${o}_${S}_${Math.random().toString(36).slice(2,8)}`,type:"function",function:{name:l.name,arguments:l.arguments}}));if(ne&&M.length>0){let l=qe(M,ne);l.denied.length>0&&r.info(`sidechain policy denied: ${l.denied.map(p=>`${p.toolCall.function.name} (${p.reason})`).join(", ")}`),M=l.allowed}if(M.length===0){if(k=Z(k,{replayMessages:v,lastStopReason:"completed"}),N&&(yield{type:"sidechain_completed",turnId:o,depth:w,toolCallCount:x}),x>0){let l={ok:!0,toolCallCount:x,distinctToolCount:B.size,multiStep:x>=2,hasSidechain:N,feedback:null,existingSkillName:null},p=V(l,{tools:[...B]});p&&(yield{type:"skill_instruction",turnId:o,instruction:p})}yield{type:"end",turnId:o,content:D,usage:P,model:a};return}for(let l of M)yield{type:"tool_call",turnId:o,callId:l.id,name:l.function.name,arguments:l.function.arguments};if(v.push(se(M)),!N){if(w>z){yield{type:"error",turnId:o,error:`sidechain depth ${w} exceeds max ${z}`,code:"SIDECHAIN_DEPTH_LIMIT"};return}N=!0;let l=w>=z?"leaf":"orchestrator";yield{type:"sidechain_started",turnId:o,depth:w,role:l},ne=Ue({type:"planner",depth:w,maxDepth:z,role:w>=z?"leaf":"orchestrator",toolNames:i.map(p=>p.function.name)})}k=me(k,{replayMessages:v,pendingToolCallIds:M.map(l=>l.id),completedToolCallIds:k.completedToolCallIds,lastStopReason:"tool_calls"});let ve=pe({requestedPreference:void 0,providerSupportsParallel:!0,toolCalls:M,toolCapabilities:E.map(l=>({name:l.function.name,requiresApproval:l.meta?.requiresApproval??!1,approvalMode:l.meta?.requiresApproval?"user-confirm":"pre-authorized",parallelSafe:l.meta?.parallelSafe??!0,serialOnly:l.meta?.serialOnly??!1}))}),ie=[];try{for(let l of ve.batches)if(l.mode==="parallel"){let p={hooks:h,sessionId:c},y=await Promise.all(l.calls.map(T=>Ke(T,o,n,f,r,p)));for(let T of y)T.blocked&&(yield{type:"tool_blocked",turnId:o,callId:T.callId,name:T.toolName,reason:T.blockReason??"blocked"}),v.push(T.message),ie.push(T.callId),B.add(T.toolName),x++,yield{type:"tool_result",turnId:o,callId:T.callId,name:T.toolName,ok:T.ok,error:T.error}}else for(let p of l.calls){if(f?.aborted){yield{type:"error",turnId:o,error:"Turn aborted",code:"ABORTED"};return}let y=await Ke(p,o,n,f,r,{hooks:h,sessionId:c});y.blocked&&(yield{type:"tool_blocked",turnId:o,callId:y.callId,name:y.toolName,reason:y.blockReason??"blocked"}),v.push(y.message),ie.push(y.callId),B.add(y.toolName),x++,yield{type:"tool_result",turnId:o,callId:y.callId,name:y.toolName,ok:y.ok,error:y.error}}}catch(l){let p=l instanceof Error?l.message:String(l);yield{type:"error",turnId:o,error:p,code:"TOOL_EXECUTION_ERROR"};return}k=Z(k,{replayMessages:v,completedToolCallIds:[...k.completedToolCallIds,...ie],lastStopReason:ve.mode});let Se=v.slice(-M.length);if(Se.length>0&&Se.every(l=>{let p=l?.content;if(typeof p!="string")return!1;try{return JSON.parse(p)?.ok===!1}catch{return!1}})){if(U+=1,U>=Zt&&D){r.info(`early exit: ${U} consecutive failed rounds, returning partial response`),yield{type:"end",turnId:o,content:D,usage:P};return}}else U=0}if(D){if(r.info(`tool loop budget exhausted (${_} rounds), returning partial response`),N&&(yield{type:"sidechain_completed",turnId:o,depth:w,toolCallCount:x}),x>0){let S={ok:!0,toolCallCount:x,distinctToolCount:B.size,multiStep:x>=2,hasSidechain:N,feedback:null,existingSkillName:null},R=V(S,{tools:[...B]});R&&(yield{type:"skill_instruction",turnId:o,instruction:R})}yield{type:"end",turnId:o,content:D,usage:P,model:a};return}yield{type:"error",turnId:o,error:`Tool loop exceeded budget (${_} rounds)`,code:"TOOL_LOOP_LIMIT"}}async function Ke(e,t,n,r,o,c){let s=e.function.name;o.debug(`tool_call: ${s}`);let i=c?.hooks,a=e.function.arguments;if(i)try{let f=await i.invoke("tool.before_invoke",{sessionId:c?.sessionId??"",turnId:t,callId:e.id,toolName:s,arguments:tn(e.function.arguments)});if(f.action==="abort"){let m=f.reason??"blocked by policy";o.info(`tool ${s} blocked: ${m}`),i.invoke("approval.requested",{sessionId:c?.sessionId??"",turnId:t,approvalId:e.id,callId:e.id,toolName:s}).catch(()=>{});let g=G(e.id,{ok:!1,error:m});return{callId:e.id,toolName:s,ok:!1,error:m,blocked:!0,blockReason:m,message:g}}f.action==="continue"&&f.context?.arguments&&(a=JSON.stringify(f.context.arguments),o.debug(`tool ${s}: input mutated by hook`))}catch{}let u=await n.invoke(t,s,a,r),d=!u.error,h=G(e.id,{ok:d,payload:u.result,error:u.error});return i?.invoke(d?"tool.after_invoke":"tool.invoke_failed",{sessionId:c?.sessionId??"",turnId:t,callId:e.id,toolName:s,ok:d,...u.error?{error:u.error}:{}}).catch(()=>{}),{callId:e.id,toolName:s,ok:d,error:u.error,message:h}}function tn(e){try{return JSON.parse(e)}catch{return}}async function*nn(e,t,n,r,o,c,s,i){let a=[],u;i.debug(`single LLM round, messages: ${n.length}`);for await(let d of s.stream({model:t,messages:n,temperature:o},r,c))switch(d.type){case"delta":a.push(d.text),yield{type:"delta",turnId:e,text:d.text};break;case"usage":u={prompt:d.promptTokens,completion:d.completionTokens,reasoning:d.reasoningTokens};break;case"done":break}yield{type:"end",turnId:e,content:a.join(""),usage:u??{prompt:0,completion:0},model:t}}var Je=100,Qe=class{transport;apiKey;toolInvoker;log;hooks;maxRounds;constructor(t){this.transport=t.llmTransport,this.apiKey=t.apiKey,this.toolInvoker=t.toolInvoker,this.log=t.log,this.hooks=t.hooks,this.maxRounds=Math.min(t.maxRounds??25,Je)}async*run(t,n){let{turnId:r,messages:o,tools:c,systemPrompt:s,config:i}=t,a={sessionId:t.sessionId,turnId:r};yield{type:"start",turnId:r},this.hooks?.invoke("turn.submitted",{...a,prompt:o[o.length-1]?.content??void 0}).catch(()=>{});let u=de(o),d=[];s&&d.push({role:"system",content:s}),d.push(...u);let h=i?.model??"",f=Math.min(i?.maxRounds??this.maxRounds,Je);try{let m={turnId:r,sessionId:t.sessionId,messages:d,tools:c,model:h,apiKey:i?.apiKey??this.apiKey,temperature:i?.temperature,maxRounds:f,contextWindowTokens:i?.contextWindowTokens,maxOutputTokens:i?.maxOutputTokens,modelMaxOutputTokens:i?.modelMaxOutputTokens,toolChoice:i?.toolChoice,parentDepth:i?.parentDepth,hooks:this.hooks,signal:n},g;for await(let I of Xe(m,this.transport,this.toolInvoker,this.log))g=I,yield I;g?.type==="end"?this.hooks?.invoke("turn.completed",{...a}).catch(()=>{}):g?.type==="error"&&this.hooks?.invoke("turn.failed",{...a,code:g.code,error:g.error}).catch(()=>{})}catch(m){if(n?.aborted)this.hooks?.invoke("turn.failed",{...a,code:"ABORTED",error:"Turn aborted"}).catch(()=>{}),yield{type:"error",turnId:r,error:"Turn aborted",code:"ABORTED"};else{let g=m instanceof Error?m.message:String(m),I=typeof m?.status=="number"?m.status:void 0,E=j(I,g),_=ce(E);this.log.error(`turn ${r} error [${E}, retryable=${_.retryable}]: ${g}`),this.hooks?.invoke("turn.failed",{...a,code:E,error:g}).catch(()=>{}),yield{type:"error",turnId:r,error:g,code:E}}}}};export{Qe as Agent};