evolcore 0.0.8 → 0.0.10

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 (215) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +37 -3
  3. package/dist/agents/baseagent.js +111 -19
  4. package/dist/agents/claude-runner.js +30 -2
  5. package/dist/agents/codex-app-server-client.js +31 -0
  6. package/dist/agents/codex-runner.js +3 -0
  7. package/dist/aun/msg/group.js +9 -9
  8. package/dist/aun/msg/p2p.js +11 -6
  9. package/dist/aun/msg/upload.js +43 -29
  10. package/dist/aun/outbox.js +48 -5
  11. package/dist/channels/aun.js +199 -50
  12. package/dist/cli/aun-commands.js +10 -7
  13. package/dist/cli/daemon-commands.js +19 -4
  14. package/dist/cli/index.js +11 -1
  15. package/dist/cli/init.js +103 -4
  16. package/dist/cli/model.js +5 -5
  17. package/dist/config/builtin-role-templates.js +4 -3
  18. package/dist/config/config-field-policy.js +6 -2
  19. package/dist/config/config-manager.js +48 -11
  20. package/dist/config/contact-book-store.js +1 -1
  21. package/dist/config/role-schema.js +18 -3
  22. package/dist/config/role-service.js +3 -3
  23. package/dist/config/role-store.js +4 -3
  24. package/dist/config-store.js +4 -0
  25. package/dist/core/auth/operation-authorizer.js +3 -1
  26. package/dist/core/bootstrap-service.js +0 -12
  27. package/dist/core/channel-loader.js +5 -1
  28. package/dist/core/command/command-handler.js +1 -1
  29. package/dist/core/command/evol-menu-version-gate.js +3 -2
  30. package/dist/core/command/menu-handler.js +33 -22
  31. package/dist/core/command/menu-protocol.js +7 -4
  32. package/dist/core/command/slash-handler.js +4 -4
  33. package/dist/core/data-migration.js +79 -27
  34. package/dist/core/inference/text-inference.js +18 -5
  35. package/dist/core/message/message-bridge.js +17 -6
  36. package/dist/core/message/response-engine.js +22 -3
  37. package/dist/core/model/config-scope.js +3 -3
  38. package/dist/core/permission/tool-policy.js +18 -0
  39. package/dist/index.js +43 -26
  40. package/dist/ipc.js +1 -1
  41. package/dist/product.js +1 -0
  42. package/dist/utils/autostart.js +27 -0
  43. package/dist/utils/ecweb-utils.js +15 -2
  44. package/dist/utils/macos-autostart.js +179 -0
  45. package/dist/utils/windows-autostart.js +130 -0
  46. package/ecagent/dist/agent-loop.d.ts +1 -1
  47. package/ecagent/dist/agent-loop.js +1 -1
  48. package/ecagent/dist/agent.d.ts +1 -1
  49. package/ecagent/dist/agent.js +1 -1
  50. package/ecagent/dist/harness/agent-harness.d.ts +1 -1
  51. package/ecagent/dist/harness/compaction/branch-summarization.d.ts +1 -1
  52. package/ecagent/dist/harness/compaction/compaction.d.ts +1 -1
  53. package/ecagent/dist/harness/compaction/utils.d.ts +1 -1
  54. package/ecagent/dist/harness/messages.d.ts +1 -1
  55. package/ecagent/dist/harness/session/session.d.ts +1 -1
  56. package/ecagent/dist/harness/types.d.ts +1 -1
  57. package/ecagent/dist/proxy.d.ts +1 -1
  58. package/ecagent/dist/proxy.js +1 -1
  59. package/ecagent/dist/types.d.ts +1 -1
  60. package/kits/migrations/migrate-role-config-v5.mjs +27 -2
  61. package/kits/schemas/agent-config.schema.10.json +19 -1
  62. package/kits/schemas/daemon.schema.4.json +4 -0
  63. package/kits/schemas/defaults.schema.4.json +15 -1
  64. package/kits/schemas/relation-config.schema.8.json +22 -1
  65. package/kits/schemas/role-config.schema.1.json +2 -2
  66. package/package.json +2 -4
  67. package/ecagent/dist/agent-loop.d.ts.map +0 -1
  68. package/ecagent/dist/agent-loop.js.map +0 -1
  69. package/ecagent/dist/agent.d.ts.map +0 -1
  70. package/ecagent/dist/agent.js.map +0 -1
  71. package/ecagent/dist/harness/agent-harness.d.ts.map +0 -1
  72. package/ecagent/dist/harness/agent-harness.js.map +0 -1
  73. package/ecagent/dist/harness/compaction/branch-summarization.d.ts.map +0 -1
  74. package/ecagent/dist/harness/compaction/branch-summarization.js.map +0 -1
  75. package/ecagent/dist/harness/compaction/compaction.d.ts.map +0 -1
  76. package/ecagent/dist/harness/compaction/compaction.js.map +0 -1
  77. package/ecagent/dist/harness/compaction/utils.d.ts.map +0 -1
  78. package/ecagent/dist/harness/compaction/utils.js.map +0 -1
  79. package/ecagent/dist/harness/env/nodejs.d.ts.map +0 -1
  80. package/ecagent/dist/harness/env/nodejs.js.map +0 -1
  81. package/ecagent/dist/harness/messages.d.ts.map +0 -1
  82. package/ecagent/dist/harness/messages.js.map +0 -1
  83. package/ecagent/dist/harness/prompt-templates.d.ts.map +0 -1
  84. package/ecagent/dist/harness/prompt-templates.js.map +0 -1
  85. package/ecagent/dist/harness/session/jsonl-repo.d.ts.map +0 -1
  86. package/ecagent/dist/harness/session/jsonl-repo.js.map +0 -1
  87. package/ecagent/dist/harness/session/jsonl-storage.d.ts.map +0 -1
  88. package/ecagent/dist/harness/session/jsonl-storage.js.map +0 -1
  89. package/ecagent/dist/harness/session/memory-repo.d.ts.map +0 -1
  90. package/ecagent/dist/harness/session/memory-repo.js.map +0 -1
  91. package/ecagent/dist/harness/session/memory-storage.d.ts.map +0 -1
  92. package/ecagent/dist/harness/session/memory-storage.js.map +0 -1
  93. package/ecagent/dist/harness/session/repo-utils.d.ts.map +0 -1
  94. package/ecagent/dist/harness/session/repo-utils.js.map +0 -1
  95. package/ecagent/dist/harness/session/session.d.ts.map +0 -1
  96. package/ecagent/dist/harness/session/session.js.map +0 -1
  97. package/ecagent/dist/harness/session/uuid.d.ts.map +0 -1
  98. package/ecagent/dist/harness/session/uuid.js.map +0 -1
  99. package/ecagent/dist/harness/skills.d.ts.map +0 -1
  100. package/ecagent/dist/harness/skills.js.map +0 -1
  101. package/ecagent/dist/harness/system-prompt.d.ts.map +0 -1
  102. package/ecagent/dist/harness/system-prompt.js.map +0 -1
  103. package/ecagent/dist/harness/types.d.ts.map +0 -1
  104. package/ecagent/dist/harness/types.js.map +0 -1
  105. package/ecagent/dist/harness/utils/shell-output.d.ts.map +0 -1
  106. package/ecagent/dist/harness/utils/shell-output.js.map +0 -1
  107. package/ecagent/dist/harness/utils/truncate.d.ts.map +0 -1
  108. package/ecagent/dist/harness/utils/truncate.js.map +0 -1
  109. package/ecagent/dist/index.d.ts.map +0 -1
  110. package/ecagent/dist/index.js.map +0 -1
  111. package/ecagent/dist/node.d.ts.map +0 -1
  112. package/ecagent/dist/node.js.map +0 -1
  113. package/ecagent/dist/proxy.d.ts.map +0 -1
  114. package/ecagent/dist/proxy.js.map +0 -1
  115. package/ecagent/dist/runtime/api/lazy.d.ts.map +0 -1
  116. package/ecagent/dist/runtime/api/lazy.js.map +0 -1
  117. package/ecagent/dist/runtime/api/pi-messages.d.ts.map +0 -1
  118. package/ecagent/dist/runtime/api/pi-messages.js.map +0 -1
  119. package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts.map +0 -1
  120. package/ecagent/dist/runtime/api/pi-messages.lazy.js.map +0 -1
  121. package/ecagent/dist/runtime/auth/context.d.ts.map +0 -1
  122. package/ecagent/dist/runtime/auth/context.js.map +0 -1
  123. package/ecagent/dist/runtime/auth/credential-store.d.ts.map +0 -1
  124. package/ecagent/dist/runtime/auth/credential-store.js.map +0 -1
  125. package/ecagent/dist/runtime/auth/helpers.d.ts.map +0 -1
  126. package/ecagent/dist/runtime/auth/helpers.js.map +0 -1
  127. package/ecagent/dist/runtime/auth/resolve.d.ts.map +0 -1
  128. package/ecagent/dist/runtime/auth/resolve.js.map +0 -1
  129. package/ecagent/dist/runtime/auth/types.d.ts.map +0 -1
  130. package/ecagent/dist/runtime/auth/types.js.map +0 -1
  131. package/ecagent/dist/runtime/compat.d.ts.map +0 -1
  132. package/ecagent/dist/runtime/compat.js.map +0 -1
  133. package/ecagent/dist/runtime/index.d.ts.map +0 -1
  134. package/ecagent/dist/runtime/index.js.map +0 -1
  135. package/ecagent/dist/runtime/models.d.ts.map +0 -1
  136. package/ecagent/dist/runtime/models.js.map +0 -1
  137. package/ecagent/dist/runtime/oauth.d.ts.map +0 -1
  138. package/ecagent/dist/runtime/oauth.js.map +0 -1
  139. package/ecagent/dist/runtime/providers/faux.d.ts.map +0 -1
  140. package/ecagent/dist/runtime/providers/faux.js.map +0 -1
  141. package/ecagent/dist/runtime/types.d.ts.map +0 -1
  142. package/ecagent/dist/runtime/types.js.map +0 -1
  143. package/ecagent/dist/runtime/utils/diagnostics.d.ts.map +0 -1
  144. package/ecagent/dist/runtime/utils/diagnostics.js.map +0 -1
  145. package/ecagent/dist/runtime/utils/event-stream.d.ts.map +0 -1
  146. package/ecagent/dist/runtime/utils/event-stream.js.map +0 -1
  147. package/ecagent/dist/runtime/utils/headers.d.ts.map +0 -1
  148. package/ecagent/dist/runtime/utils/headers.js.map +0 -1
  149. package/ecagent/dist/runtime/utils/json-parse.d.ts.map +0 -1
  150. package/ecagent/dist/runtime/utils/json-parse.js.map +0 -1
  151. package/ecagent/dist/runtime/utils/oauth/types.d.ts.map +0 -1
  152. package/ecagent/dist/runtime/utils/oauth/types.js.map +0 -1
  153. package/ecagent/dist/runtime/utils/overflow.d.ts.map +0 -1
  154. package/ecagent/dist/runtime/utils/overflow.js.map +0 -1
  155. package/ecagent/dist/runtime/utils/provider-env.d.ts.map +0 -1
  156. package/ecagent/dist/runtime/utils/provider-env.js.map +0 -1
  157. package/ecagent/dist/runtime/utils/retry.d.ts.map +0 -1
  158. package/ecagent/dist/runtime/utils/retry.js.map +0 -1
  159. package/ecagent/dist/runtime/utils/typebox-helpers.d.ts.map +0 -1
  160. package/ecagent/dist/runtime/utils/typebox-helpers.js.map +0 -1
  161. package/ecagent/dist/runtime/utils/validation.d.ts.map +0 -1
  162. package/ecagent/dist/runtime/utils/validation.js.map +0 -1
  163. package/ecagent/dist/types.d.ts.map +0 -1
  164. package/ecagent/dist/types.js.map +0 -1
  165. package/ecagent/package.json +0 -93
  166. package/ecagent/src/agent-loop.ts +0 -792
  167. package/ecagent/src/agent.ts +0 -575
  168. package/ecagent/src/harness/agent-harness.ts +0 -1029
  169. package/ecagent/src/harness/compaction/branch-summarization.ts +0 -261
  170. package/ecagent/src/harness/compaction/compaction.ts +0 -753
  171. package/ecagent/src/harness/compaction/utils.ts +0 -144
  172. package/ecagent/src/harness/env/nodejs.ts +0 -569
  173. package/ecagent/src/harness/messages.ts +0 -164
  174. package/ecagent/src/harness/prompt-templates.ts +0 -267
  175. package/ecagent/src/harness/session/jsonl-repo.ts +0 -179
  176. package/ecagent/src/harness/session/jsonl-storage.ts +0 -314
  177. package/ecagent/src/harness/session/memory-repo.ts +0 -50
  178. package/ecagent/src/harness/session/memory-storage.ts +0 -133
  179. package/ecagent/src/harness/session/repo-utils.ts +0 -51
  180. package/ecagent/src/harness/session/session.ts +0 -338
  181. package/ecagent/src/harness/session/uuid.ts +0 -54
  182. package/ecagent/src/harness/skills.ts +0 -375
  183. package/ecagent/src/harness/system-prompt.ts +0 -34
  184. package/ecagent/src/harness/types.ts +0 -838
  185. package/ecagent/src/harness/utils/shell-output.ts +0 -135
  186. package/ecagent/src/harness/utils/truncate.ts +0 -344
  187. package/ecagent/src/index.ts +0 -46
  188. package/ecagent/src/node.ts +0 -2
  189. package/ecagent/src/proxy.ts +0 -367
  190. package/ecagent/src/runtime/api/lazy.ts +0 -70
  191. package/ecagent/src/runtime/api/pi-messages.lazy.ts +0 -4
  192. package/ecagent/src/runtime/api/pi-messages.ts +0 -436
  193. package/ecagent/src/runtime/auth/context.ts +0 -45
  194. package/ecagent/src/runtime/auth/credential-store.ts +0 -47
  195. package/ecagent/src/runtime/auth/helpers.ts +0 -46
  196. package/ecagent/src/runtime/auth/resolve.ts +0 -141
  197. package/ecagent/src/runtime/auth/types.ts +0 -182
  198. package/ecagent/src/runtime/compat.ts +0 -158
  199. package/ecagent/src/runtime/index.ts +0 -31
  200. package/ecagent/src/runtime/models.ts +0 -452
  201. package/ecagent/src/runtime/oauth.ts +0 -1
  202. package/ecagent/src/runtime/providers/faux.ts +0 -538
  203. package/ecagent/src/runtime/types.ts +0 -718
  204. package/ecagent/src/runtime/utils/diagnostics.ts +0 -45
  205. package/ecagent/src/runtime/utils/event-stream.ts +0 -88
  206. package/ecagent/src/runtime/utils/headers.ts +0 -18
  207. package/ecagent/src/runtime/utils/json-parse.ts +0 -124
  208. package/ecagent/src/runtime/utils/oauth/types.ts +0 -79
  209. package/ecagent/src/runtime/utils/overflow.ts +0 -165
  210. package/ecagent/src/runtime/utils/provider-env.ts +0 -52
  211. package/ecagent/src/runtime/utils/retry.ts +0 -101
  212. package/ecagent/src/runtime/utils/typebox-helpers.ts +0 -24
  213. package/ecagent/src/runtime/utils/validation.ts +0 -310
  214. package/ecagent/src/types.ts +0 -430
  215. package/ecagent/tsconfig.build.json +0 -32
@@ -27,9 +27,11 @@ export function showActivitiesPolicy(mode, chatType, _identity) {
27
27
  // ── ChannelLoader ──────────────────────────────────────────────────────────
28
28
  export class ChannelLoader {
29
29
  processDebug;
30
+ aunDefaultEncrypt;
30
31
  plugins = new Map();
31
- constructor(processDebug) {
32
+ constructor(processDebug, aunDefaultEncrypt) {
32
33
  this.processDebug = processDebug;
34
+ this.aunDefaultEncrypt = aunDefaultEncrypt;
33
35
  }
34
36
  register(plugin) {
35
37
  if (this.plugins.has(plugin.name)) {
@@ -51,6 +53,7 @@ export class ChannelLoader {
51
53
  agentName: agent.aid,
52
54
  defaultProjectPath: agent.config.projects?.defaultPath ?? process.cwd(),
53
55
  debug: this.processDebug,
56
+ aunDefaultEncrypt: this.aunDefaultEncrypt,
54
57
  };
55
58
  // Build the full list of config instances to create.
56
59
  // AUN is synthesised from agent.aid; any explicit aun entry in channels[] is skipped.
@@ -266,6 +269,7 @@ export function buildReloadHooks(deps) {
266
269
  agentName: agent.aid ?? agent.config?.aid,
267
270
  defaultProjectPath: agent.config?.projects?.defaultPath ?? process.cwd(),
268
271
  debug: channelLoader.processDebug,
272
+ aunDefaultEncrypt: channelLoader.aunDefaultEncrypt,
269
273
  };
270
274
  const plugin = channelLoader.getPlugin(cfgInst.type);
271
275
  if (!plugin)
@@ -372,7 +372,7 @@ export class CommandHandler {
372
372
  const effortDecision = validateRuntimeStringFieldOverride({
373
373
  selfAid: definition.agentAid,
374
374
  role,
375
- field: `baseagents.${base}.${base === 'codex' ? 'reasoning' : 'effort'}`,
375
+ field: `baseagents.${base}.effort`,
376
376
  value: definition.execution.effort,
377
377
  });
378
378
  if (!effortDecision.ok)
@@ -30,9 +30,10 @@ export function isEvolMenuVersionGateEnabled() {
30
30
  export function isAunMenuTokenRequired() {
31
31
  return loadDaemonConfig().aun?.menuTokenRequired === true;
32
32
  }
33
- export function evolMenuResponseTransportMetadata(_requestEncrypted) {
33
+ export function evolMenuResponseTransportMetadata() {
34
34
  return {
35
- encrypted: true,
35
+ // Clear inherited request state so AUNChannel applies daemon aun.defaultEncrypt.
36
+ encrypted: undefined,
36
37
  protectedHeaders: { ec_version: readInstalledEvolcoreVersion() },
37
38
  };
38
39
  }
@@ -9,6 +9,7 @@ import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
9
9
  import { buildEnvelope } from '../message/message-utils.js';
10
10
  import path from 'path';
11
11
  import fs from 'fs';
12
+ import os from 'os';
12
13
  import crypto from 'crypto';
13
14
  import { execFileSync } from 'child_process';
14
15
  import { CronExpressionParser } from 'cron-parser';
@@ -452,9 +453,6 @@ function menuConfigSource(fieldPath, sel) {
452
453
  return null;
453
454
  }
454
455
  function menuModelConfigFieldPath(target) {
455
- if (target.field === 'effort' && target.baseagent === 'codex') {
456
- return `baseagents.${target.baseagent}.reasoning`;
457
- }
458
456
  return target.fieldPath;
459
457
  }
460
458
  function menuChatmodeScope(args) {
@@ -1881,7 +1879,6 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
1881
1879
  const target = resolveCapabilityTarget.call(this, { channel, args, session, evolagent, fromControlChannel });
1882
1880
  if ('error' in target)
1883
1881
  return target;
1884
- const identity = this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
1885
1882
  const data = queryCapabilityTypes(target.ctx, target.config, type);
1886
1883
  return { data: sanitizeCapabilityQueryForRole(data, identity.role) };
1887
1884
  }
@@ -2179,11 +2176,13 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2179
2176
  if (!subject.isDaemonOwner) {
2180
2177
  return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
2181
2178
  }
2182
- const owningAgent = this.getOwningAgent(channel);
2183
2179
  const data = {
2184
2180
  aid: loadDaemonConfig().aid ?? null,
2185
2181
  pid: process.pid,
2186
- node: process.version,
2182
+ nodejs: process.version,
2183
+ hostname: os.hostname(),
2184
+ platform: process.platform,
2185
+ arch: process.arch,
2187
2186
  uptime: Math.floor(process.uptime()),
2188
2187
  };
2189
2188
  try {
@@ -2204,14 +2203,6 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2204
2203
  const ecwebPkg = resolveGlobalPkg(WEB_PACKAGE_NAME);
2205
2204
  if (ecwebPkg?.version)
2206
2205
  data.ecwebVersion = ecwebPkg.version;
2207
- const channels = owningAgent?.channelInstanceNames?.() ?? [];
2208
- if (channels.length) {
2209
- // 将 channelKey 字符串(如 "feishu#aid#name")解析为对象 { type, instName }
2210
- data.channels = channels.map((key) => {
2211
- const parts = key.split('#');
2212
- return { type: parts[0], instName: parts[2] || parts[0] };
2213
- });
2214
- }
2215
2206
  // 收集主机级可用的所有 baseagent 类型(检测已安装的 CLI + 版本)
2216
2207
  const baseagents = [];
2217
2208
  for (const cmd of ['claude', 'gemini', 'codex']) {
@@ -2260,7 +2251,9 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
2260
2251
  if (cmdBase === '/perm') {
2261
2252
  return { error: 'permissionMode 只能通过角色策略编辑器修改', code: 'ROLE_POLICY_MANAGED' };
2262
2253
  }
2263
- const scopeError = validateConfigWriteScope(cmdBase, args);
2254
+ // JSON Menu 入口在 validateMenuRequest 中要求 activity 显式 scope;保留
2255
+ // 底层 execMenuUpdate 的旧默认行为,兼容旧版 slash/内部直调调用方。
2256
+ const scopeError = validateConfigWriteScope(cmdBase, args, { includeActivity: false });
2264
2257
  if (scopeError)
2265
2258
  return { error: scopeError.message, code: scopeError.code };
2266
2259
  args = withoutLegacySessionConfigArgs(args);
@@ -3517,26 +3510,43 @@ const SYSTEM_CONTROL_NAME_MAP = {
3517
3510
  config: '/config', capability: '/capability', role: '/role',
3518
3511
  group: '/group', connect: '/connect',
3519
3512
  };
3513
+ function menuCommandToken(cmd) {
3514
+ if (typeof cmd !== 'string')
3515
+ return undefined;
3516
+ return cmd.trim().split(/\s+/, 1)[0] || undefined;
3517
+ }
3520
3518
  function isProcessLevelMenu(name, cmd) {
3519
+ const cmdToken = menuCommandToken(cmd);
3521
3520
  return name === 'system' || name === 'agent' || name === 'gateway' || name === 'config'
3522
- || cmd === '/system' || cmd === '/agent' || cmd === '/gateway' || cmd === '/config';
3521
+ || cmdToken === '/system' || cmdToken === '/agent' || cmdToken === '/gateway' || cmdToken === '/config';
3523
3522
  }
3524
3523
  async function execMenuForSystemControl(payload, context) {
3525
3524
  const id = payload?.id ?? '';
3526
3525
  const name = payload?.name;
3527
- if (name === 'cli' || payload?.cmd === '/cli') {
3526
+ const compatCmd = menuCommandToken(payload?.cmd);
3527
+ // `menu.query(/file)` returns file metadata and is authorized as
3528
+ // `file.fetch`, while `menu.action(file/fetch)` sends the file. Neither
3529
+ // operation is available through Host/control: file operations must be
3530
+ // addressed to the current Agent AID so the Agent project boundary applies.
3531
+ const isFileFetch = (payload?.type === 'menu.query'
3532
+ || (payload?.type === 'menu.action' && payload?.action === 'fetch'))
3533
+ && (name === 'file' || compatCmd === '/file');
3534
+ if (isFileFetch) {
3535
+ return ecwebErr(id, name, 'NOT_SUPPORTED', 'Host/control 不支持 file.fetch,请通过目标 Agent AID 请求');
3536
+ }
3537
+ if (name === 'cli' || compatCmd === '/cli') {
3528
3538
  const message = context.source === 'ecweb'
3529
3539
  ? 'cli 不在 ECWeb 控制范围'
3530
3540
  : 'cli 不在控制 channel 范围';
3531
3541
  return { type: 'menu.response', id, name, error: { code: 'NOT_SUPPORTED', message } };
3532
3542
  }
3533
3543
  const isProcessLevel = isProcessLevelMenu(name, payload?.cmd);
3534
- const isRoleRequest = name === 'role' || payload?.cmd === '/role';
3535
- const isConnectRequest = name === 'connect' || payload?.cmd === '/connect';
3544
+ const isRoleRequest = name === 'role' || compatCmd === '/role';
3545
+ const isConnectRequest = name === 'connect' || compatCmd === '/connect';
3536
3546
  const requiresAgentTarget = isRoleRequest || isConnectRequest || [
3537
3547
  'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group',
3538
3548
  ].includes(name)
3539
- || ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(payload?.cmd);
3549
+ || ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(compatCmd ?? '');
3540
3550
  const target = isProcessLevel
3541
3551
  ? { channel: SYSTEM_CONTROL_CHANNEL }
3542
3552
  : resolveExternalMenuAgentChannel(this, payload, SYSTEM_CONTROL_CHANNEL, requiresAgentTarget);
@@ -3635,8 +3645,9 @@ export async function execMenuForControl(payload, peerId) {
3635
3645
  if (validationError)
3636
3646
  return menuFailure({ id, ...(name ? { name } : {}) }, validationError);
3637
3647
  const owners = loadDaemonConfig().owners ?? [];
3638
- const isRoleRequest = name === 'role' || payload?.cmd === '/role';
3639
- const isConnectRequest = name === 'connect' || payload?.cmd === '/connect';
3648
+ const compatCmd = menuCommandToken(payload?.cmd);
3649
+ const isRoleRequest = name === 'role' || compatCmd === '/role';
3650
+ const isConnectRequest = name === 'connect' || compatCmd === '/connect';
3640
3651
  if (!isRoleRequest && !isConnectRequest && !isProcessLevelOwner(peerId, owners)) {
3641
3652
  return menuFailure({ id, ...(name ? { name } : {}) }, { code: 'ROLE_ACCESS_DENIED', message: '控制 channel 操作需要 owner 权限' });
3642
3653
  }
@@ -15,6 +15,7 @@ const EXPLICIT_SCOPE_UPDATE_NAMES = new Set([
15
15
  'mentionmode',
16
16
  'group',
17
17
  'permission',
18
+ 'activity',
18
19
  ]);
19
20
  const EXPLICIT_SCOPE_UPDATE_COMMANDS = new Set([
20
21
  '/model',
@@ -24,18 +25,20 @@ const EXPLICIT_SCOPE_UPDATE_COMMANDS = new Set([
24
25
  '/mentionmode',
25
26
  '/group',
26
27
  '/perm',
28
+ '/activity',
27
29
  ]);
28
30
  function firstCommandToken(value) {
29
31
  if (typeof value !== 'string')
30
32
  return undefined;
31
33
  return value.trim().split(/\s+/, 1)[0] || undefined;
32
34
  }
33
- function requiresExplicitConfigWriteScope(nameOrCmd) {
35
+ function requiresExplicitConfigWriteScope(nameOrCmd, includeActivity = true) {
34
36
  const token = firstCommandToken(nameOrCmd);
35
- return !!token && (EXPLICIT_SCOPE_UPDATE_NAMES.has(token) || EXPLICIT_SCOPE_UPDATE_COMMANDS.has(token));
37
+ return !!token && (EXPLICIT_SCOPE_UPDATE_NAMES.has(token) || EXPLICIT_SCOPE_UPDATE_COMMANDS.has(token))
38
+ && (includeActivity || (token !== 'activity' && token !== '/activity'));
36
39
  }
37
- export function validateConfigWriteScope(nameOrCmd, args) {
38
- if (!requiresExplicitConfigWriteScope(nameOrCmd))
40
+ export function validateConfigWriteScope(nameOrCmd, args, options) {
41
+ if (!requiresExplicitConfigWriteScope(nameOrCmd, options?.includeActivity ?? true))
39
42
  return null;
40
43
  const rawScope = args?.scope;
41
44
  if (rawScope === undefined || rawScope === null || (typeof rawScope === 'string' && rawScope.trim().length === 0)) {
@@ -1146,7 +1146,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1146
1146
  }
1147
1147
  catch { }
1148
1148
  const configuredModel = modelConfig.model;
1149
- const configuredEffort = modelConfig[fallbackBaseagent === 'codex' ? 'reasoning' : 'effort'];
1149
+ const configuredEffort = modelConfig.effort;
1150
1150
  const rawModels = hasModelSwitcher(modelAgent) ? await modelAgent.listModels() : [];
1151
1151
  const models = hasModelSwitcher(modelAgent)
1152
1152
  ? filterModelsForRole(identity.role, fallbackBaseagent, rawModels, modelAgent.resolveModelId?.bind(modelAgent))
@@ -1468,7 +1468,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1468
1468
  }
1469
1469
  catch { }
1470
1470
  const configuredModel = modelConfig.model;
1471
- const configuredEffort = modelConfig[fallbackBaseagent === 'codex' ? 'reasoning' : 'effort'];
1471
+ const configuredEffort = modelConfig.effort;
1472
1472
  const currentModel = hasModelSwitcher(effortAgent) ? (configuredModel || effortAgent.getModel()) : effortAgent.name;
1473
1473
  const efforts = getAvailableEfforts(effortAgent, currentModel);
1474
1474
  const currentEffort = configuredEffort ?? effortAgent.getEffort?.() ?? 'auto';
@@ -1530,7 +1530,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1530
1530
  }
1531
1531
  catch { }
1532
1532
  const configuredModel = modelConfig.model;
1533
- const configuredEffort = modelConfig[fallbackBaseagent === 'codex' ? 'reasoning' : 'effort'];
1533
+ const configuredEffort = modelConfig.effort;
1534
1534
  const currentModel = hasModelSwitcher(effortAgent) ? (configuredModel || effortAgent.getModel()) : effortAgent.name;
1535
1535
  const efforts = getAvailableEfforts(effortAgent, currentModel);
1536
1536
  const currentEffort = configuredEffort ?? effortAgent.getEffort?.() ?? 'auto';
@@ -2210,7 +2210,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2210
2210
  }
2211
2211
  catch { }
2212
2212
  const backendModel = modelConfig.model || newRunner.getModel?.() || newAgent?.model;
2213
- const backendEffort = modelConfig[newBaseagent === 'codex' ? 'reasoning' : 'effort']
2213
+ const backendEffort = modelConfig.effort
2214
2214
  ?? newRunner.getEffort?.()
2215
2215
  ?? newAgent?.effort;
2216
2216
  const backendBits = [newBaseagent, backendModel, backendEffort].filter(Boolean).join(' · ');
@@ -54,6 +54,14 @@ function operationTopologyKey(operation) {
54
54
  sessionChannelKey: operation.sessionChannelKey,
55
55
  });
56
56
  }
57
+ function operationProofTopologyKey(operation) {
58
+ if (operation.kind !== 'contact-audit-partition')
59
+ return operationTopologyKey(operation);
60
+ return JSON.stringify({
61
+ kind: operation.kind,
62
+ source: operation.source,
63
+ });
64
+ }
57
65
  /**
58
66
  * Manifests written before allowExistingDestinationMerge was introduced still
59
67
  * need safe recovery. Re-derive that narrow permission from their immutable
@@ -125,26 +133,40 @@ function acknowledgeCompletedCopies(root, operations) {
125
133
  if (manifest.planSignature === currentPlanSignature)
126
134
  return;
127
135
  for (const operation of manifest.operations ?? []) {
128
- if (operation.kind !== 'copy' || (operation.status !== 'committed' && operation.status !== 'skipped') || !operation.sourceHash)
136
+ if ((operation.kind !== 'copy' && operation.kind !== 'contact-audit-partition')
137
+ || (operation.status !== 'committed' && operation.status !== 'skipped')
138
+ || !operation.sourceHash)
129
139
  continue;
130
- const key = operationTopologyKey(operation);
131
- if (!proofs.has(key))
132
- proofs.set(key, { migrationId: manifest.id, sourceHash: operation.sourceHash });
140
+ const key = operationProofTopologyKey(operation);
141
+ if (!proofs.has(key)) {
142
+ proofs.set(key, {
143
+ migrationId: manifest.id,
144
+ sourceHash: operation.sourceHash,
145
+ destinations: operation.kind === 'contact-audit-partition'
146
+ ? [...(operation.destinations ?? [])]
147
+ : operation.destination ? [operation.destination] : [],
148
+ });
149
+ }
133
150
  }
134
151
  }
135
152
  }
136
153
  catch { }
137
154
  for (const operation of operations) {
138
- if (operation.kind !== 'copy' || !operation.destination || !fs.existsSync(operation.destination))
155
+ if (operation.kind !== 'copy' && operation.kind !== 'contact-audit-partition')
139
156
  continue;
140
- const proof = proofs.get(operationTopologyKey(operation));
157
+ const proof = proofs.get(operationProofTopologyKey(operation));
141
158
  if (!proof)
142
159
  continue;
160
+ if (operation.kind === 'copy' && (!operation.destination || !fs.existsSync(operation.destination)))
161
+ continue;
162
+ if (operation.kind === 'contact-audit-partition' && proof.destinations.some(destination => !fs.existsSync(destination)))
163
+ continue;
143
164
  try {
144
165
  if (treeHash(operation.source, operation.exclude) !== proof.sourceHash)
145
166
  continue;
146
167
  operation.status = 'acknowledged';
147
168
  operation.acknowledgedBy = proof.migrationId;
169
+ operation.sourceHash = proof.sourceHash;
148
170
  }
149
171
  catch { }
150
172
  }
@@ -1419,6 +1441,18 @@ function atomicWriteText(target, content) {
1419
1441
  function hashContent(content) {
1420
1442
  return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
1421
1443
  }
1444
+ function mergeJsonlContent(existing, incoming, deduplicateExisting = false) {
1445
+ const existingLines = existing.split('\n').filter(line => line.trim());
1446
+ const merged = deduplicateExisting ? [...new Set(existingLines)] : [...existingLines];
1447
+ const lines = new Set(merged);
1448
+ for (const line of incoming.split('\n')) {
1449
+ if (!line.trim() || lines.has(line))
1450
+ continue;
1451
+ lines.add(line);
1452
+ merged.push(line);
1453
+ }
1454
+ return merged.length ? `${merged.join('\n')}\n` : '';
1455
+ }
1422
1456
  function appendIssue(operation, issue) {
1423
1457
  const issues = operation.issues ?? [];
1424
1458
  if (!issues.includes(issue))
@@ -1457,20 +1491,18 @@ function executeContactAudit(manifest, operation, checkpoint) {
1457
1491
  for (const [aid, lines] of groups) {
1458
1492
  const destination = path.join(manifest.root, 'agents', aid, 'data', 'contact-audit.jsonl');
1459
1493
  const content = lines.join('\n') + '\n';
1460
- const expectedHash = hashContent(content);
1494
+ const existing = fs.existsSync(destination) ? fs.readFileSync(destination, 'utf8') : undefined;
1495
+ if (existing !== undefined)
1496
+ strictJsonValidation(destination);
1497
+ const expectedContent = existing === undefined ? content : mergeJsonlContent(existing, content);
1498
+ const expectedHash = hashContent(expectedContent);
1461
1499
  operation.destinations.push(destination);
1462
1500
  operation.destinationHashes[destination] = expectedHash;
1463
- if (fs.existsSync(destination)) {
1464
- const existing = fs.readFileSync(destination, 'utf8');
1501
+ if (existing !== undefined) {
1465
1502
  if (hashContent(existing) !== expectedHash) {
1466
- // Audit logs are append-only. An exact existing prefix is a partial
1467
- // prior copy, so replacing it with the verified complete partition is
1468
- // lossless; every other mismatch remains a manual conflict.
1469
- if (!content.startsWith(existing)) {
1470
- appendIssue(operation, `destination conflict: ${destination}`);
1471
- continue;
1472
- }
1473
- atomicWriteText(destination, content);
1503
+ // The per-Agent audit may have advanced after an earlier migration.
1504
+ // Preserve its order and append only legacy records not already present.
1505
+ atomicWriteText(destination, expectedContent);
1474
1506
  }
1475
1507
  continue;
1476
1508
  }
@@ -1600,15 +1632,7 @@ function trackMergeSources(operation) {
1600
1632
  return sources;
1601
1633
  }
1602
1634
  function mergeJsonl(target, source) {
1603
- const lines = new Set(fs.readFileSync(target, 'utf8').split('\n').filter(line => line.trim()));
1604
- const merged = [...lines];
1605
- for (const line of fs.readFileSync(source, 'utf8').split('\n')) {
1606
- if (!line.trim() || lines.has(line))
1607
- continue;
1608
- lines.add(line);
1609
- merged.push(line);
1610
- }
1611
- fs.writeFileSync(target, merged.length ? `${merged.join('\n')}\n` : '');
1635
+ fs.writeFileSync(target, mergeJsonlContent(fs.readFileSync(target, 'utf8'), fs.readFileSync(source, 'utf8'), true));
1612
1636
  }
1613
1637
  function copyTree(source, destination) {
1614
1638
  const stat = fs.statSync(source);
@@ -1741,6 +1765,34 @@ export function readDataMigration(root, id) {
1741
1765
  throw new Error(`migration manifest not found: ${id}`);
1742
1766
  return manifest;
1743
1767
  }
1768
+ function completedMigrationMatchesPlan(manifest, plan, planSignature) {
1769
+ if (manifest.state !== 'completed' || manifest.planSignature !== planSignature)
1770
+ return false;
1771
+ const proofs = new Map(manifest.operations.map(operation => [operationProofTopologyKey(operation), operation]));
1772
+ for (const operation of plan.operations.filter(item => item.status !== 'acknowledged')) {
1773
+ const proof = proofs.get(operationProofTopologyKey(operation));
1774
+ if (!proof || (proof.status !== 'committed' && proof.status !== 'skipped' && proof.status !== 'acknowledged'))
1775
+ return false;
1776
+ try {
1777
+ for (const source of operationSources(operation)) {
1778
+ const expected = proof.sourceHashes?.[source]
1779
+ ?? (source === proof.source ? proof.sourceHash : undefined);
1780
+ if (!expected || treeHash(source, operation.exclude) !== expected)
1781
+ return false;
1782
+ }
1783
+ }
1784
+ catch {
1785
+ return false;
1786
+ }
1787
+ if ((operation.kind === 'copy' || operation.kind === 'merge-copy')
1788
+ && (!operation.destination || !fs.existsSync(operation.destination)))
1789
+ return false;
1790
+ if (operation.kind === 'contact-audit-partition'
1791
+ && (proof.destinations ?? []).some(destination => !fs.existsSync(destination)))
1792
+ return false;
1793
+ }
1794
+ return true;
1795
+ }
1744
1796
  /**
1745
1797
  * Startup gate for the explicit migration workflow. Legacy source files are
1746
1798
  * deliberately retained after success, so their mere presence is not enough
@@ -1763,7 +1815,7 @@ export function inspectDataMigrationRequirement(root) {
1763
1815
  if (!entry.isFile() || !entry.name.endsWith('.json'))
1764
1816
  continue;
1765
1817
  const manifest = readJson(path.join(migrationDir(root), entry.name));
1766
- if (manifest?.state !== 'completed' || manifest.planSignature !== planSignature)
1818
+ if (!manifest || !completedMigrationMatchesPlan(manifest, plan, planSignature))
1767
1819
  continue;
1768
1820
  completedMigrationId = manifest.id;
1769
1821
  break;
@@ -3,8 +3,20 @@ function apiEndpoint(baseUrl, resource) {
3
3
  const configured = baseUrl?.trim();
4
4
  if (!configured)
5
5
  throw new Error('Direct text inference baseUrl is not configured');
6
- const base = configured.replace(/\/+$/, '');
7
- return base.endsWith('/v1') ? `${base}/${resource}` : `${base}/v1/${resource}`;
6
+ const parsed = new URL(configured);
7
+ const pathname = parsed.pathname.replace(/\/+$/, '');
8
+ parsed.pathname = pathname.endsWith('/v1')
9
+ ? `${pathname}/${resource}`
10
+ : `${pathname}/v1/${resource}`;
11
+ return parsed.toString();
12
+ }
13
+ function withQueryParams(url, queryParams) {
14
+ if (!queryParams || Object.keys(queryParams).length === 0)
15
+ return url;
16
+ const parsed = new URL(url);
17
+ for (const [name, value] of Object.entries(queryParams))
18
+ parsed.searchParams.set(name, value);
19
+ return parsed.toString();
8
20
  }
9
21
  async function parseError(response) {
10
22
  let raw = '';
@@ -28,10 +40,10 @@ export class AnthropicTextInferenceProvider {
28
40
  this.config = config;
29
41
  }
30
42
  async completeText(request) {
31
- const response = await fetch(apiEndpoint(this.config.baseUrl, 'messages'), {
43
+ const response = await fetch(withQueryParams(apiEndpoint(this.config.baseUrl, 'messages'), this.config.queryParams), {
32
44
  method: 'POST',
33
45
  signal: request.signal,
34
- headers: this.headers(),
46
+ headers: { ...this.headers(), ...this.config.headers },
35
47
  body: JSON.stringify({
36
48
  model: request.model,
37
49
  max_tokens: 256,
@@ -69,12 +81,13 @@ export class OpenAITextInferenceProvider {
69
81
  this.config = config;
70
82
  }
71
83
  async completeText(request) {
72
- const response = await fetch(apiEndpoint(this.config.baseUrl, 'responses'), {
84
+ const response = await fetch(withQueryParams(apiEndpoint(this.config.baseUrl, 'responses'), this.config.queryParams), {
73
85
  method: 'POST',
74
86
  signal: request.signal,
75
87
  headers: {
76
88
  'Content-Type': 'application/json',
77
89
  Authorization: `Bearer ${this.config.apiKey}`,
90
+ ...this.config.headers,
78
91
  },
79
92
  body: JSON.stringify({
80
93
  model: request.model,
@@ -627,6 +627,7 @@ export class MessageBridge {
627
627
  const menuControl = resolvedChannelType === 'aun'
628
628
  ? parseMenuControl(content)
629
629
  : { isMenu: false };
630
+ let menuResponseWarning;
630
631
  if (menuControl.isMenu) {
631
632
  this.logMenuInbound(channelName, msg, menuControl);
632
633
  if (!hasValidMenuId(menuControl)) {
@@ -649,7 +650,7 @@ export class MessageBridge {
649
650
  await this.sendMenuResponse(adapter, channelName, msg, menuFailure(header, {
650
651
  code: 'MENU_TOKEN_ENCRYPTION_REQUIRED',
651
652
  message: 'Menu 请求必须使用 E2EE',
652
- }), true);
653
+ }));
653
654
  return;
654
655
  }
655
656
  if (!isTokenRequest) {
@@ -666,6 +667,13 @@ export class MessageBridge {
666
667
  }));
667
668
  return;
668
669
  }
670
+ if (!menuTokenRequired && tokenSupplied && !tokenValid) {
671
+ this.logMenuDiagnostic('token-invalid-optional', channelName, msg, menuControl, 'MENU_TOKEN_INVALID');
672
+ menuResponseWarning = {
673
+ code: 'MENU_TOKEN_INVALID',
674
+ message: 'Menu token 无效,请重新申请',
675
+ };
676
+ }
669
677
  }
670
678
  const validationError = validateMenuRequest(menuControl.request);
671
679
  if (validationError) {
@@ -712,7 +720,7 @@ export class MessageBridge {
712
720
  });
713
721
  const identity = authSubject.identity;
714
722
  if (menuControl.isMenu) {
715
- await this.handleMenuControl(menuControl, channelName, resolvedChannelType, msg, adapter, authSubject);
723
+ await this.handleMenuControl(menuControl, channelName, resolvedChannelType, msg, adapter, authSubject, menuResponseWarning);
716
724
  return;
717
725
  }
718
726
  // 普通消息保留原始日志;控制 payload 使用脱敏元数据日志。
@@ -1074,7 +1082,7 @@ export class MessageBridge {
1074
1082
  throw { code: 'NOT_SUPPORTED', message: `Unsupported menu name: ${name}` };
1075
1083
  return mapped;
1076
1084
  }
1077
- async handleMenuControl(parsed, channel, channelType, msg, adapter, authSubject) {
1085
+ async handleMenuControl(parsed, channel, channelType, msg, adapter, authSubject, responseWarning) {
1078
1086
  if (!hasValidMenuId(parsed))
1079
1087
  return;
1080
1088
  const header = { id: parsed.id, ...(parsed.name?.trim() ? { name: parsed.name } : {}) };
@@ -1122,7 +1130,10 @@ export class MessageBridge {
1122
1130
  }
1123
1131
  if (deduped.replayed)
1124
1132
  this.logMenuDiagnostic('request-replay', channel, msg, parsed);
1125
- await this.sendMenuResponse(adapter, channel, msg, deduped.value, isTokenRequest);
1133
+ const response = responseWarning && 'data' in deduped.value
1134
+ ? { ...deduped.value, warning: responseWarning }
1135
+ : deduped.value;
1136
+ await this.sendMenuResponse(adapter, channel, msg, response);
1126
1137
  const deferredEffect = afterResponse;
1127
1138
  if (!deduped.replayed && deferredEffect) {
1128
1139
  try {
@@ -1191,7 +1202,7 @@ export class MessageBridge {
1191
1202
  return menuFailure(header, normalizeMenuError(error));
1192
1203
  }
1193
1204
  }
1194
- async sendMenuResponse(adapter, channel, msg, response, forceEncryption = false) {
1205
+ async sendMenuResponse(adapter, channel, msg, response) {
1195
1206
  if (!adapter?.send) {
1196
1207
  this.logMenuDiagnostic('transport-unavailable', channel, msg, {
1197
1208
  isMenu: true,
@@ -1208,7 +1219,7 @@ export class MessageBridge {
1208
1219
  ...(msg.replyContext ?? {}),
1209
1220
  metadata: {
1210
1221
  ...(msg.replyContext?.metadata ?? {}),
1211
- ...evolMenuResponseTransportMetadata(forceEncryption ? true : msg.encrypted),
1222
+ ...evolMenuResponseTransportMetadata(),
1212
1223
  },
1213
1224
  };
1214
1225
  const envelope = buildEnvelope({
@@ -890,6 +890,16 @@ export class ResponseEngine {
890
890
  return;
891
891
  renderer.addNotice(text, severity, subtype, true);
892
892
  }
893
+ async cancelStaleRunnerStart(agent, sessionId, streamKey, turnLease, taskId) {
894
+ if (this.turnCoordinator.isCurrent(turnLease))
895
+ return false;
896
+ logger.info(`[ResponseEngine] Stale turn detected after agent.runQuery start; cancelling unconsumed stream: session=${sessionId} task=${taskId}`);
897
+ await agent.interrupt(sessionId).catch(interruptError => {
898
+ logger.warn(`[ResponseEngine] Failed to cancel stale runner start: session=${sessionId} task=${taskId} error=${interruptError instanceof Error ? interruptError.message : String(interruptError)}`);
899
+ });
900
+ agent.cleanupStream(streamKey);
901
+ return true;
902
+ }
893
903
  async retryAfterContextRecovery(prompt, opts) {
894
904
  const { streamKey, renderer, agent, session, absoluteProjectPath, effectiveSystemPrompt, modelOverride, runtimeEnv, resetTimer, shouldSuppress, proactive, turnLease, } = opts;
895
905
  if (!session.agentSessionId || !canCompactAgent(agent)) {
@@ -911,6 +921,9 @@ export class ResponseEngine {
911
921
  await this.emitOperationalNotice(renderer, '✅ 压缩完成,继续处理...', 'info', 'compact-retry');
912
922
  await renderer.flush();
913
923
  const retryStream = await agent.runQuery(session.id, prompt, absoluteProjectPath, session.agentSessionId, undefined, effectiveSystemPrompt, this.sessionManager, modelOverride, runtimeEnv);
924
+ if (await this.cancelStaleRunnerStart(agent, session.id, streamKey, turnLease, turnLease.taskId)) {
925
+ throw new Error('RUNNER_START_SUPERSEDED');
926
+ }
914
927
  agent.registerStream(streamKey, retryStream);
915
928
  return await this.processEventStream(retryStream, session, agent, renderer, resetTimer, shouldSuppress, proactive, undefined, // 重试分支不调插件钩子
916
929
  undefined, turnLease);
@@ -2331,7 +2344,7 @@ export class ResponseEngine {
2331
2344
  try {
2332
2345
  const selector = { self: selfAid || undefined, peerKey };
2333
2346
  const modelResolution = resolveEffectiveFieldWithSource(`baseagents.${normalizedBaseagent.canonical}.model`, selector, { cache: true });
2334
- const effortResolution = resolveEffectiveFieldWithSource(`baseagents.${normalizedBaseagent.canonical}.${normalizedBaseagent.canonical === 'codex' ? 'reasoning' : 'effort'}`, selector, { cache: true });
2347
+ const effortResolution = resolveEffectiveFieldWithSource(`baseagents.${normalizedBaseagent.canonical}.effort`, selector, { cache: true });
2335
2348
  const modelDecision = constrainResolvedModelForRole({
2336
2349
  role: peerRole,
2337
2350
  baseagent: normalizedBaseagent.canonical,
@@ -2342,7 +2355,7 @@ export class ResponseEngine {
2342
2355
  selfAid: selfAid || undefined,
2343
2356
  });
2344
2357
  const model = modelDecision.model;
2345
- const effortField = `baseagents.${normalizedBaseagent.canonical}.${normalizedBaseagent.canonical === 'codex' ? 'reasoning' : 'effort'}`;
2358
+ const effortField = `baseagents.${normalizedBaseagent.canonical}.effort`;
2346
2359
  const effortDecision = resolveRuntimeStringField({
2347
2360
  selfAid: selfAid || undefined,
2348
2361
  role: peerRole,
@@ -2389,7 +2402,7 @@ export class ResponseEngine {
2389
2402
  effectiveModelSource = triggerModelDecision.constrained ? 'role' : 'trigger';
2390
2403
  }
2391
2404
  if (triggerEffortOverride) {
2392
- const effortField = `baseagents.${normalizedBaseagent.canonical}.${normalizedBaseagent.canonical === 'codex' ? 'reasoning' : 'effort'}`;
2405
+ const effortField = `baseagents.${normalizedBaseagent.canonical}.effort`;
2393
2406
  const triggerEffortDecision = resolveRuntimeStringField({
2394
2407
  selfAid: selfAid || undefined,
2395
2408
  role: peerRole,
@@ -2766,6 +2779,12 @@ export class ResponseEngine {
2766
2779
  try {
2767
2780
  logger.info(`[ResponseEngine] agent.runQuery start: agent=${agent.name} session=${session.id} task=${taskId} attempt=${runAttempt} apiRetries=${retryScheduler.apiRetryAttemptsMade} agentSessionId=${session.agentSessionId ?? 'none'}`);
2768
2781
  const stream = await agent.runQuery(session.id, effectivePrompt, absoluteProjectPath, session.agentSessionId, renderResult?.images.length ? renderResult.images : message.images, effectiveSystemPrompt, this.sessionManager, modelOverride, runtimeEnv);
2782
+ // The turn may be superseded while the runner is still creating its
2783
+ // backend query. Replay the interrupt after runQuery resolves, when
2784
+ // every runner is required to have an addressable cancellation handle.
2785
+ if (await this.cancelStaleRunnerStart(agent, session.id, streamKey, turnLease, taskId)) {
2786
+ throw new Error('RUNNER_START_SUPERSEDED');
2787
+ }
2769
2788
  agent.registerStream(streamKey, stream);
2770
2789
  streamRegistered = true;
2771
2790
  if (shouldTimeoutRetryAttempt) {
@@ -82,9 +82,9 @@ export function activeBaseagent(self) {
82
82
  catch { /* fall through */ }
83
83
  return 'claude';
84
84
  }
85
- /** codex 的推理强度字段名是 reasoning,其余是 effort */
86
- function effortField(ba) {
87
- return ba === 'codex' ? 'reasoning' : 'effort';
85
+ /** 所有 baseagent 统一使用 effort 表达推理强度。 */
86
+ function effortField(_ba) {
87
+ return 'effort';
88
88
  }
89
89
  // ── 写:单作用域 ──────────────────────────────────────────────────────
90
90
  /** 写入指定作用域。patch 中 undefined 不动;显式 null 删除字段。 */