evolcore 0.0.25 → 0.0.26
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.
- package/CHANGELOG.md +607 -565
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +160 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +40 -29
- package/bin/install-codex-managed-hooks.mjs +582 -555
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +50 -12
- package/dist/agents/codex/app-server-client.js +13 -5
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +121 -38
- package/dist/agents/ecagent-runner.js +4 -9
- package/dist/agents/gemini-runner.js +2 -7
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +81 -8
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/menu-client.js +74 -0
- package/dist/aun/msg/group.js +1 -1
- package/dist/aun/msg/managed-operation.js +1 -1
- package/dist/aun/msg/p2p.js +6 -1
- package/dist/aun/msg/upload.js +30 -22
- package/dist/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +105 -84
- package/dist/cli/agent-create.js +112 -10
- package/dist/cli/agent.js +16 -5
- package/dist/cli/aun-commands.js +297 -296
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +47 -28
- package/dist/cli/contact.js +70 -13
- package/dist/cli/ctl-command.js +22 -22
- package/dist/cli/daemon-commands.js +105 -40
- package/dist/cli/diag.js +31 -0
- package/dist/cli/fs-command.js +67 -67
- package/dist/cli/handoff-command.js +41 -9
- package/dist/cli/index.js +83 -77
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +46 -31
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +29 -29
- package/dist/cli/response.js +30 -30
- package/dist/cli/restart-monitor.js +87 -40
- package/dist/cli/stats.js +51 -51
- package/dist/cli/trigger-command.js +56 -56
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-operation-service.js +145 -0
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +27 -7
- package/dist/core/agent-application-service.js +2 -1
- package/dist/core/auth/authorization-audit.js +20 -12
- package/dist/core/auth/operation-authorizer.js +25 -4
- package/dist/core/auth/operation-catalog.js +35 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- package/dist/core/command/command-handler.js +83 -26
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +12 -3
- package/dist/core/command/menu-handler.js +47 -26
- package/dist/core/command/menu-protocol.js +5 -0
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +86 -21
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/message/im-renderer.js +72 -21
- package/dist/core/message/message-bridge.js +69 -9
- package/dist/core/message/message-queue.js +26 -0
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +47 -28
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/ec-command-parser.js +51 -12
- package/dist/core/permission/sandbox-runtime.js +36 -14
- package/dist/core/permission/tool-policy.js +2 -1
- package/dist/index.js +280 -163
- package/dist/ipc.js +54 -2
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/price-resolver.js +28 -10
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +21 -0
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +3 -1
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/npm-ops.js +176 -35
- package/dist/utils/welcome.js +22 -22
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +66 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +56 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +364 -364
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +71 -71
- package/kits/docs/evolcore/agent-create.md +170 -165
- package/kits/docs/evolcore/agent.md +66 -66
- package/kits/docs/evolcore/aid.md +60 -60
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
- package/kits/docs/evolcore/config.md +202 -195
- package/kits/docs/evolcore/contact.md +75 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +243 -243
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +152 -152
- package/kits/docs/evolcore/menu-client.md +42 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +166 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +50 -50
- package/kits/docs/evolcore/trigger.md +575 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +269 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +37 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +190 -190
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +162 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +36 -36
- package/kits/rules/04-relation.md +75 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +63 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +427 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +201 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +92 -92
- package/kits/templates/roles/member.json +96 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +85 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +31 -29
- package/kits/templates/system-fragments/channel.md +64 -64
- package/kits/templates/system-fragments/commands.md +28 -28
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +100 -98
|
@@ -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.
|
|
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: {
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
237
|
-
? deriveCausation(
|
|
238
|
-
: instance.
|
|
239
|
-
? deriveCausation(instance.
|
|
240
|
-
:
|
|
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
|
-
|
|
259
|
-
|
|
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
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
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) {
|