evolcore 0.0.32 → 0.0.34

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 (214) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +135 -37
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +111 -15
  6. package/dist/agents/codex/app-server-client.js +3 -0
  7. package/dist/agents/codex/diagnostics.js +112 -54
  8. package/dist/agents/codex-runner.js +241 -63
  9. package/dist/agents/ecagent-runner.js +78 -33
  10. package/dist/agents/runner-types.js +5 -6
  11. package/dist/aun/aid/identity.js +1 -1
  12. package/dist/aun/group/operations.js +0 -1
  13. package/dist/aun/msg/history.js +1 -0
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/channels/aun.js +44 -8
  17. package/dist/channels/daemon.js +261 -3
  18. package/dist/cli/agent-command.js +58 -24
  19. package/dist/cli/agent.js +1 -1
  20. package/dist/cli/aun-commands.js +368 -31
  21. package/dist/cli/contact.js +8 -2
  22. package/dist/cli/ctl-command.js +3 -1
  23. package/dist/cli/daemon-commands.js +197 -27
  24. package/dist/cli/debug-mode.js +154 -0
  25. package/dist/cli/fs-command.js +2 -1
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +98 -67
  28. package/dist/cli/init.js +2 -2
  29. package/dist/cli/integrity-command.js +196 -0
  30. package/dist/cli/managed-command-guard.js +5 -0
  31. package/dist/cli/memory-command.js +166 -146
  32. package/dist/cli/queue-command.js +9 -4
  33. package/dist/cli/session-command.js +194 -0
  34. package/dist/cli/skill-command.js +78 -2
  35. package/dist/cli/task-context.js +1 -0
  36. package/dist/cli/thread-command.js +622 -0
  37. package/dist/cli/trigger-command.js +10 -3
  38. package/dist/config/builtin-roles.js +1 -0
  39. package/dist/config/config-field-policy.js +6 -3
  40. package/dist/config/config-manager.js +26 -2
  41. package/dist/config/contact-book-store.js +5 -0
  42. package/dist/config/resolved-config-op.js +3 -0
  43. package/dist/config/role-service.js +13 -0
  44. package/dist/config/runtime-env.js +16 -0
  45. package/dist/config-store.js +45 -0
  46. package/dist/core/audit/log-integrity.js +3 -1
  47. package/dist/core/auth/agent-delegation.js +133 -36
  48. package/dist/core/auth/debug-context.js +139 -0
  49. package/dist/core/auth/delegation-diagnostics.js +414 -0
  50. package/dist/core/auth/operation-authorizer.js +25 -2
  51. package/dist/core/auth/operation-catalog.js +49 -0
  52. package/dist/core/background-session/session-cleanup.js +212 -0
  53. package/dist/core/background-session/types.js +10 -0
  54. package/dist/core/bootstrap-messages.js +1 -1
  55. package/dist/core/bootstrap-service.js +6 -3
  56. package/dist/core/command/agent-control.js +18 -26
  57. package/dist/core/command/command-handler.js +197 -27
  58. package/dist/core/command/daemon-diagnostics.js +1 -0
  59. package/dist/core/command/memory-slash.js +6 -2
  60. package/dist/core/command/menu-catalog.js +51 -1
  61. package/dist/core/command/menu-handler.js +114 -23
  62. package/dist/core/command/menu-protocol.js +25 -1
  63. package/dist/core/command/skill-slash.js +322 -0
  64. package/dist/core/command/slash-gate.js +6 -4
  65. package/dist/core/command/slash-handler.js +195 -13
  66. package/dist/core/command/topic-slash.js +528 -0
  67. package/dist/core/daemon-bootstrap.js +4 -7
  68. package/dist/core/evolagent-registry.js +20 -5
  69. package/dist/core/file-integrity/read.js +61 -0
  70. package/dist/core/file-integrity/store.js +262 -0
  71. package/dist/core/handoff/runtime.js +5 -11
  72. package/dist/core/handoff/store.js +5 -0
  73. package/dist/core/ipc-registration.js +175 -3
  74. package/dist/core/memory/batch-meta.js +132 -0
  75. package/dist/core/memory/consolidation-context.js +83 -29
  76. package/dist/core/memory/consolidation-ops-records.js +49 -16
  77. package/dist/core/memory/consolidation-queue-format.js +21 -0
  78. package/dist/core/memory/consolidation.js +264 -205
  79. package/dist/core/memory/index-manager.js +19 -28
  80. package/dist/core/memory/memory-admin.js +19 -14
  81. package/dist/core/memory/memory-document.js +53 -9
  82. package/dist/core/memory/memory-writer.js +30 -15
  83. package/dist/core/message/aun-ipc-sender.js +161 -6
  84. package/dist/core/message/im-renderer.js +169 -10
  85. package/dist/core/message/message-bridge.js +15 -4
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/peer-mode.js +6 -1
  88. package/dist/core/message/response-engine.js +929 -110
  89. package/dist/core/message/subtask-tool-projection.js +140 -0
  90. package/dist/core/model/catalog-service.js +17 -12
  91. package/dist/core/permission/ec-command-parser.js +217 -9
  92. package/dist/core/permission/protected-paths.js +5 -1
  93. package/dist/core/permission/readonly-shell-query.js +43 -5
  94. package/dist/core/permission/session-path-grants.js +334 -0
  95. package/dist/core/permission/tool-canonical.js +226 -0
  96. package/dist/core/permission/tool-policy.js +30 -62
  97. package/dist/core/role/runtime-policy.js +21 -10
  98. package/dist/core/session/session-fs-store.js +123 -8
  99. package/dist/core/session/session-idle-monitor.js +249 -22
  100. package/dist/core/session/session-manager.js +129 -14
  101. package/dist/core/skill/command-parser.js +13 -0
  102. package/dist/core/skill/index-generator.js +21 -4
  103. package/dist/core/skill/startup.js +41 -0
  104. package/dist/core/skill/switch-resolver.js +55 -29
  105. package/dist/core/system-session/grants.js +40 -0
  106. package/dist/core/system-session/memory.js +222 -0
  107. package/dist/core/system-session/registry.js +74 -0
  108. package/dist/core/system-session/types.js +14 -0
  109. package/dist/core/thread/command-parser.js +125 -0
  110. package/dist/core/thread/index.js +1 -0
  111. package/dist/core/thread/ipc-operation.js +95 -0
  112. package/dist/core/thread/thread-context-storage.js +131 -0
  113. package/dist/core/thread/thread-operation.js +258 -0
  114. package/dist/core/thread/thread-route.js +33 -0
  115. package/dist/core/thread/thread-service.js +770 -0
  116. package/dist/core/thread/types.js +1 -0
  117. package/dist/eck/kit-renderer.js +7 -2
  118. package/dist/eck/manifest-engine.js +24 -4
  119. package/dist/eck/message-renderer.js +35 -0
  120. package/dist/index.js +19 -7
  121. package/dist/ipc.js +140 -50
  122. package/dist/paths.js +93 -0
  123. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  124. package/dist/stats/billing.js +20 -8
  125. package/dist/trigger/authorization.js +57 -28
  126. package/dist/trigger/feedback.js +13 -2
  127. package/dist/trigger/parser.js +70 -1
  128. package/dist/trigger/system-definitions.js +3 -3
  129. package/dist/trigger/validation.js +14 -0
  130. package/dist/utils/cross-platform.js +11 -2
  131. package/dist/utils/error-dict.json +18 -4
  132. package/dist/utils/error-utils.js +143 -1
  133. package/dist/utils/instance-registry.js +13 -8
  134. package/dist/utils/log-writer.js +12 -0
  135. package/dist/utils/process-introspect.js +11 -0
  136. package/kits/docs/context-assembly.md +1 -1
  137. package/kits/docs/evolcore/INDEX.md +1 -0
  138. package/kits/docs/evolcore/agent-create.md +40 -28
  139. package/kits/docs/evolcore/agent.md +21 -6
  140. package/kits/docs/evolcore/config.md +2 -2
  141. package/kits/docs/evolcore/ctl.md +2 -2
  142. package/kits/docs/evolcore/ec-capabilities.md +5 -1
  143. package/kits/docs/evolcore/fs-architecture.md +4 -5
  144. package/kits/docs/evolcore/fs.md +1 -1
  145. package/kits/docs/evolcore/group-collaboration.md +4 -6
  146. package/kits/docs/evolcore/group.md +11 -12
  147. package/kits/docs/evolcore/memory-architecture.md +4 -5
  148. package/kits/docs/evolcore/memory-consolidation.md +19 -15
  149. package/kits/docs/evolcore/memory-tags.md +3 -1
  150. package/kits/docs/evolcore/memory.md +24 -12
  151. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  152. package/kits/docs/evolcore/menu-protocol.md +7 -0
  153. package/kits/docs/evolcore/model.md +16 -0
  154. package/kits/docs/evolcore/msg.md +8 -11
  155. package/kits/docs/evolcore/thread-chain.md +267 -0
  156. package/kits/docs/prompt-loading-architecture.md +1 -1
  157. package/kits/eck_manifest.json +55 -40
  158. package/kits/eck_manifest.memory.json +0 -12
  159. package/kits/eck_message_manifest.json +169 -10
  160. package/kits/rules/01-overview.md +11 -0
  161. package/kits/rules/03-identity.md +6 -3
  162. package/kits/rules/05-venue.md +1 -1
  163. package/kits/rules/06-channel.md +1 -1
  164. package/kits/rules/07-memory-and-thread.md +199 -0
  165. package/kits/schemas/_meta.json +7 -1
  166. package/kits/schemas/agent-config.schema.13.json +1 -1
  167. package/kits/schemas/daemon.schema.7.json +140 -0
  168. package/kits/schemas/relation-config.schema.9.json +18 -3
  169. package/kits/schemas/role-config.schema.1.json +6 -6
  170. package/kits/schemas/single-session.schema.1.json +6 -6
  171. package/kits/schemas/single-session.schema.2.json +6 -6
  172. package/kits/schemas/single-session.schema.3.json +6 -6
  173. package/kits/skills/.switches.json +12 -1
  174. package/kits/skills/ec-group/SKILL.md +234 -0
  175. package/kits/skills/ec-skill/SKILL.md +10 -1
  176. package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
  177. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  178. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  179. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  180. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  181. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  182. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  183. package/kits/skills/thread-ops/SKILL.md +492 -0
  184. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  185. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  186. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  187. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  188. package/kits/templates/bootstrap-welcome.md +7 -7
  189. package/kits/templates/memory-consolidation/forget.md +7 -2
  190. package/kits/templates/memory-consolidation/generalize.md +9 -4
  191. package/kits/templates/memory-consolidation/notes.md +11 -11
  192. package/kits/templates/memory-consolidation/profile.md +8 -3
  193. package/kits/templates/memory-consolidation/promote.md +8 -3
  194. package/kits/templates/memory-consolidation/workflow.md +6 -6
  195. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  196. package/kits/templates/message-fragments/item.md +2 -1
  197. package/kits/templates/message-fragments/thread-context.md +6 -0
  198. package/kits/templates/message-fragments/trigger-session.md +2 -0
  199. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  200. package/kits/templates/roles/member.json +2 -0
  201. package/kits/templates/roles/visitor.json +2 -0
  202. package/kits/templates/system-fragments/bootstrap.md +67 -72
  203. package/kits/templates/system-fragments/channel.md +3 -6
  204. package/kits/templates/system-fragments/commands.md +1 -0
  205. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  206. package/kits/templates/system-fragments/memory.md +16 -9
  207. package/kits/templates/system-fragments/reply.md +7 -6
  208. package/kits/templates/system-fragments/session.md +5 -14
  209. package/package.json +6 -3
  210. package/dist/core/memory/memory-session-policy.js +0 -41
  211. package/dist/core/memory/memory-signature.js +0 -257
  212. package/kits/docs/evolcore/group-fs.md +0 -17
  213. package/kits/docs/venues/aun-group.md +0 -45
  214. package/kits/docs/venues/group.md +0 -43
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Runner 子任务(Claude 后台任务/子智能体/工作流、Codex 子智能体)的出站投影。
3
+ *
4
+ * 子任务本身就是某次工具调用的执行过程:Claude 的 task_started / task_notification
5
+ * 都带来源工具的 tool_use_id。实测两种时序(events.log):
6
+ * - 前台:tool_use → task_started → task_notification → 真实 tool_result(数毫秒后)
7
+ * - 后台:tool_use → task_started → 占位 tool_result("running in background")→ … → task_notification
8
+ *
9
+ * 因此有来源工具时,子任务不再单独出卡片,而是**归拢到来源工具的 tool_result**:
10
+ * 终态合并进那条 tool_result(见 foldSubtaskTerminal),后台场景先扣住占位结果,等终态再发。
11
+ * 渲染器负责扣留与合并(IMRenderer.beginParentSubtask / endParentSubtask)。
12
+ *
13
+ * 没有来源工具(如 Codex 子智能体只有子线程 ID)时,退回合成卡片:
14
+ * 开始一条 tool_call,终态一条 tool_result,共用 call_id = subtask-<taskId>。
15
+ */
16
+ /** 合成 call_id 前缀,避免与真实工具调用 ID 相撞。 */
17
+ const SUBTASK_CALL_ID_PREFIX = 'subtask';
18
+ /** 描述作为命令摘要展示时的字符上限。 */
19
+ const SHORT_DESCRIPTION_MAX_CHARS = 120;
20
+ /** 终态摘要作为 tool_result.result 展示时的字符上限。 */
21
+ const RESULT_SUMMARY_MAX_CHARS = 500;
22
+ /** 无来源工具时,同一子任务开始与终态共用的合成 call_id。 */
23
+ export function subtaskCallId(taskId) {
24
+ return `${SUBTASK_CALL_ID_PREFIX}-${taskId}`;
25
+ }
26
+ /** 子任务在工具卡片上的显示名(仅用于合成卡片或拿不到来源工具名时)。 */
27
+ export function subtaskToolName(input) {
28
+ if (input.taskKind === 'agent')
29
+ return input.subagentType || 'Agent';
30
+ if (input.taskKind === 'workflow')
31
+ return 'Workflow';
32
+ if (input.taskType === 'local_bash')
33
+ return 'Bash';
34
+ return input.taskType || 'Task';
35
+ }
36
+ function collapse(text) {
37
+ return text ? text.replace(/\s+/g, ' ').trim() : '';
38
+ }
39
+ function truncate(text, maxChars) {
40
+ if (text.length <= maxChars)
41
+ return text;
42
+ return `${text.slice(0, maxChars)}…`;
43
+ }
44
+ /** 把描述压成一行短摘要:去掉换行与多余空白,超长截断。 */
45
+ export function shortenSubtaskDescription(description) {
46
+ return truncate(collapse(description), SHORT_DESCRIPTION_MAX_CHARS);
47
+ }
48
+ function formatDuration(durationMs) {
49
+ if (typeof durationMs !== 'number' || !Number.isFinite(durationMs) || durationMs <= 0)
50
+ return '';
51
+ if (durationMs < 1000)
52
+ return `${Math.round(durationMs)}ms`;
53
+ return `${Math.round(durationMs / 1000)}s`;
54
+ }
55
+ /** 终态文案。状态由 ok/detail_code 表达,客户端不需要解析正文。 */
56
+ export function subtaskStatusText(status, durationMs) {
57
+ const duration = formatDuration(durationMs);
58
+ const label = status === 'completed' ? '子任务完成'
59
+ : status === 'failed' ? '子任务失败'
60
+ : status === 'killed' ? '子任务已终止'
61
+ : '子任务已停止';
62
+ return duration ? `${label} (${duration})` : label;
63
+ }
64
+ /**
65
+ * 把子任务终态合并进来源工具的 tool_result。
66
+ *
67
+ * - ok:工具结果与子任务都成功才为 true。子任务失败/停止/终止时一律 false——实测存在
68
+ * "任务 failed、工具结果 isError=false" 的组合,不能用工具的成功覆盖子任务的失败。
69
+ * - 工具结果已带错误分类时保留原分类;工具成功而子任务失败时补 EXECUTION_FAILED。
70
+ * 停止与终止不是执行失败,只用 detail_code 区分。
71
+ * - 正文、result、text 保留来源工具原值;子任务耗时更长(后台任务)时以子任务为准。
72
+ */
73
+ export function foldSubtaskTerminal(item, terminal) {
74
+ if (item.kind !== 'tool_result')
75
+ return item;
76
+ const merged = { ...item };
77
+ if (typeof terminal.durationMs === 'number' && terminal.durationMs > (merged.duration_ms ?? 0)) {
78
+ merged.duration_ms = terminal.durationMs;
79
+ }
80
+ if (terminal.status === 'completed')
81
+ return merged;
82
+ merged.ok = false;
83
+ merged.detail_code ??= `subtask_${terminal.status}`;
84
+ if (!merged.error_code && terminal.status === 'failed')
85
+ merged.error_code = 'EXECUTION_FAILED';
86
+ if (!merged.error) {
87
+ const summary = collapse(terminal.summary);
88
+ merged.error = summary ? truncate(summary, RESULT_SUMMARY_MAX_CHARS) : subtaskStatusText(terminal.status);
89
+ }
90
+ return merged;
91
+ }
92
+ function buildArguments(input, shortDescription) {
93
+ const args = {
94
+ task_id: input.taskId,
95
+ subtask_kind: input.taskKind,
96
+ };
97
+ if (shortDescription)
98
+ args.command = shortDescription;
99
+ if (input.taskType)
100
+ args.task_type = input.taskType;
101
+ if (input.subagentType)
102
+ args.subagent_type = input.subagentType;
103
+ if (input.workflowName)
104
+ args.workflow_name = input.workflowName;
105
+ return args;
106
+ }
107
+ /** 无来源工具的子任务开始 → 合成 tool_call。 */
108
+ export function projectSubtaskStarted(input) {
109
+ const shortDescription = shortenSubtaskDescription(input.description);
110
+ return {
111
+ kind: 'tool_call',
112
+ call_id: subtaskCallId(input.taskId),
113
+ name: subtaskToolName(input),
114
+ arguments: buildArguments(input, shortDescription),
115
+ text: shortDescription,
116
+ };
117
+ }
118
+ /**
119
+ * 子任务终态 → 独立 tool_result。
120
+ *
121
+ * 用于两种情况:无来源工具的合成卡片;或来源工具卡片已在本轮之外/已提前释放,
122
+ * 只能补发一条同 call_id 的终态结果来更新那张卡片。
123
+ */
124
+ export function projectSubtaskTerminal(input) {
125
+ const shortDescription = shortenSubtaskDescription(input.description);
126
+ // 与描述比较用未截断的折叠文本:local_bash 未给 description 时 SDK 会把整条命令
127
+ // 同时塞进 description 和 summary,两者只是截断长度不同,仍属同一段文字。
128
+ const rawSummary = collapse(input.summary);
129
+ const summary = rawSummary ? truncate(rawSummary, RESULT_SUMMARY_MAX_CHARS) : '';
130
+ const base = {
131
+ kind: 'tool_result',
132
+ call_id: input.callId ?? subtaskCallId(input.taskId),
133
+ name: input.toolName ?? subtaskToolName(input),
134
+ ok: true,
135
+ text: `${subtaskStatusText(input.status, input.durationMs)}${shortDescription ? ` · ${shortDescription}` : ''}`,
136
+ };
137
+ if (summary && rawSummary !== collapse(input.description))
138
+ base.result = summary;
139
+ return foldSubtaskTerminal(base, { status: input.status, summary, durationMs: input.durationMs });
140
+ }
@@ -13,14 +13,19 @@ export const CATALOG_ROUTE_TIMEOUT_MS = 15_000;
13
13
  * 否则旧规则写下的持久化快照与新快照无法区分,会继续被服务。
14
14
  * v2:保留上下文窗口后缀(`claude-opus-5[1m]` 不再被折叠成基础 ID)。
15
15
  * v3:保留 Codex 上游声明的 max effort,重新获取旧快照中过滤掉的能力。
16
+ * v4:统一缺失 effort 元数据时的 transport fallback,并记录来源。
16
17
  */
17
- export const CATALOG_SNAPSHOT_VERSION = 3;
18
+ export const CATALOG_SNAPSHOT_VERSION = 4;
19
+ export const CONFIGURABLE_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
18
20
  export const TRANSPORT_EFFORTS = {
19
21
  claude: ['low', 'medium', 'high', 'xhigh', 'max'],
20
22
  codex: ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'],
21
23
  ecagent: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'],
22
24
  gemini: [],
23
25
  };
26
+ export function configurableEfforts(baseagent, efforts = TRANSPORT_EFFORTS[baseagent] ?? []) {
27
+ return efforts.filter((effort) => CONFIGURABLE_EFFORTS.includes(effort) && (TRANSPORT_EFFORTS[baseagent] ?? []).includes(effort));
28
+ }
24
29
  function stable(value) {
25
30
  if (Array.isArray(value))
26
31
  return `[${value.map(stable).join(',')}]`;
@@ -93,10 +98,7 @@ const BUILTIN_IDS = {
93
98
  gemini: ['gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite'],
94
99
  };
95
100
  export function builtinCatalog(baseagent) {
96
- return (BUILTIN_IDS[baseagent] ?? []).map(id => ({ id, verification: 'unverified',
97
- ...(baseagent === 'codex' ? { reasoningLevels: ['low', 'medium', 'high', 'xhigh'] } : {}),
98
- ...(/^claude-(opus|sonnet|haiku)-/.test(id) && baseagent === 'claude' ? { reasoningLevels: [...TRANSPORT_EFFORTS.claude] } : {}),
99
- }));
101
+ return (BUILTIN_IDS[baseagent] ?? []).map(id => ({ id, verification: 'unverified' }));
100
102
  }
101
103
  export function deriveAliases(models, baseagent) {
102
104
  if (baseagent !== 'claude')
@@ -126,16 +128,19 @@ function compatible(model, baseagent) {
126
128
  return !model.protocols || !protocols[baseagent] || model.protocols.some(p => protocols[baseagent].includes(p));
127
129
  }
128
130
  function adapt(models, baseagent) {
129
- const available = models.filter(m => compatible(m, baseagent)).map(m => ({ ...m,
130
- ...(m.reasoningLevels ? { reasoningLevels: m.reasoningLevels.filter(e => TRANSPORT_EFFORTS[baseagent]?.includes(e)) } : {}),
131
- }));
131
+ const available = models.filter(m => compatible(m, baseagent)).map(m => {
132
+ const reasoningLevels = configurableEfforts(baseagent, m.reasoningLevels);
133
+ const effortSource = reasoningLevels.length === 0 ? 'none'
134
+ : m.reasoningLevels === undefined ? 'transport-fallback' : 'catalog';
135
+ return { ...m, reasoningLevels, effortSource };
136
+ });
132
137
  const aliases = deriveAliases(available, baseagent);
133
138
  return [...Object.entries(aliases).filter(([id]) => !available.some(m => m.id === id)).map(([id, aliasFor]) => ({ ...available.find(m => m.id === aliasFor), id, aliasFor, owned_by: 'alias' })), ...available];
134
139
  }
135
140
  export function resolveCatalogModel(snapshot, model) { return snapshot.models.find(m => m.id === model)?.aliasFor ?? model; }
136
141
  export function catalogEfforts(snapshot, model, baseagent) {
137
142
  const entry = snapshot.models.find(m => m.id === resolveCatalogModel(snapshot, model));
138
- return entry?.reasoningLevels?.filter(e => TRANSPORT_EFFORTS[baseagent]?.includes(e)) ?? [];
143
+ return entry ? configurableEfforts(baseagent, entry.reasoningLevels) : [];
139
144
  }
140
145
  export function assertCatalogSelectable(snapshot) {
141
146
  if (snapshot.diagnostics.some(d => d.code === 'AUTH_FAILED'))
@@ -288,7 +293,7 @@ export class ModelCatalogService {
288
293
  // suggestions; never let it attest gateway membership for another key.
289
294
  for (const [family, id] of Object.entries(aliases))
290
295
  if (typeof id === 'string' && /^(opus|sonnet|haiku)$/.test(family) && new RegExp(`^claude-${family}-[A-Za-z0-9._-]+$`).test(id) && !models.some(m => m.id === id))
291
- models.unshift({ id, verification: 'unverified', reasoningLevels: [...TRANSPORT_EFFORTS.claude] });
296
+ models.unshift({ id, verification: 'unverified' });
292
297
  }
293
298
  catch { /* No legacy data. */ }
294
299
  return models;
@@ -302,8 +307,8 @@ export class ModelCatalogService {
302
307
  const models = await gatewayModels(context, this.options.fetch ?? fetch);
303
308
  // Enrich only IDs the gateway listed. Native models never add/remove membership.
304
309
  const native = context.baseagent === 'codex' && models.length ? await this.options.native?.(context).catch(() => null) : null;
305
- const metadata = new Map((native ?? builtinCatalog(context.baseagent)).map(m => [m.id, m]));
306
- const enriched = models.map(m => ({ ...m, ...(m.reasoningLevels === undefined && metadata.get(m.id)?.reasoningLevels ? { reasoningLevels: metadata.get(m.id).reasoningLevels } : {}) }));
310
+ const metadata = new Map((native ?? []).map(m => [m.id, m]));
311
+ const enriched = models.map(m => ({ ...m, ...(m.reasoningLevels === undefined && metadata.get(m.id)?.reasoningLevels !== undefined ? { reasoningLevels: metadata.get(m.id).reasoningLevels } : {}) }));
307
312
  const result = this.make(context, 'gateway', 'fresh', enriched, diagnostics);
308
313
  this.save(context, result);
309
314
  return result;
@@ -663,7 +663,7 @@ const INLINE_TEXT_FLAGS = {
663
663
  };
664
664
  const MESSAGE_POSITIONAL_FLAGS = new Set([
665
665
  '--file', '--payload', '--as', '--content-type', '--text', '--transcript',
666
- '--thread', '--return', '--title', '--description', '--text-from-file',
666
+ '--thread', '--title', '--description', '--text-from-file',
667
667
  '--handoff-context', '--aun-path', '--app', '--format', '--encrypt', '--no-encrypt',
668
668
  ]);
669
669
  /** Decode the EC inline text contract exactly once, after shell parsing. */
@@ -714,6 +714,77 @@ function decodePositionalText(argv, start, optionFlags) {
714
714
  argv[index] = decodeEvolcoreInlineText(value);
715
715
  }
716
716
  }
717
+ // 取值为「逗号分隔、单项不含空格」的列表参数。aid、标签值、角色名里都不会有
718
+ // 空格,所以按空格拆分是安全的。
719
+ const MEMORY_LIST_FLAGS = new Set(['--tags', '--speakers']);
720
+ const SEND_LIST_FLAGS = new Set(['--mention']);
721
+ const FS_LIST_FLAGS = new Set(['--allow-roles']);
722
+ // 取值可能含空格的列表参数,只能按逗号拆:关键词是自由文本,记忆路径是 config
723
+ // 相对路径,两者都可能有空格。
724
+ const MEMORY_COMMA_ONLY_FLAGS = new Set(['--keywords', '--replaces']);
725
+ /** 逗号分隔项:去空、去首尾空白,重新用逗号连接。 */
726
+ function joinCommaList(value) {
727
+ return value.split(',').map(item => item.trim()).filter(Boolean).join(',');
728
+ }
729
+ /**
730
+ * 把列表参数统一成逗号分隔。PowerShell 把未加引号的 `a,b` 当数组,经 npm 的
731
+ * ec.ps1 用 `$args` 转发给 node 时逗号消失:`a,b` 变成 `a b`,`a, b` 和
732
+ * `a,b, c` 也变成 `a b` / `a b c`(实测 PS 5.1)。审批侧按命令串解析时看到的
733
+ * 是 `a,` 和 `b` 两个 token。两边在这里收敛到同一表示,委托哈希才能对上。
734
+ *
735
+ * 值以逗号结尾说明逗号数组还没结束:把后续 token 并进同一个值里再拆。值里本来
736
+ * 带逗号却没有空格的写法(bash 下 `--tags "a, b"`)也会被拆成两个标签,这是刻
737
+ * 意取舍——标签都是不含空格的单词,而 ec.ps1 已经把 `a, b` 和 `a,b` 归成了一
738
+ * 个值,无法再区分。
739
+ */
740
+ function canonicalizeListFlags(argv, flags) {
741
+ for (let index = 0; index < argv.length; index++) {
742
+ const value = argv[index];
743
+ if (value === '--')
744
+ break;
745
+ if (!flags.has(value))
746
+ continue;
747
+ if (argv[index + 1] === undefined || argv[index + 1].startsWith('--'))
748
+ continue;
749
+ // `--tags a, b` 在 PowerShell 里是同一个值,审批侧却解析成 `a,` 加 `b`,
750
+ // 这里按逗号是否收尾把后续 token 续接回来
751
+ let end = index + 1;
752
+ while (argv[end].endsWith(',') && argv[end + 1] !== undefined
753
+ && !argv[end + 1].startsWith('--')) {
754
+ end++;
755
+ }
756
+ argv[index + 1] = argv.slice(index + 1, end + 1)
757
+ .join(' ')
758
+ .split(/[,\s]+/u)
759
+ .filter(Boolean)
760
+ .join(',');
761
+ if (end > index + 1)
762
+ argv.splice(index + 2, end - index - 1);
763
+ index++;
764
+ }
765
+ }
766
+ /**
767
+ * 只按逗号归一化,不动值里的空格。
768
+ *
769
+ * 与 canonicalizeListFlags 的区别:`--keywords 'a b'` 是一个含空格的关键词,
770
+ * 不能被拆成两个。所以只在值本身带逗号时整理(顺带去掉逗号周围的空白),
771
+ * `--keywords a,b` 这种未加引号的写法在 PowerShell 下会先被折成 `a b`,此处
772
+ * 认不出来——那属于被拒绝的写法,靠规范要求单引号避免。
773
+ */
774
+ function canonicalizeCommaOnlyFlags(argv, flags) {
775
+ for (let index = 0; index < argv.length; index++) {
776
+ const value = argv[index];
777
+ if (value === '--')
778
+ break;
779
+ if (!flags.has(value))
780
+ continue;
781
+ const next = argv[index + 1];
782
+ if (next === undefined || next.startsWith('--') || !next.includes(','))
783
+ continue;
784
+ argv[index + 1] = joinCommaList(next);
785
+ index++;
786
+ }
787
+ }
717
788
  function canonicalizeHandoffText(argv) {
718
789
  const action = argv[2];
719
790
  if (action !== 'return' && action !== 'recover-return')
@@ -739,7 +810,8 @@ function canonicalizeHandoffText(argv) {
739
810
  * Convert an EC invocation to the one argv representation shared by approval,
740
811
  * delegation hashing, IPC, and command execution. Shell syntax is already
741
812
  * resolved before this function is called; this function only normalizes the
742
- * EC entrypoint and applies the command-specific inline text contract.
813
+ * EC entrypoint, applies the command-specific inline text contract, and joins
814
+ * memory list flags with commas.
743
815
  */
744
816
  export function canonicalizeEvolcoreCommandArgv(argv) {
745
817
  const canonical = normalizeEvolcoreCommandArgv(argv);
@@ -759,11 +831,13 @@ export function canonicalizeEvolcoreCommandArgv(argv) {
759
831
  else if (command === 'msg' && subcommand === 'send') {
760
832
  decodeInlineFlags(result, INLINE_TEXT_FLAGS.msgSend);
761
833
  decodePositionalText(result, 5, MESSAGE_POSITIONAL_FLAGS);
834
+ canonicalizeListFlags(result, SEND_LIST_FLAGS);
762
835
  }
763
836
  else if (command === 'group') {
764
837
  if (subcommand === 'send') {
765
838
  decodeInlineFlags(result, INLINE_TEXT_FLAGS.groupSend);
766
839
  decodePositionalText(result, 5, MESSAGE_POSITIONAL_FLAGS);
840
+ canonicalizeListFlags(result, SEND_LIST_FLAGS);
767
841
  }
768
842
  else if (subcommand === 'create')
769
843
  decodeInlineFlags(result, INLINE_TEXT_FLAGS.groupCreate);
@@ -785,6 +859,13 @@ export function canonicalizeEvolcoreCommandArgv(argv) {
785
859
  else if (command === 'handoff') {
786
860
  canonicalizeHandoffText(result);
787
861
  }
862
+ else if (command === 'memory') {
863
+ canonicalizeListFlags(result, MEMORY_LIST_FLAGS);
864
+ canonicalizeCommaOnlyFlags(result, MEMORY_COMMA_ONLY_FLAGS);
865
+ }
866
+ else if (command === 'fs' && subcommand === 'chmod') {
867
+ canonicalizeListFlags(result, FS_LIST_FLAGS);
868
+ }
788
869
  return result;
789
870
  }
790
871
  const POWERSHELL_CREDENTIAL_ENV_RE = /^(EVOLCORE_CODEX_SESSION_TOKEN|EVOLCORE_DELEGATION_TOKEN)$/i;
@@ -1496,6 +1577,8 @@ export function parseLiteralShellCommand(command, options = {}) {
1496
1577
  let token = '';
1497
1578
  let tokenStarted = false;
1498
1579
  let quote = null;
1580
+ // EC 命令的自由文本有额外的写法约束(见双引号分支的反引号/$ 转义处理)。
1581
+ const isEcCommand = hasEcCommandPrefix(input);
1499
1582
  const finishToken = () => {
1500
1583
  if (!tokenStarted)
1501
1584
  return;
@@ -1527,6 +1610,12 @@ export function parseLiteralShellCommand(command, options = {}) {
1527
1610
  return parseFailure(issue, input, index, argv.length, 'posix');
1528
1611
  if (char === '\\') {
1529
1612
  const next = input[++index];
1613
+ // EC 自由文本只接受单引号一种写法。双引号里用反斜杠转义 ` 或 $ 虽然
1614
+ // 能得到字面量,但同一条规则下要记两套写法,所以对 ec 命令显式拒绝,
1615
+ // 把正文一律导向单引号。非 ec 命令保持原行为。
1616
+ if (isEcCommand && (next === '`' || next === '$')) {
1617
+ return parseFailure('unsupported-escape', input, index, argv.length, 'posix');
1618
+ }
1530
1619
  token += ['"', '\\', '$', '`'].includes(next) ? next : `\\${next}`;
1531
1620
  continue;
1532
1621
  }
@@ -1715,7 +1804,7 @@ function normalizeApplyPatchPath(value) {
1715
1804
  return undefined;
1716
1805
  return normalized;
1717
1806
  }
1718
- function parseApplyPatchDocument(source) {
1807
+ export function parseApplyPatchDocument(source) {
1719
1808
  const lines = source.replace(/\r\n?/gu, '\n').split('\n');
1720
1809
  while (lines.at(-1) === '')
1721
1810
  lines.pop();
@@ -2762,7 +2851,8 @@ function findSingleFileOption(argv, startIndex) {
2762
2851
  const PROACTIVE_SEND_VALUE_FLAGS = new Set([
2763
2852
  '--format', '--app', '--file', '--text-from-file', '--link', '--payload',
2764
2853
  '--title', '--description', '--text', '--transcript', '--as',
2765
- '--content-type', '--mention', '--aun-path', '--thread', '--return', '--handoff-context',
2854
+ '--content-type', '--mention', '--aun-path', '--thread', '--handoff-context',
2855
+ '--check-every', '--trigger-prompt',
2766
2856
  ]);
2767
2857
  const PROACTIVE_SEND_BOOLEAN_FLAGS = new Set([
2768
2858
  '--encrypt', '--no-encrypt', '--mention-all',
@@ -2850,6 +2940,13 @@ function hasExecutableProactiveSendPayload(argv, scope) {
2850
2940
  if (PROACTIVE_SEND_BOOLEAN_FLAGS.has(value))
2851
2941
  continue;
2852
2942
  if (!PROACTIVE_SEND_VALUE_FLAGS.has(value)) {
2943
+ // 未知 `--flag`(可能带值)不是正文;只有裸 token 计入正文。
2944
+ if (value.startsWith('--')) {
2945
+ const next = argv[index + 1];
2946
+ if (next !== undefined && !next.startsWith('--'))
2947
+ index++;
2948
+ continue;
2949
+ }
2853
2950
  positionals.push(value);
2854
2951
  continue;
2855
2952
  }
@@ -2860,8 +2957,6 @@ function hasExecutableProactiveSendPayload(argv, scope) {
2860
2957
  return false;
2861
2958
  if (value === '--app' || value === '--aun-path')
2862
2959
  return false;
2863
- if (value === '--return' && operand !== 'none' && operand !== 'required')
2864
- return false;
2865
2960
  if (value === '--format' && operand !== 'json')
2866
2961
  return false;
2867
2962
  if (value === '--link') {
@@ -2970,8 +3065,12 @@ export function isExactEvolcoreSendCommandForSession(toolName, input, channelId,
2970
3065
  && !!argv
2971
3066
  && hasExecutableProactiveSendPayload(argv, expectedScope);
2972
3067
  }
2973
- /** Diagnostic only: this never broadens the exact-send authorization check. */
2974
- export function describeProactiveSendCommandFailure(toolName, input) {
3068
+ /**
3069
+ * 逐项诊断 proactive 发送命令被拒的具体原因。只读 argv 与(可选的)会话参数,
3070
+ * 不扩大任何授权面:授权判定仍在 isExactEvolcoreSendCommandForSession /
3071
+ * isThreadRoutedEvolcoreSendCommand 中。
3072
+ */
3073
+ export function describeProactiveSendCommandFailure(toolName, input, session) {
2975
3074
  if (!['Bash', 'Shell', 'exec'].includes(toolName) || !input || typeof input !== 'object')
2976
3075
  return undefined;
2977
3076
  const commandText = [input.command, input.input, ...(Array.isArray(input.commandArgv) ? input.commandArgv : [])]
@@ -2985,5 +3084,114 @@ export function describeProactiveSendCommandFailure(toolName, input) {
2985
3084
  return `当前汇报命令未通过规范解析(${result.issue},${result.dialect},位置 ${result.offset});`
2986
3085
  + '请检查引号和转义,并将发送作为一条独立命令。';
2987
3086
  }
2988
- return '当前汇报命令不符合当前会话的发送要求;请核对发送者、私聊或群目标,以及正文和参数。';
3087
+ // 正文里只是提到 "ec msg send" 的其它命令(如 ec memory note "...")不是发送调用。
3088
+ const argv = result.argv;
3089
+ if (argv[0] !== 'ec' || (argv[1] !== 'msg' && argv[1] !== 'group') || argv[2] !== 'send')
3090
+ return undefined;
3091
+ // --help / -h 是只读的参数查询,不是发送调用,更不该计入 proactive 违规计数。
3092
+ if (argv.includes('--help') || argv.includes('-h'))
3093
+ return undefined;
3094
+ const scope = argv[1];
3095
+ const prefix = '发送命令被拒:';
3096
+ const expectedForm = `ec ${scope} send ${session?.selfAid ?? '<selfAid>'} `
3097
+ + (scope === 'group' ? `${session?.channelId ?? '<群AID>'}` : `${session?.channelId ?? '<对端AID>'}`)
3098
+ + ' "<正文>"';
3099
+ // 带 --thread 的发送走话题路由校验,允许省略 from/to;走到这里说明话题本身没解析通过。
3100
+ if (sendOptionValue(argv, '--thread')) {
3101
+ return `${prefix}带 --thread 的跨话题发送未通过校验——话题必须已存在于本端索引,from(若给出)必须是本人,`
3102
+ + `to(若给出)必须是该话题对端,且需携带 --check-every。可用 ec thread list <对方AID> 确认话题。`;
3103
+ }
3104
+ // 私聊/群聊用错了 scope。
3105
+ if (session?.chatType && (session.chatType === 'group') !== (scope === 'group')) {
3106
+ return session.chatType === 'group'
3107
+ ? `${prefix}当前是群聊会话,公开回复必须用 ec group send ${session.selfAid ?? '<selfAid>'} `
3108
+ + `${session.channelId ?? '<群AID>'} "<正文>";ec msg send 仅用于已获授权时私下联系本条消息发送者。`
3109
+ : `${prefix}当前是私聊会话,回复用 ${expectedForm};群回复才用 ec group send。`;
3110
+ }
3111
+ const from = argv[3];
3112
+ const to = argv[4];
3113
+ const fullFormHint = `${prefix}缺少发送者与目标。完整形式为 ${expectedForm};`
3114
+ + `省略 from/to 仅在带 --thread 的跨话题发送中允许,普通回复也可用 ec ctl send '<正文>' 继承当前会话。`;
3115
+ if (!from || from.startsWith('--')) {
3116
+ return fullFormHint;
3117
+ }
3118
+ if (!to || to.startsWith('--')) {
3119
+ return looksLikeAid(from)
3120
+ ? fullFormHint
3121
+ : `${prefix}缺少发送者与目标("${from}" 是正文而非 AID)。完整形式为 ${expectedForm};`
3122
+ + `省略 from/to 仅在带 --thread 的跨话题发送中允许,普通回复也可用 ec ctl send '<正文>' 继承当前会话。`;
3123
+ }
3124
+ if (!looksLikeAid(from) || !looksLikeAid(to)) {
3125
+ return `${prefix}发送者/目标不是合法 AID(收到 from="${from}" to="${to}")。完整形式为 ${expectedForm}。`;
3126
+ }
3127
+ if (session?.selfAid && from !== session.selfAid) {
3128
+ return `${prefix}发送者必须是本会话身份 ${session.selfAid},命令里写的是 ${from}。完整形式为 ${expectedForm}。`;
3129
+ }
3130
+ if (session?.channelId && to !== session.channelId) {
3131
+ return `${prefix}目标必须是当前会话${scope === 'group' ? '群' : '对端'} ${session.channelId},命令里写的是 ${to};`
3132
+ + `要联系其他对象请走 --thread 跨话题发送(ec thread list <对方AID> 选中话题后加 --check-every)。`;
3133
+ }
3134
+ return `${prefix}发送者与目标正确,但正文或参数无效——正文不能为空;--file/--text-from-file 属于文件发送,`
3135
+ + '不在普通回复放行范围;--link 的值必须是 http(s) URL;--payload 的值必须是 JSON 对象。';
3136
+ }
3137
+ /** 与 CLI `isValidAid` 同一规则;parser 不依赖 AID 模块,避免引入身份存储副作用。 */
3138
+ function looksLikeAid(value) {
3139
+ if (!value)
3140
+ return false;
3141
+ const labels = value.split('.');
3142
+ return labels.length >= 3 && labels.every(label => /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(label));
3143
+ }
3144
+ function sendOptionValue(argv, flag) {
3145
+ for (let index = 3; index < argv.length; index++) {
3146
+ if (argv[index] === '--')
3147
+ return undefined;
3148
+ if (argv[index] === flag) {
3149
+ const value = argv[index + 1];
3150
+ return value && !value.startsWith('--') ? value : undefined;
3151
+ }
3152
+ }
3153
+ return undefined;
3154
+ }
3155
+ /**
3156
+ * 带 `--thread` 的跨话题发送:目标由话题推断,不受「只能发给当前对端/群」限制。
3157
+ *
3158
+ * 支持完整形式 `ec msg|group send <self> <target> ... --thread <id>`,以及私聊
3159
+ * 简化形式 `ec msg send "<text>" --thread <id>`(目标取话题 peer)。
3160
+ * `resolveThread` 必须校验话题存在于本端索引、scope 与话题类型一致,且给出
3161
+ * target 时与话题 peer 相同;这里只做语法与发送者校验。
3162
+ */
3163
+ export function isThreadRoutedEvolcoreSendCommand(toolName, input, selfAid, resolveThread) {
3164
+ if (!selfAid || !['Bash', 'Shell', 'exec'].includes(toolName) || !input || typeof input !== 'object')
3165
+ return false;
3166
+ const result = parseProactiveToolCommand(toolName, input);
3167
+ if (!result?.ok)
3168
+ return false;
3169
+ const argv = result.argv;
3170
+ if (argv[0] !== 'ec' || (argv[1] !== 'msg' && argv[1] !== 'group') || argv[2] !== 'send')
3171
+ return false;
3172
+ const scope = argv[1];
3173
+ const threadId = sendOptionValue(argv, '--thread');
3174
+ if (!threadId)
3175
+ return false;
3176
+ const self = selfAid.replace(/^@/, '');
3177
+ const hasFrom = looksLikeAid(argv[3]);
3178
+ const hasTo = hasFrom && looksLikeAid(argv[4]);
3179
+ let normalized;
3180
+ let target;
3181
+ if (hasFrom && hasTo) {
3182
+ if (argv[3] !== self)
3183
+ return false;
3184
+ target = argv[4];
3185
+ normalized = argv;
3186
+ }
3187
+ else if (!hasFrom && scope === 'msg') {
3188
+ // 与 CLI 托管推断一致:from/to 均省略时由 selfAid 与话题 peer 补齐。
3189
+ normalized = ['ec', scope, 'send', self, '<thread-peer>', ...argv.slice(3)];
3190
+ }
3191
+ else {
3192
+ return false;
3193
+ }
3194
+ if (!hasExecutableProactiveSendPayload(normalized, scope))
3195
+ return false;
3196
+ return resolveThread(scope, threadId, target);
2989
3197
  }
@@ -938,7 +938,11 @@ export function lClassGrantIncludesProtectedPath(value, options = {}) {
938
938
  // A grant covering an Agent root also covers its L-class configuration.
939
939
  if (segments.length <= 2)
940
940
  return true;
941
- return ['config.json', 'contact.json', 'contact-operations.jsonl', 'relations', 'roles', 'triggers']
941
+ // relations/ 下的 L 类只有 relations/<peer>/config.json:只有范围是 relations/ 或
942
+ // relations/<peer> 本身时才覆盖它,更深的路径(如 relations/<peer>/memory/…)不会。
943
+ if (segments[2] === 'relations')
944
+ return segments.length <= 4;
945
+ return ['config.json', 'contact.json', 'contact-operations.jsonl', 'roles', 'triggers']
942
946
  .includes(segments[2]);
943
947
  }
944
948
  return false;