evolcore 0.0.25 → 0.0.27

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 (304) hide show
  1. package/CHANGELOG.md +632 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +203 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent-availability.js +68 -0
  10. package/dist/agents/baseagent.js +96 -46
  11. package/dist/agents/claude-runner.js +172 -33
  12. package/dist/agents/codex/app-server-client.js +76 -31
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +34 -1
  15. package/dist/agents/codex-runner.js +434 -446
  16. package/dist/agents/ecagent-runner.js +174 -34
  17. package/dist/agents/gemini-runner.js +8 -7
  18. package/dist/agents/runner-types.js +75 -19
  19. package/dist/aun/aid/agentmd.js +151 -36
  20. package/dist/aun/aid/client.js +1 -1
  21. package/dist/aun/aid/control-aid.js +180 -1
  22. package/dist/aun/aid/identity.js +4 -4
  23. package/dist/aun/aid/index.js +1 -1
  24. package/dist/aun/aid/store.js +17 -13
  25. package/dist/aun/group/history.js +303 -0
  26. package/dist/aun/group/identity.js +43 -0
  27. package/dist/aun/group/index.js +5 -0
  28. package/dist/aun/{msg/group.js → group/operations.js} +162 -39
  29. package/dist/aun/{menu-client.js → menu/client.js} +85 -6
  30. package/dist/aun/menu/index.js +2 -0
  31. package/dist/aun/menu/transport.js +49 -0
  32. package/dist/aun/msg/index.js +2 -1
  33. package/dist/aun/msg/managed-operation.js +85 -19
  34. package/dist/aun/msg/mention-schema.js +51 -0
  35. package/dist/aun/msg/p2p.js +19 -9
  36. package/dist/aun/msg/upload.js +43 -80
  37. package/dist/aun/outbox.js +29 -7
  38. package/dist/aun/rpc/client.js +1 -0
  39. package/dist/aun/storage/client.js +39 -50
  40. package/dist/channels/aun.js +842 -318
  41. package/dist/channels/daemon.js +10 -6
  42. package/dist/cli/agent-command.js +212 -89
  43. package/dist/cli/agent-create.js +626 -11
  44. package/dist/cli/agent.js +125 -13
  45. package/dist/cli/aun-commands.js +487 -337
  46. package/dist/cli/bench.js +20 -20
  47. package/dist/cli/config.js +55 -33
  48. package/dist/cli/contact.js +72 -15
  49. package/dist/cli/ctl-command.js +24 -24
  50. package/dist/cli/daemon-commands.js +140 -63
  51. package/dist/cli/diag.js +31 -0
  52. package/dist/cli/fs-command.js +70 -70
  53. package/dist/cli/handoff-command.js +46 -14
  54. package/dist/cli/index.js +91 -79
  55. package/dist/cli/init.js +34 -1
  56. package/dist/cli/link-rules.js +21 -21
  57. package/dist/cli/managed-command-guard.js +5 -1
  58. package/dist/cli/menu-client.js +51 -32
  59. package/dist/cli/model.js +3 -3
  60. package/dist/cli/net-check.js +22 -22
  61. package/dist/cli/queue-command.js +31 -31
  62. package/dist/cli/response.js +32 -32
  63. package/dist/cli/restart-monitor.js +103 -42
  64. package/dist/cli/stats.js +51 -51
  65. package/dist/cli/task-command.js +59 -0
  66. package/dist/cli/task-context.js +97 -49
  67. package/dist/cli/trigger-command.js +74 -64
  68. package/dist/config/builtin-roles.js +11 -0
  69. package/dist/config/config-field-policy.js +11 -0
  70. package/dist/config/config-manager.js +32 -4
  71. package/dist/config/config-operation-service.js +160 -0
  72. package/dist/config/contact-book-store.js +1 -1
  73. package/dist/config/contact-book.js +1 -1
  74. package/dist/config/contact-operation-service.js +97 -8
  75. package/dist/config/peer-role-resolver.js +1 -1
  76. package/dist/config/resolved-config-op.js +30 -7
  77. package/dist/config-store.js +1 -1
  78. package/dist/core/agent-application-service.js +7 -1
  79. package/dist/core/audit/event-key.js +17 -2
  80. package/dist/core/audit/log-integrity.js +34 -8
  81. package/dist/core/auth/agent-delegation.js +48 -14
  82. package/dist/core/auth/auth-gateway.js +2 -0
  83. package/dist/core/auth/authorization-audit.js +53 -49
  84. package/dist/core/auth/operation-authorizer.js +90 -7
  85. package/dist/core/auth/operation-catalog.js +145 -4
  86. package/dist/core/auth/remote-access-store.js +155 -10
  87. package/dist/core/bootstrap-messages.js +66 -7
  88. package/dist/core/bootstrap-service.js +111 -20
  89. package/dist/core/command/agent-control.js +88 -7
  90. package/dist/core/command/agent-storage-menu.js +443 -0
  91. package/dist/core/command/cli-intent-parser.js +19 -0
  92. package/dist/core/command/command-handler.js +159 -65
  93. package/dist/core/command/data-root-resolver.js +273 -0
  94. package/dist/core/command/diag-service.js +118 -0
  95. package/dist/core/command/group-menu.js +7 -2
  96. package/dist/core/command/menu-catalog.js +46 -7
  97. package/dist/core/command/menu-handler.js +392 -100
  98. package/dist/core/command/menu-protocol.js +45 -6
  99. package/dist/core/command/menu-token-store.js +41 -8
  100. package/dist/core/command/role-menu.js +2 -2
  101. package/dist/core/command/slash-gate.js +34 -4
  102. package/dist/core/command/slash-handler.js +123 -43
  103. package/dist/core/event-catalog.js +4 -0
  104. package/dist/core/evolagent-registry.js +28 -2
  105. package/dist/core/group/invite-service.js +924 -0
  106. package/dist/core/handoff/runtime.js +205 -27
  107. package/dist/core/handoff/store.js +193 -4
  108. package/dist/core/interaction-router.js +22 -8
  109. package/dist/core/message/im-renderer.js +146 -34
  110. package/dist/core/message/message-bridge.js +163 -20
  111. package/dist/core/message/message-log.js +5 -3
  112. package/dist/core/message/message-queue.js +99 -41
  113. package/dist/core/message/message-utils.js +1 -0
  114. package/dist/core/message/response-engine.js +755 -144
  115. package/dist/core/model/catalog-service.js +79 -49
  116. package/dist/core/model/context-window-suffix.js +33 -0
  117. package/dist/core/model/model-catalog.js +1 -1
  118. package/dist/core/model/model-permission.js +12 -3
  119. package/dist/core/permission/approval-gateway.js +36 -3
  120. package/dist/core/permission/ec-command-parser.js +112 -41
  121. package/dist/core/permission/protected-paths.js +134 -16
  122. package/dist/core/permission/sandbox-runtime.js +108 -17
  123. package/dist/core/permission/tool-error-code.js +15 -0
  124. package/dist/core/permission/tool-policy.js +9 -8
  125. package/dist/core/relation/peer-identity.js +57 -6
  126. package/dist/core/relation/shared-data.js +106 -0
  127. package/dist/core/role/runtime-policy.js +64 -0
  128. package/dist/core/runtime-lock.js +10 -26
  129. package/dist/core/session/runner-task-history.js +170 -0
  130. package/dist/core/session/session-manager.js +34 -6
  131. package/dist/core/session/session-mapper.js +41 -0
  132. package/dist/core/session/session-turn-coordinator.js +37 -7
  133. package/dist/core/session/session-turns.js +48 -7
  134. package/dist/core/task/task-recovery-service.js +176 -0
  135. package/dist/core/task/task-resume-operation.js +32 -0
  136. package/dist/eck/group-rules-sync.js +5 -3
  137. package/dist/eck/kit-renderer.js +1 -0
  138. package/dist/index.js +588 -217
  139. package/dist/ipc.js +193 -15
  140. package/dist/paths.js +17 -4
  141. package/dist/product.js +2 -0
  142. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  143. package/dist/stats/billing.js +41 -15
  144. package/dist/stats/db.js +159 -159
  145. package/dist/stats/eck-vars.js +9 -9
  146. package/dist/stats/price-resolver.js +28 -10
  147. package/dist/stats/query.js +139 -139
  148. package/dist/stats/role-budget.js +6 -6
  149. package/dist/stats/writer.js +51 -51
  150. package/dist/trigger/event-source.js +19 -0
  151. package/dist/trigger/feedback.js +233 -40
  152. package/dist/trigger/parser.js +55 -5
  153. package/dist/trigger/patch.js +9 -0
  154. package/dist/trigger/scheduler.js +30 -4
  155. package/dist/trigger/validation.js +20 -3
  156. package/dist/utils/cross-platform.js +30 -1
  157. package/dist/utils/ecweb-utils.js +1 -0
  158. package/dist/utils/error-dict.json +160 -160
  159. package/dist/utils/error-utils.js +61 -2
  160. package/dist/utils/evolcore-version.js +129 -0
  161. package/dist/utils/logger.js +26 -14
  162. package/dist/utils/macos-autostart.js +22 -22
  163. package/dist/utils/model-prices.jsonl +23 -23
  164. package/dist/utils/npm-ops.js +176 -35
  165. package/dist/utils/tool-summary.js +59 -3
  166. package/dist/utils/welcome.js +196 -23
  167. package/ecagent/LICENSE +21 -21
  168. package/ecagent/NOTICE +11 -11
  169. package/ecagent/README.md +61 -61
  170. package/ecagent/dist/harness/agent-harness.js +3 -0
  171. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  172. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  173. package/ecagent/dist/harness/messages.js +7 -7
  174. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  175. package/kits/docs/GUIDE.md +20 -20
  176. package/kits/docs/INDEX.md +60 -66
  177. package/kits/docs/aun/CHEATSHEET.md +19 -19
  178. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  179. package/kits/docs/channels/aun.md +69 -56
  180. package/kits/docs/channels/feishu.md +56 -56
  181. package/kits/docs/context-assembly.md +379 -367
  182. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  183. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  184. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  185. package/kits/docs/eck_templates/runtime.template.md +19 -19
  186. package/kits/docs/evolcore/INDEX.md +76 -71
  187. package/kits/docs/evolcore/agent-create.md +202 -165
  188. package/kits/docs/evolcore/agent.md +99 -67
  189. package/kits/docs/evolcore/aid.md +68 -60
  190. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  191. package/kits/docs/evolcore/config.md +241 -195
  192. package/kits/docs/evolcore/contact.md +75 -68
  193. package/kits/docs/evolcore/ctl.md +46 -46
  194. package/kits/docs/evolcore/event.md +216 -216
  195. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  196. package/kits/docs/evolcore/fs.md +110 -110
  197. package/kits/docs/evolcore/group-collaboration.md +244 -244
  198. package/kits/docs/evolcore/group-fs.md +17 -17
  199. package/kits/docs/evolcore/group-rules.md +251 -251
  200. package/kits/docs/evolcore/group.md +267 -152
  201. package/kits/docs/evolcore/menu-client.md +58 -34
  202. package/kits/docs/evolcore/model.md +50 -50
  203. package/kits/docs/evolcore/msg.md +169 -165
  204. package/kits/docs/evolcore/response.md +75 -75
  205. package/kits/docs/evolcore/rpc.md +37 -37
  206. package/kits/docs/evolcore/self-summary.md +29 -29
  207. package/kits/docs/evolcore/stats.md +83 -83
  208. package/kits/docs/evolcore/storage.md +53 -50
  209. package/kits/docs/evolcore/task.md +14 -0
  210. package/kits/docs/evolcore/trigger.md +623 -575
  211. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  212. package/kits/docs/identity/PATH_OPS.md +16 -16
  213. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  214. package/kits/docs/identity/identity-tools.md +26 -26
  215. package/kits/docs/path-registry.md +62 -62
  216. package/kits/docs/prompt-loading-architecture.md +269 -269
  217. package/kits/docs/venues/aun-group.md +45 -45
  218. package/kits/docs/venues/aun-private.md +10 -10
  219. package/kits/docs/venues/client-desktop.md +10 -10
  220. package/kits/docs/venues/client-mobile.md +10 -10
  221. package/kits/docs/venues/feishu-group.md +13 -13
  222. package/kits/docs/venues/feishu-private.md +9 -9
  223. package/kits/docs/venues/group.md +42 -25
  224. package/kits/docs/venues/private.md +10 -10
  225. package/kits/eck_manifest.auxiliary.json +43 -43
  226. package/kits/eck_manifest.json +217 -190
  227. package/kits/eck_message_manifest.json +74 -74
  228. package/kits/migrations/README-role-config-v4.md +32 -32
  229. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  230. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  231. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  232. package/kits/migrations/rename-config-file.mjs +99 -99
  233. package/kits/rules/01-overview.md +166 -162
  234. package/kits/rules/02-navigation.md +96 -96
  235. package/kits/rules/03-identity.md +36 -36
  236. package/kits/rules/04-relation.md +76 -75
  237. package/kits/rules/05-venue.md +60 -60
  238. package/kits/rules/06-channel.md +65 -63
  239. package/kits/schemas/_meta.json +53 -53
  240. package/kits/schemas/agent-config.schema.1.json +177 -177
  241. package/kits/schemas/agent-config.schema.10.json +395 -395
  242. package/kits/schemas/agent-config.schema.11.json +421 -421
  243. package/kits/schemas/agent-config.schema.12.json +451 -427
  244. package/kits/schemas/agent-config.schema.2.json +239 -239
  245. package/kits/schemas/agent-config.schema.3.json +119 -119
  246. package/kits/schemas/agent-config.schema.4.json +208 -208
  247. package/kits/schemas/agent-config.schema.5.json +326 -326
  248. package/kits/schemas/agent-config.schema.6.json +322 -322
  249. package/kits/schemas/agent-config.schema.7.json +303 -303
  250. package/kits/schemas/agent-config.schema.8.json +304 -304
  251. package/kits/schemas/agent-config.schema.9.json +364 -364
  252. package/kits/schemas/contact-book.schema.1.json +36 -36
  253. package/kits/schemas/contact-book.schema.2.json +43 -43
  254. package/kits/schemas/contact-book.schema.3.json +67 -67
  255. package/kits/schemas/daemon.schema.1.json +91 -91
  256. package/kits/schemas/daemon.schema.2.json +101 -101
  257. package/kits/schemas/daemon.schema.3.json +123 -123
  258. package/kits/schemas/daemon.schema.4.json +136 -136
  259. package/kits/schemas/daemon.schema.5.json +135 -135
  260. package/kits/schemas/daemon.schema.6.json +131 -131
  261. package/kits/schemas/defaults.schema.1.json +81 -81
  262. package/kits/schemas/defaults.schema.2.json +85 -85
  263. package/kits/schemas/defaults.schema.3.json +73 -73
  264. package/kits/schemas/defaults.schema.4.json +107 -107
  265. package/kits/schemas/defaults.schema.5.json +119 -119
  266. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  267. package/kits/schemas/migrations/README.md +30 -30
  268. package/kits/schemas/relation-config.schema.1.json +158 -158
  269. package/kits/schemas/relation-config.schema.2.json +73 -73
  270. package/kits/schemas/relation-config.schema.3.json +50 -50
  271. package/kits/schemas/relation-config.schema.4.json +47 -47
  272. package/kits/schemas/relation-config.schema.5.json +47 -47
  273. package/kits/schemas/relation-config.schema.6.json +46 -46
  274. package/kits/schemas/relation-config.schema.7.json +59 -59
  275. package/kits/schemas/relation-config.schema.8.json +103 -103
  276. package/kits/schemas/role-config.schema.1.json +215 -201
  277. package/kits/schemas/role-registry.schema.1.json +35 -35
  278. package/kits/schemas/single-session.schema.1.json +31 -31
  279. package/kits/schemas/single-session.schema.2.json +57 -57
  280. package/kits/schemas/single-session.schema.3.json +32 -32
  281. package/kits/templates/bootstrap-welcome.md +17 -17
  282. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  283. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  284. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  285. package/kits/templates/message-fragments/inject-default.md +2 -2
  286. package/kits/templates/message-fragments/item.md +2 -2
  287. package/kits/templates/roles/admin.json +92 -92
  288. package/kits/templates/roles/member.json +101 -96
  289. package/kits/templates/roles/owner.json +9 -9
  290. package/kits/templates/roles/visitor.json +90 -85
  291. package/kits/templates/system-fragments/baseagent.md +14 -14
  292. package/kits/templates/system-fragments/bootstrap.md +53 -34
  293. package/kits/templates/system-fragments/channel.md +28 -64
  294. package/kits/templates/system-fragments/commands.md +36 -28
  295. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  296. package/kits/templates/system-fragments/identity.md +13 -13
  297. package/kits/templates/system-fragments/relation.md +19 -19
  298. package/kits/templates/system-fragments/reply.md +56 -0
  299. package/kits/templates/system-fragments/session.md +59 -59
  300. package/kits/templates/system-fragments/venue.md +31 -31
  301. package/package.json +104 -98
  302. package/dist/aun/group-identity.js +0 -9
  303. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  304. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
package/dist/ipc.js CHANGED
@@ -5,13 +5,14 @@ import fs from 'fs';
5
5
  import crypto from 'crypto';
6
6
  import os from 'os';
7
7
  import path from 'path';
8
+ import { execFile } from 'node:child_process';
8
9
  import { logger } from './utils/logger.js';
9
10
  import { withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
10
11
  import { fileCache } from './core/daemon-file-cache.js';
11
12
  import { DAEMON_RUNTIME_EPOCH_ENV } from './cli/task-context.js';
12
13
  import { HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from './core/handoff/types.js';
13
14
  import { AgentReloadBusyError } from './core/agent-reload-coordinator.js';
14
- import { getWindowsInstanceSocketPathCandidates, normalizeIpcEndpoint, resolvePaths } from './paths.js';
15
+ import { getPackageRoot, getWindowsInstanceSocketPathCandidates, normalizeIpcEndpoint, resolvePaths } from './paths.js';
15
16
  import { getProcessStartTime, isSameOrOlderProcess } from './utils/process-introspect.js';
16
17
  import { classifyProcessTree, ProcessTreeSampler, readProcessMetrics } from './utils/process-tree-stats.js';
17
18
  import { readSystemMemoryUsage } from './utils/system-memory.js';
@@ -73,10 +74,42 @@ function nextIpcQueryId(cmd) {
73
74
  return `ipc-${ipcQuerySequence}`;
74
75
  }
75
76
  function requiresLocalControlToken(cmd) {
76
- // Managed bootstrap completion is authenticated by the session delegation
77
- // ticket. A standalone CLI invocation still needs the daemon control token.
78
- return LOCAL_CONTROL_COMMANDS.has(String(cmd.type ?? ''))
79
- || (cmd.type === 'evolagent.bootstrapComplete' && typeof cmd.sessionId !== 'string');
77
+ // Managed calls use task delegation. Standalone queries must not gain
78
+ // management scope merely by omitting (or emptying) sessionId.
79
+ return cmd.type === 'evolagent.initialize' || LOCAL_CONTROL_COMMANDS.has(String(cmd.type ?? ''))
80
+ || (['evolagent.bootstrapComplete', 'diag', 'handoff-status', 'handoff-list', 'handoff-trace', 'task.resume'].includes(String(cmd.type))
81
+ && !(typeof cmd.sessionId === 'string' && cmd.sessionId.trim()));
82
+ }
83
+ /** Preserve the pipe DACL and grant only the provisioned Codex users data I/O. */
84
+ function grantWindowsSandboxPipeAccess(endpoint) {
85
+ const powershell = path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
86
+ const request = Buffer.from(JSON.stringify({ action: 'pipe', path: endpoint })).toString('base64');
87
+ return new Promise((resolve, reject) => {
88
+ // Asynchronous: the ACL helper connects as a client, so Node must keep
89
+ // accepting pipe instances while it runs. Never interpolate shell source.
90
+ execFile(powershell, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass',
91
+ '-File', path.join(getPackageRoot(), 'bin', 'codex-managed-acl.ps1'), '-Request', request], { windowsHide: true, timeout: 15_000, maxBuffer: 64 * 1024 }, (error, stdout, stderr) => {
92
+ if (error) {
93
+ reject(new Error(`Windows IPC sandbox ACL setup failed: ${stderr.trim() || error.message}`));
94
+ return;
95
+ }
96
+ try {
97
+ const result = JSON.parse(stdout.replace(/^\uFEFF/, '').trim());
98
+ if (!Array.isArray(result.granted))
99
+ throw new Error('invalid ACL helper response');
100
+ if (result.granted.length) {
101
+ logger.info(`[IPC] Sandbox pipe access granted to ${result.granted.join(', ')}`);
102
+ }
103
+ else {
104
+ logger.info('[IPC] Codex sandbox accounts are not provisioned; restart daemon after Windows sandbox setup');
105
+ }
106
+ resolve();
107
+ }
108
+ catch (parseError) {
109
+ reject(parseError);
110
+ }
111
+ });
112
+ });
80
113
  }
81
114
  export function summarizeMonitorAgents(agents, aids) {
82
115
  const connectedAgents = new Set(aids.filter(aid => aid.status === 'connected').map(aid => aid.agentName || aid.aid));
@@ -102,21 +135,28 @@ export class IpcServer {
102
135
  aunAidStatsRecorder;
103
136
  menuExecutor;
104
137
  agentOperationExecutor;
138
+ initializationExecutor;
105
139
  statsProvider;
106
140
  agentStatsProvider;
107
141
  queueSnapshotProvider;
108
142
  queueActionExecutor;
109
143
  queueOperationExecutor;
110
144
  statusAuthorizationExecutor;
145
+ taskResumeExecutor;
111
146
  storageAuthorizationExecutor;
147
+ storageUploadExecutor;
112
148
  triggerExecutor;
113
149
  bootstrapCompleteExecutor;
114
150
  agentReloadExecutor;
115
151
  shutdownExecutor;
116
152
  taskRuntimeContextProvider;
153
+ taskRuntimeContextAuthorizer;
117
154
  codexPreToolUseExecutor;
118
155
  aunMsgSender;
156
+ diagExecutor;
157
+ menuClientExecutor;
119
158
  handoffReturnExecutor;
159
+ handoffRecoverReturnExecutor;
120
160
  handoffStatusExecutor;
121
161
  handoffListExecutor;
122
162
  handoffTraceExecutor;
@@ -135,6 +175,8 @@ export class IpcServer {
135
175
  controlToken;
136
176
  daemonRuntimeEpoch;
137
177
  remoteAccessService = remoteAccessService;
178
+ /** Current daemon owner used to bind locally issued ECWeb pairing codes. */
179
+ remoteAccessOwnerProvider;
138
180
  // CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
139
181
  // 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
140
182
  // procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
@@ -189,6 +231,15 @@ export class IpcServer {
189
231
  setRemoteAccessService(service) {
190
232
  this.remoteAccessService = service;
191
233
  }
234
+ /**
235
+ * Supply the live process-level owner for pairing requests that do not carry
236
+ * an explicit owner (for example `ec watch web` and the ECWeb supervisor).
237
+ * The provider is evaluated for every request so owner updates take effect
238
+ * without restarting ECWeb.
239
+ */
240
+ setRemoteAccessOwnerProvider(provider) {
241
+ this.remoteAccessOwnerProvider = provider;
242
+ }
192
243
  /** Set the process-lifetime marker used to reject stale managed children. */
193
244
  setDaemonRuntimeEpoch(epoch) {
194
245
  this.daemonRuntimeEpoch = epoch;
@@ -197,6 +248,9 @@ export class IpcServer {
197
248
  setMenuExecutor(executor) {
198
249
  this.menuExecutor = executor;
199
250
  }
251
+ setInitializationExecutor(executor) {
252
+ this.initializationExecutor = executor;
253
+ }
200
254
  setAgentOperationExecutor(executor) {
201
255
  this.agentOperationExecutor = executor;
202
256
  }
@@ -340,9 +394,15 @@ export class IpcServer {
340
394
  setStatusAuthorizationExecutor(executor) {
341
395
  this.statusAuthorizationExecutor = executor;
342
396
  }
397
+ setTaskResumeExecutor(executor) {
398
+ this.taskResumeExecutor = executor;
399
+ }
343
400
  setStorageAuthorizationExecutor(executor) {
344
401
  this.storageAuthorizationExecutor = executor;
345
402
  }
403
+ setStorageUploadExecutor(executor) {
404
+ this.storageUploadExecutor = executor;
405
+ }
346
406
  /** Inject daemon-level trigger executor for ec trigger. */
347
407
  setTriggerExecutor(executor) {
348
408
  this.triggerExecutor = executor;
@@ -355,6 +415,9 @@ export class IpcServer {
355
415
  setTaskRuntimeContextProvider(provider) {
356
416
  this.taskRuntimeContextProvider = provider;
357
417
  }
418
+ setTaskRuntimeContextAuthorizer(authorizer) {
419
+ this.taskRuntimeContextAuthorizer = authorizer;
420
+ }
358
421
  setCodexPreToolUseExecutor(executor) {
359
422
  this.codexPreToolUseExecutor = executor;
360
423
  }
@@ -362,10 +425,15 @@ export class IpcServer {
362
425
  setAunMsgSender(sender) {
363
426
  this.aunMsgSender = sender;
364
427
  }
428
+ setDiagExecutor(executor) { this.diagExecutor = executor; }
429
+ setMenuClientExecutor(executor) { this.menuClientExecutor = executor; }
365
430
  /** Inject internal handoff result return executor for `ec handoff return`. */
366
431
  setHandoffReturnExecutor(executor) {
367
432
  this.handoffReturnExecutor = executor;
368
433
  }
434
+ setHandoffRecoverReturnExecutor(executor) {
435
+ this.handoffRecoverReturnExecutor = executor;
436
+ }
369
437
  setHandoffStatusExecutor(executor) {
370
438
  this.handoffStatusExecutor = executor;
371
439
  }
@@ -773,8 +841,17 @@ export class IpcServer {
773
841
  }
774
842
  catch { }
775
843
  }
776
- logger.info(`[IPC] Listening on ${this.socketPath}`);
777
- resolve();
844
+ const permissionsReady = isNamedPipe(this.socketPath)
845
+ ? grantWindowsSandboxPipeAccess(this.socketPath)
846
+ : Promise.resolve();
847
+ void permissionsReady.then(() => {
848
+ logger.info(`[IPC] Listening on ${this.socketPath}`);
849
+ resolve();
850
+ }, error => {
851
+ this.stop();
852
+ logger.error('[IPC] Unable to configure pipe permissions:', error);
853
+ reject(error);
854
+ });
778
855
  });
779
856
  });
780
857
  }
@@ -795,11 +872,10 @@ export class IpcServer {
795
872
  if (requiresLocalControlToken(cmd) && !this.hasValidControlToken(cmd.controlToken)) {
796
873
  return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
797
874
  }
798
- if (typeof cmd.sessionId === 'string' && cmd.sessionId
799
- && typeof cmd.delegationToken === 'string' && cmd.delegationToken
875
+ if (typeof cmd.delegationToken === 'string' && cmd.delegationToken
800
876
  && this.daemonRuntimeEpoch
801
877
  && cmd.daemonRuntimeEpoch !== this.daemonRuntimeEpoch) {
802
- logger.warn(`[IPC] stale delegation rejected: session=${ipcLogToken(cmd.sessionId)} type=${ipcLogToken(cmd.type)} cause=daemon_runtime_epoch_mismatch`);
878
+ logger.warn(`[IPC] stale delegation rejected: session=${ipcLogToken(cmd.sessionId ?? cmd.originSessionId ?? cmd.actorSessionId)} type=${ipcLogToken(cmd.type)} cause=daemon_runtime_epoch_mismatch`);
803
879
  return {
804
880
  ok: false,
805
881
  code: 'DAEMON_RESTARTED',
@@ -807,6 +883,10 @@ export class IpcServer {
807
883
  };
808
884
  }
809
885
  switch (cmd.type) {
886
+ case 'diag':
887
+ return this.diagExecutor ? this.diagExecutor(cmd) : { ok: false, code: 'NOT_CONFIGURED', message: '诊断入口未配置' };
888
+ case 'menu-client.request':
889
+ return this.menuClientExecutor ? this.menuClientExecutor(cmd) : { ok: false, code: 'NOT_CONFIGURED', error: 'Managed Menu client is not configured' };
810
890
  case 'model.catalog': {
811
891
  if ((cmd.self !== undefined && typeof cmd.self !== 'string')
812
892
  || (cmd.baseagent !== undefined && !['claude', 'codex', 'ecagent', 'gemini'].includes(cmd.baseagent))) {
@@ -887,14 +967,21 @@ export class IpcServer {
887
967
  });
888
968
  }
889
969
  case 'remote-auth.pair-code': {
890
- const pair = this.remoteAccessService.getPairingCode(typeof cmd.authorizedBy === 'string' ? cmd.authorizedBy : undefined);
970
+ // IPC issues local codes. Owner-authenticated /pair and Menu paths
971
+ // supply provenance in-process, never from a caller-supplied source.
972
+ if (cmd.source !== undefined && cmd.source !== 'host-cli' && cmd.source !== 'ecweb-local') {
973
+ return { ok: false, code: 'INVALID_ARGUMENT', error: 'invalid local pairing source' };
974
+ }
975
+ const requestedOwner = typeof cmd.authorizedBy === 'string' ? cmd.authorizedBy.trim() : undefined;
976
+ const authorizedBy = requestedOwner || this.remoteAccessOwnerProvider?.();
977
+ const pair = this.remoteAccessService.getPairingCode(authorizedBy, Date.now(), cmd.source ?? 'host-cli');
891
978
  return { ok: true, ...pair };
892
979
  }
893
980
  case 'remote-auth.issue-local': {
894
981
  return this.remoteAccessService.issueLocalToken(cmd.label);
895
982
  }
896
983
  case 'remote-auth.redeem': {
897
- return this.remoteAccessService.redeemPairingCode(cmd.code, cmd.label, cmd.authorizedBy);
984
+ return this.remoteAccessService.redeemPairingCode(cmd.code, cmd.label);
898
985
  }
899
986
  case 'remote-auth.validate': {
900
987
  return { ok: true, ...this.remoteAccessService.validateToken(cmd.token) };
@@ -1068,6 +1155,25 @@ export class IpcServer {
1068
1155
  delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
1069
1156
  });
1070
1157
  }
1158
+ case 'storage.upload': {
1159
+ if (!this.storageUploadExecutor)
1160
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'storage upload not configured' };
1161
+ if (typeof cmd.sessionId !== 'string' || !cmd.sessionId
1162
+ || typeof cmd.aid !== 'string' || !cmd.aid
1163
+ || typeof cmd.localFile !== 'string' || !cmd.localFile
1164
+ || typeof cmd.remotePath !== 'string' || !cmd.remotePath) {
1165
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing sessionId, aid, localFile, or remotePath' };
1166
+ }
1167
+ return await this.storageUploadExecutor({
1168
+ sessionId: cmd.sessionId,
1169
+ aid: cmd.aid,
1170
+ localFile: cmd.localFile,
1171
+ remotePath: cmd.remotePath,
1172
+ isPublic: cmd.isPublic === true,
1173
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
1174
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
1175
+ });
1176
+ }
1071
1177
  case 'aun-aid-stats-record-outbound': {
1072
1178
  if (!this.aunAidStatsRecorder)
1073
1179
  return { ok: false, error: 'recorder not configured' };
@@ -1085,15 +1191,55 @@ export class IpcServer {
1085
1191
  return { ok: false, error: String(e?.message || e) };
1086
1192
  }
1087
1193
  }
1194
+ case 'task.resume': {
1195
+ const allowed = new Set(['type', 'targetSessionId', 'sessionId', 'delegationToken',
1196
+ 'delegationCommandHash', 'controlToken', 'daemonRuntimeEpoch', 'requestId', 'id']);
1197
+ if (Object.keys(cmd).some(key => !allowed.has(key))
1198
+ || typeof cmd.targetSessionId !== 'string' || !cmd.targetSessionId.trim()
1199
+ || (cmd.sessionId !== undefined && (typeof cmd.sessionId !== 'string' || !cmd.sessionId.trim()))) {
1200
+ return { ok: false, code: 'INVALID_REQUEST', error: 'task.resume accepts only a target session and caller credentials' };
1201
+ }
1202
+ if (cmd.sessionId && (typeof cmd.delegationToken !== 'string' || !cmd.delegationToken)) {
1203
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: 'managed task resume requires a delegation token' };
1204
+ }
1205
+ if (!this.taskResumeExecutor)
1206
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'task.resume not configured' };
1207
+ try {
1208
+ return await this.taskResumeExecutor({
1209
+ targetSessionId: cmd.targetSessionId,
1210
+ sessionId: cmd.sessionId,
1211
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
1212
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
1213
+ localControl: !cmd.sessionId && this.hasValidControlToken(cmd.controlToken),
1214
+ });
1215
+ }
1216
+ catch (error) {
1217
+ return { ok: false, code: 'TASK_RESUME_FAILED', error: error instanceof Error ? error.message : String(error) };
1218
+ }
1219
+ }
1088
1220
  case 'task-runtime-context': {
1089
1221
  if (!this.taskRuntimeContextProvider)
1090
1222
  return { ok: false, error: 'task runtime context not configured' };
1091
1223
  if (!cmd.sessionId || typeof cmd.sessionId !== 'string')
1092
1224
  return { ok: false, error: 'missing sessionId' };
1225
+ if (!this.taskRuntimeContextAuthorizer) {
1226
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: 'task runtime context authorization is not configured' };
1227
+ }
1228
+ const authorization = this.taskRuntimeContextAuthorizer({
1229
+ sessionId: cmd.sessionId,
1230
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
1231
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
1232
+ });
1233
+ if (!authorization.ok)
1234
+ return authorization;
1093
1235
  const context = this.taskRuntimeContextProvider({ sessionId: cmd.sessionId }) ?? null;
1094
1236
  return { ok: true, context };
1095
1237
  }
1096
1238
  case 'aun-msg-send': {
1239
+ if (cmd.handoffContext !== undefined && (typeof cmd.handoffContext !== 'string'
1240
+ || !cmd.handoffContext.trim() || cmd.handoffContext.length > 64 * 1024 || cmd.scope === 'group')) {
1241
+ return { ok: false, code: 'INVALID_HANDOFF_CONTEXT', error: 'Private handoff context must contain 1–65536 characters' };
1242
+ }
1097
1243
  if (!this.aunMsgSender)
1098
1244
  return { ok: false, error: 'aun msg sender not configured' };
1099
1245
  if (!cmd.aid || typeof cmd.aid !== 'string')
@@ -1148,6 +1294,7 @@ export class IpcServer {
1148
1294
  encrypt: typeof cmd.encrypt === 'boolean' ? cmd.encrypt : undefined,
1149
1295
  thread: typeof cmd.thread === 'string' ? cmd.thread : undefined,
1150
1296
  returnPolicy: cmd.returnPolicy === 'required' || cmd.returnPolicy === 'none' ? cmd.returnPolicy : undefined,
1297
+ handoffContext: cmd.handoffContext,
1151
1298
  originSessionId: typeof cmd.originSessionId === 'string' ? cmd.originSessionId : undefined,
1152
1299
  originMessageId: typeof cmd.originMessageId === 'string' ? cmd.originMessageId : undefined,
1153
1300
  delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
@@ -1177,6 +1324,28 @@ export class IpcServer {
1177
1324
  return { ok: false, error: e?.message || String(e) };
1178
1325
  }
1179
1326
  }
1327
+ case 'handoff-recover-return': {
1328
+ if (!this.handoffRecoverReturnExecutor)
1329
+ return { ok: false, error: 'handoff recovery not configured' };
1330
+ if (typeof cmd.sessionId !== 'string' || !cmd.sessionId)
1331
+ return { ok: false, code: 'HANDOFF_CALL_SESSION_INVALID', error: 'missing sessionId' };
1332
+ if (typeof cmd.handoffId !== 'string' || !cmd.handoffId)
1333
+ return { ok: false, code: 'INVALID_HANDOFF_ID', error: 'missing handoff_id' };
1334
+ if (typeof cmd.content !== 'string')
1335
+ return { ok: false, code: 'HANDOFF_RETURN_CONTENT_REQUIRED', error: 'missing content' };
1336
+ try {
1337
+ return await this.handoffRecoverReturnExecutor({
1338
+ sessionId: cmd.sessionId,
1339
+ handoffId: cmd.handoffId,
1340
+ content: cmd.content,
1341
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
1342
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
1343
+ });
1344
+ }
1345
+ catch (e) {
1346
+ return { ok: false, error: e?.message || String(e) };
1347
+ }
1348
+ }
1180
1349
  case 'handoff-status': {
1181
1350
  if (!this.handoffStatusExecutor)
1182
1351
  return { ok: false, code: 'HANDOFF_STATUS_UNAVAILABLE', error: 'handoff status not configured' };
@@ -1309,6 +1478,13 @@ export class IpcServer {
1309
1478
  }
1310
1479
  return await this.fsOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
1311
1480
  }
1481
+ case 'evolagent.initialize': {
1482
+ if (cmd.sessionId || cmd.delegationToken)
1483
+ return { ok: false, code: 'NOT_ALLOWED', error: 'Managed callers must use agent.op' };
1484
+ if (!this.initializationExecutor)
1485
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'Initialization service unavailable' };
1486
+ return this.initializationExecutor(cmd);
1487
+ }
1312
1488
  case 'agent.op': {
1313
1489
  if (!this.agentOperationExecutor)
1314
1490
  return { ok: false, code: 'NOT_CONFIGURED', error: 'agent.op not configured' };
@@ -1376,12 +1552,13 @@ export class IpcServer {
1376
1552
  if (!this.bootstrapCompleteExecutor)
1377
1553
  return { ok: false, error: 'bootstrap complete executor not configured' };
1378
1554
  try {
1379
- return await this.bootstrapCompleteExecutor(aid, {
1555
+ const auth = {
1380
1556
  sessionId: typeof cmd.sessionId === 'string' ? cmd.sessionId : undefined,
1381
1557
  delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
1382
1558
  delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
1383
1559
  controlToken: typeof cmd.controlToken === 'string' ? cmd.controlToken : undefined,
1384
- });
1560
+ };
1561
+ return await this.bootstrapCompleteExecutor(aid, auth);
1385
1562
  }
1386
1563
  catch (e) {
1387
1564
  return { ok: false, error: e?.message || String(e) };
@@ -1548,7 +1725,7 @@ export class IpcTransportError extends Error {
1548
1725
  endpoint;
1549
1726
  constructor(code, endpoint) {
1550
1727
  const hint = code === 'EPERM' || code === 'EACCES'
1551
- ? '权限被拒绝;Windows 下请检查 daemon 与 CLI 是否使用相同用户和提升权限级别。'
1728
+ ? '权限被拒绝;请检查 IPC 端点权限及沙箱隔离。Windows 下同用户进程也可能因受限令牌无法连接命名管道。'
1552
1729
  : code === 'ETIMEDOUT'
1553
1730
  ? '等待 daemon 响应超时;请求可能已执行,请先检查状态再重试。'
1554
1731
  : code === 'ENOENT' || code === 'ECONNREFUSED'
@@ -1619,6 +1796,7 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
1619
1796
  const request = {
1620
1797
  ...(delegationCommandHash ? { ...authenticatedCmd, delegationCommandHash } : authenticatedCmd),
1621
1798
  ...(process.env[DAEMON_RUNTIME_EPOCH_ENV]
1799
+ && authenticatedCmd.daemonRuntimeEpoch === undefined
1622
1800
  && typeof authenticatedCmd.delegationToken === 'string'
1623
1801
  ? { daemonRuntimeEpoch: process.env[DAEMON_RUNTIME_EPOCH_ENV] }
1624
1802
  : {}),
package/dist/paths.js CHANGED
@@ -72,11 +72,11 @@ export function aunPath() {
72
72
  export function aidsDir() {
73
73
  return path.join(resolveRoot(), 'AIDs');
74
74
  }
75
- export function aidLocalDir(aid) {
76
- return path.join(resolveRoot(), 'AIDs', aid);
75
+ export function aidLocalDir(aid, root = resolveRoot()) {
76
+ return path.join(root, 'AIDs', aid);
77
77
  }
78
- export function agentMdPath(aid) {
79
- return path.join(resolveRoot(), 'AIDs', aid, 'agent.md');
78
+ export function agentMdPath(aid, root = resolveRoot()) {
79
+ return path.join(root, 'AIDs', aid, 'agent.md');
80
80
  }
81
81
  // ── per-agent 路径(参数化,不进 resolvePaths() 的固定 map)──
82
82
  export function agentDir(aid) {
@@ -94,6 +94,10 @@ export function agentContactOperations(aid) {
94
94
  export function agentPersonalDir(aid) {
95
95
  return path.join(agentDir(aid), 'personal');
96
96
  }
97
+ /** Agent-owned Bootstrap completion welcome template. */
98
+ export function agentWelcomePath(aid) {
99
+ return path.join(agentPersonalDir(aid), 'welcome.md');
100
+ }
97
101
  export function agentRolesDir(aid) {
98
102
  return path.join(agentDir(aid), 'roles');
99
103
  }
@@ -106,6 +110,12 @@ export function agentRoleConfig(aid, roleId) {
106
110
  export function agentRelationsDir(aid) {
107
111
  return path.join(agentDir(aid), 'relations');
108
112
  }
113
+ export function agentRelationDir(aid, peerKey) {
114
+ return path.join(agentRelationsDir(aid), peerKey);
115
+ }
116
+ export function agentRelationSharedDir(aid, peerKey) {
117
+ return path.join(agentRelationDir(aid, peerKey), 'shared');
118
+ }
109
119
  /** @deprecated Use agentRelationsDir instead */
110
120
  export function agentIdentitiesDir(aid) {
111
121
  return agentRelationsDir(aid);
@@ -128,6 +138,9 @@ export function agentDataCacheDir(aid) {
128
138
  export function agentHandoffDir(aid) {
129
139
  return path.join(agentDataDir(aid), 'handoff');
130
140
  }
141
+ export function agentGroupInviteReviewsPath(aid) {
142
+ return path.join(agentDataDir(aid), 'aun', 'group-invite-reviews.json');
143
+ }
131
144
  export function agentOutboxPath(aid) {
132
145
  return path.join(resolvePaths().outboxDir, encodeURIComponent(aid), 'outbox.jsonl');
133
146
  }
package/dist/product.js CHANGED
@@ -14,6 +14,8 @@ export const MIN_ECWEB_VERSION = '0.0.8';
14
14
  export const DAEMON_CONFIG_FILENAME = 'daemon.json';
15
15
  export const DAEMON_SCHEMA_NAME = 'daemon';
16
16
  export const RUNTIME_HOME_ENV = 'EVOLCORE_HOME';
17
+ export const SESSION_ID_ENV = 'EVOLCORE_SESSION_ID';
18
+ export const SELF_AID_ENV = 'EVOLCORE_SELF_AID';
17
19
  export const DEFAULT_RUNTIME_HOME_DIRNAME = '.evolcore';
18
20
  export const INSTANCE_SOCKET_FILENAME = 'daemon.sock';
19
21
  export const WINDOWS_PIPE_PREFIX = 'daemon';
@@ -12,7 +12,18 @@ const STATE_KEY = 'proactive';
12
12
  const TOOL_REPORT_INTERVAL = 10;
13
13
  const SEND_RECEIPT_RE = /["']?(?:message[_ -]?id|handoff[_ -]?id)["']?\s*[:=]\s*["']?([A-Za-z0-9][A-Za-z0-9._:-]*)/gi;
14
14
  const HANDOFF_TEXT_RECEIPT_RE = /\bhandoff\s+(h-[A-Za-z0-9._-]+)/i;
15
- function hasSuccessfulSendReceipt(result, isError) {
15
+ const RETURN_CONTENT_RE = /["']?(?:return[_ -]?content|returnContent)["']?\s*[:=]\s*["']?([^}\n,]*)/i;
16
+ function hasRequiredReturnFlag(toolInput) {
17
+ if (!toolInput || typeof toolInput !== 'object')
18
+ return false;
19
+ const input = toolInput;
20
+ const commandValues = ['command', 'cmd', 'script'].flatMap(key => {
21
+ const value = input[key];
22
+ return typeof value === 'string' ? [value] : [];
23
+ });
24
+ return commandValues.some(command => /(?:^|\s)--return(?:=|\s+)['"]?required(?:['"]?)(?=\s|$)/i.test(command));
25
+ }
26
+ function hasSuccessfulSendReceipt(result, isError, requiresBusinessReturn = false) {
16
27
  if (isError === true)
17
28
  return false;
18
29
  const hasReceiptValue = (value) => {
@@ -40,6 +51,41 @@ function hasSuccessfulSendReceipt(result, isError) {
40
51
  };
41
52
  if (hasExplicitFailure(result, new Set()))
42
53
  return false;
54
+ const isBusinessReturn = (value) => {
55
+ if (typeof value !== 'string')
56
+ return false;
57
+ const normalized = value.trim();
58
+ if (!normalized || /^(?:null|undefined|-)$/.test(normalized))
59
+ return false;
60
+ // Contact admission/refusal is a real reply, but it is not task completion.
61
+ if (/(?:当前不接受陌生人消息|可发送\s*\/request|contact\s+(?:required|request)|stranger message)/i.test(normalized))
62
+ return false;
63
+ return true;
64
+ };
65
+ const inspectBusinessReturn = (value, seen) => {
66
+ if (!value || typeof value !== 'object')
67
+ return false;
68
+ if (seen.has(value))
69
+ return false;
70
+ seen.add(value);
71
+ if (Array.isArray(value))
72
+ return value.some(item => inspectBusinessReturn(item, seen));
73
+ for (const [key, item] of Object.entries(value)) {
74
+ if ((key === 'return_content' || key === 'returnContent') && isBusinessReturn(item))
75
+ return true;
76
+ if (inspectBusinessReturn(item, seen))
77
+ return true;
78
+ }
79
+ return false;
80
+ };
81
+ if (requiresBusinessReturn) {
82
+ if (inspectBusinessReturn(result, new Set()))
83
+ return true;
84
+ const returnText = typeof result === 'string'
85
+ ? result.match(RETURN_CONTENT_RE)?.[1]
86
+ : undefined;
87
+ return isBusinessReturn(returnText);
88
+ }
43
89
  const inspect = (value, seen) => {
44
90
  if (!value || typeof value !== 'object')
45
91
  return false;
@@ -153,6 +199,11 @@ export const proactiveFlow = {
153
199
  // Codex only invokes this hook for local tool paths. Hosted tools stay
154
200
  // enabled and do not satisfy or clear the pending first-send gate.
155
201
  const isSendCommand = ctx.isSendCommand(toolName, toolInput);
202
+ if (!isSendCommand && ((state.firstSendRequired && !state.firstToolDone) || state.toolReportPending)) {
203
+ const sendFailure = ctx.describeSendCommandFailure?.(toolName, toolInput);
204
+ if (sendFailure)
205
+ return { block: true, policyCode: 'proactive_send_invalid', reason: sendFailure };
206
+ }
156
207
  if (state.firstSendRequired && !state.firstToolDone) {
157
208
  if (!isSendCommand) {
158
209
  const cmdHint = state.chatType === 'group' ? 'ec group send' : 'ec msg send';
@@ -221,7 +272,13 @@ export const proactiveFlow = {
221
272
  return;
222
273
  if (!ctx.isSendCommand(ctx.toolName, ctx.toolInput))
223
274
  return;
224
- if (!hasSuccessfulSendReceipt(ctx.result, ctx.isError || !!ctx.error))
275
+ const requiresBusinessReturn = hasRequiredReturnFlag(ctx.toolInput)
276
+ && (typeof ctx.result === 'string'
277
+ ? HANDOFF_TEXT_RECEIPT_RE.test(ctx.result)
278
+ : !!ctx.result && typeof ctx.result === 'object'
279
+ && ('handoff_id' in ctx.result || 'handoffId' in ctx.result));
280
+ HANDOFF_TEXT_RECEIPT_RE.lastIndex = 0;
281
+ if (!hasSuccessfulSendReceipt(ctx.result, ctx.isError || !!ctx.error, requiresBusinessReturn))
225
282
  return;
226
283
  if (state.firstSendRequired && !state.firstToolDone)
227
284
  state.firstToolDone = true;
@@ -5,6 +5,7 @@
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import { getPackageRoot } from '../paths.js';
8
+ import { parseModelId, stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
8
9
  // 模型参数缓存(进程内,5 分钟 TTL)
9
10
  let _parameterCache = null;
10
11
  let _parameterCacheTs = 0;
@@ -79,21 +80,36 @@ function loadPrices(evolcoreHome) {
79
80
  _priceCacheTs = now;
80
81
  return _priceCache;
81
82
  }
83
+ /**
84
+ * 按优先级列出一个模型 ID 的查表候选键。
85
+ *
86
+ * 上下文窗口变体(`claude-opus-5[1m]`)与基础模型同价,价表只需一条基础条目;
87
+ * 但仍先试精确匹配,好让用户能给某个变体单独定价。
88
+ */
89
+ export function modelLookupKeys(evolcoreHome, model) {
90
+ const keys = [];
91
+ const push = (key) => { if (key && !keys.includes(key))
92
+ keys.push(key); };
93
+ push(model);
94
+ push(resolveCanonicalModel(evolcoreHome, model));
95
+ const base = stripContextWindowSuffix(model);
96
+ if (base !== model) {
97
+ push(base);
98
+ push(resolveCanonicalModel(evolcoreHome, base));
99
+ }
100
+ return keys;
101
+ }
82
102
  /** 取 model 在 ts 时刻生效的价格行(effective_from <= ts 中最新的一条)。
83
- * 精确匹配失败时,通过 model-aliases.jsonl 映射到规范 ID 再查一次。 */
103
+ * 精确匹配失败时,依次回落到 alias 规范 ID 和去掉窗口后缀的基础 ID。 */
84
104
  export function resolvePriceRow(evolcoreHome, model, ts) {
85
105
  const prices = loadPrices(evolcoreHome);
86
- let candidates = prices.filter(p => p.model === model && p.effective_from <= ts);
87
- if (!candidates.length) {
88
- // fallback: alias → canonical
89
- const canonical = resolveCanonicalModel(evolcoreHome, model);
90
- if (canonical !== model) {
91
- candidates = prices.filter(p => p.model === canonical && p.effective_from <= ts);
106
+ for (const key of modelLookupKeys(evolcoreHome, model)) {
107
+ const candidates = prices.filter(p => p.model === key && p.effective_from <= ts);
108
+ if (candidates.length) {
109
+ return candidates.reduce((a, b) => a.effective_from >= b.effective_from ? a : b);
92
110
  }
93
111
  }
94
- if (!candidates.length)
95
- return null;
96
- return candidates.reduce((a, b) => a.effective_from >= b.effective_from ? a : b);
112
+ return null;
97
113
  }
98
114
  export const BILLING_FNS = {
99
115
  // 通用 per-token(Claude / OpenAI 兼容 / Kimi / MiniMax)
@@ -164,14 +180,24 @@ function loadSpecs(evolcoreHome) {
164
180
  _specCacheTs = now;
165
181
  return _specCache;
166
182
  }
167
- /** 取模型在 ts 时刻的能力参数。找不到时返回默认值。 */
183
+ /** 取模型在 ts 时刻的能力参数。找不到时返回默认值。
184
+ * 带窗口后缀的 ID 复用基础模型的参数行,但窗口由后缀决定(网关的权威声明)。 */
168
185
  export function resolveModelSpec(evolcoreHome, model, ts) {
169
186
  const specs = loadSpecs(evolcoreHome);
170
187
  const t = ts ?? Date.now();
171
- const candidates = specs.filter(s => s.model === model && s.effective_from <= t);
172
- if (candidates.length) {
173
- return candidates.reduce((a, b) => a.effective_from >= b.effective_from ? a : b);
188
+ const suffixWindow = parseModelId(model).contextWindow;
189
+ for (const key of modelLookupKeys(evolcoreHome, model)) {
190
+ const candidates = specs.filter(s => s.model === key && s.effective_from <= t);
191
+ if (!candidates.length)
192
+ continue;
193
+ const row = candidates.reduce((a, b) => a.effective_from >= b.effective_from ? a : b);
194
+ if (suffixWindow === undefined || suffixWindow === row.context_window)
195
+ return row;
196
+ // 后缀声明的窗口覆盖基础行,输入上限按原行的预留比例等比缩放。
197
+ const ratio = row.context_window > 0 ? row.max_input_tokens / row.context_window : 0.9;
198
+ return { ...row, model, context_window: suffixWindow, max_input_tokens: Math.floor(suffixWindow * ratio) };
174
199
  }
175
200
  // 默认值
176
- return { model, effective_from: 0, context_window: 200000, max_input_tokens: 180000, max_output_tokens: 8192 };
201
+ const contextWindow = suffixWindow ?? 200000;
202
+ return { model, effective_from: 0, context_window: contextWindow, max_input_tokens: Math.floor(contextWindow * 0.9), max_output_tokens: 8192 };
177
203
  }