evolcore 0.0.35 → 0.0.36
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 +31 -0
- package/README.md +6 -0
- package/bin/install-codex-managed-hooks.mjs +20 -3
- package/dist/agents/baseagent-availability.js +1 -1
- package/dist/agents/baseagent.js +88 -0
- package/dist/agents/claude-runner.js +59 -12
- package/dist/agents/codex/app-server-client.js +18 -1
- package/dist/agents/codex-runner.js +262 -21
- package/dist/agents/ecagent-runner.js +47 -11
- package/dist/agents/gemini-runner.js +36 -12
- package/dist/aun/aid/client.js +4 -3
- package/dist/aun/aid/control-aid.js +270 -8
- package/dist/aun/aid/identity.js +6 -3
- package/dist/aun/aid/ownership-operation.js +58 -0
- package/dist/aun/aid/store.js +1 -0
- package/dist/aun/group/fs-download.js +67 -2
- package/dist/aun/group/history.js +20 -5
- package/dist/aun/group/operations.js +8 -2
- package/dist/aun/menu/client.js +39 -2
- package/dist/aun/menu/transport.js +1 -1
- package/dist/aun/msg/history.js +43 -4
- package/dist/aun/msg/managed-operation.js +30 -53
- package/dist/aun/msg/p2p.js +51 -15
- package/dist/aun/outbox.js +4 -0
- package/dist/channels/aun.js +260 -73
- package/dist/channels/daemon.js +6 -4
- package/dist/channels/feishu.js +3 -0
- package/dist/cli/agent-command.js +161 -223
- package/dist/cli/agent-create.js +190 -223
- package/dist/cli/agent.js +3 -1
- package/dist/cli/aid-bind-command.js +53 -0
- package/dist/cli/aun-commands.js +384 -91
- package/dist/cli/bench.js +1 -1
- package/dist/cli/cli-argv.js +74 -0
- package/dist/cli/daemon-commands.js +105 -74
- package/dist/cli/diag.js +1 -1
- package/dist/cli/fs-command.js +18 -19
- package/dist/cli/getmeta-command.js +159 -6
- package/dist/cli/index.js +6 -12
- package/dist/cli/init-channel.js +8 -55
- package/dist/cli/init.js +125 -218
- package/dist/cli/queue-command.js +11 -8
- package/dist/cli/restart-monitor.js +134 -48
- package/dist/cli/session-command.js +1 -1
- package/dist/cli/stats.js +7 -5
- package/dist/cli/thread-command.js +103 -30
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-msg-diag.js +1 -0
- package/dist/config/access-policy.js +2 -2
- package/dist/config/boot-log.js +14 -11
- package/dist/config/config-field-policy.js +9 -1
- package/dist/config/config-manager.js +176 -90
- package/dist/config/config-operation-service.js +7 -14
- package/dist/config/contact-book-store.js +142 -26
- package/dist/config/contact-book.js +4 -2
- package/dist/config/lifecycle.js +1 -1
- package/dist/config/owner-config.js +96 -0
- package/dist/config/peer-role-resolver.js +12 -18
- package/dist/config/providers-config.js +382 -0
- package/dist/config/providers-import.js +74 -0
- package/dist/config/providers-management.js +328 -0
- package/dist/config/providers.js +22 -0
- package/dist/config/resolved-config-op.js +17 -14
- package/dist/config/snapshot.js +28 -16
- package/dist/config-store.js +18 -14
- package/dist/core/agent-ipc-registration.js +21 -63
- package/dist/core/agent-runtime.js +16 -5
- package/dist/core/auth/agent-delegation.js +199 -25
- package/dist/core/auth/auth-gateway.js +44 -2
- package/dist/core/auth/authorization-denial.js +1 -1
- package/dist/core/auth/debug-context.js +2 -1
- package/dist/core/auth/delegation-authorization.js +2 -2
- package/dist/core/auth/delegation-diagnostics.js +4 -1
- package/dist/core/auth/operation-authorizer.js +10 -1
- package/dist/core/auth/operation-catalog.js +19 -1
- package/dist/core/bootstrap-service.js +43 -20
- package/dist/core/channel-loader.js +0 -1
- package/dist/core/channel-runtime.js +30 -4
- package/dist/core/command/agent-control.js +196 -37
- package/dist/core/command/cli-intent-parser.js +60 -4
- package/dist/core/command/command-handler.js +212 -60
- package/dist/core/command/connect-menu.js +1 -0
- package/dist/core/command/diag-service.js +5 -4
- package/dist/core/command/file-search.js +368 -0
- package/dist/core/command/menu-catalog.js +2 -1
- package/dist/core/command/menu-handler.js +292 -107
- package/dist/core/command/menu-protocol.js +23 -0
- package/dist/core/command/role-menu.js +7 -19
- package/dist/core/command/skill-slash.js +130 -25
- package/dist/core/command/slash-handler.js +101 -53
- package/dist/core/command/topic-slash.js +16 -5
- package/dist/core/daemon-bootstrap.js +7 -1
- package/dist/core/daemon-control.js +19 -1
- package/dist/core/daemon-lifecycle.js +2 -1
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/evolagent-registry.js +13 -2
- package/dist/core/evolagent.js +3 -6
- package/dist/core/handoff/ipc-registration.js +2 -2
- package/dist/core/handoff/runtime.js +40 -6
- package/dist/core/handoff/store.js +1 -0
- package/dist/core/inference/text-inference.js +95 -6
- package/dist/core/ipc-registration.js +43 -24
- package/dist/core/memory/consolidation-context.js +4 -1
- package/dist/core/memory/consolidation.js +25 -14
- package/dist/core/message/aun-ipc-sender.js +296 -88
- package/dist/core/message/aun-request-registry.js +226 -0
- package/dist/core/message/create-status.js +33 -4
- package/dist/core/message/message-bridge.js +62 -13
- package/dist/core/message/message-log.js +0 -1
- package/dist/core/message/message-queue.js +4 -1
- package/dist/core/message/message-utils.js +8 -1
- package/dist/core/message/peer-mode.js +17 -9
- package/dist/core/message/response-engine.js +266 -124
- package/dist/core/message/retry-scheduler.js +11 -8
- package/dist/core/model/catalog-service.js +60 -18
- package/dist/core/model/model-catalog.js +112 -9
- package/dist/core/model/model-fallback.js +94 -26
- package/dist/core/ownership/owner-resolver.js +687 -0
- package/dist/core/permission/approval-gateway.js +196 -62
- package/dist/core/permission/ec-command-parser.js +138 -25
- package/dist/core/permission/filesystem-approval-scope.js +141 -0
- package/dist/core/permission/protected-paths.js +14 -1
- package/dist/core/permission/sandbox-runtime.js +171 -18
- package/dist/core/permission/tool-policy.js +13 -2
- package/dist/core/role/runtime-policy.js +3 -4
- package/dist/core/session/session-idle-monitor.js +10 -8
- package/dist/core/session/session-manager.js +72 -45
- package/dist/core/session/session-mapper.js +18 -7
- package/dist/core/session/session-renew.js +5 -4
- package/dist/core/session/session-title.js +2 -1
- package/dist/core/session/session-turn-coordinator.js +4 -2
- package/dist/core/skill/index-generator.js +2 -16
- package/dist/core/task/task-recovery-service.js +15 -2
- package/dist/core/thread/command-parser.js +28 -13
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +11 -8
- package/dist/core/thread/thread-operation.js +79 -44
- package/dist/core/thread/thread-route.js +54 -6
- package/dist/core/thread/thread-service.js +236 -45
- package/dist/core/thread/topic-id.js +248 -0
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/eck/message-renderer.js +12 -2
- package/dist/index.js +50 -27
- package/dist/ipc.js +59 -6
- package/dist/paths.js +1 -0
- package/dist/product.js +1 -0
- package/dist/response-system/engines/v1/proactive-flow.js +10 -3
- package/dist/stats/db.js +8 -0
- package/dist/stats/writer.js +26 -4
- package/dist/trigger/authorization.js +48 -3
- package/dist/trigger/feedback.js +28 -10
- package/dist/trigger/ipc-service.js +86 -10
- package/dist/trigger/local-files.js +55 -23
- package/dist/trigger/scheduler.js +57 -11
- package/dist/trigger/system-definitions.js +1 -0
- package/dist/trigger/validation.js +44 -15
- package/dist/utils/aid-bind.js +17 -297
- package/dist/utils/avatar-upload.js +16 -4
- package/dist/utils/cross-platform.js +109 -34
- package/dist/utils/ecweb-supervisor.js +1 -1
- package/dist/utils/ecweb-utils.js +8 -1
- package/dist/utils/error-utils.js +44 -6
- package/dist/utils/instance-registry.js +12 -2
- package/dist/utils/npm-ops.js +102 -27
- package/dist/utils/package-version-info.js +72 -27
- package/dist/utils/restart-safety.js +50 -0
- package/dist/utils/upgrade-jobs.js +304 -0
- package/dist/utils/windows-autostart.js +17 -10
- package/kits/docs/INDEX.md +5 -0
- package/kits/docs/aun/ABILITY_CALLING.md +239 -0
- package/kits/docs/evolcore/INDEX.md +14 -3
- package/kits/docs/evolcore/agent-create.md +55 -141
- package/kits/docs/evolcore/agent.md +38 -16
- package/kits/docs/evolcore/aid.md +34 -0
- package/kits/docs/evolcore/config.md +20 -1
- package/kits/docs/evolcore/event.md +2 -2
- package/kits/docs/evolcore/fs.md +18 -2
- package/kits/docs/evolcore/group-collaboration.md +13 -246
- package/kits/docs/evolcore/group.md +2 -1
- package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +65 -1
- package/kits/docs/evolcore/msg.md +64 -2
- package/kits/docs/evolcore/skill.md +86 -0
- package/kits/docs/evolcore/stats.md +6 -0
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +145 -51
- package/kits/docs/evolcore/trigger.md +48 -3
- package/kits/eck_manifest.json +21 -0
- package/kits/eck_message_manifest.json +4 -4
- package/kits/migrations/migrate-role-config-v5.mjs +20 -7
- package/kits/rules/01-overview.md +2 -1
- package/kits/rules/03-identity.md +3 -1
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/07-memory-and-thread.md +1 -1
- package/kits/schemas/_meta.json +16 -1
- package/kits/schemas/agent-config.schema.13.json +44 -2
- package/kits/schemas/contact-book.schema.3.json +5 -0
- package/kits/schemas/daemon.schema.6.json +3 -1
- package/kits/schemas/daemon.schema.8.json +329 -0
- package/kits/schemas/defaults.schema.5.json +39 -3
- package/kits/schemas/providers.schema.1.json +76 -0
- package/kits/schemas/relation-config.schema.9.json +5 -1
- package/kits/skills/.switches.json +28 -0
- package/kits/skills/aid-ability/SKILL.md +214 -0
- package/kits/skills/ec-agent-create/SKILL.md +162 -0
- package/kits/skills/ec-group/SKILL.md +2 -1
- package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
- package/kits/skills/ec-identity/SKILL.md +143 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
- package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
- package/kits/skills/ec-trigger/SKILL.md +167 -0
- package/kits/skills/ec-trigger/references/examples.md +153 -0
- package/kits/skills/thread-ops/SKILL.md +108 -420
- package/kits/templates/message-fragments/thread-context.md +1 -2
- package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
- package/kits/templates/roles/member.json +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +1 -0
- package/kits/templates/system-fragments/channel.md +2 -1
- package/kits/templates/system-fragments/commands.md +5 -4
- package/kits/templates/system-fragments/identity.md +2 -0
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { initializationBlocksTasks } from '../../cli/agent-create.js';
|
|
2
|
+
import { assertOwnershipAuthorization, getDaemonOwners, refreshOwnershipForAuthorization, OWNERSHIP_CACHE_MAX_AGE_MS } from '../ownership/owner-resolver.js';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import fs from 'fs';
|
|
4
5
|
import os from 'os';
|
|
@@ -24,7 +25,8 @@ import { ExecutionTimeoutError, OrphanedTargetSessionError, isExecutionFailure,
|
|
|
24
25
|
import { readEvolcoreRuntimeFlavor, tagErrorTextWithEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
25
26
|
import { describeProactiveSendCommandFailure, isExactEvolcoreSendCommandForSession, isThreadRoutedEvolcoreSendCommand, parseCodexToolCommand, resolveCodexShellCarrierArgv, resolveCodexShellCarrierString, } from '../permission/ec-command-parser.js';
|
|
26
27
|
import { resolveThreadRouteTarget } from '../thread/thread-route.js';
|
|
27
|
-
import {
|
|
28
|
+
import { isMainSession, isTopicSession, isTopicThreadId, sessionLocalTopicId, sessionTopicThreadId } from '../thread/topic-id.js';
|
|
29
|
+
import { summarizeToolInputForDisplay, toolInputForDisplay } from '../../utils/tool-summary.js';
|
|
28
30
|
import { agentDataDir, agentDir as resolveAgentDir, agentMdPath, getPackageRoot, resolveRoot, resolvePaths, debugSessionsDir } from '../../paths.js';
|
|
29
31
|
import { renderKitSections, resolveSessionManifest, DEFAULT_MANIFEST_FILE } from '../../eck/kit-renderer.js';
|
|
30
32
|
import { renderMessageBody } from '../../eck/message-renderer.js';
|
|
@@ -65,10 +67,13 @@ import { normalizeToolErrorClassification } from '../permission/tool-error-code.
|
|
|
65
67
|
import { normalizeExecutionPermissionMode } from '../permission/mode.js';
|
|
66
68
|
import { buildToolLifecycleEventKey } from '../audit/event-key.js';
|
|
67
69
|
import { aunMessageTranscriptUuid } from '../session/session-transcript.js';
|
|
68
|
-
import { loadDaemonConfig } from '../../config-store.js';
|
|
69
70
|
import { assertAgentPolicyAuthorization } from '../role/runtime-policy.js';
|
|
70
71
|
import { auditFullAccessEvent } from '../auth/authorization-audit.js';
|
|
71
|
-
import { TaskRecoveryService, isTaskRecoveryCurrent } from '../task/task-recovery-service.js';
|
|
72
|
+
import { REPLY_DELIVERY_FAILED_REASON, TaskRecoveryService, isTaskRecoveryCurrent } from '../task/task-recovery-service.js';
|
|
73
|
+
/** Resolve the channel type separately from a channel instance name. */
|
|
74
|
+
export function resolveMessageChannelType(options, message) {
|
|
75
|
+
return options?.channelType || message.channelType || message.channel;
|
|
76
|
+
}
|
|
72
77
|
const QUESTION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
73
78
|
/** Pending async questions outlive the originating turn, but never change its backend binding. */
|
|
74
79
|
/** 带 --thread 的跨话题发送:话题存在于本端索引且目标一致时视为合法发送。 */
|
|
@@ -136,7 +141,7 @@ export class AsyncQuestionService {
|
|
|
136
141
|
continue;
|
|
137
142
|
const replyContext = {
|
|
138
143
|
delivery: envelope.delivery ?? envelope.replyContext?.delivery,
|
|
139
|
-
threadId: session
|
|
144
|
+
threadId: sessionTopicThreadId(session),
|
|
140
145
|
metadata: {
|
|
141
146
|
encrypted: message.encrypted ?? envelope.replyContext?.metadata?.encrypted,
|
|
142
147
|
chatmode: envelope.chatmode,
|
|
@@ -150,7 +155,7 @@ export class AsyncQuestionService {
|
|
|
150
155
|
channel: message.channel, channelType: session.channelType,
|
|
151
156
|
channelId: session.channelId, peerId: message.peerId, peerType: message.peerType,
|
|
152
157
|
chatType: session.chatType === 'group' ? 'group' : 'private',
|
|
153
|
-
threadId: session
|
|
158
|
+
threadId: sessionTopicThreadId(session), selfAID: session.selfAID,
|
|
154
159
|
},
|
|
155
160
|
envelope: { ...envelope, sessionId: session.id, replyContext, delivery: replyContext.delivery },
|
|
156
161
|
channelKey: adapter.channelKey, createdAt: Date.now(), expiresAt: Date.now() + QUESTION_TTL_MS,
|
|
@@ -217,7 +222,7 @@ export class AsyncQuestionService {
|
|
|
217
222
|
|| session.channel !== record.origin.channel
|
|
218
223
|
|| session.channelType !== record.origin.channelType
|
|
219
224
|
|| session.channelId !== record.origin.channelId
|
|
220
|
-
|| (session
|
|
225
|
+
|| (sessionTopicThreadId(session) ?? '') !== (record.origin.threadId || '')
|
|
221
226
|
|| !(await this.deps.authorize(record, session)))
|
|
222
227
|
return undefined;
|
|
223
228
|
return session;
|
|
@@ -327,11 +332,18 @@ export class AsyncQuestionService {
|
|
|
327
332
|
},
|
|
328
333
|
});
|
|
329
334
|
try {
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
335
|
+
const envelope = { ...record.envelope, operationId: record.id };
|
|
336
|
+
const receipt = await adapter.restoreInteraction?.(envelope, interaction, record.deliveryReceipt)
|
|
337
|
+
?? await sendInteractionPayload(adapter, envelope, interaction, renderActionAsText(interaction), record.envelope.replyContext);
|
|
333
338
|
if (!isInteractionSendAccepted(receipt))
|
|
334
339
|
throw new Error('Async question card was not accepted for delivery');
|
|
340
|
+
if (typeof receipt === 'object' && (receipt.status === 'sent' || receipt.status === 'queued')) {
|
|
341
|
+
const current = this.read(record.selfAid, record.id);
|
|
342
|
+
if (current) {
|
|
343
|
+
current.deliveryReceipt = receipt;
|
|
344
|
+
this.save(current);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
335
347
|
}
|
|
336
348
|
catch (error) {
|
|
337
349
|
// Keep the durable pending record for a later channel reconnect/daemon restore.
|
|
@@ -418,6 +430,7 @@ function isShowActivitiesMode(value) {
|
|
|
418
430
|
}
|
|
419
431
|
const TOOL_INPUT_DIAGNOSTIC_MAX_LENGTH = 96_000;
|
|
420
432
|
const TOOL_INPUT_DIAGNOSTIC_MAX_FINDINGS = 3;
|
|
433
|
+
const TOOL_INPUT_DIAGNOSTIC_MAX_SHELL_DEPTH = 4;
|
|
421
434
|
const TOOL_INPUT_DIAGNOSTIC_TIMEOUT_MS = 1_000;
|
|
422
435
|
function isToolResultFailure(event) {
|
|
423
436
|
return event.isError === true
|
|
@@ -595,49 +608,59 @@ function diagnosticLocation(source, startOffset, endOffset = startOffset) {
|
|
|
595
608
|
function diagnosticFinding(ruleId, title, advice, confidence, source, startOffset, endOffset) {
|
|
596
609
|
return { ruleId, title, advice, confidence, location: diagnosticLocation(source, startOffset, endOffset) };
|
|
597
610
|
}
|
|
598
|
-
function
|
|
611
|
+
function diagnosticCandidate(source, command = source.value, shellDepth = 0) {
|
|
612
|
+
while (shellDepth < TOOL_INPUT_DIAGNOSTIC_MAX_SHELL_DEPTH) {
|
|
613
|
+
const carrier = resolveCodexShellCarrierString(command);
|
|
614
|
+
if (!carrier)
|
|
615
|
+
break;
|
|
616
|
+
command = carrier.command;
|
|
617
|
+
shellDepth++;
|
|
618
|
+
}
|
|
619
|
+
const commandOffset = source.value.lastIndexOf(command);
|
|
620
|
+
if (commandOffset < 0)
|
|
621
|
+
return undefined;
|
|
622
|
+
return { source, command, commandOffset, carrier: shellDepth > 0, nestedShell: shellDepth > 1 };
|
|
623
|
+
}
|
|
624
|
+
function diagnosticCandidates(input, displayInput) {
|
|
599
625
|
const candidates = [];
|
|
600
|
-
if (typeof
|
|
601
|
-
const source = { field: 'command', value:
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
commandOffset: commandOffset >= 0 ? commandOffset : 0,
|
|
609
|
-
carrier: true,
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
else {
|
|
613
|
-
candidates.push({ source, command: input.command, commandOffset: 0, carrier: false });
|
|
614
|
-
}
|
|
626
|
+
if (typeof displayInput.command === 'string' && boundedDiagnosticInput(displayInput.command)) {
|
|
627
|
+
const source = { field: 'command', value: displayInput.command };
|
|
628
|
+
const rawCarrier = typeof input.command === 'string'
|
|
629
|
+
? resolveCodexShellCarrierString(input.command)
|
|
630
|
+
: undefined;
|
|
631
|
+
const candidate = diagnosticCandidate(source, rawCarrier?.command ?? source.value, rawCarrier ? 1 : 0);
|
|
632
|
+
if (candidate)
|
|
633
|
+
candidates.push(candidate);
|
|
615
634
|
}
|
|
616
635
|
if (isStringArgv(input.commandArgv) && input.commandArgv.every(boundedDiagnosticInput)) {
|
|
617
636
|
const carrier = resolveCodexShellCarrierArgv(input.commandArgv);
|
|
618
637
|
if (carrier) {
|
|
619
638
|
const argumentIndex = input.commandArgv.length - 1;
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
commandOffset: 0,
|
|
624
|
-
carrier: true,
|
|
625
|
-
});
|
|
639
|
+
const candidate = diagnosticCandidate({ field: 'commandArgv', value: input.commandArgv[argumentIndex], argumentIndex }, carrier.command, 1);
|
|
640
|
+
if (candidate)
|
|
641
|
+
candidates.push(candidate);
|
|
626
642
|
}
|
|
627
643
|
else {
|
|
628
644
|
const argumentIndex = 0;
|
|
629
|
-
if (findApplyPatchArgumentsOffset(input.commandArgv[argumentIndex] ?? '') >= 0
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
commandOffset: 0,
|
|
634
|
-
carrier: false,
|
|
645
|
+
if (findApplyPatchArgumentsOffset(input.commandArgv[argumentIndex] ?? '') >= 0
|
|
646
|
+
&& input.commandArgv.slice(1).every(value => value.trim() === '')) {
|
|
647
|
+
const candidate = diagnosticCandidate({
|
|
648
|
+
field: 'commandArgv', value: input.commandArgv[argumentIndex], argumentIndex,
|
|
635
649
|
});
|
|
650
|
+
if (candidate)
|
|
651
|
+
candidates.push(candidate);
|
|
636
652
|
}
|
|
637
653
|
}
|
|
638
654
|
}
|
|
639
655
|
return candidates;
|
|
640
656
|
}
|
|
657
|
+
function addApplyPatchMissingDocumentFinding(findings, candidate) {
|
|
658
|
+
const argumentsOffset = findApplyPatchArgumentsOffset(candidate.command);
|
|
659
|
+
if (argumentsOffset < 0 || candidate.command.slice(argumentsOffset).trim().length > 0)
|
|
660
|
+
return;
|
|
661
|
+
const executableStart = candidate.command.length - candidate.command.trimStart().length;
|
|
662
|
+
findings.push(diagnosticFinding('apply-patch-missing-document', 'apply_patch 缺少完整补丁文档', '提交完整补丁文档,并以 *** Begin Patch 开头、以 *** End Patch 结尾。', '高', candidate.source, candidate.commandOffset + executableStart, candidate.commandOffset + argumentsOffset));
|
|
663
|
+
}
|
|
641
664
|
function addHeredocDiagnosticFindings(findings, candidate) {
|
|
642
665
|
const argumentsOffset = findApplyPatchArgumentsOffset(candidate.command);
|
|
643
666
|
if (argumentsOffset < 0)
|
|
@@ -673,11 +696,11 @@ function addApplyPatchCompositionFinding(findings, candidate) {
|
|
|
673
696
|
findings.push(diagnosticFinding('apply-patch-composed-command', 'apply_patch 与其它 Shell 命令组合提交', '一次工具调用只提交完整的 apply_patch;测试、检查或其它命令另行调用。', '高', candidate.source, candidate.commandOffset + separator, candidate.commandOffset + separator + 1));
|
|
674
697
|
}
|
|
675
698
|
function addNestedShellDiagnosticFinding(findings, candidate) {
|
|
676
|
-
if (!candidate.
|
|
699
|
+
if (!candidate.nestedShell || findApplyPatchArgumentsOffset(candidate.command) < 0)
|
|
677
700
|
return;
|
|
678
701
|
findings.push(diagnosticFinding('nested-shell-escaping-risk', '命令包含嵌套 Shell 包装层', '尽量直接提交单层 Shell 命令;需要 heredoc 或 JSON 时,减少外层转义和包装。', '中', candidate.source, candidate.commandOffset, candidate.commandOffset + candidate.command.length));
|
|
679
702
|
}
|
|
680
|
-
function addJsonArrayDiagnosticFinding(findings, input) {
|
|
703
|
+
function addJsonArrayDiagnosticFinding(findings, input, displayInput) {
|
|
681
704
|
const rawArgv = isStringArgv(input.commandArgv) ? input.commandArgv : undefined;
|
|
682
705
|
const parsedArgv = rawArgv ?? (typeof input.command === 'string'
|
|
683
706
|
? (() => {
|
|
@@ -694,7 +717,7 @@ function addJsonArrayDiagnosticFinding(findings, input) {
|
|
|
694
717
|
const value = parsedArgv[6];
|
|
695
718
|
const source = rawArgv
|
|
696
719
|
? { field: 'commandArgv', value: rawArgv[6] ?? '', argumentIndex: 6 }
|
|
697
|
-
: { field: 'command', value: typeof
|
|
720
|
+
: { field: 'command', value: typeof displayInput.command === 'string' ? displayInput.command : '' };
|
|
698
721
|
if (value === undefined) {
|
|
699
722
|
findings.push(diagnosticFinding('json-array-argument-missing', 'tags 参数缺少完整 JSON 数组值', '将 tags 作为一个完整参数传入,例如 PowerShell 使用单引号包住 ["DSH","AUN"]。', '高', source, source.value.length));
|
|
700
723
|
return;
|
|
@@ -713,7 +736,7 @@ function addJsonArrayDiagnosticFinding(findings, input) {
|
|
|
713
736
|
&& (value.startsWith('[') || value.endsWith(','));
|
|
714
737
|
findings.push(diagnosticFinding(split ? 'json-array-argument-splitting' : 'json-array-argument-invalid', split ? 'tags 的 JSON 数组疑似被拆成多个参数' : 'tags 参数不是可解析的 JSON 字符串数组', '保证整个 JSON 数组位于同一个参数中;PowerShell 普通单引号字符串内不要再添加反斜杠转义。', split ? '中' : '高', source, split ? source.value.length : 0, source.value.length));
|
|
715
738
|
}
|
|
716
|
-
function addCommandArgvMismatchFinding(findings, input) {
|
|
739
|
+
function addCommandArgvMismatchFinding(findings, input, displayInput) {
|
|
717
740
|
if (typeof input.command !== 'string' || !isStringArgv(input.commandArgv))
|
|
718
741
|
return;
|
|
719
742
|
const commandArgv = input.commandArgv;
|
|
@@ -725,7 +748,8 @@ function addCommandArgvMismatchFinding(findings, input) {
|
|
|
725
748
|
if (!parsed.ok || parsed.argv.length === commandArgv.length
|
|
726
749
|
&& parsed.argv.every((value, index) => value === commandArgv[index]))
|
|
727
750
|
return;
|
|
728
|
-
|
|
751
|
+
const displayCommand = typeof displayInput.command === 'string' ? displayInput.command : input.command;
|
|
752
|
+
findings.push(diagnosticFinding('command-argv-mismatch', 'command 与 commandArgv 的解析结果不一致', '以执行器提供的结构化 commandArgv 为准,避免在外层重新拼接或改写命令文本。', '中', { field: 'command', value: displayCommand }, 0, displayCommand.length));
|
|
729
753
|
}
|
|
730
754
|
function deduplicateDiagnosticFindings(findings) {
|
|
731
755
|
const seen = new Set();
|
|
@@ -749,21 +773,23 @@ function diagnoseToolInput(original, event, deadline) {
|
|
|
749
773
|
return undefined;
|
|
750
774
|
}
|
|
751
775
|
}
|
|
752
|
-
const
|
|
776
|
+
const displayInput = toolInputForDisplay(original.name, input) ?? input;
|
|
777
|
+
const candidates = diagnosticCandidates(input, displayInput);
|
|
753
778
|
if (Date.now() > deadline)
|
|
754
779
|
return undefined;
|
|
755
780
|
const findings = [];
|
|
756
781
|
for (const candidate of candidates) {
|
|
757
782
|
if (Date.now() > deadline)
|
|
758
783
|
return undefined;
|
|
784
|
+
addApplyPatchMissingDocumentFinding(findings, candidate);
|
|
759
785
|
addHeredocDiagnosticFindings(findings, candidate);
|
|
760
786
|
addApplyPatchCompositionFinding(findings, candidate);
|
|
761
787
|
addNestedShellDiagnosticFinding(findings, candidate);
|
|
762
788
|
}
|
|
763
789
|
if (Date.now() <= deadline)
|
|
764
|
-
addJsonArrayDiagnosticFinding(findings, input);
|
|
790
|
+
addJsonArrayDiagnosticFinding(findings, input, displayInput);
|
|
765
791
|
if (Date.now() <= deadline)
|
|
766
|
-
addCommandArgvMismatchFinding(findings, input);
|
|
792
|
+
addCommandArgvMismatchFinding(findings, input, displayInput);
|
|
767
793
|
if (Date.now() > deadline)
|
|
768
794
|
return undefined;
|
|
769
795
|
const deduplicated = deduplicateDiagnosticFindings(findings);
|
|
@@ -944,7 +970,10 @@ function formatRetryInputAlreadySubmittedMessage(error) {
|
|
|
944
970
|
if (retryCount !== undefined) {
|
|
945
971
|
return `⚠️ Codex 已重连 ${retryCount} 次模型服务后仍失败。本次执行已停止。`;
|
|
946
972
|
}
|
|
947
|
-
|
|
973
|
+
// 无重试进度可报时,说明输入已提交但终态未确认:明确告知未自动重试,
|
|
974
|
+
// 避免用户误以为系统会在后台继续。
|
|
975
|
+
const reason = getErrorMessage(error, undefined, false) || 'Codex 暂时不可用';
|
|
976
|
+
return `⚠️ ${reason}\n当前输入已经提交,但本轮未能确认完成。系统未自动重试,请检查会话状态后再决定是否重试。`;
|
|
948
977
|
}
|
|
949
978
|
function isNonErrorInterruptReason(reason) {
|
|
950
979
|
return reason === 'new_message'
|
|
@@ -1007,9 +1036,10 @@ function formatProtocolReplayExhaustedMessage(reason, replayAttempts) {
|
|
|
1007
1036
|
return `${detail}(${reason}),已立即重试 ${replayAttempts} 次仍失败,任务未提交。\n请稍后重试;若持续出现,请切换模型或向上游服务商提供错误代码和发生时间。`;
|
|
1008
1037
|
}
|
|
1009
1038
|
function withFallbackModel(override, candidate) {
|
|
1010
|
-
const { model: _model, effort: _effort, effortMode: _effortMode, ...preserved } = override || {};
|
|
1039
|
+
const { model: _model, provider: _provider, effort: _effort, effortMode: _effortMode, ...preserved } = override || {};
|
|
1011
1040
|
return {
|
|
1012
1041
|
...preserved,
|
|
1042
|
+
...(candidate.provider ? { provider: candidate.provider } : {}),
|
|
1013
1043
|
model: candidate.model,
|
|
1014
1044
|
...(candidate.effort ? { effort: candidate.effort } : { effortMode: 'model_default' }),
|
|
1015
1045
|
};
|
|
@@ -1109,6 +1139,9 @@ function mergeContextRecoveryResult(previous, next) {
|
|
|
1109
1139
|
function isStandaloneContextLimitErrorLine(line) {
|
|
1110
1140
|
return /^(?:(?:api )?error\s*:\s*)?(?:prompt is too long|input is too long|context too long|context_length_exceeded|context_window_exceeded|context window limit|maximum context length|上下文过长)\s*[.!。]?$/i.test(line.trim());
|
|
1111
1141
|
}
|
|
1142
|
+
function stripThinkingBlocks(text) {
|
|
1143
|
+
return text.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '');
|
|
1144
|
+
}
|
|
1112
1145
|
/**
|
|
1113
1146
|
* 网关有时把上下文超限错误包装进 text event。只丢弃独立错误行,保留同一 event
|
|
1114
1147
|
* 中其它行及包含错误字面量的正常输出;原文本仍由调用方保留作恢复判定。
|
|
@@ -1211,6 +1244,7 @@ export class ResponseEngine {
|
|
|
1211
1244
|
agentMap;
|
|
1212
1245
|
legacySingleRunnerMode;
|
|
1213
1246
|
primaryRunnerKey;
|
|
1247
|
+
daemonTriggerOwnerAid;
|
|
1214
1248
|
interruptedSessions = new Map(); // sessionId → reason ('new_message' | 'stop' | ...)
|
|
1215
1249
|
timeoutErrors = new Map();
|
|
1216
1250
|
activeTimeoutHandlers = new Map();
|
|
@@ -1262,6 +1296,17 @@ export class ResponseEngine {
|
|
|
1262
1296
|
&& this.agentMap.values().next().value?.name === requestedBaseagent) {
|
|
1263
1297
|
return this.agentMap.values().next().value;
|
|
1264
1298
|
}
|
|
1299
|
+
// The daemon scheduler has no Agent registry entry. Its runner owner is
|
|
1300
|
+
// explicitly bound at startup, so multiple Agents using the same backend
|
|
1301
|
+
// cannot make host-owned Trigger sessions ambiguous.
|
|
1302
|
+
if (channel === 'daemon' && this.daemonTriggerOwnerAid
|
|
1303
|
+
&& selfAID === this.daemonTriggerOwnerAid && requestedBaseagent) {
|
|
1304
|
+
const ownerName = this.primaryRunnerKey.slice(0, this.primaryRunnerKey.lastIndexOf('::'));
|
|
1305
|
+
const runner = this.agentMap.get(`${ownerName}::${requestedBaseagent}`);
|
|
1306
|
+
if (runner)
|
|
1307
|
+
return runner;
|
|
1308
|
+
throw new BaseagentRunnerUnavailableError(ownerName, requestedBaseagent, this.getAvailableBaseagentsForOwner(ownerName));
|
|
1309
|
+
}
|
|
1265
1310
|
if (selfAID && requestedBaseagent) {
|
|
1266
1311
|
const key = `${selfAID}::${requestedBaseagent}`;
|
|
1267
1312
|
if (this.agentMap.has(key))
|
|
@@ -1301,7 +1346,7 @@ export class ResponseEngine {
|
|
|
1301
1346
|
if (!aid)
|
|
1302
1347
|
return undefined;
|
|
1303
1348
|
const config = resolveEffective({ self: aid }, { cache: true, expand: true });
|
|
1304
|
-
return createDecisionInferenceProvider(config.decision_model);
|
|
1349
|
+
return createDecisionInferenceProvider(config.decision_model, config.Services);
|
|
1305
1350
|
}
|
|
1306
1351
|
getAvailableBaseagentsForOwner(evolName) {
|
|
1307
1352
|
return loadedBaseagentIds(this.agentMap.keys(), evolName);
|
|
@@ -1350,7 +1395,8 @@ export class ResponseEngine {
|
|
|
1350
1395
|
}
|
|
1351
1396
|
/** 判断是否为后台会话(仅主会话参与判断,话题会话独立) */
|
|
1352
1397
|
isBackgroundSession(session, _channel, _channelId) {
|
|
1353
|
-
|
|
1398
|
+
// 只有话题会话是独立的;主话题(含规范 id)仍是这条关系的主会话。
|
|
1399
|
+
if (isTopicSession(session))
|
|
1354
1400
|
return false;
|
|
1355
1401
|
// 使用 session 自身的 channelType 精确定位 active.json,避免扫描误匹配
|
|
1356
1402
|
const active = this.sessionManager.getActiveSessionSync(session.channel, session.channelId, session.channelType, session.selfAID);
|
|
@@ -1578,12 +1624,12 @@ export class ResponseEngine {
|
|
|
1578
1624
|
}
|
|
1579
1625
|
}
|
|
1580
1626
|
async observerTargetSession(target) {
|
|
1581
|
-
const session = target.threadId
|
|
1582
|
-
? await this.sessionManager.getThreadSession(target.channelKey, target.channelId, target.threadId)
|
|
1627
|
+
const session = isTopicThreadId(target.threadId)
|
|
1628
|
+
? await this.sessionManager.getThreadSession(target.channelKey, target.channelId, String(target.threadId))
|
|
1583
1629
|
: this.sessionManager.getActiveSessionSync(target.channelKey, target.channelId, 'aun', target.selfAid);
|
|
1584
1630
|
if (!session || session.deletedAt || session.selfAID !== target.selfAid || session.channel !== target.channelKey
|
|
1585
1631
|
|| session.channelId !== target.channelId || session.chatType !== target.chatType
|
|
1586
|
-
|| (session
|
|
1632
|
+
|| (sessionTopicThreadId(session) ?? '') !== (target.threadId || '')
|
|
1587
1633
|
|| !this.sessionManager.hasBoundAgentSession(session)
|
|
1588
1634
|
|| (target.chatType === 'group' && (session.metadata?.groupId !== target.channelId
|
|
1589
1635
|
|| !session.metadata?.observerLastActorId)))
|
|
@@ -1640,7 +1686,7 @@ export class ResponseEngine {
|
|
|
1640
1686
|
try {
|
|
1641
1687
|
await this.messageQueue.enqueuePersisted(session.id, {
|
|
1642
1688
|
channel: session.channel, channelType: 'aun', channelId: session.channelId,
|
|
1643
|
-
selfAID: session.selfAID, baseagent: session.baseagent, threadId: session
|
|
1689
|
+
selfAID: session.selfAID, baseagent: session.baseagent, threadId: sessionTopicThreadId(session) ?? '',
|
|
1644
1690
|
chatType: target.chatType, groupId: target.chatType === 'group' ? target.channelId : undefined,
|
|
1645
1691
|
peerId: actorId, peerName: session.metadata?.observerLastPeerName || session.metadata?.peerName,
|
|
1646
1692
|
peerType: session.metadata?.observerLastPeerType || session.metadata?.peerType,
|
|
@@ -1727,7 +1773,7 @@ export class ResponseEngine {
|
|
|
1727
1773
|
return { state: 'terminal', taskId: terminal.taskId, generation: terminal.generation,
|
|
1728
1774
|
outcome: terminal.status === 'committed' ? 'completed'
|
|
1729
1775
|
: ['cancelled', 'superseded', 'interrupted'].includes(terminal.status) ? 'skipped' : 'failed',
|
|
1730
|
-
reason: terminal.reason };
|
|
1776
|
+
reason: terminal.reason, recoverable: terminal.recoverable };
|
|
1731
1777
|
}
|
|
1732
1778
|
const queued = this.messageQueue?.getMessageState(sessionId, attemptId, selfAid);
|
|
1733
1779
|
if (queued?.state === 'queued')
|
|
@@ -1765,9 +1811,9 @@ export class ResponseEngine {
|
|
|
1765
1811
|
if (timeout)
|
|
1766
1812
|
await timeout(error);
|
|
1767
1813
|
else
|
|
1768
|
-
await this.interruptSession(sessionId, error instanceof OrphanedTargetSessionError ? 'execution_error' : 'timeout');
|
|
1814
|
+
await this.interruptSession(sessionId, error instanceof OrphanedTargetSessionError ? 'execution_error' : 'timeout', false, isRetryableError(error));
|
|
1769
1815
|
}
|
|
1770
|
-
async interruptSession(sessionId, reason, strict = false) {
|
|
1816
|
+
async interruptSession(sessionId, reason, strict = false, recoverable) {
|
|
1771
1817
|
this.clearPauseSession(sessionId);
|
|
1772
1818
|
// A retry delay has no active runner stream to abort. Wake it before any
|
|
1773
1819
|
// session-store I/O so /stop and a newer message take effect immediately.
|
|
@@ -1796,7 +1842,7 @@ export class ResponseEngine {
|
|
|
1796
1842
|
// shutdown 超时退出会再次留下 started/interrupting 的悬空任务。
|
|
1797
1843
|
const runtimeContext = this.activeTaskRuntimeContexts.get(sessionId);
|
|
1798
1844
|
const taskSpan = this.activeTaskSpans.get(sessionId);
|
|
1799
|
-
const taskId = (await this.turnCoordinator.completeInterrupt(session, effectiveReason))
|
|
1845
|
+
const taskId = (await this.turnCoordinator.completeInterrupt(session, effectiveReason, recoverable))
|
|
1800
1846
|
?? interruptedTaskId
|
|
1801
1847
|
?? taskSpan?.taskId;
|
|
1802
1848
|
if (taskId) {
|
|
@@ -1841,7 +1887,7 @@ export class ResponseEngine {
|
|
|
1841
1887
|
replyContext: {
|
|
1842
1888
|
...replyContextFromSession(session),
|
|
1843
1889
|
sessionId: session.id,
|
|
1844
|
-
threadId: session
|
|
1890
|
+
threadId: sessionTopicThreadId(session) ?? session.metadata?.replyContext?.threadId,
|
|
1845
1891
|
},
|
|
1846
1892
|
});
|
|
1847
1893
|
const payload = {
|
|
@@ -2013,6 +2059,13 @@ export class ResponseEngine {
|
|
|
2013
2059
|
for (const { adapter } of this.channels.values())
|
|
2014
2060
|
this.restoreAsyncQuestions(adapter);
|
|
2015
2061
|
}
|
|
2062
|
+
/** Bind daemon-owned Trigger sessions to the configured primary runner owner. */
|
|
2063
|
+
setDaemonTriggerOwner(aid) {
|
|
2064
|
+
const normalized = aid.trim();
|
|
2065
|
+
if (!normalized)
|
|
2066
|
+
throw new Error('daemon trigger owner AID is required');
|
|
2067
|
+
this.daemonTriggerOwnerAid = normalized;
|
|
2068
|
+
}
|
|
2016
2069
|
/** 更新 EvolAgent.lastActivity —— 每次发出 status.* 事件(含 progress)时调用 */
|
|
2017
2070
|
touchAgentActivity(channelKey) {
|
|
2018
2071
|
const owning = this.agentRegistry?.resolveByChannel(channelKey);
|
|
@@ -2085,10 +2138,10 @@ export class ResponseEngine {
|
|
|
2085
2138
|
if (!peerChannelId)
|
|
2086
2139
|
return { items: [] };
|
|
2087
2140
|
try {
|
|
2088
|
-
const claim = claimHints(resolvePaths().sessionsDir, 'aun', peerChannelId, selfAID, session
|
|
2141
|
+
const claim = claimHints(resolvePaths().sessionsDir, 'aun', peerChannelId, selfAID, (sessionTopicThreadId(session) ?? ''), (message.chatType || session.chatType || 'private'), message.source === 'owner-inject' ? 'interrupt' : 'next', message.observerHintId, session.channel);
|
|
2089
2142
|
if (claim.hints.length === 0)
|
|
2090
2143
|
return { items: [] };
|
|
2091
|
-
logger.info(`[ResponseEngine] claimed ${claim.hints.length} owner-hint(s) for ${peerChannelId} thread=${session
|
|
2144
|
+
logger.info(`[ResponseEngine] claimed ${claim.hints.length} owner-hint(s) for ${peerChannelId} thread=${sessionTopicThreadId(session) ?? 'main'}`);
|
|
2092
2145
|
return { items: hintsToSubMessages(claim.hints), claim };
|
|
2093
2146
|
}
|
|
2094
2147
|
catch (e) {
|
|
@@ -2193,7 +2246,7 @@ export class ResponseEngine {
|
|
|
2193
2246
|
// while the original override can still describe the pre-run UNBOUND
|
|
2194
2247
|
// snapshot. Compact and retry must address the accepted backend under the
|
|
2195
2248
|
// same complete TurnLease or a runner could create a second backend.
|
|
2196
|
-
const recoveryModelOverride = session
|
|
2249
|
+
const recoveryModelOverride = isTopicSession(session)
|
|
2197
2250
|
? {
|
|
2198
2251
|
...(modelOverride || {}),
|
|
2199
2252
|
backend: { kind: 'topic', agentSessionId: session.agentSessionId },
|
|
@@ -2213,6 +2266,7 @@ export class ResponseEngine {
|
|
|
2213
2266
|
this.announcedCompactStarts.add(session.id);
|
|
2214
2267
|
let compacted;
|
|
2215
2268
|
try {
|
|
2269
|
+
await beforeRunQuery?.();
|
|
2216
2270
|
compacted = await agent.compact(session.id, session.agentSessionId, absoluteProjectPath, recoveryModelOverride);
|
|
2217
2271
|
}
|
|
2218
2272
|
finally {
|
|
@@ -2222,7 +2276,7 @@ export class ResponseEngine {
|
|
|
2222
2276
|
if (compactResult === true || compactResult?.ok === true) {
|
|
2223
2277
|
await this.emitOperationalNotice(renderer, '✅ 压缩完成,继续处理...', 'info', 'compact-retry');
|
|
2224
2278
|
await renderer.flush();
|
|
2225
|
-
beforeRunQuery?.();
|
|
2279
|
+
await beforeRunQuery?.();
|
|
2226
2280
|
const retryStream = await agent.runQuery(session.id, insertMessagePromptContext(prompt, messagePromptContext), absoluteProjectPath, session.agentSessionId, undefined, effectiveSystemPrompt, this.sessionManager, recoveryModelOverride, runtimeEnv);
|
|
2227
2281
|
if (await this.cancelStaleRunnerStart(agent, session.id, streamKey, turnLease, turnLease.taskId)) {
|
|
2228
2282
|
throw new Error('RUNNER_START_SUPERSEDED');
|
|
@@ -2355,18 +2409,27 @@ export class ResponseEngine {
|
|
|
2355
2409
|
return false;
|
|
2356
2410
|
}
|
|
2357
2411
|
try {
|
|
2358
|
-
return new Set(
|
|
2412
|
+
return new Set(getDaemonOwners()).has(trigger.authorizedBy);
|
|
2359
2413
|
}
|
|
2360
2414
|
catch {
|
|
2361
2415
|
return false;
|
|
2362
2416
|
}
|
|
2363
2417
|
}
|
|
2418
|
+
async assertCurrentExecutionAuthorization(context, agent) {
|
|
2419
|
+
if (context.executionPermission) {
|
|
2420
|
+
await this.assertCurrentFullAccessExecutionAuthorization(context.executionPermission, agent);
|
|
2421
|
+
}
|
|
2422
|
+
else {
|
|
2423
|
+
await assertOwnershipAuthorization(context);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2364
2426
|
/**
|
|
2365
2427
|
* The authorization envelope is immutable for one logical run, but the
|
|
2366
2428
|
* approving owner and runner profile are live policy. Check
|
|
2367
2429
|
* them immediately before every Runner invocation, including retries.
|
|
2368
2430
|
*/
|
|
2369
|
-
assertCurrentFullAccessExecutionAuthorization(authorization, agent) {
|
|
2431
|
+
async assertCurrentFullAccessExecutionAuthorization(authorization, agent) {
|
|
2432
|
+
await refreshOwnershipForAuthorization(authorization.agentAid);
|
|
2370
2433
|
if (authorization.permissionMode !== 'fullaccess'
|
|
2371
2434
|
|| authorization.processRole !== 'fullaccess-run'
|
|
2372
2435
|
|| authorization.dataScope !== 'daemon'
|
|
@@ -2384,7 +2447,7 @@ export class ResponseEngine {
|
|
|
2384
2447
|
else if (authorization.source !== 'fullaccess-command' && authorization.source !== 'trigger') {
|
|
2385
2448
|
throw new Error('fullaccess execution authorization has an unsupported source');
|
|
2386
2449
|
}
|
|
2387
|
-
const currentOwners = new Set(
|
|
2450
|
+
const currentOwners = new Set(getDaemonOwners());
|
|
2388
2451
|
if (!currentOwners.has(authorization.authorizedBy)) {
|
|
2389
2452
|
throw new Error('fullaccess execution authorization is no longer owned');
|
|
2390
2453
|
}
|
|
@@ -2395,6 +2458,13 @@ export class ResponseEngine {
|
|
|
2395
2458
|
resolveTriggerExecutionIdentity(message, selfAid) {
|
|
2396
2459
|
if (message.source !== 'trigger' || !message.triggerMeta?.triggerId)
|
|
2397
2460
|
return undefined;
|
|
2461
|
+
// 跨话题复查(target_session 投递):本轮是"T2 侧 agent 被自己的复查通知唤醒",
|
|
2462
|
+
// 身份就是 T2 会话自身的对端,与普通入站消息一致。
|
|
2463
|
+
// 若沿用 originPeerId(来源话题 T1 的对端),整个轮次的角色与权限都会错位。
|
|
2464
|
+
if (message.triggerMeta.reviewKind === 'cross-thread-review'
|
|
2465
|
+
|| message.triggerMeta.labels?.kind === 'cross-thread-review') {
|
|
2466
|
+
return undefined;
|
|
2467
|
+
}
|
|
2398
2468
|
const actorId = message.triggerMeta.originPeerId;
|
|
2399
2469
|
const channelType = message.triggerMeta.originChannelType;
|
|
2400
2470
|
const originChannelKey = message.triggerMeta.originChannelKey;
|
|
@@ -2518,6 +2588,7 @@ export class ResponseEngine {
|
|
|
2518
2588
|
originTriggerId: trigger.triggerId,
|
|
2519
2589
|
messageId: opts.messageId ?? message.messageId ?? runId,
|
|
2520
2590
|
error,
|
|
2591
|
+
recoverable: opts.recoverable ?? isRetryableError(new Error(error)),
|
|
2521
2592
|
targetChannel: message.channel,
|
|
2522
2593
|
targetChannelId: message.channelId,
|
|
2523
2594
|
fireTime: trigger.fireTime ?? 0,
|
|
@@ -2691,7 +2762,7 @@ export class ResponseEngine {
|
|
|
2691
2762
|
// 先解析会话,再优先用 session.metadata.channelKey 精确定位实例级 adapter
|
|
2692
2763
|
// message.channel 现在存实例名(channelName),可直接用于精确路由
|
|
2693
2764
|
const { session, absoluteProjectPath } = await this.resolveSession(message);
|
|
2694
|
-
if (message.source === 'owner-inject' && (!message.observerHintId || !peekHints(resolvePaths().sessionsDir, 'aun', session.channelId, session.selfAID || '', session
|
|
2765
|
+
if (message.source === 'owner-inject' && (!message.observerHintId || !peekHints(resolvePaths().sessionsDir, 'aun', session.channelId, session.selfAID || '', (sessionTopicThreadId(session) ?? ''), session.chatType, 'interrupt', session.channel).some(item => item.id === message.observerHintId))) {
|
|
2695
2766
|
this.reportObserverHintFailureForMessage(message, 'HINT_UNAVAILABLE');
|
|
2696
2767
|
return;
|
|
2697
2768
|
}
|
|
@@ -2703,6 +2774,7 @@ export class ResponseEngine {
|
|
|
2703
2774
|
const accessAdapter = accessChannelInfo?.adapter;
|
|
2704
2775
|
const accessAdapterSelfAid = typeof accessAdapter?._selfAid === 'function' ? accessAdapter._selfAid() : undefined;
|
|
2705
2776
|
const selfAidForAccess = accessAdapterSelfAid || message.selfAID || session.selfAID || undefined;
|
|
2777
|
+
await refreshOwnershipForAuthorization(selfAidForAccess, { maxAgeMs: OWNERSHIP_CACHE_MAX_AGE_MS });
|
|
2706
2778
|
if (message.handoffDelivery?.direction === 'target'
|
|
2707
2779
|
&& this.handoffRuntime
|
|
2708
2780
|
&& selfAidForAccess) {
|
|
@@ -2719,7 +2791,10 @@ export class ResponseEngine {
|
|
|
2719
2791
|
const triggerExecutionIdentity = this.resolveTriggerExecutionIdentity(message, selfAidForAccess);
|
|
2720
2792
|
const handoffExecutionIdentity = this.resolveHandoffOriginExecutionIdentity(message, selfAidForAccess);
|
|
2721
2793
|
const executionIdentity = triggerExecutionIdentity ?? handoffExecutionIdentity;
|
|
2722
|
-
|
|
2794
|
+
// 跨话题复查走会话自身身份,本来就解析不出 Trigger 的 origin 身份,不属于"身份缺失"。
|
|
2795
|
+
const isCrossThreadReview = message.triggerMeta?.reviewKind === 'cross-thread-review'
|
|
2796
|
+
|| message.triggerMeta?.labels?.kind === 'cross-thread-review';
|
|
2797
|
+
if (message.source === 'trigger' && !triggerExecutionIdentity && !isCrossThreadReview) {
|
|
2723
2798
|
logger.warn(`[ResponseEngine] Trigger execution identity missing: trigger=${message.triggerMeta?.triggerId ?? '<none>'}`);
|
|
2724
2799
|
const channelInfo = this.resolveChannelInfo(accessChannelKey);
|
|
2725
2800
|
if (channelInfo) {
|
|
@@ -2872,12 +2947,13 @@ export class ResponseEngine {
|
|
|
2872
2947
|
return;
|
|
2873
2948
|
timeoutTriggered = true;
|
|
2874
2949
|
timeoutControl.error = error;
|
|
2950
|
+
timeoutControl.recoverable = isRetryableError(error);
|
|
2875
2951
|
this.timeoutErrors.set(session.id, error);
|
|
2876
2952
|
this.turnCoordinator.hydrate(session);
|
|
2877
2953
|
const reason = error instanceof OrphanedTargetSessionError ? 'execution_error' : 'timeout';
|
|
2878
2954
|
this.turnCoordinator.invalidate(session.id, reason);
|
|
2879
2955
|
this.interruptedSessions.set(session.id, reason);
|
|
2880
|
-
const barrier = this.interruptSession(session.id, reason)
|
|
2956
|
+
const barrier = this.interruptSession(session.id, reason, false, timeoutControl.recoverable)
|
|
2881
2957
|
.catch(interruptError => {
|
|
2882
2958
|
logger.debug(`[ResponseEngine] Timeout interrupt barrier failed: ${interruptError instanceof Error ? interruptError.message : String(interruptError)}`);
|
|
2883
2959
|
});
|
|
@@ -3029,7 +3105,7 @@ export class ResponseEngine {
|
|
|
3029
3105
|
&& commandAuthorization.authorizedBy.length > 0) {
|
|
3030
3106
|
let currentOwner = false;
|
|
3031
3107
|
try {
|
|
3032
|
-
currentOwner = new Set(
|
|
3108
|
+
currentOwner = new Set(getDaemonOwners()).has(commandAuthorization.authorizedBy);
|
|
3033
3109
|
if (commandAuthorization.source === 'agent-policy')
|
|
3034
3110
|
assertAgentPolicyAuthorization(commandAuthorization);
|
|
3035
3111
|
}
|
|
@@ -3153,9 +3229,9 @@ export class ResponseEngine {
|
|
|
3153
3229
|
return;
|
|
3154
3230
|
}
|
|
3155
3231
|
// Agent-owned turns are runnable only in the two explicit execution
|
|
3156
|
-
// states.
|
|
3157
|
-
//
|
|
3158
|
-
//
|
|
3232
|
+
// states. Setup and owner-pending agents must not fall through to a base
|
|
3233
|
+
// runner's default prompt. Missing lifecycle is normalized to active for
|
|
3234
|
+
// legacy configurations.
|
|
3159
3235
|
if (owningAgentForTask && ((!isActive && !isBootstrapping) || initializationBlocksTasks(owningAgentForTask.config))) {
|
|
3160
3236
|
const blockedLifecycle = lifecycle
|
|
3161
3237
|
?? `invalid (${String(owningAgentForTask.config.lifecycle)})`;
|
|
@@ -3257,7 +3333,7 @@ export class ResponseEngine {
|
|
|
3257
3333
|
causation: taskCausation,
|
|
3258
3334
|
});
|
|
3259
3335
|
// ─── 解析 self/peer/config(响应模式解析的输入)───
|
|
3260
|
-
const currentChannelType = options
|
|
3336
|
+
const currentChannelType = resolveMessageChannelType(options, message);
|
|
3261
3337
|
const adapterAny = channelInfo.adapter;
|
|
3262
3338
|
const adapterSelfAid = typeof adapterAny._selfAid === 'function' ? adapterAny._selfAid() : undefined;
|
|
3263
3339
|
// During bootstrap the lifecycle owner is the authoritative identity. Do
|
|
@@ -3283,11 +3359,20 @@ export class ResponseEngine {
|
|
|
3283
3359
|
// operator. Keep the historical private-chat fallback (where channelId
|
|
3284
3360
|
// is the peer identity for AUN), but fail closed for group messages that
|
|
3285
3361
|
// arrive without a sender/operator ID.
|
|
3362
|
+
// 跨话题复查投递的 message.peerId 是占位的 `trigger:<id>`,不是真实对端。这一轮身份
|
|
3363
|
+
// 按 T2 会话自身的对端算(resolveTriggerExecutionIdentity 对这类投递返回 undefined),
|
|
3364
|
+
// 私聊即 channelId;否则 actor 落成伪身份,在唤醒话题里连本条复查都管不了。
|
|
3365
|
+
const isCrossThreadReviewTurn = message.source === 'trigger'
|
|
3366
|
+
&& (message.triggerMeta?.reviewKind === 'cross-thread-review'
|
|
3367
|
+
|| message.triggerMeta?.labels?.kind === 'cross-thread-review');
|
|
3368
|
+
const turnPeerId = isCrossThreadReviewTurn && configChatType === 'private'
|
|
3369
|
+
? message.channelId
|
|
3370
|
+
: peerIdRaw;
|
|
3286
3371
|
const configActorId = authorizationIdentity?.actorId
|
|
3287
|
-
?? (
|
|
3372
|
+
?? (turnPeerId || (configChatType === 'private' ? message.channelId : undefined));
|
|
3288
3373
|
const peerKeyId = authorizationIdentity?.actorId ?? (session.chatType === 'group'
|
|
3289
3374
|
? (session.metadata?.groupId || message.channelId)
|
|
3290
|
-
:
|
|
3375
|
+
: turnPeerId);
|
|
3291
3376
|
const peerKey = authorizationIdentity?.peerKey ?? ((configChannelType && peerKeyId)
|
|
3292
3377
|
? formatPeerKey(configChannelType, peerKeyId)
|
|
3293
3378
|
: undefined);
|
|
@@ -3417,7 +3502,7 @@ export class ResponseEngine {
|
|
|
3417
3502
|
return {
|
|
3418
3503
|
...replyContextFromSession(session, base),
|
|
3419
3504
|
sessionId: session.id,
|
|
3420
|
-
threadId: session
|
|
3505
|
+
threadId: sessionTopicThreadId(session) ?? base?.threadId,
|
|
3421
3506
|
metadata: withTaskMetadata(base?.metadata),
|
|
3422
3507
|
};
|
|
3423
3508
|
};
|
|
@@ -3489,6 +3574,7 @@ export class ResponseEngine {
|
|
|
3489
3574
|
timeoutControl.deliverUnhandled = async (timeoutError) => {
|
|
3490
3575
|
if (!timeoutControl.claimTerminal())
|
|
3491
3576
|
return;
|
|
3577
|
+
const recoverable = timeoutControl.recoverable === true;
|
|
3492
3578
|
const isTotalExecutionTimeout = timeoutError.message === 'TOTAL_EXECUTION_TIMEOUT';
|
|
3493
3579
|
const totalExecutionMs = timeoutError instanceof ExecutionTimeoutError ? timeoutError.timeoutMs : this.totalExecutionLimitMs();
|
|
3494
3580
|
const statusPayload = timeoutError instanceof OrphanedTargetSessionError ? {
|
|
@@ -3525,6 +3611,7 @@ export class ResponseEngine {
|
|
|
3525
3611
|
await adapter.send(envelope, {
|
|
3526
3612
|
kind: 'result.error',
|
|
3527
3613
|
text: userMessage,
|
|
3614
|
+
recoverable,
|
|
3528
3615
|
...(getErrorRawText(timeoutError) ? { raw: getErrorRawText(timeoutError) } : {}),
|
|
3529
3616
|
subtype: normalizeCoreErrorSubtype(timeoutError, undefined, isTotalExecutionTimeout ? 'timeout' : 'timeout'),
|
|
3530
3617
|
reason: isExecutionFailure(timeoutError)
|
|
@@ -3538,6 +3625,7 @@ export class ResponseEngine {
|
|
|
3538
3625
|
this.publishTriggerExecutionFailure(message, isExecutionFailure(timeoutError) ? timeoutError.reason : timeoutError.message, {
|
|
3539
3626
|
messageId,
|
|
3540
3627
|
causation: taskCausation,
|
|
3628
|
+
recoverable,
|
|
3541
3629
|
});
|
|
3542
3630
|
const errorType = prefixErrorType(ERROR_PREFIX.INFRA, classifyError(timeoutError));
|
|
3543
3631
|
this.eventBus.publish({
|
|
@@ -3550,6 +3638,7 @@ export class ResponseEngine {
|
|
|
3550
3638
|
taskId,
|
|
3551
3639
|
generation: turnLease?.generation,
|
|
3552
3640
|
error: timeoutError.message,
|
|
3641
|
+
recoverable,
|
|
3553
3642
|
terminalReason: isExecutionFailure(timeoutError) ? timeoutError.reason : undefined,
|
|
3554
3643
|
errorType,
|
|
3555
3644
|
agentName: agentNameForStats,
|
|
@@ -3817,6 +3906,12 @@ export class ResponseEngine {
|
|
|
3817
3906
|
}
|
|
3818
3907
|
}
|
|
3819
3908
|
opts.sessionId = session.id;
|
|
3909
|
+
// 本地日志用的完整话题身份(含主话题规范 id)。上面只合并了入站
|
|
3910
|
+
// message.replyContext,它不含主话题 id;线上字段由 threadId 单独控制,
|
|
3911
|
+
// 这里补的 localTopicId 不会进入报文。
|
|
3912
|
+
const localTopicId = sessionLocalTopicId(session);
|
|
3913
|
+
if (localTopicId)
|
|
3914
|
+
opts.localTopicId = localTopicId;
|
|
3820
3915
|
if (payload.kind === 'result.text' && payload.isFinal) {
|
|
3821
3916
|
opts.ext = { ...opts.ext, isFinal: true };
|
|
3822
3917
|
}
|
|
@@ -3911,6 +4006,7 @@ export class ResponseEngine {
|
|
|
3911
4006
|
approverPolicy: actorIsManager ? 'agent_manager' : 'requester',
|
|
3912
4007
|
owners,
|
|
3913
4008
|
admins,
|
|
4009
|
+
hostOwners: Array.from(new Set(getDaemonOwners())),
|
|
3914
4010
|
resolveApproverCandidates,
|
|
3915
4011
|
ownerAdapter: aunAdapter,
|
|
3916
4012
|
forceHandoff: !!authorizationIdentity,
|
|
@@ -3927,7 +4023,7 @@ export class ResponseEngine {
|
|
|
3927
4023
|
: undefined,
|
|
3928
4024
|
originAuthRevision,
|
|
3929
4025
|
originRole: peerRole,
|
|
3930
|
-
originThreadId: session.threadId
|
|
4026
|
+
originThreadId: sessionTopicThreadId(session) ?? (isTopicThreadId(message.threadId) ? message.threadId : undefined),
|
|
3931
4027
|
originChatDir: this.sessionManager.getChatDir(session),
|
|
3932
4028
|
approvalTtlMs: 20 * 60 * 1000,
|
|
3933
4029
|
reauthorizeApproval: (request) => {
|
|
@@ -3959,6 +4055,7 @@ export class ResponseEngine {
|
|
|
3959
4055
|
actor: currentApprover,
|
|
3960
4056
|
owners: currentOwners,
|
|
3961
4057
|
admins: currentAdmins,
|
|
4058
|
+
hostOwners: Array.from(new Set(getDaemonOwners())),
|
|
3962
4059
|
originPrincipalId: hasTrustedPrincipal(currentOrigin)
|
|
3963
4060
|
? currentOrigin.principalId
|
|
3964
4061
|
: undefined,
|
|
@@ -4080,13 +4177,17 @@ export class ResponseEngine {
|
|
|
4080
4177
|
}
|
|
4081
4178
|
: {}),
|
|
4082
4179
|
armApprovedDelegationCommand: this.agentDelegationRegistry
|
|
4083
|
-
? (sessionToken, commandHash) => this.agentDelegationRegistry.armApprovedCommand({
|
|
4180
|
+
? (sessionToken, commandHash, approvalId) => this.agentDelegationRegistry.armApprovedCommand({
|
|
4084
4181
|
sessionToken,
|
|
4085
4182
|
sessionId: session.id,
|
|
4086
4183
|
taskId,
|
|
4087
4184
|
commandHash,
|
|
4185
|
+
approvalId,
|
|
4088
4186
|
})
|
|
4089
4187
|
: undefined,
|
|
4188
|
+
revokeApprovedDelegationCommand: this.agentDelegationRegistry
|
|
4189
|
+
? (approvalId) => this.agentDelegationRegistry.revokeApprovedCommand(session.id, taskId, approvalId)
|
|
4190
|
+
: undefined,
|
|
4090
4191
|
bindSessionCredential: this.agentDelegationRegistry
|
|
4091
4192
|
? (sessionToken, threadId) => this.agentDelegationRegistry.registerSessionCredential({
|
|
4092
4193
|
sessionToken, threadId, sessionId: session.id, taskId,
|
|
@@ -4240,6 +4341,7 @@ export class ResponseEngine {
|
|
|
4240
4341
|
let skipEvolcoreModel = false;
|
|
4241
4342
|
let probingPrimaryModel = false;
|
|
4242
4343
|
let primaryModel;
|
|
4344
|
+
let primaryProvider;
|
|
4243
4345
|
let fallbackCandidates = [];
|
|
4244
4346
|
let previousFallbackState;
|
|
4245
4347
|
let agentModel;
|
|
@@ -4311,7 +4413,8 @@ export class ResponseEngine {
|
|
|
4311
4413
|
|| !message.triggerMeta.authorizedBy) {
|
|
4312
4414
|
throw new Error('fullaccess Trigger override lacks trusted scheduler identity or approval provenance');
|
|
4313
4415
|
}
|
|
4314
|
-
|
|
4416
|
+
await refreshOwnershipForAuthorization(selfAid);
|
|
4417
|
+
const currentOwners = new Set(getDaemonOwners());
|
|
4315
4418
|
if (!currentOwners.has(message.triggerMeta.authorizedBy)) {
|
|
4316
4419
|
throw new Error('fullaccess Trigger approval is no longer owned');
|
|
4317
4420
|
}
|
|
@@ -4326,7 +4429,7 @@ export class ResponseEngine {
|
|
|
4326
4429
|
authorizedBy: message.triggerMeta.authorizedBy,
|
|
4327
4430
|
};
|
|
4328
4431
|
}
|
|
4329
|
-
this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent);
|
|
4432
|
+
await this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent);
|
|
4330
4433
|
effectivePermissionMode = 'fullaccess';
|
|
4331
4434
|
}
|
|
4332
4435
|
else {
|
|
@@ -4394,7 +4497,10 @@ export class ResponseEngine {
|
|
|
4394
4497
|
? `${effortResolution.source.target}:${effortResolution.source.file}`
|
|
4395
4498
|
: undefined;
|
|
4396
4499
|
if (model || effort) {
|
|
4397
|
-
evolcoreModelOverride = {
|
|
4500
|
+
evolcoreModelOverride = {
|
|
4501
|
+
model,
|
|
4502
|
+
effort,
|
|
4503
|
+
};
|
|
4398
4504
|
effectiveModel = model;
|
|
4399
4505
|
}
|
|
4400
4506
|
effectiveEffort = effectiveEffortValue;
|
|
@@ -4403,6 +4509,11 @@ export class ResponseEngine {
|
|
|
4403
4509
|
logger.warn(`[ResponseEngine] effective model config resolution failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
4404
4510
|
}
|
|
4405
4511
|
modelOverride = evolcoreModelOverride;
|
|
4512
|
+
primaryProvider = normalizedBaseagent.canonical === 'unknown'
|
|
4513
|
+
? undefined
|
|
4514
|
+
: effectiveAgentConfig?.baseagents?.[normalizedBaseagent.canonical]?.provider;
|
|
4515
|
+
if (primaryProvider)
|
|
4516
|
+
modelOverride = { ...(modelOverride || {}), provider: primaryProvider };
|
|
4406
4517
|
if (triggerModelOverride || triggerEffortOverride) {
|
|
4407
4518
|
const constrainedOverride = { ...(modelOverride || {}) };
|
|
4408
4519
|
if (triggerModelOverride) {
|
|
@@ -4448,6 +4559,7 @@ export class ResponseEngine {
|
|
|
4448
4559
|
selfAid: selfAid || undefined,
|
|
4449
4560
|
catalog: agent.getModelCatalog?.bind(agent),
|
|
4450
4561
|
baseagent: normalizedBaseagent.canonical,
|
|
4562
|
+
currentProvider: primaryProvider,
|
|
4451
4563
|
currentModel: primaryModel,
|
|
4452
4564
|
resolveModelId: typeof agent.resolveModelId === 'function'
|
|
4453
4565
|
? agent.resolveModelId.bind(agent)
|
|
@@ -4464,7 +4576,9 @@ export class ResponseEngine {
|
|
|
4464
4576
|
if (existingFallback
|
|
4465
4577
|
&& (existingFallback.baseagent !== normalizedBaseagent.canonical
|
|
4466
4578
|
|| existingFallback.primaryModel !== primaryModel
|
|
4467
|
-
||
|
|
4579
|
+
|| existingFallback.primaryProvider !== primaryProvider
|
|
4580
|
+
|| !fallbackCandidates.some(candidate => candidate.model === existingFallback.activeFallback.model
|
|
4581
|
+
&& candidate.provider === existingFallback.activeFallback.provider))) {
|
|
4468
4582
|
this.modelFallbackMap.delete(session.id);
|
|
4469
4583
|
}
|
|
4470
4584
|
else if (existingFallback) {
|
|
@@ -4509,7 +4623,7 @@ export class ResponseEngine {
|
|
|
4509
4623
|
if (normalizedBaseagent.canonical === 'claude') {
|
|
4510
4624
|
modelOverride = {
|
|
4511
4625
|
...(modelOverride || {}),
|
|
4512
|
-
sessionTitle: deriveSessionTitle(session.name, message.content, session
|
|
4626
|
+
sessionTitle: deriveSessionTitle(session.name, message.content, sessionTopicThreadId(session)),
|
|
4513
4627
|
};
|
|
4514
4628
|
}
|
|
4515
4629
|
// Capture the authoritative topic binding after begin() has durably
|
|
@@ -4518,10 +4632,10 @@ export class ResponseEngine {
|
|
|
4518
4632
|
// happen before task-start auto compact because compact is a backend
|
|
4519
4633
|
// management action just like the subsequent runQuery.
|
|
4520
4634
|
let runAgentSessionId = session.agentSessionId;
|
|
4521
|
-
if ((session
|
|
4635
|
+
if ((isTopicSession(session) || message.taskRecovery)
|
|
4522
4636
|
&& typeof this.sessionManager.getSessionById === 'function') {
|
|
4523
4637
|
const latestBackendSession = await this.sessionManager.getSessionById(session.id);
|
|
4524
|
-
if (!latestBackendSession || latestBackendSession.deletedAt || latestBackendSession
|
|
4638
|
+
if (!latestBackendSession || latestBackendSession.deletedAt || sessionLocalTopicId(latestBackendSession) !== sessionLocalTopicId(session)) {
|
|
4525
4639
|
throw new Error('topic session changed before backend run started');
|
|
4526
4640
|
}
|
|
4527
4641
|
if (message.taskRecovery && (latestBackendSession.agentSessionId !== message.taskRecovery.agentSessionId
|
|
@@ -4537,7 +4651,7 @@ export class ResponseEngine {
|
|
|
4537
4651
|
...(message.taskRecovery
|
|
4538
4652
|
? { recovery: { agentSessionId: message.taskRecovery.agentSessionId } }
|
|
4539
4653
|
: {}),
|
|
4540
|
-
...(session
|
|
4654
|
+
...(isTopicSession(session)
|
|
4541
4655
|
? { backend: { kind: 'topic', agentSessionId: runAgentSessionId ?? null } }
|
|
4542
4656
|
: {}),
|
|
4543
4657
|
turn: {
|
|
@@ -4549,10 +4663,10 @@ export class ResponseEngine {
|
|
|
4549
4663
|
},
|
|
4550
4664
|
};
|
|
4551
4665
|
const refreshTopicBindingForLogicalRetry = async () => {
|
|
4552
|
-
if ((
|
|
4666
|
+
if ((isMainSession(session) && !message.taskRecovery) || typeof this.sessionManager.getSessionById !== 'function')
|
|
4553
4667
|
return;
|
|
4554
4668
|
const latest = await this.sessionManager.getSessionById(session.id);
|
|
4555
|
-
if (!latest || latest
|
|
4669
|
+
if (!latest || sessionLocalTopicId(latest) !== sessionLocalTopicId(session)) {
|
|
4556
4670
|
throw new Error('topic session changed during backend run');
|
|
4557
4671
|
}
|
|
4558
4672
|
const latestAgentSessionId = latest.agentSessionId;
|
|
@@ -4574,9 +4688,7 @@ export class ResponseEngine {
|
|
|
4574
4688
|
};
|
|
4575
4689
|
// 预压缩必须使用与本轮 runQuery 相同的有效模型/强度覆盖。
|
|
4576
4690
|
// 模型解析完成后再执行,避免关系级模型存在时回落到 runner 默认模型。
|
|
4577
|
-
await this.runPendingAutoCompactAtTaskStart(session, agent, absoluteProjectPath, renderer, modelOverride,
|
|
4578
|
-
? () => this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent)
|
|
4579
|
-
: undefined);
|
|
4691
|
+
await this.runPendingAutoCompactAtTaskStart(session, agent, absoluteProjectPath, renderer, modelOverride, () => this.assertCurrentExecutionAuthorization(permissionContext, agent));
|
|
4580
4692
|
startTime = Date.now();
|
|
4581
4693
|
const causationPath = inputCausation.trigger?.path ?? [];
|
|
4582
4694
|
const originNode = causationPath[0];
|
|
@@ -4644,8 +4756,6 @@ export class ResponseEngine {
|
|
|
4644
4756
|
RELATIONS_DIR: selfAid ? path.join(resolveRoot(), 'agents', selfAid, 'relations') : undefined,
|
|
4645
4757
|
VENUES_DIR: selfAid ? path.join(resolveRoot(), 'agents', selfAid, 'venues') : undefined,
|
|
4646
4758
|
selfAid: selfAid || undefined,
|
|
4647
|
-
// ownerAid:当前 agent 的第一个 owner AID,用于 owner 作用域记忆路径展开。
|
|
4648
|
-
// 来源:getFirstStaticAgentOwner 读 config.json owners[0];AID 格式不合法时置 undefined。
|
|
4649
4759
|
ownerAid: (() => {
|
|
4650
4760
|
const raw = selfAid ? getFirstStaticAgentOwner(selfAid) : undefined;
|
|
4651
4761
|
if (!raw)
|
|
@@ -4709,7 +4819,7 @@ export class ResponseEngine {
|
|
|
4709
4819
|
localDate: currentLocalDate(),
|
|
4710
4820
|
weekday: currentWeekday(),
|
|
4711
4821
|
osInfo: OS_INFO,
|
|
4712
|
-
threadId: session
|
|
4822
|
+
threadId: sessionTopicThreadId(session),
|
|
4713
4823
|
// Stage 3: sessionKey 持久化字段
|
|
4714
4824
|
sessionKey: session.sessionKey,
|
|
4715
4825
|
chatMode: isProactive ? 'proactive' : 'interactive',
|
|
@@ -5006,7 +5116,7 @@ export class ResponseEngine {
|
|
|
5006
5116
|
executionSource: fullAccessAuthorization.source,
|
|
5007
5117
|
} : {}),
|
|
5008
5118
|
daemonRuntimeEpoch: this.agentDelegationRegistry?.getRuntimeEpoch(),
|
|
5009
|
-
threadId: session
|
|
5119
|
+
threadId: sessionTopicThreadId(session),
|
|
5010
5120
|
sessionRuntimeDir,
|
|
5011
5121
|
runtimeLockDir: ensureRuntimeLockDir(sessionRuntimeDir),
|
|
5012
5122
|
handoffIds: v2HandoffIds.length > 0 && v2HandoffDirection === 'target' ? v2HandoffIds : undefined,
|
|
@@ -5038,7 +5148,7 @@ export class ResponseEngine {
|
|
|
5038
5148
|
}
|
|
5039
5149
|
const retryScheduler = new RetryScheduler(fallbackCandidates, modelOverride?.model || agentModel, model => (typeof agent.resolveModelId === 'function'
|
|
5040
5150
|
? (agent.resolveModelId(model) ?? model)
|
|
5041
|
-
: model));
|
|
5151
|
+
: model), modelOverride?.provider ?? primaryProvider);
|
|
5042
5152
|
const apiRetryTimeoutMs = this.apiRetryTimeoutMs();
|
|
5043
5153
|
const retryInputIsAtMostOnce = agent.retryInputSemantics === 'at_most_once';
|
|
5044
5154
|
let runAttempt = 1;
|
|
@@ -5060,10 +5170,10 @@ export class ResponseEngine {
|
|
|
5060
5170
|
const shouldTimeoutRetryAttempt = retryScheduler.shouldTimeoutAttempt() && apiRetryTimeoutMs !== undefined;
|
|
5061
5171
|
let attemptTimeout;
|
|
5062
5172
|
try {
|
|
5063
|
-
logger.info(`[ResponseEngine] agent.runQuery start: agent=${agent.name} session=${session.id} task=${taskId} attempt=${runAttempt} apiRetries=${retryScheduler.apiRetryAttemptsMade} agentSessionId=${runAgentSessionId ?? 'none'} topicBinding=${session
|
|
5173
|
+
logger.info(`[ResponseEngine] agent.runQuery start: agent=${agent.name} session=${session.id} task=${taskId} attempt=${runAttempt} apiRetries=${retryScheduler.apiRetryAttemptsMade} agentSessionId=${runAgentSessionId ?? 'none'} topicBinding=${isTopicSession(session) ? 'authoritative' : 'legacy'}`);
|
|
5064
5174
|
import('../../utils/msg-diag.js').then(m => m.diagLog('task_start', { aid: session.selfAID ?? '', msgId: message.messageId ?? '', taskId, sessionId: runAgentSessionId ?? undefined, text: message.content ?? '' })).catch(() => { });
|
|
5175
|
+
await this.assertCurrentExecutionAuthorization(permissionContext, agent);
|
|
5065
5176
|
if (fullAccessAuthorization) {
|
|
5066
|
-
this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent);
|
|
5067
5177
|
if (fullAccessAuditStartedAt === undefined) {
|
|
5068
5178
|
fullAccessAuditProcessRole = 'fullaccess-run';
|
|
5069
5179
|
fullAccessAuditStartedAt = Date.now();
|
|
@@ -5093,7 +5203,7 @@ export class ResponseEngine {
|
|
|
5093
5203
|
// Audit is synchronous today, but it is an observable boundary.
|
|
5094
5204
|
// Revalidate once more so an administrative revocation observed
|
|
5095
5205
|
// there cannot be followed by a privileged Runner call.
|
|
5096
|
-
this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent);
|
|
5206
|
+
await this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent);
|
|
5097
5207
|
}
|
|
5098
5208
|
fullAccessRunnerInvoked = true;
|
|
5099
5209
|
}
|
|
@@ -5255,7 +5365,7 @@ export class ResponseEngine {
|
|
|
5255
5365
|
if (classifyError(retryError) === ErrorType.MODEL_UNAVAILABLE && !hasExplicitTriggerModel) {
|
|
5256
5366
|
if (probingPrimaryModel && previousFallbackState) {
|
|
5257
5367
|
const resumedFallback = previousFallbackState.activeFallback;
|
|
5258
|
-
retryScheduler.markModelAttempted(resumedFallback.model);
|
|
5368
|
+
retryScheduler.markModelAttempted(resumedFallback.model, resumedFallback.provider ?? primaryProvider);
|
|
5259
5369
|
modelOverride = withFallbackModel(modelOverride, resumedFallback);
|
|
5260
5370
|
usedFallback = resumedFallback;
|
|
5261
5371
|
skipEvolcoreModel = true;
|
|
@@ -5277,6 +5387,7 @@ export class ResponseEngine {
|
|
|
5277
5387
|
this.modelFallbackMap.set(session.id, {
|
|
5278
5388
|
baseagent: normalizedBaseagent.canonical,
|
|
5279
5389
|
primaryModel,
|
|
5390
|
+
primaryProvider,
|
|
5280
5391
|
activeFallback: modelFallback.candidate,
|
|
5281
5392
|
messagesSinceFallback: 0,
|
|
5282
5393
|
nextProbeAt: 2,
|
|
@@ -5293,7 +5404,7 @@ export class ResponseEngine {
|
|
|
5293
5404
|
const candidateHint = fallbackCandidates.length === 0
|
|
5294
5405
|
? '未配置可用备用模型'
|
|
5295
5406
|
: '备用模型均不可用';
|
|
5296
|
-
throw markModelFallbackExhausted(new Error(`model_not_found: ${primaryModel ?? 'runner_default'}`), `当前模型 ${primaryModel ?? '默认模型'} 不可用,${candidateHint}。请稍后重试;管理员可在 agents/defaults.json 的 baseagents.${normalizedBaseagent.canonical}.
|
|
5407
|
+
throw markModelFallbackExhausted(new Error(`model_not_found: ${primaryModel ?? 'runner_default'}`), `当前模型 ${primaryModel ?? '默认模型'} 不可用,${candidateHint}。请稍后重试;管理员可在 agents/defaults.json 的 baseagents.${normalizedBaseagent.canonical}.fallback 配置同类备用模型。`, retryError);
|
|
5297
5408
|
}
|
|
5298
5409
|
if (retryAttemptTimedOut || isRetryableError(retryError)) {
|
|
5299
5410
|
await recordRetryHealthError(retryError);
|
|
@@ -5350,9 +5461,7 @@ export class ResponseEngine {
|
|
|
5350
5461
|
permissionMode: effectivePermissionMode,
|
|
5351
5462
|
turnLease,
|
|
5352
5463
|
messagePromptContext: fullAccessMessagePromptContext,
|
|
5353
|
-
beforeRunQuery:
|
|
5354
|
-
? () => this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent)
|
|
5355
|
-
: undefined,
|
|
5464
|
+
beforeRunQuery: () => this.assertCurrentExecutionAuthorization(permissionContext, agent),
|
|
5356
5465
|
});
|
|
5357
5466
|
}
|
|
5358
5467
|
else {
|
|
@@ -5397,9 +5506,7 @@ export class ResponseEngine {
|
|
|
5397
5506
|
permissionMode: effectivePermissionMode,
|
|
5398
5507
|
turnLease,
|
|
5399
5508
|
messagePromptContext: fullAccessMessagePromptContext,
|
|
5400
|
-
beforeRunQuery:
|
|
5401
|
-
? () => this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent)
|
|
5402
|
-
: undefined,
|
|
5509
|
+
beforeRunQuery: () => this.assertCurrentExecutionAuthorization(permissionContext, agent),
|
|
5403
5510
|
});
|
|
5404
5511
|
// 重试后仍然 prompt_too_long:显示友好提示
|
|
5405
5512
|
const retryStillTooLong = streamResult.isError && streamHitContextLimit(streamResult);
|
|
@@ -5608,6 +5715,9 @@ export class ResponseEngine {
|
|
|
5608
5715
|
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_turn', taskCausation);
|
|
5609
5716
|
return;
|
|
5610
5717
|
}
|
|
5718
|
+
const streamRecoverable = !permissionTerminalFailure
|
|
5719
|
+
&& !isExpectedTerminalInterrupt(streamResult.terminalReason)
|
|
5720
|
+
&& isRetryableError(new Error(getStreamErrorText(streamResult)));
|
|
5611
5721
|
if ((visibleFinalReplyText || streamResult.syntheticDiagnosticText) && !isTriggerNoop) {
|
|
5612
5722
|
// `hasReceivedText` 是任务级计数。工具调用前的文字已经显示,并不表示最后一轮
|
|
5613
5723
|
// (通常来自 complete.result)的文字也已显示;用本轮投影标记避免终态丢失。
|
|
@@ -5623,7 +5733,6 @@ export class ResponseEngine {
|
|
|
5623
5733
|
const errorText = visibleFinalReplyText || streamResult.syntheticDiagnosticText || getStreamErrorMessage(streamResult, false);
|
|
5624
5734
|
if (streamResult.isError) {
|
|
5625
5735
|
// 用原始诊断文本(含错误码)判断可重试性,不用已过 stripContextLimitErrorLines 的展示文本
|
|
5626
|
-
const recoverable = isRetryableError(new Error(getStreamErrorText(streamResult)));
|
|
5627
5736
|
const subtype = normalizeCoreErrorSubtype(new Error(streamRawErrorText || getStreamErrorText(streamResult)), streamResult.subtype, streamResult.terminalReason);
|
|
5628
5737
|
const reason = normalizeCoreErrorReason(streamResult.terminalReason);
|
|
5629
5738
|
await adapter.send(envelope, {
|
|
@@ -5632,7 +5741,7 @@ export class ResponseEngine {
|
|
|
5632
5741
|
...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
|
|
5633
5742
|
subtype,
|
|
5634
5743
|
...(reason ? { reason } : {}),
|
|
5635
|
-
recoverable,
|
|
5744
|
+
recoverable: streamRecoverable,
|
|
5636
5745
|
}).catch(() => { });
|
|
5637
5746
|
sentErrorAsSystemError = true;
|
|
5638
5747
|
}
|
|
@@ -5673,11 +5782,22 @@ export class ResponseEngine {
|
|
|
5673
5782
|
// Flush 剩余内容(文件标记已在 flush 时自动移除)
|
|
5674
5783
|
// A final text flush can also contain failed output. Preserve the
|
|
5675
5784
|
// outcome so AUN does not label that text as normal completion.
|
|
5676
|
-
|
|
5677
|
-
|
|
5785
|
+
try {
|
|
5786
|
+
if (streamResult.isError && !isExpectedTerminalInterrupt(streamResult.terminalReason)) {
|
|
5787
|
+
await renderer.flush(true, 'failed');
|
|
5788
|
+
}
|
|
5789
|
+
else {
|
|
5790
|
+
await renderer.flush(!isExpectedTerminalInterrupt(streamResult.terminalReason));
|
|
5791
|
+
}
|
|
5678
5792
|
}
|
|
5679
|
-
|
|
5680
|
-
|
|
5793
|
+
catch (flushError) {
|
|
5794
|
+
// Agent 已正常跑完,只是最终回复投递失败:打标记,让 catch 记录
|
|
5795
|
+
// reply_delivery_failed,__task-recovery 不再为投递问题重跑 agent。
|
|
5796
|
+
if (streamResult.isError)
|
|
5797
|
+
throw flushError;
|
|
5798
|
+
const tagged = flushError instanceof Error ? flushError : new Error(String(flushError));
|
|
5799
|
+
tagged._replyDeliveryFailed = true;
|
|
5800
|
+
throw tagged;
|
|
5681
5801
|
}
|
|
5682
5802
|
}
|
|
5683
5803
|
const terminalInterrupt = isExpectedTerminalInterrupt(streamResult.terminalReason);
|
|
@@ -5702,7 +5822,7 @@ export class ResponseEngine {
|
|
|
5702
5822
|
}
|
|
5703
5823
|
else {
|
|
5704
5824
|
await this.turnCoordinator.finish(session, turnLease, terminalInterrupt ? 'cancelled' : 'failed', permissionTerminalFailure ? 'permission_denied'
|
|
5705
|
-
: streamResult.protocolIncompleteReason || streamResult.terminalReason || streamResult.subtype);
|
|
5825
|
+
: streamResult.protocolIncompleteReason || streamResult.terminalReason || streamResult.subtype, streamRecoverable);
|
|
5706
5826
|
}
|
|
5707
5827
|
// 注意:不在此处清除 interruptedSessions,由下一条消息的 prompt 包装逻辑消费
|
|
5708
5828
|
const interruptReason = this.interruptedSessions.get(session.id);
|
|
@@ -5730,6 +5850,7 @@ export class ResponseEngine {
|
|
|
5730
5850
|
if (!isExpectedInterrupt && !permissionTerminalFailure) {
|
|
5731
5851
|
const terminalMetadata = {
|
|
5732
5852
|
errorType: rawSubtype,
|
|
5853
|
+
recoverable: streamRecoverable,
|
|
5733
5854
|
message: userErrorSummary,
|
|
5734
5855
|
durationMs: Date.now() - startTime,
|
|
5735
5856
|
numTurns: streamResult.numTurns,
|
|
@@ -5748,6 +5869,7 @@ export class ResponseEngine {
|
|
|
5748
5869
|
await adapter.send(envelope, {
|
|
5749
5870
|
kind: 'result.error',
|
|
5750
5871
|
text: userErrorSummary,
|
|
5872
|
+
recoverable: streamRecoverable,
|
|
5751
5873
|
...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
|
|
5752
5874
|
subtype: coreSubtype,
|
|
5753
5875
|
...(coreReason ? { reason: coreReason } : {}),
|
|
@@ -5759,6 +5881,7 @@ export class ResponseEngine {
|
|
|
5759
5881
|
await adapter.send(envelope, {
|
|
5760
5882
|
kind: 'result.error',
|
|
5761
5883
|
text: userErrorSummary,
|
|
5884
|
+
recoverable: streamRecoverable,
|
|
5762
5885
|
...(streamRawErrorText ? { raw: streamRawErrorText } : {}),
|
|
5763
5886
|
subtype: coreSubtype,
|
|
5764
5887
|
...(coreReason ? { reason: coreReason } : {}),
|
|
@@ -5795,7 +5918,7 @@ export class ResponseEngine {
|
|
|
5795
5918
|
else {
|
|
5796
5919
|
logger.error(`[ResponseEngine] Task failed: session=${session.id} task=${taskId} ` +
|
|
5797
5920
|
`type=${errorType} terminalReason=${streamResult.terminalReason ?? 'none'} error=${errorSummary}`);
|
|
5798
|
-
this.publishTriggerExecutionFailure(message, errorSummary, { messageId, causation: taskCausation });
|
|
5921
|
+
this.publishTriggerExecutionFailure(message, errorSummary, { messageId, causation: taskCausation, recoverable: streamRecoverable });
|
|
5799
5922
|
this.eventBus.publish({
|
|
5800
5923
|
type: 'task:error',
|
|
5801
5924
|
sessionId: session.id,
|
|
@@ -5806,6 +5929,7 @@ export class ResponseEngine {
|
|
|
5806
5929
|
taskId,
|
|
5807
5930
|
generation: turnLease?.generation,
|
|
5808
5931
|
error: errorSummary,
|
|
5932
|
+
recoverable: streamRecoverable,
|
|
5809
5933
|
errorType,
|
|
5810
5934
|
agentName: agentNameForStats,
|
|
5811
5935
|
terminalReason: streamResult.terminalReason,
|
|
@@ -5874,7 +5998,7 @@ export class ResponseEngine {
|
|
|
5874
5998
|
// 写库即得价格对(原价 + 网关价),无需再调 calcCost 重复计算。
|
|
5875
5999
|
// 网关价格缓存(/v1/models 的 pricing/effective_pricing,1h TTL)由当前 runner 提供。
|
|
5876
6000
|
const gwPricing = agent.getGatewayPricing?.();
|
|
5877
|
-
const prices = insertUsageEvent(resolveRoot(), event, gwPricing);
|
|
6001
|
+
const prices = insertUsageEvent(resolveRoot(), event, gwPricing, streamResult.modelCalls);
|
|
5878
6002
|
turnCost = { official: prices.official, gateway: prices.gateway };
|
|
5879
6003
|
// 逐次大模型调用明细落库(model_calls 表)
|
|
5880
6004
|
if (streamResult.modelCalls?.length) {
|
|
@@ -5887,6 +6011,8 @@ export class ResponseEngine {
|
|
|
5887
6011
|
peer_key: statsPeerKey,
|
|
5888
6012
|
call_index: mc.call_index,
|
|
5889
6013
|
model: mc.model || statsModel,
|
|
6014
|
+
source: mc.source,
|
|
6015
|
+
provider_thread_id: mc.providerThreadId,
|
|
5890
6016
|
request_id: mc.request_id,
|
|
5891
6017
|
message_id: mc.message_id,
|
|
5892
6018
|
input_tokens: mc.tokenUsage.input_tokens ?? 0,
|
|
@@ -6127,6 +6253,8 @@ export class ResponseEngine {
|
|
|
6127
6253
|
&& !isTotalExecutionTimeout;
|
|
6128
6254
|
const isUserInterrupt = isNonErrorInterruptReason(interruptReason) || staleSupersededTurn;
|
|
6129
6255
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
6256
|
+
const recoverable = !isUserInterrupt && !error?._replyDeliveryFailed
|
|
6257
|
+
&& (error === timeoutControl?.error ? timeoutControl?.recoverable === true : isRetryableError(error));
|
|
6130
6258
|
if (isUserInterrupt) {
|
|
6131
6259
|
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, interruptReason ?? 'stale_turn', taskCausation);
|
|
6132
6260
|
}
|
|
@@ -6134,10 +6262,14 @@ export class ResponseEngine {
|
|
|
6134
6262
|
this.publishTriggerExecutionFailure(message, errorMsg, {
|
|
6135
6263
|
messageId,
|
|
6136
6264
|
causation: taskCausation,
|
|
6265
|
+
recoverable,
|
|
6137
6266
|
});
|
|
6138
6267
|
}
|
|
6139
6268
|
if (leaseWasCurrent && turnLease) {
|
|
6140
|
-
|
|
6269
|
+
const failureReason = error instanceof Error ? error.message : String(error);
|
|
6270
|
+
await this.turnCoordinator.finish(session, turnLease, 'failed', error?._replyDeliveryFailed
|
|
6271
|
+
? `${REPLY_DELIVERY_FAILED_REASON}: ${failureReason}`
|
|
6272
|
+
: failureReason, recoverable);
|
|
6141
6273
|
}
|
|
6142
6274
|
const totalExecutionMs = error instanceof ExecutionTimeoutError ? error.timeoutMs : this.totalExecutionLimitMs();
|
|
6143
6275
|
const procStatus = errType === ErrorType.SDK_TIMEOUT || isTotalExecutionTimeout ? 'timeout'
|
|
@@ -6184,6 +6316,7 @@ export class ResponseEngine {
|
|
|
6184
6316
|
taskId,
|
|
6185
6317
|
generation: turnLease?.generation,
|
|
6186
6318
|
error: errorMsg,
|
|
6319
|
+
recoverable,
|
|
6187
6320
|
terminalReason: isExecutionFailure(error) ? error.reason : undefined,
|
|
6188
6321
|
errorType,
|
|
6189
6322
|
agentName: agentNameForStats,
|
|
@@ -6275,6 +6408,7 @@ export class ResponseEngine {
|
|
|
6275
6408
|
const errorPayload = {
|
|
6276
6409
|
kind: 'result.error',
|
|
6277
6410
|
text: userMessage,
|
|
6411
|
+
recoverable,
|
|
6278
6412
|
subtype: errorSubtype,
|
|
6279
6413
|
...(rawErrorText ? { raw: rawErrorText } : {}),
|
|
6280
6414
|
...(errorReason ? { reason: errorReason } : {}),
|
|
@@ -6289,6 +6423,7 @@ export class ResponseEngine {
|
|
|
6289
6423
|
metadata: {
|
|
6290
6424
|
errorType: errType,
|
|
6291
6425
|
message: userMessage,
|
|
6426
|
+
recoverable,
|
|
6292
6427
|
durationMs: Date.now() - startTime,
|
|
6293
6428
|
numTurns: streamResult.numTurns,
|
|
6294
6429
|
toolCallCount: streamResult.toolUseCount ?? 0,
|
|
@@ -6428,7 +6563,7 @@ export class ResponseEngine {
|
|
|
6428
6563
|
// fullaccess run. Revalidate immediately before invoking the runner so a
|
|
6429
6564
|
// daemon gate/owner revocation observed after task setup cannot still
|
|
6430
6565
|
// cause host-level runner activity.
|
|
6431
|
-
beforeCompact?.();
|
|
6566
|
+
await beforeCompact?.();
|
|
6432
6567
|
const compacted = await agent.compact(session.id, session.agentSessionId, absoluteProjectPath, modelOverride);
|
|
6433
6568
|
const compactResult = compacted;
|
|
6434
6569
|
if (compactResult === true || compactResult?.ok === true) {
|
|
@@ -6507,7 +6642,7 @@ export class ResponseEngine {
|
|
|
6507
6642
|
if (!message.observerHintId || !bound || bound.deletedAt || !bound.agentSessionId
|
|
6508
6643
|
|| bound.channel !== message.channel || bound.channelId !== message.channelId
|
|
6509
6644
|
|| bound.selfAID !== message.selfAID || bound.chatType !== message.chatType
|
|
6510
|
-
|| (bound
|
|
6645
|
+
|| sessionTopicThreadId(bound) !== (isTopicThreadId(message.threadId) ? message.threadId : undefined)) {
|
|
6511
6646
|
throw new Error('Observer hint target session is no longer available');
|
|
6512
6647
|
}
|
|
6513
6648
|
// An enqueued priority hint is bound to exactly the session selected at
|
|
@@ -6515,7 +6650,7 @@ export class ResponseEngine {
|
|
|
6515
6650
|
// file must never silently resume a stale/different backend session.
|
|
6516
6651
|
const current = await this.observerTargetSession({
|
|
6517
6652
|
channelKey: bound.channel, channelId: bound.channelId,
|
|
6518
|
-
chatType: bound.chatType, threadId: bound
|
|
6653
|
+
chatType: bound.chatType, threadId: sessionTopicThreadId(bound),
|
|
6519
6654
|
selfAid: bound.selfAID,
|
|
6520
6655
|
});
|
|
6521
6656
|
if (current?.id !== bound.id || current.agentSessionId !== bound.agentSessionId) {
|
|
@@ -6526,7 +6661,7 @@ export class ResponseEngine {
|
|
|
6526
6661
|
? bound.projectPath : path.resolve(process.cwd(), bound.projectPath) };
|
|
6527
6662
|
}
|
|
6528
6663
|
// 话题会话创建时写入创建者和 replyContext(threadId 路由);主会话不写(避免群聊覆盖)
|
|
6529
|
-
const metadata = message.threadId
|
|
6664
|
+
const metadata = isTopicThreadId(message.threadId)
|
|
6530
6665
|
? {
|
|
6531
6666
|
...(message.replyContext ? { replyContext: message.replyContext } : {}),
|
|
6532
6667
|
...(message.peerId ? { peerId: message.peerId } : {}),
|
|
@@ -6558,7 +6693,7 @@ export class ResponseEngine {
|
|
|
6558
6693
|
// valid mapped owner/admin to role=none at the execution boundary.
|
|
6559
6694
|
if (resolvedIdentity)
|
|
6560
6695
|
bound.identity = resolvedIdentity;
|
|
6561
|
-
if (bound
|
|
6696
|
+
if (isTopicSession(bound) || bound.metadata?.diagnostic) {
|
|
6562
6697
|
const absoluteProjectPath = path.isAbsolute(bound.projectPath)
|
|
6563
6698
|
? bound.projectPath : path.resolve(process.cwd(), bound.projectPath);
|
|
6564
6699
|
return { session: bound, absoluteProjectPath };
|
|
@@ -6756,6 +6891,7 @@ export class ResponseEngine {
|
|
|
6756
6891
|
if (hasText)
|
|
6757
6892
|
textCount++;
|
|
6758
6893
|
assistantUuid = event.assistantUuid ?? assistantUuid;
|
|
6894
|
+
event = { ...event, text: stripThinkingBlocks(event.text) };
|
|
6759
6895
|
}
|
|
6760
6896
|
else if (event.type === 'tool_use') {
|
|
6761
6897
|
// 产生工具调用同样证明输入已被消费,且此时重放会重复发起副作用。
|
|
@@ -6827,6 +6963,12 @@ export class ResponseEngine {
|
|
|
6827
6963
|
else if (event.type === 'complete') {
|
|
6828
6964
|
assistantUuid = event.assistantUuid ?? assistantUuid;
|
|
6829
6965
|
protocolIncompleteReason = event.protocolIncompleteReason ?? protocolIncompleteReason;
|
|
6966
|
+
if (event.result) {
|
|
6967
|
+
const result = stripThinkingBlocks(event.result);
|
|
6968
|
+
if (textCount === 0 && event.result.trim() && !result.trim())
|
|
6969
|
+
textCount++;
|
|
6970
|
+
event = { ...event, result };
|
|
6971
|
+
}
|
|
6830
6972
|
}
|
|
6831
6973
|
// 记录事件类型:高价值事件(含子任务生命周期)INFO,
|
|
6832
6974
|
// 框架事件(session_id/state_changed/status)DEBUG
|
|
@@ -7341,7 +7483,7 @@ export class ResponseEngine {
|
|
|
7341
7483
|
const isAbort = isExpectedTerminalInterrupt(event.terminalReason);
|
|
7342
7484
|
logger.info(`[ResponseEngine] ${isAbort ? 'task interrupted' : 'complete event'}: queryFinal=${event.queryFinal !== false} isError=${event.isError} terminalReason=${event.terminalReason ?? 'none'} subtype=${event.subtype ?? 'none'} hasReceivedText=${hasReceivedText}`);
|
|
7343
7485
|
// 自动回填会话名称
|
|
7344
|
-
if (event.sessionTitle && shouldAutoFillSessionTitle(session.name, session
|
|
7486
|
+
if (event.sessionTitle && shouldAutoFillSessionTitle(session.name, sessionTopicThreadId(session))) {
|
|
7345
7487
|
await this.sessionManager.renameSession(session.id, event.sessionTitle);
|
|
7346
7488
|
logger.info(`[ResponseEngine] Auto-filled session name: ${event.sessionTitle}`);
|
|
7347
7489
|
}
|
|
@@ -7392,7 +7534,7 @@ export class ResponseEngine {
|
|
|
7392
7534
|
continue;
|
|
7393
7535
|
}
|
|
7394
7536
|
// 自动回填会话名称
|
|
7395
|
-
if (event.sessionTitle && shouldAutoFillSessionTitle(session.name, session
|
|
7537
|
+
if (event.sessionTitle && shouldAutoFillSessionTitle(session.name, sessionTopicThreadId(session))) {
|
|
7396
7538
|
await this.sessionManager.renameSession(session.id, event.sessionTitle);
|
|
7397
7539
|
logger.info(`[ResponseEngine] Auto-filled session name: ${event.sessionTitle}`);
|
|
7398
7540
|
}
|