evolcore 0.0.31 → 0.0.33

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 (179) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/bin/install-codex-managed-hooks.mjs +124 -37
  3. package/dist/agents/claude-runner.js +29 -2
  4. package/dist/agents/codex/app-server-client.js +3 -0
  5. package/dist/agents/codex/diagnostics.js +76 -44
  6. package/dist/agents/codex-runner.js +51 -24
  7. package/dist/agents/ecagent-runner.js +9 -13
  8. package/dist/agents/gemini-runner.js +4 -0
  9. package/dist/aun/aid/store.js +0 -81
  10. package/dist/aun/group/fs-download.js +1 -10
  11. package/dist/aun/group/operations.js +0 -1
  12. package/dist/aun/msg/history.js +1 -0
  13. package/dist/aun/msg/managed-operation.js +130 -32
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/aun/service-proxy.js +12 -18
  17. package/dist/aun/storage/index.js +29 -0
  18. package/dist/channels/aun.js +38 -137
  19. package/dist/channels/daemon.js +257 -2
  20. package/dist/cli/agent-command.js +64 -31
  21. package/dist/cli/agent-create.js +43 -24
  22. package/dist/cli/agent.js +16 -70
  23. package/dist/cli/aun-commands.js +512 -32
  24. package/dist/cli/contact.js +8 -2
  25. package/dist/cli/ctl-command.js +1 -2
  26. package/dist/cli/daemon-commands.js +82 -13
  27. package/dist/cli/debug-mode.js +154 -0
  28. package/dist/cli/diag.js +2 -2
  29. package/dist/cli/handoff-command.js +18 -5
  30. package/dist/cli/index.js +81 -64
  31. package/dist/cli/init.js +2 -2
  32. package/dist/cli/managed-command-guard.js +5 -0
  33. package/dist/cli/memory-command.js +93 -12
  34. package/dist/cli/model.js +14 -138
  35. package/dist/cli/queue-command.js +51 -0
  36. package/dist/cli/response.js +5 -54
  37. package/dist/cli/session-command.js +194 -0
  38. package/dist/cli/skill-command.js +85 -3
  39. package/dist/cli/stats.js +10 -4
  40. package/dist/cli/task-command.js +1 -0
  41. package/dist/cli/task-context.js +16 -0
  42. package/dist/cli/thread-command.js +620 -0
  43. package/dist/cli/trigger-command.js +76 -50
  44. package/dist/config/builtin-roles.js +9 -7
  45. package/dist/config/config-field-policy.js +2 -3
  46. package/dist/config/config-manager.js +7 -2
  47. package/dist/config/resolved-config-op.js +3 -0
  48. package/dist/config-store.js +15 -0
  49. package/dist/core/agent-ipc-registration.js +65 -22
  50. package/dist/core/auth/agent-delegation.js +42 -2
  51. package/dist/core/auth/debug-context.js +139 -0
  52. package/dist/core/auth/delegation-authorization.js +1 -0
  53. package/dist/core/auth/operation-authorizer.js +24 -1
  54. package/dist/core/auth/operation-catalog.js +44 -3
  55. package/dist/core/background-session/session-cleanup.js +212 -0
  56. package/dist/core/background-session/types.js +10 -0
  57. package/dist/core/bootstrap-messages.js +1 -1
  58. package/dist/core/bootstrap-service.js +6 -3
  59. package/dist/core/command/command-handler.js +224 -45
  60. package/dist/core/command/diag-service.js +5 -1
  61. package/dist/core/command/memory-slash.js +6 -2
  62. package/dist/core/command/menu-catalog.js +48 -1
  63. package/dist/core/command/menu-handler.js +92 -16
  64. package/dist/core/command/menu-protocol.js +24 -1
  65. package/dist/core/command/skill-slash.js +322 -0
  66. package/dist/core/command/slash-gate.js +6 -4
  67. package/dist/core/command/slash-handler.js +193 -13
  68. package/dist/core/command/topic-slash.js +528 -0
  69. package/dist/core/evolagent-registry.js +20 -5
  70. package/dist/core/handoff/ipc-registration.js +38 -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 +210 -9
  74. package/dist/core/memory/consolidation-context.js +49 -16
  75. package/dist/core/memory/consolidation-ops-records.js +35 -0
  76. package/dist/core/memory/consolidation-queue-format.js +21 -0
  77. package/dist/core/memory/consolidation.js +174 -180
  78. package/dist/core/message/aun-ipc-sender.js +207 -8
  79. package/dist/core/message/im-renderer.js +149 -6
  80. package/dist/core/message/message-bridge.js +13 -3
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +131 -3
  83. package/dist/core/message/peer-mode.js +6 -1
  84. package/dist/core/message/response-engine.js +379 -69
  85. package/dist/core/message/subtask-tool-projection.js +140 -0
  86. package/dist/core/model/catalog-service.js +17 -12
  87. package/dist/core/permission/ec-command-parser.js +126 -7
  88. package/dist/core/permission/sandbox-runtime.js +14 -1
  89. package/dist/core/session/session-idle-monitor.js +249 -22
  90. package/dist/core/session/session-manager.js +54 -9
  91. package/dist/core/skill/command-parser.js +13 -0
  92. package/dist/core/skill/index-generator.js +4 -1
  93. package/dist/core/skill/startup.js +41 -0
  94. package/dist/core/skill/switch-resolver.js +55 -29
  95. package/dist/core/task/task-recovery-service.js +2 -0
  96. package/dist/core/task/task-resume-operation.js +11 -0
  97. package/dist/core/thread/command-parser.js +125 -0
  98. package/dist/core/thread/index.js +1 -0
  99. package/dist/core/thread/ipc-operation.js +95 -0
  100. package/dist/core/thread/thread-context-storage.js +131 -0
  101. package/dist/core/thread/thread-operation.js +258 -0
  102. package/dist/core/thread/thread-route.js +33 -0
  103. package/dist/core/thread/thread-service.js +777 -0
  104. package/dist/core/thread/types.js +1 -0
  105. package/dist/eck/message-renderer.js +35 -0
  106. package/dist/index.js +10 -7
  107. package/dist/ipc.js +234 -17
  108. package/dist/paths.js +23 -0
  109. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  110. package/dist/trigger/authorization.js +59 -29
  111. package/dist/trigger/feedback.js +7 -1
  112. package/dist/trigger/ipc-service.js +49 -35
  113. package/dist/trigger/parser.js +63 -1
  114. package/dist/trigger/script-sandbox.js +58 -77
  115. package/dist/trigger/validation.js +14 -0
  116. package/dist/utils/error-utils.js +22 -1
  117. package/kits/docs/AUTHORING.md +1 -1
  118. package/kits/docs/channels/aun.md +8 -0
  119. package/kits/docs/context-assembly.md +1 -0
  120. package/kits/docs/evolcore/INDEX.md +3 -2
  121. package/kits/docs/evolcore/agent-create.md +39 -28
  122. package/kits/docs/evolcore/agent.md +19 -4
  123. package/kits/docs/evolcore/ec-capabilities.md +4 -0
  124. package/kits/docs/evolcore/group-collaboration.md +3 -5
  125. package/kits/docs/evolcore/group.md +3 -4
  126. package/kits/docs/evolcore/memory-consolidation.md +1 -1
  127. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  128. package/kits/docs/evolcore/menu-protocol.md +7 -0
  129. package/kits/docs/evolcore/msg.md +5 -8
  130. package/kits/docs/evolcore/task.md +2 -0
  131. package/kits/docs/evolcore/thread-chain.md +267 -0
  132. package/kits/docs/evolcore/trigger.md +2 -2
  133. package/kits/docs/prompt-loading-architecture.md +1 -0
  134. package/kits/eck_manifest.json +61 -26
  135. package/kits/eck_message_manifest.json +169 -10
  136. package/kits/rules/01-overview.md +0 -9
  137. package/kits/rules/05-venue.md +1 -1
  138. package/kits/rules/06-channel.md +7 -1
  139. package/kits/rules/07-memory-and-thread.md +202 -0
  140. package/kits/schemas/relation-config.schema.9.json +18 -3
  141. package/kits/schemas/role-config.schema.1.json +6 -6
  142. package/kits/schemas/single-session.schema.1.json +6 -6
  143. package/kits/schemas/single-session.schema.2.json +6 -6
  144. package/kits/schemas/single-session.schema.3.json +6 -6
  145. package/kits/skills/.switches.json +12 -1
  146. package/kits/skills/ec-group/SKILL.md +234 -0
  147. package/kits/skills/ec-skill/SKILL.md +10 -1
  148. package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
  149. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  150. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  151. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  152. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  153. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  154. package/kits/skills/thread-ops/SKILL.md +492 -0
  155. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  156. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  157. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  158. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  159. package/kits/templates/bootstrap-welcome.md +7 -7
  160. package/kits/templates/memory-consolidation/forget.md +5 -0
  161. package/kits/templates/memory-consolidation/generalize.md +5 -0
  162. package/kits/templates/memory-consolidation/profile.md +5 -0
  163. package/kits/templates/memory-consolidation/promote.md +5 -0
  164. package/kits/templates/message-fragments/item.md +2 -1
  165. package/kits/templates/message-fragments/thread-context.md +6 -0
  166. package/kits/templates/message-fragments/trigger-session.md +2 -0
  167. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  168. package/kits/templates/roles/member.json +2 -0
  169. package/kits/templates/roles/visitor.json +2 -0
  170. package/kits/templates/system-fragments/bootstrap.md +66 -72
  171. package/kits/templates/system-fragments/channel.md +3 -6
  172. package/kits/templates/system-fragments/commands.md +2 -1
  173. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  174. package/kits/templates/system-fragments/reply.md +3 -2
  175. package/kits/templates/system-fragments/session.md +5 -14
  176. package/package.json +3 -1
  177. package/kits/docs/evolcore/group-fs.md +0 -17
  178. package/kits/docs/venues/aun-group.md +0 -45
  179. 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. */
@@ -2762,7 +2762,8 @@ function findSingleFileOption(argv, startIndex) {
2762
2762
  const PROACTIVE_SEND_VALUE_FLAGS = new Set([
2763
2763
  '--format', '--app', '--file', '--text-from-file', '--link', '--payload',
2764
2764
  '--title', '--description', '--text', '--transcript', '--as',
2765
- '--content-type', '--mention', '--aun-path', '--thread', '--return', '--handoff-context',
2765
+ '--content-type', '--mention', '--aun-path', '--thread', '--handoff-context',
2766
+ '--check-every', '--trigger-prompt',
2766
2767
  ]);
2767
2768
  const PROACTIVE_SEND_BOOLEAN_FLAGS = new Set([
2768
2769
  '--encrypt', '--no-encrypt', '--mention-all',
@@ -2850,6 +2851,13 @@ function hasExecutableProactiveSendPayload(argv, scope) {
2850
2851
  if (PROACTIVE_SEND_BOOLEAN_FLAGS.has(value))
2851
2852
  continue;
2852
2853
  if (!PROACTIVE_SEND_VALUE_FLAGS.has(value)) {
2854
+ // 未知 `--flag`(可能带值)不是正文;只有裸 token 计入正文。
2855
+ if (value.startsWith('--')) {
2856
+ const next = argv[index + 1];
2857
+ if (next !== undefined && !next.startsWith('--'))
2858
+ index++;
2859
+ continue;
2860
+ }
2853
2861
  positionals.push(value);
2854
2862
  continue;
2855
2863
  }
@@ -2860,8 +2868,6 @@ function hasExecutableProactiveSendPayload(argv, scope) {
2860
2868
  return false;
2861
2869
  if (value === '--app' || value === '--aun-path')
2862
2870
  return false;
2863
- if (value === '--return' && operand !== 'none' && operand !== 'required')
2864
- return false;
2865
2871
  if (value === '--format' && operand !== 'json')
2866
2872
  return false;
2867
2873
  if (value === '--link') {
@@ -2970,8 +2976,12 @@ export function isExactEvolcoreSendCommandForSession(toolName, input, channelId,
2970
2976
  && !!argv
2971
2977
  && hasExecutableProactiveSendPayload(argv, expectedScope);
2972
2978
  }
2973
- /** Diagnostic only: this never broadens the exact-send authorization check. */
2974
- export function describeProactiveSendCommandFailure(toolName, input) {
2979
+ /**
2980
+ * 逐项诊断 proactive 发送命令被拒的具体原因。只读 argv 与(可选的)会话参数,
2981
+ * 不扩大任何授权面:授权判定仍在 isExactEvolcoreSendCommandForSession /
2982
+ * isThreadRoutedEvolcoreSendCommand 中。
2983
+ */
2984
+ export function describeProactiveSendCommandFailure(toolName, input, session) {
2975
2985
  if (!['Bash', 'Shell', 'exec'].includes(toolName) || !input || typeof input !== 'object')
2976
2986
  return undefined;
2977
2987
  const commandText = [input.command, input.input, ...(Array.isArray(input.commandArgv) ? input.commandArgv : [])]
@@ -2985,5 +2995,114 @@ export function describeProactiveSendCommandFailure(toolName, input) {
2985
2995
  return `当前汇报命令未通过规范解析(${result.issue},${result.dialect},位置 ${result.offset});`
2986
2996
  + '请检查引号和转义,并将发送作为一条独立命令。';
2987
2997
  }
2988
- return '当前汇报命令不符合当前会话的发送要求;请核对发送者、私聊或群目标,以及正文和参数。';
2998
+ // 正文里只是提到 "ec msg send" 的其它命令(如 ec memory note "...")不是发送调用。
2999
+ const argv = result.argv;
3000
+ if (argv[0] !== 'ec' || (argv[1] !== 'msg' && argv[1] !== 'group') || argv[2] !== 'send')
3001
+ return undefined;
3002
+ // --help / -h 是只读的参数查询,不是发送调用,更不该计入 proactive 违规计数。
3003
+ if (argv.includes('--help') || argv.includes('-h'))
3004
+ return undefined;
3005
+ const scope = argv[1];
3006
+ const prefix = '发送命令被拒:';
3007
+ const expectedForm = `ec ${scope} send ${session?.selfAid ?? '<selfAid>'} `
3008
+ + (scope === 'group' ? `${session?.channelId ?? '<群AID>'}` : `${session?.channelId ?? '<对端AID>'}`)
3009
+ + ' "<正文>"';
3010
+ // 带 --thread 的发送走话题路由校验,允许省略 from/to;走到这里说明话题本身没解析通过。
3011
+ if (sendOptionValue(argv, '--thread')) {
3012
+ return `${prefix}带 --thread 的跨话题发送未通过校验——话题必须已存在于本端索引,from(若给出)必须是本人,`
3013
+ + `to(若给出)必须是该话题对端,且需携带 --check-every。可用 ec thread list <对方AID> 确认话题。`;
3014
+ }
3015
+ // 私聊/群聊用错了 scope。
3016
+ if (session?.chatType && (session.chatType === 'group') !== (scope === 'group')) {
3017
+ return session.chatType === 'group'
3018
+ ? `${prefix}当前是群聊会话,公开回复必须用 ec group send ${session.selfAid ?? '<selfAid>'} `
3019
+ + `${session.channelId ?? '<群AID>'} "<正文>";ec msg send 仅用于已获授权时私下联系本条消息发送者。`
3020
+ : `${prefix}当前是私聊会话,回复用 ${expectedForm};群回复才用 ec group send。`;
3021
+ }
3022
+ const from = argv[3];
3023
+ const to = argv[4];
3024
+ const fullFormHint = `${prefix}缺少发送者与目标。完整形式为 ${expectedForm};`
3025
+ + '省略 from/to 仅在带 --thread 的跨话题发送中允许,普通回复也可用 ec ctl send "<正文>" 继承当前会话。';
3026
+ if (!from || from.startsWith('--')) {
3027
+ return fullFormHint;
3028
+ }
3029
+ if (!to || to.startsWith('--')) {
3030
+ return looksLikeAid(from)
3031
+ ? fullFormHint
3032
+ : `${prefix}缺少发送者与目标("${from}" 是正文而非 AID)。完整形式为 ${expectedForm};`
3033
+ + '省略 from/to 仅在带 --thread 的跨话题发送中允许,普通回复也可用 ec ctl send "<正文>" 继承当前会话。';
3034
+ }
3035
+ if (!looksLikeAid(from) || !looksLikeAid(to)) {
3036
+ return `${prefix}发送者/目标不是合法 AID(收到 from="${from}" to="${to}")。完整形式为 ${expectedForm}。`;
3037
+ }
3038
+ if (session?.selfAid && from !== session.selfAid) {
3039
+ return `${prefix}发送者必须是本会话身份 ${session.selfAid},命令里写的是 ${from}。完整形式为 ${expectedForm}。`;
3040
+ }
3041
+ if (session?.channelId && to !== session.channelId) {
3042
+ return `${prefix}目标必须是当前会话${scope === 'group' ? '群' : '对端'} ${session.channelId},命令里写的是 ${to};`
3043
+ + `要联系其他对象请走 --thread 跨话题发送(ec thread list <对方AID> 选中话题后加 --check-every)。`;
3044
+ }
3045
+ return `${prefix}发送者与目标正确,但正文或参数无效——正文不能为空;--file/--text-from-file 属于文件发送,`
3046
+ + '不在普通回复放行范围;--link 的值必须是 http(s) URL;--payload 的值必须是 JSON 对象。';
3047
+ }
3048
+ /** 与 CLI `isValidAid` 同一规则;parser 不依赖 AID 模块,避免引入身份存储副作用。 */
3049
+ function looksLikeAid(value) {
3050
+ if (!value)
3051
+ return false;
3052
+ const labels = value.split('.');
3053
+ return labels.length >= 3 && labels.every(label => /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(label));
3054
+ }
3055
+ function sendOptionValue(argv, flag) {
3056
+ for (let index = 3; index < argv.length; index++) {
3057
+ if (argv[index] === '--')
3058
+ return undefined;
3059
+ if (argv[index] === flag) {
3060
+ const value = argv[index + 1];
3061
+ return value && !value.startsWith('--') ? value : undefined;
3062
+ }
3063
+ }
3064
+ return undefined;
3065
+ }
3066
+ /**
3067
+ * 带 `--thread` 的跨话题发送:目标由话题推断,不受「只能发给当前对端/群」限制。
3068
+ *
3069
+ * 支持完整形式 `ec msg|group send <self> <target> ... --thread <id>`,以及私聊
3070
+ * 简化形式 `ec msg send "<text>" --thread <id>`(目标取话题 peer)。
3071
+ * `resolveThread` 必须校验话题存在于本端索引、scope 与话题类型一致,且给出
3072
+ * target 时与话题 peer 相同;这里只做语法与发送者校验。
3073
+ */
3074
+ export function isThreadRoutedEvolcoreSendCommand(toolName, input, selfAid, resolveThread) {
3075
+ if (!selfAid || !['Bash', 'Shell', 'exec'].includes(toolName) || !input || typeof input !== 'object')
3076
+ return false;
3077
+ const result = parseProactiveToolCommand(toolName, input);
3078
+ if (!result?.ok)
3079
+ return false;
3080
+ const argv = result.argv;
3081
+ if (argv[0] !== 'ec' || (argv[1] !== 'msg' && argv[1] !== 'group') || argv[2] !== 'send')
3082
+ return false;
3083
+ const scope = argv[1];
3084
+ const threadId = sendOptionValue(argv, '--thread');
3085
+ if (!threadId)
3086
+ return false;
3087
+ const self = selfAid.replace(/^@/, '');
3088
+ const hasFrom = looksLikeAid(argv[3]);
3089
+ const hasTo = hasFrom && looksLikeAid(argv[4]);
3090
+ let normalized;
3091
+ let target;
3092
+ if (hasFrom && hasTo) {
3093
+ if (argv[3] !== self)
3094
+ return false;
3095
+ target = argv[4];
3096
+ normalized = argv;
3097
+ }
3098
+ else if (!hasFrom && scope === 'msg') {
3099
+ // 与 CLI 托管推断一致:from/to 均省略时由 selfAid 与话题 peer 补齐。
3100
+ normalized = ['ec', scope, 'send', self, '<thread-peer>', ...argv.slice(3)];
3101
+ }
3102
+ else {
3103
+ return false;
3104
+ }
3105
+ if (!hasExecutableProactiveSendPayload(normalized, scope))
3106
+ return false;
3107
+ return resolveThread(scope, threadId, target);
2989
3108
  }
@@ -563,6 +563,14 @@ function appendLClassReadOnlyBinds(args, root, options = {}) {
563
563
  args.push('--ro-bind', target.path, target.path);
564
564
  }
565
565
  }
566
+ function appendLClassMasks(args, root, options = {}) {
567
+ for (const target of getExistingLClassReadOnlyTargets(root, options)) {
568
+ if (target.kind === 'directory')
569
+ pushMaskedDirectory(args, target.path);
570
+ else
571
+ pushMaskedFile(args, target.path);
572
+ }
573
+ }
566
574
  export function ensureCodexManagedMountpoint(systemConfigDirectory = '/etc/codex', effectiveUid = typeof process.getuid === 'function' ? process.getuid() : undefined) {
567
575
  let systemConfigStat;
568
576
  try {
@@ -691,7 +699,12 @@ export function buildBubblewrapCommand(executable, executableArgs, options) {
691
699
  pushMaskedFile(args, resolved);
692
700
  }
693
701
  }
694
- appendLClassReadOnlyBinds(args, root, options);
702
+ if (options.denyLClassRead) {
703
+ appendLClassMasks(args, root, options);
704
+ }
705
+ else {
706
+ appendLClassReadOnlyBinds(args, root, options);
707
+ }
695
708
  appendHClassMasks(args, root, true, options);
696
709
  if (options.exposeDaemonIpc !== false)
697
710
  appendDaemonIpcSocket(args, root);