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
@@ -11,6 +11,8 @@ const HANDOFF_STALE_WARN_MS = 72 * 60 * 60 * 1000;
11
11
  const HANDOFF_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
12
12
  const HANDOFF_LARGE_AGGREGATE_COUNT = 10;
13
13
  const HANDOFF_LARGE_AGGREGATE_CHARS = 50_000;
14
+ /** Keep origin-side context bounded; the persisted handoff remains complete. */
15
+ const HANDOFF_ORIGIN_REQUEST_MAX_CHARS = 800;
14
16
  export class HandoffTargetBlockedError extends Error {
15
17
  blockingHandoffId;
16
18
  attentionReason;
@@ -93,6 +95,8 @@ export class HandoffRuntime {
93
95
  }, undefined, input.to, targetChatType, origin.baseagent, input.selfAid, 'aun', targetChatType === 'group' ? 'group' : 'agent');
94
96
  throwIfAborted(input.signal);
95
97
  if (target.id === origin.id) {
98
+ if (input.handoffContext !== undefined)
99
+ throw new Error('handoff context requires a different target session');
96
100
  return { crossSession: false, targetSession: target };
97
101
  }
98
102
  if (!input.explicitReturnPolicy)
@@ -119,6 +123,7 @@ export class HandoffRuntime {
119
123
  payload: input.payload,
120
124
  encrypt: input.encrypt,
121
125
  contextOnly: input.contextOnly,
126
+ handoffContext: input.handoffContext,
122
127
  delivery: targetChatType === 'group'
123
128
  ? { chatType: 'group', groupId: input.to }
124
129
  : { chatType: 'private' },
@@ -193,28 +198,108 @@ export class HandoffRuntime {
193
198
  }
194
199
  async returnHandoff(input) {
195
200
  const returnCausation = deriveCausation(normalizeCausation(input.causation) ?? createRootCausation());
196
- const response = this.store.returnHandoff({ ...input, returnCausation });
201
+ const response = await this.mutexes.forKey(this.returnKey(input.selfAid, input.handoffId ?? input.currentSessionId))
202
+ .runExclusive(async () => this.store.returnHandoff({ ...input, returnCausation }));
197
203
  if (response.ok && !response.idempotent) {
198
204
  recordCausationSpan(returnCausation, 'handoff.response', {
199
205
  status: 'completed',
200
- refs: { handoffId: response.handoff_id, sessionId: input.currentSessionId },
206
+ refs: {
207
+ handoffId: response.handoff_id,
208
+ sessionId: input.currentSessionId,
209
+ ...(response.result_id ? { resultId: response.result_id } : {}),
210
+ },
211
+ });
212
+ }
213
+ if (response.ok && !response.idempotent && response.result_id) {
214
+ void this.enqueueOrigin(input.selfAid, response.handoff_id, response.result_id);
215
+ }
216
+ else if (response.ok && !response.idempotent && response.state === 'origin_queued') {
217
+ void this.enqueueOrigin(input.selfAid, response.handoff_id);
218
+ }
219
+ return response;
220
+ }
221
+ /** Recover a return from the original owner session after target-session replacement. */
222
+ async recoverReturnHandoff(input) {
223
+ const instance = this.store.get(input.selfAid, input.handoffId);
224
+ if (!instance || instance.origin_session_id !== input.recoverySessionId) {
225
+ return {
226
+ ok: false,
227
+ code: 'HANDOFF_NOT_FOUND',
228
+ error: 'handoff is not owned by the recovery session',
229
+ handoff_id: input.handoffId,
230
+ };
231
+ }
232
+ const recovery = {
233
+ actorId: input.actorId,
234
+ recoverySessionId: input.recoverySessionId,
235
+ };
236
+ const returnCausation = deriveCausation(normalizeCausation(input.causation) ?? createRootCausation());
237
+ const response = await this.mutexes.forKey(this.returnKey(input.selfAid, input.handoffId))
238
+ .runExclusive(async () => this.store.returnHandoff({
239
+ selfAid: input.selfAid,
240
+ currentSessionId: input.recoverySessionId,
241
+ handoffId: input.handoffId,
242
+ content: input.content,
243
+ returnCausation,
244
+ recovery,
245
+ }));
246
+ if (response.ok && !response.idempotent) {
247
+ recordCausationSpan(returnCausation, 'handoff.response', {
248
+ status: 'completed',
249
+ refs: {
250
+ handoffId: response.handoff_id,
251
+ sessionId: input.recoverySessionId,
252
+ ...(response.result_id ? { resultId: response.result_id } : {}),
253
+ },
201
254
  });
202
255
  }
203
- if (response.ok && !response.idempotent && response.state === 'origin_queued') {
256
+ if (response.ok && !response.idempotent && response.result_id) {
257
+ void this.enqueueOrigin(input.selfAid, response.handoff_id, response.result_id);
258
+ }
259
+ else if (response.ok && !response.idempotent && response.state === 'origin_queued') {
204
260
  void this.enqueueOrigin(input.selfAid, response.handoff_id);
205
261
  }
206
262
  return response;
207
263
  }
208
- async enqueueOrigin(selfAid, handoffId) {
264
+ async enqueueOrigin(selfAid, handoffId, resultId) {
265
+ const operationSequence = ++this.lifecycleSequence;
266
+ await this.mutexes.forKey(this.returnKey(selfAid, handoffId)).runExclusive(async () => {
267
+ await this.enqueueOriginUnlocked(selfAid, handoffId, resultId, operationSequence);
268
+ });
269
+ }
270
+ async enqueueOriginUnlocked(selfAid, handoffId, resultId, operationSequence) {
209
271
  const instance = this.store.get(selfAid, handoffId);
210
- if (!instance || instance.state !== 'origin_queued' || !instance.return_content)
272
+ const result = resultId
273
+ ? instance?.return_results?.find(item => item.result_id === resultId)
274
+ : instance?.return_results?.find(item => item.state === 'origin_queued');
275
+ const resultStream = Array.isArray(instance?.return_results);
276
+ if (resultStream) {
277
+ if (!instance || !result || result.state !== 'origin_queued' || !result.content)
278
+ return;
279
+ }
280
+ else if (!instance || instance.state !== 'origin_queued' || !instance.return_content)
281
+ return;
282
+ const deliveryContent = result?.content ?? instance?.return_content;
283
+ if (!instance || !deliveryContent)
211
284
  return;
212
285
  const origin = await this.sessionManager.getSessionById(instance.origin_session_id);
213
286
  if (!origin) {
214
- this.store.recordOriginEnqueueFailed(selfAid, handoffId, 'origin session not found');
287
+ if (result)
288
+ this.store.recordOriginResultEnqueueFailed(selfAid, handoffId, result.result_id, 'origin session not found');
289
+ else
290
+ this.store.recordOriginEnqueueFailed(selfAid, handoffId, 'origin session not found');
291
+ return;
292
+ }
293
+ if ((this.sessionDiscardSequence.get(this.sessionKey(selfAid, instance.origin_session_id)) ?? 0) > operationSequence) {
215
294
  return;
216
295
  }
217
- const messageId = `origin-deliver:${handoffId}:${origin.id}`;
296
+ const deliverable = this.store.get(selfAid, handoffId);
297
+ const deliveryResultId = result?.result_id ?? resultId;
298
+ if (!this.isOriginDeliveryPending(deliverable, deliveryResultId))
299
+ return;
300
+ const messageId = result
301
+ ? `origin-deliver:${handoffId}:${result.result_id}:${origin.id}`
302
+ : `origin-deliver:${handoffId}:${origin.id}`;
218
303
  const message = {
219
304
  channel: origin.channel,
220
305
  channelType: origin.channelType,
@@ -226,21 +311,24 @@ export class HandoffRuntime {
226
311
  peerId: origin.channelId,
227
312
  peerName: origin.metadata?.peerName || origin.channelId,
228
313
  peerType: origin.metadata?.peerType,
229
- content: instance.return_content,
314
+ content: deliveryContent,
230
315
  messageId,
231
316
  timestamp: Date.now(),
232
317
  source: 'handoff',
233
318
  groupId: origin.chatType === 'group' ? (origin.metadata?.groupId || origin.channelId) : undefined,
234
319
  replyContext: replyContextFromSession(origin),
235
320
  boundSessionId: origin.id,
236
- causation: instance.return_causation
237
- ? deriveCausation(instance.return_causation)
238
- : instance.causation
239
- ? deriveCausation(instance.causation)
240
- : undefined,
321
+ causation: result?.return_causation
322
+ ? deriveCausation(result.return_causation)
323
+ : instance.return_causation
324
+ ? deriveCausation(instance.return_causation)
325
+ : instance.causation
326
+ ? deriveCausation(instance.causation)
327
+ : undefined,
241
328
  handoffDelivery: {
242
329
  direction: 'origin',
243
330
  handoffId,
331
+ ...(result ? { resultId: result.result_id } : {}),
244
332
  ...(instance.origin_authorization
245
333
  ? { originAuthorization: { ...instance.origin_authorization } }
246
334
  : {}),
@@ -255,19 +343,42 @@ export class HandoffRuntime {
255
343
  sessionKeyField: origin.sessionKey,
256
344
  selfAID: origin.selfAID,
257
345
  onPersisted: () => {
258
- if (this.store.get(selfAid, handoffId)?.state !== 'origin_queued') {
259
- throw new Error(`handoff is no longer deliverable: ${handoffId}`);
346
+ const current = this.store.get(selfAid, handoffId);
347
+ if (result) {
348
+ if (!this.isOriginDeliveryPending(current, result.result_id)) {
349
+ throw new Error(`handoff result is no longer deliverable: ${handoffId}/${result.result_id}`);
350
+ }
351
+ this.store.recordOriginResultEnqueued(selfAid, handoffId, result.result_id, messageId);
352
+ }
353
+ else {
354
+ if (current?.state !== 'origin_queued') {
355
+ throw new Error(`handoff is no longer deliverable: ${handoffId}`);
356
+ }
357
+ this.store.recordOriginEnqueued(selfAid, handoffId, messageId);
260
358
  }
261
- this.store.recordOriginEnqueued(selfAid, handoffId, messageId);
262
359
  },
263
360
  });
264
361
  }
265
362
  catch (error) {
266
- if (this.store.get(selfAid, handoffId)?.state === 'origin_queued') {
363
+ const current = this.store.get(selfAid, handoffId);
364
+ if (result) {
365
+ if (current?.return_results?.find(item => item.result_id === result.result_id)?.state === 'origin_queued') {
366
+ this.store.recordOriginResultEnqueueFailed(selfAid, handoffId, result.result_id, error instanceof Error ? error.message : String(error));
367
+ }
368
+ }
369
+ else if (current?.state === 'origin_queued') {
267
370
  this.store.recordOriginEnqueueFailed(selfAid, handoffId, error instanceof Error ? error.message : String(error));
268
371
  }
269
372
  }
270
373
  }
374
+ isOriginDeliveryPending(instance, resultId) {
375
+ if (!instance || instance.state === 'discarded' || instance.state === 'failed')
376
+ return false;
377
+ if (resultId) {
378
+ return instance.return_results?.find(item => item.result_id === resultId)?.state === 'origin_queued';
379
+ }
380
+ return instance.state === 'origin_queued' && !!instance.return_content;
381
+ }
271
382
  buildPromptItem(message, fallbackPeerRole) {
272
383
  return this.buildPromptItems(message, fallbackPeerRole)[0] ?? null;
273
384
  }
@@ -297,6 +408,17 @@ export class HandoffRuntime {
297
408
  if (instance.state !== expectedState)
298
409
  continue;
299
410
  }
411
+ else if (Array.isArray(instance.return_results)) {
412
+ const resultIds = delivery.resultIds?.length
413
+ ? delivery.resultIds
414
+ : (delivery.resultId ? [delivery.resultId] : []);
415
+ const results = resultIds
416
+ .map(resultId => instance.return_results.find(result => result.result_id === resultId))
417
+ .filter((result) => !!result && result.state === 'origin_delivered');
418
+ if (results.length === 0)
419
+ continue;
420
+ instance.__deliveryResults = results;
421
+ }
300
422
  else if (instance.state !== 'origin_delivered') {
301
423
  continue;
302
424
  }
@@ -307,6 +429,13 @@ export class HandoffRuntime {
307
429
  if (delivery.direction === 'origin') {
308
430
  this.warnIfLargeAggregate(message, instances, 'origin');
309
431
  const first = instances[0];
432
+ const deliveryResultRefs = instances.flatMap(instance => ((instance.__deliveryResults ?? [])
433
+ .map(result => ({ handoffId: instance.handoff_id, resultId: result.result_id, result }))));
434
+ const originRequestSummaries = new Map();
435
+ const originRequestShown = new Set();
436
+ for (const instance of instances) {
437
+ originRequestSummaries.set(instance.handoff_id, this.originRequestSummary(instance));
438
+ }
310
439
  return [{
311
440
  kind: 'handoff',
312
441
  peerId: message.peerId,
@@ -329,11 +458,26 @@ export class HandoffRuntime {
329
458
  kind: 'response_to_origin',
330
459
  handoffId: first.handoff_id,
331
460
  handoffIds: instances.map(instance => instance.handoff_id),
332
- originEntries: instances.map(instance => ({
333
- previousContent: this.requestSummary(instance),
334
- responseContent: instance.return_content ?? '',
335
- previousMessageId: instance.target_message_id,
336
- })),
461
+ ...(deliveryResultRefs.length > 0
462
+ ? {
463
+ resultIds: deliveryResultRefs.map(({ result }) => result.result_id),
464
+ resultRefs: deliveryResultRefs.map(({ handoffId, result }) => ({ handoffId, resultId: result.result_id })),
465
+ }
466
+ : {}),
467
+ originEntries: deliveryResultRefs.length > 0
468
+ ? deliveryResultRefs.map(({ result, handoffId }) => ({
469
+ previousContent: originRequestShown.has(handoffId)
470
+ ? '[同一 handoff 的后续结果,原请求见上方]'
471
+ : (originRequestShown.add(handoffId), originRequestSummaries.get(handoffId) ?? this.originRequestSummary(first)),
472
+ responseContent: result.content,
473
+ previousMessageId: (instances.find(instance => instance.handoff_id === handoffId) ?? first).target_message_id,
474
+ resultId: result.result_id,
475
+ }))
476
+ : instances.map(instance => ({
477
+ previousContent: originRequestSummaries.get(instance.handoff_id) ?? this.originRequestSummary(instance),
478
+ responseContent: instance.return_content ?? '',
479
+ previousMessageId: instance.target_message_id,
480
+ })),
337
481
  previousContent: this.requestSummary(first),
338
482
  previousMessageId: first.target_message_id,
339
483
  returnPolicy: first.return_policy,
@@ -385,9 +529,14 @@ export class HandoffRuntime {
385
529
  },
386
530
  }];
387
531
  }
388
- completeOriginContext(selfAid, handoffId) {
532
+ completeOriginContext(selfAid, handoffId, resultId) {
389
533
  const instance = this.store.get(selfAid, handoffId);
390
- if (instance?.state === 'origin_delivered')
534
+ if (resultId) {
535
+ if (instance?.return_results?.some(result => result.result_id === resultId && result.state === 'origin_delivered')) {
536
+ this.store.completeOriginResult(selfAid, handoffId, resultId);
537
+ }
538
+ }
539
+ else if (instance?.state === 'origin_delivered')
391
540
  this.store.completeOriginContext(selfAid, handoffId);
392
541
  }
393
542
  completeTargetContext(selfAid, handoffId) {
@@ -535,6 +684,11 @@ export class HandoffRuntime {
535
684
  else if (instance.state === 'origin_queued') {
536
685
  originEnqueues.push(this.enqueueOrigin(selfAid, instance.handoff_id));
537
686
  }
687
+ else if (Array.isArray(instance.return_results)) {
688
+ for (const result of instance.return_results.filter(item => item.state === 'origin_queued')) {
689
+ originEnqueues.push(this.enqueueOrigin(selfAid, instance.handoff_id, result.result_id));
690
+ }
691
+ }
538
692
  }
539
693
  }
540
694
  await Promise.all(originEnqueues);
@@ -559,10 +713,20 @@ export class HandoffRuntime {
559
713
  return true;
560
714
  }
561
715
  async cancelOriginDelivery(instance) {
716
+ const resultDeliveries = instance.return_results?.filter(result => (result.state === 'origin_queued' || result.state === 'origin_delivered')) ?? [];
717
+ if (resultDeliveries.length > 0) {
718
+ for (const result of resultDeliveries) {
719
+ await this.cancelOriginMessage(instance, result.origin_delivery_message_id
720
+ ?? `origin-deliver:${instance.handoff_id}:${result.result_id}:${instance.origin_session_id}`);
721
+ }
722
+ return;
723
+ }
562
724
  if (instance.state !== 'origin_queued' && instance.state !== 'origin_delivered')
563
725
  return;
564
- const messageId = instance.origin_delivery_message_id
565
- ?? `origin-deliver:${instance.handoff_id}:${instance.origin_session_id}`;
726
+ await this.cancelOriginMessage(instance, instance.origin_delivery_message_id
727
+ ?? `origin-deliver:${instance.handoff_id}:${instance.origin_session_id}`);
728
+ }
729
+ async cancelOriginMessage(instance, messageId) {
566
730
  const queue = this.messageQueue;
567
731
  try {
568
732
  const queued = queue.cancelMessageByIdInSession?.(instance.origin_session_id, messageId) ?? false;
@@ -592,6 +756,9 @@ export class HandoffRuntime {
592
756
  sessionKey(selfAid, sessionId) {
593
757
  return `${selfAid}\u0000${sessionId}`;
594
758
  }
759
+ returnKey(selfAid, handoffOrSessionId) {
760
+ return `${selfAid}\u0000handoff-return\u0000${handoffOrSessionId}`;
761
+ }
595
762
  warnIfLargeAggregate(message, instances, direction) {
596
763
  const chars = message.content.length
597
764
  + instances.reduce((total, instance) => total + this.requestSummary(instance).length, 0);
@@ -606,7 +773,18 @@ export class HandoffRuntime {
606
773
  + ` count=${instances.length} chars=${chars} responseMessage=${message.messageId ?? '<none>'}`);
607
774
  }
608
775
  requestSummary(instance) {
609
- return classifyAunPayloadForLog(instance.request.payload).content || '[payload]';
776
+ const sent = classifyAunPayloadForLog(instance.request.payload).content || '[payload]';
777
+ return instance.request.context ? `${sent}\n\n[本地交接上下文]\n${instance.request.context}` : sent;
778
+ }
779
+ originRequestSummary(instance) {
780
+ const content = this.requestSummary(instance);
781
+ if (content.length <= HANDOFF_ORIGIN_REQUEST_MAX_CHARS)
782
+ return content;
783
+ const marker = `\n\n[原请求过长,回流提示仅保留首尾;完整内容共 ${content.length} 字,已省略 ${content.length - HANDOFF_ORIGIN_REQUEST_MAX_CHARS} 字]\n\n`;
784
+ const available = HANDOFF_ORIGIN_REQUEST_MAX_CHARS - marker.length;
785
+ const head = Math.ceil(available * 0.6);
786
+ const tail = available - head;
787
+ return `${content.slice(0, head)}${marker}${content.slice(-tail)}`;
610
788
  }
611
789
  }
612
790
  function throwIfAborted(signal) {
@@ -33,6 +33,9 @@ function normalizeReturnContent(content) {
33
33
  function contentHash(content) {
34
34
  return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
35
35
  }
36
+ function newReturnResultId() {
37
+ return `hr-${crypto.randomBytes(8).toString('hex')}`;
38
+ }
36
39
  function boundedQueryLimit(limit) {
37
40
  if (typeof limit !== 'number' || !Number.isInteger(limit))
38
41
  return HANDOFF_QUERY_DEFAULT_LIMIT;
@@ -111,6 +114,9 @@ export class HandoffStore {
111
114
  fs.renameSync(pendingPath, filePath);
112
115
  }
113
116
  create(input) {
117
+ if (input.handoffContext !== undefined && (typeof input.handoffContext !== 'string'
118
+ || !input.handoffContext.trim() || input.handoffContext.length > 64 * 1024))
119
+ throw new Error('invalid handoff context');
114
120
  const now = input.now ?? Date.now();
115
121
  const handoffId = newHandoffId();
116
122
  const instance = {
@@ -123,6 +129,7 @@ export class HandoffStore {
123
129
  payload: clonePayload(input.payload),
124
130
  encrypt: input.encrypt,
125
131
  ...(input.contextOnly ? { contextOnly: true } : {}),
132
+ ...(input.handoffContext ? { context: input.handoffContext } : {}),
126
133
  ...(input.delivery ? { delivery: input.delivery } : {}),
127
134
  },
128
135
  ...(input.originAuthorization
@@ -143,6 +150,7 @@ export class HandoffStore {
143
150
  return_content: null,
144
151
  return_content_hash: null,
145
152
  origin_delivery_message_id: null,
153
+ ...((input.returnPolicy ?? 'required') === 'required' && !input.contextOnly ? { return_results: [] } : {}),
146
154
  attention_required: false,
147
155
  attention_reason: null,
148
156
  version: 1,
@@ -401,7 +409,12 @@ export class HandoffStore {
401
409
  if ('ok' in selected)
402
410
  return selected;
403
411
  const { instance, selectedBy } = selected;
404
- if (instance.target_session_id !== input.currentSessionId || instance.consumed_target_session_id !== input.currentSessionId) {
412
+ const directSessionMatch = instance.target_session_id === input.currentSessionId
413
+ && instance.consumed_target_session_id === input.currentSessionId;
414
+ const recoveryAuthorized = !!input.recovery
415
+ && input.recovery.recoverySessionId === input.currentSessionId
416
+ && input.recovery.actorId === instance.origin_authorization?.actorId;
417
+ if (!directSessionMatch && !recoveryAuthorized) {
405
418
  return {
406
419
  ok: false,
407
420
  code: 'HANDOFF_TARGET_SESSION_MISMATCH',
@@ -430,6 +443,110 @@ export class HandoffStore {
430
443
  };
431
444
  }
432
445
  const hash = contentHash(normalized);
446
+ const previousState = instance.state;
447
+ // New required handoffs expose an append-only result stream. The parent
448
+ // remains return_pending while each result is delivered and consumed.
449
+ if (instance.return_policy === 'required' && Array.isArray(instance.return_results)) {
450
+ if (instance.state !== 'return_pending' && instance.state !== 'completed') {
451
+ const existing = instance.return_results.find(result => result.content_hash === hash);
452
+ if (existing) {
453
+ return {
454
+ ok: true,
455
+ code: 'HANDOFF_RETURN_ALREADY_APPLIED',
456
+ handoff_id: instance.handoff_id,
457
+ selected_by: selectedBy,
458
+ previous_state: instance.state,
459
+ state: instance.state,
460
+ idempotent: true,
461
+ result_id: existing.result_id,
462
+ };
463
+ }
464
+ return {
465
+ ok: false,
466
+ code: 'HANDOFF_NOT_RETURNABLE',
467
+ error: 'handoff is not return_pending',
468
+ handoff_id: instance.handoff_id,
469
+ state: instance.state,
470
+ };
471
+ }
472
+ if (!normalized.trim()) {
473
+ return {
474
+ ok: false,
475
+ code: 'HANDOFF_RETURN_CONTENT_REQUIRED',
476
+ error: 'return content is required',
477
+ handoff_id: instance.handoff_id,
478
+ state: instance.state,
479
+ };
480
+ }
481
+ const existing = instance.return_results.find(result => result.content_hash === hash);
482
+ if (existing) {
483
+ return {
484
+ ok: true,
485
+ code: 'HANDOFF_RETURN_ALREADY_APPLIED',
486
+ handoff_id: instance.handoff_id,
487
+ selected_by: selectedBy,
488
+ previous_state: instance.state,
489
+ state: instance.state,
490
+ idempotent: true,
491
+ result_id: existing.result_id,
492
+ };
493
+ }
494
+ const now = input.now ?? Date.now();
495
+ const previousVersion = instance.version;
496
+ const result = {
497
+ result_id: newReturnResultId(),
498
+ seq: instance.return_results.reduce((max, item) => Math.max(max, item.seq), 0) + 1,
499
+ content: normalized,
500
+ content_hash: hash,
501
+ state: 'origin_queued',
502
+ origin_delivery_message_id: null,
503
+ return_causation: normalizeCausation(input.returnCausation),
504
+ created_at: now,
505
+ updated_at: now,
506
+ completed_at: null,
507
+ };
508
+ instance.return_results.push(result);
509
+ instance.return_causation = result.return_causation;
510
+ instance.version++;
511
+ instance.updated_at = now;
512
+ try {
513
+ this.appendEvent(input.selfAid, 'return_accepted', instance.handoff_id, {
514
+ operationKey: `handoff-return:${instance.handoff_id}:${result.result_id}`,
515
+ fromVersion: previousVersion,
516
+ toVersion: instance.version,
517
+ details: {
518
+ return_policy: instance.return_policy,
519
+ result_id: result.result_id,
520
+ seq: result.seq,
521
+ ...(recoveryAuthorized
522
+ ? { recovery: true, recovery_session_id: input.currentSessionId, recovery_actor_id: input.recovery?.actorId }
523
+ : {}),
524
+ },
525
+ now,
526
+ });
527
+ this.write(input.selfAid, instance);
528
+ }
529
+ catch {
530
+ return {
531
+ ok: false,
532
+ code: 'HANDOFF_STORE_WRITE_FAILED',
533
+ error: 'failed to persist handoff return',
534
+ handoff_id: instance.handoff_id,
535
+ state: previousState,
536
+ retryable: true,
537
+ };
538
+ }
539
+ return {
540
+ ok: true,
541
+ code: 'HANDOFF_RETURN_ACCEPTED',
542
+ handoff_id: instance.handoff_id,
543
+ selected_by: selectedBy,
544
+ previous_state: previousState,
545
+ state: instance.state,
546
+ idempotent: false,
547
+ result_id: result.result_id,
548
+ };
549
+ }
433
550
  if (instance.state !== 'return_pending') {
434
551
  if (instance.return_content_hash) {
435
552
  if (instance.return_content_hash === hash) {
@@ -469,7 +586,6 @@ export class HandoffStore {
469
586
  };
470
587
  }
471
588
  const now = input.now ?? Date.now();
472
- const previousState = instance.state;
473
589
  const previousVersion = instance.version;
474
590
  instance.state = instance.return_policy === 'none' ? 'completed' : 'origin_queued';
475
591
  instance.return_content = instance.return_policy === 'none' ? null : normalized;
@@ -483,7 +599,12 @@ export class HandoffStore {
483
599
  operationKey: `handoff-return:${instance.handoff_id}`,
484
600
  fromVersion: previousVersion,
485
601
  toVersion: instance.version,
486
- details: { return_policy: instance.return_policy },
602
+ details: {
603
+ return_policy: instance.return_policy,
604
+ ...(recoveryAuthorized
605
+ ? { recovery: true, recovery_session_id: input.currentSessionId, recovery_actor_id: input.recovery?.actorId }
606
+ : {}),
607
+ },
487
608
  now,
488
609
  });
489
610
  this.write(input.selfAid, instance);
@@ -524,6 +645,31 @@ export class HandoffStore {
524
645
  this.write(selfAid, instance);
525
646
  return instance;
526
647
  }
648
+ recordOriginResultEnqueued(selfAid, handoffId, resultId, messageId, now = Date.now()) {
649
+ const instance = this.get(selfAid, handoffId);
650
+ if (!instance)
651
+ throw new Error('handoff not found');
652
+ const result = instance.return_results?.find(item => item.result_id === resultId);
653
+ if (!result)
654
+ throw new Error('handoff result not found');
655
+ if (result.state !== 'origin_queued')
656
+ throw new Error(`handoff result state ${result.state}, expected origin_queued`);
657
+ const previous = instance.version;
658
+ result.state = 'origin_delivered';
659
+ result.origin_delivery_message_id = messageId;
660
+ result.updated_at = now;
661
+ instance.version++;
662
+ instance.updated_at = now;
663
+ this.appendEvent(selfAid, 'origin_enqueue_succeeded', handoffId, {
664
+ operationKey: messageId,
665
+ fromVersion: previous,
666
+ toVersion: instance.version,
667
+ details: { result_id: resultId },
668
+ now,
669
+ });
670
+ this.write(selfAid, instance);
671
+ return instance;
672
+ }
527
673
  recordOriginEnqueueFailed(selfAid, handoffId, error, now = Date.now()) {
528
674
  const instance = this.requireState(selfAid, handoffId, 'origin_queued');
529
675
  this.appendEvent(selfAid, 'origin_enqueue_failed', handoffId, {
@@ -532,6 +678,19 @@ export class HandoffStore {
532
678
  now,
533
679
  });
534
680
  }
681
+ recordOriginResultEnqueueFailed(selfAid, handoffId, resultId, error, now = Date.now()) {
682
+ const instance = this.get(selfAid, handoffId);
683
+ if (!instance)
684
+ throw new Error('handoff not found');
685
+ if (!instance.return_results?.some(item => item.result_id === resultId)) {
686
+ throw new Error('handoff result not found');
687
+ }
688
+ this.appendEvent(selfAid, 'origin_enqueue_failed', handoffId, {
689
+ operationKey: `origin-deliver:${handoffId}:${resultId}:${instance.origin_session_id}`,
690
+ details: { error, result_id: resultId },
691
+ now,
692
+ });
693
+ }
535
694
  completeOriginContext(selfAid, handoffId, now = Date.now()) {
536
695
  const instance = this.requireState(selfAid, handoffId, 'origin_delivered');
537
696
  const previous = instance.version;
@@ -548,6 +707,35 @@ export class HandoffStore {
548
707
  this.write(selfAid, instance);
549
708
  return instance;
550
709
  }
710
+ completeOriginResult(selfAid, handoffId, resultId, now = Date.now()) {
711
+ const instance = this.get(selfAid, handoffId);
712
+ if (!instance)
713
+ throw new Error('handoff not found');
714
+ const result = instance.return_results?.find(item => item.result_id === resultId);
715
+ if (!result)
716
+ throw new Error('handoff result not found');
717
+ if (result.state !== 'origin_delivered')
718
+ throw new Error(`handoff result state ${result.state}, expected origin_delivered`);
719
+ const previous = instance.version;
720
+ result.state = 'completed';
721
+ result.updated_at = now;
722
+ result.completed_at = now;
723
+ if (instance.return_results.every(item => item.state === 'completed')) {
724
+ instance.state = 'completed';
725
+ instance.completed_at = now;
726
+ }
727
+ instance.version++;
728
+ instance.updated_at = now;
729
+ this.appendEvent(selfAid, 'origin_context_consumed', handoffId, {
730
+ operationKey: result.origin_delivery_message_id ?? undefined,
731
+ fromVersion: previous,
732
+ toVersion: instance.version,
733
+ details: { result_id: resultId },
734
+ now,
735
+ });
736
+ this.write(selfAid, instance);
737
+ return instance;
738
+ }
551
739
  discard(selfAid, handoffId, reason, details = {}, now = Date.now()) {
552
740
  const instance = this.get(selfAid, handoffId);
553
741
  if (!instance)
@@ -617,7 +805,8 @@ export class HandoffStore {
617
805
  };
618
806
  }
619
807
  requestSummary(instance) {
620
- return classifyAunPayloadForLog(instance.request.payload).content || '[payload]';
808
+ const sent = classifyAunPayloadForLog(instance.request.payload).content || '[payload]';
809
+ return instance.request.context ? `${sent}\n\n[本地交接上下文]\n${instance.request.context}` : sent;
621
810
  }
622
811
  selectReturnCandidate(input) {
623
812
  if (input.handoffId) {