evolcore 0.0.11 → 0.0.13
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 +44 -0
- package/bin/codex-managed-hook.mjs +80 -0
- package/bin/ec-safe-output.js +26 -7
- package/dist/agents/claude-runner.js +86 -7
- package/dist/agents/codex-app-server-client.js +174 -20
- package/dist/agents/codex-runner.js +375 -92
- package/dist/agents/ecagent-runner.js +39 -4
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +91 -44
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +865 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +655 -240
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +56 -8
- package/dist/cli/agent.js +108 -31
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +70 -55
- package/dist/cli/fs-command.js +79 -10
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +30 -2
- package/dist/cli/init.js +77 -8
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +53 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +7 -32
- package/dist/cli/trigger-command.js +11 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +9 -0
- package/dist/config/config-manager.js +21 -1
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/mention-mode.js +8 -24
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -2
- package/dist/core/auth/auth-gateway.js +29 -1
- package/dist/core/auth/authorization-audit.js +15 -0
- package/dist/core/auth/operation-authorizer.js +31 -7
- package/dist/core/auth/operation-catalog.js +196 -25
- package/dist/core/bootstrap-messages.js +25 -0
- package/dist/core/bootstrap-service.js +64 -4
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +1 -1
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +650 -42
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +370 -0
- package/dist/core/command/menu-handler.js +230 -51
- package/dist/core/command/menu-protocol.js +94 -2
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +99 -31
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/event-catalog.js +6 -0
- package/dist/core/evolagent.js +4 -2
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +157 -22
- package/dist/core/handoff/store.js +29 -1
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +19 -0
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +298 -88
- package/dist/core/message/message-log.js +3 -0
- package/dist/core/message/message-queue.js +268 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +161 -77
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +243 -18
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +43 -1
- package/dist/core/permission/tool-policy.js +322 -57
- package/dist/core/protected-paths.js +114 -4
- package/dist/core/session/session-manager.js +42 -4
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +541 -180
- package/dist/ipc.js +232 -10
- package/dist/paths.js +3 -0
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +10 -0
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +27 -0
- package/kits/templates/roles/member.json +34 -1
- package/kits/templates/roles/visitor.json +35 -3
- package/kits/templates/system-fragments/bootstrap.md +1 -1
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +2 -2
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/command/evol-menu-version-gate.js +0 -39
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
|
@@ -11,7 +11,7 @@ import { StreamIdleMonitor } from './stream-idle-monitor.js';
|
|
|
11
11
|
import { logger } from '../../utils/logger.js';
|
|
12
12
|
import { isHostChinese } from '../../utils/locale.js';
|
|
13
13
|
import { getErrorMessage, classifyError, ErrorType, ERROR_PREFIX, isInfraError, isAbortTerminalReason, prefixErrorType, isRetryableError, isContextTooLongText } from '../../utils/error-utils.js';
|
|
14
|
-
import {
|
|
14
|
+
import { isExactEvolcoreSendCommandForSession } from '../permission/ec-command-parser.js';
|
|
15
15
|
import { summarizeToolInput } from '../../utils/tool-summary.js';
|
|
16
16
|
import { getPackageRoot, resolveRoot, resolvePaths } from '../../paths.js';
|
|
17
17
|
import { renderKitSections } from '../../eck/kit-renderer.js';
|
|
@@ -22,7 +22,7 @@ import { createRootCausation, deriveCausation, normalizeCausation } from '../cau
|
|
|
22
22
|
import { recordCausationLink, recordCausationSpan } from '../causation/audit.js';
|
|
23
23
|
import { normalizeBaseagent } from '../../agents/baseagent.js';
|
|
24
24
|
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
25
|
-
import { buildEnvelope } from './message-utils.js';
|
|
25
|
+
import { buildEnvelope, replyContextFromSession, withEnvelopeReplyContext } from './message-utils.js';
|
|
26
26
|
import { isSystemOrServicePeer, resolveChatMode } from './peer-mode.js';
|
|
27
27
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
|
|
28
28
|
import { hasTrustedPrincipal } from '../auth/authenticated-actor.js';
|
|
@@ -33,7 +33,6 @@ import { eligibleFallbackModels } from '../model/model-fallback.js';
|
|
|
33
33
|
import { RetryScheduler } from './retry-scheduler.js';
|
|
34
34
|
import { constrainRuntimePermissionMode, resolveRuntimeStringField } from '../role/runtime-policy.js';
|
|
35
35
|
import { resolveEffective, resolveEffectiveFieldWithSource } from '../../config/config-manager.js';
|
|
36
|
-
import { dispatchToMentionMode } from '../../config/mention-mode.js';
|
|
37
36
|
import { authorizationConfigRevision, checkRoleAccess, getFirstStaticAgentOwner, listStaticAgentAdmins, listStaticAgentOwners, resolvePeerRoleDetail, roleToSessionIdentity, } from '../../config/peer-role-resolver.js';
|
|
38
37
|
import { insertUsageEvent, insertContextBreakdown, insertModelCalls } from '../../stats/writer.js';
|
|
39
38
|
import { normalizeUsage } from '../../stats/normalizer.js';
|
|
@@ -137,6 +136,7 @@ const RETRY_EXHAUSTED_MARK = Symbol('evolcore.retryExhausted');
|
|
|
137
136
|
const RETRY_MADE_PROGRESS_MARK = Symbol('evolcore.retryMadeProgress');
|
|
138
137
|
const RETRY_HEALTH_RECORDED_MARK = Symbol('evolcore.retryHealthRecorded');
|
|
139
138
|
const MODEL_FALLBACK_EXHAUSTED_MARK = Symbol('evolcore.modelFallbackExhausted');
|
|
139
|
+
const RETRY_INPUT_ALREADY_SUBMITTED_MARK = Symbol('evolcore.retryInputAlreadySubmitted');
|
|
140
140
|
function markRetryExhausted(error, retries) {
|
|
141
141
|
if (error && typeof error === 'object') {
|
|
142
142
|
error[RETRY_EXHAUSTED_MARK] = retries;
|
|
@@ -177,6 +177,21 @@ function getModelFallbackExhaustedMessage(error) {
|
|
|
177
177
|
: undefined;
|
|
178
178
|
return typeof message === 'string' ? message : undefined;
|
|
179
179
|
}
|
|
180
|
+
function markRetryInputAlreadySubmitted(error) {
|
|
181
|
+
if (error && typeof error === 'object') {
|
|
182
|
+
error[RETRY_INPUT_ALREADY_SUBMITTED_MARK] = true;
|
|
183
|
+
// A terminal delivery guard, rather than any buffered activity, owns the
|
|
184
|
+
// final user-visible explanation.
|
|
185
|
+
delete error._errorAlreadySent;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function wasRetryInputAlreadySubmitted(error) {
|
|
189
|
+
return !!(error && typeof error === 'object' && error[RETRY_INPUT_ALREADY_SUBMITTED_MARK] === true);
|
|
190
|
+
}
|
|
191
|
+
function formatRetryInputAlreadySubmittedMessage(error) {
|
|
192
|
+
const reason = getErrorMessage(error, undefined, false) || 'Codex 暂时不可用';
|
|
193
|
+
return `⚠️ Codex 已接收本次消息,但本轮未能确认完成。为避免在会话中重复写入同一消息,系统未自动重试。请检查会话状态后重新发送。\n原因:${reason}`;
|
|
194
|
+
}
|
|
180
195
|
function isNonErrorInterruptReason(reason) {
|
|
181
196
|
return reason === 'new_message'
|
|
182
197
|
|| reason === 'stop'
|
|
@@ -493,7 +508,7 @@ export class ResponseEngine {
|
|
|
493
508
|
return;
|
|
494
509
|
const available = error.availableBaseagents.length ? error.availableBaseagents.join(', ') : '(none)';
|
|
495
510
|
const text = `❌ 当前会话绑定的 baseagent 不可用: ${error.baseagent}\nAgent: ${error.evolagentName}\n可用: ${available}\n请使用 /baseagent 切换到可用后端。`;
|
|
496
|
-
const sessionReplyContext = { ...(replyContext
|
|
511
|
+
const sessionReplyContext = { ...replyContextFromSession(session, replyContext), sessionId: session.id };
|
|
497
512
|
await channelInfo.adapter.send(buildEnvelope({
|
|
498
513
|
taskId: `system-${crypto.randomUUID().replace(/-/g, '').slice(0, 10)}`,
|
|
499
514
|
sessionId: session.id,
|
|
@@ -656,7 +671,7 @@ export class ResponseEngine {
|
|
|
656
671
|
agentName: agent.name,
|
|
657
672
|
chatmode: this.resolveEffectiveChatmodeForSession(session, channelKey, session.channelId),
|
|
658
673
|
replyContext: {
|
|
659
|
-
...(session
|
|
674
|
+
...replyContextFromSession(session),
|
|
660
675
|
sessionId: session.id,
|
|
661
676
|
threadId: session.threadId || session.metadata?.replyContext?.threadId,
|
|
662
677
|
},
|
|
@@ -727,7 +742,19 @@ export class ResponseEngine {
|
|
|
727
742
|
const selfAid = runtime?.selfAid || session?.selfAID;
|
|
728
743
|
if (!selfAid)
|
|
729
744
|
return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'self agent not found' };
|
|
730
|
-
return this.
|
|
745
|
+
return this.returnAuthorizedHandoffResult({
|
|
746
|
+
sessionId: params.sessionId,
|
|
747
|
+
handoffId: params.handoffId,
|
|
748
|
+
content: params.content,
|
|
749
|
+
}, selfAid);
|
|
750
|
+
}
|
|
751
|
+
/** Execute a handoff return after the daemon IPC boundary has authorized the task delegation. */
|
|
752
|
+
async returnAuthorizedHandoffResult(params, selfAid) {
|
|
753
|
+
if (!this.handoffRuntime) {
|
|
754
|
+
return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff runtime not configured' };
|
|
755
|
+
}
|
|
756
|
+
const runtime = this.activeTaskRuntimeContexts.get(params.sessionId);
|
|
757
|
+
return await this.handoffRuntime.returnHandoff({
|
|
731
758
|
selfAid,
|
|
732
759
|
currentSessionId: params.sessionId,
|
|
733
760
|
handoffId: params.handoffId,
|
|
@@ -926,7 +953,7 @@ export class ResponseEngine {
|
|
|
926
953
|
}
|
|
927
954
|
agent.registerStream(streamKey, retryStream);
|
|
928
955
|
return await this.processEventStream(retryStream, session, agent, renderer, resetTimer, shouldSuppress, proactive, undefined, // 重试分支不调插件钩子
|
|
929
|
-
undefined, turnLease);
|
|
956
|
+
undefined, turnLease, opts.permissionMode);
|
|
930
957
|
}
|
|
931
958
|
// Dropping the whole session hides the root cause and can replay side
|
|
932
959
|
// effects in unattended trigger tasks. Surface an actionable failure
|
|
@@ -1152,6 +1179,14 @@ export class ResponseEngine {
|
|
|
1152
1179
|
async processMessage(message) {
|
|
1153
1180
|
const idleMs = (this.globalSettings.idleMonitor?.timeout ?? 120) * 1000;
|
|
1154
1181
|
const totalExecutionMs = this.totalExecutionLimitMs();
|
|
1182
|
+
if (message.handoffDelivery
|
|
1183
|
+
&& this.handoffRuntime
|
|
1184
|
+
&& this.handoffRuntime.isDiscardedDelivery(message)) {
|
|
1185
|
+
logger.info(`[ResponseEngine] Skip stale handoff delivery before session resolution:`
|
|
1186
|
+
+ ` direction=${message.handoffDelivery.direction}`
|
|
1187
|
+
+ ` handoff=${message.handoffDelivery.handoffId}`);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1155
1190
|
// 先解析会话,再优先用 session.metadata.channelKey 精确定位实例级 adapter
|
|
1156
1191
|
// message.channel 现在存实例名(channelName),可直接用于精确路由
|
|
1157
1192
|
const { session, absoluteProjectPath } = await this.resolveSession(message);
|
|
@@ -1180,7 +1215,7 @@ export class ResponseEngine {
|
|
|
1180
1215
|
logger.warn(`[ResponseEngine] Trigger execution identity missing: trigger=${message.triggerMeta?.triggerId ?? '<none>'}`);
|
|
1181
1216
|
const channelInfo = this.resolveChannelInfo(accessChannelKey);
|
|
1182
1217
|
if (channelInfo) {
|
|
1183
|
-
await channelInfo.adapter.send({
|
|
1218
|
+
await channelInfo.adapter.send(buildEnvelope({
|
|
1184
1219
|
taskId: `trigger-identity-missing-${Date.now()}`,
|
|
1185
1220
|
sessionId: session.id,
|
|
1186
1221
|
channel: accessChannelKey,
|
|
@@ -1189,7 +1224,7 @@ export class ResponseEngine {
|
|
|
1189
1224
|
chatmode: 'interactive',
|
|
1190
1225
|
replyContext: message.replyContext,
|
|
1191
1226
|
timestamp: Date.now(),
|
|
1192
|
-
}, {
|
|
1227
|
+
}), {
|
|
1193
1228
|
kind: 'system.error',
|
|
1194
1229
|
text: 'Trigger 创建者身份缺失,已拒绝执行',
|
|
1195
1230
|
subtype: 'trigger_identity_missing',
|
|
@@ -1211,7 +1246,7 @@ export class ResponseEngine {
|
|
|
1211
1246
|
const channelInfo = this.resolveChannelInfo(channelKey);
|
|
1212
1247
|
if (channelInfo) {
|
|
1213
1248
|
try {
|
|
1214
|
-
await channelInfo.adapter.send({
|
|
1249
|
+
await channelInfo.adapter.send(buildEnvelope({
|
|
1215
1250
|
taskId: `access-denied-${Date.now()}`,
|
|
1216
1251
|
sessionId: session.id,
|
|
1217
1252
|
channel: channelKey,
|
|
@@ -1220,7 +1255,7 @@ export class ResponseEngine {
|
|
|
1220
1255
|
chatmode: 'interactive',
|
|
1221
1256
|
replyContext: message.replyContext,
|
|
1222
1257
|
timestamp: Date.now(),
|
|
1223
|
-
}, {
|
|
1258
|
+
}), {
|
|
1224
1259
|
kind: 'system.error',
|
|
1225
1260
|
text: '暂无权限访问本 agent,请联系 agent 管理员授权访问',
|
|
1226
1261
|
subtype: 'access_denied',
|
|
@@ -1478,7 +1513,12 @@ export class ResponseEngine {
|
|
|
1478
1513
|
}
|
|
1479
1514
|
/** 自动安全模式已禁用:仅保留错误计数,不再自动切换状态 */
|
|
1480
1515
|
async _processMessageInternal(message, session, absoluteProjectPath, resetTimer, shouldSuppress, getLastIdleSec, outputState = { middleOutputMode: 'all' }, timeoutControl) {
|
|
1481
|
-
|
|
1516
|
+
// The model must see the time at which the adapter accepted the input.
|
|
1517
|
+
// Runner start is recorded separately so queue delay never rewrites input
|
|
1518
|
+
// chronology after a historical replay or a long backlog.
|
|
1519
|
+
const inboundTimestamp = message.receivedAt ?? message.enqueuedAt ?? message.timestamp ?? Date.now();
|
|
1520
|
+
const runnerStartedAt = Date.now();
|
|
1521
|
+
const messageId = `${message.channel}_${message.channelId}_${inboundTimestamp}`;
|
|
1482
1522
|
const channelKey = this.messageChannelKey(message, session);
|
|
1483
1523
|
const channelInfo = this.resolveChannelInfo(channelKey);
|
|
1484
1524
|
const taskAgentAid = message.selfAID || session.selfAID;
|
|
@@ -1705,7 +1745,7 @@ export class ResponseEngine {
|
|
|
1705
1745
|
const taskReplyContext = () => {
|
|
1706
1746
|
const base = this.getReplyContext(message);
|
|
1707
1747
|
return {
|
|
1708
|
-
...(base
|
|
1748
|
+
...replyContextFromSession(session, base),
|
|
1709
1749
|
sessionId: session.id,
|
|
1710
1750
|
threadId: session.threadId || base?.threadId,
|
|
1711
1751
|
metadata: withTaskMetadata(base?.metadata),
|
|
@@ -1720,11 +1760,11 @@ export class ResponseEngine {
|
|
|
1720
1760
|
messageId: message.messageId, peerId: message.peerId, content: message.content,
|
|
1721
1761
|
peerType: message.peerType || session.metadata?.peerType,
|
|
1722
1762
|
chatType: chatType, isMentioned: message.isMentioned,
|
|
1723
|
-
mentionAids: message.mentionAids, source: message.source,
|
|
1763
|
+
mentions: message.mentions, mentionAids: message.mentionAids, source: message.source,
|
|
1724
1764
|
},
|
|
1725
1765
|
modeConfig: resolvedMode.context.modeConfig,
|
|
1726
1766
|
state: modeState,
|
|
1727
|
-
isSendCommand: (toolName, toolInput) =>
|
|
1767
|
+
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, chatType, session.selfAID || message.selfAID),
|
|
1728
1768
|
logger,
|
|
1729
1769
|
} : null;
|
|
1730
1770
|
if (resolvedMode?.mode.beforeProcess && modeProcessCtx) {
|
|
@@ -1825,7 +1865,7 @@ export class ResponseEngine {
|
|
|
1825
1865
|
// Kept outside the task body so a thrown terminal error can preserve the
|
|
1826
1866
|
// work already performed in its trigger audit metadata.
|
|
1827
1867
|
let streamResult = { isError: false, lastReplyText: '', fullText: '', hasReceivedText: false };
|
|
1828
|
-
let startTime =
|
|
1868
|
+
let startTime = runnerStartedAt;
|
|
1829
1869
|
let protocolReplayAttempts = 0;
|
|
1830
1870
|
try {
|
|
1831
1871
|
const isBackground = this.isBackgroundSession(session, message.channel, message.channelId);
|
|
@@ -1834,7 +1874,10 @@ export class ResponseEngine {
|
|
|
1834
1874
|
msgId: messageId,
|
|
1835
1875
|
sessionId: session.id,
|
|
1836
1876
|
dir: 'inbound',
|
|
1837
|
-
status: 'received'
|
|
1877
|
+
status: 'received',
|
|
1878
|
+
receivedAt: message.receivedAt ?? inboundTimestamp,
|
|
1879
|
+
enqueuedAt: message.enqueuedAt,
|
|
1880
|
+
processingStartedAt: runnerStartedAt,
|
|
1838
1881
|
});
|
|
1839
1882
|
this.eventBus.publish({
|
|
1840
1883
|
type: 'message:received',
|
|
@@ -1843,7 +1886,10 @@ export class ResponseEngine {
|
|
|
1843
1886
|
channelId: message.channelId,
|
|
1844
1887
|
content: message.content,
|
|
1845
1888
|
agentName: agentNameForStats,
|
|
1846
|
-
timestamp:
|
|
1889
|
+
timestamp: inboundTimestamp,
|
|
1890
|
+
receivedAt: message.receivedAt ?? inboundTimestamp,
|
|
1891
|
+
enqueuedAt: message.enqueuedAt,
|
|
1892
|
+
processingStartedAt: runnerStartedAt,
|
|
1847
1893
|
});
|
|
1848
1894
|
// ── 硬上限检查:超限直接返回提示,不调模型 ──
|
|
1849
1895
|
{
|
|
@@ -1988,7 +2034,7 @@ export class ResponseEngine {
|
|
|
1988
2034
|
opts.metadata = withTaskMetadata(opts.metadata);
|
|
1989
2035
|
if (payload.kind.startsWith('status.'))
|
|
1990
2036
|
this.touchAgentActivity(channelKey);
|
|
1991
|
-
const enrichedEnvelope =
|
|
2037
|
+
const enrichedEnvelope = withEnvelopeReplyContext(envelope, opts);
|
|
1992
2038
|
snapshot.pushOutbound(session.id, taskId, { kind: payload.kind, decision: 'sent' });
|
|
1993
2039
|
await adapter.send(enrichedEnvelope, payload);
|
|
1994
2040
|
},
|
|
@@ -2013,7 +2059,7 @@ export class ResponseEngine {
|
|
|
2013
2059
|
const capturedReplyContext = taskReplyContext();
|
|
2014
2060
|
// 设置权限审批的消息发送回调(指向当前渠道)
|
|
2015
2061
|
const permissionPrompt = async (text) => {
|
|
2016
|
-
await adapter.send(
|
|
2062
|
+
await adapter.send(withEnvelopeReplyContext(envelope, capturedReplyContext), { kind: 'result.text', text, isFinal: false });
|
|
2017
2063
|
};
|
|
2018
2064
|
const authChatType = configChatType;
|
|
2019
2065
|
const authConversationId = authChatType === 'group'
|
|
@@ -2027,15 +2073,16 @@ export class ResponseEngine {
|
|
|
2027
2073
|
: undefined;
|
|
2028
2074
|
const permissionAdapter = triggerOriginChannelInfo?.adapter ?? adapter;
|
|
2029
2075
|
const permissionChannelId = authorizationIdentity?.actorId ?? capturedChannelId;
|
|
2030
|
-
const permissionReplyContext = authorizationIdentity
|
|
2076
|
+
const permissionReplyContext = authorizationIdentity
|
|
2077
|
+
? { delivery: { chatType: 'private' } }
|
|
2078
|
+
: capturedReplyContext;
|
|
2031
2079
|
let permissionTerminalFailure;
|
|
2032
2080
|
const permissionPromptForOrigin = async (text) => {
|
|
2033
|
-
await permissionAdapter.send({
|
|
2081
|
+
await permissionAdapter.send(withEnvelopeReplyContext({
|
|
2034
2082
|
...envelope,
|
|
2035
2083
|
channel: authorizationIdentity?.originChannelKey ?? envelope.channel,
|
|
2036
2084
|
channelId: permissionChannelId,
|
|
2037
|
-
|
|
2038
|
-
}, { kind: 'result.text', text, isFinal: false });
|
|
2085
|
+
}, permissionReplyContext), { kind: 'result.text', text, isFinal: false });
|
|
2039
2086
|
};
|
|
2040
2087
|
agent.setSendPrompt(permissionPromptForOrigin);
|
|
2041
2088
|
const owningAgentForAuth = owningAgentForTask;
|
|
@@ -2118,11 +2165,21 @@ export class ResponseEngine {
|
|
|
2118
2165
|
},
|
|
2119
2166
|
};
|
|
2120
2167
|
})();
|
|
2168
|
+
let effectivePermissionMode = 'readonly';
|
|
2121
2169
|
const recordExecutionAnomaly = triggerRunId
|
|
2122
2170
|
? (anomaly) => {
|
|
2123
|
-
recordTriggerExecutionAnomaly(triggerRunId,
|
|
2171
|
+
recordTriggerExecutionAnomaly(triggerRunId, {
|
|
2172
|
+
...anomaly,
|
|
2173
|
+
agentAid: anomaly.agentAid ?? session.selfAID ?? message.selfAID,
|
|
2174
|
+
sessionId: anomaly.sessionId ?? session.id,
|
|
2175
|
+
permissionMode: anomaly.permissionMode ?? effectivePermissionMode,
|
|
2176
|
+
});
|
|
2124
2177
|
}
|
|
2125
2178
|
: undefined;
|
|
2179
|
+
const runModeConfig = resolvedMode?.mode.configureRun && modeProcessCtx
|
|
2180
|
+
? resolvedMode.mode.configureRun(modeProcessCtx)
|
|
2181
|
+
: undefined;
|
|
2182
|
+
const pureSessionPolicyHook = runModeConfig?.policyHook;
|
|
2126
2183
|
// 设置权限审批的交互上下文(支持交互卡片)
|
|
2127
2184
|
agent.setPermissionContext?.(session.id, {
|
|
2128
2185
|
sendPrompt: permissionPromptForOrigin,
|
|
@@ -2144,6 +2201,7 @@ export class ResponseEngine {
|
|
|
2144
2201
|
approvalRouting,
|
|
2145
2202
|
approvalInteractionPolicy: authorizationIdentity ? 'deny' : 'interactive',
|
|
2146
2203
|
recordExecutionAnomaly,
|
|
2204
|
+
preToolUsePolicyHook: pureSessionPolicyHook,
|
|
2147
2205
|
armApprovedDelegationCommand: this.agentDelegationRegistry
|
|
2148
2206
|
? (carrierToken, commandHash) => this.agentDelegationRegistry.armApprovedCommand({
|
|
2149
2207
|
carrierToken,
|
|
@@ -2170,7 +2228,7 @@ export class ResponseEngine {
|
|
|
2170
2228
|
},
|
|
2171
2229
|
},
|
|
2172
2230
|
permissionStateChanged: async (permissionEvent) => {
|
|
2173
|
-
const executionEnvelope =
|
|
2231
|
+
const executionEnvelope = withEnvelopeReplyContext(envelope, capturedReplyContext);
|
|
2174
2232
|
if (permissionEvent.state === 'waiting') {
|
|
2175
2233
|
const payload = {
|
|
2176
2234
|
kind: 'status.requires_action',
|
|
@@ -2184,12 +2242,11 @@ export class ResponseEngine {
|
|
|
2184
2242
|
};
|
|
2185
2243
|
await adapter.send(executionEnvelope, payload);
|
|
2186
2244
|
if (permissionAdapter !== adapter || permissionChannelId !== capturedChannelId) {
|
|
2187
|
-
await permissionAdapter.send({
|
|
2245
|
+
await permissionAdapter.send(withEnvelopeReplyContext({
|
|
2188
2246
|
...envelope,
|
|
2189
2247
|
channel: authorizationIdentity?.originChannelKey ?? envelope.channel,
|
|
2190
2248
|
channelId: permissionChannelId,
|
|
2191
|
-
|
|
2192
|
-
}, payload);
|
|
2249
|
+
}, permissionReplyContext), payload);
|
|
2193
2250
|
}
|
|
2194
2251
|
return;
|
|
2195
2252
|
}
|
|
@@ -2233,10 +2290,7 @@ export class ResponseEngine {
|
|
|
2233
2290
|
// [迁移点2] policyHook 由 ProactiveMode.configureRun 提供(插件决策);
|
|
2234
2291
|
// 引擎补充副作用:违规时注入提醒到模型上下文 + 行为探针。
|
|
2235
2292
|
policyHook: (() => {
|
|
2236
|
-
const
|
|
2237
|
-
? resolvedMode.mode.configureRun(modeProcessCtx)
|
|
2238
|
-
: undefined;
|
|
2239
|
-
const pluginHook = runConfig?.policyHook;
|
|
2293
|
+
const pluginHook = pureSessionPolicyHook;
|
|
2240
2294
|
if (!pluginHook)
|
|
2241
2295
|
return undefined;
|
|
2242
2296
|
return (toolName, toolInput) => {
|
|
@@ -2250,9 +2304,9 @@ export class ResponseEngine {
|
|
|
2250
2304
|
agent.injectUserMessage?.(session.id, errorMsg);
|
|
2251
2305
|
return result;
|
|
2252
2306
|
}
|
|
2253
|
-
//
|
|
2254
|
-
|
|
2255
|
-
if (wasFirstPending
|
|
2307
|
+
// 这里只记录审批结果。firstToolDone 必须等真实 tool_use 事件再推进,
|
|
2308
|
+
// 避免伪造 managed hook IPC 提前解除门禁。
|
|
2309
|
+
if (wasFirstPending) {
|
|
2256
2310
|
snapshot.set(session.id, taskId, { policyHook: { triggered: true, blocked: false, toolName } });
|
|
2257
2311
|
}
|
|
2258
2312
|
return undefined;
|
|
@@ -2290,8 +2344,6 @@ export class ResponseEngine {
|
|
|
2290
2344
|
let handoffPromptRendered = false;
|
|
2291
2345
|
let runtimeEnv;
|
|
2292
2346
|
try {
|
|
2293
|
-
// 动态构建运行时上下文提示
|
|
2294
|
-
const contextParts = [];
|
|
2295
2347
|
// 通道能力
|
|
2296
2348
|
const supportsFileMarker = currentChannelType !== 'aun' && !isProactive && !!channelInfo.adapter.capabilities?.file;
|
|
2297
2349
|
const capParts = [];
|
|
@@ -2301,14 +2353,11 @@ export class ResponseEngine {
|
|
|
2301
2353
|
capParts.push('图片输出');
|
|
2302
2354
|
if (!isProactive && channelInfo.adapter.capabilities?.file)
|
|
2303
2355
|
capParts.push('文件发送');
|
|
2304
|
-
// Personal
|
|
2356
|
+
// Personal state is exposed to the manifest as vars. The manifest owns
|
|
2357
|
+
// loading persona.md and working.md into the system prompt.
|
|
2305
2358
|
const owningAgent = owningAgentForTask;
|
|
2306
2359
|
const persona = owningAgent?.getPersona?.() || undefined;
|
|
2307
2360
|
const working = owningAgent?.getWorkingMemory?.() || undefined;
|
|
2308
|
-
if (persona)
|
|
2309
|
-
contextParts.push(persona);
|
|
2310
|
-
if (working)
|
|
2311
|
-
contextParts.push(`[当前关注]\n${working}`);
|
|
2312
2361
|
// 计算 peerKey:群聊固定按 groupId/channelId,私聊按发送者 peerId。
|
|
2313
2362
|
// 这样单条和积压合并批次不会因队列状态不同而切换关系级配置。
|
|
2314
2363
|
const normalizedBaseagent = normalizeBaseagent(agent.name);
|
|
@@ -2316,7 +2365,6 @@ export class ResponseEngine {
|
|
|
2316
2365
|
// Trigger 只能降低本次调用权限,不能突破当前角色的权限上限。
|
|
2317
2366
|
// 作为 per-call 入参随 modelOverride 传入 runQuery —— 与 model/effort 同构,
|
|
2318
2367
|
// 不写 AgentRunner 实例字段,多对端/多会话并发共享同一 runner 实例时互不污染。
|
|
2319
|
-
let effectivePermissionMode;
|
|
2320
2368
|
const triggerPermissionModeOverride = message.triggerMeta?.permissionModeOverride;
|
|
2321
2369
|
try {
|
|
2322
2370
|
effectivePermissionMode = constrainRuntimePermissionMode({
|
|
@@ -2532,9 +2580,10 @@ export class ResponseEngine {
|
|
|
2532
2580
|
channel: currentChannelType || null,
|
|
2533
2581
|
venueUid: undefined,
|
|
2534
2582
|
// 群 @ 处理模式 / 客户端类型 / 权限模式
|
|
2535
|
-
// 优先 agent/relation mentionMode 配置,fallback 到服务器
|
|
2583
|
+
// 优先 agent/relation mentionMode 配置,fallback 到服务器 mention_mode 缓存。
|
|
2536
2584
|
mentionMode: effectiveAgentConfig?.mentionMode
|
|
2537
|
-
??
|
|
2585
|
+
?? session.metadata?.mentionMode
|
|
2586
|
+
?? message.mentionMode
|
|
2538
2587
|
?? undefined,
|
|
2539
2588
|
clientType: message.clientType || undefined,
|
|
2540
2589
|
permissionMode: effectivePermissionMode,
|
|
@@ -2583,17 +2632,13 @@ export class ResponseEngine {
|
|
|
2583
2632
|
const sessionManifests = this.agentRegistry?.resolveByChannel(channelKey)?.config?.sessionManifests;
|
|
2584
2633
|
const manifestFile = sessionManifests?.[sessionType] ?? 'eck_manifest.json';
|
|
2585
2634
|
const kitContext = renderKitSections(kitCtx, manifestFile);
|
|
2586
|
-
|
|
2587
|
-
contextParts.push(kitContext);
|
|
2588
|
-
effectiveSystemPrompt = [options?.systemPromptAppend, ...contextParts].filter(Boolean).join('\n') || undefined;
|
|
2635
|
+
effectiveSystemPrompt = [options?.systemPromptAppend, kitContext].filter(Boolean).join('\n') || undefined;
|
|
2589
2636
|
// ── Stats: context_breakdown 旁路采集(各段估算 token 数,字符数/4 近似) ──
|
|
2590
2637
|
try {
|
|
2591
2638
|
const estTokens = (s) => s ? Math.ceil(s.length / 4) : 0;
|
|
2592
2639
|
const cbModel = effectiveModel || agentModel || 'unknown';
|
|
2593
2640
|
const cbMaxTokens = 200000; // 保守默认,后续可从 model-catalog 取
|
|
2594
2641
|
const systemPromptTokens = estTokens(options?.systemPromptAppend);
|
|
2595
|
-
const personaTokens = estTokens(persona);
|
|
2596
|
-
const workingTokens = estTokens(working);
|
|
2597
2642
|
const kitTokens = estTokens(kitContext);
|
|
2598
2643
|
const totalEst = estTokens(effectiveSystemPrompt);
|
|
2599
2644
|
insertContextBreakdown(resolveRoot(), {
|
|
@@ -2603,7 +2648,7 @@ export class ResponseEngine {
|
|
|
2603
2648
|
turn_count: 0, // 按 ts 排序得轮次
|
|
2604
2649
|
model: cbModel,
|
|
2605
2650
|
max_tokens: cbMaxTokens,
|
|
2606
|
-
system_prompt: systemPromptTokens
|
|
2651
|
+
system_prompt: systemPromptTokens,
|
|
2607
2652
|
system_tools: 0, // 工具 schema 不在此层,留 0(后续 runner 层补)
|
|
2608
2653
|
mcp_tools: 0,
|
|
2609
2654
|
custom_agents: 0,
|
|
@@ -2629,6 +2674,7 @@ export class ResponseEngine {
|
|
|
2629
2674
|
encrypted: message.encrypted,
|
|
2630
2675
|
content: message.content, timestamp: message.timestamp,
|
|
2631
2676
|
images: message.images,
|
|
2677
|
+
mentions: message.mentions,
|
|
2632
2678
|
mentionAids: message.mentionAids,
|
|
2633
2679
|
}];
|
|
2634
2680
|
const peerItems = (() => {
|
|
@@ -2758,6 +2804,7 @@ export class ResponseEngine {
|
|
|
2758
2804
|
? (agent.resolveModelId(model) ?? model)
|
|
2759
2805
|
: model));
|
|
2760
2806
|
const retryAttemptTimeoutMs = this.retryAttemptTimeoutMs();
|
|
2807
|
+
const retryInputIsAtMostOnce = agent.retryInputSemantics === 'at_most_once';
|
|
2761
2808
|
let runAttempt = 1;
|
|
2762
2809
|
const recordRetryHealthError = async (retryError) => {
|
|
2763
2810
|
if (!policy.accumulateErrors(chatType, identityRole))
|
|
@@ -2794,14 +2841,17 @@ export class ResponseEngine {
|
|
|
2794
2841
|
const processAttempt = this.processEventStream(stream, session, agent, renderer, (eventType, toolName) => {
|
|
2795
2842
|
resetTimer(eventType, toolName);
|
|
2796
2843
|
attemptTimeout?.reset();
|
|
2797
|
-
}, shouldSuppress, proactive, resolvedMode ? { mode: resolvedMode.mode, state: modeState } : undefined, taskCausation, turnLease);
|
|
2844
|
+
}, shouldSuppress, proactive, resolvedMode ? { mode: resolvedMode.mode, state: modeState } : undefined, taskCausation, turnLease, effectivePermissionMode);
|
|
2798
2845
|
streamResult = attemptTimeout
|
|
2799
2846
|
? await Promise.race([processAttempt, attemptTimeout.promise])
|
|
2800
2847
|
: await processAttempt;
|
|
2801
2848
|
if (!streamResult.isError) {
|
|
2802
2849
|
const protocolDecision = this.turnCoordinator.evaluateCommit(turnLease, this.buildTurnCommitEvidence(session, streamResult));
|
|
2803
2850
|
const protocolReason = protocolDecision.ok ? undefined : protocolDecision.reason;
|
|
2804
|
-
|
|
2851
|
+
// Codex app-server turn/start appends its input before runQuery
|
|
2852
|
+
// resolves. Replaying here would create a second user turn.
|
|
2853
|
+
const safeProtocolReplay = !retryInputIsAtMostOnce
|
|
2854
|
+
&& isSafeProtocolReplayCandidate(streamResult, protocolReason);
|
|
2805
2855
|
const protocolRetry = retryScheduler.afterProtocolFailure(safeProtocolReplay ? protocolReason : undefined, safeProtocolReplay);
|
|
2806
2856
|
if (protocolRetry.kind === 'protocol_replay') {
|
|
2807
2857
|
protocolReplayAttempts = protocolRetry.attempt;
|
|
@@ -2846,6 +2896,16 @@ export class ResponseEngine {
|
|
|
2846
2896
|
permissionError._errorAlreadySent = true;
|
|
2847
2897
|
throw permissionError;
|
|
2848
2898
|
}
|
|
2899
|
+
const retryWouldReplaySubmittedInput = retryAttemptTimedOut
|
|
2900
|
+
|| isRetryableError(retryError)
|
|
2901
|
+
|| classifyError(retryError) === ErrorType.MODEL_UNAVAILABLE;
|
|
2902
|
+
if (retryInputIsAtMostOnce && streamRegistered && retryWouldReplaySubmittedInput) {
|
|
2903
|
+
await recordRetryHealthError(retryError);
|
|
2904
|
+
markRetryInputAlreadySubmitted(retryError);
|
|
2905
|
+
logger.warn(`[ResponseEngine] Not replaying already-submitted input: agent=${agent.name} `
|
|
2906
|
+
+ `session=${session.id} task=${taskId} attempt=${runAttempt}`);
|
|
2907
|
+
throw retryError;
|
|
2908
|
+
}
|
|
2849
2909
|
if (classifyError(retryError) === ErrorType.MODEL_UNAVAILABLE && !hasExplicitTriggerModel) {
|
|
2850
2910
|
if (probingPrimaryModel && previousFallbackState) {
|
|
2851
2911
|
const resumedFallback = previousFallbackState.activeFallback;
|
|
@@ -2939,6 +2999,7 @@ export class ResponseEngine {
|
|
|
2939
2999
|
resetTimer,
|
|
2940
3000
|
shouldSuppress,
|
|
2941
3001
|
proactive,
|
|
3002
|
+
permissionMode: effectivePermissionMode,
|
|
2942
3003
|
turnLease,
|
|
2943
3004
|
});
|
|
2944
3005
|
}
|
|
@@ -2980,6 +3041,7 @@ export class ResponseEngine {
|
|
|
2980
3041
|
resetTimer,
|
|
2981
3042
|
shouldSuppress,
|
|
2982
3043
|
proactive,
|
|
3044
|
+
permissionMode: effectivePermissionMode,
|
|
2983
3045
|
turnLease,
|
|
2984
3046
|
});
|
|
2985
3047
|
// 重试后仍然 prompt_too_long:显示友好提示
|
|
@@ -3051,7 +3113,7 @@ export class ResponseEngine {
|
|
|
3051
3113
|
return;
|
|
3052
3114
|
const isCurrentlyBackground = this.isBackgroundSession(session, message.channel, message.channelId);
|
|
3053
3115
|
if (!isCurrentlyBackground) {
|
|
3054
|
-
await adapter.send(
|
|
3116
|
+
await adapter.send(withEnvelopeReplyContext(envelope, capturedReplyContext), payload);
|
|
3055
3117
|
if (payload.kind === 'result.text') {
|
|
3056
3118
|
logger.info(`[ResponseEngine] afterProcess sent: task=${taskId} text="${payload.text.slice(0, 60)}"`);
|
|
3057
3119
|
}
|
|
@@ -3139,7 +3201,10 @@ export class ResponseEngine {
|
|
|
3139
3201
|
// 发送文件
|
|
3140
3202
|
logger.info(`[${adapter.channelName}] Sending file via ${targetInfo.adapter.channelName}: ${resolvedPath}`);
|
|
3141
3203
|
try {
|
|
3142
|
-
|
|
3204
|
+
const targetReplyContext = isCrossChannel
|
|
3205
|
+
? { delivery: { chatType: 'private' } }
|
|
3206
|
+
: capturedReplyContext;
|
|
3207
|
+
await targetInfo.adapter.send(buildEnvelope({ taskId, channel: targetInfo.adapter.channelName, channelId: targetChannelId, agentName: agentNameForStats, replyContext: targetReplyContext }), { kind: 'result.file', filePath: resolvedPath });
|
|
3143
3208
|
this.eventBus.publish({ type: 'runner:file-sent', sessionId: session.id, filePath: resolvedPath, channel: targetInfo.adapter.channelName });
|
|
3144
3209
|
sent++;
|
|
3145
3210
|
if (isCrossChannel) {
|
|
@@ -3662,6 +3727,7 @@ export class ResponseEngine {
|
|
|
3662
3727
|
// processEventStream 已通过 renderer 发过错误时也跳过
|
|
3663
3728
|
const retryExhaustedCount = getRetryExhaustedCount(error);
|
|
3664
3729
|
const modelFallbackExhaustedMessage = getModelFallbackExhaustedMessage(error);
|
|
3730
|
+
const retryInputAlreadySubmitted = wasRetryInputAlreadySubmitted(error);
|
|
3665
3731
|
if (isUserInterrupt) {
|
|
3666
3732
|
logger.info(`[ResponseEngine] User interrupt by new_message, skip sending error message`);
|
|
3667
3733
|
}
|
|
@@ -3673,13 +3739,15 @@ export class ResponseEngine {
|
|
|
3673
3739
|
const idleSec = getLastIdleSec?.() || 0;
|
|
3674
3740
|
const userMessage = retryExhaustedCount
|
|
3675
3741
|
? formatRetryableErrorFinalMessage(error, retryExhaustedCount)
|
|
3676
|
-
:
|
|
3677
|
-
?
|
|
3678
|
-
:
|
|
3679
|
-
?
|
|
3680
|
-
:
|
|
3681
|
-
?
|
|
3682
|
-
:
|
|
3742
|
+
: retryInputAlreadySubmitted
|
|
3743
|
+
? formatRetryInputAlreadySubmittedMessage(error)
|
|
3744
|
+
: modelFallbackExhaustedMessage
|
|
3745
|
+
? modelFallbackExhaustedMessage
|
|
3746
|
+
: isTotalExecutionTimeout
|
|
3747
|
+
? `⚠️ 任务超过总执行时限(${Math.round(totalExecutionMs / 1000)}秒),已自动中断`
|
|
3748
|
+
: isTimeout
|
|
3749
|
+
? (idleSec > 0 ? `⚠️ 任务超时(${idleSec}秒无响应),已自动中断` : '⚠️ 任务超时,已自动中断')
|
|
3750
|
+
: getErrorMessage(error, undefined);
|
|
3683
3751
|
// 获取 session 用于话题回复(如果 resolveSession 已执行)
|
|
3684
3752
|
let sendOpts;
|
|
3685
3753
|
try {
|
|
@@ -3696,14 +3764,14 @@ export class ResponseEngine {
|
|
|
3696
3764
|
const errorPayload = {
|
|
3697
3765
|
kind: 'result.error',
|
|
3698
3766
|
text: userMessage,
|
|
3699
|
-
reason: retryExhaustedCount ? 'retry_exhausted' : modelFallbackExhaustedMessage ? 'model_unavailable' : isTotalExecutionTimeout ? 'total_execution_timeout' : isTimeout ? 'timeout' : errType,
|
|
3767
|
+
reason: retryExhaustedCount ? 'retry_exhausted' : retryInputAlreadySubmitted ? 'input_already_submitted' : modelFallbackExhaustedMessage ? 'model_unavailable' : isTotalExecutionTimeout ? 'total_execution_timeout' : isTimeout ? 'timeout' : errType,
|
|
3700
3768
|
};
|
|
3701
3769
|
if (!isUserInterrupt && daemonTrigger && procStatus === 'error') {
|
|
3702
3770
|
// DaemonChannel settles a trigger on the first terminal payload. Put
|
|
3703
3771
|
// the complete failure metadata first so the audit retains tool
|
|
3704
3772
|
// counts and context-recovery diagnostics even for thrown errors
|
|
3705
3773
|
// (for example when compact itself fails).
|
|
3706
|
-
await adapter.send(
|
|
3774
|
+
await adapter.send(withEnvelopeReplyContext(envelope, sendOpts), {
|
|
3707
3775
|
kind: 'status.error',
|
|
3708
3776
|
metadata: {
|
|
3709
3777
|
errorType: errType,
|
|
@@ -3716,17 +3784,17 @@ export class ResponseEngine {
|
|
|
3716
3784
|
contextRecoveryAttempts: streamResult.contextRecoveryAttempts ?? 0,
|
|
3717
3785
|
},
|
|
3718
3786
|
}).catch(() => { });
|
|
3719
|
-
await adapter.send(
|
|
3787
|
+
await adapter.send(withEnvelopeReplyContext(envelope, sendOpts), errorPayload);
|
|
3720
3788
|
}
|
|
3721
3789
|
else {
|
|
3722
|
-
await adapter.send(
|
|
3790
|
+
await adapter.send(withEnvelopeReplyContext(envelope, sendOpts), errorPayload);
|
|
3723
3791
|
}
|
|
3724
3792
|
if (!isUserInterrupt && daemonTrigger && procStatus !== 'error') {
|
|
3725
|
-
await adapter.send(
|
|
3793
|
+
await adapter.send(withEnvelopeReplyContext(envelope, sendOpts), statusPayload).catch(() => { });
|
|
3726
3794
|
}
|
|
3727
3795
|
// Proactive 可观测:catch 块的基础设施错误也透传为 thought,保证按 task_id 聚合完整
|
|
3728
3796
|
if (isProactive && adapter.capabilities?.thought) {
|
|
3729
|
-
await adapter.send(
|
|
3797
|
+
await adapter.send(withEnvelopeReplyContext(envelope, sendOpts), {
|
|
3730
3798
|
kind: 'activity.batch',
|
|
3731
3799
|
items: [{
|
|
3732
3800
|
kind: 'notice',
|
|
@@ -3916,12 +3984,10 @@ export class ResponseEngine {
|
|
|
3916
3984
|
await this.sessionManager.updateSession(session.id, { metadata: session.metadata });
|
|
3917
3985
|
}
|
|
3918
3986
|
}
|
|
3919
|
-
//
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
logger.info(`[ResponseEngine] dispatchMode sync: sessionId=${session.id} ${session.metadata?.dispatchMode ?? 'none'} -> ${message.dispatchMode}`);
|
|
3924
|
-
session.metadata = { ...(session.metadata || {}), dispatchMode: message.dispatchMode };
|
|
3987
|
+
// 同步服务端 mention_mode,供同一群会话的上下文和菜单展示使用。
|
|
3988
|
+
if (message.chatType === 'group' && message.mentionMode && session.metadata?.mentionMode !== message.mentionMode) {
|
|
3989
|
+
logger.info(`[ResponseEngine] mentionMode sync: sessionId=${session.id} ${session.metadata?.mentionMode ?? 'none'} -> ${message.mentionMode}`);
|
|
3990
|
+
session.metadata = { ...(session.metadata || {}), mentionMode: message.mentionMode };
|
|
3925
3991
|
await this.sessionManager.updateSession(session.id, { metadata: session.metadata });
|
|
3926
3992
|
}
|
|
3927
3993
|
// chatMode 策略由 agent/relation behavior 配置在处理阶段解析;此处不再写 session 级参数。
|
|
@@ -3956,7 +4022,7 @@ export class ResponseEngine {
|
|
|
3956
4022
|
*/
|
|
3957
4023
|
async processEventStream(stream, session, agent, renderer, resetTimer, shouldSuppress, proactive,
|
|
3958
4024
|
/** [迁移点4/5] 响应模式插件 + 状态,用于调 onToolUse/onComplete 钩子 */
|
|
3959
|
-
modeHooks, causation, turnLease) {
|
|
4025
|
+
modeHooks, causation, turnLease, permissionMode) {
|
|
3960
4026
|
// Per-session agent name for stats bucketing
|
|
3961
4027
|
const statsChannelKey = session.channel === 'daemon' ? session.channel : (session.metadata?.channelKey || session.channel);
|
|
3962
4028
|
const agentNameForStats = this.agentRegistry?.resolveByChannel(statsChannelKey)?.name ?? '<unknown>';
|
|
@@ -4042,6 +4108,11 @@ export class ResponseEngine {
|
|
|
4042
4108
|
}
|
|
4043
4109
|
}
|
|
4044
4110
|
}
|
|
4111
|
+
else if (event.type === 'notice') {
|
|
4112
|
+
for (const callId of event.closedToolCallIds ?? []) {
|
|
4113
|
+
openToolUseIds.delete(callId);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4045
4116
|
else if (event.type === 'complete') {
|
|
4046
4117
|
assistantUuid = event.assistantUuid ?? assistantUuid;
|
|
4047
4118
|
protocolIncompleteReason = event.protocolIncompleteReason ?? protocolIncompleteReason;
|
|
@@ -4278,6 +4349,9 @@ export class ResponseEngine {
|
|
|
4278
4349
|
sessionId: session.id,
|
|
4279
4350
|
toolName: event.name,
|
|
4280
4351
|
input: event.input,
|
|
4352
|
+
...(event.callId ? { callId: event.callId } : {}),
|
|
4353
|
+
...(session.selfAID ? { agentAid: session.selfAID } : {}),
|
|
4354
|
+
...(permissionMode ? { permissionMode } : {}),
|
|
4281
4355
|
timestamp: Date.now(),
|
|
4282
4356
|
causation,
|
|
4283
4357
|
});
|
|
@@ -4293,6 +4367,7 @@ export class ResponseEngine {
|
|
|
4293
4367
|
}
|
|
4294
4368
|
// [迁移点4] 工具汇报提醒:由 ProactiveMode.onToolUse 实现
|
|
4295
4369
|
if (modeHooks?.mode?.onToolUse) {
|
|
4370
|
+
const proactiveState = modeHooks.state.get('proactive');
|
|
4296
4371
|
modeHooks.mode.onToolUse({
|
|
4297
4372
|
session,
|
|
4298
4373
|
state: modeHooks.state,
|
|
@@ -4300,7 +4375,7 @@ export class ResponseEngine {
|
|
|
4300
4375
|
toolInput: event.input || {},
|
|
4301
4376
|
injectToModel: (text) => { agent.injectUserMessage?.(session.id, text); },
|
|
4302
4377
|
getQueueLength: () => this.messageQueue?.getQueueLength(session.id) ?? 0,
|
|
4303
|
-
isSendCommand: (toolName, toolInput) =>
|
|
4378
|
+
isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, proactiveState?.chatType === 'group' ? 'group' : 'private', session.selfAID),
|
|
4304
4379
|
logger,
|
|
4305
4380
|
});
|
|
4306
4381
|
}
|
|
@@ -4326,6 +4401,9 @@ export class ResponseEngine {
|
|
|
4326
4401
|
toolName: event.name,
|
|
4327
4402
|
isError: event.isError,
|
|
4328
4403
|
agentName: agentNameForStats,
|
|
4404
|
+
...(event.callId ? { callId: event.callId } : {}),
|
|
4405
|
+
...(session.selfAID ? { agentAid: session.selfAID } : {}),
|
|
4406
|
+
...(permissionMode ? { permissionMode } : {}),
|
|
4329
4407
|
timestamp: Date.now(),
|
|
4330
4408
|
causation,
|
|
4331
4409
|
});
|
|
@@ -4342,6 +4420,12 @@ export class ResponseEngine {
|
|
|
4342
4420
|
renderer.addToolResult(event.name || '\u5de5\u5177', true, event.result, undefined, event.callId, event.durationMs, cachedDesc);
|
|
4343
4421
|
}
|
|
4344
4422
|
}
|
|
4423
|
+
// 非终态运行时告警走 status notify,不进入 activity/thought 历史。
|
|
4424
|
+
if (event.type === 'notice' && event.severity === 'warn') {
|
|
4425
|
+
await renderer.sendWarningStatus(event.message, event.subtype, {
|
|
4426
|
+
...(event.missingItems !== undefined && { missingItems: event.missingItems }),
|
|
4427
|
+
});
|
|
4428
|
+
}
|
|
4345
4429
|
// 运行时错误(Codex: turn.failed / item error)
|
|
4346
4430
|
if (event.type === 'error') {
|
|
4347
4431
|
logger.warn(`[ResponseEngine] error event: ${event.errorType}: ${event.error}`);
|