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.
Files changed (183) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +19 -9
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +6 -2
  7. package/dist/agents/codex/diagnostics.js +4 -3
  8. package/dist/agents/codex-runner.js +396 -95
  9. package/dist/agents/gemini-runner.js +35 -17
  10. package/dist/aun/group/history.js +130 -90
  11. package/dist/aun/group/operations.js +1 -3
  12. package/dist/aun/menu/transport.js +5 -3
  13. package/dist/aun/msg/managed-operation.js +70 -0
  14. package/dist/aun/msg/p2p.js +1 -3
  15. package/dist/aun/msg/upload.js +2 -2
  16. package/dist/aun/outbox.js +1 -37
  17. package/dist/channels/aun.js +249 -89
  18. package/dist/channels/daemon.js +18 -1
  19. package/dist/channels/feishu.js +1 -2
  20. package/dist/cli/agent-command.js +23 -3
  21. package/dist/cli/agent-create.js +68 -2
  22. package/dist/cli/agent.js +12 -2
  23. package/dist/cli/aun-commands.js +87 -13
  24. package/dist/cli/cli-argv.js +26 -0
  25. package/dist/cli/daemon-commands.js +40 -10
  26. package/dist/cli/data-command.js +4 -4
  27. package/dist/cli/handoff-command.js +2 -0
  28. package/dist/cli/index.js +18 -1
  29. package/dist/cli/managed-command-guard.js +10 -0
  30. package/dist/cli/memory-command.js +441 -0
  31. package/dist/cli/restart-monitor.js +62 -6
  32. package/dist/cli/skill-command.js +41 -0
  33. package/dist/cli/task-context.js +2 -23
  34. package/dist/cli/trigger-command.js +9 -1
  35. package/dist/cli/watch-msg-diag.js +209 -0
  36. package/dist/config/config-field-policy.js +52 -3
  37. package/dist/config/config-manager.js +17 -4
  38. package/dist/config/contact-book-store.js +2 -2
  39. package/dist/config/mention-mode.js +7 -2
  40. package/dist/config/merge.js +28 -9
  41. package/dist/config/resolved-config-op.js +1 -1
  42. package/dist/config/runtime-env.js +137 -0
  43. package/dist/config/schema-registry.js +11 -1
  44. package/dist/config-store.js +10 -10
  45. package/dist/core/audit/event-key.js +3 -1
  46. package/dist/core/audit/log-integrity.js +4 -0
  47. package/dist/core/auth/agent-delegation.js +2 -1
  48. package/dist/core/auth/auth-gateway.js +13 -6
  49. package/dist/core/auth/authorization-audit.js +54 -19
  50. package/dist/core/auth/operation-authorizer.js +17 -0
  51. package/dist/core/auth/operation-catalog.js +9 -0
  52. package/dist/core/causation/context.js +5 -1
  53. package/dist/core/command/agent-control.js +1 -2
  54. package/dist/core/command/command-handler.js +51 -23
  55. package/dist/core/command/memory-slash.js +270 -0
  56. package/dist/core/command/menu-catalog.js +1 -0
  57. package/dist/core/command/menu-handler.js +315 -60
  58. package/dist/core/command/menu-protocol.js +42 -4
  59. package/dist/core/command/slash-gate.js +10 -10
  60. package/dist/core/command/slash-handler.js +206 -64
  61. package/dist/core/data-migration.js +282 -505
  62. package/dist/core/event-catalog.js +26 -0
  63. package/dist/core/evolagent-registry.js +6 -0
  64. package/dist/core/evolagent.js +0 -10
  65. package/dist/core/handoff/store.js +1 -20
  66. package/dist/core/inference/text-inference.js +57 -1
  67. package/dist/core/memory/consolidation-context.js +505 -0
  68. package/dist/core/memory/consolidation-ops-records.js +124 -0
  69. package/dist/core/memory/consolidation-trigger.js +394 -0
  70. package/dist/core/memory/index-manager.js +308 -0
  71. package/dist/core/memory/launch-consolidation.js +395 -0
  72. package/dist/core/memory/memory-admin.js +234 -0
  73. package/dist/core/memory/memory-document.js +100 -0
  74. package/dist/core/memory/memory-session-policy.js +41 -0
  75. package/dist/core/memory/memory-signature.js +257 -0
  76. package/dist/core/memory/memory-writer.js +432 -0
  77. package/dist/core/memory/staleness.js +117 -0
  78. package/dist/core/message/auto-mention-gate.js +454 -0
  79. package/dist/core/message/im-renderer.js +69 -16
  80. package/dist/core/message/message-bridge.js +287 -21
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +114 -27
  83. package/dist/core/message/pending-hints.js +329 -161
  84. package/dist/core/message/response-engine.js +567 -80
  85. package/dist/core/permission/ec-command-parser.js +1029 -80
  86. package/dist/core/permission/protected-paths.js +376 -126
  87. package/dist/core/permission/sandbox-runtime.js +40 -12
  88. package/dist/core/permission/tool-error-code.js +96 -13
  89. package/dist/core/permission/tool-policy.js +276 -23
  90. package/dist/core/role/runtime-policy.js +3 -7
  91. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  92. package/dist/core/session/session-fs-store.js +0 -76
  93. package/dist/core/session/session-idle-monitor.js +355 -0
  94. package/dist/core/session/session-manager.js +478 -17
  95. package/dist/core/session/session-mapper.js +33 -16
  96. package/dist/core/session/session-renew.js +217 -155
  97. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  98. package/dist/core/skill/global-watcher.js +282 -0
  99. package/dist/core/skill/index-generator.js +84 -0
  100. package/dist/core/skill/startup.js +73 -0
  101. package/dist/core/skill/switch-resolver.js +115 -0
  102. package/dist/core/task/task-activity-store.js +219 -0
  103. package/dist/eck/kit-renderer.js +30 -3
  104. package/dist/eck/manifest-engine.js +26 -4
  105. package/dist/index.js +708 -18
  106. package/dist/ipc.js +564 -2
  107. package/dist/paths.js +30 -9
  108. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  109. package/dist/response-system/modes/single-session/index.js +3 -0
  110. package/dist/trigger/feedback.js +1 -0
  111. package/dist/trigger/manager.js +0 -10
  112. package/dist/trigger/scheduler.js +11 -0
  113. package/dist/trigger/validation.js +22 -0
  114. package/dist/utils/avatar-upload.js +59 -18
  115. package/dist/utils/error-dict.json +6 -3
  116. package/dist/utils/error-utils.js +27 -0
  117. package/dist/utils/logger.js +32 -10
  118. package/dist/utils/media-cache.js +84 -0
  119. package/dist/utils/msg-diag.js +97 -0
  120. package/dist/utils/welcome.js +37 -8
  121. package/kits/docs/AUTHORING.md +314 -0
  122. package/kits/docs/GUIDE.md +2 -0
  123. package/kits/docs/INDEX.md +2 -0
  124. package/kits/docs/aun/CHEATSHEET.md +4 -0
  125. package/kits/docs/aun/SDK.md +60 -0
  126. package/kits/docs/context-assembly.md +10 -6
  127. package/kits/docs/evolcore/INDEX.md +17 -3
  128. package/kits/docs/evolcore/agent-create.md +24 -0
  129. package/kits/docs/evolcore/agent.md +5 -2
  130. package/kits/docs/evolcore/config.md +1 -1
  131. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  132. package/kits/docs/evolcore/group.md +34 -18
  133. package/kits/docs/evolcore/memory-architecture.md +78 -0
  134. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  135. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  136. package/kits/docs/evolcore/memory-tags.md +63 -0
  137. package/kits/docs/evolcore/memory.md +110 -0
  138. package/kits/docs/evolcore/menu-client.md +27 -11
  139. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  140. package/kits/docs/evolcore/menu-protocol.md +213 -0
  141. package/kits/docs/evolcore/msg.md +2 -0
  142. package/kits/docs/evolcore/trigger.md +7 -5
  143. package/kits/docs/path-registry.md +6 -8
  144. package/kits/docs/prompt-loading-architecture.md +5 -3
  145. package/kits/docs/venues/group.md +5 -4
  146. package/kits/eck_manifest.json +294 -88
  147. package/kits/eck_manifest.memory.json +257 -0
  148. package/kits/rules/03-identity.md +8 -8
  149. package/kits/rules/05-venue.md +4 -2
  150. package/kits/rules/06-channel.md +1 -1
  151. package/kits/schemas/_meta.json +276 -47
  152. package/kits/schemas/agent-config.schema.12.json +38 -2
  153. package/kits/schemas/agent-config.schema.13.json +591 -0
  154. package/kits/schemas/daemon.schema.6.json +9 -9
  155. package/kits/schemas/defaults.schema.5.json +29 -0
  156. package/kits/schemas/relation-config.schema.8.json +14 -1
  157. package/kits/schemas/relation-config.schema.9.json +129 -0
  158. package/kits/schemas/role-config.schema.1.json +3 -3
  159. package/kits/skills/.switches.json +3 -0
  160. package/kits/skills/ec-skill/SKILL.md +217 -0
  161. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  162. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  163. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  164. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  165. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  166. package/kits/templates/decision/auto-mention.md +25 -0
  167. package/kits/templates/decision/session-renew.md +22 -0
  168. package/kits/templates/memory-consolidation/forget.md +104 -0
  169. package/kits/templates/memory-consolidation/generalize.md +76 -0
  170. package/kits/templates/memory-consolidation/notes.md +133 -0
  171. package/kits/templates/memory-consolidation/profile.md +58 -0
  172. package/kits/templates/memory-consolidation/promote.md +72 -0
  173. package/kits/templates/memory-consolidation/workflow.md +75 -0
  174. package/kits/templates/system-fragments/bootstrap.md +14 -5
  175. package/kits/templates/system-fragments/commands.md +1 -0
  176. package/kits/templates/system-fragments/group-history.md +6 -0
  177. package/kits/templates/system-fragments/identity.md +0 -3
  178. package/kits/templates/system-fragments/memory.md +79 -0
  179. package/kits/templates/system-fragments/reply.md +1 -0
  180. package/kits/templates/system-fragments/venue.md +1 -1
  181. package/package.json +4 -6
  182. package/dist/trigger/legacy-session-history.js +0 -19
  183. 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 { consumeHints, hintsToSubMessages, composeHintFallback } from './pending-hints.js';
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 { classifyToolErrorCode, normalizeToolErrorCode } from '../permission/tool-error-code.js';
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 { isFullAccessEnabled, loadDaemonConfig } from '../../config-store.js';
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 hints = consumeHints(resolvePaths().sessionsDir, 'aun', peerChannelId, selfAID, session.threadId);
1415
- if (hints.length === 0)
1416
- return [];
1417
- logger.info(`[ResponseEngine] consumed ${hints.length} owner-hint(s) for ${peerChannelId} thread=${session.threadId || 'main'}`);
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 the daemon gate and persisted owner
1599
- * provenance still validate. This is intentionally stricter than merely
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 isFullAccessEnabled()
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
- * daemon gate, approving owner, and runner profile are live policy. Check
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 = isFullAccessEnabled()
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 = authorizationIdentity?.role
2501
- ?? (peerRoleDetail
2502
- ? (peerRoleDetail.effectiveRole ?? 'none')
2503
- : (session.identity?.role ?? 'none'));
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
- turnLease = await this.turnCoordinator.begin(session, taskId, undefined, configActorId ? {
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 = message.replyContext?.metadata?.encrypted != null ? !!(message.replyContext.metadata.encrypted) : undefined;
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.title = '\u2705 \u6700\u7ec8\u56de\u590d:';
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 pureSessionPolicyHook = runModeConfig?.policyHook;
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 and working.md into the system prompt.
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
- proactiveFirstSendRequired: proactive?.firstSendRequired ?? false,
4052
+ // 明确续跑已表态时,提示词和运行时门禁都不再要求重复开场。
4053
+ proactiveFirstSendRequired: proactive?.firstSendRequired === true && !proactive.firstToolDone,
3709
4054
  proactiveToolReportRequired: proactive?.toolReportRequired ?? false,
3710
- proactiveToolReportInterval: proactive?.toolReportInterval ?? 10,
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
- // 按会话原型(sessionType)选 manifest 文件:config.sessionManifests 映射,缺省回退主 manifest。
3727
- const sessionType = session.sessionType ?? 'main';
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
- ? 'eck_manifest.json'
3734
- : sessionManifests?.[sessionType] ?? 'eck_manifest.json';
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
- // 观察者插话(v0.3):消费 (对端, thread) 的待用提示,包成 owner-hint item 排在对端消息前。
3799
- // 一次性语义:consumeOwnerHints 读取并删除(见 pending-hints.ts)。在 try 外消费,
3800
- // 这样即便 renderMessageBody 抛错走 raw 兜底,也把提示原文拼进去——绝不静默丢提示。
3801
- const hintItems = this.consumeOwnerHints(session, message);
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, using raw content: ${e instanceof Error ? e.message : String(e)}`);
3843
- effectivePrompt = wrapPrompt(composeHintFallback(hintItems, fallbackContent));
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 stream = await agent.runQuery(session.id, effectivePrompt, absoluteProjectPath, runAgentSessionId, renderResult?.images.length ? renderResult.images : message.images, effectiveSystemPrompt, this.sessionManager, modelOverride, runtimeEnv);
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 ? { mode: resolvedMode.mode, state: modeState } : undefined, taskCausation, turnLease, effectivePermissionMode, selfAid, async (request) => {
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 classificationMissing = !normalizeToolErrorCode(event.errorCode)
5421
- && event.decisionSource !== 'policy'
5422
- && event.decisionSource !== 'approval';
5423
- const errorCode = classifyToolErrorCode({
5424
- errorCode: event.errorCode,
5425
- decisionSource: event.decisionSource,
5426
- });
5427
- if (event.errorCode !== errorCode) {
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: classifyToolErrorCode({
5867
- errorCode: event.errorCode,
5868
- decisionSource: event.decisionSource,
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 历史。