evolcore 0.0.38 → 0.0.40
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/CHANGELOG.md +64 -0
- package/README.md +31 -20
- package/bin/ec-safe-output.js +14 -2
- package/dist/agents/baseagent-availability.js +1 -0
- package/dist/agents/claude-runner.js +32 -46
- package/dist/agents/codex-runner.js +54 -30
- package/dist/agents/ecagent-runner.js +18 -7
- package/dist/agents/gemini-runner.js +1 -2
- package/dist/agents/runner-types.js +28 -6
- package/dist/aun/aid/agentmd.js +21 -0
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +23 -98
- package/dist/aun/aid/ownership-operation.js +35 -12
- package/dist/aun/group/invite-service.js +6 -0
- package/dist/aun/msg/p2p.js +8 -12
- package/dist/channels/aun.js +66 -48
- package/dist/channels/dingtalk.js +8 -2
- package/dist/channels/feishu.js +6 -1
- package/dist/channels/wecom.js +7 -3
- package/dist/cli/agent-create.js +30 -6
- package/dist/cli/agent.js +30 -37
- package/dist/cli/aid-bind-command.js +16 -2
- package/dist/cli/aun-commands.js +31 -23
- package/dist/cli/config.js +15 -18
- package/dist/cli/contact.js +9 -12
- package/dist/cli/ctl-command.js +3 -3
- package/dist/cli/daemon-commands.js +242 -17
- package/dist/cli/diag.js +7 -3
- package/dist/cli/fs-command.js +5 -4
- package/dist/cli/handoff-command.js +11 -11
- package/dist/cli/help.js +18 -0
- package/dist/cli/index.js +25 -13
- package/dist/cli/init-channel.js +2 -1
- package/dist/cli/memory-command.js +47 -27
- package/dist/cli/model.js +5 -4
- package/dist/cli/queue-command.js +3 -3
- package/dist/cli/response.js +5 -4
- package/dist/cli/restart-monitor.js +35 -13
- package/dist/cli/skill-command.js +13 -13
- package/dist/cli/task-command.js +3 -3
- package/dist/cli/task-context.js +1 -0
- package/dist/cli/thread-command.js +5 -3
- package/dist/cli/trigger-command.js +3 -3
- package/dist/cli/version.js +6 -2
- package/dist/config/config-field-policy.js +1 -0
- package/dist/contracts/ecweb-service-protocol.js +44 -0
- package/dist/core/agent-runtime.js +6 -0
- package/dist/core/auth/agent-delegation.js +7 -4
- package/dist/core/auth/auth-gateway.js +3 -1
- package/dist/core/auth/authorization-audit.js +5 -1
- package/dist/core/auth/debug-context.js +5 -1
- package/dist/core/auth/operation-catalog.js +17 -1
- package/dist/core/bootstrap-service.js +20 -12
- package/dist/core/command/cli-intent-parser.js +5 -2
- package/dist/core/command/command-handler.js +20 -5
- package/dist/core/command/menu-catalog.js +48 -12
- package/dist/core/command/menu-handler.js +74 -6
- package/dist/core/command/menu-protocol.js +20 -1
- package/dist/core/command/slash-gate.js +17 -3
- package/dist/core/command/slash-handler.js +90 -7
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/interaction-router.js +2 -1
- package/dist/core/ipc-registration.js +26 -7
- package/dist/core/memory/batch-meta.js +2 -0
- package/dist/core/memory/consolidation-context.js +42 -51
- package/dist/core/memory/consolidation-ops-records.js +39 -7
- package/dist/core/memory/consolidation-trigger.js +71 -64
- package/dist/core/memory/consolidation.js +226 -20
- package/dist/core/memory/memory-writer.js +160 -34
- package/dist/core/memory/relation-idle-monitor.js +287 -0
- package/dist/core/memory/relation-sweep.js +206 -0
- package/dist/core/message/aun-ipc-sender.js +5 -0
- package/dist/core/message/message-bridge.js +64 -55
- package/dist/core/message/message-log.js +17 -0
- package/dist/core/message/message-queue.js +6 -0
- package/dist/core/message/outbound.js +70 -0
- package/dist/core/message/peer-mode.js +7 -2
- package/dist/core/message/response-engine.js +341 -73
- package/dist/core/ownership/owner-resolver.js +465 -140
- package/dist/core/permission/ec-command-parser.js +19 -3
- package/dist/core/permission/protected-paths.js +51 -0
- package/dist/core/permission/tool-policy.js +7 -7
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/session/session-manager.js +25 -5
- package/dist/core/system-session/memory.js +3 -18
- package/dist/ecweb/basic-settings-service.js +709 -0
- package/dist/ecweb/providers-service.js +172 -0
- package/dist/index.js +92 -4
- package/dist/ipc.js +78 -11
- package/dist/stats/db.js +33 -0
- package/dist/stats/index.js +2 -2
- package/dist/stats/query.js +246 -0
- package/dist/stats/writer.js +41 -1
- package/dist/trigger/application-service.js +12 -0
- package/dist/trigger/authorization.js +2 -2
- package/dist/trigger/history.js +36 -0
- package/dist/trigger/scheduler.js +3 -0
- package/dist/utils/ecweb-supervisor.js +31 -10
- package/dist/utils/error-utils.js +8 -4
- package/dist/utils/evolcore-version.js +3 -2
- package/dist/utils/host-info.js +52 -0
- package/dist/utils/welcome.js +18 -24
- package/ecagent/dist/harness/agent-harness.js +4 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/agent.md +1 -1
- package/kits/docs/evolcore/aid.md +58 -8
- package/kits/docs/evolcore/config.md +9 -7
- package/kits/docs/evolcore/memory-architecture.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +28 -7
- package/kits/docs/evolcore/memory-tags.md +1 -1
- package/kits/docs/evolcore/memory.md +1 -1
- package/kits/docs/evolcore/menu-client.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +6 -0
- package/kits/docs/evolcore/msg.md +1 -1
- package/kits/eck_manifest.json +3 -3
- package/kits/rules/04-relation.md +3 -2
- package/kits/schemas/agent-config.schema.13.json +5 -1
- package/kits/templates/memory-consolidation/generalize.md +32 -4
- package/kits/templates/memory-consolidation/notes.md +2 -2
- package/kits/templates/memory-consolidation/promote.md +10 -2
- package/kits/templates/system-fragments/baseagent.md +5 -0
- package/kits/templates/system-fragments/memory.md +12 -3
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/{rules/07-memory-and-thread.md → templates/system-fragments/thread.md} +17 -80
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,70 @@
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 0.0.40 (2026-10-26)
|
|
9
|
+
|
|
10
|
+
### Ownership 与绑定
|
|
11
|
+
|
|
12
|
+
- 改进 AUN `/bind` 发起前的状态检查,已绑定、已有待处理请求或状态未确认时避免重复提交。
|
|
13
|
+
- 升级 AUN SDK 最低兼容版本至 `0.5.25`,并使用 SDK 恢复和管理 Ownership 请求状态。
|
|
14
|
+
- 支持宿主 CLI 通过短连接查询、接受和拒绝 Ownership 邀约,并完善本机双身份绑定与共识校验。
|
|
15
|
+
- 修复 Agent 初始化期间 Owner 绑定竞态,并在初始化运行时补充 Owner 授权校验。
|
|
16
|
+
- Owner 状态暂时无法刷新时按暂态故障处理;绑定类敏感操作继续要求严格共识校验并提供刷新诊断。
|
|
17
|
+
|
|
18
|
+
### 会话权限与交互
|
|
19
|
+
|
|
20
|
+
- 新增当前 EC 会话 Fullaccess 状态查询和开关,主会话与话题会话分别保存授权状态。
|
|
21
|
+
- 增加 Fullaccess 会话授权复核和审计,并支持通过 Menu 管理当前会话设置。
|
|
22
|
+
- 无人值守会话不再代替用户回答交互问题;阻塞式问题等待用户选择期间暂停回合超时计时。
|
|
23
|
+
- 交互卡片可展示模型建议选项,同时保留用户最终选择;拒绝空白回答并支持显式取消。
|
|
24
|
+
- 主动模式熔断后阻止新的工具调用,待当前工具调用收尾后再中断任务。
|
|
25
|
+
|
|
26
|
+
### 记忆整理
|
|
27
|
+
|
|
28
|
+
- 记忆整理支持按关系空闲时触发,并通过兜底扫描推进待整理任务。
|
|
29
|
+
- 增加整理任务租约恢复和 SDK 超时任务恢复,减少异常中断后任务滞留。
|
|
30
|
+
- 按关系范围拆分记忆泛化任务,并完善整理过程状态与审计上下文。
|
|
31
|
+
|
|
32
|
+
### 状态与运维
|
|
33
|
+
|
|
34
|
+
- 新增 `ec status --format json` 状态快照,汇总 daemon activity、Agent 用量、Trigger、实时队列和渠道状态。
|
|
35
|
+
- 补充主机、操作系统、硬件和 baseagent 信息;文本状态输出兼容 build ID,并修正 task activity 的 Agent AID 归属。
|
|
36
|
+
- 统一启动与重启流程的自动升级开关,使 `ec start`、`ec restart` 和 restart-monitor 遵循一致策略。
|
|
37
|
+
|
|
38
|
+
### 错误诊断与安全
|
|
39
|
+
|
|
40
|
+
- 改进受管工具调用诊断和 Codex 命令参数提取,保留原始错误原因并限制递归读取。
|
|
41
|
+
- 修复受控输出过滤器对短选项连写的处理,并禁止 `grep -d` 递归读取。
|
|
42
|
+
- 保留 ecagent 中断失败的底层原因,避免 AUN 错误消息重复展示诊断。
|
|
43
|
+
- 修正 AUN 原始消息 payload 的话题路由传递。
|
|
44
|
+
|
|
45
|
+
### AUN 与 EC Web
|
|
46
|
+
|
|
47
|
+
- 修正 AUN 私聊出站模式解析,并为托管 AUN 文本、链接、图片、视频、语音和文件消息写入当前任务 ID。
|
|
48
|
+
- EC Web 与 daemon 启动时协商服务协议,并改进 daemon 不可用或协议不兼容时的处理。
|
|
49
|
+
- 将 EC Web Provider 管理和基础设置改由 daemon IPC 服务处理。
|
|
50
|
+
- EC Web 状态页显示实时 Channels,并在受限时提供 EvolAgents 运行汇总。
|
|
51
|
+
- 统一 EC Web 项目路径编码与运行时规则,迁移旧会话项目选择,修复会话页可能显示空白的问题。
|
|
52
|
+
- 修复会话详情自动刷新时的滚动位置跳动。
|
|
53
|
+
- 用量探索器支持按 Agent 或对端筛选,并切换查看 Token 或费用趋势;模型筛选仅展示有数据的模型。
|
|
54
|
+
- 修正 EC Web 未就绪时的日志路径提示。
|
|
55
|
+
- 修复 ecagent 错误或中断时持久化无效 assistant 消息的问题,并增强 OpenAI assistant 数组消息转换。
|
|
56
|
+
|
|
57
|
+
## 0.0.39 (2026-10-24)
|
|
58
|
+
|
|
59
|
+
### Ownership 与 Agent 初始化
|
|
60
|
+
|
|
61
|
+
- 支持通过 `ec aid bind status` 查看经 SDK 验证的 Ownership 邀约,并按具体操作 ID 接受或拒绝。
|
|
62
|
+
- 按绑定任务上下文校验审批结果,避免处理不相关或已失效的邀约。
|
|
63
|
+
- Bootstrap 会验证本地 `agent.md`;已验签的名片不再重复发布,无效或未签名内容会阻止不安全的重发。
|
|
64
|
+
- 更新 AUN Owner 欢迎文案,更清晰地介绍 Agent 可提供的帮助与个性化设置。
|
|
65
|
+
|
|
66
|
+
### 权限与兼容性
|
|
67
|
+
|
|
68
|
+
- 优化 Codex 在 Linux 上的文件权限规则,减少被父目录拒绝规则覆盖的冗余子规则,同时保留必要的读写例外。
|
|
69
|
+
- 将 AUN SDK 最低兼容版本提升至 `0.5.23`,以支持 Ownership 邀约审批。
|
|
70
|
+
- 改进 daemon IPC 连接失败诊断,相关命令现在会保留具体的连接错误信息。
|
|
71
|
+
|
|
8
72
|
## 0.0.38 (2026-10-24)
|
|
9
73
|
|
|
10
74
|
### npm 升级稳定性
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ EvolCore 是一个轻量级 AI Agent 网关系统。它为 Claude Code / Codex
|
|
|
20
20
|
- 🧩 **Agent 多实例**:一个 JSON 文件定义一个 Agent,多 Agent 并发运行,Agent 运行时隔离 + 热重载无需重启
|
|
21
21
|
- ⏰ **Trigger 自主任务**:Agent 可设置延迟/定时/周期任务,cron 表达式支持,独立 silent session 执行,支持脚本模式和 prompt 模式
|
|
22
22
|
- 🔧 **Daemon 模块**:系统级后台服务,管理升级检查、全局触发器等跨 Agent 任务
|
|
23
|
-
- 🎯 **Agent 启动引导**:首次启动自动引导配置 AID、
|
|
23
|
+
- 🎯 **Agent 启动引导**:首次启动自动引导配置 AID、owner、channels,降低上手门槛
|
|
24
24
|
- 🎭 **主动模式策略**:proactive 模式下智能判断响应时机,群聊支持静默策略,避免无效打扰
|
|
25
25
|
- 🎴 **交互卡片体系**:CommandCard + ActionInteraction,Feishu 与 AUN 统一支持
|
|
26
26
|
|
|
@@ -157,7 +157,7 @@ ec init aun
|
|
|
157
157
|
- 渠道选择(飞书 / 微信 / 钉钉 / QQ 频道 / 企业微信 / AUN)并扫码登录
|
|
158
158
|
- 默认项目路径
|
|
159
159
|
- 模型选择(sonnet/opus/haiku)
|
|
160
|
-
-
|
|
160
|
+
- AUN 协议 Owner 绑定:使用统一命令 `ec aid bind <ownerAid> [--subject <subjectAid>]`;托管会话默认当前 Agent,独立 CLI 默认 daemon Control AID。Owner 通过其 SDK 明确审批,Evol App 绑定留待二期。协议 Owner 不从 daemon/Agent 配置读取或回写;非 AUN 渠道配置保持现状。
|
|
161
161
|
- 自动写入 `EVOLCORE_HOME` 到 shell profile(Unix)或用户环境变量(Windows)
|
|
162
162
|
|
|
163
163
|
配置文件生成在 `{EVOLCORE_HOME}/data/daemon.json`(默认 `~/.evolcore/data/daemon.json`)。
|
|
@@ -234,8 +234,11 @@ Claude、Codex 或 EC Agent 兼容网关需要额外请求头或查询参数时
|
|
|
234
234
|
### Baseagent 模型与备用链
|
|
235
235
|
|
|
236
236
|
Session Renew 的辅助模型配置与主模型、推理强度放在同一个
|
|
237
|
-
`baseagents.<baseagent>` 块中,与 `responseMode`
|
|
238
|
-
|
|
237
|
+
`baseagents.<baseagent>` 块中,与 `responseMode` 正交。主模型故障备用链使用 `fallback`,
|
|
238
|
+
只在 `agents/defaults.json` 中配置;每个候选可单独指定 Provider,省略时沿用主模型 Provider:
|
|
239
|
+
|
|
240
|
+
示例中的 Provider ID 需先在 `providers.json` 定义;备用 Provider 必须提供与主模型相同的
|
|
241
|
+
`serviceType`,例如 `backup-codex` 提供 `codex` 服务、`backup-text` 提供 `text` 服务。
|
|
239
242
|
|
|
240
243
|
```jsonc
|
|
241
244
|
{
|
|
@@ -245,18 +248,28 @@ Session Renew 的辅助模型配置与主模型、推理强度放在同一个
|
|
|
245
248
|
"effort": "medium",
|
|
246
249
|
"auxiliaryModel": "claude-haiku-4-5-20251001",
|
|
247
250
|
"auxiliaryEffort": "low",
|
|
248
|
-
"
|
|
251
|
+
"fallback": [
|
|
249
252
|
{ "model": "claude-sonnet-4-5", "effort": "medium" },
|
|
250
253
|
{ "model": "claude-haiku-4-5", "effort": "low" }
|
|
251
254
|
]
|
|
252
255
|
},
|
|
253
256
|
"codex": {
|
|
257
|
+
"provider": "company-codex",
|
|
254
258
|
"model": "gpt-5.6",
|
|
255
259
|
"effort": "high",
|
|
256
260
|
"auxiliaryModel": "gpt-5.6-sol",
|
|
257
261
|
"auxiliaryEffort": "medium",
|
|
258
|
-
"
|
|
259
|
-
{ "model": "gpt-5.4", "effort": "medium" }
|
|
262
|
+
"fallback": [
|
|
263
|
+
{ "provider": "backup-codex", "model": "gpt-5.4", "effort": "medium" }
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
"Services": {
|
|
268
|
+
"auxiliary": {
|
|
269
|
+
"provider": "company-text",
|
|
270
|
+
"model": "text-primary",
|
|
271
|
+
"fallback": [
|
|
272
|
+
{ "provider": "backup-text", "model": "text-backup" }
|
|
260
273
|
]
|
|
261
274
|
}
|
|
262
275
|
}
|
|
@@ -264,7 +277,10 @@ Session Renew 的辅助模型配置与主模型、推理强度放在同一个
|
|
|
264
277
|
```
|
|
265
278
|
|
|
266
279
|
`auxiliaryEffort` 必须与同一 baseagent 块中的 `auxiliaryModel` 一起配置;未配置辅助模型时,
|
|
267
|
-
Session Renew 按 `session_renew.fallback_action` 处理。`
|
|
280
|
+
Session Renew 按 `session_renew.fallback_action` 处理。`baseagents.<baseagent>.fallback` 只用于
|
|
281
|
+
该 Baseagent 的主模型,不会被 Session Renew 使用。`Services.<purpose>.fallback` 则只用于该用途;
|
|
282
|
+
备用项可跨 Provider,但必须使用相同的 `serviceType`。例如 `Services.auxiliary` 使用 Provider
|
|
283
|
+
的 `services.text` 模型目录,Provider 不配置 `services.auxiliary`。
|
|
268
284
|
|
|
269
285
|
升级旧版本时,daemon 启动前会自动把模式桶中的辅助字段迁移到对应的 baseagent 块并创建备份。
|
|
270
286
|
迁移逻辑集中在 `src/config/schema-migration.ts`,运行时不会回退读取旧字段。
|
|
@@ -413,21 +429,16 @@ evolcore/
|
|
|
413
429
|
|
|
414
430
|
### ⚠️ 进程级 menu 操作鉴权(v3.2 Breaking)
|
|
415
431
|
|
|
416
|
-
|
|
417
|
-
`daemon.json` 顶层 `owners` 字段(v3.2 起,不再读 `agents/defaults.json`)。
|
|
418
|
-
升级后**必须**在 `daemon.json` 配置 `owners`,否则这些操作一律返回 `FORBIDDEN`(daemon 启动时也会 warn 提示)。
|
|
432
|
+
进程级管理操作的 Owner 权限来自 Control AID 的 Ownership 2.1 新鲜共识,不使用 daemon 配置中的手工 `owner/owners[]`。
|
|
419
433
|
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
434
|
+
```bash
|
|
435
|
+
ec aid bind <ownerAid> # 默认绑定 daemon Control AID
|
|
436
|
+
ec aid bind status # 核验 Owner 共识和运行态同步
|
|
437
|
+
ec aid bind <ownerAid> --subject <aid> # 指定本机 Agent AID
|
|
424
438
|
```
|
|
425
439
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
- **`owners`**:进程级管理者 AID 名单。可执行 `/system`(重启/升级)与 `/agent`
|
|
429
|
-
(create / delete / enable / disable / list / show)。
|
|
430
|
-
- 关系级的 `/trigger`(set / cancel / update / list)仍走 channel 角色(owner/admin)+ scoped 鉴权,**不**受 `owners` 影响。
|
|
440
|
+
托管 Agent 会话默认以当前 `selfAid` 为绑定主体;每个 AID 的 Owner 独立核验,daemon 权限不自动继承给 Agent。`unavailable`、断线或过期共识不会授予 Owner 权限。Evol App 绑定与非 AUN 渠道的 owner 配置不在一期范围。
|
|
441
|
+
- 关系级的 `/trigger`(set / cancel / update / list)仍走 channel 角色(owner/admin)+ scoped 鉴权,**不**受 daemon `owner` 影响。
|
|
431
442
|
- `/agent create` 为「受理即返回」:立即回 `{ accepted: true, aid }`,后台跑完整创建流程并把各
|
|
432
443
|
环节写入 `agents/<aid>/create-status.json`;客户端用 `menu.query name=agent args={aid}` 轮询
|
|
433
444
|
`createProgress.status` 直到 `ready` / `failed`。
|
package/bin/ec-safe-output.js
CHANGED
|
@@ -36,7 +36,9 @@ const STDOUT_FILTER_SPECS = new Map([
|
|
|
36
36
|
// `grep` takes either `-e`/`--regexp` or one leading pattern operand. With an
|
|
37
37
|
// explicit pattern option the operand budget is zero, because a bare operand
|
|
38
38
|
// would be a file to read. `-r`, `-R` and `-f` are absent, so recursion and
|
|
39
|
-
// pattern files stay out.
|
|
39
|
+
// pattern files stay out. `-d recurse` is the same recursion as `-r`, so
|
|
40
|
+
// `-d`/`--directories` are absent too, along with `--include`/`--exclude`/
|
|
41
|
+
// `--exclude-dir`, which only act on recursion.
|
|
40
42
|
['grep', {
|
|
41
43
|
flags: new Set([
|
|
42
44
|
'-i', '--ignore-case', '-v', '--invert-match', '-c', '--count',
|
|
@@ -50,7 +52,7 @@ const STDOUT_FILTER_SPECS = new Map([
|
|
|
50
52
|
valueOptions: new Set([
|
|
51
53
|
'-m', '--max-count', '-A', '--after-context', '-B', '--before-context',
|
|
52
54
|
'-C', '--context', '--color', '--colour', '--binary-files', '--label',
|
|
53
|
-
'--group-separator',
|
|
55
|
+
'--group-separator',
|
|
54
56
|
]),
|
|
55
57
|
patternValueOptions: new Set(['-e', '--regexp']),
|
|
56
58
|
maxOperands: 1,
|
|
@@ -253,6 +255,16 @@ function resolveFilterArgv(filterArgv) {
|
|
|
253
255
|
continue;
|
|
254
256
|
}
|
|
255
257
|
if (spec.numericShorthand && /^-\d+$/.test(arg)) continue;
|
|
258
|
+
// getopt reads the rest of a short option token as its value (`-A2`,
|
|
259
|
+
// `-n30`, `-ePATTERN`). Only the leading letter counts; `-iA2` is rejected.
|
|
260
|
+
if (!arg.startsWith('--') && arg.length > 2) {
|
|
261
|
+
const shortOption = arg.slice(0, 2);
|
|
262
|
+
if (spec.patternValueOptions?.has(shortOption)) {
|
|
263
|
+
sawPatternValue = true;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (spec.valueOptions.has(shortOption)) continue;
|
|
267
|
+
}
|
|
256
268
|
const option = arg.split('=', 1)[0];
|
|
257
269
|
if (spec.patternValueOptions?.has(option)) {
|
|
258
270
|
sawPatternValue = true;
|
|
@@ -119,6 +119,7 @@ export function buildBaseagentSnapshot(config, detections, loaded) {
|
|
|
119
119
|
model: text(blocks.get(id)?.model),
|
|
120
120
|
effort: text(blocks.get(id)?.effort ?? blocks.get(id)?.reasoning),
|
|
121
121
|
version: probes.get(id)?.version ?? null,
|
|
122
|
+
detectionSource: probes.get(id)?.detectionSource ?? [],
|
|
122
123
|
})),
|
|
123
124
|
};
|
|
124
125
|
}
|
|
@@ -941,19 +941,8 @@ export class AgentRunner {
|
|
|
941
941
|
const questions = input.questions;
|
|
942
942
|
// 没有交互上下文(无渠道适配器),回退到纯文本
|
|
943
943
|
const permCtx = this.permissionContexts.get(sessionId);
|
|
944
|
-
if (usesUnattendedInteractions(permCtx))
|
|
945
|
-
|
|
946
|
-
const unattendedAnswer = '当前为无人值守 Trigger,请基于已有上下文选择最安全合理的方案并继续执行。';
|
|
947
|
-
const answers = {};
|
|
948
|
-
for (const question of questions ?? []) {
|
|
949
|
-
answers[question.question] = question.multiSelect ? [unattendedAnswer] : unattendedAnswer;
|
|
950
|
-
}
|
|
951
|
-
return {
|
|
952
|
-
behavior: 'allow',
|
|
953
|
-
updatedInput: { ...input, answers },
|
|
954
|
-
decisionClassification: 'user_temporary',
|
|
955
|
-
};
|
|
956
|
-
}
|
|
944
|
+
if (usesUnattendedInteractions(permCtx))
|
|
945
|
+
throw new Error('无人值守任务不能自动回答用户问题');
|
|
957
946
|
if (!permCtx?.adapter || !permCtx?.channelId) {
|
|
958
947
|
return this.handleAskUserQuestionFallback(sessionId, input, questions, options);
|
|
959
948
|
}
|
|
@@ -990,11 +979,15 @@ export class AgentRunner {
|
|
|
990
979
|
buttons: [
|
|
991
980
|
{ key: 'submit', label: '✅ 确认选择', style: 'primary' },
|
|
992
981
|
],
|
|
982
|
+
...(typeof q.recommendedOption === 'string'
|
|
983
|
+
? { recommendedOption: q.options.find(opt => opt.label === q.recommendedOption)?.label }
|
|
984
|
+
: {}),
|
|
993
985
|
allowCustomInput: true,
|
|
994
986
|
},
|
|
995
987
|
channelId: permCtx.channelId,
|
|
996
988
|
sessionId,
|
|
997
989
|
initiatorId: permCtx.userId,
|
|
990
|
+
blocking: true,
|
|
998
991
|
};
|
|
999
992
|
}
|
|
1000
993
|
else {
|
|
@@ -1018,11 +1011,15 @@ export class AgentRunner {
|
|
|
1018
1011
|
label: opt.label,
|
|
1019
1012
|
style: 'default',
|
|
1020
1013
|
})),
|
|
1014
|
+
...(typeof q.recommendedOption === 'string'
|
|
1015
|
+
? { recommendedOption: q.options.find(opt => opt.label === q.recommendedOption)?.label }
|
|
1016
|
+
: {}),
|
|
1021
1017
|
allowCustomInput: true,
|
|
1022
1018
|
},
|
|
1023
1019
|
channelId: permCtx.channelId,
|
|
1024
1020
|
sessionId,
|
|
1025
1021
|
initiatorId: permCtx.userId,
|
|
1022
|
+
blocking: true,
|
|
1026
1023
|
};
|
|
1027
1024
|
}
|
|
1028
1025
|
let cardSent = false;
|
|
@@ -1048,14 +1045,7 @@ export class AgentRunner {
|
|
|
1048
1045
|
logger.warn(`[AgentRunner] AskUserQuestion card send failed for q${i}:`, err);
|
|
1049
1046
|
}
|
|
1050
1047
|
if (!cardSent) {
|
|
1051
|
-
|
|
1052
|
-
const firstLabel = q.options[0]?.label || '';
|
|
1053
|
-
answers[q.question] = q.multiSelect ? [firstLabel] : firstLabel;
|
|
1054
|
-
if (sendPrompt) {
|
|
1055
|
-
const optText = q.options.map((o, idx) => ` ${idx + 1}. ${o.label}${o.description ? ` — ${o.description}` : ''}`).join('\n');
|
|
1056
|
-
await sendPrompt(`💬 ${q.header || q.question}\n${q.header ? q.question + '\n' : ''}${optText}\n → 自动选择:${firstLabel}`);
|
|
1057
|
-
}
|
|
1058
|
-
continue;
|
|
1048
|
+
throw new Error(`AskUserQuestion 卡片投递失败:${q.question}`);
|
|
1059
1049
|
}
|
|
1060
1050
|
try {
|
|
1061
1051
|
await permCtx.turn?.onInteractionOpen?.({
|
|
@@ -1063,7 +1053,6 @@ export class AgentRunner {
|
|
|
1063
1053
|
kind: 'ask_user',
|
|
1064
1054
|
toolUseId: options.toolUseID,
|
|
1065
1055
|
requestId: options.requestId,
|
|
1066
|
-
expiresAt: Date.now() + 20 * 60 * 1000,
|
|
1067
1056
|
});
|
|
1068
1057
|
}
|
|
1069
1058
|
catch (error) {
|
|
@@ -1091,7 +1080,10 @@ export class AgentRunner {
|
|
|
1091
1080
|
}
|
|
1092
1081
|
else if (action === '_custom_input') {
|
|
1093
1082
|
const customText = values?.custom_text;
|
|
1094
|
-
|
|
1083
|
+
const trimmed = typeof customText === 'string' ? customText.trim() : '';
|
|
1084
|
+
if (!trimmed)
|
|
1085
|
+
return false;
|
|
1086
|
+
finish({ kind: 'answered', value: trimmed });
|
|
1095
1087
|
}
|
|
1096
1088
|
else if (action === 'submit' && q.multiSelect && values) {
|
|
1097
1089
|
const selected = [];
|
|
@@ -1099,15 +1091,18 @@ export class AgentRunner {
|
|
|
1099
1091
|
if (values[`opt_${idx}`] === true)
|
|
1100
1092
|
selected.push(opt.label);
|
|
1101
1093
|
});
|
|
1102
|
-
|
|
1094
|
+
if (selected.length === 0)
|
|
1095
|
+
return false;
|
|
1096
|
+
finish({ kind: 'answered', value: selected });
|
|
1103
1097
|
}
|
|
1104
1098
|
else {
|
|
1105
1099
|
finish({ kind: 'answered', value: action });
|
|
1106
1100
|
}
|
|
1101
|
+
return true;
|
|
1107
1102
|
}, {
|
|
1108
1103
|
initiatorId: permCtx.userId,
|
|
1109
1104
|
fallbackCommand: 'ask',
|
|
1110
|
-
|
|
1105
|
+
// User questions wait until an explicit answer or cancellation.
|
|
1111
1106
|
onCancel: async (reason) => {
|
|
1112
1107
|
finish({ kind: 'cancelled', reason });
|
|
1113
1108
|
await cancellationClosed;
|
|
@@ -1176,6 +1171,7 @@ export class AgentRunner {
|
|
|
1176
1171
|
channelId: permCtx.channelId || '',
|
|
1177
1172
|
sessionId,
|
|
1178
1173
|
initiatorId: permCtx.userId,
|
|
1174
|
+
blocking: true,
|
|
1179
1175
|
kind: {
|
|
1180
1176
|
kind: 'action',
|
|
1181
1177
|
title: `💬 ${q.question}`,
|
|
@@ -1198,7 +1194,6 @@ export class AgentRunner {
|
|
|
1198
1194
|
kind: 'ask_user',
|
|
1199
1195
|
toolUseId: options.toolUseID,
|
|
1200
1196
|
requestId: options.requestId,
|
|
1201
|
-
expiresAt: Date.now() + 20 * 60 * 1000,
|
|
1202
1197
|
});
|
|
1203
1198
|
}
|
|
1204
1199
|
catch (error) {
|
|
@@ -1221,6 +1216,8 @@ export class AgentRunner {
|
|
|
1221
1216
|
};
|
|
1222
1217
|
permCtx.interactionRouter.register(requestId, sessionId, (action) => {
|
|
1223
1218
|
const answer = action.trim();
|
|
1219
|
+
if (!answer)
|
|
1220
|
+
return false;
|
|
1224
1221
|
const selectionParts = answer.split(/[,,]/).map(part => part.trim());
|
|
1225
1222
|
const selectedIndexes = selectionParts.map(part => Number(part));
|
|
1226
1223
|
const hasValidNumericSelection = selectionParts.length > 0
|
|
@@ -1240,10 +1237,10 @@ export class AgentRunner {
|
|
|
1240
1237
|
else {
|
|
1241
1238
|
finish({ kind: 'answered', value: answer });
|
|
1242
1239
|
}
|
|
1240
|
+
return true;
|
|
1243
1241
|
}, {
|
|
1244
1242
|
initiatorId: permCtx.userId,
|
|
1245
1243
|
fallbackCommand: 'ask',
|
|
1246
|
-
timeoutMs: 20 * 60 * 1000,
|
|
1247
1244
|
onCancel: async (reason) => {
|
|
1248
1245
|
finish({ kind: 'cancelled', reason });
|
|
1249
1246
|
await cancellationClosed;
|
|
@@ -1283,12 +1280,7 @@ export class AgentRunner {
|
|
|
1283
1280
|
}
|
|
1284
1281
|
}
|
|
1285
1282
|
else {
|
|
1286
|
-
|
|
1287
|
-
answers[q.question] = firstLabel;
|
|
1288
|
-
if (sendPrompt) {
|
|
1289
|
-
const optText = q.options.map((o, i) => ` ${i + 1}. ${o.label}${o.description ? ` — ${o.description}` : ''}`).join('\n');
|
|
1290
|
-
await sendPrompt(`💬 ${q.question}\n${optText}\n\n → 自动选择:${firstLabel}`);
|
|
1291
|
-
}
|
|
1283
|
+
throw new Error(`AskUserQuestion 没有可用的交互路由:${q.question}`);
|
|
1292
1284
|
}
|
|
1293
1285
|
}
|
|
1294
1286
|
}
|
|
@@ -1300,17 +1292,14 @@ export class AgentRunner {
|
|
|
1300
1292
|
*/
|
|
1301
1293
|
async handleExitPlanMode(sessionId, input, options) {
|
|
1302
1294
|
const permCtx = this.permissionContexts.get(sessionId);
|
|
1303
|
-
if (usesUnattendedInteractions(permCtx))
|
|
1304
|
-
|
|
1305
|
-
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_temporary' };
|
|
1306
|
-
}
|
|
1295
|
+
if (usesUnattendedInteractions(permCtx))
|
|
1296
|
+
throw new Error('无人值守任务不能自动批准计划');
|
|
1307
1297
|
const sendPrompt = permCtx?.adapter && permCtx?.channelId
|
|
1308
1298
|
? async (text) => permCtx.adapter.send(buildEnvelope({ channel: permCtx.adapter.channelName, channelId: permCtx.channelId, replyContext: permCtx.replyContext }), { kind: 'result.text', text, isFinal: true })
|
|
1309
1299
|
: this.sendPromptFn;
|
|
1310
1300
|
// 无任何交互能力,直接 allow
|
|
1311
|
-
if (!permCtx?.channelId || !sendPrompt)
|
|
1312
|
-
|
|
1313
|
-
}
|
|
1301
|
+
if (!permCtx?.channelId || !sendPrompt)
|
|
1302
|
+
throw new Error('计划审批没有可用的交互路由');
|
|
1314
1303
|
// 立即暂停 idle 监控,不等卡片发完再 register
|
|
1315
1304
|
permCtx.interactionRouter?.markWaiting(sessionId);
|
|
1316
1305
|
const waitForPlanDecision = async (requestId) => {
|
|
@@ -1321,7 +1310,6 @@ export class AgentRunner {
|
|
|
1321
1310
|
kind: 'plan_approval',
|
|
1322
1311
|
toolUseId: options.toolUseID,
|
|
1323
1312
|
requestId: options.requestId,
|
|
1324
|
-
expiresAt: Date.now() + 20 * 60 * 1000,
|
|
1325
1313
|
});
|
|
1326
1314
|
}
|
|
1327
1315
|
catch (error) {
|
|
@@ -1369,7 +1357,6 @@ export class AgentRunner {
|
|
|
1369
1357
|
}, {
|
|
1370
1358
|
initiatorId: permCtx.userId,
|
|
1371
1359
|
fallbackCommand: 'ask',
|
|
1372
|
-
timeoutMs: 20 * 60 * 1000,
|
|
1373
1360
|
onCancel: async (reason) => {
|
|
1374
1361
|
finish({ kind: 'cancelled', reason });
|
|
1375
1362
|
await cancellationClosed;
|
|
@@ -1446,6 +1433,7 @@ export class AgentRunner {
|
|
|
1446
1433
|
channelId: permCtx.channelId,
|
|
1447
1434
|
sessionId,
|
|
1448
1435
|
initiatorId: permCtx.userId,
|
|
1436
|
+
blocking: true,
|
|
1449
1437
|
fallback: {
|
|
1450
1438
|
command: 'ask',
|
|
1451
1439
|
buttonArgMap: { approve: '1', reject: '2' },
|
|
@@ -1482,6 +1470,7 @@ export class AgentRunner {
|
|
|
1482
1470
|
channelId: permCtx.channelId || '',
|
|
1483
1471
|
sessionId,
|
|
1484
1472
|
initiatorId: permCtx.userId,
|
|
1473
|
+
blocking: true,
|
|
1485
1474
|
kind: {
|
|
1486
1475
|
kind: 'action',
|
|
1487
1476
|
title: '📋 计划审批',
|
|
@@ -1501,11 +1490,8 @@ export class AgentRunner {
|
|
|
1501
1490
|
permCtx.interactionRouter.unmarkWaiting(sessionId);
|
|
1502
1491
|
return await waitForPlanDecision(fallbackRequestId);
|
|
1503
1492
|
}
|
|
1504
|
-
// 无交互能力,发提示后直接 allow
|
|
1505
1493
|
permCtx?.interactionRouter?.unmarkWaiting(sessionId);
|
|
1506
|
-
|
|
1507
|
-
await sendPrompt('📋 计划审批\nAI 已完成规划,自动批准执行。');
|
|
1508
|
-
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_temporary' };
|
|
1494
|
+
throw new Error('计划审批没有可用的交互路由,已拒绝自动批准');
|
|
1509
1495
|
}
|
|
1510
1496
|
/**
|
|
1511
1497
|
* SDK 原始事件 → 标准 AgentEvent 转换
|
|
@@ -6,7 +6,7 @@ import { requestDangerousCommandPermission } from '../core/permission/approval-g
|
|
|
6
6
|
import { checkReadonly, checkDangerousCommand, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
7
7
|
import { isShellToolName } from '../core/permission/tool-canonical.js';
|
|
8
8
|
import { normalizeExecutionPermissionMode, normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
|
|
9
|
-
import { assertWindowsCodexFilesystemRuleBudget, buildCodexProtectedFilesystemRules, getExistingRelationAttachmentDirs, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
|
|
9
|
+
import { assertWindowsCodexFilesystemRuleBudget, buildCodexProtectedFilesystemRules, compactCodexLinuxFilesystemRules, getExistingRelationAttachmentDirs, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
|
|
10
10
|
import { CODEX_THREAD_ACTIVE_WRITER, CodexAppServerClient, } from './codex/app-server-client.js';
|
|
11
11
|
import { resolveOpenaiConfig, resolveCodexCliPath, resolveCodexPowerShellShimPath, } from './baseagent.js';
|
|
12
12
|
import { logger } from '../utils/logger.js';
|
|
@@ -781,8 +781,9 @@ export class CodexRunner {
|
|
|
781
781
|
...(expectedCredentialToken !== undefined ? { expectedCredentialToken } : {}),
|
|
782
782
|
};
|
|
783
783
|
const classification = classifyEvolcoreShellCommand(command, parseOptions);
|
|
784
|
-
if (classification.kind === 'bounded-output')
|
|
785
|
-
return { argv: classification.command.argv };
|
|
784
|
+
if (classification.kind === 'bounded-output') {
|
|
785
|
+
return { argv: canonicalizeEvolcoreCommandArgv(classification.command.argv) };
|
|
786
|
+
}
|
|
786
787
|
// Keep the approval/delegation parser aligned with the preflight parser:
|
|
787
788
|
// the session-managed `$TMPDIR` token is a permitted EC path reference.
|
|
788
789
|
const parsed = executable
|
|
@@ -913,7 +914,20 @@ export class CodexRunner {
|
|
|
913
914
|
allowManagedTmpDir: true,
|
|
914
915
|
...(managedTempDir ? { managedTempDir } : {}),
|
|
915
916
|
});
|
|
916
|
-
|
|
917
|
+
if (classification.kind === 'bounded-output'
|
|
918
|
+
&& normalizeEvolcoreCommandArgv(classification.command.argv)[0] === 'ec') {
|
|
919
|
+
const argv = classification.command.argv;
|
|
920
|
+
return containsLiteralManagedTmpDirOutsideSendContent(argv) || containsUnresolvedManagedTmpDirReference(argv)
|
|
921
|
+
? { parseFailure: {
|
|
922
|
+
issue: 'unsafe-expansion',
|
|
923
|
+
offset: 0,
|
|
924
|
+
tokenIndex: 0,
|
|
925
|
+
dialect: 'posix',
|
|
926
|
+
inputLength: command.length,
|
|
927
|
+
} }
|
|
928
|
+
: { argv: canonicalizeEvolcoreCommandArgv(argv) };
|
|
929
|
+
}
|
|
930
|
+
return { parseFailure: outer };
|
|
917
931
|
}
|
|
918
932
|
return {};
|
|
919
933
|
}
|
|
@@ -1428,7 +1442,7 @@ export class CodexRunner {
|
|
|
1428
1442
|
onlyOtherAgents: true,
|
|
1429
1443
|
}))
|
|
1430
1444
|
relationAttachmentDenies[directory] = 'deny';
|
|
1431
|
-
const filesystem = {
|
|
1445
|
+
const filesystem = compactCodexLinuxFilesystemRules({
|
|
1432
1446
|
...buildCodexProtectedFilesystemRules(resolveRoot(), {
|
|
1433
1447
|
selfAid: this.resolvedConfig.agentAid,
|
|
1434
1448
|
denyLClassRead: mode === 'readonly',
|
|
@@ -1455,7 +1469,7 @@ export class CodexRunner {
|
|
|
1455
1469
|
[path.join(managedTempDir, 'locks')]: 'write',
|
|
1456
1470
|
[path.join(managedTempDir, 'locks') + '/**']: 'write',
|
|
1457
1471
|
} : {}),
|
|
1458
|
-
};
|
|
1472
|
+
});
|
|
1459
1473
|
// Codex resolves this complete profile before serializing the Windows
|
|
1460
1474
|
// helper payload. Check after all runner-owned additions, not merely the
|
|
1461
1475
|
// protected-path base projection, so Git and managed-temp rules cannot
|
|
@@ -1736,6 +1750,8 @@ export class CodexRunner {
|
|
|
1736
1750
|
const policyCode = result.policyCode ?? 'session_policy_hook';
|
|
1737
1751
|
const reason = result.reason ?? 'session policy denied this tool call';
|
|
1738
1752
|
await recordPreToolUseDenial(policyCode, reason);
|
|
1753
|
+
if (policyCode === 'proactive_circuit_break')
|
|
1754
|
+
context.onProactiveCircuitBreak?.();
|
|
1739
1755
|
return { ok: true, applicable: true, decision: 'deny', reason };
|
|
1740
1756
|
}
|
|
1741
1757
|
return { ok: true, applicable: true, decision: 'allow' };
|
|
@@ -3373,6 +3389,8 @@ export class CodexRunner {
|
|
|
3373
3389
|
`reason=${policyResult.reason ?? 'policy denied'} executed=false`);
|
|
3374
3390
|
const policyCode = policyResult.policyCode ?? 'session_policy_hook';
|
|
3375
3391
|
const policyReason = policyResult.reason ?? 'session policy denied approval';
|
|
3392
|
+
if (policyCode === 'proactive_circuit_break')
|
|
3393
|
+
permissionContext?.onProactiveCircuitBreak?.();
|
|
3376
3394
|
this.rememberToolDenial(request, params, {
|
|
3377
3395
|
decision: 'deny',
|
|
3378
3396
|
decisionSource: 'policy',
|
|
@@ -3857,25 +3875,9 @@ export class CodexRunner {
|
|
|
3857
3875
|
}
|
|
3858
3876
|
async collectUserInputAnswer(sessionKey, question) {
|
|
3859
3877
|
const options = Array.isArray(question.options) ? question.options : [];
|
|
3860
|
-
const fallback = options[0]?.label ? [String(options[0].label)] : [''];
|
|
3861
3878
|
const context = this.permissionContexts.get(sessionKey);
|
|
3862
3879
|
if (context?.approvalInteractionPolicy === 'deny') {
|
|
3863
|
-
|
|
3864
|
-
const labels = options
|
|
3865
|
-
.map((option) => typeof option?.label === 'string' ? option.label.trim() : '')
|
|
3866
|
-
.filter(Boolean);
|
|
3867
|
-
if (labels.length > 0) {
|
|
3868
|
-
const prompt = `${String(question.header ?? '')} ${String(question.question ?? '')}`;
|
|
3869
|
-
const isPlanApproval = /\bplan\b|计划|方案/i.test(prompt);
|
|
3870
|
-
const affirmative = isPlanApproval
|
|
3871
|
-
? labels.find(label => {
|
|
3872
|
-
const kind = this.classifyMcpApprovalLabel(label);
|
|
3873
|
-
return kind === 'allow_once' || kind === 'allow_persistent';
|
|
3874
|
-
})
|
|
3875
|
-
: undefined;
|
|
3876
|
-
return [affirmative ?? labels[0]];
|
|
3877
|
-
}
|
|
3878
|
-
return ['当前为无人值守 Trigger,请基于已有上下文选择最安全合理的方案并继续执行。'];
|
|
3880
|
+
throw new Error('无人值守任务不能自动回答用户问题');
|
|
3879
3881
|
}
|
|
3880
3882
|
const canFreeText = question.isOther !== false || options.length === 0;
|
|
3881
3883
|
const sendPrompt = context?.adapter && context.channelId
|
|
@@ -3887,8 +3889,8 @@ export class CodexRunner {
|
|
|
3887
3889
|
: this.sendPromptFn;
|
|
3888
3890
|
if (!context?.interactionRouter || !sendPrompt) {
|
|
3889
3891
|
if (sendPrompt)
|
|
3890
|
-
await sendPrompt(this.formatUserInputFallback(question
|
|
3891
|
-
|
|
3892
|
+
await sendPrompt(this.formatUserInputFallback(question));
|
|
3893
|
+
throw new Error('用户问题没有可用的交互路由,已拒绝自动选择');
|
|
3892
3894
|
}
|
|
3893
3895
|
const requestId = `codex-ask-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3894
3896
|
const buttonArgMap = {};
|
|
@@ -3914,6 +3916,9 @@ export class CodexRunner {
|
|
|
3914
3916
|
title: String(question.header || '问题'),
|
|
3915
3917
|
body: bodyLines.join('\n'),
|
|
3916
3918
|
buttons,
|
|
3919
|
+
...(typeof question.recommendedOption === 'string'
|
|
3920
|
+
? { recommendedOption: buttons.find(button => button.label === question.recommendedOption)?.key }
|
|
3921
|
+
: {}),
|
|
3917
3922
|
allowCustomInput: canFreeText,
|
|
3918
3923
|
},
|
|
3919
3924
|
fallback: {
|
|
@@ -3951,14 +3956,19 @@ export class CodexRunner {
|
|
|
3951
3956
|
}
|
|
3952
3957
|
if (!sent) {
|
|
3953
3958
|
router.unmarkWaiting(sessionKey);
|
|
3954
|
-
|
|
3959
|
+
throw new Error('用户问题卡片投递失败,已拒绝自动选择');
|
|
3955
3960
|
}
|
|
3956
3961
|
return new Promise((resolve) => {
|
|
3957
3962
|
router.register(requestId, sessionKey, (action, values) => {
|
|
3958
|
-
|
|
3963
|
+
const answer = this.parseUserInputAction(action, values, options, []);
|
|
3964
|
+
if (answer.length === 0 || answer.every(item => !item.trim()))
|
|
3965
|
+
return false;
|
|
3966
|
+
resolve(answer);
|
|
3967
|
+
return true;
|
|
3959
3968
|
}, {
|
|
3960
3969
|
initiatorId: context.userId,
|
|
3961
3970
|
fallbackCommand: 'ask',
|
|
3971
|
+
retainOnRejected: true,
|
|
3962
3972
|
});
|
|
3963
3973
|
if (waitMarked) {
|
|
3964
3974
|
router.unmarkWaiting(sessionKey);
|
|
@@ -3986,13 +3996,13 @@ export class CodexRunner {
|
|
|
3986
3996
|
}
|
|
3987
3997
|
return action.trim() ? [action.trim()] : fallback;
|
|
3988
3998
|
}
|
|
3989
|
-
formatUserInputFallback(question
|
|
3999
|
+
formatUserInputFallback(question) {
|
|
3990
4000
|
const options = Array.isArray(question.options) ? question.options : [];
|
|
3991
4001
|
const lines = [String(question.header || '问题'), String(question.question || '')].filter(Boolean);
|
|
3992
4002
|
if (options.length > 0) {
|
|
3993
4003
|
lines.push('', ...options.map((option, index) => `${index + 1}. ${String(option.label || `选项 ${index + 1}`)}${option.description ? ` — ${option.description}` : ''}`));
|
|
3994
4004
|
}
|
|
3995
|
-
lines.push('',
|
|
4005
|
+
lines.push('', '等待用户选择后继续。');
|
|
3996
4006
|
return lines.join('\n');
|
|
3997
4007
|
}
|
|
3998
4008
|
findSessionKeyByThread(threadId, conversationId) {
|
|
@@ -5402,10 +5412,24 @@ export class CodexRunner {
|
|
|
5402
5412
|
const isError = exitCode !== undefined
|
|
5403
5413
|
? exitCode !== 0
|
|
5404
5414
|
: item.status === 'failed' || item.status === 'declined';
|
|
5415
|
+
const rawOutput = item.aggregatedOutput ?? '';
|
|
5416
|
+
const commandStr = Array.isArray(item.command)
|
|
5417
|
+
? item.command.join(' ')
|
|
5418
|
+
: typeof item.command === 'string' ? item.command : '';
|
|
5419
|
+
const isApplyPatchFailure = isError
|
|
5420
|
+
&& /apply_patch/i.test(commandStr)
|
|
5421
|
+
&& /The last line of the patch must be|Invalid patch/i.test(rawOutput);
|
|
5422
|
+
const isApplyPatchArgFailure = isError
|
|
5423
|
+
&& /--codex-run-as-apply-patch requires a UTF-8 PATCH argument/i.test(rawOutput);
|
|
5424
|
+
const applyPatchHint = isApplyPatchFailure
|
|
5425
|
+
? '\n[EvolCore 提示] apply_patch 在 Windows 上通过 .bat shim 调用时会截断多行参数,补丁末尾 *** End Patch 被丢弃。正确做法:改用 FileChange 工具直接写文件,或绕过 .bat 直接调用 codex.exe --codex-run-as-apply-patch。不要继续用 here-string 变体重试。'
|
|
5426
|
+
: isApplyPatchArgFailure
|
|
5427
|
+
? '\n[EvolCore 提示] --codex-run-as-apply-patch 要求补丁内容通过命令行参数传入,不接受 PowerShell here-string 直接输出到 stdout 的方式。正确做法:改用 FileChange 工具直接写文件。'
|
|
5428
|
+
: '';
|
|
5405
5429
|
yield {
|
|
5406
5430
|
type: 'tool_result',
|
|
5407
5431
|
name: 'Shell',
|
|
5408
|
-
result:
|
|
5432
|
+
result: rawOutput + applyPatchHint,
|
|
5409
5433
|
...(item.aggregatedOutput != null ? { outputKind: 'combined' } : {}),
|
|
5410
5434
|
...(blockedFields ?? {
|
|
5411
5435
|
isError,
|