evolcore 0.0.28 → 0.0.29
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 +12 -0
- package/bin/ec-safe-output.js +270 -54
- package/dist/agents/baseagent-availability.js +37 -1
- package/dist/agents/baseagent.js +19 -9
- package/dist/agents/claude-runner.js +171 -13
- package/dist/agents/codex/app-server-client.js +6 -2
- package/dist/agents/codex/diagnostics.js +4 -3
- package/dist/agents/codex-runner.js +396 -95
- package/dist/agents/gemini-runner.js +35 -17
- package/dist/aun/group/history.js +130 -90
- package/dist/aun/group/operations.js +1 -3
- package/dist/aun/menu/transport.js +5 -3
- package/dist/aun/msg/managed-operation.js +70 -0
- package/dist/aun/msg/p2p.js +1 -3
- package/dist/aun/msg/upload.js +2 -2
- package/dist/aun/outbox.js +1 -37
- package/dist/channels/aun.js +249 -89
- package/dist/channels/daemon.js +18 -1
- package/dist/channels/feishu.js +1 -2
- package/dist/cli/agent-command.js +23 -3
- package/dist/cli/agent-create.js +68 -2
- package/dist/cli/agent.js +12 -2
- package/dist/cli/aun-commands.js +87 -13
- package/dist/cli/cli-argv.js +26 -0
- package/dist/cli/daemon-commands.js +40 -10
- package/dist/cli/data-command.js +4 -4
- package/dist/cli/handoff-command.js +2 -0
- package/dist/cli/index.js +18 -1
- package/dist/cli/managed-command-guard.js +10 -0
- package/dist/cli/memory-command.js +441 -0
- package/dist/cli/restart-monitor.js +62 -6
- package/dist/cli/skill-command.js +41 -0
- package/dist/cli/task-context.js +2 -23
- package/dist/cli/trigger-command.js +9 -1
- package/dist/cli/watch-msg-diag.js +209 -0
- package/dist/config/config-field-policy.js +52 -3
- package/dist/config/config-manager.js +17 -4
- package/dist/config/contact-book-store.js +2 -2
- package/dist/config/mention-mode.js +7 -2
- package/dist/config/merge.js +28 -9
- package/dist/config/resolved-config-op.js +1 -1
- package/dist/config/runtime-env.js +137 -0
- package/dist/config/schema-registry.js +11 -1
- package/dist/config-store.js +10 -10
- package/dist/core/audit/event-key.js +3 -1
- package/dist/core/audit/log-integrity.js +4 -0
- package/dist/core/auth/agent-delegation.js +2 -1
- package/dist/core/auth/auth-gateway.js +13 -6
- package/dist/core/auth/authorization-audit.js +54 -19
- package/dist/core/auth/operation-authorizer.js +17 -0
- package/dist/core/auth/operation-catalog.js +9 -0
- package/dist/core/causation/context.js +5 -1
- package/dist/core/command/agent-control.js +1 -2
- package/dist/core/command/command-handler.js +51 -23
- package/dist/core/command/memory-slash.js +270 -0
- package/dist/core/command/menu-catalog.js +1 -0
- package/dist/core/command/menu-handler.js +315 -60
- package/dist/core/command/menu-protocol.js +42 -4
- package/dist/core/command/slash-gate.js +10 -10
- package/dist/core/command/slash-handler.js +206 -64
- package/dist/core/data-migration.js +282 -505
- package/dist/core/event-catalog.js +26 -0
- package/dist/core/evolagent-registry.js +6 -0
- package/dist/core/evolagent.js +0 -10
- package/dist/core/handoff/store.js +1 -20
- package/dist/core/inference/text-inference.js +57 -1
- package/dist/core/memory/consolidation-context.js +505 -0
- package/dist/core/memory/consolidation-ops-records.js +124 -0
- package/dist/core/memory/consolidation-trigger.js +394 -0
- package/dist/core/memory/index-manager.js +308 -0
- package/dist/core/memory/launch-consolidation.js +395 -0
- package/dist/core/memory/memory-admin.js +234 -0
- package/dist/core/memory/memory-document.js +100 -0
- package/dist/core/memory/memory-session-policy.js +41 -0
- package/dist/core/memory/memory-signature.js +257 -0
- package/dist/core/memory/memory-writer.js +432 -0
- package/dist/core/memory/staleness.js +117 -0
- package/dist/core/message/auto-mention-gate.js +454 -0
- package/dist/core/message/im-renderer.js +69 -16
- package/dist/core/message/message-bridge.js +287 -21
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +114 -27
- package/dist/core/message/pending-hints.js +329 -161
- package/dist/core/message/response-engine.js +567 -80
- package/dist/core/permission/ec-command-parser.js +1029 -80
- package/dist/core/permission/protected-paths.js +376 -126
- package/dist/core/permission/sandbox-runtime.js +40 -12
- package/dist/core/permission/tool-error-code.js +96 -13
- package/dist/core/permission/tool-policy.js +276 -23
- package/dist/core/role/runtime-policy.js +3 -7
- package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
- package/dist/core/session/session-fs-store.js +0 -76
- package/dist/core/session/session-idle-monitor.js +355 -0
- package/dist/core/session/session-manager.js +478 -17
- package/dist/core/session/session-mapper.js +33 -16
- package/dist/core/session/session-renew.js +217 -155
- package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
- package/dist/core/skill/global-watcher.js +282 -0
- package/dist/core/skill/index-generator.js +84 -0
- package/dist/core/skill/startup.js +73 -0
- package/dist/core/skill/switch-resolver.js +115 -0
- package/dist/core/task/task-activity-store.js +219 -0
- package/dist/eck/kit-renderer.js +30 -3
- package/dist/eck/manifest-engine.js +26 -4
- package/dist/index.js +708 -18
- package/dist/ipc.js +564 -2
- package/dist/paths.js +30 -9
- package/dist/response-system/engines/v1/proactive-flow.js +116 -15
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +1 -0
- package/dist/trigger/manager.js +0 -10
- package/dist/trigger/scheduler.js +11 -0
- package/dist/trigger/validation.js +22 -0
- package/dist/utils/avatar-upload.js +59 -18
- package/dist/utils/error-dict.json +6 -3
- package/dist/utils/error-utils.js +27 -0
- package/dist/utils/logger.js +32 -10
- package/dist/utils/media-cache.js +84 -0
- package/dist/utils/msg-diag.js +97 -0
- package/dist/utils/welcome.js +37 -8
- package/kits/docs/AUTHORING.md +314 -0
- package/kits/docs/GUIDE.md +2 -0
- package/kits/docs/INDEX.md +2 -0
- package/kits/docs/aun/CHEATSHEET.md +4 -0
- package/kits/docs/aun/SDK.md +60 -0
- package/kits/docs/context-assembly.md +10 -6
- package/kits/docs/evolcore/INDEX.md +17 -3
- package/kits/docs/evolcore/agent-create.md +24 -0
- package/kits/docs/evolcore/agent.md +5 -2
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/ec-capabilities.md +364 -0
- package/kits/docs/evolcore/group.md +34 -18
- package/kits/docs/evolcore/memory-architecture.md +78 -0
- package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
- package/kits/docs/evolcore/memory-consolidation.md +263 -0
- package/kits/docs/evolcore/memory-tags.md +63 -0
- package/kits/docs/evolcore/memory.md +110 -0
- package/kits/docs/evolcore/menu-client.md +27 -11
- package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
- package/kits/docs/evolcore/menu-protocol.md +213 -0
- package/kits/docs/evolcore/msg.md +2 -0
- package/kits/docs/evolcore/trigger.md +7 -5
- package/kits/docs/path-registry.md +6 -8
- package/kits/docs/prompt-loading-architecture.md +5 -3
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +294 -88
- package/kits/eck_manifest.memory.json +257 -0
- package/kits/rules/03-identity.md +8 -8
- package/kits/rules/05-venue.md +4 -2
- package/kits/rules/06-channel.md +1 -1
- package/kits/schemas/_meta.json +276 -47
- package/kits/schemas/agent-config.schema.12.json +38 -2
- package/kits/schemas/agent-config.schema.13.json +591 -0
- package/kits/schemas/daemon.schema.6.json +9 -9
- package/kits/schemas/defaults.schema.5.json +29 -0
- package/kits/schemas/relation-config.schema.8.json +14 -1
- package/kits/schemas/relation-config.schema.9.json +129 -0
- package/kits/schemas/role-config.schema.1.json +3 -3
- package/kits/skills/.switches.json +3 -0
- package/kits/skills/ec-skill/SKILL.md +217 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
- package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
- package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
- package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
- package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
- package/kits/templates/decision/auto-mention.md +25 -0
- package/kits/templates/decision/session-renew.md +22 -0
- package/kits/templates/memory-consolidation/forget.md +104 -0
- package/kits/templates/memory-consolidation/generalize.md +76 -0
- package/kits/templates/memory-consolidation/notes.md +133 -0
- package/kits/templates/memory-consolidation/profile.md +58 -0
- package/kits/templates/memory-consolidation/promote.md +72 -0
- package/kits/templates/memory-consolidation/workflow.md +75 -0
- package/kits/templates/system-fragments/bootstrap.md +14 -5
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/group-history.md +6 -0
- package/kits/templates/system-fragments/identity.md +0 -3
- package/kits/templates/system-fragments/memory.md +79 -0
- package/kits/templates/system-fragments/reply.md +1 -0
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -6
- package/dist/trigger/legacy-session-history.js +0 -19
- package/kits/templates/system-fragments/group-unread-history.md +0 -7
|
@@ -9,9 +9,11 @@ import { isDiagnosticSessionActive } from '../command/diag-service.js';
|
|
|
9
9
|
import { evaluateInboundAdmissionPreflight, evaluateOrdinaryInboundAdmission } from './message-admission.js';
|
|
10
10
|
import { authorizeAccess, authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
11
11
|
import { buildTaskRuntimeEnv, ensureRuntimeLockDir, ensureSessionRuntimeDir } from '../../cli/task-context.js';
|
|
12
|
+
import { memorySessionPolicyHook } from '../memory/memory-session-policy.js';
|
|
12
13
|
import { IMRenderer } from './im-renderer.js';
|
|
14
|
+
import { TaskActivityStore } from '../task/task-activity-store.js';
|
|
13
15
|
import { createSendFileMarkerPattern } from './file-markers.js';
|
|
14
|
-
import { createTextInferenceProvider } from '../inference/text-inference.js';
|
|
16
|
+
import { createDecisionInferenceProvider, createTextInferenceProvider } from '../inference/text-inference.js';
|
|
15
17
|
import { StreamIdleMonitor } from './stream-idle-monitor.js';
|
|
16
18
|
import { logger } from '../../utils/logger.js';
|
|
17
19
|
import { isHostChinese } from '../../utils/locale.js';
|
|
@@ -20,10 +22,10 @@ import { readEvolcoreRuntimeFlavor, tagErrorTextWithEvolcoreVersion } from '../.
|
|
|
20
22
|
import { describeProactiveSendCommandFailure, isExactEvolcoreSendCommandForSession } from '../permission/ec-command-parser.js';
|
|
21
23
|
import { summarizeToolInputForDisplay } from '../../utils/tool-summary.js';
|
|
22
24
|
import { agentDataDir, agentDir as resolveAgentDir, agentMdPath, getPackageRoot, resolveRoot, resolvePaths } from '../../paths.js';
|
|
23
|
-
import { renderKitSections } from '../../eck/kit-renderer.js';
|
|
25
|
+
import { renderKitSections, resolveSessionManifest, DEFAULT_MANIFEST_FILE } from '../../eck/kit-renderer.js';
|
|
24
26
|
import { renderMessageBody } from '../../eck/message-renderer.js';
|
|
25
27
|
import { syncGroupRulesContext } from '../../eck/group-rules-sync.js';
|
|
26
|
-
import {
|
|
28
|
+
import { claimHints, commitHintClaim, releaseHintClaim, peekHints, hintsToSubMessages, composeHintFallback, OBSERVER_HINT_FAILURE_MESSAGES, } from './pending-hints.js';
|
|
27
29
|
import { createRootCausation, deriveCausation, normalizeCausation } from '../causation/context.js';
|
|
28
30
|
import { recordCausationLink, recordCausationSpan } from '../causation/audit.js';
|
|
29
31
|
import { normalizeBaseagent } from '../../agents/baseagent.js';
|
|
@@ -33,6 +35,7 @@ import { buildEnvelope, isInteractionSendAccepted, replyContextFromSession, send
|
|
|
33
35
|
import { isSystemOrServicePeer, resolveChatMode } from './peer-mode.js';
|
|
34
36
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
|
|
35
37
|
import { hasTrustedPrincipal } from '../auth/authenticated-actor.js';
|
|
38
|
+
import { isValidAid } from '../../aun/aid/identity.js';
|
|
36
39
|
// Re-export 工具函数(向后兼容,让其他模块可以从 response-engine 导入)
|
|
37
40
|
export { buildEnvelope, sendInteractionPayload } from './message-utils.js';
|
|
38
41
|
import { constrainResolvedModelForRole, isModelAllowedForRoleBaseagent } from '../model/model-permission.js';
|
|
@@ -54,10 +57,11 @@ import { registerBuiltinModes } from '../../response-system/modes/index.js';
|
|
|
54
57
|
import { deriveSessionTitle, shouldAutoFillSessionTitle } from '../session/session-title.js';
|
|
55
58
|
import { SessionTurnCoordinator } from '../session/session-turn-coordinator.js';
|
|
56
59
|
import { recordTriggerExecutionAnomaly } from '../../trigger/anomaly-store.js';
|
|
57
|
-
import {
|
|
60
|
+
import { normalizeToolErrorClassification } from '../permission/tool-error-code.js';
|
|
58
61
|
import { normalizeExecutionPermissionMode } from '../permission/mode.js';
|
|
59
62
|
import { buildToolLifecycleEventKey } from '../audit/event-key.js';
|
|
60
|
-
import {
|
|
63
|
+
import { aunMessageTranscriptUuid } from '../session/session-transcript.js';
|
|
64
|
+
import { loadDaemonConfig } from '../../config-store.js';
|
|
61
65
|
import { assertAgentPolicyAuthorization } from '../role/runtime-policy.js';
|
|
62
66
|
import { auditFullAccessEvent } from '../auth/authorization-audit.js';
|
|
63
67
|
import { TaskRecoveryService, isTaskRecoveryCurrent } from '../task/task-recovery-service.js';
|
|
@@ -889,6 +893,13 @@ export class ResponseEngine {
|
|
|
889
893
|
const config = resolveEffective({ self: aid }, { cache: true, expand: true });
|
|
890
894
|
return createTextInferenceProvider(baseagent, config);
|
|
891
895
|
}
|
|
896
|
+
getDecisionInferenceProvider(selfAID) {
|
|
897
|
+
const aid = selfAID;
|
|
898
|
+
if (!aid)
|
|
899
|
+
return undefined;
|
|
900
|
+
const config = resolveEffective({ self: aid }, { cache: true, expand: true });
|
|
901
|
+
return createDecisionInferenceProvider(config.decision_model);
|
|
902
|
+
}
|
|
892
903
|
getAvailableBaseagentsForOwner(evolName) {
|
|
893
904
|
return loadedBaseagentIds(this.agentMap.keys(), evolName);
|
|
894
905
|
}
|
|
@@ -1060,6 +1071,198 @@ export class ResponseEngine {
|
|
|
1060
1071
|
hasChannel: (session) => !!this.resolveChannelInfo(session.metadata?.channelKey || session.channel),
|
|
1061
1072
|
});
|
|
1062
1073
|
}
|
|
1074
|
+
observerHintConsumed;
|
|
1075
|
+
observerHintFailed;
|
|
1076
|
+
observerHintFailureReported = new WeakMap();
|
|
1077
|
+
scheduledObserverHints = new Set();
|
|
1078
|
+
setObserverHintConsumed(fn) {
|
|
1079
|
+
this.observerHintConsumed = fn;
|
|
1080
|
+
}
|
|
1081
|
+
setObserverHintFailed(fn) {
|
|
1082
|
+
this.observerHintFailed = fn;
|
|
1083
|
+
}
|
|
1084
|
+
observerHintStatusHint(hint, requestId) {
|
|
1085
|
+
return requestId && !hint.promoteRequestId ? { ...hint, promoteRequestId: requestId } : hint;
|
|
1086
|
+
}
|
|
1087
|
+
reportObserverHintFailure(message, hint, target, code, requestId, action) {
|
|
1088
|
+
const reported = this.observerHintFailureReported.get(message) ?? new Set();
|
|
1089
|
+
if (reported.has(hint.id))
|
|
1090
|
+
return;
|
|
1091
|
+
reported.add(hint.id);
|
|
1092
|
+
this.observerHintFailureReported.set(message, reported);
|
|
1093
|
+
const statusAction = action ?? (hint.promoteRequestId ? 'promote' : 'add');
|
|
1094
|
+
const statusRequestId = requestId ?? hint.promoteRequestId ?? (statusAction === 'add' ? message.observerHintRequestId : undefined);
|
|
1095
|
+
const statusHint = this.observerHintStatusHint(hint, statusAction === 'promote' ? statusRequestId : undefined);
|
|
1096
|
+
try {
|
|
1097
|
+
this.observerHintFailed?.(statusHint, target, {
|
|
1098
|
+
code, message: OBSERVER_HINT_FAILURE_MESSAGES[code], action: statusAction, requestId: statusRequestId,
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
catch (error) {
|
|
1102
|
+
logger.warn(`[ResponseEngine] observer hint failure status failed id=${hint.id}: ${error}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
reportObserverHintFailureForMessage(message, code, requestId) {
|
|
1106
|
+
if (message.source !== 'owner-inject' || !message.observerHintId || !message.selfAID)
|
|
1107
|
+
return;
|
|
1108
|
+
const hint = peekHints(resolvePaths().sessionsDir, 'aun', message.channelId, message.selfAID, message.threadId, (message.chatType || 'private'), undefined, message.channel).find(item => item.id === message.observerHintId);
|
|
1109
|
+
if (!hint)
|
|
1110
|
+
return;
|
|
1111
|
+
this.reportObserverHintFailure(message, hint, {
|
|
1112
|
+
channelKey: message.channel,
|
|
1113
|
+
channelId: message.channelId,
|
|
1114
|
+
chatType: (message.chatType || 'private'),
|
|
1115
|
+
threadId: message.threadId,
|
|
1116
|
+
}, code, requestId, hint.promoteRequestId ? 'promote' : 'add');
|
|
1117
|
+
}
|
|
1118
|
+
observerHintFailureCodeForError(error) {
|
|
1119
|
+
const code = error && typeof error === 'object' && 'code' in error
|
|
1120
|
+
? error.code : undefined;
|
|
1121
|
+
const knownCodes = [
|
|
1122
|
+
'SESSION_NOT_FOUND', 'INTERRUPT_FAILED', 'INTERRUPT_TIMEOUT', 'QUEUE_FAILED',
|
|
1123
|
+
'QUEUE_PERSIST_FAILED', 'RENDER_FAILED', 'RUNNER_START_FAILED',
|
|
1124
|
+
'RUNNER_START_SUPERSEDED', 'HINT_UNAVAILABLE',
|
|
1125
|
+
];
|
|
1126
|
+
if (typeof code === 'string' && knownCodes.includes(code)) {
|
|
1127
|
+
return code;
|
|
1128
|
+
}
|
|
1129
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
1130
|
+
if (text === 'RUNNER_START_SUPERSEDED')
|
|
1131
|
+
return 'RUNNER_START_SUPERSEDED';
|
|
1132
|
+
if (text.includes('interrupt barrier timed out'))
|
|
1133
|
+
return 'INTERRUPT_TIMEOUT';
|
|
1134
|
+
if (text.includes('interrupt'))
|
|
1135
|
+
return 'INTERRUPT_FAILED';
|
|
1136
|
+
if (text.includes('queue is unavailable'))
|
|
1137
|
+
return 'QUEUE_FAILED';
|
|
1138
|
+
if (text.includes('enqueue') || text.includes('queue'))
|
|
1139
|
+
return 'QUEUE_PERSIST_FAILED';
|
|
1140
|
+
if (text.includes('renderMessageBody'))
|
|
1141
|
+
return 'RENDER_FAILED';
|
|
1142
|
+
if (text.includes('target session') || text.includes('SESSION_NOT_FOUND'))
|
|
1143
|
+
return 'SESSION_NOT_FOUND';
|
|
1144
|
+
return 'RUNNER_START_FAILED';
|
|
1145
|
+
}
|
|
1146
|
+
reportObserverHintFailureForClaim(message, session, claim, code) {
|
|
1147
|
+
for (const hint of claim.hints) {
|
|
1148
|
+
this.reportObserverHintFailure(message, hint, {
|
|
1149
|
+
channelKey: session.channel,
|
|
1150
|
+
channelId: claim.channelId,
|
|
1151
|
+
chatType: claim.chatType,
|
|
1152
|
+
threadId: claim.threadId || undefined,
|
|
1153
|
+
}, code, hint.promoteRequestId ?? message.observerHintRequestId, hint.promoteRequestId ? 'promote' : 'add');
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
observerHintScheduleFailure(target, code) {
|
|
1157
|
+
const hint = peekHints(resolvePaths().sessionsDir, 'aun', target.channelId, target.selfAid, target.threadId, target.chatType, undefined, target.channelKey).find(item => item.id === target.hintId);
|
|
1158
|
+
if (!hint)
|
|
1159
|
+
return;
|
|
1160
|
+
const statusAction = hint.promoteRequestId ? 'promote' : 'add';
|
|
1161
|
+
const statusHint = this.observerHintStatusHint(hint, statusAction === 'promote' ? target.requestId : undefined);
|
|
1162
|
+
try {
|
|
1163
|
+
this.observerHintFailed?.(statusHint, {
|
|
1164
|
+
channelKey: target.channelKey,
|
|
1165
|
+
channelId: target.channelId,
|
|
1166
|
+
chatType: target.chatType,
|
|
1167
|
+
threadId: target.threadId,
|
|
1168
|
+
}, {
|
|
1169
|
+
code, message: OBSERVER_HINT_FAILURE_MESSAGES[code], action: statusAction,
|
|
1170
|
+
requestId: target.requestId ?? hint.promoteRequestId,
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
catch (error) {
|
|
1174
|
+
logger.warn(`[ResponseEngine] observer hint schedule failure status failed id=${hint.id}: ${error}`);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
async observerTargetSession(target) {
|
|
1178
|
+
const session = target.threadId
|
|
1179
|
+
? await this.sessionManager.getThreadSession(target.channelKey, target.channelId, target.threadId)
|
|
1180
|
+
: this.sessionManager.getActiveSessionSync(target.channelKey, target.channelId, 'aun', target.selfAid);
|
|
1181
|
+
if (!session || session.deletedAt || session.selfAID !== target.selfAid || session.channel !== target.channelKey
|
|
1182
|
+
|| session.channelId !== target.channelId || session.chatType !== target.chatType
|
|
1183
|
+
|| (session.threadId || '') !== (target.threadId || '')
|
|
1184
|
+
|| !this.sessionManager.hasBoundAgentSession(session)
|
|
1185
|
+
|| (target.chatType === 'group' && (session.metadata?.groupId !== target.channelId
|
|
1186
|
+
|| !session.metadata?.observerLastActorId)))
|
|
1187
|
+
return undefined;
|
|
1188
|
+
return session;
|
|
1189
|
+
}
|
|
1190
|
+
async hasObserverTargetSession(target) {
|
|
1191
|
+
return !!(await this.observerTargetSession(target));
|
|
1192
|
+
}
|
|
1193
|
+
async scheduleObserverHint(target) {
|
|
1194
|
+
const session = await this.observerTargetSession(target);
|
|
1195
|
+
if (!session) {
|
|
1196
|
+
this.observerHintScheduleFailure(target, 'SESSION_NOT_FOUND');
|
|
1197
|
+
return 'SESSION_NOT_FOUND';
|
|
1198
|
+
}
|
|
1199
|
+
if (!this.messageQueue) {
|
|
1200
|
+
this.observerHintScheduleFailure(target, 'QUEUE_FAILED');
|
|
1201
|
+
throw new Error('Observer hint queue is unavailable');
|
|
1202
|
+
}
|
|
1203
|
+
const scheduledKey = `${session.id}\u0000${target.hintId}`;
|
|
1204
|
+
if (this.scheduledObserverHints.has(scheduledKey))
|
|
1205
|
+
return 'duplicate';
|
|
1206
|
+
this.scheduledObserverHints.add(scheduledKey);
|
|
1207
|
+
try {
|
|
1208
|
+
await this.messageQueue.withSessionBarrier(session.id, async (snapshot) => {
|
|
1209
|
+
if (snapshot.active && !snapshot.dispatching) {
|
|
1210
|
+
let timeout;
|
|
1211
|
+
try {
|
|
1212
|
+
try {
|
|
1213
|
+
await Promise.race([
|
|
1214
|
+
this.messageQueue.interruptForPriority(session.id),
|
|
1215
|
+
new Promise((_, reject) => {
|
|
1216
|
+
timeout = setTimeout(() => reject(new Error('Observer hint interrupt barrier timed out')), 30_000);
|
|
1217
|
+
timeout.unref?.();
|
|
1218
|
+
}),
|
|
1219
|
+
]);
|
|
1220
|
+
}
|
|
1221
|
+
catch (error) {
|
|
1222
|
+
const code = error instanceof Error
|
|
1223
|
+
&& error.message === 'Observer hint interrupt barrier timed out'
|
|
1224
|
+
? 'INTERRUPT_TIMEOUT' : 'INTERRUPT_FAILED';
|
|
1225
|
+
this.observerHintScheduleFailure(target, code);
|
|
1226
|
+
throw error;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
finally {
|
|
1230
|
+
if (timeout)
|
|
1231
|
+
clearTimeout(timeout);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
const receivedAt = Date.now();
|
|
1235
|
+
const actorId = target.chatType === 'group'
|
|
1236
|
+
? session.metadata.observerLastActorId : target.channelId;
|
|
1237
|
+
try {
|
|
1238
|
+
await this.messageQueue.enqueuePersisted(session.id, {
|
|
1239
|
+
channel: session.channel, channelType: 'aun', channelId: session.channelId,
|
|
1240
|
+
selfAID: session.selfAID, baseagent: session.baseagent, threadId: session.threadId,
|
|
1241
|
+
chatType: target.chatType, groupId: target.chatType === 'group' ? target.channelId : undefined,
|
|
1242
|
+
peerId: actorId, peerName: session.metadata?.observerLastPeerName || session.metadata?.peerName,
|
|
1243
|
+
peerType: session.metadata?.observerLastPeerType || session.metadata?.peerType,
|
|
1244
|
+
replyContext: replyContextFromSession(session),
|
|
1245
|
+
boundSessionId: session.id, content: '', source: 'owner-inject', observerHintId: target.hintId,
|
|
1246
|
+
...(target.requestId ? { observerHintRequestId: target.requestId } : {}),
|
|
1247
|
+
messageId: `owner-hint-${crypto.randomUUID()}`, receivedAt, timestamp: receivedAt,
|
|
1248
|
+
}, session.projectPath, {
|
|
1249
|
+
priority: true, interruptible: false, interruptSamePeer: false,
|
|
1250
|
+
agentName: this.agentRegistry?.resolveByChannel(session.channel)?.name,
|
|
1251
|
+
selfAID: target.selfAid, sessionKeyField: session.sessionKey,
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
catch (error) {
|
|
1255
|
+
this.observerHintScheduleFailure(target, 'QUEUE_PERSIST_FAILED');
|
|
1256
|
+
throw error;
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
return 'queued';
|
|
1260
|
+
}
|
|
1261
|
+
catch (error) {
|
|
1262
|
+
this.scheduledObserverHints.delete(scheduledKey);
|
|
1263
|
+
throw error;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1063
1266
|
async recoverTask(request) {
|
|
1064
1267
|
if (!this.taskRecoveryService) {
|
|
1065
1268
|
return { ok: false, status: 'not_recoverable', sessionId: request.sessionId, message: '消息队列尚未初始化' };
|
|
@@ -1160,7 +1363,7 @@ export class ResponseEngine {
|
|
|
1160
1363
|
else
|
|
1161
1364
|
await this.interruptSession(sessionId, error instanceof OrphanedTargetSessionError ? 'execution_error' : 'timeout');
|
|
1162
1365
|
}
|
|
1163
|
-
async interruptSession(sessionId, reason) {
|
|
1366
|
+
async interruptSession(sessionId, reason, strict = false) {
|
|
1164
1367
|
this.clearPauseSession(sessionId);
|
|
1165
1368
|
// A retry delay has no active runner stream to abort. Wake it before any
|
|
1166
1369
|
// session-store I/O so /stop and a newer message take effect immediately.
|
|
@@ -1240,6 +1443,8 @@ export class ResponseEngine {
|
|
|
1240
1443
|
})();
|
|
1241
1444
|
const runnerInterrupt = agent.interrupt(sessionId).catch(error => {
|
|
1242
1445
|
logger.debug(`[ResponseEngine] interrupt barrier runner failure: session=${sessionId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1446
|
+
if (strict)
|
|
1447
|
+
throw error;
|
|
1243
1448
|
});
|
|
1244
1449
|
await Promise.all([terminalStatus, runnerInterrupt]);
|
|
1245
1450
|
}
|
|
@@ -1394,32 +1599,30 @@ export class ResponseEngine {
|
|
|
1394
1599
|
const globalCm = agent.config?.chatmode ?? this.globalSettings.chatmode;
|
|
1395
1600
|
return agent.getContext(channelName, chatType, globalCm);
|
|
1396
1601
|
}
|
|
1397
|
-
/**
|
|
1398
|
-
* 观察者插话(v0.3):消费当前 (对端, thread) 的待用提示,转成 owner-hint SubMessage。
|
|
1399
|
-
* 一次性语义:consumeHints 回放算有效集后清该 thread(其它 thread 残留则保留,否则删文件)。
|
|
1400
|
-
* 仅 aun 渠道(pending-hints 落在 sessions/aun/<self>/<对端>/)。
|
|
1401
|
-
*/
|
|
1602
|
+
/** Claim matching hints for this turn; only commit after the runner accepts its prompt. */
|
|
1402
1603
|
consumeOwnerHints(session, message) {
|
|
1604
|
+
if (message.source !== 'owner-inject' && message.source !== 'user' && message.source !== undefined)
|
|
1605
|
+
return { items: [] };
|
|
1403
1606
|
const channelType = session.channelType || message.channelType || session.channel;
|
|
1404
1607
|
if (channelType !== 'aun')
|
|
1405
|
-
return [];
|
|
1608
|
+
return { items: [] };
|
|
1406
1609
|
const selfAID = session.selfAID || message.selfAID;
|
|
1407
1610
|
if (!selfAID)
|
|
1408
|
-
return [];
|
|
1611
|
+
return { items: [] };
|
|
1409
1612
|
// 会话定位键:私聊=对端 AID,群聊=groupId(均为 session.channelId)。
|
|
1410
1613
|
const peerChannelId = session.channelId;
|
|
1411
1614
|
if (!peerChannelId)
|
|
1412
|
-
return [];
|
|
1615
|
+
return { items: [] };
|
|
1413
1616
|
try {
|
|
1414
|
-
const
|
|
1415
|
-
if (hints.length === 0)
|
|
1416
|
-
return [];
|
|
1417
|
-
logger.info(`[ResponseEngine]
|
|
1418
|
-
return hintsToSubMessages(hints);
|
|
1617
|
+
const claim = claimHints(resolvePaths().sessionsDir, 'aun', peerChannelId, selfAID, session.threadId, (message.chatType || session.chatType || 'private'), message.source === 'owner-inject' ? 'interrupt' : 'next', message.observerHintId, session.channel);
|
|
1618
|
+
if (claim.hints.length === 0)
|
|
1619
|
+
return { items: [] };
|
|
1620
|
+
logger.info(`[ResponseEngine] claimed ${claim.hints.length} owner-hint(s) for ${peerChannelId} thread=${session.threadId || 'main'}`);
|
|
1621
|
+
return { items: hintsToSubMessages(claim.hints), claim };
|
|
1419
1622
|
}
|
|
1420
1623
|
catch (e) {
|
|
1421
1624
|
logger.warn(`[ResponseEngine] consumeOwnerHints failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1422
|
-
return [];
|
|
1625
|
+
return { items: [] };
|
|
1423
1626
|
}
|
|
1424
1627
|
}
|
|
1425
1628
|
/**
|
|
@@ -1595,8 +1798,8 @@ export class ResponseEngine {
|
|
|
1595
1798
|
}
|
|
1596
1799
|
/**
|
|
1597
1800
|
* A fullaccess Trigger may run with no current relation access for its
|
|
1598
|
-
* origin actor, but only after
|
|
1599
|
-
*
|
|
1801
|
+
* origin actor, but only after its persisted owner provenance still
|
|
1802
|
+
* validates. This is intentionally stricter than merely
|
|
1600
1803
|
* checking the message's requested permission mode.
|
|
1601
1804
|
*/
|
|
1602
1805
|
isCurrentFullAccessTrigger(message) {
|
|
@@ -1610,8 +1813,7 @@ export class ResponseEngine {
|
|
|
1610
1813
|
return false;
|
|
1611
1814
|
}
|
|
1612
1815
|
try {
|
|
1613
|
-
return
|
|
1614
|
-
&& new Set(loadDaemonConfig().owners ?? []).has(trigger.authorizedBy);
|
|
1816
|
+
return new Set(loadDaemonConfig().owners ?? []).has(trigger.authorizedBy);
|
|
1615
1817
|
}
|
|
1616
1818
|
catch {
|
|
1617
1819
|
return false;
|
|
@@ -1619,7 +1821,7 @@ export class ResponseEngine {
|
|
|
1619
1821
|
}
|
|
1620
1822
|
/**
|
|
1621
1823
|
* The authorization envelope is immutable for one logical run, but the
|
|
1622
|
-
*
|
|
1824
|
+
* approving owner and runner profile are live policy. Check
|
|
1623
1825
|
* them immediately before every Runner invocation, including retries.
|
|
1624
1826
|
*/
|
|
1625
1827
|
assertCurrentFullAccessExecutionAuthorization(authorization, agent) {
|
|
@@ -1640,9 +1842,6 @@ export class ResponseEngine {
|
|
|
1640
1842
|
else if (authorization.source !== 'fullaccess-command' && authorization.source !== 'trigger') {
|
|
1641
1843
|
throw new Error('fullaccess execution authorization has an unsupported source');
|
|
1642
1844
|
}
|
|
1643
|
-
if (!isFullAccessEnabled()) {
|
|
1644
|
-
throw new Error('fullaccess execution is disabled by daemon.json');
|
|
1645
|
-
}
|
|
1646
1845
|
const currentOwners = new Set(loadDaemonConfig().owners ?? []);
|
|
1647
1846
|
if (!currentOwners.has(authorization.authorizedBy)) {
|
|
1648
1847
|
throw new Error('fullaccess execution authorization is no longer owned');
|
|
@@ -1832,7 +2031,7 @@ export class ResponseEngine {
|
|
|
1832
2031
|
'/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork',
|
|
1833
2032
|
'/stop', '/pause', '/resume', '/clear', '/compact', '/renew', '/del', '/perm', '/file', '/check',
|
|
1834
2033
|
'/s ', '/name ', '/rewind', '/rw', '/rw ', '/activity', '/observable', '/chatmode',
|
|
1835
|
-
'/aid', '/upgrade', '/evolagent', '/pair ',
|
|
2034
|
+
'/aid', '/upgrade', '/evolagent', '/pair ', '/memory',
|
|
1836
2035
|
];
|
|
1837
2036
|
/** 判断消息内容是否为已知命令 */
|
|
1838
2037
|
isKnownCommand(content) {
|
|
@@ -1850,9 +2049,15 @@ export class ResponseEngine {
|
|
|
1850
2049
|
}
|
|
1851
2050
|
catch (error) {
|
|
1852
2051
|
outerFailureReason = error instanceof Error ? error.message : String(error);
|
|
2052
|
+
if (message.source === 'owner-inject') {
|
|
2053
|
+
this.reportObserverHintFailureForMessage(message, this.observerHintFailureCodeForError(error));
|
|
2054
|
+
}
|
|
1853
2055
|
throw error;
|
|
1854
2056
|
}
|
|
1855
2057
|
finally {
|
|
2058
|
+
if (message.source === 'owner-inject' && message.observerHintId && message.boundSessionId) {
|
|
2059
|
+
this.scheduledObserverHints.delete(`${message.boundSessionId}\u0000${message.observerHintId}`);
|
|
2060
|
+
}
|
|
1856
2061
|
if (fallbackFullAccessAudit && !this.fullAccessTerminalMessages.has(message)) {
|
|
1857
2062
|
auditFullAccessEvent({
|
|
1858
2063
|
event: fallbackFullAccessAudit.authorization.source === 'trigger'
|
|
@@ -1913,6 +2118,10 @@ export class ResponseEngine {
|
|
|
1913
2118
|
// 先解析会话,再优先用 session.metadata.channelKey 精确定位实例级 adapter
|
|
1914
2119
|
// message.channel 现在存实例名(channelName),可直接用于精确路由
|
|
1915
2120
|
const { session, absoluteProjectPath } = await this.resolveSession(message);
|
|
2121
|
+
if (message.source === 'owner-inject' && (!message.observerHintId || !peekHints(resolvePaths().sessionsDir, 'aun', session.channelId, session.selfAID || '', session.threadId, session.chatType, 'interrupt', session.channel).some(item => item.id === message.observerHintId))) {
|
|
2122
|
+
this.reportObserverHintFailureForMessage(message, 'HINT_UNAVAILABLE');
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
1916
2125
|
if ((session.metadata?.diagnostic || session.metadata?.diagnosticStatus === 'cancelled')
|
|
1917
2126
|
&& !isDiagnosticSessionActive(session, this.sessionManager.getChatDir(session)))
|
|
1918
2127
|
return;
|
|
@@ -2004,6 +2213,21 @@ export class ResponseEngine {
|
|
|
2004
2213
|
this.publishTriggerExecutionFailure(message, 'access_denied');
|
|
2005
2214
|
return;
|
|
2006
2215
|
}
|
|
2216
|
+
if (message.chatType === 'group' && (message.source === 'user' || !message.source)) {
|
|
2217
|
+
const actorId = message.items?.filter(item => item.peerId).at(-1)?.peerId || message.peerId;
|
|
2218
|
+
if (actorId) {
|
|
2219
|
+
try {
|
|
2220
|
+
await this.sessionManager.patchSessionMetadata(session.id, {
|
|
2221
|
+
observerLastActorId: actorId,
|
|
2222
|
+
observerLastPeerName: message.peerName,
|
|
2223
|
+
observerLastPeerType: message.peerType,
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
catch (error) {
|
|
2227
|
+
logger.warn(`[ResponseEngine] observer group actor snapshot failed: ${error}`);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2007
2231
|
// thread(feishu) pending strategy: inject replyContext so first reply creates the thread
|
|
2008
2232
|
if (message.triggerMeta?.pendingThread && message.triggerMeta?.rootMessageId) {
|
|
2009
2233
|
message.replyContext = {
|
|
@@ -2035,6 +2259,7 @@ export class ResponseEngine {
|
|
|
2035
2259
|
catch (error) {
|
|
2036
2260
|
if (error instanceof BaseagentRunnerUnavailableError) {
|
|
2037
2261
|
logger.error(`[ResponseEngine] baseagent mismatch blocked: session=${session.id} channel=${channelKey} requested=${session.baseagent} owner=${error.evolagentName} available=${error.availableBaseagents.join(',') || '<none>'}`);
|
|
2262
|
+
this.reportObserverHintFailureForMessage(message, 'RUNNER_START_FAILED');
|
|
2038
2263
|
await this.sendBaseagentMismatch(channelKey, message.channelId, session, error, message.replyContext);
|
|
2039
2264
|
this.publishTriggerExecutionFailure(message, `baseagent_unavailable:${error.baseagent}`);
|
|
2040
2265
|
return;
|
|
@@ -2231,8 +2456,7 @@ export class ResponseEngine {
|
|
|
2231
2456
|
&& commandAuthorization.authorizedBy.length > 0) {
|
|
2232
2457
|
let currentOwner = false;
|
|
2233
2458
|
try {
|
|
2234
|
-
currentOwner =
|
|
2235
|
-
&& new Set(loadDaemonConfig().owners ?? []).has(commandAuthorization.authorizedBy);
|
|
2459
|
+
currentOwner = new Set(loadDaemonConfig().owners ?? []).has(commandAuthorization.authorizedBy);
|
|
2236
2460
|
if (commandAuthorization.source === 'agent-policy')
|
|
2237
2461
|
assertAgentPolicyAuthorization(commandAuthorization);
|
|
2238
2462
|
}
|
|
@@ -2348,6 +2572,8 @@ export class ResponseEngine {
|
|
|
2348
2572
|
const isBootstrapping = lifecycle === 'bootstrapping';
|
|
2349
2573
|
// Per-method agent name for stats bucketing (agent.name or '<unknown>')
|
|
2350
2574
|
const agentNameForStats = owningAgentForTask?.name ?? taskAgentAid ?? '<unknown>';
|
|
2575
|
+
let ownerHintClaim;
|
|
2576
|
+
let observerHintFailureCode;
|
|
2351
2577
|
if (!channelInfo) {
|
|
2352
2578
|
logger.error(`[ResponseEngine] Unknown channel: ${channelKey}`);
|
|
2353
2579
|
this.publishTriggerExecutionFailure(message, `unknown_channel:${channelKey}`);
|
|
@@ -2402,6 +2628,29 @@ export class ResponseEngine {
|
|
|
2402
2628
|
}
|
|
2403
2629
|
// 为本次任务处理生成唯一 task_id(客户端生成,格式 task-{10hex})
|
|
2404
2630
|
const taskId = `task-${crypto.randomUUID().replace(/-/g, '').slice(0, 10)}`;
|
|
2631
|
+
let taskActivityStatus = 'running';
|
|
2632
|
+
const taskActivityStore = runnerSelfAid ? new TaskActivityStore(runnerSelfAid) : undefined;
|
|
2633
|
+
if (taskActivityStore) {
|
|
2634
|
+
try {
|
|
2635
|
+
const activityChatType = chatType === 'group' ? 'group' : 'private';
|
|
2636
|
+
const activityChannelId = activityChatType === 'group'
|
|
2637
|
+
? (message.groupId || session.metadata?.groupId || message.channelId)
|
|
2638
|
+
: (message.peerId || message.channelId);
|
|
2639
|
+
taskActivityStore.createTask({
|
|
2640
|
+
taskId,
|
|
2641
|
+
sessionId: session.id,
|
|
2642
|
+
channel: message.channelType || session.channelType || message.channel,
|
|
2643
|
+
channelId: activityChannelId,
|
|
2644
|
+
chatType: activityChatType,
|
|
2645
|
+
...(activityChatType === 'group' ? { groupId: activityChannelId } : {}),
|
|
2646
|
+
...(activityChatType === 'private' ? { peerId: activityChannelId } : {}),
|
|
2647
|
+
taskStatus: taskActivityStatus,
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2650
|
+
catch (error) {
|
|
2651
|
+
logger.warn(`[ResponseEngine] task activity store initialization failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2405
2654
|
const taskPauseController = this.getPauseController(session.id);
|
|
2406
2655
|
let turnLease;
|
|
2407
2656
|
const inputCausation = normalizeCausation(message.causation) ?? createRootCausation();
|
|
@@ -2497,10 +2746,12 @@ export class ResponseEngine {
|
|
|
2497
2746
|
// this inbound actor. Do not fall back to a stale session owner/admin
|
|
2498
2747
|
// after an alias or Agent-role revocation; only use the persisted session
|
|
2499
2748
|
// identity when role resolution itself was unavailable.
|
|
2500
|
-
const peerRole =
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2749
|
+
const peerRole = message.autoMentionBatch && message.items?.some(item => item.peerRole && item.peerRole !== peerRoleDetail?.effectiveRole)
|
|
2750
|
+
? 'visitor'
|
|
2751
|
+
: authorizationIdentity?.role
|
|
2752
|
+
?? (peerRoleDetail
|
|
2753
|
+
? (peerRoleDetail.effectiveRole ?? 'none')
|
|
2754
|
+
: (session.identity?.role ?? 'none'));
|
|
2504
2755
|
const effectiveAgentConfig = (() => {
|
|
2505
2756
|
try {
|
|
2506
2757
|
return resolveEffective({ self: selfAid || undefined, peerKey }, { cache: true });
|
|
@@ -2583,6 +2834,9 @@ export class ResponseEngine {
|
|
|
2583
2834
|
outputState.middleOutputMode = middleOutputMode;
|
|
2584
2835
|
// 诊断日志:记录 inbound message_id 和生成的 task_id 的对应关系
|
|
2585
2836
|
logger.info(`[ResponseEngine] Task created: inboundMsgId=${message.messageId ?? 'none'} taskId=${taskId} sessionId=${session.id} chatmode=${chatmode} mode=${resolvedMode?.mode.id ?? 'override/fallback'}`);
|
|
2837
|
+
{
|
|
2838
|
+
import('../../utils/msg-diag.js').then(m => m.diagLog('task_created', { aid: session.selfAID ?? '', msgId: message.messageId ?? '', taskId, text: message.content ?? '' })).catch(() => { });
|
|
2839
|
+
}
|
|
2586
2840
|
// 构建带 taskId/chatmode 的 ReplyContext(本次任务所有出站消息共用)
|
|
2587
2841
|
const taskReplyContext = () => {
|
|
2588
2842
|
const base = this.getReplyContext(message);
|
|
@@ -2606,6 +2860,19 @@ export class ResponseEngine {
|
|
|
2606
2860
|
},
|
|
2607
2861
|
modeConfig: resolvedMode.context.modeConfig,
|
|
2608
2862
|
state: modeState,
|
|
2863
|
+
sessionState: resolvedMode.context.sessionState,
|
|
2864
|
+
continuation: message.taskRecovery
|
|
2865
|
+
? {
|
|
2866
|
+
kind: 'taskRecovery',
|
|
2867
|
+
taskId: message.taskRecovery.taskId,
|
|
2868
|
+
generation: message.taskRecovery.generation,
|
|
2869
|
+
groupId: message.groupId,
|
|
2870
|
+
threadId: message.threadId,
|
|
2871
|
+
}
|
|
2872
|
+
: message.restartResume?.submitted
|
|
2873
|
+
? { kind: 'restartResume', groupId: message.groupId, threadId: message.threadId }
|
|
2874
|
+
: undefined,
|
|
2875
|
+
previousTurnState: this.turnCoordinator.current(session),
|
|
2609
2876
|
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, chatType, selfAid),
|
|
2610
2877
|
describeSendCommandFailure: describeProactiveSendCommandFailure,
|
|
2611
2878
|
logger,
|
|
@@ -2842,7 +3109,18 @@ export class ResponseEngine {
|
|
|
2842
3109
|
this.publishTriggerExecutionSkipped(message, 'recovery_stale');
|
|
2843
3110
|
return;
|
|
2844
3111
|
}
|
|
2845
|
-
|
|
3112
|
+
const sourceMessageId = message.sourceMessageIds === undefined
|
|
3113
|
+
? message.messageId
|
|
3114
|
+
: message.sourceMessageIds.length === 1 ? message.sourceMessageIds[0] : undefined;
|
|
3115
|
+
const inputId = normalizeBaseagent(agent.name).canonical === 'claude'
|
|
3116
|
+
&& configChatType === 'private'
|
|
3117
|
+
&& (message.channelType ?? session.channelType) === 'aun'
|
|
3118
|
+
&& message.source !== 'trigger'
|
|
3119
|
+
&& !message.taskRecovery
|
|
3120
|
+
&& sourceMessageId
|
|
3121
|
+
? aunMessageTranscriptUuid(sourceMessageId)
|
|
3122
|
+
: undefined;
|
|
3123
|
+
turnLease = await this.turnCoordinator.begin(session, taskId, inputId, configActorId ? {
|
|
2846
3124
|
peerId: configActorId,
|
|
2847
3125
|
encrypted: message.encrypted ?? message.replyContext?.metadata?.encrypted,
|
|
2848
3126
|
baseagent: session.baseagent,
|
|
@@ -2852,7 +3130,11 @@ export class ResponseEngine {
|
|
|
2852
3130
|
triggerAttemptId: message.triggerMeta?.attemptId,
|
|
2853
3131
|
} : undefined);
|
|
2854
3132
|
// 记录开始处理
|
|
2855
|
-
const taskEncrypt =
|
|
3133
|
+
const taskEncrypt = typeof message.encrypted === 'boolean'
|
|
3134
|
+
? message.encrypted
|
|
3135
|
+
: message.replyContext?.metadata?.encrypted != null
|
|
3136
|
+
? !!message.replyContext.metadata.encrypted
|
|
3137
|
+
: undefined;
|
|
2856
3138
|
this.eventBus.publish({
|
|
2857
3139
|
type: 'task:started',
|
|
2858
3140
|
sessionId: session.id,
|
|
@@ -2884,6 +3166,9 @@ export class ResponseEngine {
|
|
|
2884
3166
|
});
|
|
2885
3167
|
// 创建 IMRenderer(统一 interactive/proactive 两条路径)
|
|
2886
3168
|
let firstReply = true;
|
|
3169
|
+
// 出站 debug.model:每次 runQuery 前更新(含备用模型切换)。单独声明是因为
|
|
3170
|
+
// send 闭包早于下方 modelOverride/agentModel 的 let 声明,直接引用会撞 TDZ。
|
|
3171
|
+
let debugRunnerModel;
|
|
2887
3172
|
const renderer = new IMRenderer({
|
|
2888
3173
|
adapter,
|
|
2889
3174
|
envelope,
|
|
@@ -2895,6 +3180,29 @@ export class ResponseEngine {
|
|
|
2895
3180
|
deferTextUntilBoundary: (message.channelType ?? session.channelType) === 'aun' && session.chatType === 'private',
|
|
2896
3181
|
operationalNoticesAsText: !isProactive && middleOutputMode !== 'none',
|
|
2897
3182
|
fileMarkerPattern: options?.fileMarkerPattern,
|
|
3183
|
+
recordActivity: item => {
|
|
3184
|
+
if (!taskActivityStore || (turnLease && !this.turnCoordinator.canPublish(turnLease)))
|
|
3185
|
+
return;
|
|
3186
|
+
try {
|
|
3187
|
+
const activityChatType = chatType === 'group' ? 'group' : 'private';
|
|
3188
|
+
const activityChannelId = activityChatType === 'group'
|
|
3189
|
+
? (message.groupId || session.metadata?.groupId || message.channelId)
|
|
3190
|
+
: (message.peerId || message.channelId);
|
|
3191
|
+
taskActivityStore.append({
|
|
3192
|
+
taskId,
|
|
3193
|
+
sessionId: session.id,
|
|
3194
|
+
channel: message.channelType || session.channelType || message.channel,
|
|
3195
|
+
channelId: activityChannelId,
|
|
3196
|
+
chatType: activityChatType,
|
|
3197
|
+
...(activityChatType === 'group' ? { groupId: activityChannelId } : {}),
|
|
3198
|
+
...(activityChatType === 'private' ? { peerId: activityChannelId } : {}),
|
|
3199
|
+
item,
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
catch (error) {
|
|
3203
|
+
logger.warn(`[ResponseEngine] task activity record failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3204
|
+
}
|
|
3205
|
+
},
|
|
2898
3206
|
send: async (payload) => {
|
|
2899
3207
|
if (turnLease && !this.turnCoordinator.canPublish(turnLease)) {
|
|
2900
3208
|
snapshot.pushOutbound(session.id, taskId, { kind: payload.kind, decision: 'suppressed-stale-turn' });
|
|
@@ -2926,8 +3234,13 @@ export class ResponseEngine {
|
|
|
2926
3234
|
}
|
|
2927
3235
|
opts.sessionId = session.id;
|
|
2928
3236
|
if (payload.kind === 'result.text' && payload.isFinal) {
|
|
2929
|
-
opts.
|
|
3237
|
+
opts.ext = { ...opts.ext, isFinal: true };
|
|
2930
3238
|
}
|
|
3239
|
+
opts.debug = {
|
|
3240
|
+
sessionId: session.id,
|
|
3241
|
+
...(session.agentSessionId ? { agentSessionId: session.agentSessionId } : {}),
|
|
3242
|
+
...(debugRunnerModel ? { model: debugRunnerModel } : {}),
|
|
3243
|
+
};
|
|
2931
3244
|
opts.metadata = withTaskMetadata({
|
|
2932
3245
|
...(opts.metadata ?? {}),
|
|
2933
3246
|
...(payload.kind === 'result.text' ? payload.metadata : {}),
|
|
@@ -3099,7 +3412,12 @@ export class ResponseEngine {
|
|
|
3099
3412
|
const runModeConfig = resolvedMode?.mode.configureRun && modeProcessCtx
|
|
3100
3413
|
? resolvedMode.mode.configureRun(modeProcessCtx)
|
|
3101
3414
|
: undefined;
|
|
3102
|
-
const
|
|
3415
|
+
const modePolicyHook = runModeConfig?.policyHook;
|
|
3416
|
+
// 整理会话:写工具全拦、Bash 只放行 ec memory create|update|archive,先于模式策略判定
|
|
3417
|
+
const isMemorySession = (session.sessionType ?? session.metadata?.sessionType) === 'memory';
|
|
3418
|
+
const pureSessionPolicyHook = isMemorySession
|
|
3419
|
+
? (toolName, toolInput) => memorySessionPolicyHook(toolName, toolInput) ?? modePolicyHook?.(toolName, toolInput)
|
|
3420
|
+
: modePolicyHook;
|
|
3103
3421
|
// 设置权限审批的交互上下文(支持交互卡片)
|
|
3104
3422
|
const permissionContext = {
|
|
3105
3423
|
sendPrompt: permissionPromptForOrigin,
|
|
@@ -3222,6 +3540,9 @@ export class ResponseEngine {
|
|
|
3222
3540
|
const stBefore = modeState.get('proactive');
|
|
3223
3541
|
const wasFirstPending = stBefore && !stBefore.firstToolDone;
|
|
3224
3542
|
const result = pluginHook(toolName, toolInput);
|
|
3543
|
+
// 整理会话的拦截原因已经写清楚怎么改,不套 proactive 违规提醒
|
|
3544
|
+
if (result?.block && result.policyCode?.startsWith('memory_session_'))
|
|
3545
|
+
return result;
|
|
3225
3546
|
if (result?.block) {
|
|
3226
3547
|
// 拦截事件(首工具非表态 / 工具进展汇报未完成)
|
|
3227
3548
|
snapshot.set(session.id, taskId, { policyHook: { triggered: true, blocked: true, toolName } });
|
|
@@ -3283,10 +3604,9 @@ export class ResponseEngine {
|
|
|
3283
3604
|
if (!isProactive && channelInfo.adapter.capabilities?.file)
|
|
3284
3605
|
capParts.push('文件发送');
|
|
3285
3606
|
// Personal state is exposed to the manifest as vars. The manifest owns
|
|
3286
|
-
// loading persona.md
|
|
3607
|
+
// loading persona.md into the system prompt.
|
|
3287
3608
|
const owningAgent = owningAgentForTask;
|
|
3288
3609
|
const persona = isActive ? (owningAgent?.getPersona?.() || undefined) : undefined;
|
|
3289
|
-
const working = isActive ? (owningAgent?.getWorkingMemory?.() || undefined) : undefined;
|
|
3290
3610
|
// 计算 peerKey:群聊固定按 groupId/channelId,私聊按发送者 peerId。
|
|
3291
3611
|
// 这样单条和积压合并批次不会因队列状态不同而切换关系级配置。
|
|
3292
3612
|
const normalizedBaseagent = normalizeBaseagent(agent.name);
|
|
@@ -3628,6 +3948,10 @@ export class ResponseEngine {
|
|
|
3628
3948
|
},
|
|
3629
3949
|
}).allow;
|
|
3630
3950
|
};
|
|
3951
|
+
// 会话原型与整理模式:必须在 kitCtx 构造之前读出,manifest 的 when 条件
|
|
3952
|
+
// 依赖它们选段(缺了会 fail-closed —— 模式手册全不加载,且不报错)。
|
|
3953
|
+
const renderedSessionType = session.sessionType ?? session.metadata?.sessionType ?? 'main';
|
|
3954
|
+
const memoryMode = session.metadata?.memoryMode;
|
|
3631
3955
|
// Kit renderer: 组装上下文
|
|
3632
3956
|
const pkgRoot = getPackageRoot();
|
|
3633
3957
|
const kitCtx = {
|
|
@@ -3645,16 +3969,36 @@ export class ResponseEngine {
|
|
|
3645
3969
|
// evolcore 运行模式:dev=源码仓库 | install=全局安装包
|
|
3646
3970
|
evolcoreMode: readEvolcoreRuntimeFlavor(),
|
|
3647
3971
|
// 路径变量(用于 manifest 路径展开,resolvePath 用 ctx.vars 取真值)
|
|
3972
|
+
// AGENT_DIR 是 manifest 的合法别名(buildPathMappings 有注册),
|
|
3973
|
+
// 缺了它引用 $AGENT_DIR 的段(如技能索引)会整段静默丢弃。
|
|
3974
|
+
AGENT_DIR: selfAid ? path.join(resolveRoot(), 'agents', selfAid) : undefined,
|
|
3648
3975
|
PERSONAL_DIR: selfAid ? path.join(resolveRoot(), 'agents', selfAid, 'personal') : undefined,
|
|
3649
3976
|
RELATIONS_DIR: selfAid ? path.join(resolveRoot(), 'agents', selfAid, 'relations') : undefined,
|
|
3650
3977
|
VENUES_DIR: selfAid ? path.join(resolveRoot(), 'agents', selfAid, 'venues') : undefined,
|
|
3651
3978
|
selfAid: selfAid || undefined,
|
|
3979
|
+
// ownerAid:当前 agent 的第一个 owner AID,用于 owner 作用域记忆路径展开。
|
|
3980
|
+
// 来源:getFirstStaticAgentOwner 读 config.json owners[0];AID 格式不合法时置 undefined。
|
|
3981
|
+
ownerAid: (() => {
|
|
3982
|
+
const raw = selfAid ? getFirstStaticAgentOwner(selfAid) : undefined;
|
|
3983
|
+
if (!raw)
|
|
3984
|
+
return undefined;
|
|
3985
|
+
return isValidAid(raw) ? raw : undefined;
|
|
3986
|
+
})(),
|
|
3987
|
+
// 记忆系统开关变量(来自 effectiveAgentConfig.memory,默认全部启用)。
|
|
3988
|
+
// enableRelation 是总开关:relation 是所有记忆的唯一出生地。
|
|
3989
|
+
// memory.enableRelation 在 relation-config schema 里声明了 x-merge-logic: AND,
|
|
3990
|
+
// mergeLayers 会自动处理 agent 级与 relation 级的 AND 合并,无需手动读两层。
|
|
3991
|
+
memoryEnableRelation: effectiveAgentConfig?.memory?.enableRelation ?? true,
|
|
3992
|
+
memoryEnableAgent: effectiveAgentConfig?.memory?.enableAgent ?? true,
|
|
3993
|
+
memoryEnableOwner: effectiveAgentConfig?.memory?.enableOwner ?? true,
|
|
3994
|
+
// 会话原型与整理模式(manifest 的 when 条件靠它们选段)
|
|
3995
|
+
sessionType: renderedSessionType,
|
|
3996
|
+
memoryMode,
|
|
3652
3997
|
selfName: selfName || undefined,
|
|
3653
3998
|
agentMdPath: selfAid ? agentMdPath(selfAid) : undefined,
|
|
3654
3999
|
lifecycle,
|
|
3655
4000
|
isBootstrapping,
|
|
3656
4001
|
hasPersona: !!persona,
|
|
3657
|
-
hasWorkingMemory: !!working,
|
|
3658
4002
|
peerId: peerIdRaw || undefined,
|
|
3659
4003
|
peerKey,
|
|
3660
4004
|
peerName: peerName || undefined,
|
|
@@ -3705,9 +4049,10 @@ export class ResponseEngine {
|
|
|
3705
4049
|
// 默认值不在此硬编码,出厂默认由模式 schema 声明、经 buildState 落入 proactive.*。
|
|
3706
4050
|
proactivePreTool1stMsgChk: proactive?.preTool1stMsgChk ?? false,
|
|
3707
4051
|
proactiveToolUseReminder: proactive?.toolUseReminder ?? false,
|
|
3708
|
-
|
|
4052
|
+
// 明确续跑已表态时,提示词和运行时门禁都不再要求重复开场。
|
|
4053
|
+
proactiveFirstSendRequired: proactive?.firstSendRequired === true && !proactive.firstToolDone,
|
|
3709
4054
|
proactiveToolReportRequired: proactive?.toolReportRequired ?? false,
|
|
3710
|
-
proactiveToolReportInterval: proactive?.toolReportInterval ??
|
|
4055
|
+
proactiveToolReportInterval: proactive?.toolReportInterval ?? 20,
|
|
3711
4056
|
proactiveSendTargetLabel: chatType === 'group' ? '群里' : '对方',
|
|
3712
4057
|
readonly: effectivePermissionMode === 'readonly',
|
|
3713
4058
|
baseAgent: normalizedBaseagent.canonical,
|
|
@@ -3723,15 +4068,20 @@ export class ResponseEngine {
|
|
|
3723
4068
|
},
|
|
3724
4069
|
sessionId: session.id,
|
|
3725
4070
|
};
|
|
3726
|
-
//
|
|
3727
|
-
|
|
4071
|
+
// 确保技能索引是最新的(懒加载机制)
|
|
4072
|
+
if (selfAid) {
|
|
4073
|
+
const { ensureSkillIndexUpToDate } = await import('../skill/startup.js');
|
|
4074
|
+
ensureSkillIndexUpToDate(resolveAgentDir(selfAid), selfAid);
|
|
4075
|
+
}
|
|
4076
|
+
// 按会话原型(sessionType)选 manifest 文件:config.sessionManifests 覆盖 → 内置映射 → 主 manifest。
|
|
4077
|
+
const sessionType = renderedSessionType;
|
|
3728
4078
|
const sessionManifests = this.agentRegistry?.resolveByChannel(channelKey)?.config?.sessionManifests;
|
|
3729
4079
|
// Bootstrap always uses the lifecycle-gated main manifest. A custom
|
|
3730
4080
|
// session manifest (for example the auxiliary manifest with `always`
|
|
3731
4081
|
// sections) must not reintroduce normal rules or command context.
|
|
3732
4082
|
const manifestFile = isBootstrapping
|
|
3733
|
-
?
|
|
3734
|
-
:
|
|
4083
|
+
? DEFAULT_MANIFEST_FILE
|
|
4084
|
+
: resolveSessionManifest(sessionType, sessionManifests);
|
|
3735
4085
|
const kitContext = renderKitSections(kitCtx, manifestFile);
|
|
3736
4086
|
effectiveSystemPrompt = [options?.systemPromptAppend, kitContext].filter(Boolean).join('\n') || undefined;
|
|
3737
4087
|
// ── Stats: context_breakdown 旁路采集(各段估算 token 数,字符数/4 近似) ──
|
|
@@ -3766,9 +4116,9 @@ export class ResponseEngine {
|
|
|
3766
4116
|
catch { /* non-fatal */ }
|
|
3767
4117
|
// 消息渲染层:用 message manifest 逐条渲染(时间 + 群聊发送者),组装成最终正文。
|
|
3768
4118
|
// 单条消息构造单元素 items;批量合并的消息 message.items 已由队列填充。
|
|
3769
|
-
const hasContent = message.content.trim() || (message.items && message.items.length > 0);
|
|
4119
|
+
const hasContent = !!message.observerHintId || message.content.trim() || !!message.images?.length || (message.items && message.items.length > 0);
|
|
3770
4120
|
if (hasContent) {
|
|
3771
|
-
const rawPeerItems = message.items && message.items.length > 0
|
|
4121
|
+
const rawPeerItems = message.source === 'owner-inject' ? [] : message.items && message.items.length > 0
|
|
3772
4122
|
? message.items
|
|
3773
4123
|
: [{
|
|
3774
4124
|
peerId: message.peerId, peerName: peerName || undefined,
|
|
@@ -3795,10 +4145,17 @@ export class ResponseEngine {
|
|
|
3795
4145
|
}
|
|
3796
4146
|
return rawPeerItems;
|
|
3797
4147
|
})();
|
|
3798
|
-
//
|
|
3799
|
-
//
|
|
3800
|
-
|
|
3801
|
-
|
|
4148
|
+
// 将匹配的 pending hint 暂时 claim,排在真实消息之前;渲染或启动
|
|
4149
|
+
// 失败时释放 claim,只有 runner 接受 prompt 后才持久化 consumed。
|
|
4150
|
+
const hintResult = this.consumeOwnerHints(session, message);
|
|
4151
|
+
ownerHintClaim = hintResult.claim;
|
|
4152
|
+
const hintItems = hintResult.items;
|
|
4153
|
+
if (message.source === 'owner-inject' && hintItems.length === 0) {
|
|
4154
|
+
if (turnLease)
|
|
4155
|
+
await this.turnCoordinator.finish(session, turnLease, 'cancelled', 'owner_hint_unavailable');
|
|
4156
|
+
this.sessionManager.clearProcessingIfTask(session.id, taskId);
|
|
4157
|
+
return;
|
|
4158
|
+
}
|
|
3802
4159
|
const renderItems = hintItems.length > 0 ? [...hintItems, ...peerItems] : peerItems;
|
|
3803
4160
|
const fallbackContent = (() => {
|
|
3804
4161
|
if (!message.restartResume?.submitted || peerItems.length === 0)
|
|
@@ -3839,8 +4196,16 @@ export class ResponseEngine {
|
|
|
3839
4196
|
effectivePrompt = wrapPrompt(composeHintFallback(hintItems, fallbackContent));
|
|
3840
4197
|
}
|
|
3841
4198
|
catch (e) {
|
|
3842
|
-
logger.warn(`[ResponseEngine] renderMessageBody failed
|
|
3843
|
-
|
|
4199
|
+
logger.warn(`[ResponseEngine] renderMessageBody failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
4200
|
+
if (ownerHintClaim) {
|
|
4201
|
+
observerHintFailureCode = 'RENDER_FAILED';
|
|
4202
|
+
this.reportObserverHintFailureForClaim(message, session, ownerHintClaim, observerHintFailureCode);
|
|
4203
|
+
releaseHintClaim(ownerHintClaim);
|
|
4204
|
+
ownerHintClaim = undefined;
|
|
4205
|
+
}
|
|
4206
|
+
if (message.source === 'owner-inject')
|
|
4207
|
+
throw e;
|
|
4208
|
+
effectivePrompt = wrapPrompt(fallbackContent);
|
|
3844
4209
|
}
|
|
3845
4210
|
if (v2HandoffIds.length > 0 && renderResult?.body.trim())
|
|
3846
4211
|
handoffPromptRendered = true;
|
|
@@ -3854,6 +4219,12 @@ export class ResponseEngine {
|
|
|
3854
4219
|
// 空消息防护:在 agent 调用之前检查 prompt 是否为空
|
|
3855
4220
|
// 防止空消息(或纯空格消息)浪费 API 调用
|
|
3856
4221
|
if (!effectivePrompt.trim()) {
|
|
4222
|
+
if (ownerHintClaim) {
|
|
4223
|
+
observerHintFailureCode = 'HINT_UNAVAILABLE';
|
|
4224
|
+
this.reportObserverHintFailureForClaim(message, session, ownerHintClaim, observerHintFailureCode);
|
|
4225
|
+
releaseHintClaim(ownerHintClaim);
|
|
4226
|
+
ownerHintClaim = undefined;
|
|
4227
|
+
}
|
|
3857
4228
|
logger.info(`[ResponseEngine] Skip agent call: empty prompt after render. session=${session.id} task=${taskId}`);
|
|
3858
4229
|
if (turnLease)
|
|
3859
4230
|
await this.turnCoordinator.finish(session, turnLease, 'cancelled', 'empty_prompt');
|
|
@@ -3889,9 +4260,18 @@ export class ResponseEngine {
|
|
|
3889
4260
|
chatType: configChatType,
|
|
3890
4261
|
selfAid,
|
|
3891
4262
|
peerId: configActorId || undefined,
|
|
4263
|
+
peerKey,
|
|
3892
4264
|
peerName: peerName || undefined,
|
|
3893
4265
|
peerType: message.peerType || session.metadata?.peerType || undefined,
|
|
3894
4266
|
peerRole,
|
|
4267
|
+
// 会话原型与整理模式:ipc 侧靠 sessionType 拦非整理会话的写命令,
|
|
4268
|
+
// 靠 memoryMode 校验 --scope 是否与本模式匹配。
|
|
4269
|
+
sessionType: renderedSessionType,
|
|
4270
|
+
memoryMode: session.metadata?.memoryMode ?? undefined,
|
|
4271
|
+
memoryScope: session.metadata?.memoryScope ?? undefined,
|
|
4272
|
+
consolidationBatchDir: session.metadata?.consolidationBatchDir
|
|
4273
|
+
?? session.metadata?.consolidationLogBase
|
|
4274
|
+
?? undefined,
|
|
3895
4275
|
permissionMode: effectivePermissionMode,
|
|
3896
4276
|
...(fullAccessAuthorization ? {
|
|
3897
4277
|
processRole: fullAccessAuthorization.processRole,
|
|
@@ -3951,6 +4331,7 @@ export class ResponseEngine {
|
|
|
3951
4331
|
let attemptTimeout;
|
|
3952
4332
|
try {
|
|
3953
4333
|
logger.info(`[ResponseEngine] agent.runQuery start: agent=${agent.name} session=${session.id} task=${taskId} attempt=${runAttempt} apiRetries=${retryScheduler.apiRetryAttemptsMade} agentSessionId=${runAgentSessionId ?? 'none'} topicBinding=${session.threadId ? 'authoritative' : 'legacy'}`);
|
|
4334
|
+
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(() => { });
|
|
3954
4335
|
if (fullAccessAuthorization) {
|
|
3955
4336
|
this.assertCurrentFullAccessExecutionAuthorization(fullAccessAuthorization, agent);
|
|
3956
4337
|
if (fullAccessAuditStartedAt === undefined) {
|
|
@@ -4003,13 +4384,41 @@ export class ResponseEngine {
|
|
|
4003
4384
|
modelOverride = { ...modelOverride,
|
|
4004
4385
|
enableRequestUserInput: effectiveAgentConfig?.baseagents?.codex?.enableRequestUserInput ?? true };
|
|
4005
4386
|
}
|
|
4006
|
-
const
|
|
4387
|
+
const sourceImages = renderResult?.images.length ? renderResult.images : message.images;
|
|
4388
|
+
const runnerImages = sourceImages?.map(image => ({
|
|
4389
|
+
data: image.data,
|
|
4390
|
+
mimeType: image.mimeType,
|
|
4391
|
+
}));
|
|
4392
|
+
debugRunnerModel = modelOverride?.model || agentModel;
|
|
4393
|
+
const stream = await agent.runQuery(session.id, effectivePrompt, absoluteProjectPath, runAgentSessionId, runnerImages, effectiveSystemPrompt, this.sessionManager, modelOverride, runtimeEnv);
|
|
4007
4394
|
// The turn may be superseded while the runner is still creating its
|
|
4008
4395
|
// backend query. Replay the interrupt after runQuery resolves, when
|
|
4009
4396
|
// every runner is required to have an addressable cancellation handle.
|
|
4010
4397
|
if (await this.cancelStaleRunnerStart(agent, session.id, streamKey, turnLease, taskId)) {
|
|
4011
4398
|
throw new Error('RUNNER_START_SUPERSEDED');
|
|
4012
4399
|
}
|
|
4400
|
+
if (ownerHintClaim) {
|
|
4401
|
+
const accepted = ownerHintClaim;
|
|
4402
|
+
if (commitHintClaim(accepted)) {
|
|
4403
|
+
for (const hint of accepted.hints) {
|
|
4404
|
+
try {
|
|
4405
|
+
this.observerHintConsumed?.(hint, {
|
|
4406
|
+
channelKey: session.channel, channelId: accepted.channelId,
|
|
4407
|
+
chatType: accepted.chatType, threadId: accepted.threadId || undefined,
|
|
4408
|
+
}, {
|
|
4409
|
+
action: hint.promoteRequestId ? 'promote' : 'add',
|
|
4410
|
+
requestId: hint.promoteRequestId ?? message.observerHintRequestId ?? (hint.mode === 'interrupt' ? hint.id : undefined),
|
|
4411
|
+
});
|
|
4412
|
+
}
|
|
4413
|
+
catch (error) {
|
|
4414
|
+
logger.warn(`[ResponseEngine] observer hint status failed id=${hint.id}: ${error}`);
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
else
|
|
4419
|
+
releaseHintClaim(accepted);
|
|
4420
|
+
ownerHintClaim = undefined;
|
|
4421
|
+
}
|
|
4013
4422
|
agent.registerStream(streamKey, stream);
|
|
4014
4423
|
streamRegistered = true;
|
|
4015
4424
|
if (shouldTimeoutRetryAttempt) {
|
|
@@ -4019,7 +4428,11 @@ export class ResponseEngine {
|
|
|
4019
4428
|
const processAttempt = this.processEventStream(stream, session, agent, renderer, (eventType, toolName) => {
|
|
4020
4429
|
resetTimer(eventType, toolName);
|
|
4021
4430
|
attemptTimeout?.reset();
|
|
4022
|
-
}, shouldSuppress, proactive, resolvedMode ? {
|
|
4431
|
+
}, shouldSuppress, proactive, resolvedMode ? {
|
|
4432
|
+
mode: resolvedMode.mode,
|
|
4433
|
+
state: modeState,
|
|
4434
|
+
sessionState: resolvedMode.context.sessionState,
|
|
4435
|
+
} : undefined, taskCausation, turnLease, effectivePermissionMode, selfAid, async (request) => {
|
|
4023
4436
|
if (!this.asyncQuestions)
|
|
4024
4437
|
throw new Error('Async question service is unavailable');
|
|
4025
4438
|
if (permissionContext.approvalInteractionPolicy === 'deny') {
|
|
@@ -4185,6 +4598,7 @@ export class ResponseEngine {
|
|
|
4185
4598
|
}
|
|
4186
4599
|
}
|
|
4187
4600
|
catch (error) {
|
|
4601
|
+
observerHintFailureCode = this.observerHintFailureCodeForError(error);
|
|
4188
4602
|
if (classifyError(error) === ErrorType.CONTEXT_TOO_LONG && session.agentSessionId && canCompactAgent(agent)) {
|
|
4189
4603
|
streamResult = await this.recoverFromContextLimit(streamResult, {
|
|
4190
4604
|
streamKey,
|
|
@@ -4439,6 +4853,11 @@ export class ResponseEngine {
|
|
|
4439
4853
|
// 最终回复文本:suppressed 模式或无 text 事件时需要兜底添加
|
|
4440
4854
|
const finalReplyText = streamResult.lastReplyText || streamResult.fullText;
|
|
4441
4855
|
const visibleFinalReplyText = stripContextLimitErrorLines(finalReplyText);
|
|
4856
|
+
const isDiagnosticFallback = streamResult.isError && !streamResult.hasReceivedText;
|
|
4857
|
+
const isTriggerNoop = message.source === 'trigger'
|
|
4858
|
+
&& typeof message.triggerMeta?.noopSentinel === 'string'
|
|
4859
|
+
&& message.triggerMeta.noopSentinel.length > 0
|
|
4860
|
+
&& visibleFinalReplyText.trim() === message.triggerMeta.noopSentinel;
|
|
4442
4861
|
if (!this.turnCoordinator.isCurrent(turnLease)) {
|
|
4443
4862
|
agent.cleanupStream(streamKey);
|
|
4444
4863
|
this.sessionManager.clearProcessingIfTask(session.id, taskId);
|
|
@@ -4450,7 +4869,7 @@ export class ResponseEngine {
|
|
|
4450
4869
|
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_turn', taskCausation);
|
|
4451
4870
|
return;
|
|
4452
4871
|
}
|
|
4453
|
-
if (visibleFinalReplyText) {
|
|
4872
|
+
if (visibleFinalReplyText && !isDiagnosticFallback && !isTriggerNoop) {
|
|
4454
4873
|
// `hasReceivedText` 是任务级计数。工具调用前的文字已经显示,并不表示最后一轮
|
|
4455
4874
|
// (通常来自 complete.result)的文字也已显示;用本轮投影标记避免终态丢失。
|
|
4456
4875
|
if (shouldSuppress() || !streamResult.hasProjectedCurrentReplyText) {
|
|
@@ -4466,6 +4885,7 @@ export class ResponseEngine {
|
|
|
4466
4885
|
}
|
|
4467
4886
|
this.agentDelegationRegistry?.revokeTask(session.id, taskId);
|
|
4468
4887
|
logger.info(`[ResponseEngine] session ${session.id} processing cleared task=${taskId}`);
|
|
4888
|
+
import('../../utils/msg-diag.js').then(m => m.diagLog('task_done', { aid: session.selfAID ?? '', msgId: message.messageId ?? '', taskId, text: message.content ?? '' })).catch(() => { });
|
|
4469
4889
|
// 降级模型回复末尾追加标记(代码层硬注入,不依赖模型输出)
|
|
4470
4890
|
if (usedFallback) {
|
|
4471
4891
|
const curFbState = this.modelFallbackMap.get(session.id);
|
|
@@ -4537,6 +4957,7 @@ export class ResponseEngine {
|
|
|
4537
4957
|
// 这不是真正的失败,不应把诊断串暴露给用户,也不计入错误统计。
|
|
4538
4958
|
const isExpectedInterrupt = isNonErrorInterruptReason(interruptReason)
|
|
4539
4959
|
|| isExpectedTerminalInterrupt(streamResult.terminalReason);
|
|
4960
|
+
taskActivityStatus = isExpectedInterrupt ? 'interrupted' : 'error';
|
|
4540
4961
|
if (!isExpectedInterrupt && !permissionTerminalFailure) {
|
|
4541
4962
|
const terminalMetadata = {
|
|
4542
4963
|
errorType: rawSubtype,
|
|
@@ -4626,6 +5047,7 @@ export class ResponseEngine {
|
|
|
4626
5047
|
}
|
|
4627
5048
|
else {
|
|
4628
5049
|
// 真正的成功
|
|
5050
|
+
taskActivityStatus = 'completed';
|
|
4629
5051
|
if (!this.turnCoordinator.canPublish(turnLease)) {
|
|
4630
5052
|
logger.info(`[ResponseEngine] Suppressing stale success path: session=${session.id} task=${taskId}`);
|
|
4631
5053
|
await this.publishTriggerExecutionInterrupted(message, adapter, envelope, 'stale_turn', taskCausation);
|
|
@@ -4828,6 +5250,7 @@ export class ResponseEngine {
|
|
|
4828
5250
|
durationMs: Date.now() - startTime,
|
|
4829
5251
|
agentName: agentNameForStats,
|
|
4830
5252
|
numTurns: streamResult.numTurns,
|
|
5253
|
+
encrypted: message.encrypted ?? message.replyContext?.metadata?.encrypted,
|
|
4831
5254
|
timestamp: Date.now(),
|
|
4832
5255
|
causation: taskCausation,
|
|
4833
5256
|
});
|
|
@@ -4866,6 +5289,15 @@ export class ResponseEngine {
|
|
|
4866
5289
|
});
|
|
4867
5290
|
}
|
|
4868
5291
|
catch (error) {
|
|
5292
|
+
if (error instanceof Error && (error.message === 'TOTAL_EXECUTION_TIMEOUT' || error.message === 'SDK_TIMEOUT')) {
|
|
5293
|
+
taskActivityStatus = 'timeout';
|
|
5294
|
+
}
|
|
5295
|
+
else if (isNonErrorInterruptReason(this.interruptedSessions.get(session.id))) {
|
|
5296
|
+
taskActivityStatus = 'interrupted';
|
|
5297
|
+
}
|
|
5298
|
+
else {
|
|
5299
|
+
taskActivityStatus = 'error';
|
|
5300
|
+
}
|
|
4869
5301
|
fullAccessExecutionFailed = true;
|
|
4870
5302
|
fullAccessExecutionFailureReason = error instanceof Error ? error.message : String(error);
|
|
4871
5303
|
const authoritativeTimeoutError = timeoutControl?.claim();
|
|
@@ -5083,6 +5515,37 @@ export class ResponseEngine {
|
|
|
5083
5515
|
}
|
|
5084
5516
|
}
|
|
5085
5517
|
finally {
|
|
5518
|
+
if (taskActivityStore) {
|
|
5519
|
+
try {
|
|
5520
|
+
taskActivityStore.updateStatus(taskId, taskActivityStatus);
|
|
5521
|
+
}
|
|
5522
|
+
catch (error) {
|
|
5523
|
+
logger.warn(`[ResponseEngine] task activity status update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
if (turnLease && resolvedMode?.mode.onTurnEnd) {
|
|
5527
|
+
try {
|
|
5528
|
+
const current = this.turnCoordinator.current(session);
|
|
5529
|
+
await resolvedMode.mode.onTurnEnd({
|
|
5530
|
+
session,
|
|
5531
|
+
state: modeState,
|
|
5532
|
+
sessionState: resolvedMode.context.sessionState,
|
|
5533
|
+
turn: turnLease,
|
|
5534
|
+
terminal: current.active ? undefined : current.lastTerminal,
|
|
5535
|
+
logger,
|
|
5536
|
+
});
|
|
5537
|
+
}
|
|
5538
|
+
catch (error) {
|
|
5539
|
+
logger.warn(`[ResponseEngine] onTurnEnd failed: session=${session.id} task=${taskId} error=${String(error)}`);
|
|
5540
|
+
}
|
|
5541
|
+
}
|
|
5542
|
+
if (ownerHintClaim) {
|
|
5543
|
+
// A runner error, interruption, or pre-run failure leaves the durable
|
|
5544
|
+
// hint pending for the next attempt.
|
|
5545
|
+
this.reportObserverHintFailureForClaim(message, session, ownerHintClaim, observerHintFailureCode ?? 'RUNNER_START_FAILED');
|
|
5546
|
+
releaseHintClaim(ownerHintClaim);
|
|
5547
|
+
ownerHintClaim = undefined;
|
|
5548
|
+
}
|
|
5086
5549
|
if (fullAccessAuthorization) {
|
|
5087
5550
|
const startedAt = fullAccessAuditStartedAt;
|
|
5088
5551
|
const executed = fullAccessRunnerInvoked;
|
|
@@ -5231,6 +5694,30 @@ export class ResponseEngine {
|
|
|
5231
5694
|
* 解析会话和项目路径
|
|
5232
5695
|
*/
|
|
5233
5696
|
async resolveSession(message) {
|
|
5697
|
+
if (message.source === 'owner-inject') {
|
|
5698
|
+
const bound = message.boundSessionId
|
|
5699
|
+
? await this.sessionManager.getSessionById(message.boundSessionId) : undefined;
|
|
5700
|
+
if (!message.observerHintId || !bound || bound.deletedAt || !bound.agentSessionId
|
|
5701
|
+
|| bound.channel !== message.channel || bound.channelId !== message.channelId
|
|
5702
|
+
|| bound.selfAID !== message.selfAID || bound.chatType !== message.chatType
|
|
5703
|
+
|| (bound.threadId || '') !== (message.threadId || '')) {
|
|
5704
|
+
throw new Error('Observer hint target session is no longer available');
|
|
5705
|
+
}
|
|
5706
|
+
// An enqueued priority hint is bound to exactly the session selected at
|
|
5707
|
+
// submission. A later /new, /renew, thread switch or missing provider
|
|
5708
|
+
// file must never silently resume a stale/different backend session.
|
|
5709
|
+
const current = await this.observerTargetSession({
|
|
5710
|
+
channelKey: bound.channel, channelId: bound.channelId,
|
|
5711
|
+
chatType: bound.chatType, threadId: bound.threadId,
|
|
5712
|
+
selfAid: bound.selfAID,
|
|
5713
|
+
});
|
|
5714
|
+
if (current?.id !== bound.id || current.agentSessionId !== bound.agentSessionId) {
|
|
5715
|
+
throw new Error('Observer hint target session is no longer available');
|
|
5716
|
+
}
|
|
5717
|
+
bound.identity = this.inferDirectSessionIdentity(message, this.agentRegistry?.resolveByChannel(bound.channel));
|
|
5718
|
+
return { session: bound, absoluteProjectPath: path.isAbsolute(bound.projectPath)
|
|
5719
|
+
? bound.projectPath : path.resolve(process.cwd(), bound.projectPath) };
|
|
5720
|
+
}
|
|
5234
5721
|
// 话题会话创建时写入创建者和 replyContext(threadId 路由);主会话不写(避免群聊覆盖)
|
|
5235
5722
|
const metadata = message.threadId
|
|
5236
5723
|
? {
|
|
@@ -5290,7 +5777,7 @@ export class ResponseEngine {
|
|
|
5290
5777
|
logger.warn(`[ResponseEngine] Bound session ${boundSessionId} not found, falling back to latest`);
|
|
5291
5778
|
}
|
|
5292
5779
|
}
|
|
5293
|
-
const session = await this.sessionManager.getOrCreateSession(message.channel, message.channelId, projectPath, message.threadId, metadata, message.topicName, message.peerId, message.chatType, resolvedBaseagent, message.selfAID, message.channelType, message.peerType, resolvedIdentity, message.lable);
|
|
5780
|
+
const session = await this.sessionManager.getOrCreateSession(message.channel, message.channelId, projectPath, message.threadId, metadata, message.topicName, message.peerId, message.chatType, resolvedBaseagent, message.selfAID, message.channelType, message.peerType, resolvedIdentity, message.label ?? message.lable, message.topicStatus);
|
|
5294
5781
|
this.ensureSessionBaseagent(session, resolvedBaseagent);
|
|
5295
5782
|
// 群名解析:群会话首次取群显示名(group.get),缓存到 metadata,供信封渲染。
|
|
5296
5783
|
// 渠道私有方法 getGroupName 自带进程缓存 + 容错;取不到不阻塞(groupName 保持空,模板回退 groupId)。
|
|
@@ -5417,16 +5904,14 @@ export class ResponseEngine {
|
|
|
5417
5904
|
event = normalizeCompleteAgentEvent(event);
|
|
5418
5905
|
}
|
|
5419
5906
|
if (event.type === 'tool_result' && event.isError) {
|
|
5420
|
-
const
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
event = { ...event, errorCode, ...(classificationMissing ? { classificationMissing: true } : {}) };
|
|
5429
|
-
}
|
|
5907
|
+
const classification = normalizeToolErrorClassification(event);
|
|
5908
|
+
event = {
|
|
5909
|
+
...event,
|
|
5910
|
+
errorCode: classification.errorCode,
|
|
5911
|
+
errorDetailCode: classification.errorDetailCode,
|
|
5912
|
+
classificationMissing: classification.classificationMissing || event.classificationMissing,
|
|
5913
|
+
classificationConflict: classification.classificationConflict || event.classificationConflict,
|
|
5914
|
+
};
|
|
5430
5915
|
}
|
|
5431
5916
|
// 每收到事件重置空闲超时
|
|
5432
5917
|
const toolName = event.type === 'tool_use' ? event.name : undefined;
|
|
@@ -5804,7 +6289,7 @@ export class ResponseEngine {
|
|
|
5804
6289
|
if (!shouldSuppress()) {
|
|
5805
6290
|
if (event.callId)
|
|
5806
6291
|
toolDescByCallId.set(event.callId, desc);
|
|
5807
|
-
renderer.addToolCall(event.name, event.input, event.callId, desc, event.turn, event.outputTokens);
|
|
6292
|
+
renderer.addToolCall(event.name, event.input, event.callId, desc, event.turn, event.outputTokens, event.assistantUuid);
|
|
5808
6293
|
}
|
|
5809
6294
|
if (event.callId && isCtlQueueReadCommand(event.name, event.input || {})) {
|
|
5810
6295
|
ctlQueueReadCallIds.add(event.callId);
|
|
@@ -5815,6 +6300,8 @@ export class ResponseEngine {
|
|
|
5815
6300
|
modeHooks.mode.onToolUse({
|
|
5816
6301
|
session,
|
|
5817
6302
|
state: modeHooks.state,
|
|
6303
|
+
sessionState: modeHooks.sessionState,
|
|
6304
|
+
turn: turnLease,
|
|
5818
6305
|
toolName: event.name,
|
|
5819
6306
|
toolInput: event.input || {},
|
|
5820
6307
|
injectToModel: (text) => { agent.injectUserMessage?.(session.id, text); },
|
|
@@ -5863,14 +6350,12 @@ export class ResponseEngine {
|
|
|
5863
6350
|
toolName: event.name,
|
|
5864
6351
|
isError: event.isError,
|
|
5865
6352
|
...(event.isError ? {
|
|
5866
|
-
errorCode:
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
}),
|
|
5870
|
-
...((event.classificationMissing || (!normalizeToolErrorCode(event.errorCode)
|
|
5871
|
-
&& event.decisionSource !== 'policy'
|
|
5872
|
-
&& event.decisionSource !== 'approval')) ? { classificationMissing: true } : {}),
|
|
6353
|
+
errorCode: event.errorCode,
|
|
6354
|
+
...(event.errorDetailCode ? { errorDetailCode: event.errorDetailCode } : {}),
|
|
6355
|
+
...(event.classificationMissing ? { classificationMissing: true } : {}),
|
|
6356
|
+
...(event.classificationConflict ? { classificationConflict: true } : {}),
|
|
5873
6357
|
} : {}),
|
|
6358
|
+
...(event.exitCode !== undefined ? { exitCode: event.exitCode } : {}),
|
|
5874
6359
|
...(event.callId ? { callId: event.callId } : {}),
|
|
5875
6360
|
...(event.correlationId || event.callId ? { correlationId: event.correlationId ?? event.callId } : {}),
|
|
5876
6361
|
...(event.requestId ? { requestId: event.requestId } : {}),
|
|
@@ -5926,6 +6411,8 @@ export class ResponseEngine {
|
|
|
5926
6411
|
await modeHooks.mode.onToolResult({
|
|
5927
6412
|
session,
|
|
5928
6413
|
state: modeHooks.state,
|
|
6414
|
+
sessionState: modeHooks.sessionState,
|
|
6415
|
+
turn: turnLease,
|
|
5929
6416
|
toolName: original.name,
|
|
5930
6417
|
toolInput: original.input,
|
|
5931
6418
|
result: event.result,
|
|
@@ -5960,10 +6447,10 @@ export class ResponseEngine {
|
|
|
5960
6447
|
let errorMsg = event.error || (typeof event.result === 'string' ? event.result : JSON.stringify(event.result)) || '\u6267\u884c\u5931\u8d25';
|
|
5961
6448
|
// 移除 XML 风格的错误标签
|
|
5962
6449
|
errorMsg = errorMsg.replace(/<tool_use_error>(.*?)<\/tool_use_error>/gs, '$1');
|
|
5963
|
-
renderer.addToolResult(event.name || '\u5de5\u5177', false, undefined, errorMsg, event.callId, event.durationMs, cachedDesc, event);
|
|
6450
|
+
renderer.addToolResult(event.name || '\u5de5\u5177', false, undefined, errorMsg, event.callId, event.durationMs, cachedDesc, event, event.assistantUuid);
|
|
5964
6451
|
}
|
|
5965
6452
|
else if (!event.isError && !shouldSuppress()) {
|
|
5966
|
-
renderer.addToolResult(event.name || '\u5de5\u5177', true, event.result, undefined, event.callId, event.durationMs, cachedDesc);
|
|
6453
|
+
renderer.addToolResult(event.name || '\u5de5\u5177', true, event.result, undefined, event.callId, event.durationMs, cachedDesc, undefined, event.assistantUuid);
|
|
5967
6454
|
}
|
|
5968
6455
|
}
|
|
5969
6456
|
// 非终态运行时告警走 status notify,不进入 activity/thought 历史。
|