evolcore 0.0.20 → 0.0.22
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 +65 -0
- package/README.md +58 -9
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +393 -108
- package/dist/agents/codex-app-server-client.js +41 -7
- package/dist/agents/codex-runner.js +1292 -220
- package/dist/agents/ecagent-runner.js +171 -61
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +189 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +618 -123
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +98 -123
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +50 -0
- package/dist/cli/trigger-command.js +14 -5
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +19 -5
- package/dist/config/config-manager.js +167 -22
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +43 -1
- package/dist/core/auth/auth-gateway.js +41 -4
- package/dist/core/auth/authorization-audit.js +216 -8
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +99 -27
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +311 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +288 -80
- package/dist/core/command/menu-protocol.js +1 -1
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +377 -36
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +37 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +33 -3
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +90 -87
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +184 -12
- package/dist/core/message/message-log.js +47 -7
- package/dist/core/message/message-queue.js +227 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +658 -109
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +627 -69
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +618 -23
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +329 -30
- package/dist/core/session/session-renew.js +37 -13
- package/dist/core/session/session-turn-coordinator.js +16 -5
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +316 -50
- package/dist/ipc.js +459 -29
- package/dist/paths.js +82 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +70 -7
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +13 -3
- package/dist/trigger/scheduler.js +20 -3
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +30 -6
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +3 -3
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
|
@@ -246,6 +246,8 @@ const CATALOG = [
|
|
|
246
246
|
description: 'agent 任务开始',
|
|
247
247
|
fields: [
|
|
248
248
|
{ path: 'sessionId', type: 'string' },
|
|
249
|
+
{ path: 'taskId', type: 'string', optional: true },
|
|
250
|
+
{ path: 'generation', type: 'number', optional: true },
|
|
249
251
|
{ path: 'agentName', type: 'string', optional: true },
|
|
250
252
|
{ path: 'encrypt', type: 'boolean', optional: true },
|
|
251
253
|
{ path: 'chatmode', type: 'string', optional: true },
|
|
@@ -269,6 +271,8 @@ const CATALOG = [
|
|
|
269
271
|
description: 'agent 任务完成',
|
|
270
272
|
fields: [
|
|
271
273
|
{ path: 'sessionId', type: 'string' },
|
|
274
|
+
{ path: 'taskId', type: 'string', optional: true },
|
|
275
|
+
{ path: 'generation', type: 'number', optional: true },
|
|
272
276
|
{ path: 'channel', type: 'string' },
|
|
273
277
|
{ path: 'channelName', type: 'string', optional: true },
|
|
274
278
|
{ path: 'channelId', type: 'string' },
|
|
@@ -287,6 +291,8 @@ const CATALOG = [
|
|
|
287
291
|
description: 'agent 任务失败',
|
|
288
292
|
fields: [
|
|
289
293
|
{ path: 'sessionId', type: 'string' },
|
|
294
|
+
{ path: 'taskId', type: 'string', optional: true },
|
|
295
|
+
{ path: 'generation', type: 'number', optional: true },
|
|
290
296
|
{ path: 'error', type: 'string' },
|
|
291
297
|
{ path: 'errorType', type: 'string' },
|
|
292
298
|
{ path: 'terminalReason', type: 'string', optional: true },
|
|
@@ -300,6 +306,8 @@ const CATALOG = [
|
|
|
300
306
|
description: 'agent 任务被中断',
|
|
301
307
|
fields: [
|
|
302
308
|
{ path: 'sessionId', type: 'string' },
|
|
309
|
+
{ path: 'taskId', type: 'string', optional: true },
|
|
310
|
+
{ path: 'generation', type: 'number', optional: true },
|
|
303
311
|
{ path: 'reason', type: 'string', optional: true },
|
|
304
312
|
{ path: 'agentName', type: 'string', optional: true },
|
|
305
313
|
],
|
|
@@ -317,6 +325,7 @@ const CATALOG = [
|
|
|
317
325
|
{ path: 'eventPhase', type: 'string' },
|
|
318
326
|
{ path: 'callId', type: 'string', optional: true },
|
|
319
327
|
{ path: 'correlationId', type: 'string', optional: true },
|
|
328
|
+
{ path: 'agentName', type: 'string', optional: true },
|
|
320
329
|
{ path: 'agentAid', type: 'string', optional: true },
|
|
321
330
|
{ path: 'permissionMode', type: 'string', optional: true },
|
|
322
331
|
{ path: 'decision', type: 'string' },
|
|
@@ -338,13 +347,17 @@ const CATALOG = [
|
|
|
338
347
|
{ path: 'eventPhase', type: 'string' },
|
|
339
348
|
{ path: 'isError', type: 'boolean', optional: true },
|
|
340
349
|
{ path: 'errorCode', type: 'string', optional: true },
|
|
350
|
+
{ path: 'classificationMissing', type: 'boolean', optional: true },
|
|
341
351
|
{ path: 'agentName', type: 'string', optional: true },
|
|
342
352
|
{ path: 'callId', type: 'string', optional: true },
|
|
343
353
|
{ path: 'correlationId', type: 'string', optional: true },
|
|
354
|
+
{ path: 'requestId', type: 'string', optional: true },
|
|
344
355
|
{ path: 'agentAid', type: 'string', optional: true },
|
|
345
356
|
{ path: 'permissionMode', type: 'string', optional: true },
|
|
346
357
|
{ path: 'decision', type: 'string' },
|
|
347
358
|
{ path: 'decisionSource', type: 'string' },
|
|
359
|
+
{ path: 'policyCode', type: 'string', optional: true },
|
|
360
|
+
{ path: 'reason', type: 'string', optional: true },
|
|
348
361
|
{ path: 'executed', type: 'boolean' },
|
|
349
362
|
{ path: 'executionState', type: 'string' },
|
|
350
363
|
{ path: 'timestamp', type: 'number', optional: true },
|
|
@@ -373,6 +386,8 @@ const CATALOG = [
|
|
|
373
386
|
{ path: 'approved', type: 'boolean' },
|
|
374
387
|
{ path: 'toolName', type: 'string', optional: true },
|
|
375
388
|
{ path: 'reason', type: 'string', optional: true },
|
|
389
|
+
{ path: 'decisionSource', type: 'string', optional: true },
|
|
390
|
+
{ path: 'policyCode', type: 'string', optional: true },
|
|
376
391
|
],
|
|
377
392
|
},
|
|
378
393
|
{
|
|
@@ -385,6 +400,8 @@ const CATALOG = [
|
|
|
385
400
|
{ path: 'requestId', type: 'string' },
|
|
386
401
|
{ path: 'toolName', type: 'string', optional: true },
|
|
387
402
|
{ path: 'reason', type: 'string', optional: true },
|
|
403
|
+
{ path: 'decisionSource', type: 'string', optional: true },
|
|
404
|
+
{ path: 'policyCode', type: 'string', optional: true },
|
|
388
405
|
],
|
|
389
406
|
},
|
|
390
407
|
{
|
|
@@ -394,6 +411,21 @@ const CATALOG = [
|
|
|
394
411
|
description: 'runner 开始压缩',
|
|
395
412
|
fields: [{ path: 'sessionId', type: 'string' }],
|
|
396
413
|
},
|
|
414
|
+
{
|
|
415
|
+
type: 'runner:proactive-reminder',
|
|
416
|
+
namespace: 'runner',
|
|
417
|
+
name: 'proactive-reminder',
|
|
418
|
+
description: '主动模式提醒已生成并请求注入模型上下文',
|
|
419
|
+
fields: [
|
|
420
|
+
{ path: 'sessionId', type: 'string' },
|
|
421
|
+
{ path: 'kind', type: 'string' },
|
|
422
|
+
{ path: 'text', type: 'string' },
|
|
423
|
+
{ path: 'queueLength', type: 'number', optional: true },
|
|
424
|
+
{ path: 'toolCount', type: 'number', optional: true },
|
|
425
|
+
{ path: 'injection', type: 'string' },
|
|
426
|
+
{ path: 'timestamp', type: 'number', optional: true },
|
|
427
|
+
],
|
|
428
|
+
},
|
|
397
429
|
{
|
|
398
430
|
type: 'runner:compact-complete',
|
|
399
431
|
namespace: 'runner',
|
|
@@ -685,6 +717,11 @@ const CATALOG = [
|
|
|
685
717
|
{ path: 'runId', type: 'string' },
|
|
686
718
|
{ path: 'originTriggerId', type: 'string' },
|
|
687
719
|
{ path: 'reason', type: 'string' },
|
|
720
|
+
{ path: 'reasonCode', type: 'string', optional: true },
|
|
721
|
+
{ path: 'decisionSource', type: 'string', optional: true },
|
|
722
|
+
{ path: 'executionState', type: 'string', optional: true },
|
|
723
|
+
{ path: 'generation', type: 'number', optional: true },
|
|
724
|
+
{ path: 'currentGeneration', type: 'number', optional: true },
|
|
688
725
|
{ path: 'targetChannel', type: 'string' },
|
|
689
726
|
{ path: 'targetChannelId', type: 'string' },
|
|
690
727
|
{ path: 'fireTime', type: 'number', optional: true },
|
package/dist/core/evolagent.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ConfigTarget, read as cfgRead, write as cfgWrite, ensureFile as cfgEnsu
|
|
|
6
6
|
import { withLifecycleForWrite } from '../config/lifecycle.js';
|
|
7
7
|
import { isStaticAgentAdmin, isStaticAgentOwner } from '../config/peer-role-resolver.js';
|
|
8
8
|
import { resolvePrincipal } from '../config/contact-book.js';
|
|
9
|
+
import { isSupportedBaseagent } from '../agents/baseagent.js';
|
|
9
10
|
/**
|
|
10
11
|
* EvolAgent —— 一个 self-agent 的运行时表示。
|
|
11
12
|
*
|
|
@@ -168,6 +169,9 @@ export class EvolAgent {
|
|
|
168
169
|
// ── Baseagent 字段写入 ────────────────────────────
|
|
169
170
|
/** 切换当前活跃 baseagent(写入 config.json)。 */
|
|
170
171
|
setActiveBaseagent(value) {
|
|
172
|
+
if (value !== undefined && !isSupportedBaseagent(value)) {
|
|
173
|
+
throw new Error(`Unsupported baseagent: ${value} (choose: claude/codex/gemini/ecagent)`);
|
|
174
|
+
}
|
|
171
175
|
this.merged.active_baseagent = value;
|
|
172
176
|
this.updateAgentConfig(b => {
|
|
173
177
|
if (value === undefined)
|
|
@@ -63,6 +63,10 @@ export class HandoffDispatcher {
|
|
|
63
63
|
isAgentPaused(selfAid) {
|
|
64
64
|
return this.pausedAgents.has(selfAid);
|
|
65
65
|
}
|
|
66
|
+
/** Whether delivery work for this exact target is currently executing. */
|
|
67
|
+
isRunning(selfAid, targetSessionId) {
|
|
68
|
+
return this.running.has(this.key(selfAid, targetSessionId));
|
|
69
|
+
}
|
|
66
70
|
async drain(selfAid, targetSessionId) {
|
|
67
71
|
const key = this.key(selfAid, targetSessionId);
|
|
68
72
|
if (this.running.has(key)) {
|
|
@@ -150,6 +150,16 @@ export class HandoffRuntime {
|
|
|
150
150
|
.some(instance => instance.state === 'target_sent'
|
|
151
151
|
|| (instance.state === 'return_pending' && instance.return_policy === 'required'));
|
|
152
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Probe only in-flight delivery/return work. Persisted handoffs that are
|
|
155
|
+
* merely waiting in the store may cross a backend generation; the queue or
|
|
156
|
+
* interaction probe handles the active parts separately.
|
|
157
|
+
*/
|
|
158
|
+
hasActiveSessionWork(selfAid, sessionId) {
|
|
159
|
+
return this.dispatcher.isRunning(selfAid, sessionId)
|
|
160
|
+
|| this.store.list(selfAid).some(instance => (instance.origin_session_id === sessionId
|
|
161
|
+
|| instance.target_session_id === sessionId) && instance.state === 'origin_queued' && this.dispatcher.isRunning(selfAid, instance.target_session_id));
|
|
162
|
+
}
|
|
153
163
|
async bindReply(input) {
|
|
154
164
|
const key = `${input.selfAid}\u0000${input.targetSessionId}`;
|
|
155
165
|
return this.mutexes.forKey(key).runExclusive(async () => {
|
|
@@ -256,8 +266,8 @@ export class HandoffRuntime {
|
|
|
256
266
|
}
|
|
257
267
|
}
|
|
258
268
|
}
|
|
259
|
-
buildPromptItem(message) {
|
|
260
|
-
return this.buildPromptItems(message)[0] ?? null;
|
|
269
|
+
buildPromptItem(message, fallbackPeerRole) {
|
|
270
|
+
return this.buildPromptItems(message, fallbackPeerRole)[0] ?? null;
|
|
261
271
|
}
|
|
262
272
|
isDiscardedDelivery(message) {
|
|
263
273
|
const delivery = message.handoffDelivery;
|
|
@@ -267,7 +277,7 @@ export class HandoffRuntime {
|
|
|
267
277
|
const ids = delivery.handoffIds?.length ? delivery.handoffIds : [delivery.handoffId];
|
|
268
278
|
return ids.length > 0 && ids.every(handoffId => this.store.get(selfAid, handoffId)?.state === 'discarded');
|
|
269
279
|
}
|
|
270
|
-
buildPromptItems(message) {
|
|
280
|
+
buildPromptItems(message, fallbackPeerRole) {
|
|
271
281
|
const delivery = message.handoffDelivery;
|
|
272
282
|
const selfAid = message.selfAID;
|
|
273
283
|
if (!delivery || !selfAid)
|
|
@@ -300,8 +310,18 @@ export class HandoffRuntime {
|
|
|
300
310
|
peerId: message.peerId,
|
|
301
311
|
peerName: message.peerName,
|
|
302
312
|
peerType: message.peerType,
|
|
313
|
+
peerRole: message.batchRole ?? message.resolvedIdentity?.role ?? fallbackPeerRole,
|
|
314
|
+
sameDevice: message.sameDevice,
|
|
315
|
+
sameNetwork: message.sameNetwork,
|
|
316
|
+
sameEgressIp: message.sameEgressIp,
|
|
317
|
+
encrypted: message.encrypted,
|
|
303
318
|
content: message.content,
|
|
304
319
|
timestamp: message.timestamp,
|
|
320
|
+
receivedAt: message.receivedAt,
|
|
321
|
+
gatewaySeq: message.gatewaySeq,
|
|
322
|
+
images: message.images,
|
|
323
|
+
mentions: message.mentions,
|
|
324
|
+
mentionAids: message.mentionAids,
|
|
305
325
|
causation: message.causation,
|
|
306
326
|
handoff: {
|
|
307
327
|
kind: 'response_to_origin',
|
|
@@ -338,8 +358,18 @@ export class HandoffRuntime {
|
|
|
338
358
|
peerId: message.peerId,
|
|
339
359
|
peerName: message.peerName,
|
|
340
360
|
peerType: message.peerType,
|
|
361
|
+
peerRole: message.batchRole ?? message.resolvedIdentity?.role ?? fallbackPeerRole,
|
|
362
|
+
sameDevice: message.sameDevice,
|
|
363
|
+
sameNetwork: message.sameNetwork,
|
|
364
|
+
sameEgressIp: message.sameEgressIp,
|
|
365
|
+
encrypted: message.encrypted,
|
|
341
366
|
content: message.content,
|
|
342
367
|
timestamp: message.timestamp,
|
|
368
|
+
receivedAt: message.receivedAt,
|
|
369
|
+
gatewaySeq: message.gatewaySeq,
|
|
370
|
+
images: message.images,
|
|
371
|
+
mentions: message.mentions,
|
|
372
|
+
mentionAids: message.mentionAids,
|
|
343
373
|
causation: message.causation,
|
|
344
374
|
handoff: {
|
|
345
375
|
kind: 'request_to_target',
|
|
@@ -9,6 +9,7 @@ import { HANDOFF_SCHEMA_VERSION, HANDOFF_QUERY_DEFAULT_LIMIT, HANDOFF_QUERY_MAX_
|
|
|
9
9
|
import { normalizeCausation } from '../causation/context.js';
|
|
10
10
|
const SAFE_ID_RE = /^[A-Za-z0-9._-]+$/;
|
|
11
11
|
export const HANDOFF_ID_RE = /^h-[A-Za-z0-9._-]+$/;
|
|
12
|
+
const HANDOFF_SNAPSHOT_FILE_RE = /^(h-[A-Za-z0-9._-]+)\.json(?:__?)?$/;
|
|
12
13
|
const TERMINAL_HANDOFF_STATES = new Set(['failed', 'completed', 'discarded']);
|
|
13
14
|
function safeSegment(value, field) {
|
|
14
15
|
if (!value || !SAFE_ID_RE.test(value) || value === '.' || value === '..') {
|
|
@@ -55,13 +56,16 @@ export class HandoffStore {
|
|
|
55
56
|
? path.join(this.rootDir, aid, 'data', 'handoff')
|
|
56
57
|
: path.join(this.rootDir, aid);
|
|
57
58
|
}
|
|
58
|
-
handoffsDir(selfAid) {
|
|
59
|
-
return path.join(this.agentDir(selfAid), 'handoffs');
|
|
60
|
-
}
|
|
61
59
|
instancePath(selfAid, handoffId) {
|
|
62
60
|
if (!HANDOFF_ID_RE.test(handoffId))
|
|
63
61
|
throw new Error('invalid handoff_id');
|
|
64
|
-
return path.join(this.
|
|
62
|
+
return path.join(this.agentDir(selfAid), `${safeSegment(handoffId, 'handoff_id')}.json`);
|
|
63
|
+
}
|
|
64
|
+
/** Read the pre-flat layout during the transition to the flat store. */
|
|
65
|
+
legacyInstancePath(selfAid, handoffId) {
|
|
66
|
+
if (!HANDOFF_ID_RE.test(handoffId))
|
|
67
|
+
throw new Error('invalid handoff_id');
|
|
68
|
+
return path.join(this.agentDir(selfAid), 'handoffs', safeSegment(handoffId, 'handoff_id'), 'handoff.json');
|
|
65
69
|
}
|
|
66
70
|
historyPath(selfAid) {
|
|
67
71
|
return path.join(this.agentDir(selfAid), 'history.jsonl');
|
|
@@ -147,7 +151,8 @@ export class HandoffStore {
|
|
|
147
151
|
get(selfAid, handoffId) {
|
|
148
152
|
if (!HANDOFF_ID_RE.test(handoffId))
|
|
149
153
|
return null;
|
|
150
|
-
const parsed = atomicReadJson(this.instancePath(selfAid, handoffId))
|
|
154
|
+
const parsed = atomicReadJson(this.instancePath(selfAid, handoffId))
|
|
155
|
+
?? atomicReadJson(this.legacyInstancePath(selfAid, handoffId));
|
|
151
156
|
if (!parsed)
|
|
152
157
|
return null;
|
|
153
158
|
if (parsed.schema_version !== HANDOFF_SCHEMA_VERSION || parsed.handoff_id !== handoffId) {
|
|
@@ -156,14 +161,32 @@ export class HandoffStore {
|
|
|
156
161
|
return parsed;
|
|
157
162
|
}
|
|
158
163
|
list(selfAid) {
|
|
159
|
-
const dir = this.
|
|
164
|
+
const dir = this.agentDir(selfAid);
|
|
160
165
|
if (!fs.existsSync(dir))
|
|
161
166
|
return [];
|
|
162
|
-
const
|
|
167
|
+
const handoffIds = new Set();
|
|
163
168
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
164
|
-
if (!entry.
|
|
169
|
+
if (!entry.isFile())
|
|
165
170
|
continue;
|
|
166
|
-
const
|
|
171
|
+
const match = HANDOFF_SNAPSHOT_FILE_RE.exec(entry.name);
|
|
172
|
+
if (match && HANDOFF_ID_RE.test(match[1]))
|
|
173
|
+
handoffIds.add(match[1]);
|
|
174
|
+
}
|
|
175
|
+
// Keep nested snapshots visible; a later flat snapshot takes precedence.
|
|
176
|
+
const legacyDir = path.join(dir, 'handoffs');
|
|
177
|
+
try {
|
|
178
|
+
for (const entry of fs.readdirSync(legacyDir, { withFileTypes: true })) {
|
|
179
|
+
if (entry.isDirectory() && HANDOFF_ID_RE.test(entry.name))
|
|
180
|
+
handoffIds.add(entry.name);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (error.code !== 'ENOENT')
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
const result = [];
|
|
188
|
+
for (const handoffId of handoffIds) {
|
|
189
|
+
const instance = this.get(selfAid, handoffId);
|
|
167
190
|
if (instance)
|
|
168
191
|
result.push(instance);
|
|
169
192
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeBaseagent, resolveAnthropicDirectConfig, resolveEcagentConfig, resolveOpenaiDirectConfig, } from '../../agents/baseagent.js';
|
|
2
2
|
import { buildModelRequestHeaders } from '../../agents/request-identity.js';
|
|
3
|
+
import { appendRequestQueryParams } from '../../agents/request-identity.js';
|
|
3
4
|
function apiEndpoint(baseUrl, resource) {
|
|
4
5
|
const configured = baseUrl?.trim();
|
|
5
6
|
if (!configured)
|
|
@@ -11,14 +12,6 @@ function apiEndpoint(baseUrl, resource) {
|
|
|
11
12
|
: `${pathname}/v1/${resource}`;
|
|
12
13
|
return parsed.toString();
|
|
13
14
|
}
|
|
14
|
-
function withQueryParams(url, queryParams) {
|
|
15
|
-
if (!queryParams || Object.keys(queryParams).length === 0)
|
|
16
|
-
return url;
|
|
17
|
-
const parsed = new URL(url);
|
|
18
|
-
for (const [name, value] of Object.entries(queryParams))
|
|
19
|
-
parsed.searchParams.set(name, value);
|
|
20
|
-
return parsed.toString();
|
|
21
|
-
}
|
|
22
15
|
async function parseError(response) {
|
|
23
16
|
let raw = '';
|
|
24
17
|
try {
|
|
@@ -41,7 +34,7 @@ export class AnthropicTextInferenceProvider {
|
|
|
41
34
|
this.config = config;
|
|
42
35
|
}
|
|
43
36
|
async completeText(request) {
|
|
44
|
-
const response = await fetch(
|
|
37
|
+
const response = await fetch(appendRequestQueryParams(apiEndpoint(this.config.baseUrl, 'messages'), this.config.queryParams), {
|
|
45
38
|
method: 'POST',
|
|
46
39
|
signal: request.signal,
|
|
47
40
|
headers: { ...this.headers(), ...this.config.headers },
|
|
@@ -82,7 +75,7 @@ export class OpenAITextInferenceProvider {
|
|
|
82
75
|
this.config = config;
|
|
83
76
|
}
|
|
84
77
|
async completeText(request) {
|
|
85
|
-
const response = await fetch(
|
|
78
|
+
const response = await fetch(appendRequestQueryParams(apiEndpoint(this.config.baseUrl, 'responses'), this.config.queryParams), {
|
|
86
79
|
method: 'POST',
|
|
87
80
|
signal: request.signal,
|
|
88
81
|
headers: {
|
|
@@ -118,20 +111,20 @@ export class OpenAITextInferenceProvider {
|
|
|
118
111
|
export class EcagentTextInferenceProvider {
|
|
119
112
|
config;
|
|
120
113
|
baseagent = 'ecagent';
|
|
121
|
-
constructor(config) {
|
|
114
|
+
constructor(config, agentAid) {
|
|
122
115
|
this.config = config;
|
|
123
116
|
this.config = {
|
|
124
117
|
...config,
|
|
125
118
|
headers: buildModelRequestHeaders({
|
|
126
119
|
baseagent: 'ecagent',
|
|
127
120
|
baseUrl: config.baseUrl,
|
|
128
|
-
agentAid
|
|
121
|
+
agentAid,
|
|
129
122
|
configuredHeaders: config.headers,
|
|
130
123
|
}),
|
|
131
124
|
};
|
|
132
125
|
}
|
|
133
126
|
async completeText(request) {
|
|
134
|
-
const response = await fetch(apiEndpoint(this.config.baseUrl, 'chat/completions'), {
|
|
127
|
+
const response = await fetch(appendRequestQueryParams(apiEndpoint(this.config.baseUrl, 'chat/completions'), this.config.queryParams), {
|
|
135
128
|
method: 'POST',
|
|
136
129
|
signal: request.signal,
|
|
137
130
|
headers: {
|
|
@@ -190,10 +183,9 @@ export function createTextInferenceProvider(baseagent, config) {
|
|
|
190
183
|
try {
|
|
191
184
|
const override = {
|
|
192
185
|
...(config.baseagents?.ecagent ?? {}),
|
|
193
|
-
evolcoreAgentAid: config.aid,
|
|
194
186
|
};
|
|
195
187
|
const resolved = resolveEcagentConfig({ agents: { ecagent: override } }, override);
|
|
196
|
-
return new EcagentTextInferenceProvider(resolved);
|
|
188
|
+
return new EcagentTextInferenceProvider(resolved, config.aid);
|
|
197
189
|
}
|
|
198
190
|
catch {
|
|
199
191
|
return undefined;
|
|
@@ -1,23 +1,7 @@
|
|
|
1
1
|
import { logger } from '../../utils/logger.js';
|
|
2
|
-
import { summarizeToolInput } from '../../utils/tool-summary.js';
|
|
2
|
+
import { summarizeToolInput, toolInputForDisplay } from '../../utils/tool-summary.js';
|
|
3
3
|
import { getErrorMessage, isContextTooLongText } from '../../utils/error-utils.js';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import { resolvePaths } from '../../paths.js';
|
|
7
4
|
import { DEFAULT_FLUSH_DELAY_MS } from '../../types.js';
|
|
8
|
-
let diagStream = null;
|
|
9
|
-
function getDiagStream() {
|
|
10
|
-
if (!diagStream) {
|
|
11
|
-
const logDir = resolvePaths().logs;
|
|
12
|
-
diagStream = fs.createWriteStream(path.join(logDir, 'im-renderer-diag.log'), { flags: 'a' });
|
|
13
|
-
}
|
|
14
|
-
return diagStream;
|
|
15
|
-
}
|
|
16
|
-
function diag(instanceId, action, meta = {}) {
|
|
17
|
-
const line = JSON.stringify({ ts: new Date().toISOString(), id: instanceId, action, ...meta });
|
|
18
|
-
getDiagStream().write(line + '\n');
|
|
19
|
-
}
|
|
20
|
-
let instanceCounter = 0;
|
|
21
5
|
export class IMRenderer {
|
|
22
6
|
opts;
|
|
23
7
|
itemsQueue = [];
|
|
@@ -28,8 +12,6 @@ export class IMRenderer {
|
|
|
28
12
|
sentContent = false;
|
|
29
13
|
flushCount = 0;
|
|
30
14
|
messageTimestamps = [];
|
|
31
|
-
instanceId;
|
|
32
|
-
diagEnabled;
|
|
33
15
|
/** 串行发送队列:保证消息按序到达 */
|
|
34
16
|
sendChain = Promise.resolve();
|
|
35
17
|
/** proactive:是否已发过 text 文本(用于去重 complete.result) */
|
|
@@ -38,16 +20,6 @@ export class IMRenderer {
|
|
|
38
20
|
syntheticCallSeq = 0;
|
|
39
21
|
constructor(opts) {
|
|
40
22
|
this.opts = opts;
|
|
41
|
-
this.diagEnabled = opts.diagEnabled ?? false;
|
|
42
|
-
this.instanceId = `R${++instanceCounter}`;
|
|
43
|
-
if (this.diagEnabled) {
|
|
44
|
-
diag(this.instanceId, 'created', {
|
|
45
|
-
chatmode: opts.envelope.chatmode,
|
|
46
|
-
flushDelay: opts.flushDelay,
|
|
47
|
-
suppressActivityItems: this.suppressActivityItems(),
|
|
48
|
-
suppressIntermediateText: this.suppressIntermediateText(),
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
23
|
}
|
|
52
24
|
suppressActivityItems() {
|
|
53
25
|
return this.opts.suppressActivityItems ?? this.opts.suppressActivities ?? false;
|
|
@@ -62,6 +34,9 @@ export class IMRenderer {
|
|
|
62
34
|
const correlatedEvent = event.type === 'tool_use' || event.type === 'tool_result'
|
|
63
35
|
? { ...event, correlationId: event.correlationId ?? event.callId }
|
|
64
36
|
: event;
|
|
37
|
+
const permissionMode = typeof this.opts.permissionMode === 'function'
|
|
38
|
+
? this.opts.permissionMode()
|
|
39
|
+
: this.opts.permissionMode;
|
|
65
40
|
logger.event({
|
|
66
41
|
source: 'runner',
|
|
67
42
|
taskId: this.opts.envelope.taskId,
|
|
@@ -69,6 +44,7 @@ export class IMRenderer {
|
|
|
69
44
|
channelId: this.opts.envelope.channelId,
|
|
70
45
|
agentAid: this.opts.agentAid,
|
|
71
46
|
agentName: this.opts.envelope.agentName,
|
|
47
|
+
...(permissionMode ? { permissionMode } : {}),
|
|
72
48
|
...(correlatedEvent.type === 'tool_use' || correlatedEvent.type === 'tool_result'
|
|
73
49
|
? { correlationId: correlatedEvent.correlationId, toolName: correlatedEvent.name }
|
|
74
50
|
: {}),
|
|
@@ -138,10 +114,14 @@ export class IMRenderer {
|
|
|
138
114
|
this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
|
|
139
115
|
const payload = { kind: 'result.text', text: rawText, isFinal: false };
|
|
140
116
|
this.sentContent = true;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
117
|
+
try {
|
|
118
|
+
await this.sendPayload(payload);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
// A failed durable enqueue must remain retryable by the final flush.
|
|
122
|
+
this.textBuffer = rawText + this.textBuffer;
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
145
125
|
this.lastFlush = Date.now();
|
|
146
126
|
this.flushCount++;
|
|
147
127
|
}
|
|
@@ -195,13 +175,6 @@ export class IMRenderer {
|
|
|
195
175
|
this.textBuffer += text;
|
|
196
176
|
this.allText += text;
|
|
197
177
|
this.messageTimestamps.push(Date.now());
|
|
198
|
-
if (this.diagEnabled) {
|
|
199
|
-
diag(this.instanceId, 'addText', {
|
|
200
|
-
len: text.length,
|
|
201
|
-
preview: text.substring(0, 60),
|
|
202
|
-
bufLen: this.textBuffer.length,
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
178
|
this.scheduleFlush();
|
|
206
179
|
}
|
|
207
180
|
/** 添加工具调用 */
|
|
@@ -215,12 +188,10 @@ export class IMRenderer {
|
|
|
215
188
|
kind: 'tool_call',
|
|
216
189
|
call_id: callId || this.synthCallId(),
|
|
217
190
|
name,
|
|
218
|
-
arguments: input,
|
|
191
|
+
arguments: toolInputForDisplay(name, input),
|
|
219
192
|
text: descText,
|
|
220
193
|
});
|
|
221
194
|
this.messageTimestamps.push(Date.now());
|
|
222
|
-
if (this.diagEnabled)
|
|
223
|
-
diag(this.instanceId, 'addToolCall', { name, callId });
|
|
224
195
|
this.scheduleFlush();
|
|
225
196
|
}
|
|
226
197
|
/** 添加工具结果 */
|
|
@@ -241,8 +212,6 @@ export class IMRenderer {
|
|
|
241
212
|
...(descText !== undefined && { text: descText }),
|
|
242
213
|
});
|
|
243
214
|
this.messageTimestamps.push(Date.now());
|
|
244
|
-
if (this.diagEnabled)
|
|
245
|
-
diag(this.instanceId, 'addToolResult', { name, ok, callId });
|
|
246
215
|
this.scheduleFlush();
|
|
247
216
|
}
|
|
248
217
|
/** 添加进度提示 */
|
|
@@ -330,10 +299,7 @@ export class IMRenderer {
|
|
|
330
299
|
await this.flushText();
|
|
331
300
|
const payload = { kind: 'result.text', text, isFinal: false };
|
|
332
301
|
this.sentContent = true;
|
|
333
|
-
|
|
334
|
-
.then(() => this.opts.send(payload))
|
|
335
|
-
.catch(e => logger.warn('[IMRenderer] notice text send failed:', e));
|
|
336
|
-
await this.sendChain;
|
|
302
|
+
await this.sendPayload(payload);
|
|
337
303
|
this.lastFlush = Date.now();
|
|
338
304
|
this.flushCount++;
|
|
339
305
|
return true;
|
|
@@ -342,10 +308,48 @@ export class IMRenderer {
|
|
|
342
308
|
synthCallId() {
|
|
343
309
|
return `synth-${this.opts.envelope.taskId}-${++this.syntheticCallSeq}`;
|
|
344
310
|
}
|
|
311
|
+
/** Convert a structured failed receipt into the same error shape as a rejected sender. */
|
|
312
|
+
receiptError(receipt) {
|
|
313
|
+
return Object.assign(new Error(receipt.error), {
|
|
314
|
+
code: receipt.code,
|
|
315
|
+
outboxId: receipt.outboxId,
|
|
316
|
+
operationId: receipt.operationId,
|
|
317
|
+
status: receipt.status,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
isOutboxFull(error) {
|
|
321
|
+
const value = error && typeof error === 'object' ? error : {};
|
|
322
|
+
const cause = value.cause && typeof value.cause === 'object'
|
|
323
|
+
? value.cause
|
|
324
|
+
: undefined;
|
|
325
|
+
return value.code === 'OUTBOX_FULL' || cause?.code === 'OUTBOX_FULL';
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Serialize durable sends while keeping the chain usable after a rejected
|
|
329
|
+
* operation. Activity queue saturation is best-effort: it is logged, but
|
|
330
|
+
* must not prevent the final response from being sent.
|
|
331
|
+
*/
|
|
332
|
+
sendPayload(payload) {
|
|
333
|
+
const operation = this.sendChain.then(async () => {
|
|
334
|
+
const receipt = await this.opts.send(payload);
|
|
335
|
+
if (receipt?.status === 'failed') {
|
|
336
|
+
throw this.receiptError(receipt);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
const handled = operation.catch(error => {
|
|
340
|
+
if (payload.kind === 'activity.batch' && this.isOutboxFull(error)) {
|
|
341
|
+
logger.warn(`[IMRenderer] activity outbox full; continuing without activity batch: ${error instanceof Error ? error.message : String(error)}`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
throw error;
|
|
345
|
+
});
|
|
346
|
+
// A failed operation must not poison later sends. The caller still gets
|
|
347
|
+
// the rejection through `handled`, while the internal tail is recovered.
|
|
348
|
+
this.sendChain = handled.catch(() => { });
|
|
349
|
+
return handled;
|
|
350
|
+
}
|
|
345
351
|
scheduleFlush() {
|
|
346
352
|
if (this.timer) {
|
|
347
|
-
if (this.diagEnabled)
|
|
348
|
-
diag(this.instanceId, 'scheduleFlush:skip', { reason: 'timer_exists' });
|
|
349
353
|
return;
|
|
350
354
|
}
|
|
351
355
|
const interval = this.opts.flushDelay ?? DEFAULT_FLUSH_DELAY_MS;
|
|
@@ -364,15 +368,11 @@ export class IMRenderer {
|
|
|
364
368
|
}
|
|
365
369
|
const elapsed = Date.now() - this.lastFlush;
|
|
366
370
|
const delay = Math.max(0, targetDelay - elapsed);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
targetDelay,
|
|
371
|
-
elapsed,
|
|
372
|
-
actualDelay: delay,
|
|
371
|
+
this.timer = setTimeout(() => {
|
|
372
|
+
void this.flushInternal().catch(error => {
|
|
373
|
+
logger.warn(`[IMRenderer] scheduled flush failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
373
374
|
});
|
|
374
|
-
}
|
|
375
|
-
this.timer = setTimeout(() => this.flushInternal(), delay);
|
|
375
|
+
}, delay);
|
|
376
376
|
}
|
|
377
377
|
calculateDynamicDelay(interval) {
|
|
378
378
|
const recent = this.messageTimestamps.slice(-10);
|
|
@@ -402,13 +402,14 @@ export class IMRenderer {
|
|
|
402
402
|
if (this.suppressActivityItems())
|
|
403
403
|
return;
|
|
404
404
|
const payload = { kind: 'activity.batch', items: nonThinking };
|
|
405
|
-
if (this.diagEnabled)
|
|
406
|
-
diag(this.instanceId, 'flushActivitiesOnly', { itemCount: nonThinking.length });
|
|
407
405
|
this.sentContent = true;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
406
|
+
try {
|
|
407
|
+
await this.sendPayload(payload);
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
this.itemsQueue = [...nonThinking, ...this.itemsQueue];
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
412
413
|
this.lastFlush = Date.now();
|
|
413
414
|
this.flushCount++;
|
|
414
415
|
}
|
|
@@ -446,24 +447,20 @@ export class IMRenderer {
|
|
|
446
447
|
const finalText = isFinal ? this.textBuffer : '';
|
|
447
448
|
if (isFinal)
|
|
448
449
|
this.textBuffer = '';
|
|
449
|
-
if (this.diagEnabled) {
|
|
450
|
-
diag(this.instanceId, 'flush', {
|
|
451
|
-
isFinal,
|
|
452
|
-
itemCount: items.length,
|
|
453
|
-
finalTextLen: finalText.length,
|
|
454
|
-
flushCount: this.flushCount,
|
|
455
|
-
sinceLastFlush: Date.now() - this.lastFlush,
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
450
|
// 1. interactive 模式下:不发 text items(由 result.text 统一发送最终文本)
|
|
459
451
|
const itemsForBatch = this.suppressActivityItems() ? [] : items.filter(it => it.kind !== 'text');
|
|
460
452
|
if (itemsForBatch.length > 0) {
|
|
461
453
|
const payload = { kind: 'activity.batch', items: itemsForBatch };
|
|
462
454
|
this.sentContent = true;
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
455
|
+
try {
|
|
456
|
+
await this.sendPayload(payload);
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
this.itemsQueue = [...itemsForBatch, ...this.itemsQueue];
|
|
460
|
+
if (isFinal && finalText)
|
|
461
|
+
this.textBuffer = finalText + this.textBuffer;
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
467
464
|
this.lastFlush = Date.now();
|
|
468
465
|
this.flushCount++;
|
|
469
466
|
}
|
|
@@ -481,10 +478,13 @@ export class IMRenderer {
|
|
|
481
478
|
this.textBuffer = '';
|
|
482
479
|
const payload = { kind: 'result.text', text, isFinal: false };
|
|
483
480
|
this.sentContent = true;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
481
|
+
try {
|
|
482
|
+
await this.sendPayload(payload);
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
this.textBuffer = text + this.textBuffer;
|
|
486
|
+
throw error;
|
|
487
|
+
}
|
|
488
488
|
this.lastFlush = Date.now();
|
|
489
489
|
this.flushCount++;
|
|
490
490
|
}
|
|
@@ -492,10 +492,13 @@ export class IMRenderer {
|
|
|
492
492
|
if (isFinal && finalText.length > 0) {
|
|
493
493
|
const payload = { kind: 'result.text', text: finalText, isFinal: true };
|
|
494
494
|
this.sentContent = true;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
495
|
+
try {
|
|
496
|
+
await this.sendPayload(payload);
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
this.textBuffer = finalText + this.textBuffer;
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
499
502
|
this.lastFlush = Date.now();
|
|
500
503
|
this.flushCount++;
|
|
501
504
|
}
|
|
@@ -569,7 +572,7 @@ export class IMRenderer {
|
|
|
569
572
|
text: desc,
|
|
570
573
|
};
|
|
571
574
|
if (event.input !== undefined)
|
|
572
|
-
item.arguments = event.input;
|
|
575
|
+
item.arguments = toolInputForDisplay(event.name, event.input);
|
|
573
576
|
return item;
|
|
574
577
|
}
|
|
575
578
|
case 'tool_result':
|